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