Return-Path: Delivered-To: apmail-incubator-wink-commits-archive@minotaur.apache.org Received: (qmail 57790 invoked from network); 23 Jun 2009 10:14:33 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 23 Jun 2009 10:14:33 -0000 Received: (qmail 74101 invoked by uid 500); 23 Jun 2009 10:14:43 -0000 Delivered-To: apmail-incubator-wink-commits-archive@incubator.apache.org Received: (qmail 73992 invoked by uid 500); 23 Jun 2009 10:14:43 -0000 Mailing-List: contact wink-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: wink-dev@incubator.apache.org Delivered-To: mailing list wink-commits@incubator.apache.org Delivered-To: moderator for wink-commits@incubator.apache.org Received: (qmail 96369 invoked by uid 99); 23 Jun 2009 05:42:53 -0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r787557 [7/12] - in /incubator/wink/contrib/ibm-jaxrs: ./ lib/ src/ src/com/ src/com/ibm/ src/com/ibm/ws/ src/com/ibm/ws/jaxrs/ src/com/ibm/ws/jaxrs/annotations/ src/com/ibm/ws/jaxrs/context/ src/com/ibm/ws/jaxrs/core/ src/com/ibm/ws/jaxrs/... Date: Tue, 23 Jun 2009 05:41:55 -0000 To: wink-commits@incubator.apache.org From: ngallardo@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090623054202.18ED62388965@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/JAXRSOperationResourceValidator.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/JAXRSOperationResourceValidator.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/JAXRSOperationResourceValidator.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/JAXRSOperationResourceValidator.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,352 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.validation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import javax.ws.rs.FormParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.cxf.jaxrs.model.ClassResourceInfo; +import org.apache.cxf.jaxrs.model.OperationResourceInfo; +import org.apache.cxf.jaxrs.utils.AnnotationUtils; + +import com.ibm.ws.jaxrs.i18n.Messages; + +/** + * This class will be responsible for validating a set of OperationResourceInfo + * objects created from a particular resource class. The validations done by + * this class are those listed in the JAX-RS specification. + * + */ +public class JAXRSOperationResourceValidator implements OperationResourceValidator { + + public JAXRSOperationResourceValidator() { + + } + + /** + * This method will validate the list of OperationResourceInfo objects that + * the validator was supplied. A list of ValidationMessage objects will be + * returned containing any necessary messages. + * + */ + public List validate(List infos) { + List messages = new ArrayList(); + + checkMultipleDesignators(infos, messages); + for (OperationResourceInfo info : infos) { + checkMethodVisibility(info, messages); + checkParams(info, messages); + checkFormParameters(info, messages); + } + + return messages; + } + + /** + * This method will check to ensure that multiple methods are not annotated + * with the same request designator unless one has the @Path annotation. It + * will also check to see that methods annotated with a request designator + * and an @Path annotation have different path values. + * + * @param messages + */ + void checkMultipleDesignators(List infos, List messages) { + + Map> methodTypeMap = new HashMap>(); + + Map> pathMap = new HashMap>(); + + // first initialize the mapping of http methods and paths to OperationResourceInfos + // we only create the mapping for methods that do not have the @Path annotation + for (OperationResourceInfo info : infos) { + String httpMethod = info.getHttpMethod(); + if (httpMethod != null && !"".equals(httpMethod) + && !info.isSubResourceLocator() + && !info.isSubResourceMethod()) { + List values = methodTypeMap + .get(httpMethod); + if (values == null) { + values = new ArrayList(); + methodTypeMap.put(httpMethod, values); + } + values.add(info); + } + String pathValue = info.getPath(); + if (pathValue != null) { + String key = pathValue + + ":" + + (info.getHttpMethod() != null ? info.getHttpMethod() + : ""); + List values = pathMap.get(key); + if (values == null) { + values = new ArrayList(); + pathMap.put(key, values); + } + values.add(info); + } + } + + // now make sure that for each method, we don't have more than + // one OperationResourceInfo + if (!methodTypeMap.isEmpty()) { + Iterator>> entries = methodTypeMap + .entrySet().iterator(); + while (entries.hasNext()) { + Entry> entry = entries + .next(); + List values = entry.getValue(); + String httpMethod = entry.getKey(); + if (values != null && values.size() > 1) { + int size = values.size(); + boolean isWarningEmitted = false; + for (int firstIndex = 0; firstIndex < size; ++firstIndex) { + for (int secondIndex = firstIndex + 1; secondIndex < size; ++secondIndex) { + boolean isConsumesCompatible = false; + Set firstConsumesSet = new HashSet( + values.get(firstIndex).getConsumes()); + Set secondConsumesSet = new HashSet( + values.get(secondIndex).getConsumes()); + for (MediaType firstConsumes : firstConsumesSet) { + for (MediaType secondConsumes : secondConsumesSet) { + if (firstConsumes + .isCompatible(secondConsumes)) { + isConsumesCompatible = true; + } + } + } + + boolean isProducesCompatible = false; + Set firstProducesSet = new HashSet( + values.get(firstIndex).getProduces()); + Set secondProducesSet = new HashSet( + values.get(secondIndex).getProduces()); + for (MediaType firstProduces : firstProducesSet) { + for (MediaType secondProduces : secondProducesSet) { + if (firstProduces + .isCompatible(secondProduces)) { + isProducesCompatible = true; + } + } + } + + if (isConsumesCompatible && isProducesCompatible) { + isWarningEmitted = true; + break; + } + } + } + + if (isWarningEmitted) { + ValidationMessage message = new ValidationMessage( + ValidationMessage.Type.WARNING); + StringBuffer sb = new StringBuffer(); + for (OperationResourceInfo value : values) { + sb.append(" ").append( + value.getMethodToInvoke().getName()); + } + String msg = Messages.getMessage( + "multipleRequestDesignators", httpMethod, sb + .toString(), values.get(0) + .getClassResourceInfo() + .getServiceClass().getName()); + message.setMessage(msg); + messages.add(message); + } + } + } + } + + // let's make sure we don't have duplicate entries with the same @Path value + // unless the request designator is different + if (!pathMap.isEmpty()) { + Iterator>> entries = pathMap + .entrySet().iterator(); + while (entries.hasNext()) { + Entry> entry = entries + .next(); + List values = entry.getValue(); + String path = entry.getKey(); + if (values != null && values.size() > 1) { + ValidationMessage message = new ValidationMessage( + ValidationMessage.Type.WARNING); + StringBuffer sb = new StringBuffer(); + path = path.substring(0, path.indexOf(":")); + for (OperationResourceInfo value : values) { + sb.append(" ").append( + value.getMethodToInvoke().getName()); + } + String msg = Messages.getMessage("pathAnnotFail01", path, + sb.toString(), values.get(0).getClassResourceInfo() + .getServiceClass().getName()); + message.setMessage(msg); + messages.add(message); + } + } + } + } + + /** + * This method will check to see if a method is annotated with a request + * designator or an @Path annotation and is not public. If this condition is + * found, a warning will be created. + * + */ + void checkMethodVisibility(OperationResourceInfo info, List messages) { + Method method = info.getMethodToInvoke(); + boolean pathFound = info.getPath() != null; + + // if a request designator or @Path annotation is found, check the + // visiblity + if ((info.getHttpMethod() != null && !"".equals(info.getHttpMethod())) + || pathFound) { + if (!Modifier.isPublic(method.getModifiers())) { + ValidationMessage message = new ValidationMessage( + ValidationMessage.Type.WARNING); + message + .setMessage(Messages.getMessage("methodNotPublic", + method.getName(), method.getDeclaringClass() + .getName())); + messages.add(message); + } + } + + } + + /** + * This method will check that a given resource method does not contain more + * than one entity parameter. If more than one entity parameter is found, an + * error will be created. + * + */ + void checkParams(OperationResourceInfo info, List messages) { + Method method = info.getMethodToInvoke(); + Class[] params = method.getParameterTypes(); + Annotation[][] allParamAnnots = method.getParameterAnnotations(); + int nonAnnotatedParams = 0; + for (int i = 0; i < params.length; ++i) { + if (i >= allParamAnnots.length) { + nonAnnotatedParams++; + } else { + Annotation[] paramAnnots = allParamAnnots[i]; + + // indicates whether or not we find an appropriate annotation + // on the parameter + boolean annotFound = false; + for (Annotation paramAnnot : paramAnnots) { + if (AnnotationUtils.isParamAnnotationClass(paramAnnot + .annotationType())) { + annotFound = true; + break; + } + } + + // if we didn't find one, update our count of non-annotated + // params + if (!annotFound) { + nonAnnotatedParams++; + } + } + } + + if (nonAnnotatedParams > 1) { + ValidationMessage message = new ValidationMessage( + ValidationMessage.Type.ERROR); + message.setMessage(Messages.getMessage("invalidEntityParam00", + method.getName(), method.getDeclaringClass().getName())); + messages.add(message); + } + } + + void checkFormParameters(OperationResourceInfo info, List messages) { + ClassResourceInfo resource = info.getClassResourceInfo(); + boolean foundFormParam = false; + if (!foundFormParam) { + foundFormParam = isFormParamFound(resource.getParameterMethods()); + } + + if (!foundFormParam) { + foundFormParam = isFormParamFound(resource.getParameterFields()); + } + + Method m = info.getAnnotatedMethod(); + Annotation[][] paramAnnotations = m.getParameterAnnotations(); + Class typeOfEntity = null; + for (int c = 0; c < paramAnnotations.length; ++c) { + if (paramAnnotations[c].length == 0) { + // found entity + typeOfEntity = m.getParameterTypes()[c]; + } else { + foundFormParam = foundFormParam ? true + : findFormParam(paramAnnotations[c]); + } + } + + if (foundFormParam) { + if (typeOfEntity != null) { + if (!MultivaluedMap.class.isAssignableFrom(typeOfEntity)) { + ValidationMessage message = new ValidationMessage( + ValidationMessage.Type.ERROR); + String msg = Messages.getMessage("invalidEntityParam01", m + .getName(), m.getDeclaringClass().getName()); + message.setMessage(msg); + messages.add(message); + } + } + } + + } + + private static boolean isFormParamFound(List annotatedElements) { + if (annotatedElements.size() > 0) { + for (AnnotatedElement e : annotatedElements) { + Annotation[] annotations = e.getAnnotations(); + if (annotations.length != 0) { + if (findFormParam(annotations)) { + return true; + } + } + } + } + return false; + } + + private static boolean findFormParam(Annotation[] annotations) { + for (Annotation a : annotations) { + if (FormParam.class.isAssignableFrom(a.annotationType())) { + return true; + } + } + return false; + } +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/OperationResourceValidator.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/OperationResourceValidator.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/OperationResourceValidator.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/OperationResourceValidator.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.validation; + +import java.util.List; + +import org.apache.cxf.jaxrs.model.OperationResourceInfo; + +/** + * This interface will be implemented by classes that provide validation + * function on a set of OperationResourceInfo objects. + * + */ +public interface OperationResourceValidator { + + /** + * This method will validate the set of OperationResourceInfo objects that + * are provided. It will return a list of ValidationMessage objects that + * represent any validation error, warning, or debug messages. + * + */ + public List validate(List infos); + +} \ No newline at end of file Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/RESTValidator.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/RESTValidator.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/RESTValidator.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/RESTValidator.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.validation; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.cxf.jaxrs.model.ClassResourceInfo; +import org.apache.cxf.jaxrs.model.OperationResourceInfo; + +import com.ibm.ws.jaxrs.i18n.Messages; +import com.ibm.ws.jaxrs.model.JAXRSInfoOutput; +import com.ibm.ws.jaxrs.validation.ValidationMessage.Type; + +/** + * This class will be responsible for encapsulating the validation that + * takes place on a given instance of RESTInfoOutput. This will drive + * the validation of resource classes and methods. + * + */ +public class RESTValidator { + + private static Log log = LogFactory.getLog(RESTValidator.class); + + private static List classValidators = new ArrayList(); + + private static List opValidators = new ArrayList(); + + // add the default validators + static { + classValidators.add(new JAXRSClassResourceValidator()); + opValidators.add(new JAXRSOperationResourceValidator()); + } + + /** + * This will drive the call of the validators for resource classes and methods. + * + */ + public static void validateOutput(JAXRSInfoOutput output) throws Exception { + List messages = new ArrayList(); + List classInfos = output.getClassInfoList(); + + if (classInfos != null && !classInfos.isEmpty()) { + + // first drive the validation of the resource classes + for (ClassResourceValidator classValidator : classValidators) { + messages.addAll(classValidator.validate(classInfos)); + } + + // now drive the resolution of resource methods + for (ClassResourceInfo classInfo : classInfos) { + List opInfos = new ArrayList( + classInfo.getMethodDispatcher() + .getOperationResourceInfos()); + if (!opInfos.isEmpty()) { + for (OperationResourceValidator opValidator : opValidators) { + messages.addAll(opValidator.validate(opInfos)); + } + } + } + } + + // now let's do the necessary reporting + boolean errorFound = false; + for (ValidationMessage message : messages) { + Type messageType = message.getMessageType(); + if (messageType.equals(Type.ERROR)) { + errorFound = true; + } + if (messageType.equals(Type.ERROR)) { + if (log.isErrorEnabled()) { + log.error(message.getMessage()); + } + } else if (messageType.equals(Type.WARNING)) { + if (log.isWarnEnabled()) { + log.warn(message.getMessage()); + } + } else if (messageType.equals(Type.DEBUG)) { + if (log.isDebugEnabled()) { + log.debug(message.getMessage()); + } + } + } + + if (errorFound) { + throw new ResourceValidationException(Messages + .getMessage("genericValidationError")); + } + + } + +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/ResourceValidationException.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/ResourceValidationException.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/ResourceValidationException.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/ResourceValidationException.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.validation; + +/** + * This is a custom exception classes for validation errors that are found + * when processing resource classes and methods. + * + */ +public class ResourceValidationException extends Exception { + + private static final long serialVersionUID = -4863356847734034433L; + + public ResourceValidationException() { + super(); + } + + public ResourceValidationException(String message, Throwable cause) { + super(message, cause); + } + + public ResourceValidationException(String message) { + super(message); + } + + public ResourceValidationException(Throwable cause) { + super(cause); + } + +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/ValidationMessage.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/ValidationMessage.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/ValidationMessage.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/validation/ValidationMessage.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.validation; + +/** + * This class will be used to represent a message that is created + * during validation of REST classes. + * + */ +public class ValidationMessage { + + // indicates the type of message the instance represents + public enum Type { + ERROR, WARNING, DEBUG + } + + private String message; + + private Type messageType; + + public ValidationMessage(Type messageType) { + this.messageType = messageType; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Type getMessageType() { + return messageType; + } + + public void setMessageType(Type messageType) { + this.messageType = messageType; + } + +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/RESTServlet.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/RESTServlet.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/RESTServlet.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/RESTServlet.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,439 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.cxf.jaxrs.impl.MetadataMap; +import org.apache.cxf.jaxrs.utils.JAXRSUtils; + +import com.ibm.ws.jaxrs.context.ContextConstants; +import com.ibm.ws.jaxrs.context.RESTContext; +import com.ibm.ws.jaxrs.engine.RESTEngine; +import com.ibm.ws.jaxrs.integration.ApplicationProvider; +import com.ibm.ws.jaxrs.integration.DefaultJAXRSProviderCacheProvider; +import com.ibm.ws.jaxrs.integration.IntegrationRegistry; +import com.ibm.ws.jaxrs.integration.JAXRSProviderCacheProvider; +import com.ibm.ws.jaxrs.integration.MetaDataProvider; +import com.ibm.ws.jaxrs.integration.OptionsResponseProvider; +import com.ibm.ws.jaxrs.integration.ResponseWriter; +import com.ibm.ws.jaxrs.provider.JAXBOptionsResponseProvider; + +/** + * This is the servlet that will receive REST requests and route them as needed + * to the runtime. + * + */ +public class RESTServlet extends HttpServlet { + + private static final long serialVersionUID = 41205902322406552L; + + private static final Log log = LogFactory.getLog(RESTServlet.class); + + private boolean metadataInitialized = false; + + private ServletConfig servletConfig; + + public RESTServlet() { + super(); + } + + // unit testing constructor + public RESTServlet(Class... resourceClasses) throws ServletException { + buildRESTInfo(null, resourceClasses); + } + + @Override + public void doDelete(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + if (log.isDebugEnabled()) { + log.debug("Received DELETE request for: " + + req.getRequestURL().toString()); + } + processHttpRequest(req, resp); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + if (log.isDebugEnabled()) { + log.debug("Received GET request for: " + + req.getRequestURL().toString()); + } + processHttpRequest(req, resp); + } + + @Override + public void doOptions(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + if (log.isDebugEnabled()) { + log.debug("Received OPTIONS request for: " + + req.getRequestURL().toString()); + } + processHttpRequest(req, resp); + } + + @Override + public void doHead(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + if (log.isDebugEnabled()) { + log.debug("Received HEAD request for: " + + req.getRequestURL().toString()); + } + super.doHead(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + if (log.isDebugEnabled()) { + log.debug("Received POST request for: " + + req.getRequestURL().toString()); + } + processHttpRequest(req, resp); + } + + @Override + public void doPut(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + if (log.isDebugEnabled()) { + log.debug("Received PUT request for: " + + req.getRequestURL().toString()); + } + processHttpRequest(req, resp); + } + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + this.servletConfig = config; + try { + buildRESTInfo(config, new Class[] {}); + } catch (ServletException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e.getMessage(), e); + } + } + + @Override + public void destroy() { + IntegrationRegistry.removeIntegrationProvider(servletConfig, null); + } + + /** + * This method will initialize the needed REST metadata that will be used to + * serve incoming request to this servlet. + * + */ + void buildRESTInfo(ServletConfig config, Class... resourceClasses) + throws ServletException { + if (!metadataInitialized) { + try { + ApplicationProvider appProvider = new ServletApplicationProvider( + config, Thread.currentThread().getContextClassLoader(), + resourceClasses); + IntegrationRegistry.addIntegrationProvider(config, + ApplicationProvider.class, appProvider); + metadataInitialized = true; + } catch (Exception e) { + throw new ServletException(e.getMessage(), e); + } + + // add servlet response writer + IntegrationRegistry.addIntegrationProvider(servletConfig, + ResponseWriter.class, ServletResponseWriter.getInstance()); + + // add our class responsible for caching + IntegrationRegistry.addIntegrationProvider(servletConfig, + MetaDataProvider.class, new ServletMetadataProvider()); + + // add provider factory cache + IntegrationRegistry.addIntegrationProvider(servletConfig, + JAXRSProviderCacheProvider.class, + new DefaultJAXRSProviderCacheProvider()); + + // add our OPTIONS response provider + IntegrationRegistry.addIntegrationProvider(servletConfig, + OptionsResponseProvider.class, JAXBOptionsResponseProvider + .getInstance(), true); + + } + } + + /* + * This method drives the request processing for all invocation types. + */ + private void processHttpRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException { + try { + RESTContext context = new RESTContext(); + initContext(request, response, context); + RESTEngine.invoke(context); + } catch (Throwable e) { + throw new ServletException(e.getMessage(), e); + } + } + + /** + * This method will initialize various properties on the RESTContext that are + * derived from the current request. + */ + void initContext(HttpServletRequest request, HttpServletResponse response, RESTContext context) + throws IOException, Exception { + + // set the request and response on the context + context.setProperty(ContextConstants.SERVLET_REQUEST, request); + context.setProperty(ContextConstants.SERVLET_RESPONSE, response); + + // set the ServletContext and ServletConfig on the context + try { + context.setProperty(ContextConstants.SERVLET_CONTEXT, servletConfig + .getServletContext()); + } catch (Throwable t) { + /* this is only a test framework issue */ + } + + context.setProperty(ContextConstants.SERVLET_CONFIG, servletConfig); + + context.setProperty(ContextConstants.HTTP_ACCEPT_HEADER, request + .getHeader(HttpHeaders.ACCEPT)); + context.setProperty(ContextConstants.HTTP_METHOD, request.getMethod()); + + context.setProperty(ContextConstants.CONTENT_TYPE, request + .getContentType()); + context.setProperty(ContextConstants.HTTP_PATH_INFO, + normalizeURIValue(request.getPathInfo())); + context.setProperty(ContextConstants.HTTP_REQUEST_ENCODING, request + .getCharacterEncoding()); + context.setProperty(ContextConstants.HTTP_REQUEST_URI, + normalizeURIValue(request.getRequestURI())); + context.setProperty(ContextConstants.HTTP_REQUEST_URL, + normalizeURIValue(request.getRequestURL().toString())); + context.setProperty(ContextConstants.HTTP_REQUEST_CONTEXT_PATH, request + .getContextPath()); + context.setProperty(ContextConstants.HTTP_QUERY_STRING, request + .getQueryString()); + context.setProperty(ContextConstants.AUTHENTICATION_SCHEME, request + .getAuthType()); + context.setProperty(ContextConstants.USER_PRINCIPAL, request + .getUserPrincipal()); + context.setProperty(ContextConstants.REQUEST_SECURE, Boolean + .valueOf(request.isSecure())); + context.setProperty(ContextConstants.CONTENT_LANGUAGE, request + .getHeader(HttpHeaders.CONTENT_LANGUAGE)); + context.setProperty(ContextConstants.HTTP_REQUEST_LOCALE, request + .getLocale()); + context.setProperty(ContextConstants.REQUEST_INPUT_STREAM, request + .getInputStream()); + context.setProperty(ContextConstants.RESPONSE_OUTPUT_STREAM, response + .getOutputStream()); + context.setProperty(ContextConstants.CONTEXT_CLASSLOADER, Thread + .currentThread().getContextClassLoader()); + context.setProperty(ContextConstants.METADATA_KEY, + servletConfig != null ? servletConfig.getServletName() : null); + + // set the config file URL if the servlet-config parameter is set + String fileLoc = servletConfig != null ? servletConfig + .getInitParameter("com.ibm.ws.jaxrs.ConfigFile") : null; + if (log.isDebugEnabled()) { + log.debug("IBM config file init parameter value is : " + fileLoc); + } + if (fileLoc != null && !"".equals(fileLoc)) { + File file = new File(fileLoc); + URL url = null; + if (!file.exists()) { + url = Thread.currentThread().getContextClassLoader() + .getResource(fileLoc); + if (url == null) { + if (!fileLoc.startsWith("/")) { + fileLoc = "/" + fileLoc; + } + url = servletConfig.getServletContext() + .getResource(fileLoc); + if (log.isDebugEnabled()) { + log.debug("IBM config file on servlet context path : " + + url); + } + } else { + if (log.isDebugEnabled()) { + log + .debug("IBM config file exists on classloader path : " + + url); + } + } + } else { + url = file.toURI().toURL(); + if (log.isDebugEnabled()) { + log.debug("IBM config file exists on file system path : " + + url); + } + } + + if (log.isDebugEnabled()) { + log.debug("IBM config file setting is set to URL : " + url); + } + context.setProperty(ContextConstants.CONFIG_FILE_URL, url); + } + + context.setProperty(ContextConstants.HTTP_PATH_RESOURCE, + getPath(request)); + + // initialize the query string values + MultivaluedMap queryValuesDecoded = JAXRSUtils + .getStructuredParams(request.getQueryString(), "&", true); + MultivaluedMap queryValuesEncoded = JAXRSUtils + .getStructuredParams(request.getQueryString(), "&", false); + context.setProperty(ContextConstants.QUERY_STRING_DECODED_VALUES, + queryValuesDecoded); + context.setProperty(ContextConstants.QUERY_STRING_ENCODED_VALUES, + queryValuesEncoded); + + // initialize the HTTP headers + Map> headers = getHeaders(request); + MultivaluedMap headerValues = new MetadataMap( + headers); + context.setProperty(ContextConstants.HTTP_HEADER_VALUES, headerValues); + + // initialize the matrix parameters (both encoded/decoded) + context + .setProperty(ContextConstants.MATRIX_PARAMETER_VALUES_DECODED, + JAXRSUtils.getMatrixParams(URI.create( + request.getRequestURL().toString()) + .getRawPath(), true)); + context.setProperty(ContextConstants.MATRIX_PARAMETER_VALUES_ENCODED, + JAXRSUtils + .getMatrixParams(URI.create( + request.getRequestURL().toString()) + .getRawPath(), false)); + + // initialize the cookie array, convert the servlet cookies into JAX-RS style cookies + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + Map cookieMap = new HashMap(); + for (Cookie cookie : cookies) { + javax.ws.rs.core.Cookie jaxrsCookie = new javax.ws.rs.core.Cookie( + cookie.getName(), cookie.getValue(), cookie.getPath(), + cookie.getDomain(), cookie.getVersion()); + cookieMap.put(cookie.getName(), jaxrsCookie); + } + context.setProperty(ContextConstants.HTTP_COOKIES, cookieMap); + } + + // store all the acceptable Locales + List locales = new ArrayList(); + Enumeration localeEnum = request.getLocales(); + if (localeEnum != null) { + while (localeEnum.hasMoreElements()) { + locales.add(localeEnum.nextElement()); + } + context.setProperty(ContextConstants.HTTP_REQUEST_LOCALES, locales); + } + + context.setProperty(ContextConstants.INTEGRATION_REGISTRATION_KEY, + servletConfig); + } + + /** + * This method will return the path for the current request. This will contain + * all the parts after the context path, and the string will be encoded. + * + */ + private String getPath(HttpServletRequest request) { + // normalize this value in the rare cases where someone has + // (/someresourcepath/../someresourcepath) + String path = URI.create(request.getRequestURL().toString()) + .normalize().getRawPath(); + if (request.getContextPath() != null + && path.indexOf(request.getContextPath()) != -1) { + String cp = request.getContextPath(); + int cpLength = cp.length(); + path = path.substring(path.indexOf(cp) + cpLength); + } + + return path; + } + + /** + * Normalize a given URI value and return it. + * @param value the value + * @return the normalized value if possible, if not possible, return what was sent in + */ + private String normalizeURIValue(String value) { + if (value == null) { + return null; + } + try { + URI normalizedURI = new URI(value).normalize(); + return normalizedURI.toString(); + } catch (URISyntaxException e) { + // could this happen if the request message was made to the runtime already? + // maybe someone set up the request wrong + } + return value; + } + + /** + * This method retrieves all the headers and establishes a multivalued map + * that is stored on the RESTContext. + * + */ + private Map> getHeaders(HttpServletRequest request) { + Map> headers = new HashMap>(); + Enumeration headerNames = request.getHeaderNames(); + if (headerNames != null) { + while (headerNames.hasMoreElements()) { + String headerName = (String) headerNames.nextElement(); + List headerValueList = new ArrayList(); + Enumeration headerValues = request.getHeaders(headerName); + if (headerValues != null) { + while (headerValues.hasMoreElements()) { + headerValueList + .add((String) headerValues.nextElement()); + } + } + headerName = headerName.toLowerCase(); + headers.put(headerName, headerValueList); + } + } + return headers; + } +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletApplicationProvider.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletApplicationProvider.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletApplicationProvider.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletApplicationProvider.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.ws.rs.core.Application; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.ibm.ws.jaxrs.context.ContextConstants; +import com.ibm.ws.jaxrs.context.RESTContext; +import com.ibm.ws.jaxrs.i18n.Messages; +import com.ibm.ws.jaxrs.integration.ApplicationProvider; +import com.ibm.ws.jaxrs.integration.DefaultApplication; +import com.ibm.ws.jaxrs.utils.ClassUtils; +import com.ibm.ws.jaxrs.web.asm.JAXRSAnnotationScanner; + +/** + * This is our servlet-based implementation of the ApplicationProvider. It + * will look for an init-parameter in the config that specifies the name + * of the JAX-RS Application subclass. + * + */ +public class ServletApplicationProvider implements ApplicationProvider { + + private static final Log log = LogFactory + .getLog(ServletApplicationProvider.class); + + private ServletConfig servletConfig; + + private ClassLoader classLoader; + + private Class[] resourceClasses; + + public ServletApplicationProvider(ServletConfig servletConfig, ClassLoader classLoader, Class... resourceClasses) { + this.servletConfig = servletConfig; + this.classLoader = classLoader; + this.resourceClasses = resourceClasses; + } + + /** + * Returns the Application subclass that is specified in the init-param + */ + public List getApplicationClasses(RESTContext context) + throws Exception { + List applications = new ArrayList(); + if (servletConfig == null && resourceClasses != null) { + if (log.isDebugEnabled()) { + log.debug("Generating application wrapper class"); + } + Set> classes = new HashSet>(); + for (Class resourceClass : resourceClasses) { + classes.add(resourceClass); + } + applications.add(new DefaultApplication(classes, null)); + } else { + String appClassName = servletConfig + .getInitParameter("javax.ws.rs.Application"); + boolean appClassesFound = false; + if (appClassName != null && !"".equals(appClassName)) { + try { + Class application = classLoader.loadClass(appClassName); + Application app = (Application) ClassUtils + .newInstance(application); + if (app != null) { + + // only process this Application subclass if it gives a non-empty + // set of singletons or classes + if ((app.getClasses() != null && !app.getClasses() + .isEmpty()) + || (app.getSingletons() != null && !app + .getSingletons().isEmpty())) { + if (log.isDebugEnabled()) { + log + .debug("Loaded javax.ws.rs.core.Application subclass " + + "instance: " + appClassName); + } + applications.add(app); + appClassesFound = true; + } + } + + } catch (Exception e) { + log.error(Messages.getMessage("processAppError"), e); + throw e; + } + } + if (!appClassesFound) { + if (log.isDebugEnabled()) { + log + .debug("Classes in the web app will be scanned for JAX-RS annotations"); + } + ServletContext servletContext = servletConfig + .getServletContext(); + if (servletContext != null) { + ClassLoader cl = (ClassLoader) context + .getProperty(ContextConstants.CONTEXT_CLASSLOADER); + List classStreams = getInputStreamsFromContext( + servletContext, cl); + JAXRSAnnotationScanner scanner = new JAXRSAnnotationScanner(); + Application app = scanner.scan(classStreams, cl); + if (app != null && app.getClasses() != null + && !app.getClasses().isEmpty()) { + if (log.isDebugEnabled()) { + log + .debug("Found JAX-RS classes during annotation scanning"); + } + applications.add(app); + } + } + } + } + return applications; + } + + /* + * Collects InputStreams to .class files within a web application. + */ + List getInputStreamsFromContext(ServletContext servletContext, ClassLoader cl) { + List classStreams = new ArrayList(); + List classResourcePaths = new ArrayList(); + getClassResourcePaths(servletContext, "/WEB-INF/classes/", + classResourcePaths); + for (String classResourcePath : classResourcePaths) { + InputStream is = cl.getResourceAsStream(classResourcePath); + if (is != null) { + classStreams.add(is); + } + } + return classStreams; + } + + /* + * Recursive method to gather the full path to all .class files found + * in the WEB-INF/classes folder of a web application. + */ + void getClassResourcePaths(ServletContext servletContext, String path, List classResourcePaths) { + Set paths = servletContext.getResourcePaths(path); + if (paths != null) { + for (Object obj : paths) { + String p = (String) obj; + if (p.endsWith("/")) { + getClassResourcePaths(servletContext, p, classResourcePaths); + } else if (p.endsWith(".class")) { + classResourcePaths.add(p); + } + } + } + } +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletMetadataProvider.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletMetadataProvider.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletMetadataProvider.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletMetadataProvider.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web; + +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.core.Application; + +import com.ibm.ws.jaxrs.context.ContextConstants; +import com.ibm.ws.jaxrs.context.RESTContext; +import com.ibm.ws.jaxrs.i18n.Messages; +import com.ibm.ws.jaxrs.integration.ApplicationProvider; +import com.ibm.ws.jaxrs.integration.IntegrationRegistry; +import com.ibm.ws.jaxrs.integration.MetaDataProvider; +import com.ibm.ws.jaxrs.metadata.RESTMetaData; +import com.ibm.ws.jaxrs.model.ApplicationProcessor; +import com.ibm.ws.jaxrs.model.JAXRSInfoOutput; + +/** + * This is an implementation of the MetadataCacheProvider that will be used + * in the common component web container environment. It may be overridden + * by consumers of the REST runtime to provide a more robust cache mechanism. + * + */ +public class ServletMetadataProvider implements MetaDataProvider { + + private final Map cache; + + public ServletMetadataProvider() { + cache = new ConcurrentHashMap(); + } + + public RESTMetaData getRESTMetaData(String metadataKey) { + return cache.get(metadataKey); + } + + public RESTMetaData buildRESTMetaData(String metadataKey, RESTContext context) + throws Exception { + synchronized (cache) { + RESTMetaData metaData = cache.get(metadataKey); + if (metaData == null) { + metaData = buildMetaData(context); + cache.put(metadataKey, metaData); + } + return metaData; + } + } + + /** + * This method will be responsible for driving the calls to build up an instance + * of RESTMetaData for the current request. + * + */ + private RESTMetaData buildMetaData(RESTContext context) throws Exception { + RESTMetaData metaData = new RESTMetaData(); + + // we need to get an ApplicationProvider so we know the source of the metadata + ApplicationProvider appProvider = (ApplicationProvider) IntegrationRegistry + .getIntegrationProvider( + context + .getProperty(ContextConstants.INTEGRATION_REGISTRATION_KEY), + ApplicationProvider.class); + if (appProvider == null) { + throw new RuntimeException(Messages.getMessage("invalidMetaData00", + (String) context + .getProperty(ContextConstants.HTTP_PATH_INFO))); + } + + List applications = appProvider + .getApplicationClasses(context); + + URL configURL = (URL) context + .getProperty(ContextConstants.CONFIG_FILE_URL); + + ClassLoader classLoader = (ClassLoader) context + .getProperty(ContextConstants.CONTEXT_CLASSLOADER); + + // list must be non-null and non-empty + if ((applications == null || applications.isEmpty()) + && configURL == null) { + throw new IllegalArgumentException(Messages.getMessage( + "invalidMetaData01", appProvider.getClass().getName())); + } + + if (applications != null && !applications.isEmpty()) { + for (Application application : applications) { + createMetadata(application, configURL, classLoader, metaData); + } + } else { + createMetadata(null, configURL, classLoader, metaData); + } + + return metaData; + } + + /** + * Method to drive the building and storing of metadata for the runtime. + */ + private void createMetadata(Application application, URL configURL, ClassLoader classLoader, RESTMetaData metaData) + throws Exception { + ApplicationProcessor processor = new ApplicationProcessor(configURL, + classLoader); + JAXRSInfoOutput output = processor.processApplication(application); + + // now add both the resource and provider information + if (output.getClassInfoList() != null) { + metaData.getClassInfoList().addAll(output.getClassInfoList()); + } + if (output.getProviderInfoList() != null) { + metaData.getProviderInfoList().addAll(output.getProviderInfoList()); + } + } + +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletResponseWriter.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletResponseWriter.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletResponseWriter.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletResponseWriter.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web; + +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Iterator; +import java.util.List; + +import javax.activation.DataContentHandler; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.MessageBodyWriter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.ibm.ws.jaxrs.context.ContextConstants; +import com.ibm.ws.jaxrs.context.RESTContext; +import com.ibm.ws.jaxrs.i18n.Messages; +import com.ibm.ws.jaxrs.integration.ResponseWriter; +import com.ibm.ws.jaxrs.io.LogOutputStream; + +/** + * This is the default implementation of the ResponseWriter interface + * supplied by the core runtime. + * + */ +public class ServletResponseWriter implements ResponseWriter { + + private static final Log log = LogFactory + .getLog(ServletResponseWriter.class); + + private static final ServletResponseWriter singleton = new ServletResponseWriter(); + + public static ServletResponseWriter getInstance() { + return singleton; + } + + /** + * Called before the response is written to the OutputStream. This method will + * set the headers and status on the servlet response. + */ + public void preWrite(RESTContext context) throws Exception { + if (log.isDebugEnabled()) { + log.debug("ServletResponseWriter.preWrite(): entry"); + } + + if (log.isDebugEnabled()) { + log.debug("ServletResponseWriter.preWrite(): exit"); + } + } + + /** + * Calls the DataHandler to serialize to the OutputStream. + */ + public void writeWithDataHandler(RESTContext context, Object object, String mimeType, DataContentHandler contentHandler, OutputStream os) + throws Exception { + if (log.isDebugEnabled()) { + log.debug("ServletResponseWriter.writeWithDataHandler(): entry"); + } + Response response = (Response) context + .getProperty(ContextConstants.RESPONSE); + MultivaluedMap headers = (response == null) ? null + : response.getMetadata(); + contentHandler.writeTo(object, mimeType, new ServletStatusOutputStream( + context, headers, os, -1, mimeType)); + if (log.isDebugEnabled()) { + log.debug("ServletResponseWriter.writeWithDataHandler(): exit"); + } + } + + /** + * Calls the MessageBodyWriter to serialize to the OutputStream. + */ + public void writeWithEntityProvider(RESTContext context, MessageBodyWriter writer, Object responseObject, Class clazz, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap headers, final OutputStream os) + throws Exception { + if (log.isDebugEnabled()) { + log.debug("ServletResponseWriter.writeWithEntityProvider(): entry"); + } + final long writerSize = writer.getSize(responseObject, clazz, type, + annotations, mediaType); + writer.writeTo(responseObject, clazz, type, annotations, mediaType, + headers, new ServletStatusOutputStream(context, headers, os, + writerSize, mediaType)); + if (log.isDebugEnabled()) { + log.debug("ServletResponseWriter.writeWithEntityProvider(): exit"); + } + } + + /** + * Called after the response has been written to the OutputStream. This method will + * flush the buffer on the servlet response. + */ + public void postWrite(RESTContext context) throws Exception { + if (log.isDebugEnabled()) { + log.debug("ServletResponseWriter.postWrite(): entry"); + } + + HttpServletResponse servletResponse = (HttpServletResponse) context + .getProperty(ContextConstants.SERVLET_RESPONSE); + + if (servletResponse == null) { + throw new IllegalArgumentException(Messages + .getMessage("servletResponseNotAvailable")); + } + + /* + * check if the headers were already written. if they were, do not repeat headers. + * there is a possibility that there was no entity written so the status + * and headers need to be written here. + */ + Boolean isStatusAndHeadersAlreadyWritten = (Boolean) context + .getProperty(ContextConstants.IS_HTTP_RESPONSE_STATUS_AND_HEADERS_WRITTEN); + if (!servletResponse.isCommitted() + && ((isStatusAndHeadersAlreadyWritten == null) || (!isStatusAndHeadersAlreadyWritten + .booleanValue()))) { + setStatusAndHeaders(context); + + Integer length = ((Integer) context + .getProperty(ContextConstants.RESPONSE_CONTENT_LENGTH)); + if (length == null) { + length = Integer.valueOf(0); + } + if (log.isDebugEnabled()) { + log.debug("Setting Content-Length header on servlet response: " + + length); + } + servletResponse.setContentLength(length.intValue()); + } + + OutputStream os = (OutputStream) context + .getProperty(ContextConstants.RESPONSE_OUTPUT_STREAM); + os.flush(); + servletResponse.flushBuffer(); + + // check to see if we should log what was written to the output stream + if (log.isDebugEnabled()) { + LogOutputStream los = (LogOutputStream) context + .getProperty(ContextConstants.OUTPUT_LOGGER); + if (los != null) { + los.logBytes(); + } + log.debug("ServletResponseWriter.postWrite(): exit"); + } + } + + static void setStatusAndHeaders(RESTContext context) { + Response response = (Response) context + .getProperty(ContextConstants.RESPONSE); + if (response == null) { + throw new IllegalArgumentException(Messages + .getMessage("responseNotAvailable")); + } + + MultivaluedMap rspHeaders = response.getMetadata(); + HttpServletResponse servletResponse = (HttpServletResponse) context + .getProperty(ContextConstants.SERVLET_RESPONSE); + + if (servletResponse == null) { + throw new IllegalArgumentException((Messages + .getMessage("servletResponseNotAvailable"))); + } + + String varyHeader = (String) context + .getProperty(ContextConstants.VARY_HEADER); + if (rspHeaders != null && rspHeaders.getFirst("vary") == null + && varyHeader != null) { + if (log.isDebugEnabled()) { + log.debug("Setting Vary header on servlet response: " + + varyHeader); + } + rspHeaders.putSingle("vary", varyHeader); + } + + // add all the present headers + if (rspHeaders != null && !rspHeaders.isEmpty()) { + Iterator keys = rspHeaders.keySet().iterator(); + while (keys.hasNext()) { + String id = keys.next(); + List values = response.getMetadata().get(id); + if (values != null && !values.isEmpty()) { + for (Object value : values) { + if (log.isDebugEnabled()) { + log.debug("Adding value: " + value.toString() + + " for id: " + id + + " to HTTP response header"); + } + servletResponse.addHeader(id, value.toString()); + } + } + + } + } + + // now let's set the status + int status = response.getStatus(); + if (status == -1) { + servletResponse.setStatus((response.getEntity() == null) ? 204 + : 200); + } else { + if (log.isDebugEnabled()) { + log.debug("Setting response status to: " + status + + " from Response instance"); + } + servletResponse.setStatus(status); + } + } + +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletStatusOutputStream.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletStatusOutputStream.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletStatusOutputStream.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/ServletStatusOutputStream.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web; + +import java.io.IOException; +import java.io.OutputStream; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.ibm.ws.jaxrs.context.ContextConstants; +import com.ibm.ws.jaxrs.context.RESTContext; + +/** + * Delegates to an underlying OutputStream for all operations, but if any write + * is done, be sure to write the status code and headers in case an underlying + * buffer is flushed. + */ +class ServletStatusOutputStream extends OutputStream { + + private static final Log log = LogFactory + .getLog(ServletResponseWriter.class); + + public ServletStatusOutputStream(RESTContext context, MultivaluedMap headers, OutputStream os, long writerSize, MediaType mt) { + this(context, headers, os, writerSize, mt.getType() + "/" + + mt.getSubtype()); + } + + public ServletStatusOutputStream(RESTContext context, MultivaluedMap headers, OutputStream os, long writerSize, String contentType) { + this.context = context; + this.headers = headers; + this.osDelegate = os; + this.writerSize = writerSize; + this.contentType = contentType; + } + + final private RESTContext context; + + /* + * all keys should be lower cased + */ + final private MultivaluedMap headers; + + final private OutputStream osDelegate; + + final private long writerSize; + + private boolean isFirstWrite = true; + + final private String contentType; + + final private void checkFirstWrite() { + /* + * write the headers once the body is started to be written. no chance + * to do it later if the body becomes huge. the headers must be changed + * already by the writer. + */ + if (isFirstWrite) { + if (writerSize >= 0 && headers != null + && headers.getFirst("content-length") == null) { + if (log.isDebugEnabled()) { + log + .debug("Setting Content-Length header on servlet response: " + + writerSize); + } + headers.putSingle("content-length", Long.valueOf(writerSize)); + } + if (headers != null && headers.getFirst("content-type") == null + && contentType != null) { + if (log.isDebugEnabled()) { + log + .debug("Setting Content-Type header on servlet response: " + + contentType); + } + headers.putSingle("content-type", contentType); + } + ServletResponseWriter.setStatusAndHeaders(context); + context + .setProperty( + ContextConstants.IS_HTTP_RESPONSE_STATUS_AND_HEADERS_WRITTEN, + Boolean.TRUE); + isFirstWrite = false; + } + } + + @Override + public void write(int b) throws IOException { + checkFirstWrite(); + osDelegate.write(b); + } + + @Override + public void close() throws IOException { + osDelegate.close(); + } + + @Override + public void flush() throws IOException { + osDelegate.flush(); + } + + @Override + public void write(byte[] b) throws IOException { + checkFirstWrite(); + osDelegate.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + checkFirstWrite(); + osDelegate.write(b, off, len); + } +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationData.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationData.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationData.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationData.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web.asm; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a bean class that will be populated during the byte code + * scanning of an application. It will hold the necessary metadata + * for any JAX-RS resources or providers found in the application. + * + */ +public class JAXRSAnnotationData { + + private List resourceNames; + + private List providerNames; + + public JAXRSAnnotationData() { + resourceNames = new ArrayList(); + providerNames = new ArrayList(); + } + + public List getResourceNames() { + return resourceNames; + } + + public List getProviderNames() { + return providerNames; + } + + public List getJAXRSNames() { + resourceNames.addAll(providerNames); + return resourceNames; + } + + public void addResourceName(String name) { + resourceNames.add(name); + } + + public void addProviderName(String name) { + providerNames.add(name); + } + +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationScanner.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationScanner.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationScanner.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationScanner.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web.asm; + +import java.io.InputStream; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.ws.rs.core.Application; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import com.ibm.ws.jaxrs.integration.DefaultApplication; +import com.ibm.ws.jaxrs.utils.ClassUtils; + +/** + * This class will be the driver of our integration components with + * the ASM byte code scanning framework. It will drive the annotation + * scans to discover JAX-RS annotation metadata within a set of classes. + * + */ +public class JAXRSAnnotationScanner { + + /** + * This method accepts a list of InputStream objects. Each InputStream + * represents the stream to a .class file. The byte code of the class + * will be scanned, and an Application instance will be returned that + * contains all JAX-RS resources and providers. Null will be returned + * if no JAX-RS classes were found. + */ + public Application scan(List classInputs, ClassLoader classLoader) + throws Exception { + JAXRSAnnotationData jaxrsAnnotData = new JAXRSAnnotationData(); + if (classInputs != null) { + for (InputStream classInput : classInputs) { + ClassReader reader = new ClassReader(classInput); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + JAXRSClassAdapter adapter = new JAXRSClassAdapter(cw, + jaxrsAnnotData); + reader.accept(adapter, ClassReader.SKIP_DEBUG); + } + } + Set> classes = new HashSet>(); + if (jaxrsAnnotData.getJAXRSNames() != null) { + for (String className : jaxrsAnnotData.getJAXRSNames()) { + classes.add(ClassUtils.loadClass(className, classLoader)); + } + } + return new DefaultApplication(classes, null); + } + +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationVisitor.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationVisitor.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationVisitor.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSAnnotationVisitor.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web.asm; + +import org.objectweb.asm.AnnotationVisitor; + +/** + * This class implements the AnnotationVisitor interface in ASM, and + * it will be used to determine if a given class file contains the + * @Path or @Provider annotations. + * + */ +public class JAXRSAnnotationVisitor implements AnnotationVisitor { + + /* + * Called to process the annotation. + * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object) + */ + public void visit(String arg0, Object arg1) { + // TODO Auto-generated method stub + + } + + public AnnotationVisitor visitAnnotation(String arg0, String arg1) { + // nothing to do here for now + return null; + } + + public AnnotationVisitor visitArray(String arg0) { + // nothing to do here for now + return null; + } + + public void visitEnum(String arg0, String arg1, String arg2) { + // nothing to do here for now + } + + public void visitEnd() { + // TODO Auto-generated method stub + + } + +} Added: incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSClassAdapter.java URL: http://svn.apache.org/viewvc/incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSClassAdapter.java?rev=787557&view=auto ============================================================================== --- incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSClassAdapter.java (added) +++ incubator/wink/contrib/ibm-jaxrs/src/com/ibm/ws/jaxrs/web/asm/JAXRSClassAdapter.java Tue Jun 23 05:41:49 2009 @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.ibm.ws.jaxrs.web.asm; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassAdapter; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.commons.EmptyVisitor; + +/** + * This class will be a main integration point with the ASM framework. + * It will allow for byte-code scanning of class file contents, and we + * will use the results to determine the set of 'interesting' classes + * from a JAX-RS point of view. + * + */ +public class JAXRSClassAdapter extends ClassAdapter { + + private static final Log log = LogFactory.getLog(JAXRSClassAdapter.class); + + private static final String PATH_INTERNAL_NAME = "Ljavax/ws/rs/Path;"; + + private static final String PROVIDER_INTERNAL_NAME = "Ljavax/ws/rs/ext/Provider;"; + + private String className; + + private JAXRSAnnotationData jaxrsAnnotData; + + public JAXRSClassAdapter(ClassVisitor visitor, JAXRSAnnotationData jaxrsAnnotData) { + super(visitor); + this.jaxrsAnnotData = jaxrsAnnotData; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + className = name.replace("/", "."); + } + + @Override + public AnnotationVisitor visitAnnotation(String annotInternalName, boolean visible) { + if (PATH_INTERNAL_NAME.equals(annotInternalName)) { + if (log.isDebugEnabled()) { + log.debug("Found the @Path annotation in the class: " + + className); + } + jaxrsAnnotData.addResourceName(className); + } + if (PROVIDER_INTERNAL_NAME.equals(annotInternalName)) { + if (log.isDebugEnabled()) { + log.debug("Found the @Provider annotation in the class: " + + className); + } + jaxrsAnnotData.addResourceName(className); + } + return new EmptyVisitor(); + } + +}