View Javadoc

1   /**
2    * This file Copyright (c) 2010-2013 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  
49  import info.magnolia.cms.core.Content;
50  import info.magnolia.cms.util.ContentUtil;
51  import info.magnolia.module.blossom.annotation.Area;
52  import info.magnolia.module.blossom.annotation.Available;
53  import info.magnolia.module.blossom.annotation.AvailableComponentClasses;
54  import info.magnolia.module.blossom.annotation.AvailableComponents;
55  import info.magnolia.module.blossom.annotation.ComponentCategory;
56  import info.magnolia.module.blossom.annotation.I18nBasename;
57  import info.magnolia.module.blossom.annotation.Inherits;
58  import info.magnolia.module.blossom.annotation.Template;
59  import info.magnolia.module.blossom.annotation.TemplateDescription;
60  import info.magnolia.module.blossom.annotation.TernaryBoolean;
61  import info.magnolia.module.blossom.dispatcher.BlossomDispatcher;
62  import info.magnolia.module.blossom.support.MethodInvocationUtils;
63  import info.magnolia.module.blossom.support.ParameterResolver;
64  import info.magnolia.rendering.template.AreaDefinition;
65  import info.magnolia.rendering.template.ComponentAvailability;
66  import info.magnolia.rendering.template.InheritanceConfiguration;
67  import info.magnolia.rendering.template.TemplateAvailability;
68  import info.magnolia.rendering.template.TemplateDefinition;
69  import info.magnolia.rendering.template.configured.ConfiguredComponentAvailability;
70  import info.magnolia.rendering.template.configured.ConfiguredInheritance;
71  
72  /**
73   * Builds template descriptions from annotations.
74   *
75   * @since 1.0
76   */
77  public class TemplateDefinitionBuilder {
78  
79      private final Logger logger = LoggerFactory.getLogger(getClass());
80  
81      public BlossomTemplateDefinition buildTemplateDefinition(BlossomDispatcher dispatcher, DetectedHandlersMetaData detectedHandlers, HandlerMetaData template) {
82  
83          Class<?> handlerClass = template.getHandlerClass();
84          Object handler = template.getHandler();
85          String handlerPath = template.getHandlerPath();
86          Template annotation = handlerClass.getAnnotation(Template.class);
87  
88          BlossomTemplateDefinition definition = new BlossomTemplateDefinition();
89          definition.setId(resolveTemplateId(handlerClass));
90          definition.setName(definition.getId());
91          definition.setTitle(resolveTemplateTitle(template));
92          definition.setDescription(resolveDescription(template));
93          definition.setI18nBasename(getI18nBasename(template));
94          definition.setHandlerPath(handlerPath);
95          definition.setDialog(StringUtils.trimToNull(annotation.dialog()));
96          definition.setVisible(annotation.visible());
97          definition.setDispatcher(dispatcher);
98          definition.setHandler(handler);
99          TemplateAvailability templateAvailability = resolveTemplateAvailability(template);
100         if (templateAvailability != null) {
101             definition.setTemplateAvailability(templateAvailability);
102         }
103         definition.setRenderType("blossom");
104 
105         definition.setAreas(buildAreaDefinitionsForTemplate(dispatcher, detectedHandlers, template));
106 
107         return definition;
108     }
109 
110     protected String resolveTemplateId(Class<?> handlerClass) {
111         Template annotation = handlerClass.getAnnotation(Template.class);
112         if (annotation == null) {
113             throw new IllegalArgumentException("Could not resolve template id, @Template is not present on class [" + handlerClass.getName() + "]");
114         }
115         return annotation.id();
116     }
117 
118     protected String resolveTemplateTitle(HandlerMetaData template) {
119         Template annotation = template.getHandlerClass().getAnnotation(Template.class);
120         return annotation.title();
121     }
122 
123     protected String resolveDescription(HandlerMetaData template) {
124         TemplateDescription templateDescription = template.getHandlerClass().getAnnotation(TemplateDescription.class);
125         if (templateDescription != null && StringUtils.isNotEmpty(templateDescription.value())) {
126             return templateDescription.value();
127         }
128         return "";
129     }
130 
131     protected String getI18nBasename(HandlerMetaData handler) {
132         I18nBasename i18nBasename = handler.getHandlerClass().getAnnotation(I18nBasename.class);
133         return i18nBasename != null ? i18nBasename.value() : null;
134     }
135 
136     protected Map<String, AreaDefinition> buildAreaDefinitionsForTemplate(BlossomDispatcher dispatcher, DetectedHandlersMetaData detectedHandlers, HandlerMetaData template) {
137         Map<String, AreaDefinition> areas = new HashMap<String, AreaDefinition>();
138         List<HandlerMetaData> list = detectedHandlers.getAreasByEnclosingClass(template.getHandlerClass());
139         if (list != null) {
140             for (HandlerMetaData area : list) {
141                 BlossomAreaDefinition areaDefinition = buildAreaDefinition(dispatcher, detectedHandlers, area);
142                 areas.put(areaDefinition.getId(), areaDefinition);
143             }
144         }
145         return areas;
146     }
147 
148     protected BlossomAreaDefinition buildAreaDefinition(BlossomDispatcher dispatcher, DetectedHandlersMetaData detectedHandlers, HandlerMetaData area) {
149 
150         Area annotation = area.getHandlerClass().getAnnotation(Area.class);
151 
152         BlossomAreaDefinition definition = new BlossomAreaDefinition();
153         definition.setId(annotation.value());
154         definition.setName(annotation.value());
155         definition.setTitle(StringUtils.isNotEmpty(annotation.title()) ? annotation.title() : StringUtils.capitalize(annotation.value()));
156         definition.setRenderType("blossom");
157         definition.setHandlerPath(area.getHandlerPath());
158         definition.setHandler(area.getHandler());
159         definition.setDispatcher(dispatcher);
160         definition.setDialog(StringUtils.trimToNull(annotation.dialog()));
161         definition.setI18nBasename(getI18nBasename(area));
162         definition.setType(annotation.type().getDefinitionFormat());
163         if (annotation.optional() != TernaryBoolean.UNSPECIFIED) {
164             definition.setOptional(TernaryBoolean.toBoolean(annotation.optional()));
165         }
166 
167         // If the templateScript is null the area is rendered simply by looping the components, to prevent this we
168         // need to set it to something. The actual template script used later when rendering will be the one returned
169         // by the controller
170         definition.setTemplateScript("<area-script-placeholder>");
171 
172         definition.setInheritance(resolveInheritanceConfiguration(area));
173         definition.setAvailableComponents(resolveAvailableComponents(detectedHandlers, area));
174         definition.setAreas(buildAreaDefinitionsForTemplate(dispatcher, detectedHandlers, area));
175 
176         return definition;
177     }
178 
179     protected Map<String, ComponentAvailability> resolveAvailableComponents(DetectedHandlersMetaData detectedHandlers, HandlerMetaData area) {
180         Map<String, ComponentAvailability> map = new HashMap<String, ComponentAvailability>();
181         AvailableComponents availableComponents = area.getHandlerClass().getAnnotation(AvailableComponents.class);
182         if (availableComponents != null) {
183             for (String componentId : availableComponents.value()) {
184                 ConfiguredComponentAvailability availability = new ConfiguredComponentAvailability();
185                 availability.setId(componentId);
186                 map.put(componentId, availability);
187             }
188         }
189         AvailableComponentClasses availableComponentClasses = area.getHandlerClass().getAnnotation(AvailableComponentClasses.class);
190         if (availableComponentClasses != null) {
191             for (Class<?> clazz : availableComponentClasses.value()) {
192                 if (clazz.isAnnotation()) {
193                     if (!clazz.isAnnotationPresent(ComponentCategory.class)) {
194                         throw new IllegalArgumentException("Annotation [" + clazz.getName() + "] specified on area [" + area.getHandlerClass().getName() + "] is not a @ComponentCategory");
195                     }
196 
197                     List<String> templatesInCategory = detectedHandlers.getTemplatesInCategory((Class<? extends Annotation>) clazz);
198                     for (String componentId : templatesInCategory) {
199                         ConfiguredComponentAvailability availability = new ConfiguredComponentAvailability();
200                         availability.setId(componentId);
201                         map.put(componentId, availability);
202                     }
203                 } else {
204                     String componentId = resolveTemplateId(clazz);
205                     ConfiguredComponentAvailability availability = new ConfiguredComponentAvailability();
206                     availability.setId(componentId);
207                     map.put(componentId, availability);
208                 }
209             }
210         }
211         return map;
212     }
213 
214     protected InheritanceConfiguration resolveInheritanceConfiguration(HandlerMetaData area) {
215         Inherits inherits = area.getHandlerClass().getAnnotation(Inherits.class);
216         ConfiguredInheritance inheritance = new ConfiguredInheritance();
217         if (inherits != null) {
218             inheritance.setEnabled(true);
219             inheritance.setComponents(inherits.components().getConfigurationFormat());
220             inheritance.setProperties(inherits.properties().getConfigurationFormat());
221         }
222         return inheritance;
223     }
224 
225     protected TemplateAvailability resolveTemplateAvailability(HandlerMetaData template) {
226         Method method = resolveTemplateAvailabilityMethod(template);
227         if (method != null) {
228             return new DefaultTemplateAvailability(template.getHandler(), method);
229         }
230         return null;
231     }
232 
233     protected Method resolveTemplateAvailabilityMethod(HandlerMetaData template) {
234 
235         final Class<?> handlerClass = template.getHandlerClass();
236         final List<Method> matchingMethods = new ArrayList<Method>();
237 
238         // Iterate class hierarchy to find the method also in super classes
239 
240         Class<?> clazz = handlerClass;
241         while (matchingMethods.isEmpty() && clazz != null) {
242             Method[] methods = clazz.getDeclaredMethods();
243             for (final Method method : methods) {
244 
245                 // The method must have the annotation
246                 if (!method.isAnnotationPresent(Available.class)) {
247                     continue;
248                 }
249 
250                 // The method must not be overridden
251                 if (!method.equals(ClassUtils.getMostSpecificMethod(method, handlerClass))) {
252                     continue;
253                 }
254 
255                 if (!method.getReturnType().equals(Boolean.TYPE)) {
256                     if (logger.isWarnEnabled()) {
257                         logger.warn("Method annotated with @Available has wrong return type [" + method.getClass() + "] should be boolean.");
258                     }
259                     continue;
260                 }
261 
262                 matchingMethods.add(method);
263             }
264             clazz = clazz.getSuperclass();
265         }
266 
267         if (matchingMethods.size() == 0) {
268             return null;
269         } else if (matchingMethods.size() == 1) {
270             return matchingMethods.get(0);
271         } else {
272             throw new IllegalStateException("Multiple @Available annotated methods found for handler [" + handlerClass + "]");
273         }
274     }
275 
276     protected ParameterResolver getTemplateAvailabilityParameters(final Node node, final TemplateDefinition templateDefinition) {
277         return new ParameterResolver() {
278 
279             @Override
280             public Object resolveParameter(Class<?> parameterType) {
281                 if (parameterType.equals(Node.class)) {
282                     return node;
283                 }
284                 if (parameterType.equals(Content.class)) {
285                     return ContentUtil.asContent(node);
286                 }
287                 if (parameterType.equals(TemplateDefinition.class)) {
288                     return templateDefinition;
289                 }
290                 return super.resolveParameter(parameterType);
291             }
292         };
293     }
294 
295     private class DefaultTemplateAvailability implements TemplateAvailability {
296 
297         private final Object handler;
298         private final Method method;
299 
300         public DefaultTemplateAvailability(Object handler, Method method) {
301             this.handler = handler;
302             this.method = method;
303         }
304 
305         @Override
306         public boolean isAvailable(Node node, TemplateDefinition templateDefinition) {
307             ParameterResolver parameters = getTemplateAvailabilityParameters(node, templateDefinition);
308             return (Boolean) MethodInvocationUtils.invoke(method, handler, parameters);
309         }
310     }
311 }