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.module.blossom.template;
35
36 import info.magnolia.config.registry.DefinitionProvider;
37 import info.magnolia.module.blossom.annotation.Area;
38 import info.magnolia.module.blossom.annotation.ComponentCategory;
39 import info.magnolia.module.blossom.annotation.PreRegister;
40 import info.magnolia.module.blossom.annotation.Template;
41 import info.magnolia.module.blossom.dialog.BlossomDialogDefinitionProvider;
42 import info.magnolia.module.blossom.dialog.BlossomDialogDescription;
43 import info.magnolia.module.blossom.dialog.DialogCreatorDefinitionDecorator;
44 import info.magnolia.module.blossom.dialog.DialogDescriptionBuilder;
45 import info.magnolia.module.blossom.dispatcher.BlossomDispatcher;
46 import info.magnolia.module.blossom.dispatcher.BlossomDispatcherAware;
47 import info.magnolia.module.blossom.dispatcher.BlossomDispatcherInitializedEvent;
48 import info.magnolia.module.blossom.support.MethodInvocationUtils;
49 import info.magnolia.module.blossom.support.ParameterResolver;
50 import info.magnolia.objectfactory.Components;
51 import info.magnolia.rendering.template.AreaDefinition;
52 import info.magnolia.rendering.template.TemplateDefinition;
53 import info.magnolia.rendering.template.registry.TemplateDefinitionRegistry;
54 import info.magnolia.ui.dialog.DialogDefinition;
55 import info.magnolia.ui.dialog.DialogDefinitionRegistry;
56
57 import java.lang.annotation.Annotation;
58 import java.lang.reflect.Method;
59 import java.lang.reflect.Modifier;
60 import java.util.ArrayList;
61 import java.util.Collection;
62 import java.util.Collections;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.concurrent.atomic.AtomicReference;
68
69 import org.apache.commons.lang3.StringUtils;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72 import org.springframework.aop.support.AopUtils;
73 import org.springframework.beans.BeansException;
74 import org.springframework.beans.factory.InitializingBean;
75 import org.springframework.beans.factory.config.BeanPostProcessor;
76 import org.springframework.context.ApplicationEvent;
77 import org.springframework.context.ApplicationListener;
78 import org.springframework.util.ClassUtils;
79 import org.springframework.util.ReflectionUtils;
80 import org.springframework.web.method.HandlerMethod;
81 import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
82 import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
83 import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
84
85
86
87
88
89
90 public class TemplateExporter implements BeanPostProcessor, InitializingBean, ApplicationListener, BlossomDispatcherAware {
91
92 private static final String TEMPLATE_DIALOG_PREFIX = "blossom-template-dialog:";
93 private static final String AREA_DIALOG_PREFIX = "blossom-area-dialog:";
94
95 private final Logger logger = LoggerFactory.getLogger(getClass());
96
97 private BlossomDispatcher dispatcher;
98 private TemplateDefinitionBuilder templateDefinitionBuilder;
99 private DialogDescriptionBuilder dialogDescriptionBuilder;
100
101 private DetectedHandlersMetaDatasMetaData.html#DetectedHandlersMetaData">DetectedHandlersMetaData detectedHandlers = new DetectedHandlersMetaData();
102
103 public void setTemplateDefinitionBuilder(TemplateDefinitionBuilder templateDefinitionBuilder) {
104 this.templateDefinitionBuilder = templateDefinitionBuilder;
105 }
106
107 public void setDialogDescriptionBuilder(DialogDescriptionBuilder dialogDescriptionBuilder) {
108 this.dialogDescriptionBuilder = dialogDescriptionBuilder;
109 }
110
111 @Override
112 public void setBlossomDispatcher(BlossomDispatcher dispatcher) {
113 this.dispatcher = dispatcher;
114 }
115
116 @Override
117 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
118 return bean;
119 }
120
121 @Override
122 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
123 if (bean instanceof AbstractUrlHandlerMapping) {
124 scanHandlerMapping((AbstractUrlHandlerMapping) bean);
125 }
126 if (bean instanceof RequestMappingInfoHandlerMapping) {
127 scanHandlerMapping((RequestMappingInfoHandlerMapping) bean);
128 }
129 return bean;
130 }
131
132 protected void scanHandlerMapping(AbstractUrlHandlerMapping handlerMapping) {
133 for (Object object : handlerMapping.getHandlerMap().entrySet()) {
134
135 Map.Entry entry = (Map.Entry) object;
136 String handlerPath = (String) entry.getKey();
137 Object handler = entry.getValue();
138
139 postProcessHandler(handler, handlerPath);
140 }
141 }
142
143 private void scanHandlerMapping(RequestMappingInfoHandlerMapping handlerMapping) {
144
145 Map<RequestMappingInfo,HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
146 for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
147
148 RequestMappingInfo requestMappingInfo = entry.getKey();
149 HandlerMethod handlerMethod = entry.getValue();
150
151 Set<String> patterns = requestMappingInfo.getPatternsCondition().getPatterns();
152 if (!patterns.isEmpty()) {
153 String pattern = patterns.iterator().next();
154 postProcessHandler(handlerMethod.createWithResolvedBean().getBean(), pattern);
155 }
156 }
157 }
158
159 protected void postProcessHandler(Object handler, String handlerPath) {
160 Class<?> handlerClass = AopUtils.getTargetClass(handler);
161 if (handlerClass.isAnnotationPresent(Area.class)) {
162 detectedHandlers.addArea(new HandlerMetaData(handler, handlerPath, handlerClass));
163 } else if (handlerClass.isAnnotationPresent(Template.class)) {
164 detectedHandlers.addTemplate(new HandlerMetaData(handler, handlerPath, handlerClass));
165 String templateId = resolveTemplateId(handlerClass);
166 if (isComponent(templateId)) {
167 List<Class<? extends Annotation>> categories = findAllAnnotationsMetaAnnotatedWith(handlerClass, ComponentCategory.class);
168 detectedHandlers.addCategories(templateId, categories);
169 }
170 }
171 }
172
173 @Override
174 public void onApplicationEvent(ApplicationEvent event) {
175 if (event instanceof BlossomDispatcherInitializedEvent && event.getSource() == dispatcher) {
176 exportTemplates();
177 }
178 }
179
180 protected void exportTemplates() {
181
182 Set<String> registeredIds = new HashSet<>();
183 for (HandlerMetaData template : detectedHandlers.getTemplates()) {
184
185 BlossomTemplateDefinition definition = templateDefinitionBuilder.buildTemplateDefinition(dispatcher, detectedHandlers, template);
186
187
188 if (registeredIds.contains(definition.getId())) {
189 continue;
190 }
191 registeredIds.add(definition.getId());
192
193 definition = postProcessTemplateDefinition(definition);
194 if (definition == null) {
195 continue;
196 }
197
198 Components.getComponent(TemplateDefinitionRegistry.class).register(createTemplateDefinitionProvider(definition));
199
200 if (logger.isDebugEnabled()) {
201 logger.debug("Registered template [" + template.getHandlerClass() + "] with id [" + definition.getId() + "]");
202 }
203
204 template.setTemplateDefinition(definition);
205
206 if (StringUtils.isEmpty(definition.getDialog())) {
207 registerTemplateDialog(definition);
208 }
209
210 registerDialogFactories(definition);
211
212 registerAreaDialogs(definition.getAreas().values());
213 }
214
215 if (logger.isInfoEnabled()) {
216 StringBuilder sb = new StringBuilder();
217 for (HandlerMetaData template : detectedHandlers.getTemplates()) {
218 if (template.getTemplateDefinition() != null) {
219 if (sb.length() != 0) {
220 sb.append(",");
221 }
222 sb.append(template.getTemplateDefinition().getId());
223 }
224 }
225 logger.info("Registered templates [" + sb.toString() + "]");
226 }
227
228
229 detectedHandlers = null;
230 }
231
232 protected void registerDialogFactories(BlossomTemplateDefinition templateDefinition) {
233
234 List<BlossomDialogDescription> dialogDescriptions = dialogDescriptionBuilder.buildDescriptions(templateDefinition.getHandler());
235
236 for (BlossomDialogDescription dialogDescription : dialogDescriptions) {
237
238 dialogDescription = postProcessDialogFactoryDialog(templateDefinition, dialogDescription);
239 if (dialogDescription == null) {
240 continue;
241 }
242
243 registerAndDecorateDialog(dialogDescription);
244
245 if (logger.isDebugEnabled()) {
246 logger.debug("Registered dialog factory within template [" + templateDefinition.getId() + "] with id [" + dialogDescription.getId() + "]");
247 }
248 }
249 }
250
251 protected void registerTemplateDialog(BlossomTemplateDefinition templateDefinition) {
252
253 String templateId = templateDefinition.getId();
254
255 String dialogId = TEMPLATE_DIALOG_PREFIX + AopUtils.getTargetClass(templateDefinition.getHandler()).getName();
256
257 BlossomDialogDescription dialogDescription = dialogDescriptionBuilder.buildDescription(dialogId, templateDefinition.getTitle(), templateDefinition.getHandler());
258
259 if (dialogDescription.getFactoryMetaData().isEmpty()) {
260 return;
261 }
262
263 dialogDescription = postProcessTemplateDialog(templateDefinition, dialogDescription);
264 if (dialogDescription == null) {
265 return;
266 }
267
268 registerAndDecorateDialog(dialogDescription);
269
270 templateDefinition.setDialog(dialogId);
271
272 if (logger.isDebugEnabled()) {
273 logger.debug("Registered dialog for template [" + templateId + "] with id [" + dialogId + "]");
274 }
275 }
276
277 protected void registerAreaDialogs(Collection<AreaDefinition> areas) {
278 for (AreaDefinition areaDefinition : areas) {
279 if (StringUtils.isEmpty(areaDefinition.getDialog())) {
280 registerAreaDialog((BlossomAreaDefinition) areaDefinition);
281 }
282 registerAreaDialogs(areaDefinition.getAreas().values());
283 }
284 }
285
286 protected void registerAreaDialog(BlossomAreaDefinition areaDefinition) {
287
288 String areaName = areaDefinition.getName();
289
290 String dialogId = AREA_DIALOG_PREFIX + AopUtils.getTargetClass(areaDefinition.getHandler()).getName();
291
292 BlossomDialogDescription dialogDescription = dialogDescriptionBuilder.buildDescription(dialogId, areaDefinition.getTitle(), areaDefinition.getHandler());
293
294 if (dialogDescription.getFactoryMetaData().isEmpty()) {
295 return;
296 }
297
298 dialogDescription = postProcessAreaDialog(areaDefinition, dialogDescription);
299 if (dialogDescription == null) {
300 return;
301 }
302
303 registerAndDecorateDialog(dialogDescription);
304
305 areaDefinition.setDialog(dialogId);
306
307 if (logger.isDebugEnabled()) {
308 logger.debug("Registered dialog for area [" + areaName + "] with id [" + dialogId + "]");
309 }
310 }
311
312 @Override
313 public void afterPropertiesSet() throws Exception {
314 if (templateDefinitionBuilder == null) {
315 templateDefinitionBuilder = new TemplateDefinitionBuilder();
316 }
317 if (dialogDescriptionBuilder == null) {
318 dialogDescriptionBuilder = new DialogDescriptionBuilder();
319 }
320 }
321
322 protected void registerAndDecorateDialog(BlossomDialogDescription dialogDescription) {
323 DialogDefinitionRegistry registry = Components.getComponent(DialogDefinitionRegistry.class);
324 DefinitionProvider<DialogDefinition> dialogProvider = createDialogDefinitionProvider(dialogDescription);
325
326 registry.register(dialogProvider);
327 registry.addDecorator(new DialogCreatorDefinitionDecorator(dialogDescription));
328 }
329
330 protected DefinitionProvider<TemplateDefinition> createTemplateDefinitionProvider(BlossomTemplateDefinition definition) {
331 return new BlossomTemplateDefinitionProvider(definition);
332 }
333
334 protected DefinitionProvider<DialogDefinition> createDialogDefinitionProvider(BlossomDialogDescription dialogDescription) {
335 return new BlossomDialogDefinitionProvider(dialogDescription);
336 }
337
338
339
340
341
342 protected BlossomTemplateDefinitionl#BlossomTemplateDefinition">BlossomTemplateDefinition postProcessTemplateDefinition(final BlossomTemplateDefinition templateDefinition) {
343
344 templateDefinition.setAreas(postProcessAreaDefinitions(templateDefinition, templateDefinition.getAreas()));
345
346 Object handler = templateDefinition.getHandler();
347
348 final Class<?> factoryClass = AopUtils.getTargetClass(handler);
349
350 final List<Method> matchingMethods = new ArrayList<Method>();
351
352 ReflectionUtils.doWithMethods(factoryClass, new ReflectionUtils.MethodCallback() {
353
354 @Override
355 public void doWith(Method method) {
356 if (method.isAnnotationPresent(PreRegister.class) && method.equals(ClassUtils.getMostSpecificMethod(method, factoryClass))) {
357 if (Modifier.isStatic(method.getModifiers())) {
358 throw new IllegalStateException("PreRegister annotation is not supported on static methods");
359 }
360 if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(BlossomTemplateDefinition.class)) {
361 matchingMethods.add(method);
362 }
363 }
364 }
365 });
366 Collections.reverse(matchingMethods);
367
368 final AtomicReference<BlossomTemplateDefinition> reference = new AtomicReference<BlossomTemplateDefinition>(templateDefinition);
369 for (Method matchingMethod : matchingMethods) {
370 Object returnValue = MethodInvocationUtils.invoke(matchingMethod, handler, new ParameterResolver() {
371 @Override
372 public Object resolveParameter(Class<?> parameterType) {
373 if (parameterType.isAssignableFrom(BlossomTemplateDefinition.class)) {
374 return reference.get();
375 }
376 return super.resolveParameter(parameterType);
377 }
378 });
379 if (returnValue == null && !matchingMethod.getReturnType().equals(Void.TYPE)) {
380 return null;
381 }
382 if (returnValue instanceof BlossomTemplateDefinition) {
383 reference.set((BlossomTemplateDefinition) returnValue);
384 }
385 }
386
387 return reference.get();
388 }
389
390
391
392
393 protected Map<String, AreaDefinition> postProcessAreaDefinitions(BlossomTemplateDefinition templateDefinition, Map<String, AreaDefinition> areas) {
394
395 for (String areaName : areas.keySet()) {
396
397 BlossomAreaDefinitionnolia/module/blossom/template/BlossomAreaDefinition.html#BlossomAreaDefinition">BlossomAreaDefinition areaDefinition = (BlossomAreaDefinition) areas.get(areaName);
398 areaDefinition.setAreas(postProcessAreaDefinitions(templateDefinition, areaDefinition.getAreas()));
399
400 areaDefinition = postProcessAreaDefinition(templateDefinition, areaDefinition);
401 if (areaDefinition == null) {
402 areas.remove(areaName);
403 } else {
404 areas.put(areaName, areaDefinition);
405 }
406 }
407
408 return areas;
409 }
410
411 protected BlossomAreaDefinitiontion postProcessAreaDefinition(final BlossomTemplateDefinition templateDefinition, BlossomAreaDefinition areaDefinition) {
412
413 Object handler = areaDefinition.getHandler();
414
415 final Class<?> factoryClass = AopUtils.getTargetClass(handler);
416
417 final List<Method> matchingMethods = new ArrayList<Method>();
418
419 ReflectionUtils.doWithMethods(factoryClass, new ReflectionUtils.MethodCallback() {
420
421 @Override
422 public void doWith(Method method) {
423 if (method.isAnnotationPresent(PreRegister.class) && method.equals(ClassUtils.getMostSpecificMethod(method, factoryClass))) {
424 if (Modifier.isStatic(method.getModifiers())) {
425 throw new IllegalStateException("PreRegister annotation is not supported on static methods");
426 }
427 if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(BlossomAreaDefinition.class)) {
428 matchingMethods.add(method);
429 }
430 }
431 }
432 });
433 Collections.reverse(matchingMethods);
434
435 final AtomicReference<BlossomAreaDefinition> reference = new AtomicReference<BlossomAreaDefinition>(areaDefinition);
436 for (Method matchingMethod : matchingMethods) {
437 Object returnValue = MethodInvocationUtils.invoke(matchingMethod, handler, new ParameterResolver() {
438 @Override
439 public Object resolveParameter(Class<?> parameterType) {
440 if (parameterType.isAssignableFrom(BlossomAreaDefinition.class)) {
441 return reference.get();
442 }
443 if (parameterType.isAssignableFrom(BlossomTemplateDefinition.class)) {
444 return templateDefinition;
445 }
446 return super.resolveParameter(parameterType);
447 }
448 });
449 if (returnValue == null && !matchingMethod.getReturnType().equals(Void.TYPE)) {
450 return null;
451 }
452 if (returnValue instanceof BlossomAreaDefinition) {
453 reference.set((BlossomAreaDefinition) returnValue);
454 }
455 }
456
457 return reference.get();
458 }
459
460
461
462
463
464 protected BlossomDialogDescriptionsomDialogDescription">BlossomDialogDescription postProcessDialogFactoryDialog(BlossomTemplateDefinition templateDefinition, BlossomDialogDescription dialogDescription) {
465 return dialogDescription;
466 }
467
468
469
470
471
472 protected BlossomDialogDescription postPrBlossomDialogDescriptionong class="jxr_keyword">final BlossomTemplateDefinition templateDefinition, final BlossomDialogDescription dialogDescription) {
473 Object factoryObject = dialogDescription.getFactoryMetaData().getFactoryObject();
474
475 final Class<?> factoryClass = AopUtils.getTargetClass(factoryObject);
476
477 final List<Method> matchingMethods = new ArrayList<Method>();
478
479 ReflectionUtils.doWithMethods(factoryClass, new ReflectionUtils.MethodCallback() {
480
481 @Override
482 public void doWith(Method method) {
483 if (method.isAnnotationPresent(PreRegister.class) && method.equals(ClassUtils.getMostSpecificMethod(method, factoryClass))) {
484 if (Modifier.isStatic(method.getModifiers())) {
485 throw new IllegalStateException("PreRegister annotation is not supported on static methods");
486 }
487 if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(BlossomDialogDescription.class)) {
488 matchingMethods.add(method);
489 }
490 }
491 }
492 });
493 Collections.reverse(matchingMethods);
494
495 final AtomicReference<BlossomDialogDescription> reference = new AtomicReference<BlossomDialogDescription>(dialogDescription);
496 for (Method matchingMethod : matchingMethods) {
497 Object returnValue = MethodInvocationUtils.invoke(matchingMethod, factoryObject, new ParameterResolver() {
498 @Override
499 public Object resolveParameter(Class<?> parameterType) {
500 if (parameterType.isAssignableFrom(BlossomDialogDescription.class)) {
501 return reference.get();
502 }
503 if (parameterType.isAssignableFrom(BlossomTemplateDefinition.class)) {
504 return templateDefinition;
505 }
506 return super.resolveParameter(parameterType);
507 }
508 });
509 if (returnValue == null && !matchingMethod.getReturnType().equals(Void.TYPE)) {
510 return null;
511 }
512 if (returnValue instanceof BlossomDialogDescription) {
513 reference.set((BlossomDialogDescription) returnValue);
514 }
515 }
516
517 return reference.get();
518 }
519
520
521
522
523
524 protected BlossomDialogDescriptioBlossomDialogDescriptionlog(final BlossomAreaDefinition areaDefinition, final BlossomDialogDescription dialogDescription) {
525
526 Object factoryObject = dialogDescription.getFactoryMetaData().getFactoryObject();
527
528 final Class<?> factoryClass = AopUtils.getTargetClass(factoryObject);
529
530 final List<Method> matchingMethods = new ArrayList<Method>();
531
532 ReflectionUtils.doWithMethods(factoryClass, new ReflectionUtils.MethodCallback() {
533
534 @Override
535 public void doWith(Method method) {
536 if (method.isAnnotationPresent(PreRegister.class) && method.equals(ClassUtils.getMostSpecificMethod(method, factoryClass))) {
537 if (Modifier.isStatic(method.getModifiers())) {
538 throw new IllegalStateException("PreRegister annotation is not supported on static methods");
539 }
540 if (method.getParameterTypes().length >= 1 && method.getParameterTypes()[0].isAssignableFrom(BlossomDialogDescription.class)) {
541 matchingMethods.add(method);
542 }
543 }
544 }
545 });
546 Collections.reverse(matchingMethods);
547
548 final AtomicReference<BlossomDialogDescription> reference = new AtomicReference<BlossomDialogDescription>(dialogDescription);
549 for (Method matchingMethod : matchingMethods) {
550 Object returnValue = MethodInvocationUtils.invoke(matchingMethod, factoryObject, new ParameterResolver() {
551 @Override
552 public Object resolveParameter(Class<?> parameterType) {
553 if (parameterType.isAssignableFrom(BlossomDialogDescription.class)) {
554 return reference.get();
555 }
556 if (parameterType.isAssignableFrom(BlossomAreaDefinition.class)) {
557 return areaDefinition;
558 }
559 return super.resolveParameter(parameterType);
560 }
561 });
562 if (returnValue == null && !matchingMethod.getReturnType().equals(Void.TYPE)) {
563 return null;
564 }
565 if (returnValue instanceof BlossomDialogDescription) {
566 reference.set((BlossomDialogDescription) returnValue);
567 }
568 }
569
570 return reference.get();
571 }
572
573
574
575
576
577
578
579
580 private String resolveTemplateId(Class<?> handlerClass) {
581 Template annotation = handlerClass.getAnnotation(Template.class);
582 if (annotation == null) {
583 throw new IllegalArgumentException("Could not resolve template id, @Template is not present on class [" + handlerClass.getName() + "]");
584 }
585 return annotation.id();
586 }
587
588
589
590
591
592
593
594
595 private List<Class<? extends Annotation>> findAllAnnotationsMetaAnnotatedWith(Class<?> clazz, Class<? extends Annotation> targetAnnotationType) {
596 List<Class<? extends Annotation>> annotationsWithMetaAnnotations = new ArrayList<Class<? extends Annotation>>();
597 Annotation[] annotations = clazz.getAnnotations();
598 for (Annotation annotation : annotations) {
599 Class<? extends Annotation> annotationType = annotation.annotationType();
600 Annotation[] metaAnnotations = annotationType.getAnnotations();
601 for (Annotation metaAnnotation : metaAnnotations) {
602 Class<? extends Annotation> metaAnnotationType = metaAnnotation.annotationType();
603 if (metaAnnotationType.equals(targetAnnotationType)) {
604 annotationsWithMetaAnnotations.add(annotationType);
605 }
606 }
607 }
608 return annotationsWithMetaAnnotations;
609 }
610
611
612
613
614
615
616
617 private boolean isComponent(String templateId) {
618 String path = StringUtils.substringAfter(templateId, ":");
619 path = StringUtils.removeStart(path, "/");
620 return path.startsWith("components/");
621 }
622 }