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