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