Java tutorial
/* * #%L * wcm.io * %% * Copyright (C) 2015 wcm.io * %% * Licensed 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. * #L% */ package io.wcm.caravan.jaxrs.publisher.impl; import java.io.IOException; import java.util.Collection; import java.util.Set; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.ws.rs.Path; import javax.ws.rs.container.PreMatching; import javax.ws.rs.ext.Provider; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Service; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Sets; import io.wcm.caravan.jaxrs.publisher.JaxRsComponent; /** * Servlet bridge that wraps a Jersy JAX-RS ServletContainer and ensures the JAX-RS context is reloaded * when service registrations (local or global) change. * It automatically registers JAX-RS components from the current bundle and instances * from global JAX-RS components factories. */ @Component(factory = ServletContainerBridge.SERVLETCONTAINER_BRIDGE_FACTORY) @Service(Servlet.class) public class ServletContainerBridge extends HttpServlet { private static final long serialVersionUID = 1L; static final String SERVLETCONTAINER_BRIDGE_FACTORY = "caravan.jaxrs.servletcontainer.bridge.factory"; static final String PROPERTY_BUNDLE = "caravan.jaxrs.relatedBundle"; private BundleContext bundleContext; private Bundle bundle; private JaxRsApplication application; private ServletContainer servletContainer; private volatile boolean isDirty; private Set<JaxRsComponent> localComponents; private Set<JaxRsComponent> globalComponents; private ServiceTracker localComponentTracker; private Collection<ServiceReference<JaxRsComponent>> globalJaxRSComponentReferences; private static final Logger log = LoggerFactory.getLogger(ServletContainerBridge.class); @Activate void activate(ComponentContext componentContext) { // bundle which contains the JAX-RS services bundle = (Bundle) componentContext.getProperties().get(PROPERTY_BUNDLE); bundleContext = bundle.getBundleContext(); // initialize component tracker to detect local and global JAX-RS components for current bundle localComponents = Sets.newConcurrentHashSet(); globalComponents = Sets.newConcurrentHashSet(); localComponentTracker = new JaxRsComponentTracker(); localComponentTracker.open(); // initialize JAX-RS application and Jersey Servlet container application = new JaxRsApplication(localComponents, globalComponents); servletContainer = new ServletContainer(ResourceConfig.forApplication(application)); } @Deactivate void deactivate(ComponentContext componentContext) { if (localComponentTracker != null) { localComponentTracker.close(); } if (globalJaxRSComponentReferences != null) { globalJaxRSComponentReferences.forEach(bundleContext::ungetService); } } @Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { reloadIfDirty(); // delegate all calls to jersey servlet container servletContainer.service(request, response); } @Override public void init() throws ServletException { servletContainer.init(); } @Override public void init(ServletConfig config) throws ServletException { servletContainer.init(config); } @Override public void destroy() { servletContainer.destroy(); } /** * Checks if components where added or removed. * If yes the jersey servlet container is reloaded with the new configuration, blocking all other calls in the * meantime. */ private void reloadIfDirty() { if (isDirty) { synchronized (this) { if (isDirty) { // reload configuration if any service requests have changed log.debug("Reload JAX-RS servlet container of {}", bundle.getSymbolicName()); servletContainer.reload(ResourceConfig.forApplication(application)); isDirty = false; } } } } @Override public String toString() { return "jaxrs-servlet:" + bundle.getSymbolicName(); } /** * Tracks JAX-RS components in the current bundle. */ private class JaxRsComponentTracker extends ServiceTracker<JaxRsComponent, Object> { JaxRsComponentTracker() { super(bundleContext, JaxRsComponent.class, null); } @Override public Object addingService(ServiceReference<JaxRsComponent> reference) { if (isJaxRsGlobal(reference)) { JaxRsComponent serviceInstance = bundle.getBundleContext().getService(reference); if (isJaxRsComponent(serviceInstance)) { log.debug("Register global component {} for {}", serviceInstance.getClass().getName(), bundle.getSymbolicName()); globalComponents.add(serviceInstance); isDirty = true; } } else if (reference.getBundle() == bundle) { JaxRsComponent serviceInstance = bundle.getBundleContext().getService(reference); if (isJaxRsComponent(serviceInstance)) { log.debug("Register component {} for {}", serviceInstance.getClass().getName(), bundle.getSymbolicName()); localComponents.add(serviceInstance); isDirty = true; } } return super.addingService(reference); } @Override public void removedService(ServiceReference<JaxRsComponent> reference, Object service) { if (isJaxRsGlobal(reference)) { JaxRsComponent serviceInstance = bundle.getBundleContext().getService(reference); if (isJaxRsComponent(serviceInstance)) { log.debug("Unregister global component {} for {}", serviceInstance.getClass().getName(), bundle.getSymbolicName()); globalComponents.remove(serviceInstance); bundleContext.ungetService(reference); isDirty = true; } } else if (reference.getBundle() == bundle) { JaxRsComponent serviceInstance = bundle.getBundleContext().getService(reference); if (isJaxRsComponent(serviceInstance)) { log.debug("Unregister component {} for {}", serviceInstance.getClass().getName(), bundle.getSymbolicName()); localComponents.remove(serviceInstance); bundleContext.ungetService(reference); isDirty = true; } } super.removedService(reference, service); } private boolean isJaxRsGlobal(ServiceReference<JaxRsComponent> serviceReference) { return "true".equals(serviceReference.getProperty(JaxRsComponent.PROPERTY_GLOBAL_COMPONENT)) && Constants.SCOPE_BUNDLE.equals(serviceReference.getProperty(Constants.SERVICE_SCOPE)); } private boolean isJaxRsComponent(Object serviceInstance) { return (serviceInstance instanceof JaxRsComponent && hasAnyJaxRsAnnotation(serviceInstance.getClass())); } private boolean hasAnyJaxRsAnnotation(Class<?> clazz) { return clazz.isAnnotationPresent(Path.class) || clazz.isAnnotationPresent(Provider.class) || clazz.isAnnotationPresent(PreMatching.class); } } }