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