Author: [email protected] Date: Wed Apr 6 17:06:21 2011 New Revision: 947
Log: AMDATU-283 wink: dispatcher aware / proper lifecycle handling / replace filter workaround with local requestwrapper Removed: branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkServletFilter.java Modified: branches/AMDATU-283-dev/amdatu-web/rest-wink/pom.xml branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/WinkRegistrationService.java branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/osgi/Activator.java branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkRegistrationServiceImpl.java branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkRestServlet.java Modified: branches/AMDATU-283-dev/amdatu-web/rest-wink/pom.xml ============================================================================== --- branches/AMDATU-283-dev/amdatu-web/rest-wink/pom.xml (original) +++ branches/AMDATU-283-dev/amdatu-web/rest-wink/pom.xml Wed Apr 6 17:06:21 2011 @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.amdatu</groupId> @@ -14,6 +15,34 @@ <dependencies> <dependency> + <groupId>org.amdatu.core</groupId> + <artifactId>tenant</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + <type>bundle</type> + </dependency> + <dependency> + <groupId>org.amdatu.web.rest</groupId> + <artifactId>jaxrs</artifactId> + <version>${project.version}</version> + <type>bundle</type> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.amdatu.web</groupId> + <artifactId>dispatcher</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + <type>bundle</type> + </dependency> + <dependency> + <groupId>org.amdatu.web</groupId> + <artifactId>httpcontext</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + <type>bundle</type> + </dependency> + <dependency> <groupId>org.apache.wink</groupId> <artifactId>wink-assembly-aggregatejar-osgi</artifactId> <version>${org.apache.wink.version}</version> @@ -32,37 +61,17 @@ <scope>compile</scope> </dependency> <dependency> - <groupId>org.amdatu.web.rest</groupId> - <artifactId>jaxrs</artifactId> - <version>${project.version}</version> - <type>bundle</type> - <scope>provided</scope> - </dependency> - <dependency> <groupId>org.apache.wink</groupId> <artifactId>wink-json-provider</artifactId> <version>${org.apache.wink.version}</version> <scope>compile</scope> </dependency> <dependency> - <groupId>org.amdatu.web</groupId> - <artifactId>httpcontext</artifactId> - <scope>provided</scope> - <type>bundle</type> - </dependency> - <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20080701</version> <scope>compile</scope> </dependency> - <dependency> - <groupId>org.amdatu.core</groupId> - <artifactId>tenant</artifactId> - <version>${project.version}</version> - <scope>provided</scope> - <type>bundle</type> - </dependency> </dependencies> <build> Modified: branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/WinkRegistrationService.java ============================================================================== --- branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/WinkRegistrationService.java (original) +++ branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/WinkRegistrationService.java Wed Apr 6 17:06:21 2011 @@ -1,5 +1,25 @@ +/* + Copyright (C) 2010 Amdatu.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ package org.amdatu.web.rest.wink; +/** + * Service interface for the Amdatu REST framework service + * based on Apache Wink. + */ public interface WinkRegistrationService { } Modified: branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/osgi/Activator.java ============================================================================== --- branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/osgi/Activator.java (original) +++ branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/osgi/Activator.java Wed Apr 6 17:06:21 2011 @@ -17,30 +17,29 @@ */ package org.amdatu.web.rest.wink.osgi; +import org.amdatu.web.dispatcher.DispatcherService; +import org.amdatu.web.rest.wink.WinkRegistrationService; import org.amdatu.web.rest.wink.service.WinkRegistrationServiceImpl; import org.apache.felix.dm.DependencyActivatorBase; import org.apache.felix.dm.DependencyManager; import org.osgi.framework.BundleContext; -import org.osgi.service.http.HttpService; import org.osgi.service.log.LogService; /** - * This is the OSGi activator for this Cassandra application bundle. - * - * @author ivol + * This is the OSGi activator for the Amdatu REST framework based on Apache Wink. */ public class Activator extends DependencyActivatorBase { - + public final static String CONTEXTID = "amdatu-rest"; public final static String ALIAS = "/rest"; @Override public void init(BundleContext context, DependencyManager manager) throws Exception { - // Create and register the wink registration service manager.add(createComponent() + .setInterface(WinkRegistrationService.class.getName(), null) .setImplementation(WinkRegistrationServiceImpl.class) - .add(createServiceDependency().setService(HttpService.class).setRequired(true)) + .add(createServiceDependency().setService(DispatcherService.class).setRequired(true)) .add(createServiceDependency().setService("(objectclass=*)").setCallbacks("onAdded", "onRemoved")) .add(createServiceDependency().setService(LogService.class).setRequired(false))); } Modified: branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkRegistrationServiceImpl.java ============================================================================== --- branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkRegistrationServiceImpl.java (original) +++ branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkRegistrationServiceImpl.java Wed Apr 6 17:06:21 2011 @@ -16,14 +16,10 @@ */ package org.amdatu.web.rest.wink.service; -import java.util.ArrayList; import java.util.Dictionary; -import java.util.Enumeration; import java.util.Hashtable; -import java.util.List; -import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; -import javax.servlet.Filter; import javax.servlet.Servlet; import javax.ws.rs.Path; import javax.ws.rs.core.Application; @@ -33,61 +29,131 @@ import javax.ws.rs.ext.RuntimeDelegate; import org.amdatu.core.tenant.Tenant; +import org.amdatu.web.dispatcher.DispatcherService; +import org.amdatu.web.httpcontext.HttpContextManagerService; import org.amdatu.web.rest.jaxrs.JaxRsSpi; import org.amdatu.web.rest.wink.WinkRegistrationService; import org.amdatu.web.rest.wink.osgi.Activator; import org.apache.felix.dm.Component; import org.apache.felix.dm.DependencyManager; import org.apache.wink.common.internal.runtime.RuntimeDelegateImpl; -import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpService; import org.osgi.service.log.LogService; /** - * This class is responsible for booting Wink as well as registration of new REST services as servlets. - * - * @author ivol + * This class is responsible for booting Wink as well as publication of new REST + * services. Any service that declares a valid <code>Path</code> annotation on + * its interfaces will be published as a REST servlet. The servlet alias is + * determined by adding the resource Path value to the REST base path. */ public class WinkRegistrationServiceImpl implements WinkRegistrationService { + // Location of application properties in this bundle private final static String APP_RPOPS = "/conf/application.properties"; - // Services injected by the Felix dependency manager - private volatile HttpService m_httpService; - private volatile LogService m_logService; - private volatile DependencyManager m_dependencyManager; + // Map of registered REST service components + private final ConcurrentHashMap<ServiceReference, Component> m_servletComponents = + new ConcurrentHashMap<ServiceReference, Component>(); - // List of registered servlets, stored to be sure the servlet is registered before unregistering it - private List<String> m_registeredServlets = new ArrayList<String>(); + // Services injected by dependency manager + private volatile DependencyManager m_dependencyManager; + private volatile LogService m_logService; - // Array to prevent depnman from injecting + // Array to prevent dependencyManager from injecting the wrong component private Component[] m_spiComponent; - /** - * The init() method is invoked by the Felix dependency manager. - */ - public void init() { + public synchronized void init() { + m_servletComponents.clear(); setRuntimeDelegate(); registerSpiService(); m_logService.log(LogService.LOG_INFO, getClass().getName() + " service initialized"); } - /** - * The destroy() method is invoked by the Felix dependency manager. - */ - public void destroy() { + public synchronized void destroy() { removeSpiService(); - while (m_registeredServlets.size() > 0) { - String servletUrl = m_registeredServlets.get(0); - m_httpService.unregister(servletUrl); - m_registeredServlets.remove(servletUrl); - m_logService.log(LogService.LOG_INFO, "Wink application unregistered REST servlet '" + servletUrl - + "'"); + for (Component servletComponent : m_servletComponents.values()) { + m_dependencyManager.remove(servletComponent); } + m_servletComponents.clear(); m_logService.log(LogService.LOG_INFO, getClass().getName() + " service destroyed"); } + public void onAdded(ServiceReference serviceReference, Object service) { + if (service.getClass().getAnnotation(Path.class) != null) { + addRestServlet(serviceReference, service); + } + } + + public void onRemoved(ServiceReference serviceReference, Object service) { + if (service.getClass().getAnnotation(Path.class) != null) { + removeRestServlet(serviceReference, service); + } + } + + private void addRestServlet(ServiceReference serviceReference, Object service) { + + Dictionary<String, String> initParams = new Hashtable<String, String>(); + initParams.put("init.applicationConfigLocation", APP_RPOPS); + + String restPath = service.getClass().getAnnotation(Path.class).value(); + if (restPath.startsWith("/")) { + restPath = restPath.substring(1); + } + String servletAlias = Activator.ALIAS + "/" + restPath; + initParams.put(DispatcherService.ALIAS_KEY, servletAlias); + + String contextId = getStringProperty(serviceReference, HttpContextManagerService.CONTEXT_ID_KEY); + if (!contextId.equals("")) { + initParams.put(HttpContextManagerService.CONTEXT_ID_KEY, contextId); + } + + String tenantId = getStringProperty(serviceReference, Tenant.TENANT_ID_SERVICEPROPERTY); + if (!tenantId.equals("")) { + initParams.put(Tenant.TENANT_ID_SERVICEPROPERTY, tenantId); + } + + WinkRestServlet restServlet = new WinkRestServlet(restPath, service, tenantId); + + Component servletComponent = m_dependencyManager.createComponent() + .setInterface(Servlet.class.getName(), initParams) + .setImplementation(restServlet) + .setCallbacks("_init", "start", "stop", "_destroy"); + + // threadsafe + if (m_servletComponents.putIfAbsent(serviceReference, servletComponent) == null) { + m_dependencyManager.add(servletComponent); + m_logService.log(LogService.LOG_DEBUG, "Wink application registered REST servlet '" + servletAlias + "'"); + } + else { + m_logService.log(LogService.LOG_ERROR, "Duplicate ServiceReference in callback: " + serviceReference); + } + } + + private void removeRestServlet(ServiceReference serviceReference, Object service) { + // threadsafe + Component servletComponent = m_servletComponents.remove(serviceReference); + if (servletComponent != null) { + m_dependencyManager.remove(servletComponent); + m_logService.log(LogService.LOG_DEBUG, "Wink application unregistered REST servlet"); + } + else { + m_logService.log(LogService.LOG_ERROR, "Unknown ServiceReference in callback: " + serviceReference); + } + } + + private void registerSpiService() { + Component comp = m_dependencyManager.createComponent() + .setInterface(JaxRsSpi.class.getName(), null) + .setImplementation(new JaxRsSpi() { + }); + m_dependencyManager.add(comp); + m_spiComponent = new Component[] { comp }; + } + + private void removeSpiService() { + m_dependencyManager.remove(m_spiComponent[0]); + } + /** * Sets the runtime delegate, used to create instances of desired endpoint classes. */ @@ -126,129 +192,6 @@ RuntimeDelegate.setInstance(new RuntimeDelegateImpl()); } - private void registerSpiService() { - Component comp = m_dependencyManager.createComponent() - .setInterface(JaxRsSpi.class.getName(), null) - .setImplementation(new JaxRsSpi() { - }); - m_dependencyManager.add(comp); - m_spiComponent = new Component[] { comp }; - } - - private void removeSpiService() { - m_dependencyManager.remove(m_spiComponent[0]); - } - - public void onAdded(ServiceReference ref, Object service) { - // Check if this is a JAX-RS annotated class - if (service.getClass().getAnnotation(Path.class) != null) { - // Register a new REST servlet for this REST resource - addRestServlet(ref, service); - } - } - - public void onRemoved(ServiceReference ref, Object service) { - // Check if this is a JAX-RS annotated class - if (service.getClass().getAnnotation(Path.class) != null) { - // Unregister the REST servlet registered earlier this REST resource - removeRestServlet(service); - } - } - - /** - * Registers a new REST servlet for this resource. - * - * @param service The REST resource service to register the servlet for - */ - - private void addRestServlet(ServiceReference ref, Object service) { - synchronized (m_registeredServlets) { - // Prepare init parameter used internally by Wink. Note that we must prefix the parameters - // by "init." according to the whiteboard style of servlet registration. - Dictionary<String, String> initParams = new Hashtable<String, String>(); - initParams.put("init.applicationConfigLocation", APP_RPOPS); - - // Create the Wink REST servlet - String restPath = service.getClass().getAnnotation(Path.class).value(); - if (restPath.startsWith("/")) { - restPath = restPath.substring(1); - } - String tenantId = getTenantId(ref); - WinkRestServlet restServlet = new WinkRestServlet(restPath, service, tenantId); - - // Register servlet whiteboard style - String servletUrl = Activator.ALIAS + "/" + restPath; - String contextId = getStringProperty(ref, "contextId"); - - registerServlet(tenantId, servletUrl, contextId, restServlet, initParams); - m_registeredServlets.add(servletUrl); - m_logService.log(LogService.LOG_DEBUG, "Wink application registered REST servlet '" + servletUrl + "'"); - - // Register a Wink servlet filter to forward requests from /rest/path to /rest/path/path - WinkServletFilter filter = new WinkServletFilter(servletUrl, servletUrl + "/" + restPath); - registerFilter(servletUrl, contextId, filter); - m_logService.log(LogService.LOG_DEBUG, "Wink application registered REST servlet filter on '" + servletUrl - + "'"); - } - } - - private void removeRestServlet(Object service) { - synchronized (m_registeredServlets) { - String restPath = service.getClass().getAnnotation(Path.class).value(); - String servletUrl = Activator.ALIAS + "/" + restPath; - if (m_registeredServlets.contains(servletUrl)) { - m_httpService.unregister(servletUrl); - m_registeredServlets.remove(servletUrl); - - m_logService.log(LogService.LOG_DEBUG, "Wink application unregistered REST servlet '" + servletUrl - + "'"); - } - } - } - - private String getTenantId(ServiceReference ref) { - if (ref != null && ref.getProperty(Tenant.TENANT_ID_SERVICEPROPERTY) != null) { - return (String) ref.getProperty(Tenant.TENANT_ID_SERVICEPROPERTY); - } - return null; - } - - private void registerServlet(String tenantId, String alias, String contextId, Servlet servlet, - Dictionary<String, String> initParams) { - Properties properties = new Properties(); - if (initParams != null) { - Enumeration<String> keys = initParams.keys(); - while (keys.hasMoreElements()) { - String key = keys.nextElement(); - properties.put(key, initParams.get(key)); - } - } - properties.put("alias", alias); - properties.put("contextId", contextId); - - if (tenantId != null) { - properties.put(Tenant.TENANT_ID_SERVICEPROPERTY, tenantId); - } - - m_dependencyManager.add(m_dependencyManager.createComponent() - .setImplementation(servlet) - .setInterface(Servlet.class.getName(), properties) - .add(m_dependencyManager.createServiceDependency().setService(LogService.class).setRequired(true)) - .setCallbacks("_init", "start", "stop", "_destroy")); - } - - private void registerFilter(String alias, String contextId, Filter filter) { - Dictionary<String, Object> filterProperties = new Hashtable<String, Object>(); - // Map filter on /rest/path OR /rest/path?... OR /rest/path/... - filterProperties.put("pattern", alias + "(|\\?.*|/.*)"); - filterProperties.put(Constants.SERVICE_RANKING, 0); - filterProperties.put("contextId", contextId); - m_dependencyManager.add(m_dependencyManager.createComponent() - .setImplementation(filter) - .setInterface(new String[] { Filter.class.getName() }, filterProperties) - .add(m_dependencyManager.createServiceDependency().setService(LogService.class).setRequired(true))); - } - private String getStringProperty(ServiceReference ref, String key) { Object value = ref.getProperty(key); return (value instanceof String) ? (String) value : ""; Modified: branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkRestServlet.java ============================================================================== --- branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkRestServlet.java (original) +++ branches/AMDATU-283-dev/amdatu-web/rest-wink/src/main/java/org/amdatu/web/rest/wink/service/WinkRestServlet.java Wed Apr 6 17:06:21 2011 @@ -20,9 +20,8 @@ import javax.servlet.ServletConfig; import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import org.apache.wink.server.internal.RequestProcessor; @@ -36,12 +35,13 @@ * is not changed when a REST servlet is unregistered/registerd, the request processor instance is the same for * all REST servlets and uneffected. This overridden implementation forces the request processor to be tight to * the servlet instance. + * * @author ivol */ public class WinkRestServlet extends RestServlet { - // The serial version UID of this HTTP servlet - private static final long serialVersionUID = 8797036173835816706L; + private final String m_resourceName; + // Indicates if this REST servlet has been initialized boolean m_initialized = false; @@ -51,16 +51,19 @@ /** * Constructor. + * * @param servletUrl The URL which serves this servlet */ public WinkRestServlet(String servletUrl, Object service, String tenantId) { super(); + m_resourceName = servletUrl; m_requestProcessorId = servletUrl + "_" + tenantId; m_service = service; } + @Override protected void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) - throws ServletException, IOException { + throws ServletException, IOException { // The next line seems to do nothing, but it does. Without requesting parameter names here, using both // @FormParam JAX-RS annotations in a REST resource as well as getParameterNames() in the same @PUT // or @POST annotated method will not work (getParameterNames() will return an empty map without the @@ -69,21 +72,7 @@ // works fine; when the input stream has already been read and written to the parameter map the // FormParam handler binds the values from this map instead of reading them from the input stream. httpServletRequest.getParameterNames(); - - super.service(httpServletRequest, httpServletResponse); - } - - public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { - // The next line seems to do nothing, but it does. Without requesting parameter names here, using both - // @FormParam JAX-RS annotations in a REST resource as well as getParameterNames() in the same @PUT - // or @POST annotated method will not work (getParameterNames() will return an empty map without the - // posted parameters). This is because the InputStream in that case is read by the FormParam handler - // first, which does not write the read from parameters to the parameter map. The other way around - // works fine; when the input stream has already been read and written to the parameter map the - // FormParam handler binds the values from this map instead of reading them from the input stream. - request.getParameterNames(); - - super.service(request, response); + super.service(new WinkResourceRequestWrapper(httpServletRequest, m_resourceName), httpServletResponse); } protected RequestProcessor getRequestProcessor() { @@ -91,7 +80,8 @@ // Override this method to enforce creation of a new requestprocessor whenever a servlet is created m_initialized = true; return null; - } else { + } + else { return RequestProcessor.getRequestProcessor(getServletContext(), m_requestProcessorId); } } @@ -104,4 +94,26 @@ super.init(config); RegistrationUtils.registerInstances(getServletContext(), m_requestProcessorId, m_service); } + + private final static class WinkResourceRequestWrapper extends HttpServletRequestWrapper { + + private final String m_resourcePath; + + public WinkResourceRequestWrapper(HttpServletRequest req, String resourcePath) { + super(req); + if (!resourcePath.startsWith("/")) + resourcePath = "/" + resourcePath; + m_resourcePath = resourcePath; + } + + @Override + public String getPathInfo() { + return m_resourcePath + super.getPathInfo(); + } + + @Override + public String getRequestURI() { + return super.getServletPath() + getPathInfo(); + } + } } _______________________________________________ Amdatu-commits mailing list [email protected] http://lists.amdatu.org/mailman/listinfo/amdatu-commits
