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