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