1. Project Clover database Fri Apr 29 2016 13:24:33 CEST
  2. Package info.magnolia.module.blossom.template

File TemplateDefinitionBuilder.java

 

Coverage histogram

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

Code metrics

82
184
23
4
496
366
66
0.36
8
5.75
2.87
4.6% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
TemplateDefinitionBuilder 90 173 0% 61 64
0.765567876.6%
TemplateDefinitionBuilder.BlossomTemplateAvailability 345 4 0% 2 0
1.0100%
TemplateDefinitionBuilder.BlossomAutoGenerationConfiguration 381 4 70.6% 1 0
1.0100%
TemplateDefinitionBuilder.BlossomGenerator 429 3 28.6% 2 5
0.00%
 

Contributing tests

This file is covered by 29 tests. .

Source view

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