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