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