1. Project Clover database Fri Jan 30 2015 14:18:22 CET
  2. Package info.magnolia.module.blossom.template

File TemplateDefinitionBuilder.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart8.png
43% of files have more coverage

Code metrics

62
158
22
4
449
320
55
0.35
7.18
5.5
2.5
5.5% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
TemplateDefinitionBuilder 82 147 0% 50 49
0.7831858478.3%
TemplateDefinitionBuilder.DefaultTemplateAvailability 298 4 0% 2 0
1.0100%
TemplateDefinitionBuilder.BlossomAutoGenerationConfiguration 334 4 70.6% 1 0
1.0100%
TemplateDefinitionBuilder.BlossomGenerator 382 3 28.6% 2 5
0.00%
 

Contributing tests

This file is covered by 24 tests. .

Source view

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.util.ArrayList;
39    import java.util.HashMap;
40    import java.util.List;
41    import java.util.Map;
42    import javax.jcr.Node;
43   
44    import org.apache.commons.lang.StringUtils;
45    import org.slf4j.Logger;
46    import org.slf4j.LoggerFactory;
47    import org.springframework.util.ClassUtils;
48    import org.springframework.util.ReflectionUtils;
49   
50    import info.magnolia.cms.core.Content;
51    import info.magnolia.cms.util.ContentUtil;
52    import info.magnolia.module.blossom.annotation.Area;
53    import info.magnolia.module.blossom.annotation.AutoGenerator;
54    import info.magnolia.module.blossom.annotation.Available;
55    import info.magnolia.module.blossom.annotation.AvailableComponentClasses;
56    import info.magnolia.module.blossom.annotation.AvailableComponents;
57    import info.magnolia.module.blossom.annotation.ComponentCategory;
58    import info.magnolia.module.blossom.annotation.I18nBasename;
59    import info.magnolia.module.blossom.annotation.Inherits;
60    import info.magnolia.module.blossom.annotation.Template;
61    import info.magnolia.module.blossom.annotation.TemplateDescription;
62    import info.magnolia.module.blossom.annotation.TernaryBoolean;
63    import info.magnolia.module.blossom.dispatcher.BlossomDispatcher;
64    import info.magnolia.module.blossom.support.MethodInvocationUtils;
65    import info.magnolia.module.blossom.support.ParameterResolver;
66    import info.magnolia.rendering.engine.RenderException;
67    import info.magnolia.rendering.generator.Generator;
68    import info.magnolia.rendering.template.AreaDefinition;
69    import info.magnolia.rendering.template.AutoGenerationConfiguration;
70    import info.magnolia.rendering.template.ComponentAvailability;
71    import info.magnolia.rendering.template.InheritanceConfiguration;
72    import info.magnolia.rendering.template.TemplateAvailability;
73    import info.magnolia.rendering.template.TemplateDefinition;
74    import info.magnolia.rendering.template.configured.ConfiguredComponentAvailability;
75    import info.magnolia.rendering.template.configured.ConfiguredInheritance;
76   
77    /**
78    * Builds template descriptions from annotations.
79    *
80    * @since 1.0
81    */
 
