Clover icon

Magnolia REST Integration 2.1.3

  1. Project Clover database Fri Oct 25 2019 12:33:17 CEST
  2. Package info.magnolia.rest

File RestJerseyDispatcherServlet.java

 

Coverage histogram

../../../img/srcFileCovDistChart8.png
23% of files have more coverage

Code metrics

22
98
12
1
302
199
25
0.26
8.17
12
2.08

Classes

Class Line # Actions
RestJerseyDispatcherServlet 74 98 0% 25 33
0.7575%
 

Contributing tests

This file is covered by 4 tests. .

Source view

1    /**
2    * This file Copyright (c) 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.rest.registry.EndpointDefinitionRegistry;
42    import info.magnolia.rest.registry.EndpointDefinitionRegistryEvent;
43    import info.magnolia.rest.registry.EndpointDefinitionRegistryEventHandler;
44   
45    import java.io.File;
46    import java.lang.reflect.Method;
47    import java.util.Arrays;
48    import java.util.Map;
49    import java.util.Set;
50    import java.util.concurrent.ConcurrentHashMap;
51    import java.util.stream.Collectors;
52   
53    import javax.inject.Inject;
54    import javax.inject.Named;
55    import javax.servlet.ServletException;
56    import javax.ws.rs.Path;
57    import javax.ws.rs.ext.Provider;
58   
59    import org.apache.commons.collections4.CollectionUtils;
60    import org.apache.commons.lang3.StringUtils;
61    import org.glassfish.jersey.server.ResourceConfig;
62    import org.glassfish.jersey.server.model.Resource;
63    import org.glassfish.jersey.server.model.ResourceMethod;
64    import org.glassfish.jersey.server.model.ResourceModel;
65    import org.glassfish.jersey.servlet.ServletContainer;
66    import org.glassfish.jersey.servlet.WebConfig;
67    import org.reflections.Reflections;
68    import org.slf4j.Logger;
69    import org.slf4j.LoggerFactory;
70   
71    /**
72    * Custom Jersey Dispatcher which handles endpoint registrations.
73    */
 
