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             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         // We're done with this structure so there's no need to keep it around
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      * Allows sub-classes to do post-processing of the template definition before it is registered. If it returns null
317      * the template will not be registered and neither will its dialog or any &#64;DialogFactory methods within it.
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      * Recursively post-process area definitions starting in bottom up order.
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      * Allows sub-classes to do post-processing on dialogs for &#64;DialogFactory annotated methods before they're
439      * registered. If it returns null the dialog will not be registered.
440      */
441     protected BlossomDialogDescriptionsomDialogDescription">BlossomDialogDescription postProcessDialogFactoryDialog(BlossomTemplateDefinition templateDefinition, BlossomDialogDescription dialogDescription) {
442         return dialogDescription;
443     }
444 
445     /**
446      * Allows sub-classes to do post-processing on dialogs for templates before they're registered. If it returns null
447      * the dialog will not be registered and the template will not have a dialog.
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      * Allows sub-classes to do post-processing on dialogs for areas before they're registered. If it returns null the
499      * dialog will not be registered and the area will not have a dialog.
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      * Resolves the template id for a handler class as specified in the &#64;Template annotation.
552      *
553      * @param handlerClass handler class to find the template id for
554      * @return the template id specified
555      * @throws IllegalArgumentException if the handler class is not annotated with @Template
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      * Returns all annotations on a class that have themselves been annotated with a certain annotation, i.e. meta annotated.
567      *
568      * @param clazz the class to introspect
569      * @param targetAnnotationType the annotation to look for
570      * @return a list of the found annotations, never null
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      * Tests if a template id is a component.
590      *
591      * @param templateId the template id to evaluate
592      * @return true if the template id is a component
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 }