1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
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 @Inject
87 public RestJerseyDispatcherServlet(final RestIntegrationModule restIntegrationModule, EndpointDefinitionRegistry endpointRegistry, @Named(SystemEventBus.NAME) EventBus systemEventBus, ComponentProvider componentProvider) {
88 super(new ResourceConfig());
89
90 this.restIntegrationModule = restIntegrationModule;
91 this.endpointRegistry = endpointRegistry;
92 this.systemEventBus = systemEventBus;
93 this.componentProvider = componentProvider;
94 }
95
96 @Override
97 public void init(WebConfig webConfig) throws ServletException {
98 super.init(webConfig);
99
100 ResourceConfig config = new ResourceConfig();
101
102 Reflections reflection = new Reflections("info.magnolia.rest");
103 final Set<Class<?>> providers = reflection.getTypesAnnotatedWith(Provider.class);
104 config.registerClasses(providers);
105
106 log.info("Registered scanned Provider classes: {}", providers.stream().map(c -> c.getName()).collect(Collectors.joining(", ")));
107
108
109 for (DefinitionProvider<EndpointDefinition> provider : endpointRegistry.getAllProviders()) {
110 try {
111 registerEndpoint(provider, config);
112 } catch (Exception e) {
113 log.error("Failed to register endpoint [{}]", provider.getMetadata().getReferenceId(), e);
114 }
115 }
116
117 reload(config);
118
119
120 registerHandler = systemEventBus.addHandler(EndpointDefinitionRegistryEvent.class, this);
121 }
122
123 @Override
124 public void destroy() {
125 super.destroy();
126 registerHandler.removeHandler();
127 endpoints.clear();
128 log.info("RestJerseyDispatcherServlet disposed.");
129 }
130
131
132
133 @Override
134 public void onEndpointRegistered(EndpointDefinitionRegistryEvent event) {
135 DefinitionProvider<EndpointDefinition> provider = event.getEndpointDefinitionProvider();
136 final String referenceId = provider.getMetadata().getReferenceId();
137 if (endpoints.containsKey(referenceId)) {
138 unregisterEndpoint(referenceId);
139 }
140 registerEndpoint(provider, null);
141 }
142
143 @Override
144 public void onEndpointReregistered(EndpointDefinitionRegistryEvent event) {
145 DefinitionProvider<EndpointDefinition> provider = event.getEndpointDefinitionProvider();
146 unregisterEndpoint(provider.getMetadata().getReferenceId());
147 registerEndpoint(provider, null);
148 }
149
150 @Override
151 public void onEndpointUnregistered(EndpointDefinitionRegistryEvent event) {
152 unregisterEndpoint(event.getEndpointName());
153 }
154
155 protected Object registerEndpoint(DefinitionProvider<EndpointDefinition> provider, ResourceConfig config) {
156 if (provider.isValid()) {
157 boolean registerLocally = config == null;
158 if (registerLocally) {
159 final ResourceConfig originalConfig = getConfiguration();
160 config = new ResourceConfig();
161 config.registerClasses(originalConfig.getClasses());
162 config.registerResources(originalConfig.getResources());
163 for (Object i : originalConfig.getInstances()) {
164 config.register(i);
165 }
166
167 }
168 EndpointDefinition endpointDefinition = provider.get();
169 String endpointReferenceId = provider.getMetadata().getReferenceId();
170
171 if (endpointDefinition.isEnabled()) {
172 Object endpoint = instantiateEndpoint(endpointDefinition);
173 endpoints.put(endpointReferenceId, endpoint);
174
175 if (supportDynamicPath(endpoint.getClass())) {
176
177 String path;
178 if (StringUtils.isNotEmpty(endpointDefinition.getEndpointPath())) {
179 path = truncatePath(endpointDefinition.getEndpointPath());
180 } else {
181 path = getBasePath(endpointReferenceId);
182 }
183
184 final Resource.Builder resourceBuilder = Resource.builder(path);
185
186 ResourceModel.Builder builder = new ResourceModel.Builder(false);
187 builder.addResource(Resource.from(endpointDefinition.getImplementationClass()));
188 final ResourceModel resourceModel = builder.build();
189
190 for (Resource c : resourceModel.getResources()) {
191 if (CollectionUtils.isNotEmpty(c.getResourceMethods())) {
192 for (ResourceMethod m : c.getResourceMethods()) {
193 final Method handlingMethod = m.getInvocable().getHandlingMethod();
194 log.debug("Add path {}", c.getPath());
195 resourceBuilder.addChildResource(c.getPath())
196 .addMethod(m)
197 .handledBy(endpoint, handlingMethod);
198 }
199 }
200 for (Resource cr : c.getChildResources()) {
201 for (ResourceMethod m : cr.getResourceMethods()) {
202 final Method handlingMethod = m.getInvocable().getHandlingMethod();
203 final Path annotation = handlingMethod.getAnnotation(Path.class);
204 final String relativePath = new String(c.getPath() + annotation.value()).replaceAll("//", "/");
205 log.debug("Add child path {}", relativePath);
206 resourceBuilder.addChildResource(relativePath)
207 .addMethod(m)
208 .handledBy(endpoint, handlingMethod);
209 }
210 }
211 }
212
213 config.registerResources(resourceBuilder.build());
214 } else {
215 config.register(endpoint);
216 }
217
218 if (registerLocally) {
219 reload(config);
220 }
221
222 return endpoint;
223 } else {
224 log.info("Endpoint %s is disabled, skipping registration", endpointReferenceId);
225 }
226 }
227
228 return null;
229 }
230
231 protected void unregisterEndpoint(String endpointReferenceId) {
232 Object endpoint = endpoints.remove(endpointReferenceId);
233
234 if (endpoint == null) {
235 return;
236 }
237
238 Class<?> endpointClass = endpoint.getClass();
239 ResourceConfig config = new ResourceConfig();
240 ResourceConfig orgConfig = getConfiguration();
241
242 if (supportDynamicPath(endpointClass) && endpoint instanceof AbstractEndpoint) {
243 String configuredPath = ((AbstractEndpoint) endpoint).getEndpointDefinition().getEndpointPath();
244 String path = StringUtils.isEmpty(configuredPath) ? getBasePath(endpointReferenceId) : truncatePath(configuredPath);
245
246
247 orgConfig.getResources().stream()
248 .filter(r -> !r.getPath().startsWith(path))
249 .forEach(r -> config.registerResources(r));
250
251
252 orgConfig.getInstances().forEach(i -> config.register(i));
253
254 log.debug("Unregister endpoint {} with base path {} from registry.", endpointReferenceId, path);
255 } else {
256
257
258 orgConfig.getInstances().stream()
259 .filter(instance -> instance != endpoint)
260 .forEach(instance -> config.register(instance));
261
262
263 orgConfig.getResources().forEach(r -> config.registerResources(r));
264
265 log.debug("Unregister endpoint which has reference id: {} from registry.", endpointReferenceId);
266 }
267 config.registerClasses(orgConfig.getClasses());
268 reload(config);
269 }
270
271 protected Object instantiateEndpoint(EndpointDefinition endpointDefinition) {
272 return componentProvider.newInstance(endpointDefinition.getImplementationClass(), endpointDefinition);
273 }
274
275
276
277
278
279
280
281
282
283
284
285
286 protected String getBasePath(String endpointReferenceId) {
287 final String[] endpointPaths = StringUtils.split(endpointReferenceId, File.separator);
288 endpointPaths[endpointPaths.length - 1] = endpointPaths[endpointPaths.length - 1].replace('_', '/');
289 return Arrays.stream(endpointPaths).collect(Collectors.joining("/"));
290 }
291
292 private boolean supportDynamicPath(final Class<?> implementationClass) {
293 return implementationClass != null && implementationClass.getAnnotation(DynamicPath.class) != null;
294 }
295
296 private String truncatePath(String configuredPath) {
297 String path = StringUtils.removeEnd(configuredPath, "/");
298 path = StringUtils.removeStart(path, "/");
299 return path;
300 }
301
302 }