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