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