82    public class TemplateDefinitionBuilder {
83   
84    private final Logger logger = LoggerFactory.getLogger(getClass());
85   
 
86  25 toggle public BlossomTemplateDefinition buildTemplateDefinition(BlossomDispatcher dispatcher, DetectedHandlersMetaData detectedHandlers, HandlerMetaData template) {
87   
88  25 Class<?> handlerClass = template.getHandlerClass();
89  25 Object handler = template.getHandler();
90  25 String handlerPath = template.getHandlerPath();
91  25 Template annotation = handlerClass.getAnnotation(Template.class);
92   
93  25 BlossomTemplateDefinition definition = new BlossomTemplateDefinition();
94  25 definition.setId(resolveTemplateId(handlerClass));
95  24 definition.setName(definition.getId());
96  24 definition.setTitle(resolveTemplateTitle(template));
97  24 definition.setDescription(resolveDescription(template));
98  24 definition.setI18nBasename(getI18nBasename(template));
99  24 definition.setHandlerPath(handlerPath);
100  24 definition.setDialog(StringUtils.trimToNull(annotation.dialog()));
101  24 definition.setVisible(annotation.visible());
102  24 definition.setDispatcher(dispatcher);
103  24 definition.setHandler(handler);
104  24 TemplateAvailability templateAvailability = resolveTemplateAvailability(template);
105  23 if (templateAvailability != null) {
106  6 definition.setTemplateAvailability(templateAvailability);
107    }
108  23 definition.setRenderType("blossom");
109   
110  23 definition.setAreas(buildAreaDefinitionsForTemplate(dispatcher, detectedHandlers, template));
111   
112  22 return definition;
113    }
114   
 
115  25 toggle protected String resolveTemplateId(Class<?> handlerClass) {
116  25 Template annotation = handlerClass.getAnnotation(Template.class);
117  25 if (annotation == null) {
118  1 throw new IllegalArgumentException("Could not resolve template id, @Template is not present on class [" + handlerClass.getName() + "]");
119    }
120  24 return annotation.id();
121    }
122   
 
123  24 toggle protected String resolveTemplateTitle(HandlerMetaData template) {
124  24 Template annotation = template.getHandlerClass().getAnnotation(Template.class);
125  24 return annotation.title();
126    }
127   
 
128  24 toggle protected String resolveDescription(HandlerMetaData template) {
129  24 TemplateDescription templateDescription = template.getHandlerClass().getAnnotation(TemplateDescription.class);
130  24 if (templateDescription != null && StringUtils.isNotEmpty(templateDescription.value())) {
131  5 return templateDescription.value();
132    }
133  19 return "";
134    }
135   
 
136  40 toggle protected String getI18nBasename(HandlerMetaData handler) {
137  40 I18nBasename i18nBasename = handler.getHandlerClass().getAnnotation(I18nBasename.class);
138  40 return i18nBasename != null ? i18nBasename.value() : null;
139    }
140   
 
141  39 toggle protected Map<String, AreaDefinition> buildAreaDefinitionsForTemplate(BlossomDispatcher dispatcher, DetectedHandlersMetaData detectedHandlers, HandlerMetaData template) {
142   
143  39 Map<String, AreaDefinition> areas = new HashMap<String, AreaDefinition>();
144   
145  39 Class<?> handlerClass = template.getHandlerClass();
146  125 while (handlerClass != null) {
147   
148  87 List<HandlerMetaData> list = detectedHandlers.getAreasByEnclosingClass(handlerClass);
149  87 if (list != null) {
150  11 for (HandlerMetaData area : list) {
151  11 BlossomAreaDefinition areaDefinition = buildAreaDefinition(dispatcher, detectedHandlers, area);
152  10 areas.put(areaDefinition.getId(), areaDefinition);
153    }
154    }
155   
156  86 handlerClass = handlerClass.getSuperclass();
157    }
158   
159  38 return areas;
160    }
161   
 
162  16 toggle protected BlossomAreaDefinition buildAreaDefinition(BlossomDispatcher dispatcher, DetectedHandlersMetaData detectedHandlers, HandlerMetaData area) {
163   
164  16 Area annotation = area.getHandlerClass().getAnnotation(Area.class);
165   
166  16 BlossomAreaDefinition definition = new BlossomAreaDefinition();
167  16 definition.setId(annotation.value());
168  16 definition.setName(annotation.value());
169  16 definition.setTitle(StringUtils.isNotEmpty(annotation.title()) ? annotation.title() : StringUtils.capitalize(annotation.value()));
170  16 definition.setRenderType("blossom");
171  16 definition.setHandlerPath(area.getHandlerPath());
172  16 definition.setHandler(area.getHandler());
173  16 definition.setDispatcher(dispatcher);
174  16 definition.setDialog(StringUtils.trimToNull(annotation.dialog()));
175  16 definition.setI18nBasename(getI18nBasename(area));
176  16 definition.setType(annotation.type().getDefinitionFormat());
177  16 if (annotation.maxComponents() != Integer.MAX_VALUE) {
178  1 definition.setMaxComponents(annotation.maxComponents());
179    }
180  16 if (annotation.optional() != TernaryBoolean.UNSPECIFIED) {
181  0 definition.setOptional(TernaryBoolean.toBoolean(annotation.optional()));
182    }
183  16 if (annotation.createAreaNode() != TernaryBoolean.UNSPECIFIED) {
184  2 Boolean createAreaNode = TernaryBoolean.toBoolean(annotation.createAreaNode());
185    // This feature was introduced in Magnolia 5.2.4 (MAGNOLIA-4711) to remain compatible with earlier
186    // versions this flag is set using reflection.
187  2 Method method = ReflectionUtils.findMethod(definition.getClass(), "setCreateAreaNode", Boolean.class);
188  2 if (method != null)
189  0 ReflectionUtils.invokeMethod(method, definition, createAreaNode);
190    }
191   
192    // If the templateScript is null the area is rendered simply by looping the components, to prevent this we
193    // need to set it to something. The actual template script used later when rendering will be the one returned
194    // by the controller
195  16 definition.setTemplateScript("<area-script-placeholder>");
196   
197  16 definition.setInheritance(resolveInheritanceConfiguration(area));
198  16 definition.setAvailableComponents(resolveAvailableComponents(detectedHandlers, area));
199  16 definition.setAreas(buildAreaDefinitionsForTemplate(dispatcher, detectedHandlers, area));
200  16 definition.setAutoGeneration(resolveAutoGenerationConfiguration(definition, area));
201   
202  15 return definition;
203    }
204   
 
205  16 toggle protected Map<String, ComponentAvailability> resolveAvailableComponents(DetectedHandlersMetaData detectedHandlers, HandlerMetaData area) {
206  16 Map<String, ComponentAvailability> map = new HashMap<String, ComponentAvailability>();
207  16 AvailableComponents availableComponents = area.getHandlerClass().getAnnotation(AvailableComponents.class);
208  16 if (availableComponents != null) {
209  0 for (String componentId : availableComponents.value()) {
210  0 ConfiguredComponentAvailability availability = new ConfiguredComponentAvailability();
211  0 availability.setId(componentId);
212  0 map.put(componentId, availability);
213    }
214    }
215  16 AvailableComponentClasses availableComponentClasses = area.getHandlerClass().getAnnotation(AvailableComponentClasses.class);
216  16 if (availableComponentClasses != null) {
217  1 for (Class<?> clazz : availableComponentClasses.value()) {
218  1 if (clazz.isAnnotation()) {
219  1 if (!clazz.isAnnotationPresent(ComponentCategory.class)) {
220  0 throw new IllegalArgumentException("Annotation [" + clazz.getName() + "] specified on area [" + area.getHandlerClass().getName() + "] is not a @ComponentCategory");
221    }
222   
223  1 List<String> templatesInCategory = detectedHandlers.getTemplatesInCategory((Class<? extends Annotation>) clazz);
224  1 for (String componentId : templatesInCategory) {
225  1 ConfiguredComponentAvailability availability = new ConfiguredComponentAvailability();
226  1 availability.setId(componentId);
227  1 map.put(componentId, availability);
228    }
229    } else {
230  0 String componentId = resolveTemplateId(clazz);
231  0 ConfiguredComponentAvailability availability = new ConfiguredComponentAvailability();
232  0 availability.setId(componentId);
233  0 map.put(componentId, availability);
234    }
235    }
236    }
237  16 return map;
238    }
239   
 
240  16 toggle protected InheritanceConfiguration resolveInheritanceConfiguration(HandlerMetaData area) {
241  16 Inherits inherits = area.getHandlerClass().getAnnotation(Inherits.class);
242  16 ConfiguredInheritance inheritance = new ConfiguredInheritance();
243  16 if (inherits != null) {
244  0 inheritance.setEnabled(true);
245  0 inheritance.setComponents(inherits.components().getConfigurationFormat());
246  0 inheritance.setProperties(inherits.properties().getConfigurationFormat());
247    }
248  16 return inheritance;
249    }
250   
 
251  24 toggle protected TemplateAvailability resolveTemplateAvailability(HandlerMetaData template) {
252  24 Method method = resolveTemplateAvailabilityMethod(template);
253  23 if (method != null) {
254  6 return new DefaultTemplateAvailability(template.getHandler(), method);
255    }
256  17 return null;
257    }
258   
 
259  24 toggle protected Method resolveTemplateAvailabilityMethod(HandlerMetaData template) {
260   
261  24 final List<Method> matchingMethods = findMethodsAnnotatedWith(template.getHandlerClass(), Available.class);
262   
263  24 if (matchingMethods.size() == 0) {
264  17 return null;
265    }
266  7 if (matchingMethods.size() != 1) {
267  1 throw new IllegalStateException("Multiple @Available annotated methods found for handler [" + template.getHandlerClass() + "]");
268    }
269  6 Method method = matchingMethods.get(0);
270  6 if (!method.getReturnType().equals(Boolean.TYPE)) {
271  0 if (logger.isWarnEnabled()) {
272  0 logger.error("Method annotated with @Available has wrong return type [" + method.getClass() + "] should be boolean.");
273    }
274  0 return null;
275    }
276  6 return method;
277    }
278   
 
279  2 toggle protected ParameterResolver getTemplateAvailabilityParameters(final Node node, final TemplateDefinition templateDefinition) {
280  2 return new ParameterResolver() {
281   
 
282  2 toggle @Override
283    public Object resolveParameter(Class<?> parameterType) {
284  2 if (parameterType.equals(Node.class)) {
285  2 return node;
286    }
287  0 if (parameterType.equals(Content.class)) {
288  0 return ContentUtil.asContent(node);
289    }
290  0 if (parameterType.equals(TemplateDefinition.class)) {
291  0 return templateDefinition;
292    }
293  0 return super.resolveParameter(parameterType);
294    }
295    };
296    }
297   
 
298    private class DefaultTemplateAvailability implements TemplateAvailability {
299   
300    private final Object handler;
301    private final Method method;
302   
 
303  6 toggle public DefaultTemplateAvailability(Object handler, Method method) {
304  6 this.handler = handler;
305  6 this.method = method;
306    }
307   
 
308  2 toggle @Override
309    public boolean isAvailable(Node node, TemplateDefinition templateDefinition) {
310  2 ParameterResolver parameters = getTemplateAvailabilityParameters(node, templateDefinition);
311  2 return (Boolean) MethodInvocationUtils.invoke(method, handler, parameters);
312    }
313    }
314   
 
315  16 toggle private AutoGenerationConfiguration resolveAutoGenerationConfiguration(BlossomAreaDefinition definition, HandlerMetaData template) {
316  16 final List<Method> matchingMethods = findMethodsAnnotatedWith(template.getHandlerClass(), AutoGenerator.class);
317   
318  16 if (matchingMethods.size() == 0) {
319  11 return null;
320    }
321  5 if (matchingMethods.size() != 1) {
322  1 throw new IllegalStateException("Multiple @AutoGenerator annotated methods found for handler [" + template.getHandlerClass() + "]");
323    }
324  4 return new BlossomAutoGenerationConfiguration(this, definition, template.getHandler(), matchingMethods.get(0));
325    }
326   
327    /**
328    * Generator configuration that keeps references needed to perform a method invocation on the handler.
329    *
330    * @see AutoGenerationConfiguration
331    * @see BlossomGenerator
332    * @since 3.0.2
333    */
 
334    public static class BlossomAutoGenerationConfiguration implements AutoGenerationConfiguration {
335   
336    private TemplateDefinitionBuilder templateDefinitionBuilder;
337    private final BlossomAreaDefinition definition;
338    private final Object handler;
339    private final Method method;
340   
 
341  4 toggle public BlossomAutoGenerationConfiguration(TemplateDefinitionBuilder templateDefinitionBuilder, BlossomAreaDefinition definition, Object handler, Method method) {
342  4 this.templateDefinitionBuilder = templateDefinitionBuilder;
343  4 this.definition = definition;
344  4 this.handler = handler;
345  4 this.method = method;
346    }
347   
 
348    toggle public TemplateDefinitionBuilder getTemplateDefinitionBuilder() {
349    return templateDefinitionBuilder;
350    }
351   
 
352    toggle public Object getHandler() {
353    return handler;
354    }
355   
 
356    toggle public Method getMethod() {
357    return method;
358    }
359   
 
360    toggle public BlossomAreaDefinition getDefinition() {
361    return definition;
362    }
363   
 
364    toggle @Override
365    public Map<String, Object> getContent() {
366    return null;
367    }
368   
 
369    toggle @Override
370    public Class getGeneratorClass() {
371    return (Class) BlossomGenerator.class;
372    }
373    }
374   
375    /**
376    * Invokes a method used to auto generate content.
377    *
378    * @see Generator
379    * @see BlossomAutoGenerationConfiguration
380    * @since 3.0.2
381    */
 
382    public static class BlossomGenerator implements Generator<BlossomAutoGenerationConfiguration> {
383   
384    private final Node node;
385   
 
386  0 toggle public BlossomGenerator(Node node) {
387  0 this.node = node;
388    }
389   
 
390    toggle public Node getNode() {
391    return node;
392    }
393   
 
394  0 toggle @Override
395    public void generate(final BlossomAutoGenerationConfiguration configuration) throws RenderException {
396  0 ParameterResolver parameters = configuration.getTemplateDefinitionBuilder().getAutoGenerationParameters(configuration, node);
397  0 MethodInvocationUtils.invoke(configuration.getMethod(), configuration.getHandler(), parameters);
398    }
399    }
400   
 
401  0 toggle protected ParameterResolver getAutoGenerationParameters(final BlossomAutoGenerationConfiguration configuration, final Node node) {
402  0 return new ParameterResolver() {
403   
 
404  0 toggle @Override
405    public Object resolveParameter(Class<?> parameterType) {
406  0 if (parameterType.equals(Node.class)) {
407  0 return node;
408    }
409  0 if (parameterType.isAssignableFrom(BlossomAreaDefinition.class)) {
410  0 return configuration.getDefinition();
411    }
412  0 return super.resolveParameter(parameterType);
413    }
414    };
415    }
416   
417    /**
418    * Finds methods with a particular annotation by inspecting the class collecting all matching methods and considering
419    * the super class only if none are found. This has the effect that methods in superclasses are ignored if any are
420    * present deeper in the hierarchy.
421    *
422    * @param clazz class to inspect
423    * @param annotationClass annotation to look for
424    * @return all matching methods on the class deepest in the hierarchy having at least one
425    */
 
426  40 toggle private static List<Method> findMethodsAnnotatedWith(Class<?> clazz, Class<? extends Annotation> annotationClass) {
427  40 List<Method> matchingMethods = new ArrayList<Method>();
428  40 Class<?> currentClass = clazz;
429  114 while (matchingMethods.isEmpty() && currentClass != null) {
430  74 Method[] methods = currentClass.getDeclaredMethods();
431  74 for (final Method method : methods) {
432   
433    // The method must have the annotation
434  384 if (!method.isAnnotationPresent(annotationClass)) {
435  368 continue;
436    }
437   
438    // The method must not be overridden
439  16 if (!method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
440  2 continue;
441    }
442   
443  14 matchingMethods.add(method);
444    }
445  74 currentClass = currentClass.getSuperclass();
446    }
447  40 return matchingMethods;
448    }
449    }