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.templating.elements;
35
36 import info.magnolia.beanmerger.BeanMergerUtil;
37 import info.magnolia.cms.beans.config.ServerConfiguration;
38 import info.magnolia.context.MgnlContext;
39 import info.magnolia.context.WebContext;
40 import info.magnolia.i18nsystem.I18nizer;
41 import info.magnolia.jcr.RuntimeRepositoryException;
42 import info.magnolia.jcr.util.ContentMap;
43 import info.magnolia.jcr.util.NodeTypes;
44 import info.magnolia.jcr.util.NodeUtil;
45 import info.magnolia.objectfactory.Components;
46 import info.magnolia.rendering.context.RenderingContext;
47 import info.magnolia.rendering.engine.AppendableOnlyOutputProvider;
48 import info.magnolia.rendering.engine.RenderException;
49 import info.magnolia.rendering.engine.RenderingEngine;
50 import info.magnolia.rendering.generator.Generator;
51 import info.magnolia.rendering.template.AreaDefinition;
52 import info.magnolia.rendering.template.AutoGenerationConfiguration;
53 import info.magnolia.rendering.template.ComponentAvailability;
54 import info.magnolia.rendering.template.RenderableDefinition;
55 import info.magnolia.rendering.template.TemplateDefinition;
56 import info.magnolia.rendering.template.configured.ConfiguredAreaDefinition;
57 import info.magnolia.rendering.template.variation.RenderableVariationResolver;
58 import info.magnolia.templating.freemarker.AbstractDirective;
59 import info.magnolia.templating.inheritance.DefaultInheritanceContentDecorator;
60 import info.magnolia.templating.renderers.NoScriptRenderer;
61
62 import java.io.IOException;
63 import java.util.ArrayList;
64 import java.util.Collection;
65 import java.util.HashMap;
66 import java.util.Iterator;
67 import java.util.List;
68 import java.util.Map;
69
70 import javax.inject.Inject;
71 import javax.jcr.Node;
72 import javax.jcr.PathNotFoundException;
73 import javax.jcr.RepositoryException;
74 import javax.jcr.Session;
75 import javax.jcr.lock.LockException;
76
77 import org.apache.commons.collections4.CollectionUtils;
78 import org.apache.commons.lang3.StringUtils;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81
82
83
84
85 public class AreaElement extends AbstractContentTemplatingElement {
86
87 private static final Logger log = LoggerFactory.getLogger(AreaElement.class);
88 public static final String CMS_AREA = "cms:area";
89
90 public static final String ATTRIBUTE_COMPONENT = "component";
91 public static final String ATTRIBUTE_COMPONENTS = "components";
92
93 private final RenderingEngine renderingEngine;
94
95 private Node areaNode;
96 private TemplateDefinition templateDefinition;
97 private AreaDefinition areaDefinition;
98 private String name;
99 private String type;
100 private String dialog;
101 private String availableComponents;
102 private String label;
103 private String description;
104 private Boolean inherit;
105 private Boolean optional;
106 private Boolean editable;
107 private Integer maxComponents;
108 private Boolean createAreaNode;
109
110 private Map<String, Object> contextAttributes = new HashMap<String, Object>();
111
112 private String areaPath;
113
114 private boolean isAreaDefinitionEnabled;
115 private final RenderableVariationResolver variationResolver;
116
117 @Inject
118 public AreaElement(ServerConfiguration server, RenderingContext renderingContext, RenderingEngine renderingEngine, RenderableVariationResolver variationResolver, I18nizer i18nizer) {
119 super(server, renderingContext, i18nizer);
120 this.renderingEngine = renderingEngine;
121 this.variationResolver = variationResolver;
122 }
123
124
125
126
127 @Deprecated
128 public AreaElement(ServerConfiguration server, RenderingContext renderingContext, RenderingEngine renderingEngine, RenderableVariationResolver variationResolver) {
129 this(server, renderingContext, renderingEngine, Components.getComponent(RenderableVariationResolver.class), Components.getComponent(I18nizer.class));
130 }
131
132 @Override
133 public void begin(Appendable out) throws IOException, RenderException {
134
135 this.templateDefinition = resolveTemplateDefinition();
136 this.areaDefinition = resolveAreaDefinition();
137
138 this.isAreaDefinitionEnabled = areaDefinition != null && (areaDefinition.getEnabled() == null || areaDefinition.getEnabled());
139
140 if (!this.isAreaDefinitionEnabled) {
141 return;
142 }
143
144 this.name = resolveName();
145 this.dialog = resolveDialog();
146 this.type = resolveType();
147 this.availableComponents = resolveAvailableComponents();
148 this.inherit = isInheritanceEnabled();
149 this.optional = resolveOptional();
150 this.editable = resolveEditable();
151 this.createAreaNode = resolveCreateAreaNode();
152
153
154 if (this.areaDefinition == null) {
155 buildAdHocAreaDefinition();
156 }
157
158 this.maxComponents = resolveMaximumOfComponents();
159
160
161 this.areaNode = getPassedContent();
162 if (this.areaNode != null) {
163 this.areaPath = getNodePath(areaNode);
164 } else {
165
166
167 Node parentNode = currentContent();
168 if (createAreaNode) {
169 this.areaNode = tryToCreateAreaNode(parentNode);
170 this.areaPath = getNodePath(parentNode) + "/" + name;
171 } else {
172 this.areaNode = parentNode;
173 this.areaPath = getNodePath(parentNode);
174 }
175 }
176
177 if (renderComments()) {
178 MarkupHelper helper = new MarkupHelper(out);
179
180 helper.openComment(CMS_AREA).attribute(AbstractDirective.CONTENT_ATTRIBUTE, this.areaPath);
181 helper.attribute("name", this.name);
182 helper.attribute("availableComponents", this.availableComponents);
183 helper.attribute("type", this.type);
184 helper.attribute("dialog", this.dialog);
185
186 this.label = resolveLabel();
187 helper.attribute("label", this.label);
188
189 helper.attribute("inherit", String.valueOf(this.inherit));
190 if (this.editable != null) {
191 helper.attribute("editable", String.valueOf(this.editable));
192 }
193 helper.attribute("optional", String.valueOf(this.optional));
194 if (isOptionalAreaCreated()) {
195 helper.attribute("created", "true");
196 }
197 helper.attribute("createAreaNode", String.valueOf(this.createAreaNode));
198 helper.attribute("showAddButton", String.valueOf(shouldShowAddButton()));
199 helper.attribute("showNewComponentArea", String.valueOf(this.shouldShowAddNewComponent()));
200
201 this.description = this.i18nize(templateDefinition).getDescription();
202
203 final String i18nBasename = StringUtils.isNotEmpty(areaDefinition.getI18nBasename()) ? areaDefinition.getI18nBasename() : templateDefinition.getI18nBasename();
204 description = this.legacyTranslate(description, i18nBasename);
205
206 if (StringUtils.isNotBlank(description)) {
207 helper.attribute("description", this.description);
208 }
209
210 helper.attribute("activationStatus", getActivationStatus(this.areaNode));
211
212 helper.append(" -->\n");
213
214 }
215 }
216
217 private boolean hasPermission(Node node) {
218 if (node == null) {
219 node = currentContent();
220 }
221 try {
222 return node.getSession().hasPermission(node.getPath(), Session.ACTION_SET_PROPERTY);
223 } catch (RepositoryException e) {
224 log.error("Could not determine permission for node {}", node);
225 }
226 return false;
227 }
228
229 private Node createNewAreaNode(Node parentNode) throws RepositoryException {
230 final String parentId = parentNode.getIdentifier();
231 final String workspaceName = parentNode.getSession().getWorkspace().getName();
232 try {
233
234 MgnlContext.doInSystemContext(new MgnlContext.LockingOp(workspaceName, parentNode.getPath(), NodeTypes.Page.NAME) {
235
236 @Override
237 public void doExec() throws RepositoryException {
238 Node parentNodeInSystemSession = NodeUtil.getNodeByIdentifier(workspaceName, parentId);
239 Node newAreaNode = NodeUtil.createPath(parentNodeInSystemSession, AreaElement.this.name, NodeTypes.Area.NAME);
240 newAreaNode.getSession().save();
241 }
242 });
243 } catch (LockException e) {
244
245 log.warn("Failed to create area due to locking problem. {}", e.getMessage(), e);
246 } catch (PathNotFoundException e) {
247
248 log.warn("Failed to create area due to concurrent deletion of page or the parent area. {}", e.getMessage(), e);
249 }
250
251 parentNode.getSession().refresh(true);
252 return parentNode.getNode(this.name);
253 }
254
255 protected void buildAdHocAreaDefinition() {
256 ConfiguredAreaDefinition addHocAreaDefinition = new ConfiguredAreaDefinition();
257 addHocAreaDefinition.setName(this.name);
258 addHocAreaDefinition.setDialog(this.dialog);
259 addHocAreaDefinition.setType(this.type);
260 addHocAreaDefinition.setRenderType(this.templateDefinition.getRenderType());
261 areaDefinition = addHocAreaDefinition;
262 }
263
264 @Override
265 public void end(Appendable out) throws RenderException {
266
267 try {
268 if (canRenderAreaScript()) {
269 if (isInherit() && areaNode != null && areaDefinition.getInheritance() != null) {
270 try {
271 areaNode = new DefaultInheritanceContentDecorator(areaNode, areaDefinition.getInheritance()).wrapNode(areaNode);
272 } catch (RepositoryException e) {
273 throw new RuntimeRepositoryException(e);
274 }
275 }
276 List<Node> listOfComponents = null;
277 int numberOfComponents = 0;
278 if (areaNode != null) {
279 listOfComponents = NodeUtil.asList(NodeUtil.getNodes(areaNode, NodeTypes.Component.NAME));
280 numberOfComponents = listOfComponents.size();
281 }
282 if (renderingEngine.getRenderEmptyAreas() || numberOfComponents > 0 || !(AreaDefinition.TYPE_LIST.equals(areaDefinition.getType()) || AreaDefinition.TYPE_SINGLE.equals(areaDefinition.getType()))) {
283
284 Map<String, Object> contextObjects = new HashMap<String, Object>();
285
286 List<ContentMap> components = new ArrayList<ContentMap>();
287
288 if (areaNode != null) {
289 if (numberOfComponents > maxComponents) {
290 listOfComponents = listOfComponents.subList(0, maxComponents);
291 log.warn("The area {} have maximum number of components set to {}, but has got {} components. Exceeded components won't be added.", areaNode, maxComponents, numberOfComponents);
292 }
293
294 for (Node node : listOfComponents) {
295 components.add(new ContentMap(node));
296 }
297 }
298
299 if (AreaDefinition.TYPE_SINGLE.equals(type)) {
300 if (components.size() > 1) {
301 log.warn("Single area [{}]: expected one component node but found [{}].", areaNode, components.size());
302 }
303 if (components.size() >= 1) {
304 contextObjects.put(ATTRIBUTE_COMPONENT, components.get(0));
305 } else {
306 contextObjects.put(ATTRIBUTE_COMPONENT, null);
307 }
308 } else {
309 contextObjects.put(ATTRIBUTE_COMPONENTS, components);
310 }
311
312
313
314
315 final ConfiguredAreaDefinition override = new ConfiguredAreaDefinition(areaDefinition.getTemplateAvailability());
316 if (areaDefinition.getTemplateScript() == null) {
317 override.setRenderType(NoScriptRenderer.NO_SCRIPT_RENDERER);
318 } else if (areaDefinition.getRenderType() == null) {
319 override.setRenderType(templateDefinition.getRenderType());
320 }
321 if (areaDefinition.getI18nBasename() == null) {
322 override.setI18nBasename(templateDefinition.getI18nBasename());
323 }
324
325 final ConfiguredAreaDefinition mergedAreaDefinition = BeanMergerUtil.merge(override, areaDefinition);
326
327 WebContext webContext = MgnlContext.getWebContext();
328 webContext.push(webContext.getRequest(), webContext.getResponse());
329 setAttributesInWebContext(contextAttributes, WebContext.LOCAL_SCOPE);
330 try {
331 AppendableOnlyOutputProvider appendable = new AppendableOnlyOutputProvider(out);
332 renderingEngine.render(areaNode, mergedAreaDefinition, contextObjects, appendable);
333 } finally {
334 webContext.pop();
335 webContext.setPageContext(null);
336 restoreAttributesInWebContext(contextAttributes, WebContext.LOCAL_SCOPE);
337 }
338 }
339 }
340 if (renderComments()) {
341 MarkupHelper helper = new MarkupHelper(out);
342 helper.closeComment(CMS_AREA);
343 }
344 } catch (Exception e) {
345 throw new RenderException("Can't render area " + areaNode + " with name " + this.name, e);
346 }
347 }
348
349 protected Node tryToCreateAreaNode(Node parentNode) throws RenderException {
350 Node area = null;
351 try {
352 if (parentNode.hasNode(name)) {
353 area = parentNode.getNode(name);
354 } else {
355 if (parentNode.getDefinition().isProtected()) {
356 log.debug("Not auto-creating area '{}', node is protected.");
357 return null;
358 } else if (!this.optional) {
359
360 area = createNewAreaNode(parentNode);
361 }
362 }
363 } catch (RepositoryException e) {
364
365 log.error("Can't autocreate area '{}'.", area, e);
366 }
367
368 if (area != null) {
369
370 final AutoGenerationConfiguration autoGeneration = areaDefinition.getAutoGeneration();
371 if (autoGeneration != null && autoGeneration.getGeneratorClass() != null) {
372 try {
373 final String areaId = area.getIdentifier();
374 final String workspaceName = area.getSession().getWorkspace().getName();
375 MgnlContext.doInSystemContext(new MgnlContext.RepositoryOp() {
376 @Override
377 public void doExec() throws RepositoryException {
378 Node areaNodeInSystemSession = NodeUtil.getNodeByIdentifier(workspaceName, areaId);
379 try {
380 ((Generator<AutoGenerationConfiguration>) Components.newInstance(autoGeneration.getGeneratorClass(), areaNodeInSystemSession)).generate(autoGeneration);
381 } catch (RenderException e) {
382 log.error("Can't render autogenerated area '{}'.", areaNodeInSystemSession);
383 }
384 return;
385 }
386 });
387 } catch (RepositoryException e) {
388 log.error("Can't autocreate area '{}'.", area, e);
389 }
390 }
391 }
392 return area;
393 }
394
395 protected AreaDefinition resolveAreaDefinition() {
396 if (areaDefinition != null) {
397 return areaDefinition;
398 }
399
400 if (!StringUtils.isEmpty(name)) {
401 if (templateDefinition != null && templateDefinition.getAreas().containsKey(name)) {
402 return templateDefinition.getAreas().get(name);
403 }
404 }
405
406
407 return null;
408 }
409
410 protected TemplateDefinition resolveTemplateDefinition() throws RenderException {
411
412 RenderableDefinition renderableDefinition = getRenderingContext().getRenderableDefinition();
413 RenderableDefinition variation = variationResolver.resolveVariation(renderableDefinition);
414 renderableDefinition = variation == null ? renderableDefinition : variation;
415
416 if (renderableDefinition == null || renderableDefinition instanceof TemplateDefinition) {
417 return (TemplateDefinition) renderableDefinition;
418 }
419 throw new RenderException("Current RenderableDefinition [" + renderableDefinition + "] is not of type TemplateDefinition. Areas cannot be supported");
420 }
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440 private boolean canRenderAreaScript() {
441 if (!this.isAreaDefinitionEnabled) {
442 return false;
443 }
444 if (this.areaNode != null) {
445 return true;
446 }
447 if (this.optional && this.getServer().isAdmin() && !MgnlContext.getAggregationState().isPreviewMode()) {
448 return true;
449 }
450 return false;
451 }
452
453 private String resolveDialog() {
454 return dialog != null ? dialog : areaDefinition != null ? areaDefinition.getDialog() : null;
455 }
456
457 private String resolveType() {
458 return type != null ? type : areaDefinition != null && areaDefinition.getType() != null ? areaDefinition.getType() : AreaDefinition.DEFAULT_TYPE;
459 }
460
461 private String resolveName() {
462 return name != null ? name : areaDefinition != null ? areaDefinition.getName() : null;
463 }
464
465 private String resolveLabel() {
466 AreaDefinition i18nizedAreaDefinition = areaDefinition == null ? null : i18nize(areaDefinition);
467 String label = this.label != null ? this.label : (i18nizedAreaDefinition != null ? i18nizedAreaDefinition.getTitle() : null);
468
469
470 final String i18nBasename = StringUtils.isNotEmpty(areaDefinition.getI18nBasename()) ? areaDefinition.getI18nBasename() : templateDefinition.getI18nBasename();
471 label = this.legacyTranslate(label, i18nBasename);
472
473 if (label == null || isMessageKey(label)) {
474 label = StringUtils.capitalize(name);
475 }
476 return label;
477 }
478
479 private Boolean resolveOptional() {
480 return optional != null ? optional : areaDefinition != null && areaDefinition.getOptional() != null ? areaDefinition.getOptional() : Boolean.FALSE;
481 }
482
483 private Boolean resolveEditable() {
484 return editable != null ? editable : areaDefinition != null && areaDefinition.getEditable() != null ? areaDefinition.getEditable() : null;
485 }
486
487 private Integer resolveMaximumOfComponents() {
488 return maxComponents != null ? maxComponents : areaDefinition != null && areaDefinition.getMaxComponents() != null ? areaDefinition.getMaxComponents() : Integer.MAX_VALUE;
489 }
490
491 private Boolean resolveCreateAreaNode() {
492 return createAreaNode != null ? createAreaNode : areaDefinition != null && areaDefinition.getCreateAreaNode() != null ? areaDefinition.getCreateAreaNode() : Boolean.TRUE;
493 }
494
495 private boolean isInheritanceEnabled() {
496 return areaDefinition != null && areaDefinition.getInheritance() != null && areaDefinition.getInheritance().isEnabled() != null && areaDefinition.getInheritance().isEnabled();
497 }
498
499 private boolean isOptionalAreaCreated() {
500 return this.optional && this.areaNode != null;
501 }
502
503 private boolean hasComponents(Node parent) throws RenderException {
504 try {
505 return NodeUtil.getNodes(parent, NodeTypes.Component.NAME).iterator().hasNext();
506 } catch (RepositoryException e) {
507 throw new RenderException(e);
508 }
509 }
510
511 private int numberOfComponents(Node parent) throws RenderException {
512 try {
513 return NodeUtil.asList(NodeUtil.getNodes(parent, NodeTypes.Component.NAME)).size();
514 } catch (RepositoryException e) {
515 throw new RenderException(e);
516 }
517 }
518
519 protected String resolveAvailableComponents() {
520 if (StringUtils.isNotEmpty(availableComponents)) {
521 return StringUtils.remove(availableComponents, " ");
522 }
523 if (areaDefinition != null && areaDefinition.getAvailableComponents().size() > 0) {
524 Iterator<ComponentAvailability> iterator = areaDefinition.getAvailableComponents().values().iterator();
525 List<String> componentIds = new ArrayList<String>();
526 final Collection<String> userRoles = MgnlContext.getUser().getAllRoles();
527 while (iterator.hasNext()) {
528 ComponentAvailability availableComponent = iterator.next();
529 if (availableComponent.isEnabled()) {
530
531 final Collection<String> roles = availableComponent.getRoles();
532 if (!roles.isEmpty()) {
533 if (CollectionUtils.containsAny(userRoles, roles)) {
534 componentIds.add(availableComponent.getId());
535 }
536 } else {
537 componentIds.add(availableComponent.getId());
538 }
539 }
540 }
541 return StringUtils.join(componentIds, ',');
542 }
543 return "";
544 }
545
546 private boolean shouldShowAddButton() throws RenderException {
547
548 if (areaNode == null || type.equals(AreaDefinition.TYPE_NO_COMPONENT) || type.equals(AreaDefinition.TYPE_SINGLE) && hasComponents(areaNode) || numberOfComponents(areaNode) >= maxComponents) {
549 return false;
550 }
551 return true;
552 }
553
554 private boolean shouldShowAddNewComponent() throws RenderException {
555
556 if (areaNode == null || type.equals(AreaDefinition.TYPE_NO_COMPONENT) || type.equals(AreaDefinition.TYPE_SINGLE) && hasComponents(areaNode)) {
557 return false;
558 }
559 return true;
560 }
561
562 public String getName() {
563 return name;
564 }
565
566 public void setName(String name) {
567 this.name = name;
568 }
569
570 public AreaDefinition getArea() {
571 return areaDefinition;
572 }
573
574 public void setArea(AreaDefinition area) {
575 this.areaDefinition = area;
576 }
577
578 public String getAvailableComponents() {
579 return availableComponents;
580 }
581
582 public void setAvailableComponents(String availableComponents) {
583 this.availableComponents = availableComponents;
584 }
585
586 public String getType() {
587 return type;
588 }
589
590 public void setType(String type) {
591 this.type = type;
592 }
593
594 public String getDialog() {
595 return dialog;
596 }
597
598 public void setDialog(String dialog) {
599 this.dialog = dialog;
600 }
601
602 public String getLabel() {
603 return label;
604 }
605
606 public void setLabel(String label) {
607 this.label = label;
608 }
609
610 public String getDescription() {
611 return description;
612 }
613
614 public void setDescription(String description) {
615 this.description = description;
616 }
617
618 public boolean isInherit() {
619 return inherit;
620 }
621
622 public void setInherit(boolean inherit) {
623 this.inherit = inherit;
624 }
625
626 public Boolean getEditable() {
627 return editable;
628 }
629
630 public void setEditable(Boolean editable) {
631 this.editable = editable;
632 }
633
634 public Map<String, Object> getContextAttributes() {
635 return contextAttributes;
636 }
637
638 public void setContextAttributes(Map<String, Object> contextAttributes) {
639 this.contextAttributes = contextAttributes;
640 }
641
642 public Integer getMaxComponents() {
643 return maxComponents;
644 }
645
646 public void setMaxComponents(Integer maxComponents) {
647 this.maxComponents = maxComponents;
648 }
649
650 public Boolean getCreateAreaNode() {
651 return createAreaNode;
652 }
653
654 public void setCreateAreaNode(Boolean createAreaNode) {
655 this.createAreaNode = createAreaNode;
656 }
657
658 @Override
659 protected boolean renderComments() {
660 return this.isAreaDefinitionEnabled && isAdmin() && !MgnlContext.getAggregationState().isPreviewMode() && hasPermission(this.areaNode);
661 }
662 }