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