1. Project Clover database Fri Mar 6 2015 14:07:48 CET
  2. Package info.magnolia.module.blossom.template

File TemplateDefinitionBuilder.java

 

Coverage histogram

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

Code metrics

70
166
22
4
466
337
59
0.36
7.55
5.5
2.68
5.1% of code in this file is excluded from these metrics.

Classes

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