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.cms.beans.config.ServerConfiguration;
37 import info.magnolia.config.registry.DefinitionMetadata;
38 import info.magnolia.config.registry.DefinitionMetadataBuilder;
39 import info.magnolia.config.registry.DefinitionProvider;
40 import info.magnolia.config.registry.DefinitionProviderBuilder;
41 import info.magnolia.event.EventBus;
42 import info.magnolia.event.HandlerRegistration;
43 import info.magnolia.event.SystemEventBus;
44 import info.magnolia.objectfactory.ComponentProvider;
45 import info.magnolia.objectfactory.Components;
46 import info.magnolia.rest.provider.AdditionalProviderDefinition;
47 import info.magnolia.rest.registry.EndpointDefinitionRegistry;
48 import info.magnolia.rest.registry.EndpointDefinitionRegistryEvent;
49 import info.magnolia.rest.registry.EndpointDefinitionRegistryEventHandler;
50
51 import java.io.File;
52 import java.util.Arrays;
53 import java.util.HashSet;
54 import java.util.Map;
55 import java.util.Objects;
56 import java.util.Set;
57 import java.util.concurrent.ConcurrentHashMap;
58 import java.util.stream.Collectors;
59
60 import javax.inject.Inject;
61 import javax.inject.Named;
62 import javax.servlet.ServletConfig;
63 import javax.servlet.ServletException;
64 import javax.ws.rs.core.Application;
65
66 import org.apache.commons.beanutils.BeanUtils;
67 import org.apache.commons.lang3.StringUtils;
68 import org.jboss.resteasy.plugins.server.servlet.ConfigurationBootstrap;
69 import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;
70 import org.jboss.resteasy.plugins.server.servlet.ServletBootstrap;
71 import org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher;
72 import org.jboss.resteasy.spi.ResteasyDeployment;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76
77
78
79 public class RestDispatcherServlet extends HttpServletDispatcher implements EndpointDefinitionRegistryEventHandler {
80
81 private static final Logger log = LoggerFactory.getLogger(RestDispatcherServlet.class);
82
83 private final RestIntegrationModule restIntegrationModule;
84 private final EndpointDefinitionRegistry endpointRegistry;
85 private final EventBus systemEventBus;
86 private final Map<String, Object> endpoints = new ConcurrentHashMap<>();
87 private final ComponentProvider componentProvider;
88 private final ServerConfiguration serverConfiguration;
89
90 private Application application = new Application() {
91 @Override
92 public Set<Object> getSingletons() {
93 HashSet<Object> singletons = new HashSet<>();
94 singletons.addAll(endpoints.values());
95 return singletons;
96 }
97 };
98 private ServletConfig servletConfig;
99 private HandlerRegistration registerHandler;
100
101 @Inject
102 public RestDispatcherServlet(final RestIntegrationModule restIntegrationModule, EndpointDefinitionRegistry endpointRegistry, @Named(SystemEventBus.NAME) EventBus systemEventBus, ComponentProvider componentProvider, ServerConfiguration serverConfiguration) {
103 this.restIntegrationModule = restIntegrationModule;
104 this.endpointRegistry = endpointRegistry;
105 this.systemEventBus = systemEventBus;
106 this.componentProvider = componentProvider;
107 this.serverConfiguration = serverConfiguration;
108 }
109
110 @Deprecated
111 public RestDispatcherServlet(final RestIntegrationModule restIntegrationModule, EndpointDefinitionRegistry endpointRegistry, @Named(SystemEventBus.NAME) EventBus systemEventBus) {
112 this(restIntegrationModule, endpointRegistry, systemEventBus, Components.getComponentProvider(), Components.getComponent(ServerConfiguration.class));
113 }
114
115 @Override
116 public void init(ServletConfig servletConfig) throws ServletException {
117 this.servletConfig = servletConfig;
118
119
120 servletContainerDispatcher = new ServletContainerDispatcher(servletConfig);
121 ConfigurationBootstrap bootstrap = createBootstrap(servletConfig);
122 servletContainerDispatcher.init(servletConfig.getServletContext(), bootstrap, this, this);
123
124
125 restIntegrationModule.getAdditionalProviders().stream()
126 .map(AdditionalProviderDefinition::getProviderClass)
127 .filter(Objects::nonNull)
128 .peek(providerClass -> log.debug("Registering additional provider [{}]", providerClass))
129 .forEach(providerClass -> super.getDispatcher().getProviderFactory().registerProvider(providerClass));
130
131
132 for (DefinitionProvider<EndpointDefinition> provider : endpointRegistry.getAllProviders()) {
133 try {
134 registerEndpoint(provider);
135 } catch (Exception e) {
136 log.error("Failed to register endpoint [{}]", provider.getMetadata().getReferenceId(), e);
137
138 }
139 }
140
141
142 registerHandler = systemEventBus.addHandler(EndpointDefinitionRegistryEvent.class, this);
143 }
144
145 private void registerPreviewEndpoint(final DefinitionProvider<EndpointDefinition> provider) {
146 final EndpointDefinition deliveryEndpointDefinition = provider.get();
147 final DefinitionMetadata deliveryMetadata = provider.getMetadata();
148 final String endpointReferenceId = deliveryMetadata.getReferenceId().replace("delivery", "preview");
149 final String path = deliveryEndpointDefinition.getEndpointPath();
150 try {
151 final DefinitionMetadata previewEndpointMetadata = DefinitionMetadataBuilder.usingNameAsId()
152 .name(endpointReferenceId)
153 .configurationSourceType(deliveryMetadata.getConfigurationSourceType())
154 .deprecation(deliveryMetadata.getDeprecation().isPresent() ? deliveryMetadata.getDeprecation().get() : null)
155 .location(deliveryMetadata.getLocation())
156 .module(deliveryMetadata.getModule())
157 .relativeLocation(deliveryMetadata.getRelativeLocation())
158 .type(deliveryMetadata.getType())
159 .build();
160 final EndpointDefinitionpointDefinition.html#EndpointDefinition">EndpointDefinition previewEndpointDefinition = (EndpointDefinition) BeanUtils.cloneBean(deliveryEndpointDefinition);
161 if (path != null) {
162 BeanUtils.setProperty(previewEndpointDefinition, "endpointPath", path.replace("delivery", "preview"));
163 }
164 final DefinitionProvider<EndpointDefinition> previewProvider = DefinitionProviderBuilder.<EndpointDefinition>newBuilder()
165 .metadata(previewEndpointMetadata)
166 .rawView(provider.getRaw())
167 .definition(previewEndpointDefinition)
168 .build();
169 registerEndpoint(previewProvider);
170
171 } catch (Exception e) {
172 log.warn("Unable register preview endpoint for {}", deliveryMetadata.getReferenceId(), e);
173 }
174 }
175
176 private boolean isDeliveryEndpoint(final DefinitionProvider<EndpointDefinition> provider) {
177 return getPath(provider).startsWith("delivery");
178 }
179
180 private String getPath(final DefinitionProvider<EndpointDefinition> provider) {
181 final EndpointDefinition endpointDefinition = provider.get();
182 String endpointReferenceId = provider.getMetadata().getReferenceId();
183 if (StringUtils.isNotEmpty(endpointDefinition.getEndpointPath())) {
184 return truncatePath(endpointDefinition.getEndpointPath());
185 } else {
186 return getBasePath(endpointReferenceId);
187 }
188 }
189
190
191 @Override
192 public ServletConfig getServletConfig() {
193 return servletConfig;
194 }
195
196 @Override
197 public void destroy() {
198 registerHandler.removeHandler();
199 super.destroy();
200 endpoints.clear();
201 }
202
203
204
205 @Override
206 public void onEndpointRegistered(EndpointDefinitionRegistryEvent event) {
207 DefinitionProvider<EndpointDefinition> provider = event.getEndpointDefinitionProvider();
208 final String referenceId = provider.getMetadata().getReferenceId();
209 if (endpoints.containsKey(referenceId)) {
210 unregisterEndpoint(referenceId);
211 }
212 registerEndpoint(provider);
213 }
214
215 @Override
216 public void onEndpointReregistered(EndpointDefinitionRegistryEvent event) {
217 DefinitionProvider<EndpointDefinition> provider = event.getEndpointDefinitionProvider();
218 unregisterEndpoint(provider.getMetadata().getReferenceId());
219 registerEndpoint(provider);
220 }
221
222 @Override
223 public void onEndpointUnregistered(EndpointDefinitionRegistryEvent event) {
224 unregisterEndpoint(event.getEndpointName());
225 }
226
227 protected Object registerEndpoint(DefinitionProvider<EndpointDefinition> provider) {
228 if (!provider.isValid()) {
229 return null;
230 }
231
232 EndpointDefinition endpointDefinition = provider.get();
233 String endpointReferenceId = provider.getMetadata().getReferenceId();
234
235 if (!endpointDefinition.isEnabled()) {
236 log.info("Endpoint {} is disabled, skipping registration", endpointReferenceId);
237 return null;
238 } else if (endpointDefinition.getImplementationClass() == null) {
239 log.warn("Endpoint {} is missing implementationClass, skipping registration", endpointReferenceId);
240 return null;
241 }
242
243 Object endpoint = instantiateEndpoint(endpointDefinition);
244
245 if (supportDynamicPath(endpointDefinition.getImplementationClass())) {
246 String path = getPath(provider);
247 super.getDispatcher().getRegistry().addSingletonResource(endpoint, path);
248 log.info("Endpoint {} is registered with base path: [context]/{}", endpointReferenceId, path);
249 } else {
250 super.getDispatcher().getRegistry().addSingletonResource(endpoint);
251 }
252 endpoints.put(endpointReferenceId, endpoint);
253
254 if (isDeliveryEndpoint(provider) && serverConfiguration.isAdmin()) {
255 registerPreviewEndpoint(provider);
256 }
257
258 return endpoint;
259 }
260
261
262
263
264
265 protected Object registerEndpoint(EndpointDefinition endpointDefinition) {
266 if (!endpointDefinition.isEnabled()) {
267 return null;
268 }
269 Object endpoint = instantiateEndpoint(endpointDefinition);
270 endpoints.put(endpointDefinition.getName(), endpoint);
271 super.getDispatcher().getRegistry().addSingletonResource(endpoint);
272 return endpoint;
273 }
274
275 protected void unregisterEndpoint(String endpointReferenceId) {
276 Object endpoint = endpoints.remove(endpointReferenceId);
277 if (endpoint != null) {
278 Class<?> endpointClass = endpoint.getClass();
279 if (supportDynamicPath(endpointClass) && endpoint instanceof AbstractEndpoint) {
280 String configuredPath = ((AbstractEndpoint) endpoint).getEndpointDefinition().getEndpointPath();
281 String path = StringUtils.isEmpty(configuredPath) ? getBasePath(endpointReferenceId) : truncatePath(configuredPath);
282 super.getDispatcher().getRegistry().removeRegistrations(endpointClass, path);
283 log.debug("Unregister endpoint {} with base path {} from registry.", endpointReferenceId, path);
284 } else {
285 super.getDispatcher().getRegistry().removeRegistrations(endpointClass);
286 log.debug("Unregister endpoint which has reference id: {} from registry.", endpointReferenceId);
287 }
288
289 }
290
291 if (endpointReferenceId.startsWith("delivery")) {
292 unregisterEndpoint(endpointReferenceId.replace("delivery", "preview"));
293 }
294 }
295
296 protected Object instantiateEndpoint(EndpointDefinition endpointDefinition) {
297 return componentProvider.newInstance(endpointDefinition.getImplementationClass(), endpointDefinition);
298 }
299
300
301
302 protected Application getApplication() {
303 return application;
304 }
305
306 protected ConfigurationBootstrap createBootstrap(ServletConfig servletConfig) {
307 return new ServletBootstrap(servletConfig) {
308 @Override
309 public ResteasyDeployment createDeployment() {
310 return configureDeployment(super.createDeployment());
311 }
312 };
313 }
314
315 protected ResteasyDeployment configureDeployment(ResteasyDeployment deployment) {
316 deployment.setApplicationClass(null);
317 deployment.setApplication(getApplication());
318 return deployment;
319 }
320
321
322
323
324
325
326
327
328
329
330
331
332 protected String getBasePath(String endpointReferenceId) {
333 final String[] endpointPaths = StringUtils.split(endpointReferenceId, File.separator);
334 endpointPaths[endpointPaths.length - 1] = endpointPaths[endpointPaths.length - 1].replace('_', '/');
335 return Arrays.stream(endpointPaths).collect(Collectors.joining("/"));
336 }
337
338 private boolean supportDynamicPath(final Class<?> implementationClass) {
339 return implementationClass != null && implementationClass.getAnnotation(DynamicPath.class) != null;
340 }
341
342 private String truncatePath(String configuredPath) {
343 String path = StringUtils.removeEnd(configuredPath, "/");
344 path = StringUtils.removeStart(path, "/");
345 return path;
346 }
347 }