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