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