/******************************************************************************* * 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 org.apache.wink.server.internal; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Map.Entry; import javax.servlet.FilterConfig; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Application; import org.apache.commons.lang.ClassUtils; import org.apache.wink.common.internal.WinkConfiguration; import org.apache.wink.common.internal.application.ApplicationValidator; import org.apache.wink.common.internal.i18n.Messages; import org.apache.wink.common.internal.lifecycle.JSR250LifecycleManager; import org.apache.wink.common.internal.lifecycle.LifecycleManagersRegistry; import org.apache.wink.common.internal.lifecycle.ObjectFactory; import org.apache.wink.common.internal.lifecycle.ScopeLifecycleManager; import org.apache.wink.common.internal.registry.InjectableFactory; import org.apache.wink.common.internal.registry.ProvidersRegistry; import org.apache.wink.common.internal.utils.FileLoader; import org.apache.wink.server.handlers.Handler; import org.apache.wink.server.handlers.HandlersFactory; import org.apache.wink.server.handlers.MediaTypeMapperFactory; import org.apache.wink.server.handlers.RequestHandler; import org.apache.wink.server.handlers.RequestHandlersChain; import org.apache.wink.server.handlers.ResponseHandler; import org.apache.wink.server.handlers.ResponseHandlersChain; import org.apache.wink.server.internal.application.ApplicationProcessor; import org.apache.wink.server.internal.handlers.CheckLocationHeaderHandler; import org.apache.wink.server.internal.handlers.CreateInvocationParametersHandler; import org.apache.wink.server.internal.handlers.FindResourceMethodHandler; import org.apache.wink.server.internal.handlers.FindRootResourceHandler; import org.apache.wink.server.internal.handlers.FlushResultHandler; import org.apache.wink.server.internal.handlers.HeadMethodHandler; import org.apache.wink.server.internal.handlers.InvokeMethodHandler; import org.apache.wink.server.internal.handlers.OptionsMethodHandler; import org.apache.wink.server.internal.handlers.PopulateErrorResponseHandler; import org.apache.wink.server.internal.handlers.PopulateResponseMediaTypeHandler; import org.apache.wink.server.internal.handlers.PopulateResponseStatusHandler; import org.apache.wink.server.internal.handlers.SearchResultHandler; import org.apache.wink.server.internal.log.Requests; import org.apache.wink.server.internal.log.ResourceInvocation; import org.apache.wink.server.internal.log.Responses; import org.apache.wink.server.internal.registry.ResourceRegistry; import org.apache.wink.server.internal.registry.ServerInjectableFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p> * This class implements a default deployment configuration for Wink. In order * to change this configuration, extend this class and override the relevant * methods. In general it's possible to override any methods of this class, but * the best practices are to override methods the "init" methods. See the * javadoc for each method for more details. */ public class DeploymentConfiguration implements WinkConfiguration { private static final Logger logger = LoggerFactory .getLogger(DeploymentConfiguration.class); private static final String ALTERNATIVE_SHORTCUTS = "META-INF/wink-alternate-shortcuts.properties"; //$NON-NLS-1$ private static final String HTTP_METHOD_OVERRIDE_HEADERS_PROP = "wink.httpMethodOverrideHeaders"; //$NON-NLS-1$ private static final String HANDLERS_FACTORY_CLASS_PROP = "wink.handlersFactoryClass"; //$NON-NLS-1$ private static final String MEDIATYPE_MAPPER_FACTORY_CLASS_PROP = "wink.mediaTypeMapperFactoryClass"; //$NON-NLS-1$ private static final String VALIDATE_LOCATION_HEADER = "wink.validateLocationHeader"; //$NON-NLS-1$ private static final String DEFAULT_RESPONSE_CHARSET = "wink.response.defaultCharset"; // $NON-NLS-1$ private static final String USE_ACCEPT_CHARSET = "wink.response.useAcceptCharset"; // $NON-NLS-1$ // handler chains private boolean isChainInitialized = false; private RequestHandlersChain requestHandlersChain; private ResponseHandlersChain responseHandlersChain; private ResponseHandlersChain errorHandlersChain; private List<RequestHandler> requestUserHandlers; private List<ResponseHandler> responseUserHandlers; private List<ResponseHandler> errorUserHandlers; // registries private ProvidersRegistry providersRegistry; private ResourceRegistry resourceRegistry; private LifecycleManagersRegistry ofFactoryRegistry; // mappers private MediaTypeMapper mediaTypeMapper; private Map<String, String> alternateShortcutMap; // external properties private Properties properties; // servlet configuration private ServletConfig servletConfig; private ServletContext servletContext; private FilterConfig filterConfig; // jax-rs application subclass private List<Application> applications; private String[] httpMethodOverrideHeaders; private Set<ObjectFactory<?>> appObjectFactories; private boolean isUseAcceptCharset = false; private boolean isDefaultResponseCharset = false; /** * Makes sure that the object was properly initialized. Should be invoked * AFTER all the setters were invoked. */ public void init() { if (properties == null) { properties = new Properties(); } appObjectFactories = new HashSet<ObjectFactory<?>>(8); logger.trace("Deployment configuration properties: {}", properties); //$NON-NLS-1$ // check to see if an override property was specified. if so, then // configure // the headers from there using a comma delimited string. String httpMethodOverrideHeadersProperty = properties.getProperty(HTTP_METHOD_OVERRIDE_HEADERS_PROP); httpMethodOverrideHeaders = (httpMethodOverrideHeadersProperty != null && httpMethodOverrideHeadersProperty .length() > 0) ? httpMethodOverrideHeadersProperty.split(",") : null; //$NON-NLS-1$ initRegistries(); initAlternateShortcutMap(); initMediaTypeMapper(); initHandlers(); // this next code is to dump the config to trace after initialization if (logger.isDebugEnabled()) { try { logger.debug("Configuration Settings:"); logger.trace("Request Handlers Chain : {}", requestHandlersChain); logger.trace("Response Handlers Chain: {}", responseHandlersChain); logger.trace("Error Handlers Chain: {}", errorHandlersChain); logger.debug("Request User Handlers: {}", String .format("%1$s", requestUserHandlers)); logger.debug("Response User Handlers: {}", String.format("%1$s", responseUserHandlers)); logger.debug("Error User Handlers: {}", String.format("%1$s", errorUserHandlers)); logger.trace("LifecycleManagerRegistry: {}", this.ofFactoryRegistry .getLifecycleManagers()); logger.debug("MediaTypeMapper: {}", this.mediaTypeMapper); // TODO // add // toString // method // for // MediaTypeMapper logger.debug("AlternateShortcutMap: {}", this.alternateShortcutMap); logger.debug("Properties: {}", this.properties); logger.trace("ServletConfig: {}", this.servletConfig); logger.trace("ServletContext: {}", this.servletContext); logger.trace("FilterConfig: {}", this.filterConfig); logger.trace("Applications: {}", this.applications); List<String> httpMethodOverrideHeadersList = new ArrayList<String>(); if (this.httpMethodOverrideHeaders != null) { for (int i = 0; i < this.httpMethodOverrideHeaders.length; ++i) httpMethodOverrideHeadersList.add(this.httpMethodOverrideHeaders[i]); } logger.debug("HttpMethodOverrideHeaders: {}", httpMethodOverrideHeadersList); } catch (Exception e) { // make sure we don't fail in case anything wrong happens here logger.debug("An Exception occurred when logging the configuration {}", e); } } } public RequestHandlersChain getRequestHandlersChain() { if (!isChainInitialized) { initHandlersChain(); } return requestHandlersChain; } public void setRequestHandlersChain(RequestHandlersChain requestHandlersChain) { this.requestHandlersChain = requestHandlersChain; } public ResponseHandlersChain getResponseHandlersChain() { if (!isChainInitialized) { initHandlersChain(); } return responseHandlersChain; } public void setResponseHandlersChain(ResponseHandlersChain responseHandlersChain) { this.responseHandlersChain = responseHandlersChain; } public ResponseHandlersChain getErrorHandlersChain() { if (!isChainInitialized) { initHandlersChain(); } return errorHandlersChain; } public void setErrorHandlersChain(ResponseHandlersChain errorHandlersChain) { this.errorHandlersChain = errorHandlersChain; } public ProvidersRegistry getProvidersRegistry() { return providersRegistry; } public ResourceRegistry getResourceRegistry() { return resourceRegistry; } public MediaTypeMapper getMediaTypeMapper() { return mediaTypeMapper; } public void setMediaTypeMapper(MediaTypeMapper mediaTypeMapper) { this.mediaTypeMapper = mediaTypeMapper; } public void setOfFactoryRegistry(LifecycleManagersRegistry ofFactoryRegistry) { this.ofFactoryRegistry = ofFactoryRegistry; } public LifecycleManagersRegistry getOfFactoryRegistry() { return ofFactoryRegistry; } public Map<String, String> getAlternateShortcutMap() { return alternateShortcutMap; } public void setAlternateShortcutMap(Map<String, String> alternateShortcutMap) { this.alternateShortcutMap = alternateShortcutMap; } public Properties getProperties() { return properties; } public void setProperties(Properties properties) { this.properties = properties; String val = properties.getProperty(USE_ACCEPT_CHARSET); isUseAcceptCharset = Boolean.valueOf(val).booleanValue(); val = properties.getProperty(DEFAULT_RESPONSE_CHARSET); isDefaultResponseCharset = Boolean.valueOf(val).booleanValue(); } public ServletConfig getServletConfig() { return servletConfig; } public void setServletConfig(ServletConfig servletConfig) { this.servletConfig = servletConfig; } public FilterConfig getFilterConfig() { return filterConfig; } public void setFilterConfig(FilterConfig filterConfig) { this.filterConfig = filterConfig; } public ServletContext getServletContext() { return servletContext; } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } public void setRequestUserHandlers(List<RequestHandler> requestUserHandlers) { this.requestUserHandlers = requestUserHandlers; } public void setResponseUserHandlers(List<ResponseHandler> responseUserHandlers) { this.responseUserHandlers = responseUserHandlers; } public List<ResponseHandler> getResponseUserHandlers() { return responseUserHandlers; } public List<RequestHandler> getRequestUserHandlers() { return requestUserHandlers; } public void setErrorUserHandlers(List<ResponseHandler> errorUserHandlers) { this.errorUserHandlers = errorUserHandlers; } public List<ResponseHandler> getErrorUserHandlers() { return errorUserHandlers; } public void addApplication(Application application, boolean isSystemApplication) { if (applications == null) { applications = new ArrayList<Application>(1); } new ApplicationProcessor(application, resourceRegistry, providersRegistry, isSystemApplication).process(); applications.add(application); } public List<Application> getApplications() { return applications; } // init methods /** * Initializes registries. Usually there should be no need to override this * method. When creating Resources or Providers registry, ensure that they * use the same instance of the ApplicationValidator. */ protected void initRegistries() { InjectableFactory.setInstance(new ServerInjectableFactory()); if (ofFactoryRegistry == null) { ofFactoryRegistry = new LifecycleManagersRegistry(); ofFactoryRegistry.addFactoryFactory(new ScopeLifecycleManager<Object>()); ofFactoryRegistry.addFactoryFactory(new JSR250LifecycleManager<Object>()); } ApplicationValidator applicationValidator = new ApplicationValidator(); providersRegistry = new ProvidersRegistry(ofFactoryRegistry, applicationValidator); resourceRegistry = new ResourceRegistry(ofFactoryRegistry, applicationValidator, properties); } /** * Initializes the AlternateShortcutMap. Override this method in order to * provide a custom AlternateShortcutMap. */ protected void initAlternateShortcutMap() { if (alternateShortcutMap == null) { InputStream is = null; try { is = FileLoader.loadFileAsStream(ALTERNATIVE_SHORTCUTS); Properties lproperties = new Properties(); lproperties.load(is); logger.trace("Alternative shortcuts properties: {}", lproperties); //$NON-NLS-1$ alternateShortcutMap = new HashMap<String, String>(); for (Entry<Object, Object> entry : lproperties.entrySet()) { alternateShortcutMap.put((String)entry.getKey(), (String)entry.getValue()); } logger.trace("Alternative shortcuts map: {}", alternateShortcutMap); //$NON-NLS-1$ } catch (IOException e) { logger.error(Messages.getMessage("alternateShortcutMapLoadFailure"), e); //$NON-NLS-1$ throw new WebApplicationException(e); } finally { try { if (is != null) { is.close(); } } catch (IOException e) { logger .info(Messages.getMessage("alternateShortcutMapCloseFailure") + ALTERNATIVE_SHORTCUTS, //$NON-NLS-1$ e); } } } } /** * Initializes the MediaTypeMapper. Override it to provide a custom * MediaTypeMapper. */ @SuppressWarnings("unchecked") protected void initMediaTypeMapper() { if (mediaTypeMapper == null) { String mediaTypeMapperFactoryClassName = properties.getProperty(MEDIATYPE_MAPPER_FACTORY_CLASS_PROP); if (mediaTypeMapperFactoryClassName != null) { mediaTypeMapper = new MediaTypeMapper(); try { logger.trace("MediaTypeMappingFactory Class is: {}", //$NON-NLS-1$ mediaTypeMapperFactoryClassName); Class<MediaTypeMapperFactory> handlerFactoryClass = (Class<MediaTypeMapperFactory>)Class .forName(mediaTypeMapperFactoryClassName); MediaTypeMapperFactory handlersFactory = handlerFactoryClass.newInstance(); mediaTypeMapper.addMappings(handlersFactory.getMediaTypeMappings()); } catch (ClassNotFoundException e) { if (logger.isErrorEnabled()) { logger.error(Messages.getMessage("isNotAClassWithMsgFormat", //$NON-NLS-1$ mediaTypeMapperFactoryClassName), e); } } catch (InstantiationException e) { if (logger.isErrorEnabled()) { logger.error(Messages .getMessage("classInstantiationExceptionWithMsgFormat", //$NON-NLS-1$ mediaTypeMapperFactoryClassName), e); } } catch (IllegalAccessException e) { if (logger.isErrorEnabled()) { logger.error(Messages.getMessage("classIllegalAccessWithMsgFormat", //$NON-NLS-1$ mediaTypeMapperFactoryClassName), e); } } } } } /** * Initializes the main handlers chain. Override in order to change the * chains. */ @SuppressWarnings("unchecked") private void initHandlers() { String handlersFactoryClassName = properties.getProperty(HANDLERS_FACTORY_CLASS_PROP); if (handlersFactoryClassName != null) { try { logger.trace("Handlers Factory Class is: {}", handlersFactoryClassName); //$NON-NLS-1$ // use ClassUtils.getClass instead of Class.forName so we have // classloader visibility into the Web module in J2EE // environments Class<HandlersFactory> handlerFactoryClass = (Class<HandlersFactory>)ClassUtils.getClass(handlersFactoryClassName); HandlersFactory handlersFactory = handlerFactoryClass.newInstance(); if (requestUserHandlers == null) { requestUserHandlers = (List<RequestHandler>)handlersFactory.getRequestHandlers(); } if (responseUserHandlers == null) { responseUserHandlers = (List<ResponseHandler>)handlersFactory.getResponseHandlers(); } if (errorUserHandlers == null) { errorUserHandlers = (List<ResponseHandler>)handlersFactory.getErrorHandlers(); } } catch (ClassNotFoundException e) { logger.error(Messages.getMessage("isNotAClassWithMsgFormat", //$NON-NLS-1$ handlersFactoryClassName), e); } catch (InstantiationException e) { logger.error(Messages.getMessage("classInstantiationExceptionWithMsgFormat", //$NON-NLS-1$ handlersFactoryClassName), e); } catch (IllegalAccessException e) { logger.error(Messages.getMessage("classIllegalAccessWithMsgFormat", //$NON-NLS-1$ handlersFactoryClassName), e); } } if (requestUserHandlers == null) { requestUserHandlers = initRequestUserHandlers(); } if (responseUserHandlers == null) { responseUserHandlers = initResponseUserHandlers(); } if (errorUserHandlers == null) { errorUserHandlers = initErrorUserHandlers(); } } private void initHandlersChain() { if (requestHandlersChain == null) { requestHandlersChain = initRequestHandlersChain(); } if (responseHandlersChain == null) { responseHandlersChain = initResponseHandlersChain(); } if (errorHandlersChain == null) { errorHandlersChain = initErrorHandlersChain(); } isChainInitialized = true; } /** * Initializes the Request Handlers Chain (the chain that handles the * in-bound request). Usually the user won't need to override this method, * but <tt>initRequestUserHandlers</tt> instead. * * @see initRequestUserHandlers */ @SuppressWarnings("unchecked") protected RequestHandlersChain initRequestHandlersChain() { RequestHandlersChain handlersChain = new RequestHandlersChain(); handlersChain.addHandler(createHandler(Requests.class)); handlersChain.addHandler(createHandler(ResourceInvocation.class)); handlersChain.addHandler(createHandler(SearchResultHandler.class)); String optionsHandler = properties.getProperty("org.apache.wink.server.options.handler", OptionsMethodHandler.class.getName()); if ("none".equals(optionsHandler)) { optionsHandler = OptionsMethodHandler.class.getName(); } logger.trace("org.apache.wink.server.options.handler value is {}", optionsHandler); try { handlersChain.addHandler(createHandler((Class<? extends RequestHandler>)Class .forName(optionsHandler))); } catch (Exception e) { logger.trace("Could not load handlers class so adding default"); handlersChain.addHandler(createHandler(OptionsMethodHandler.class)); } handlersChain.addHandler(createHandler(HeadMethodHandler.class)); handlersChain.addHandler(createHandler(FindRootResourceHandler.class)); handlersChain.addHandler(createHandler(FindResourceMethodHandler.class)); handlersChain.addHandler(createHandler(CreateInvocationParametersHandler.class)); if (requestUserHandlers != null) { for (RequestHandler h : requestUserHandlers) { h.init(properties); handlersChain.addHandler(h); } } handlersChain.addHandler(createHandler(InvokeMethodHandler.class)); logger.trace("Request handlers chain is: {}", handlersChain); //$NON-NLS-1$ return handlersChain; } /** * Initializes request (inbound) user handler. By default this method * returns an empty list. Override to add user handlers. * * @return list of RequestHandler * @see RequestHandler */ protected List<RequestHandler> initRequestUserHandlers() { return Collections.emptyList(); } /** * Initializes response (outbound) user handlers. By default this method * returns an empty list. Override to add user handlers. * * @return list of ResponseHandler * @see ResponseHandler */ protected List<ResponseHandler> initResponseUserHandlers() { ArrayList<ResponseHandler> list = new ArrayList<ResponseHandler>(1); if (Boolean.parseBoolean(properties.getProperty(VALIDATE_LOCATION_HEADER))) { list.add(new CheckLocationHeaderHandler()); } return list; } /** * Initializes error user handlers.By default this method returns an empty * list. Override to add user handlers. * * @return list of ResponseHandler * @see ResponseHandler */ protected List<ResponseHandler> initErrorUserHandlers() { return Collections.emptyList(); } /** * Initializes the Response Handlers Chain (the chain that handles the * out-bound response). Usually the user won't need to override this method, * but <tt>initResponseUserHandlers</tt> instead. */ protected ResponseHandlersChain initResponseHandlersChain() { ResponseHandlersChain handlersChain = new ResponseHandlersChain(); handlersChain.addHandler(createHandler(Responses.class)); handlersChain.addHandler(createHandler(PopulateResponseStatusHandler.class)); handlersChain.addHandler(createHandler(PopulateResponseMediaTypeHandler.class)); if (responseUserHandlers != null) { for (ResponseHandler h : responseUserHandlers) { h.init(properties); handlersChain.addHandler(h); } } handlersChain.addHandler(createHandler(FlushResultHandler.class)); handlersChain.addHandler(createHandler(HeadMethodHandler.class)); logger.trace("Response handlers chain is: {}", handlersChain); //$NON-NLS-1$ return handlersChain; } /** * Initializes the Error Handlers Chain (the chain that handles the * exceptions). Usually the user won't need to override this method, but * <tt>initErrorUserHandlers</tt> instead. */ protected ResponseHandlersChain initErrorHandlersChain() { ResponseHandlersChain handlersChain = new ResponseHandlersChain(); Responses responsesHandler = createHandler(Responses.class); responsesHandler.setIsErrorFlow(true); handlersChain.addHandler(responsesHandler); handlersChain.addHandler(createHandler(PopulateErrorResponseHandler.class)); handlersChain.addHandler(createHandler(PopulateResponseStatusHandler.class)); PopulateResponseMediaTypeHandler populateMediaTypeHandler = createHandler(PopulateResponseMediaTypeHandler.class); populateMediaTypeHandler.setErrorFlow(true); handlersChain.addHandler(populateMediaTypeHandler); if (errorUserHandlers != null) { for (ResponseHandler h : errorUserHandlers) { h.init(properties); handlersChain.addHandler(h); } } handlersChain.addHandler(createHandler(FlushResultHandler.class)); logger.trace("Error handlers chain is: {}", handlersChain); //$NON-NLS-1$ return handlersChain; } protected <T extends Handler> T createHandler(Class<T> cls) { try { T handler = cls.newInstance(); logger.trace("Calling {}.init(Properties)", cls); //$NON-NLS-1$ handler.init(getProperties()); return handler; } catch (InstantiationException e) { throw new WebApplicationException(e); } catch (IllegalAccessException e) { throw new WebApplicationException(e); } } public void setHttpMethodOverrideHeaders(String[] httpMethodOverrideHeaders) { this.httpMethodOverrideHeaders = httpMethodOverrideHeaders; if (logger.isTraceEnabled()) { List<String> overrideHeaders = (httpMethodOverrideHeaders == null) ? null : Arrays .asList(httpMethodOverrideHeaders); logger.trace("Setting HTTP Method override headers: {}", overrideHeaders); //$NON-NLS-1$ } } public String[] getHttpMethodOverrideHeaders() { return httpMethodOverrideHeaders; } /** * isDefaultResponseCharset will write charset=UTF-8 to the response * Content-Type header if a response charset is not already explicitly * defined. * * @return boolean */ public boolean isDefaultResponseCharset() { return isDefaultResponseCharset; } public void setDefaultResponseCharset(boolean val) { properties.setProperty(DEFAULT_RESPONSE_CHARSET, Boolean.toString(val)); isDefaultResponseCharset = val; } /** * isUseAcceptCharset will use the Accept-Charset header, if present, to * write a charset to the response Content-Type header if a response charset * is not already explicitly defined. This setting will override the * isDefaultResponseCharset setting when the Accept-Charset header is * present. * * @return */ public boolean isUseAcceptCharset() { return isUseAcceptCharset; } public void setUseAcceptCharset(boolean val) { properties.setProperty(USE_ACCEPT_CHARSET, Boolean.toString(val)); isUseAcceptCharset = val; } public void addApplicationObjectFactory(ObjectFactory<?> of) { appObjectFactories.add(of); } public Set<ObjectFactory<?>> getApplicationObjectFactories() { return appObjectFactories; } }