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.beanmerger.BeanMergerUtil;
37 import info.magnolia.cms.beans.config.ServerConfiguration;
38 import info.magnolia.context.MgnlContext;
39 import info.magnolia.context.WebContext;
40 import info.magnolia.i18nsystem.I18nizer;
41 import info.magnolia.jcr.RuntimeRepositoryException;
42 import info.magnolia.jcr.util.ContentMap;
43 import info.magnolia.jcr.util.NodeTypes;
44 import info.magnolia.jcr.util.NodeUtil;
45 import info.magnolia.objectfactory.Components;
46 import info.magnolia.rendering.context.RenderingContext;
47 import info.magnolia.rendering.engine.AppendableOnlyOutputProvider;
48 import info.magnolia.rendering.engine.RenderException;
49 import info.magnolia.rendering.engine.RenderingEngine;
50 import info.magnolia.rendering.generator.Generator;
51 import info.magnolia.rendering.template.AreaDefinition;
52 import info.magnolia.rendering.template.AutoGenerationConfiguration;
53 import info.magnolia.rendering.template.RenderableDefinition;
54 import info.magnolia.rendering.template.TemplateDefinition;
55 import info.magnolia.rendering.template.configured.ConfiguredAreaDefinition;
56 import info.magnolia.rendering.template.variation.RenderableVariationResolver;
57 import info.magnolia.templating.elements.attribute.AvailableComponents;
58 import info.magnolia.templating.inheritance.DefaultInheritanceContentDecorator;
59 import info.magnolia.templating.module.TemplatingModule;
60 import info.magnolia.templating.renderers.NoScriptRenderer;
61
62 import java.io.IOException;
63 import java.util.ArrayList;
64 import java.util.HashMap;
65 import java.util.List;
66 import java.util.Map;
67
68 import javax.inject.Inject;
69 import javax.inject.Provider;
70 import javax.jcr.Node;
71 import javax.jcr.PathNotFoundException;
72 import javax.jcr.RepositoryException;
73 import javax.jcr.Session;
74 import javax.jcr.lock.LockException;
75
76 import org.apache.commons.lang3.StringUtils;
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 private 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 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 optional;
101 private Integer maxComponents;
102 private Boolean createAreaNode;
103
104 private Map<String, Object> contextAttributes = new HashMap<>();
105
106 private boolean isAreaDefinitionEnabled;
107
108 private final RenderableVariationResolver variationResolver;
109 private boolean inherit;
110
111 @Inject
112 public AreaElement(ServerConfiguration server, RenderingContext renderingContext, RenderingEngine renderingEngine, RenderableVariationResolver variationResolver, Provider<TemplatingModule> templatingModuleProvider, WebContext webContext) {
113 super(server, renderingContext, templatingModuleProvider, webContext);
114 this.renderingEngine = renderingEngine;
115 this.variationResolver = variationResolver;
116 }
117
118
119
120
121 @Deprecated
122 public AreaElement(ServerConfiguration server, RenderingContext renderingContext, RenderingEngine renderingEngine, RenderableVariationResolver variationResolver, I18nizer i18nizer) {
123 this(server, renderingContext, renderingEngine, variationResolver, () -> Components.getComponent(TemplatingModule.class), Components.getComponent(WebContext.class));
124 }
125
126 @Override
127 public void begin(Appendable out) throws IOException, RenderException {
128
129 setTemplateDefinition(resolveTemplateDefinition());
130 this.areaDefinition = resolveAreaDefinition();
131
132 this.isAreaDefinitionEnabled = areaDefinition != null && (areaDefinition.getEnabled() == null || areaDefinition.getEnabled());
133
134 if (!this.isAreaDefinitionEnabled) {
135 return;
136 }
137
138 this.name = resolveName();
139 this.dialog = resolveDialog();
140 this.type = resolveType();
141 this.availableComponents = resolveAvailableComponents();
142 this.inherit = isInheritanceEnabled();
143 this.optional = resolveOptional();
144 this.createAreaNode = resolveCreateAreaNode();
145
146
147 if (this.areaDefinition == null) {
148 buildAdHocAreaDefinition();
149 }
150
151 this.maxComponents = resolveMaximumOfComponents();
152
153
154 setContent(getPassedContent());
155 if (getContent() == null) {
156
157
158 Node parentNode = currentContent();
159 if (createAreaNode) {
160 setContent(tryToCreateAreaNode(parentNode));
161 } else {
162 setContent(parentNode);
163 }
164 }
165
166 if (renderComments()) {
167 MarkupHelper helper = new MarkupHelper(out);
168 helper.openComment(CMS_AREA);
169 setPageEditorAttributes(helper, "area");
170 helper.append(" -->\n");
171 }
172 }
173
174 private boolean hasPermission(Node node) {
175 if (node == null) {
176 node = currentContent();
177 }
178 try {
179 return node.getSession().hasPermission(node.getPath(), Session.ACTION_SET_PROPERTY);
180 } catch (RepositoryException e) {
181 log.error("Could not determine permission for node {}", node);
182 }
183 return false;
184 }
185
186 private Node createNewAreaNode(Node parentNode) throws RepositoryException {
187 final String parentId = parentNode.getIdentifier();
188 final String workspaceName = parentNode.getSession().getWorkspace().getName();
189 try {
190
191 MgnlContext.doInSystemContext(new MgnlContext.LockingOp(workspaceName, parentNode.getPath(), NodeTypes.Page.NAME) {
192
193 @Override
194 public void doExec() throws RepositoryException {
195 Node parentNodeInSystemSession = NodeUtil.getNodeByIdentifier(workspaceName, parentId);
196 Node newAreaNode = NodeUtil.createPath(parentNodeInSystemSession, AreaElement.this.name, NodeTypes.Area.NAME);
197 newAreaNode.getSession().save();
198 }
199 });
200 } catch (LockException e) {
201
202 log.warn("Failed to create area due to locking problem. {}", e.getMessage(), e);
203 } catch (PathNotFoundException e) {
204
205 log.warn("Failed to create area due to concurrent deletion of page or the parent area. {}", e.getMessage(), e);
206 }
207
208 parentNode.getSession().refresh(true);
209 return parentNode.getNode(this.name);
210 }
211
212 protected void buildAdHocAreaDefinition() {
213 ConfiguredAreaDefinition addHocAreaDefinition = new ConfiguredAreaDefinition();
214 addHocAreaDefinition.setName(this.name);
215 addHocAreaDefinition.setDialog(this.dialog);
216 addHocAreaDefinition.setType(this.type);
217 addHocAreaDefinition.setRenderType(getTemplateDefinition().getRenderType());
218 areaDefinition = addHocAreaDefinition;
219 }
220
221 @Override
222 public void end(Appendable out) throws RenderException {
223
224 try {
225 if (canRenderAreaScript()) {
226 if (isInherit() && getContent() != null && areaDefinition.getInheritance() != null) {
227 try {
228 setContent(new DefaultInheritanceContentDecorator(getContent(), areaDefinition.getInheritance()).wrapNode(getContent()));
229 } catch (RepositoryException e) {
230 throw new RuntimeRepositoryException(e);
231 }
232 }
233 List<Node> listOfComponents = null;
234 int numberOfComponents = 0;
235 if (getContent() != null) {
236 listOfComponents = NodeUtil.asList(NodeUtil.getNodes(getContent(), NodeTypes.Component.NAME));
237 numberOfComponents = listOfComponents.size();
238 }
239 if (renderingEngine.getRenderEmptyAreas() || numberOfComponents > 0 || !(AreaDefinition.TYPE_LIST.equals(areaDefinition.getType()) || AreaDefinition.TYPE_SINGLE.equals(areaDefinition.getType()))) {
240
241 Map<String, Object> contextObjects = new HashMap<String, Object>();
242
243 List<ContentMap> components = new ArrayList<ContentMap>();
244
245 if (getContent() != null) {
246 if (numberOfComponents > maxComponents) {
247 listOfComponents = listOfComponents.subList(0, maxComponents);
248 log.warn("The area {} have maximum number of components set to {}, but has got {} components. Exceeded components won't be added.", getContent(), maxComponents, numberOfComponents);
249 }
250
251 for (Node node : listOfComponents) {
252 components.add(new ContentMap(node));
253 }
254 }
255
256 if (AreaDefinition.TYPE_SINGLE.equals(type)) {
257 if (components.size() > 1) {
258 log.warn("Single area [{}]: expected one component node but found [{}].", getContent(), components.size());
259 }
260 if (components.size() >= 1) {
261 contextObjects.put(ATTRIBUTE_COMPONENT, components.get(0));
262 } else {
263 contextObjects.put(ATTRIBUTE_COMPONENT, null);
264 }
265 } else {
266 contextObjects.put(ATTRIBUTE_COMPONENTS, components);
267 }
268
269
270
271
272 final ConfiguredAreaDefinition override = new ConfiguredAreaDefinition(areaDefinition.getTemplateAvailability());
273 if (areaDefinition.getTemplateScript() == null) {
274 override.setRenderType(NoScriptRenderer.NO_SCRIPT_RENDERER);
275 } else if (areaDefinition.getRenderType() == null) {
276 override.setRenderType(getTemplateDefinition().getRenderType());
277 }
278 if (areaDefinition.getI18nBasename() == null) {
279 override.setI18nBasename(getTemplateDefinition().getI18nBasename());
280 }
281
282 final ConfiguredAreaDefinition mergedAreaDefinition = BeanMergerUtil.merge(override, areaDefinition);
283
284 getWebContext().push(getWebContext().getRequest(), getWebContext().getResponse());
285 setAttributesInWebContext(contextAttributes, WebContext.LOCAL_SCOPE);
286 try {
287 AppendableOnlyOutputProvider appendable = new AppendableOnlyOutputProvider(out);
288 renderingEngine.render(getContent(), mergedAreaDefinition, contextObjects, appendable);
289 } finally {
290 getWebContext().pop();
291 restoreAttributesInWebContext(contextAttributes, WebContext.LOCAL_SCOPE);
292 }
293 }
294 }
295 if (renderComments()) {
296 MarkupHelper helper = new MarkupHelper(out);
297 helper.closeComment(CMS_AREA);
298 }
299 } catch (Exception e) {
300 throw new RenderException("Can't render area " + getContent() + " with name " + this.name, e);
301 }
302 }
303
304 protected Node tryToCreateAreaNode(Node parentNode) throws RenderException {
305 Node area = null;
306 try {
307 if (parentNode.hasNode(name)) {
308 area = parentNode.getNode(name);
309 } else {
310 if (parentNode.getDefinition().isProtected()) {
311 log.debug("Not auto-creating area '{}', node is protected.");
312 return null;
313 } else if (!this.optional) {
314
315 area = createNewAreaNode(parentNode);
316 }
317 }
318 } catch (RepositoryException e) {
319
320 log.error("Can't autocreate area '{}'.", area, e);
321 }
322
323 if (area != null) {
324
325 final AutoGenerationConfiguration autoGeneration = areaDefinition.getAutoGeneration();
326 if (autoGeneration != null && autoGeneration.getGeneratorClass() != null) {
327 try {
328 final String areaId = area.getIdentifier();
329 final String workspaceName = area.getSession().getWorkspace().getName();
330 MgnlContext.doInSystemContext(new MgnlContext.RepositoryOp() {
331 @Override
332 public void doExec() throws RepositoryException {
333 Node areaNodeInSystemSession = NodeUtil.getNodeByIdentifier(workspaceName, areaId);
334 try {
335 ((Generator<AutoGenerationConfiguration>) Components.newInstance(autoGeneration.getGeneratorClass(), areaNodeInSystemSession)).generate(autoGeneration);
336 } catch (RenderException e) {
337 log.error("Can't render autogenerated area '{}'.", areaNodeInSystemSession);
338 }
339 return;
340 }
341 });
342 } catch (RepositoryException e) {
343 log.error("Can't autocreate area '{}'.", area, e);
344 }
345 }
346 }
347 return area;
348 }
349
350 protected AreaDefinition resolveAreaDefinition() {
351 if (areaDefinition != null) {
352 return areaDefinition;
353 }
354
355 if (!StringUtils.isEmpty(name)) {
356 if (getTemplateDefinition() != null && getTemplateDefinition().getAreas().containsKey(name)) {
357 return getTemplateDefinition().getAreas().get(name);
358 }
359 }
360
361
362 return null;
363 }
364
365 protected TemplateDefinition resolveTemplateDefinition() throws RenderException {
366
367 RenderableDefinition renderableDefinition = getRenderingContext().getRenderableDefinition();
368 RenderableDefinition variation = variationResolver.resolveVariation(renderableDefinition);
369 renderableDefinition = variation == null ? renderableDefinition : variation;
370
371 if (renderableDefinition == null || renderableDefinition instanceof TemplateDefinition) {
372 return (TemplateDefinition) renderableDefinition;
373 }
374 throw new RenderException("Current RenderableDefinition [" + renderableDefinition + "] is not of type TemplateDefinition. Areas cannot be supported");
375 }
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395 private boolean canRenderAreaScript() {
396 if (!this.isAreaDefinitionEnabled) {
397 return false;
398 }
399 if (getContent() != null) {
400 return true;
401 }
402 if (this.optional && this.getServer().isAdmin() && !MgnlContext.getAggregationState().isPreviewMode()) {
403 return true;
404 }
405 return false;
406 }
407
408 private String resolveDialog() {
409 return dialog != null ? dialog : areaDefinition != null ? areaDefinition.getDialog() : null;
410 }
411
412 private String resolveType() {
413 return type != null ? type : areaDefinition != null && areaDefinition.getType() != null ? areaDefinition.getType() : AreaDefinition.DEFAULT_TYPE;
414 }
415
416 private String resolveName() {
417 return name != null ? name : areaDefinition != null ? areaDefinition.getName() : null;
418 }
419
420 private Boolean resolveOptional() {
421 return optional != null ? optional : areaDefinition != null && areaDefinition.getOptional() != null ? areaDefinition.getOptional() : Boolean.FALSE;
422 }
423
424 private Integer resolveMaximumOfComponents() {
425 return maxComponents != null ? maxComponents : areaDefinition != null && areaDefinition.getMaxComponents() != null ? areaDefinition.getMaxComponents() : Integer.MAX_VALUE;
426 }
427
428 private Boolean resolveCreateAreaNode() {
429 return createAreaNode != null ? createAreaNode : areaDefinition != null && areaDefinition.getCreateAreaNode() != null ? areaDefinition.getCreateAreaNode() : Boolean.TRUE;
430 }
431
432 private boolean isInheritanceEnabled() {
433 return areaDefinition != null && areaDefinition.getInheritance() != null && areaDefinition.getInheritance().isEnabled() != null && areaDefinition.getInheritance().isEnabled();
434 }
435
436
437
438
439 @Deprecated
440 protected String resolveAvailableComponents() {
441 return new AvailableComponents(this::getWebContext).getValue(this).orElse(null);
442 }
443
444 public String getName() {
445 return name;
446 }
447
448 public void setName(String name) {
449 this.name = name;
450 }
451
452 public AreaDefinition getArea() {
453 return areaDefinition;
454 }
455
456 public void setArea(AreaDefinition area) {
457 this.areaDefinition = area;
458 }
459
460 public String getAvailableComponents() {
461 return availableComponents;
462 }
463
464 public void setAvailableComponents(String availableComponents) {
465 this.availableComponents = availableComponents;
466 }
467
468 public String getType() {
469 return type;
470 }
471
472 public void setType(String type) {
473 this.type = type;
474 }
475
476 public String getDialog() {
477 return dialog;
478 }
479
480 public void setDialog(String dialog) {
481 this.dialog = dialog;
482 }
483
484 public String getLabel() {
485 return label;
486 }
487
488 public void setLabel(String label) {
489 this.label = label;
490 }
491
492 public String getDescription() {
493 return description;
494 }
495
496 public void setDescription(String description) {
497 this.description = description;
498 }
499
500 public boolean isInherit() {
501 return inherit;
502 }
503
504 public void setInherit(boolean inherit) {
505 this.inherit = inherit;
506 }
507
508 public Map<String, Object> getContextAttributes() {
509 return contextAttributes;
510 }
511
512 public void setContextAttributes(Map<String, Object> contextAttributes) {
513 this.contextAttributes = contextAttributes;
514 }
515
516 public Integer getMaxComponents() {
517 return maxComponents;
518 }
519
520 public void setMaxComponents(Integer maxComponents) {
521 this.maxComponents = maxComponents;
522 }
523
524 public Boolean getCreateAreaNode() {
525 return createAreaNode;
526 }
527
528 public void setCreateAreaNode(Boolean createAreaNode) {
529 this.createAreaNode = createAreaNode;
530 }
531
532 public AreaDefinition getAreaDefinition() {
533 return areaDefinition;
534 }
535
536 public Boolean getOptional() {
537 return optional;
538 }
539
540 @Override
541 protected boolean renderComments() {
542 return this.isAreaDefinitionEnabled && isAdmin() && !MgnlContext.getAggregationState().isPreviewMode() && hasPermission(getContent());
543 }
544 }