View Javadoc
1   /**
2    * This file Copyright (c) 2010-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
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   * Detects templates by inspecting HandlerMappings.
87   *
88   * @since 1.1.1
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             // Avoid registering the same template again, happens when a controller has more than one @RequestMapping
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         // We're done with this structure so there's no need to keep it around
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      * Allows sub-classes to do post-processing of the template definition before it is registered. If it returns null
340      * the template will not be registered and neither will its dialog or any &#64;DialogFactory methods within it.
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      * Recursively post-process area definitions starting in bottom up order.
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      * Allows sub-classes to do post-processing on dialogs for &#64;DialogFactory annotated methods before they're
462      * registered. If it returns null the dialog will not be registered.
463      */
464     protected BlossomDialogDescriptionsomDialogDescription">BlossomDialogDescription postProcessDialogFactoryDialog(BlossomTemplateDefinition templateDefinition, BlossomDialogDescription dialogDescription) {
465         return dialogDescription;
466     }
467 
468     /**
469      * Allows sub-classes to do post-processing on dialogs for templates before they're registered. If it returns null
470      * the dialog will not be registered and the template will not have a dialog.
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      * Allows sub-classes to do post-processing on dialogs for areas before they're registered. If it returns null the
522      * dialog will not be registered and the area will not have a dialog.
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      * Resolves the template id for a handler class as specified in the &#64;Template annotation.
575      *
576      * @param handlerClass handler class to find the template id for
577      * @return the template id specified
578      * @throws IllegalArgumentException if the handler class is not annotated with @Template
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      * Returns all annotations on a class that have themselves been annotated with a certain annotation, i.e. meta annotated.
590      *
591      * @param clazz the class to introspect
592      * @param targetAnnotationType the annotation to look for
593      * @return a list of the found annotations, never null
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      * Tests if a template id is a component.
613      *
614      * @param templateId the template id to evaluate
615      * @return true if the template id is a component
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 }