View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2015 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.caravan.jaxrs.publisher.impl;
21  
22  import java.io.IOException;
23  import java.util.Set;
24  
25  import javax.servlet.Servlet;
26  import javax.servlet.ServletException;
27  import javax.servlet.ServletRequest;
28  import javax.servlet.ServletResponse;
29  import javax.servlet.http.HttpServlet;
30  
31  import org.glassfish.jersey.server.ResourceConfig;
32  import org.glassfish.jersey.servlet.ServletContainer;
33  import org.osgi.framework.Bundle;
34  import org.osgi.framework.BundleContext;
35  import org.osgi.framework.Constants;
36  import org.osgi.framework.ServiceReference;
37  import org.osgi.service.component.ComponentContext;
38  import org.osgi.service.component.annotations.Activate;
39  import org.osgi.service.component.annotations.Component;
40  import org.osgi.service.component.annotations.Deactivate;
41  import org.osgi.util.tracker.ServiceTracker;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  import com.google.common.collect.Sets;
46  
47  import io.wcm.caravan.jaxrs.publisher.JaxRsClassesProvider;
48  import io.wcm.caravan.jaxrs.publisher.JaxRsComponent;
49  
50  /**
51   * Servlet bridge that wraps a Jersy JAX-RS ServletContainer and ensures the JAX-RS context is reloaded
52   * when service registrations (local or global) change.
53   * It automatically registers JAX-RS components from the current bundle and instances
54   * from global JAX-RS components factories.
55   */
56  @Component(service = Servlet.class, factory = ServletContainerBridge.SERVLETCONTAINER_BRIDGE_FACTORY)
57  public class ServletContainerBridge extends HttpServlet {
58    private static final long serialVersionUID = 1L;
59  
60    static final String SERVLETCONTAINER_BRIDGE_FACTORY = "caravan.jaxrs.servletcontainer.bridge.factory";
61    static final String PROPERTY_BUNDLE_ID = "caravan.jaxrs.relatedBundleId";
62  
63    private BundleContext bundleContext;
64    private Bundle bundle;
65    private JaxRsApplication application;
66    private ServletContainer servletContainer;
67    private volatile boolean isDirty;
68    private Set<JaxRsComponent> localComponents;
69    private Set<JaxRsComponent> globalComponents;
70    private ServiceTracker<JaxRsComponent, Object> jaxRsComponentTracker;
71    private Set<JaxRsClassesProvider> localClassesProviders;
72    private Set<JaxRsClassesProvider> globalClassesProviders;
73    private ServiceTracker<JaxRsClassesProvider, Object> jaxClassesProviderTracker;
74  
75    static final Logger log = LoggerFactory.getLogger(ServletContainerBridge.class);
76  
77    @Activate
78    void activate(ComponentContext componentContext) {
79      // bundle which contains the JAX-RS services
80      bundle = componentContext.getBundleContext().getBundle(
81          (Long)componentContext.getProperties().get(PROPERTY_BUNDLE_ID));
82      bundleContext = bundle.getBundleContext();
83  
84      // initialize component tracker to detect local and global JAX-RS components for current bundle
85      localComponents = Sets.newConcurrentHashSet();
86      globalComponents = Sets.newConcurrentHashSet();
87      jaxRsComponentTracker = new JaxRsComponentTracker(this);
88      jaxRsComponentTracker.open();
89  
90      // initialize component tracker to detect local and global additionall JAX-RS classes
91      localClassesProviders = Sets.newConcurrentHashSet();
92      globalClassesProviders = Sets.newConcurrentHashSet();
93      jaxClassesProviderTracker = new JaxRsClassesProviderTracker(this);
94      jaxClassesProviderTracker.open();
95    }
96  
97    @Deactivate
98    void deactivate() {
99      if (jaxRsComponentTracker != null) {
100       jaxRsComponentTracker.close();
101     }
102     if (jaxClassesProviderTracker != null) {
103       jaxClassesProviderTracker.close();
104     }
105   }
106 
107   @Override
108   public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
109     reloadIfDirty();
110     // delegate all calls to jersey servlet container
111     servletContainer.service(request, response);
112   }
113 
114   @Override
115   public void init() throws ServletException {
116     // initialize JAX-RS application and Jersey Servlet container
117     application = new JaxRsApplication(localComponents, globalComponents,
118         localClassesProviders, globalClassesProviders);
119     servletContainer = new ServletContainer(ResourceConfig.forApplication(application));
120     servletContainer.init(getServletConfig());
121   }
122 
123   @Override
124   public void destroy() {
125     servletContainer.destroy();
126   }
127 
128   /**
129    * Checks if components where added or removed.
130    * If yes the jersey servlet container is reloaded with the new configuration, blocking all other calls in the
131    * meantime.
132    */
133   private void reloadIfDirty() {
134     if (isDirty) {
135       synchronized (this) {
136         if (isDirty) {
137           // reload configuration if any service requests have changed
138           log.debug("Reload JAX-RS servlet container of {}", bundle.getSymbolicName());
139           servletContainer.reload(ResourceConfig.forApplication(application));
140           isDirty = false;
141         }
142       }
143     }
144   }
145 
146   @Override
147   public String toString() {
148     return "jaxrs-servlet:" + bundle.getSymbolicName();
149   }
150 
151   BundleContext getBundleContext() {
152     return this.bundleContext;
153   }
154 
155   Bundle getBundle() {
156     return this.bundle;
157   }
158 
159   Set<JaxRsComponent> getLocalComponents() {
160     return this.localComponents;
161   }
162 
163   Set<JaxRsComponent> getGlobalComponents() {
164     return this.globalComponents;
165   }
166 
167   Set<JaxRsClassesProvider> getLocalClassesProviders() {
168     return this.localClassesProviders;
169   }
170 
171   Set<JaxRsClassesProvider> getGlobalClassesProviders() {
172     return this.globalClassesProviders;
173   }
174 
175   void markAsDirty() {
176     this.isDirty = true;
177   }
178 
179   static boolean isJaxRsGlobal(ServiceReference<?> serviceReference) {
180     return "true".equals(serviceReference.getProperty(JaxRsComponent.PROPERTY_GLOBAL_COMPONENT))
181         && Constants.SCOPE_BUNDLE.equals(serviceReference.getProperty(Constants.SERVICE_SCOPE));
182   }
183 
184 }