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