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