74    public class RestJerseyDispatcherServlet extends ServletContainer implements EndpointDefinitionRegistryEventHandler {
75   
76    private static final Logger log = LoggerFactory.getLogger(RestJerseyDispatcherServlet.class);
77   
78    private final RestIntegrationModule restIntegrationModule;
79    private final EndpointDefinitionRegistry endpointRegistry;
80    private final EventBus systemEventBus;
81    private final Map<String, Object> endpoints = new ConcurrentHashMap<>();
82    private final ComponentProvider componentProvider;
83   
84    private HandlerRegistration registerHandler;
85   
 
86  5 toggle @Inject
87    public RestJerseyDispatcherServlet(final RestIntegrationModule restIntegrationModule, EndpointDefinitionRegistry endpointRegistry, @Named(SystemEventBus.NAME) EventBus systemEventBus, ComponentProvider componentProvider) {
88  5 super(new ResourceConfig());
89   
90  5 this.restIntegrationModule = restIntegrationModule;
91  5 this.endpointRegistry = endpointRegistry;
92  5 this.systemEventBus = systemEventBus;
93  5 this.componentProvider = componentProvider;
94    }
95   
 
96  4 toggle @Override
97    public void init(WebConfig webConfig) throws ServletException {
98  4 super.init(webConfig);
99   
100  4 ResourceConfig config = new ResourceConfig();
101   
102  4 Reflections reflection = new Reflections("info.magnolia.rest");
103  4 final Set<Class<?>> providers = reflection.getTypesAnnotatedWith(Provider.class);
104  4 config.registerClasses(providers);
105   
106  4 log.info("Registered scanned Provider classes: {}", providers.stream().map(c -> c.getName()).collect(Collectors.joining(", ")));
107   
108    // Register all currently registered endpoints
109  4 for (DefinitionProvider<EndpointDefinition> provider : endpointRegistry.getAllProviders()) {
110  10 try {
111  10 registerEndpoint(provider, config);
112    } catch (Exception e) {
113  3 log.error("Failed to register endpoint [{}]", provider.getMetadata().getReferenceId(), e);
114    }
115    }
116   
117  4 reload(config);
118   
119    // Listen for changes to the registry to observe endpoints being added or removed
120  4 registerHandler = systemEventBus.addHandler(EndpointDefinitionRegistryEvent.class, this);
121    }
122   
 
123  0 toggle @Override
124    public void destroy() {
125  0 super.destroy();
126  0 registerHandler.removeHandler();
127  0 endpoints.clear();
128  0 log.info("RestJerseyDispatcherServlet disposed.");
129    }
130   
131    // EVENT HANDLING
132   
 
133  1 toggle @Override
134    public void onEndpointRegistered(EndpointDefinitionRegistryEvent event) {
135  1 DefinitionProvider<EndpointDefinition> provider = event.getEndpointDefinitionProvider();
136  1 final String referenceId = provider.getMetadata().getReferenceId();
137  1 if (endpoints.containsKey(referenceId)) {
138  1 unregisterEndpoint(referenceId);
139    }
140  1 registerEndpoint(provider, null);
141    }
142   
 
143  0 toggle @Override
144    public void onEndpointReregistered(EndpointDefinitionRegistryEvent event) {
145  0 DefinitionProvider<EndpointDefinition> provider = event.getEndpointDefinitionProvider();
146  0 unregisterEndpoint(provider.getMetadata().getReferenceId());
147  0 registerEndpoint(provider, null);
148    }
149   
 
150  0 toggle @Override
151    public void onEndpointUnregistered(EndpointDefinitionRegistryEvent event) {
152  0 unregisterEndpoint(event.getEndpointName());
153    }
154   
 
155  14 toggle protected Object registerEndpoint(DefinitionProvider<EndpointDefinition> provider, ResourceConfig config) {
156  14 if (provider.isValid()) {
157  14 boolean registerLocally = config == null;
158  14 if (registerLocally) {
159  4 final ResourceConfig originalConfig = getConfiguration();
160  4 config = new ResourceConfig();
161  4 config.registerClasses(originalConfig.getClasses());
162  4 config.registerResources(originalConfig.getResources());
163  4 for (Object i : originalConfig.getInstances()) {
164  11 config.register(i);
165    }
166   
167    }
168  14 EndpointDefinition endpointDefinition = provider.get();
169  14 String endpointReferenceId = provider.getMetadata().getReferenceId();
170   
171  14 if (endpointDefinition.isEnabled()) {
172  14 Object endpoint = instantiateEndpoint(endpointDefinition);
173  14 endpoints.put(endpointReferenceId, endpoint);
174   
175  11 if (supportDynamicPath(endpoint.getClass())) {
176   
177  3 String path;
178  3 if (StringUtils.isNotEmpty(endpointDefinition.getEndpointPath())) {
179  1 path = truncatePath(endpointDefinition.getEndpointPath());
180    } else {
181  2 path = getBasePath(endpointReferenceId);
182    }
183   
184  3 final Resource.Builder resourceBuilder = Resource.builder(path);
185   
186  3 ResourceModel.Builder builder = new ResourceModel.Builder(false);
187  3 builder.addResource(Resource.from(endpointDefinition.getImplementationClass()));
188  3 final ResourceModel resourceModel = builder.build();
189   
190  3 for (Resource c : resourceModel.getResources()) {
191  3 if (CollectionUtils.isNotEmpty(c.getResourceMethods())) {
192  3 for (ResourceMethod m : c.getResourceMethods()) {
193  3 final Method handlingMethod = m.getInvocable().getHandlingMethod();
194  3 log.debug("Add path {}", c.getPath());
195  3 resourceBuilder.addChildResource(c.getPath())
196    .addMethod(m)
197    .handledBy(endpoint, handlingMethod);
198    }
199    }
200  3 for (Resource cr : c.getChildResources()) {
201  0 for (ResourceMethod m : cr.getResourceMethods()) {
202  0 final Method handlingMethod = m.getInvocable().getHandlingMethod();
203  0 final Path annotation = handlingMethod.getAnnotation(Path.class);
204  0 final String relativePath = new String(c.getPath() + annotation.value()).replaceAll("//", "/");
205  0 log.debug("Add child path {}", relativePath);
206  0 resourceBuilder.addChildResource(relativePath)
207    .addMethod(m)
208    .handledBy(endpoint, handlingMethod);
209    }
210    }
211    }
212   
213  3 config.registerResources(resourceBuilder.build());
214    } else {
215  8 config.register(endpoint);
216    }
217   
218  11 if (registerLocally) {
219  4 reload(config);
220    }
221   
222  11 return endpoint;
223    } else {
224  0 log.info("Endpoint {} is disabled, skipping registration", endpointReferenceId);
225    }
226    }
227   
228  0 return null;
229    }
230   
 
231  1 toggle protected void unregisterEndpoint(String endpointReferenceId) {
232  1 Object endpoint = endpoints.remove(endpointReferenceId);
233   
234  1 if (endpoint == null) {
235  0 return;
236    }
237   
238  1 Class<?> endpointClass = endpoint.getClass();
239  1 ResourceConfig config = new ResourceConfig();
240  1 ResourceConfig orgConfig = getConfiguration();
241   
242  1 if (supportDynamicPath(endpointClass) && endpoint instanceof AbstractEndpoint) {
243  0 String configuredPath = ((AbstractEndpoint) endpoint).getEndpointDefinition().getEndpointPath();
244  0 String path = StringUtils.isEmpty(configuredPath) ? getBasePath(endpointReferenceId) : truncatePath(configuredPath);
245   
246    // re-register legacy resource except the un-registered one.
247  0 orgConfig.getResources().stream()
248    .filter(r -> !r.getPath().startsWith(path))
249    .forEach(r -> config.registerResources(r));
250   
251    // non-dynamic endpoints kept as is.
252  0 orgConfig.getInstances().forEach(i -> config.register(i));
253   
254  0 log.debug("Unregister endpoint {} with base path {} from registry.", endpointReferenceId, path);
255    } else {
256   
257    // re-register instances except removed one.
258  1 orgConfig.getInstances().stream()
259    .filter(instance -> instance != endpoint)
260    .forEach(instance -> config.register(instance));
261   
262    // keep resources as is
263  1 orgConfig.getResources().forEach(r -> config.registerResources(r));
264   
265  1 log.debug("Unregister endpoint which has reference id: {} from registry.", endpointReferenceId);
266    }
267  1 config.registerClasses(orgConfig.getClasses());
268  1 reload(config);
269    }
270   
 
271  14 toggle protected Object instantiateEndpoint(EndpointDefinition endpointDefinition) {
272  14 return componentProvider.newInstance(endpointDefinition.getImplementationClass(), endpointDefinition);
273    }
274   
275    /**
276    * Extract base path from preference id and definition path.
277    * Only applicable for {@linkplain DynamicPath} annotated endpoint class.
278    * Convention:
279    * /module_name/restEndpoints/p1/p2/p3/def_v1.yaml -> {context}/p1/p2/p3/def/v1
280    * /module_name/restEndpoints/p1/p2/p3/defv1.yaml -> {context}/p1/p2/p3/defv1
281    * /module_name/restEndpoints/def_v1.yaml -> {context}/def/v1
282    * /module_name/restEndpoints/defv1.yaml -> {context}/defv1
283    *
284    * @return base path
285    */
 
286  2 toggle protected String getBasePath(String endpointReferenceId) {
287  2 final String[] endpointPaths = StringUtils.split(endpointReferenceId, File.separator);
288  2 endpointPaths[endpointPaths.length - 1] = endpointPaths[endpointPaths.length - 1].replace('_', '/');
289  2 return Arrays.stream(endpointPaths).collect(Collectors.joining("/"));
290    }
291   
 
292  12 toggle private boolean supportDynamicPath(final Class<?> implementationClass) {
293  12 return implementationClass != null && implementationClass.getAnnotation(DynamicPath.class) != null;
294    }
295   
 
296  1 toggle private String truncatePath(String configuredPath) {
297  1 String path = StringUtils.removeEnd(configuredPath, "/");
298  1 path = StringUtils.removeStart(path, "/");
299  1 return path;
300    }
301   
302    }