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