1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
74
75
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
168
169
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
239
240 Class<?> clazz = handlerClass;
241 while (matchingMethods.isEmpty() && clazz != null) {
242 Method[] methods = clazz.getDeclaredMethods();
243 for (final Method method : methods) {
244
245
246 if (!method.isAnnotationPresent(Available.class)) {
247 continue;
248 }
249
250
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 }