View Javadoc

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