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 DefinitionProvider<TemplateDefinition> templateDefinitionProvider = createTemplateDefinitionProvider(definition);
199 Components.getComponent(TemplateDefinitionRegistry.class).register(templateDefinitionProvider);
200
201 logger.info("Registered definition from blossom [{}]: {}", template.getHandlerClass().getName(), templateDefinitionProvider.getMetadata());
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
215 detectedHandlers = null;
216 }
217
218 protected void registerDialogFactories(BlossomTemplateDefinition templateDefinition) {
219
220 List<BlossomDialogDescription> dialogDescriptions = dialogDescriptionBuilder.buildDescriptions(templateDefinition.getHandler());
221
222 for (BlossomDialogDescription dialogDescription : dialogDescriptions) {
223
224 dialogDescription = postProcessDialogFactoryDialog(templateDefinition, dialogDescription);
225 if (dialogDescription == null) {
226 continue;
227 }
228
229 registerAndDecorateDialog(dialogDescription);
230 }
231 }
232
233 protected void registerTemplateDialog(BlossomTemplateDefinition templateDefinition) {
234
235 String templateId = templateDefinition.getId();
236
237 String dialogId = TEMPLATE_DIALOG_PREFIX + AopUtils.getTargetClass(templateDefinition.getHandler()).getName();
238
239 BlossomDialogDescription dialogDescription = dialogDescriptionBuilder.buildDescription(dialogId, templateDefinition.getTitle(), templateDefinition.getHandler());
240
241 if (dialogDescription.getFactoryMetaData().isEmpty()) {
242 return;
243 }
244
245 dialogDescription = postProcessTemplateDialog(templateDefinition, dialogDescription);
246 if (dialogDescription == null) {
247 return;
248 }
249
250 registerAndDecorateDialog(dialogDescription);
251
252 templateDefinition.setDialog(dialogId);
253 }
254
255 protected void registerAreaDialogs(Collection<AreaDefinition> areas) {
256 for (AreaDefinition areaDefinition : areas) {
257 if (StringUtils.isEmpty(areaDefinition.getDialog())) {
258 registerAreaDialog((BlossomAreaDefinition) areaDefinition);
259 }
260 registerAreaDialogs(areaDefinition.getAreas().values());
261 }
262 }
263
264 protected void registerAreaDialog(BlossomAreaDefinition areaDefinition) {
265
266 String areaName = areaDefinition.getName();
267
268 String dialogId = AREA_DIALOG_PREFIX + AopUtils.getTargetClass(areaDefinition.getHandler()).getName();
269
270 BlossomDialogDescription dialogDescription = dialogDescriptionBuilder.buildDescription(dialogId, areaDefinition.getTitle(), areaDefinition.getHandler());
271
272 if (dialogDescription.getFactoryMetaData().isEmpty()) {
273 return;
274 }
275
276 dialogDescription = postProcessAreaDialog(areaDefinition, dialogDescription);
277 if (dialogDescription == null) {
278 return;
279 }
280
281 registerAndDecorateDialog(dialogDescription);
282
283 areaDefinition.setDialog(dialogId);
284 }
285
286 @Override
287 public void afterPropertiesSet() throws Exception {
288 if (templateDefinitionBuilder == null) {
289 templateDefinitionBuilder = new TemplateDefinitionBuilder();
290 }
291 if (dialogDescriptionBuilder == null) {
292 dialogDescriptionBuilder = new DialogDescriptionBuilder();
293 }
294 }
295
296 protected void registerAndDecorateDialog(BlossomDialogDescription dialogDescription) {
297 DialogDefinitionRegistry registry = Components.getComponent(DialogDefinitionRegistry.class);
298 DefinitionProvider<DialogDefinition> dialogProvider = createDialogDefinitionProvider(dialogDescription);
299
300 registry.register(dialogProvider);
301 registry.addDecorator(new DialogCreatorDefinitionDecorator(dialogDescription));
302
303 logger.info("Registered definition from blossom [{}]: {}",
304 dialogProvider.getMetadata().getLocation(), dialogProvider.getMetadata());
305 }
306
307 protected DefinitionProvider<TemplateDefinition> createTemplateDefinitionProvider(BlossomTemplateDefinition definition) {
308 return new BlossomTemplateDefinitionProvider(definition);
309 }
310
311 protected DefinitionProvider<DialogDefinition> createDialogDefinitionProvider(BlossomDialogDescription dialogDescription) {
312 return new BlossomDialogDefinitionProvider(dialogDescription);
313 }
314
315
316
317
318
319 protected BlossomTemplateDefinitionl#BlossomTemplateDefinition">BlossomTemplateDefinition postProcessTemplateDefinition(final BlossomTemplateDefinition templateDefinition) {
320
321 templateDefinition.setAreas(postProcessAreaDefinitions(templateDefinition, templateDefinition.getAreas()));
322
323 Object handler = templateDefinition.getHandler();
324
325 final Class<?> factoryClass = AopUtils.getTargetClass(handler);
326
327 final List<Method> matchingMethods = new ArrayList<Method>();
328
329 ReflectionUtils.doWithMethods(factoryClass, new ReflectionUtils.MethodCallback() {
330
331 @Override
332 public void doWith(Method method) {
333 if (method.isAnnotationPresent(PreRegister.class) && method.equals(ClassUtils.getMostSpecificMethod(method, factoryClass))) {
334 if (Modifier.isStatic(method.getModifiers())) {
335 throw new IllegalStateException("PreRegister annotation is not supported on static methods");
336 }
337 if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(BlossomTemplateDefinition.class)) {
338 matchingMethods.add(method);
339 }
340 }
341 }
342 });
343 Collections.reverse(matchingMethods);
344
345 final AtomicReference<BlossomTemplateDefinition> reference = new AtomicReference<BlossomTemplateDefinition>(templateDefinition);
346 for (Method matchingMethod : matchingMethods) {
347 Object returnValue = MethodInvocationUtils.invoke(matchingMethod, handler, new ParameterResolver() {
348 @Override
349 public Object resolveParameter(Class<?> parameterType) {
350 if (parameterType.isAssignableFrom(BlossomTemplateDefinition.class)) {
351 return reference.get();
352 }
353 return super.resolveParameter(parameterType);
354 }
355 });
356 if (returnValue == null && !matchingMethod.getReturnType().equals(Void.TYPE)) {
357 return null;
358 }
359 if (returnValue instanceof BlossomTemplateDefinition) {
360 reference.set((BlossomTemplateDefinition) returnValue);
361 }
362 }
363
364 return reference.get();
365 }
366
367
368
369
370 protected Map<String, AreaDefinition> postProcessAreaDefinitions(BlossomTemplateDefinition templateDefinition, Map<String, AreaDefinition> areas) {
371
372 for (String areaName : areas.keySet()) {
373
374 BlossomAreaDefinitionnolia/module/blossom/template/BlossomAreaDefinition.html#BlossomAreaDefinition">BlossomAreaDefinition areaDefinition = (BlossomAreaDefinition) areas.get(areaName);
375 areaDefinition.setAreas(postProcessAreaDefinitions(templateDefinition, areaDefinition.getAreas()));
376
377 areaDefinition = postProcessAreaDefinition(templateDefinition, areaDefinition);
378 if (areaDefinition == null) {
379 areas.remove(areaName);
380 } else {
381 areas.put(areaName, areaDefinition);
382 }
383 }
384
385 return areas;
386 }
387
388 protected BlossomAreaDefinitiontion postProcessAreaDefinition(final BlossomTemplateDefinition templateDefinition, BlossomAreaDefinition areaDefinition) {
389
390 Object handler = areaDefinition.getHandler();
391
392 final Class<?> factoryClass = AopUtils.getTargetClass(handler);
393
394 final List<Method> matchingMethods = new ArrayList<Method>();
395
396 ReflectionUtils.doWithMethods(factoryClass, new ReflectionUtils.MethodCallback() {
397
398 @Override
399 public void doWith(Method method) {
400 if (method.isAnnotationPresent(PreRegister.class) && method.equals(ClassUtils.getMostSpecificMethod(method, factoryClass))) {
401 if (Modifier.isStatic(method.getModifiers())) {
402 throw new IllegalStateException("PreRegister annotation is not supported on static methods");
403 }
404 if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(BlossomAreaDefinition.class)) {
405 matchingMethods.add(method);
406 }
407 }
408 }
409 });
410 Collections.reverse(matchingMethods);
411
412 final AtomicReference<BlossomAreaDefinition> reference = new AtomicReference<BlossomAreaDefinition>(areaDefinition);
413 for (Method matchingMethod : matchingMethods) {
414 Object returnValue = MethodInvocationUtils.invoke(matchingMethod, handler, new ParameterResolver() {
415 @Override
416 public Object resolveParameter(Class<?> parameterType) {
417 if (parameterType.isAssignableFrom(BlossomAreaDefinition.class)) {
418 return reference.get();
419 }
420 if (parameterType.isAssignableFrom(BlossomTemplateDefinition.class)) {
421 return templateDefinition;
422 }
423 return super.resolveParameter(parameterType);
424 }
425 });
426 if (returnValue == null && !matchingMethod.getReturnType().equals(Void.TYPE)) {
427 return null;
428 }
429 if (returnValue instanceof BlossomAreaDefinition) {
430 reference.set((BlossomAreaDefinition) returnValue);
431 }
432 }
433
434 return reference.get();
435 }
436
437
438
439
440
441 protected BlossomDialogDescriptionsomDialogDescription">BlossomDialogDescription postProcessDialogFactoryDialog(BlossomTemplateDefinition templateDefinition, BlossomDialogDescription dialogDescription) {
442 return dialogDescription;
443 }
444
445
446
447
448
449 protected BlossomDialogDescription postPrBlossomDialogDescriptionong class="jxr_keyword">final BlossomTemplateDefinition templateDefinition, final BlossomDialogDescription dialogDescription) {
450 Object factoryObject = dialogDescription.getFactoryMetaData().getFactoryObject();
451
452 final Class<?> factoryClass = AopUtils.getTargetClass(factoryObject);
453
454 final List<Method> matchingMethods = new ArrayList<Method>();
455
456 ReflectionUtils.doWithMethods(factoryClass, new ReflectionUtils.MethodCallback() {
457
458 @Override
459 public void doWith(Method method) {
460 if (method.isAnnotationPresent(PreRegister.class) && method.equals(ClassUtils.getMostSpecificMethod(method, factoryClass))) {
461 if (Modifier.isStatic(method.getModifiers())) {
462 throw new IllegalStateException("PreRegister annotation is not supported on static methods");
463 }
464 if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(BlossomDialogDescription.class)) {
465 matchingMethods.add(method);
466 }
467 }
468 }
469 });
470 Collections.reverse(matchingMethods);
471
472 final AtomicReference<BlossomDialogDescription> reference = new AtomicReference<BlossomDialogDescription>(dialogDescription);
473 for (Method matchingMethod : matchingMethods) {
474 Object returnValue = MethodInvocationUtils.invoke(matchingMethod, factoryObject, new ParameterResolver() {
475 @Override
476 public Object resolveParameter(Class<?> parameterType) {
477 if (parameterType.isAssignableFrom(BlossomDialogDescription.class)) {
478 return reference.get();
479 }
480 if (parameterType.isAssignableFrom(BlossomTemplateDefinition.class)) {
481 return templateDefinition;
482 }
483 return super.resolveParameter(parameterType);
484 }
485 });
486 if (returnValue == null && !matchingMethod.getReturnType().equals(Void.TYPE)) {
487 return null;
488 }
489 if (returnValue instanceof BlossomDialogDescription) {
490 reference.set((BlossomDialogDescription) returnValue);
491 }
492 }
493
494 return reference.get();
495 }
496
497
498
499
500
501 protected BlossomDialogDescriptioBlossomDialogDescriptionlog(final BlossomAreaDefinition areaDefinition, final BlossomDialogDescription dialogDescription) {
502
503 Object factoryObject = dialogDescription.getFactoryMetaData().getFactoryObject();
504
505 final Class<?> factoryClass = AopUtils.getTargetClass(factoryObject);
506
507 final List<Method> matchingMethods = new ArrayList<Method>();
508
509 ReflectionUtils.doWithMethods(factoryClass, new ReflectionUtils.MethodCallback() {
510
511 @Override
512 public void doWith(Method method) {
513 if (method.isAnnotationPresent(PreRegister.class) && method.equals(ClassUtils.getMostSpecificMethod(method, factoryClass))) {
514 if (Modifier.isStatic(method.getModifiers())) {
515 throw new IllegalStateException("PreRegister annotation is not supported on static methods");
516 }
517 if (method.getParameterTypes().length >= 1 && method.getParameterTypes()[0].isAssignableFrom(BlossomDialogDescription.class)) {
518 matchingMethods.add(method);
519 }
520 }
521 }
522 });
523 Collections.reverse(matchingMethods);
524
525 final AtomicReference<BlossomDialogDescription> reference = new AtomicReference<BlossomDialogDescription>(dialogDescription);
526 for (Method matchingMethod : matchingMethods) {
527 Object returnValue = MethodInvocationUtils.invoke(matchingMethod, factoryObject, new ParameterResolver() {
528 @Override
529 public Object resolveParameter(Class<?> parameterType) {
530 if (parameterType.isAssignableFrom(BlossomDialogDescription.class)) {
531 return reference.get();
532 }
533 if (parameterType.isAssignableFrom(BlossomAreaDefinition.class)) {
534 return areaDefinition;
535 }
536 return super.resolveParameter(parameterType);
537 }
538 });
539 if (returnValue == null && !matchingMethod.getReturnType().equals(Void.TYPE)) {
540 return null;
541 }
542 if (returnValue instanceof BlossomDialogDescription) {
543 reference.set((BlossomDialogDescription) returnValue);
544 }
545 }
546
547 return reference.get();
548 }
549
550
551
552
553
554
555
556
557 private String resolveTemplateId(Class<?> handlerClass) {
558 Template annotation = handlerClass.getAnnotation(Template.class);
559 if (annotation == null) {
560 throw new IllegalArgumentException("Could not resolve template id, @Template is not present on class [" + handlerClass.getName() + "]");
561 }
562 return annotation.id();
563 }
564
565
566
567
568
569
570
571
572 private List<Class<? extends Annotation>> findAllAnnotationsMetaAnnotatedWith(Class<?> clazz, Class<? extends Annotation> targetAnnotationType) {
573 List<Class<? extends Annotation>> annotationsWithMetaAnnotations = new ArrayList<Class<? extends Annotation>>();
574 Annotation[] annotations = clazz.getAnnotations();
575 for (Annotation annotation : annotations) {
576 Class<? extends Annotation> annotationType = annotation.annotationType();
577 Annotation[] metaAnnotations = annotationType.getAnnotations();
578 for (Annotation metaAnnotation : metaAnnotations) {
579 Class<? extends Annotation> metaAnnotationType = metaAnnotation.annotationType();
580 if (metaAnnotationType.equals(targetAnnotationType)) {
581 annotationsWithMetaAnnotations.add(annotationType);
582 }
583 }
584 }
585 return annotationsWithMetaAnnotations;
586 }
587
588
589
590
591
592
593
594 private boolean isComponent(String templateId) {
595 String path = StringUtils.substringAfter(templateId, ":");
596 path = StringUtils.removeStart(path, "/");
597 return path.startsWith("components/");
598 }
599 }