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.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
77
78
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
96 private Node areaNode;
97 private TemplateDefinition templateDefinition;
98 private AreaDefinition areaDefinition;
99 private String name;
100 private String type;
101 private String dialog;
102 private String availableComponents;
103 private String label;
104 private String description;
105 private Boolean inherit;
106 private Boolean optional;
107 private Boolean editable;
108 private Integer maxComponents;
109
110 private Map<String, Object> contextAttributes = new HashMap<String, Object>();
111
112 private String areaPath;
113
114 private boolean isAreaDefinitionEnabled;
115
116 public AreaElement(ServerConfiguration server, RenderingContext renderingContext, RenderingEngine renderingEngine) {
117 super(server, renderingContext);
118 this.renderingEngine = renderingEngine;
119 }
120
121 @Override
122 public void begin(Appendable out) throws IOException, RenderException {
123
124 this.templateDefinition = resolveTemplateDefinition();
125 Messages messages = MessagesManager.getMessages(templateDefinition.getI18nBasename());
126
127 this.areaDefinition = resolveAreaDefinition();
128
129 this.isAreaDefinitionEnabled = areaDefinition != null && (areaDefinition.getEnabled() == null || areaDefinition.getEnabled());
130
131 if (!this.isAreaDefinitionEnabled) {
132 return;
133 }
134
135 this.name = resolveName();
136 this.dialog = resolveDialog();
137 this.type = resolveType();
138 this.label = resolveLabel();
139 this.availableComponents = resolveAvailableComponents();
140 this.inherit = isInheritanceEnabled();
141 this.optional = resolveOptional();
142 this.editable = resolveEditable();
143
144 this.description = templateDefinition.getDescription();
145
146
147 if (this.areaDefinition == null) {
148 buildAdHocAreaDefinition();
149 }
150
151 this.maxComponents = resolveMaximumOfComponents();
152
153
154 this.areaNode = getPassedContent();
155 if (this.areaNode != null) {
156 this.areaPath = getNodePath(areaNode);
157 } else {
158
159
160 Node parentNode = currentContent();
161 this.areaNode = tryToCreateAreaNode(parentNode);
162 this.areaPath = getNodePath(parentNode) + "/" + name;
163 }
164
165 if (isAdmin() && hasPermission(this.areaNode)) {
166 MarkupHelper helper = new MarkupHelper(out);
167
168 helper.openComment(CMS_AREA).attribute(AbstractDirective.CONTENT_ATTRIBUTE, this.areaPath);
169 helper.attribute("name", this.name);
170 helper.attribute("availableComponents", this.availableComponents);
171 helper.attribute("type", this.type);
172 helper.attribute("dialog", this.dialog);
173 helper.attribute("label", messages.getWithDefault(this.label, this.label));
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 log.error("ignoring problem w/ creating area in workspace {} for node {}", workspaceName, parentId);
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, MgnlNodeType.NT_COMPONENT));
269 numberOfComponents = listOfComponents.size();
270 }
271 if (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(NoScriptRenderer.NO_SCRIPT_RENDERER);
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() && this.isAreaDefinitionEnabled) {
329 MarkupHelper helper = new MarkupHelper(out);
330 helper.closeComment(CMS_AREA);
331 }
332
333 } catch (Exception e) {
334 throw new RenderException("Can't render area " + areaNode + " with name " + this.name, e);
335 }
336 }
337
338 protected Node tryToCreateAreaNode(Node parentNode) throws RenderException {
339 Node area = null;
340 try {
341 if (parentNode.hasNode(name)) {
342 area = parentNode.getNode(name);
343 } else {
344
345 if (!this.optional) {
346 area = createNewAreaNode(parentNode);
347 }
348 }
349 } catch (RepositoryException e) {
350 throw new RenderException("Can't access or create area node [" + name + "] on [" + parentNode + "]: " + e.getMessage(), e);
351 }
352
353 if (area != null) {
354
355 final AutoGenerationConfiguration autoGeneration = areaDefinition.getAutoGeneration();
356 if (autoGeneration != null && autoGeneration.getGeneratorClass() != null) {
357 Components.newInstance(autoGeneration.getGeneratorClass(), area).generate(autoGeneration);
358 }
359 }
360 return area;
361 }
362
363 protected AreaDefinition resolveAreaDefinition() {
364 if (areaDefinition != null) {
365 return areaDefinition;
366 }
367
368 if (!StringUtils.isEmpty(name)) {
369 if (templateDefinition != null && templateDefinition.getAreas().containsKey(name)) {
370 return templateDefinition.getAreas().get(name);
371 }
372 }
373
374
375 return null;
376 }
377
378 protected TemplateDefinition resolveTemplateDefinition() throws RenderException {
379 final RenderableDefinition renderableDefinition = getRenderingContext().getRenderableDefinition();
380 if (renderableDefinition == null || renderableDefinition instanceof TemplateDefinition) {
381 return (TemplateDefinition) renderableDefinition;
382 }
383 throw new RenderException("Current RenderableDefinition [" + renderableDefinition + "] is not of type TemplateDefinition. Areas cannot be supported");
384 }
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 private boolean canRenderAreaScript() {
405
406 return this.isAreaDefinitionEnabled && (areaNode != null || areaNode == null && this.optional && !MgnlContext.getAggregationState().isPreviewMode());
407 }
408
409 private String resolveDialog() {
410 return dialog != null ? dialog : areaDefinition != null ? areaDefinition.getDialog() : null;
411 }
412
413 private String resolveType() {
414 return type != null ? type : areaDefinition != null && areaDefinition.getType() != null ? areaDefinition.getType() : AreaDefinition.DEFAULT_TYPE;
415 }
416
417 private String resolveName() {
418 return name != null ? name : areaDefinition != null ? areaDefinition.getName() : null;
419 }
420
421 private String resolveLabel() {
422 return label != null ? label : areaDefinition != null && StringUtils.isNotBlank(areaDefinition.getTitle()) ? areaDefinition.getTitle() : StringUtils.capitalize(name);
423 }
424
425 private Boolean resolveOptional() {
426 return optional != null ? optional : areaDefinition != null && areaDefinition.getOptional() != null ? areaDefinition.getOptional() : Boolean.FALSE;
427 }
428
429 private Boolean resolveEditable() {
430 return editable != null ? editable : areaDefinition != null && areaDefinition.getEditable() != null ? areaDefinition.getEditable() : null;
431 }
432
433 private Integer resolveMaximumOfComponents() {
434 return maxComponents != null ? maxComponents : areaDefinition != null && areaDefinition.getMaxComponents() != null ? areaDefinition.getMaxComponents() : Integer.MAX_VALUE;
435 }
436
437 private boolean isInheritanceEnabled() {
438 return areaDefinition != null && areaDefinition.getInheritance() != null && areaDefinition.getInheritance().isEnabled() != null && areaDefinition.getInheritance().isEnabled();
439 }
440
441 private boolean isOptionalAreaCreated() {
442 return this.optional && this.areaNode != null;
443 }
444
445 private boolean hasComponents(Node parent) throws RenderException {
446 try {
447 return NodeUtil.getNodes(parent, MgnlNodeType.NT_COMPONENT).iterator().hasNext();
448 } catch (RepositoryException e) {
449 throw new RenderException(e);
450 }
451 }
452
453 private int numberOfComponents(Node parent) throws RenderException {
454 try {
455 return NodeUtil.asList(NodeUtil.getNodes(parent, MgnlNodeType.NT_COMPONENT)).size();
456 } catch (RepositoryException e) {
457 throw new RenderException(e);
458 }
459 }
460
461 protected String resolveAvailableComponents() {
462 if (StringUtils.isNotEmpty(availableComponents)) {
463 return StringUtils.remove(availableComponents, " ");
464 }
465 if (areaDefinition != null && areaDefinition.getAvailableComponents().size() > 0) {
466 Iterator<ComponentAvailability> iterator = areaDefinition.getAvailableComponents().values().iterator();
467 List<String> componentIds = new ArrayList<String>();
468 final Collection<String> userRoles = MgnlContext.getUser().getAllRoles();
469 while (iterator.hasNext()) {
470 ComponentAvailability availableComponent = iterator.next();
471 if (availableComponent.isEnabled()) {
472
473 final Collection<String> roles = availableComponent.getRoles();
474 if (!roles.isEmpty()) {
475 if (CollectionUtils.containsAny(userRoles, roles)) {
476 componentIds.add(availableComponent.getId());
477 }
478 } else {
479 componentIds.add(availableComponent.getId());
480 }
481 }
482 }
483 return StringUtils.join(componentIds, ',');
484 }
485 return "";
486 }
487
488 private boolean shouldShowAddButton() throws RenderException {
489
490 if (areaNode == null || type.equals(AreaDefinition.TYPE_NO_COMPONENT)) {
491 return false;
492 }
493
494 int numberOfComponents = numberOfComponents(areaNode);
495 if (numberOfComponents >= maxComponents || numberOfComponents > 0 && type.equals(AreaDefinition.TYPE_SINGLE)) {
496 return false;
497 }
498
499 return true;
500 }
501
502 private boolean shouldShowNewComponentArea() throws RenderException {
503
504 if (areaNode == null || type.equals(AreaDefinition.TYPE_NO_COMPONENT) || type.equals(AreaDefinition.TYPE_SINGLE) && hasComponents(areaNode)) {
505 return false;
506 }
507 return true;
508 }
509
510 public String getName() {
511 return name;
512 }
513
514 public void setName(String name) {
515 this.name = name;
516 }
517
518 public AreaDefinition getArea() {
519 return areaDefinition;
520 }
521
522 public void setArea(AreaDefinition area) {
523 this.areaDefinition = area;
524 }
525
526 public String getAvailableComponents() {
527 return availableComponents;
528 }
529
530 public void setAvailableComponents(String availableComponents) {
531 this.availableComponents = availableComponents;
532 }
533
534 public String getType() {
535 return type;
536 }
537
538 public void setType(String type) {
539 this.type = type;
540 }
541
542 public String getDialog() {
543 return dialog;
544 }
545
546 public void setDialog(String dialog) {
547 this.dialog = dialog;
548 }
549
550 public String getLabel() {
551 return label;
552 }
553
554 public void setLabel(String label) {
555 this.label = label;
556 }
557
558 public String getDescription() {
559 return description;
560 }
561
562 public void setDescription(String description) {
563 this.description = description;
564 }
565
566 public boolean isInherit() {
567 return inherit;
568 }
569
570 public void setInherit(boolean inherit) {
571 this.inherit = inherit;
572 }
573
574 public Boolean getEditable() {
575 return editable;
576 }
577
578 public void setEditable(Boolean editable) {
579 this.editable = editable;
580 }
581
582 public Map<String, Object> getContextAttributes() {
583 return contextAttributes;
584 }
585
586 public void setContextAttributes(Map<String, Object> contextAttributes) {
587 this.contextAttributes = contextAttributes;
588 }
589
590 public Integer getMaxComponents() {
591 return maxComponents;
592 }
593
594 public void setMaxComponents(Integer maxComponents) {
595 this.maxComponents = maxComponents;
596 }
597 }