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