Clover icon

Magnolia REST Integration 2.1

  1. Project Clover database Fri Mar 16 2018 18:18:02 CET
  2. Package info.magnolia.rest

File RestDispatcherServlet.java

 

Coverage histogram

../../../img/srcFileCovDistChart7.png
41% of files have more coverage

Code metrics

18
79
18
1
289
192
29
0.37
4.39
18
1.61
5% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
RestDispatcherServlet 73 79 5% 29 37
0.6782608667.8%
 

Contributing tests

This file is covered by 4 tests. .

Source view

1    /**
2    * This file Copyright (c) 2012-2018 Magnolia International
3    * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10    * This file is distributed in the hope that it will be
11    * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12    * implied warranty of MERCHANTABILITY or FITNESS FOR A
13    * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14    * Redistribution, except as permitted by whichever of the GPL
15    * or MNA you select, is prohibited.
16    *
17    * 1. For the GPL license (GPL), you can redistribute and/or
18    * modify this file under the terms of the GNU General
19    * Public License, Version 3, as published by the Free Software
20    * Foundation. You should have received a copy of the GNU
21    * General Public License, Version 3 along with this program;
22    * if not, write to the Free Software Foundation, Inc., 51
23    * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24    *
25    * 2. For the Magnolia Network Agreement (MNA), this file
26    * and the accompanying materials are made available under the
27    * terms of the MNA which accompanies this distribution, and
28    * is available at http://www.magnolia-cms.com/mna.html
29    *
30    * Any modifications to this file must keep this entire header
31    * intact.
32    *
33    */
34    package info.magnolia.rest;
35   
36    import info.magnolia.config.registry.DefinitionProvider;
37    import info.magnolia.event.EventBus;
38    import info.magnolia.event.HandlerRegistration;
39    import info.magnolia.event.SystemEventBus;
40    import info.magnolia.objectfactory.ComponentProvider;
41    import info.magnolia.objectfactory.Components;
42    import info.magnolia.rest.provider.AdditionalProviderDefinition;
43    import info.magnolia.rest.registry.EndpointDefinitionRegistry;
44    import info.magnolia.rest.registry.EndpointDefinitionRegistryEvent;
45    import info.magnolia.rest.registry.EndpointDefinitionRegistryEventHandler;
46   
47    import java.io.File;
48    import java.util.Arrays;
49    import java.util.HashSet;
50    import java.util.Map;
51    import java.util.Set;
52    import java.util.concurrent.ConcurrentHashMap;
53    import java.util.stream.Collectors;
54   
55    import javax.inject.Inject;
56    import javax.inject.Named;
57    import javax.servlet.ServletConfig;
58    import javax.servlet.ServletException;
59    import javax.ws.rs.core.Application;
60   
61    import org.apache.commons.lang3.StringUtils;
62    import org.jboss.resteasy.plugins.server.servlet.ConfigurationBootstrap;
63    import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;
64    import org.jboss.resteasy.plugins.server.servlet.ServletBootstrap;
65    import org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher;
66    import org.jboss.resteasy.spi.ResteasyDeployment;
67    import org.slf4j.Logger;
68    import org.slf4j.LoggerFactory;
69   
70    /**
71    * Dispatcher of the rest requests to the dynamically registered endpoints.
72    */
 
73    public class RestDispatcherServlet extends HttpServletDispatcher implements EndpointDefinitionRegistryEventHandler {
74   
75    private static final Logger log = LoggerFactory.getLogger(RestDispatcherServlet.class);
76   
77    private final RestIntegrationModule restIntegrationModule;
78    private final EndpointDefinitionRegistry endpointRegistry;
79    private final EventBus systemEventBus;
80    private final Map<String, Object> endpoints = new ConcurrentHashMap<>();
81    private final ComponentProvider componentProvider;
82   
83    private Application application = new Application() {
 
84    toggle @Override
85    public Set<Object> getSingletons() {
86    HashSet<Object> singletons = new HashSet<>();
87    singletons.addAll(endpoints.values());
88    return singletons;
89    }
90    };
91    private ServletConfig servletConfig;
92    private HandlerRegistration registerHandler;
93   
 
94  5 toggle @Inject
95    public RestDispatcherServlet(final RestIntegrationModule restIntegrationModule, EndpointDefinitionRegistry endpointRegistry, @Named(SystemEventBus.NAME) EventBus systemEventBus, ComponentProvider componentProvider) {
96  5 this.restIntegrationModule = restIntegrationModule;
97  5 this.endpointRegistry = endpointRegistry;
98  5 this.systemEventBus = systemEventBus;
99  5 this.componentProvider = componentProvider;
100    }
101   
 
102  0 toggle @Deprecated
103    public RestDispatcherServlet(final RestIntegrationModule restIntegrationModule, EndpointDefinitionRegistry endpointRegistry, @Named(SystemEventBus.NAME) EventBus systemEventBus) {
104  0 this(restIntegrationModule, endpointRegistry, systemEventBus, Components.getComponentProvider());
105    }
106   
 
107  4 toggle @Override
108    public void init(ServletConfig servletConfig) throws ServletException {
109  4 this.servletConfig = servletConfig;
110   
111    // Initialise the ServletContainerDispatcher
112  4 servletContainerDispatcher = new ServletContainerDispatcher();
113  4 ConfigurationBootstrap bootstrap = createBootstrap(servletConfig);
114  4 servletContainerDispatcher.init(servletConfig.getServletContext(), bootstrap, this, this);
115  4 servletContainerDispatcher.getDispatcher().getDefaultContextObjects().put(ServletConfig.class, servletConfig);
116   
117    // Register additional providers that have been configured
118  4 for (AdditionalProviderDefinition provider : restIntegrationModule.getAdditionalProviders()) {
119  0 log.debug("Registering additional provider [{}]", provider.getProviderClass());
120  0 super.getDispatcher().getProviderFactory().registerProvider(provider.getProviderClass());
121    }
122   
123    // Register all currently registered endpoints
124  4 for (DefinitionProvider<EndpointDefinition> provider : endpointRegistry.getAllProviders()) {
125  10 try {
126  10 registerEndpoint(provider);
127    } catch (Exception e) {
128  3 log.error("Failed to register endpoint [{}]", provider.getMetadata().getReferenceId(), e);
129    // Others should continue to be registered.
130    }
131    }
132   
133    // Listen for changes to the registry to observe endpoints being added or removed
134  4 registerHandler = systemEventBus.addHandler(EndpointDefinitionRegistryEvent.class, this);
135    }
136   
 
137  0 toggle @Override
138    public void destroy() {
139  0 registerHandler.removeHandler();
140  0 super.destroy();
141  0 endpoints.clear();
142    }
143   
144    // EVENT HANDLING
145   
 
146  1 toggle @Override
147    public void onEndpointRegistered(EndpointDefinitionRegistryEvent event) {
148  1 DefinitionProvider<EndpointDefinition> provider = event.getEndpointDefinitionProvider();
149  1 final String referenceId = provider.getMetadata().getReferenceId();
150  1 if (endpoints.containsKey(referenceId)) {
151  1 unregisterEndpoint(referenceId);
152    }
153  1 registerEndpoint(provider);
154    }
155   
 
156  0 toggle @Override
157    public void onEndpointReregistered(EndpointDefinitionRegistryEvent event) {
158  0 DefinitionProvider<EndpointDefinition> provider = event.getEndpointDefinitionProvider();
159  0 unregisterEndpoint(provider.getMetadata().getReferenceId());
160  0 registerEndpoint(provider);
161    }
162   
 
163  0 toggle @Override
164    public void onEndpointUnregistered(EndpointDefinitionRegistryEvent event) {
165  0 unregisterEndpoint(event.getEndpointName());
166    }
167   
168    // need to override this because we're not calling init(ServletConfig) on the super class
 
169    toggle @Override
170    public ServletConfig getServletConfig() {
171    return servletConfig;
172    }
173   
174    /**
175    * Allows an endpoint to be registered for the servlet without actually being in the registry.
176    * This is used for REST Tools in particular; otherwise {@link #registerEndpoint(DefinitionProvider)} should be preferred.
177    */
 
178  0 toggle protected Object registerEndpoint(EndpointDefinition endpointDefinition) {
179  0 if (!endpointDefinition.isEnabled()) {
180  0 return null;
181    }
182  0 Object endpoint = instantiateEndpoint(endpointDefinition);
183  0 endpoints.put(endpointDefinition.getName(), endpoint);
184  0 super.getDispatcher().getRegistry().addSingletonResource(endpoint);
185  0 return endpoint;
186    }
187   
 
188  14 toggle protected Object registerEndpoint(DefinitionProvider<EndpointDefinition> provider) {
189  14 if (!provider.isValid()) {
190  0 return null;
191    }
192   
193  14 EndpointDefinition endpointDefinition = provider.get();
194  14 String endpointReferenceId = provider.getMetadata().getReferenceId();
195   
196  14 if (!endpointDefinition.isEnabled()) {
197  0 log.info("Endpoint %s is disabled, skipping registration", endpointReferenceId);
198  0 return null;
199    }
200   
201  14 Object endpoint = instantiateEndpoint(endpointDefinition);
202   
203  14 if (supportDynamicPath(endpointDefinition.getImplementationClass())) {
204  3 String path;
205  3 if (StringUtils.isNotEmpty(endpointDefinition.getEndpointPath())) {
206  1 path = truncatePath(endpointDefinition.getEndpointPath());
207    } else {
208  2 path = getBasePath(endpointReferenceId);
209    }
210  3 super.getDispatcher().getRegistry().addSingletonResource(endpoint, path);
211  3 log.info("Endpoint {} is registered with base path: [context]/{}", endpointReferenceId, path);
212  3 endpoints.put(endpointReferenceId, endpoint);
213    } else {
214  11 super.getDispatcher().getRegistry().addSingletonResource(endpoint);
215  8 endpoints.put(endpointReferenceId, endpoint);
216    }
217   
218  11 return endpoint;
219    }
220   
 
221  1 toggle protected void unregisterEndpoint(String endpointReferenceId) {
222  1 Object endpoint = endpoints.remove(endpointReferenceId);
223  1 if (endpoint != null) {
224  1 Class<?> endpointClass = endpoint.getClass();
225  1 if (supportDynamicPath(endpointClass) && endpoint instanceof AbstractEndpoint) {
226  0 String configuredPath = ((AbstractEndpoint) endpoint).getEndpointDefinition().getEndpointPath();
227  0 String path = StringUtils.isEmpty(configuredPath) ? getBasePath(endpointReferenceId) : truncatePath(configuredPath);
228  0 super.getDispatcher().getRegistry().removeRegistrations(endpointClass, path);
229  0 log.debug("Unregister endpoint {} with base path {} from registry.", endpointReferenceId, path);
230    } else {
231  1 super.getDispatcher().getRegistry().removeRegistrations(endpointClass);
232  1 log.debug("Unregister endpoint which has reference id: {} from registry.", endpointReferenceId);
233    }
234   
235    }
236    }
237   
 
238  14 toggle protected Object instantiateEndpoint(EndpointDefinition endpointDefinition) {
239  14 return componentProvider.newInstance(endpointDefinition.getImplementationClass(), endpointDefinition);
240    }
241   
242    // DEPLOYMENT
243   
 
244  9 toggle protected Application getApplication() {
245  9 return application;
246    }
247   
 
248  4 toggle protected ConfigurationBootstrap createBootstrap(ServletConfig servletConfig) {
249  4 return new ServletBootstrap(servletConfig) {
 
250  4 toggle @Override
251    public ResteasyDeployment createDeployment() {
252  4 return configureDeployment(super.createDeployment());
253    }
254    };
255    }
256   
 
257  4 toggle protected ResteasyDeployment configureDeployment(ResteasyDeployment deployment) {
258  4 deployment.setApplicationClass(null);
259  4 deployment.setApplication(getApplication());
260  4 return deployment;
261    }
262   
263    /**
264    * Extract base path from preference id and definition path.
265    * Only applicable for {@linkplain DynamicPath} annotated endpoint class.
266    * Convention:
267    * /module_name/restEndpoints/p1/p2/p3/def_v1.yaml -> {context}/p1/p2/p3/def/v1
268    * /module_name/restEndpoints/p1/p2/p3/defv1.yaml -> {context}/p1/p2/p3/defv1
269    * /module_name/restEndpoints/def_v1.yaml -> {context}/def/v1
270    * /module_name/restEndpoints/defv1.yaml -> {context}/defv1
271    *
272    * @return base path
273    */
 
274  2 toggle protected String getBasePath(String endpointReferenceId) {
275  2 final String[] endpointPaths = StringUtils.split(endpointReferenceId, File.separator);
276  2 endpointPaths[endpointPaths.length - 1] = endpointPaths[endpointPaths.length - 1].replace('_', '/');
277  2 return Arrays.stream(endpointPaths).collect(Collectors.joining("/"));
278    }
279   
 
280  15 toggle private boolean supportDynamicPath(final Class<?> implementationClass) {
281  15 return implementationClass != null && implementationClass.getAnnotation(DynamicPath.class) != null;
282    }
283   
 
284  1 toggle private String truncatePath(String configuredPath) {
285  1 String path = StringUtils.removeEnd(configuredPath, "/");
286  1 path = StringUtils.removeStart(path, "/");
287  1 return path;
288    }
289    }