View Javadoc

1   /**
2    * This file Copyright (c) 2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
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.context.MgnlContext;
40  import info.magnolia.context.WebContext;
41  import info.magnolia.jcr.predicate.AbstractPredicate;
42  import info.magnolia.jcr.util.ContentMap;
43  import info.magnolia.jcr.util.NodeUtil;
44  import info.magnolia.jcr.wrapper.InheritanceNodeWrapper;
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.template.AreaDefinition;
51  import info.magnolia.rendering.template.AutoGenerationConfiguration;
52  import info.magnolia.rendering.template.ComponentAvailability;
53  import info.magnolia.rendering.template.RenderableDefinition;
54  import info.magnolia.rendering.template.TemplateDefinition;
55  import info.magnolia.rendering.template.configured.ConfiguredAreaDefinition;
56  
57  import java.io.IOException;
58  import java.util.ArrayList;
59  import java.util.HashMap;
60  import java.util.Iterator;
61  import java.util.List;
62  import java.util.Map;
63  
64  import javax.jcr.Node;
65  import javax.jcr.RepositoryException;
66  
67  import org.apache.commons.lang.StringUtils;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  
71  
72  /**
73   * Renders an area and outputs a marker that instructs the page editor to place a bar at this location.
74   *
75   * @version $Id$
76   */
77  public class AreaElement extends AbstractContentTemplatingElement {
78  
79      public static final String CMS_AREA = "cms:area";
80  
81      public static final String ATTRIBUTE_COMPONENT = "component";
82      public static final String ATTRIBUTE_COMPONENTS = "components";
83  
84      private final RenderingEngine renderingEngine;
85  
86      private Node parentNode;
87      private Node areaNode;
88      private TemplateDefinition templateDefinition;
89      private AreaDefinition areaDefinition;
90      private String name;
91      private String type;
92      private String dialog;
93      private String availableComponents;
94      private String label;
95      private boolean inherit;
96  
97      private Map<String, Object> contextAttributes = new HashMap<String, Object>();
98  
99      private boolean commentClosed = false;
100 
101     public AreaElement(ServerConfiguration server, RenderingContext renderingContext, RenderingEngine renderingEngine) {
102         super(server, renderingContext);
103         this.renderingEngine = renderingEngine;
104     }
105 
106     @Override
107     public void begin(Appendable out) throws IOException, RenderException {
108         this.parentNode = getTargetContent();
109 
110         this.templateDefinition = resolveTemplateDefinition();
111         this.areaDefinition = resolveAreaDefinition();
112 
113         // set the values based on the area definition if not passed
114         this.name = resolveName();
115         this.dialog = resolveDialog();
116         this.type = resolveType();
117         this.label = resolveLabel();
118         this.availableComponents = resolveAvailableComponents();
119         this.inherit = isInheritanceEnabled();
120 
121         // build an adhoc area definition if no area definition can be resolved
122         if(areaDefinition == null){
123             buildAdHocAreaDefinition();
124         }
125 
126         this.areaNode = resolveAreaNode();
127 
128         if (isAdmin()) {
129             MarkupHelper helper = new MarkupHelper(out);
130 
131             helper.openComment(CMS_AREA).attribute("content", getNodePath(parentNode));
132             helper.attribute("name", this.name);
133             helper.attribute("availableComponents", this.availableComponents);
134             helper.attribute("type", this.type);
135             helper.attribute("dialog", this.dialog);
136             helper.attribute("label", this.label);
137             helper.attribute("inherit", String.valueOf(this.inherit));
138             helper.attribute("optional", String.valueOf(this.areaDefinition.isOptional()));
139             if(isOptionalAreaCreated()) {
140                 helper.attribute("created", "true");
141             }
142             helper.attribute("showAddButton", String.valueOf(shouldShowAddButton()));
143             helper.append(" -->\n");
144 
145         }
146     }
147 
148     private Node createNewAreaNode() throws RepositoryException {
149         final String parentId = this.parentNode.getIdentifier();
150         final String workspaceName = this.parentNode.getSession().getWorkspace().getName();
151 
152         MgnlContext.doInSystemContext(new MgnlContext.Op<Void, RepositoryException>() {
153             @Override
154             public Void exec() throws RepositoryException {
155                 Node parentNodeInSystemSession = NodeUtil.getNodeByIdentifier(workspaceName, parentId);
156                 Node newAreaNode = NodeUtil.createPath(parentNodeInSystemSession, AreaElement.this.name, MgnlNodeType.NT_AREA);
157                 NodeUtil.createPath(newAreaNode, MetaData.DEFAULT_META_NODE, MgnlNodeType.NT_METADATA);
158                 newAreaNode.getSession().save();
159                 return null;
160             }
161         });
162         return this.parentNode.getNode(this.name);
163     }
164 
165     protected void buildAdHocAreaDefinition() {
166         ConfiguredAreaDefinition addHocAreaDefinition = new ConfiguredAreaDefinition();
167         addHocAreaDefinition.setName(this.name);
168         addHocAreaDefinition.setDialog(this.dialog);
169         addHocAreaDefinition.setType(this.type);
170         addHocAreaDefinition.setRenderType(this.templateDefinition.getRenderType());
171         areaDefinition = addHocAreaDefinition;
172     }
173 
174     @Override
175     public void end(Appendable out) throws RenderException {
176         try {
177             if (isEnabled()) {
178                 Map<String, Object> contextObjects = new HashMap<String, Object>();
179 
180                 List<ContentMap> components = new ArrayList<ContentMap>();
181 
182                 if (areaNode != null) {
183                     for (Node node : NodeUtil.getNodes(areaNode, MgnlNodeType.NT_COMPONENT)) {
184                         components.add(new ContentMap(node));
185                     }
186                 }
187                 if(AreaDefinition.TYPE_SINGLE.equals(type)) {
188                     if(components.size() > 1) {
189                         throw new RenderException("Can't render single area [" + areaNode + "]: expected one component node but found more.");
190                     }
191                     if(components.size() == 1) {
192                         contextObjects.put(ATTRIBUTE_COMPONENT, components.get(0));
193                     } else {
194                         contextObjects.put(ATTRIBUTE_COMPONENT, null);
195                     }
196                 } else {
197                     contextObjects.put(ATTRIBUTE_COMPONENTS, components);
198                 }
199                 // FIXME we shouldn't manipulate the area definition directly
200                 // we should use merge with the proxy approach
201                 if(areaDefinition.getRenderType() == null && areaDefinition instanceof ConfiguredAreaDefinition){
202                     ((ConfiguredAreaDefinition)areaDefinition).setRenderType(this.templateDefinition.getRenderType());
203                 }
204 
205                 // FIXME we shouldn't manipulate the area definition directly
206                 // we should use merge with the proxy approach
207                 if(areaDefinition.getI18nBasename() == null && areaDefinition instanceof ConfiguredAreaDefinition){
208                     ((ConfiguredAreaDefinition)areaDefinition).setI18nBasename(this.templateDefinition.getI18nBasename());
209                 }
210 
211                 WebContext webContext = MgnlContext.getWebContext();
212                 webContext.push(webContext.getRequest(), webContext.getResponse());
213                 setAttributesInWebContext(contextAttributes, WebContext.LOCAL_SCOPE);
214                 try {
215                     renderingEngine.render(areaNode, areaDefinition, contextObjects, new AppendableOnlyOutputProvider(out));
216                 } finally {
217                     webContext.pop();
218                     webContext.setPageContext(null);
219                     restoreAttributesInWebContext(contextAttributes, WebContext.LOCAL_SCOPE);
220                 }
221 
222             }
223 
224             if (isAdmin()) {
225                 MarkupHelper helper = new MarkupHelper(out);
226                 helper.closeComment(CMS_AREA);
227             }
228         } catch (Exception e) {
229             throw new RenderException("Can't render area " + areaNode + " with name " + this.name, e);
230         }
231     }
232 
233     protected Node resolveAreaNode() throws RenderException {
234         final Node content = getTargetContent();
235         Node area = null;
236         try {
237             if(content.hasNode(name)){
238                 area = content.getNode(name);
239             } else {
240                 //autocreate and save area only if it's not optional
241                 if(!areaDefinition.isOptional()) {
242                     area = createNewAreaNode();
243                 }
244             }
245         }
246         catch (RepositoryException e) {
247             throw new RenderException("Can't access area node [" + name + "] on [" + content + "]", e);
248         }
249         //at this stage we can be sure that the target area, unless optional, has been created.
250         if(area != null) {
251             //TODO fgrilli: what about other component types to be autogenerated (i.e. autogenerating an entire page)?
252             final AutoGenerationConfiguration autoGeneration = areaDefinition.getAutoGeneration();
253             if (autoGeneration != null && autoGeneration.getGeneratorClass() != null) {
254                 Components.newInstance(autoGeneration.getGeneratorClass(), area).generate(autoGeneration);
255             }
256             if(isInherit()) {
257                 area = new InheritanceNodeWrapper(area, new InheritablePredicate(area));
258             }
259         }
260         return area;
261     }
262 
263     protected AreaDefinition resolveAreaDefinition() {
264         if (areaDefinition != null) {
265             return areaDefinition;
266         }
267 
268         if (!StringUtils.isEmpty(name)) {
269             if (templateDefinition != null && templateDefinition.getAreas().containsKey(name)) {
270                 return templateDefinition.getAreas().get(name);
271             }
272         }
273         // happens if no area definition is passed or configured
274         // an ad-hoc area definition will be created
275         return null;
276     }
277 
278     protected TemplateDefinition resolveTemplateDefinition() throws RenderException {
279         final RenderableDefinition renderableDefinition = getRenderingContext().getRenderableDefinition();
280         if (renderableDefinition == null || renderableDefinition instanceof TemplateDefinition) {
281             return (TemplateDefinition) renderableDefinition;
282         }
283         throw new RenderException("Current RenderableDefinition [" + renderableDefinition + "] is not of type TemplateDefinition. Areas cannot be supported");
284     }
285 
286     private boolean isEnabled() {
287         return areaDefinition != null && areaDefinition.isEnabled();
288     }
289 
290     private String resolveDialog() {
291         return dialog != null ? dialog : areaDefinition != null ? areaDefinition.getDialog() : null;
292     }
293 
294     private String resolveType() {
295         return type != null ? type : areaDefinition != null && areaDefinition.getType() != null ? areaDefinition.getType() : AreaDefinition.DEFAULT_TYPE;
296     }
297 
298     private String resolveName() {
299         return name != null ? name : (areaDefinition != null ? areaDefinition.getName() : null);
300     }
301 
302     private String resolveLabel() {
303         return label != null ? label : (areaDefinition != null && StringUtils.isNotBlank(areaDefinition.getTitle()) ? areaDefinition.getTitle() : StringUtils.capitalize(name));
304     }
305 
306     private boolean isInheritanceEnabled() {
307         return areaDefinition != null && areaDefinition.getInheritance() != null && areaDefinition.getInheritance().isEnabled();
308     }
309 
310     private boolean isOptionalAreaCreated() {
311         return this.areaDefinition.isOptional() && this.areaNode != null;
312     }
313 
314     private boolean hasComponents(Node parent) throws RenderException {
315         try {
316             return NodeUtil.getNodes(parent, MgnlNodeType.NT_COMPONENT).iterator().hasNext();
317         } catch (RepositoryException e) {
318            throw new RenderException(e);
319         }
320     }
321 
322     protected String resolveAvailableComponents() {
323         if (StringUtils.isNotEmpty(availableComponents)) {
324             return availableComponents;
325         }
326         if (areaDefinition != null && areaDefinition.getAvailableComponents().size() > 0) {
327             Iterator<ComponentAvailability> iterator = areaDefinition.getAvailableComponents().values().iterator();
328             List<String> componentIds = new ArrayList<String>();
329             while (iterator.hasNext()) {
330                 ComponentAvailability availableComponent = iterator.next();
331                 if(availableComponent.isEnabled()) {
332                     componentIds.add(availableComponent.getId());
333                 }
334             }
335             return StringUtils.join(componentIds, ',');
336         }
337         return "";
338     }
339 
340     private boolean shouldShowAddButton() throws RenderException {
341 
342         if(areaNode == null || type.equals(AreaDefinition.TYPE_NO_COMPONENT) || (type.equals(AreaDefinition.TYPE_SINGLE) && hasComponents(areaNode))) {
343             return false;
344         }
345 
346         return true;
347     }
348 
349     public String getName() {
350         return name;
351     }
352 
353     public void setName(String name) {
354         this.name = name;
355     }
356 
357     public AreaDefinition getArea() {
358         return areaDefinition;
359     }
360 
361     public void setArea(AreaDefinition area) {
362         this.areaDefinition = area;
363     }
364 
365     public String getAvailableComponents() {
366         return availableComponents;
367     }
368 
369     public void setAvailableComponents(String availableComponents) {
370         this.availableComponents = availableComponents;
371     }
372 
373     public String getType() {
374         return type;
375     }
376 
377     public void setType(String type) {
378         this.type = type;
379     }
380 
381     public String getDialog() {
382         return dialog;
383     }
384 
385     public void setDialog(String dialog) {
386         this.dialog = dialog;
387     }
388 
389     public boolean isInherit() {
390         return inherit;
391     }
392 
393     public void setInherit(boolean inherit) {
394         this.inherit = inherit;
395     }
396 
397     public Map<String, Object> getContextAttributes() {
398         return contextAttributes;
399     }
400 
401     public void setContextAttributes(Map<String, Object> contextAttributes) {
402         this.contextAttributes = contextAttributes;
403     }
404 
405     /**
406      * Controls output based on the existence and value of inheritable property.
407      *
408      * @version $Id$
409      *
410      */
411     private static class InheritablePredicate extends AbstractPredicate<Node> {
412 
413         private static final Logger log = LoggerFactory.getLogger(AreaElement.class);
414 
415         private Node root;
416 
417         public InheritablePredicate(Node root) {
418             this.root = root;
419         }
420 
421         @Override
422         public boolean evaluateTyped(Node t) {
423             // FIXME not all areas using inheritance want to use the inheritable flag
424             // we need a better configuration or change to an exclution flag
425             return true;
426 
427 //            try {
428 //                if(t.getPath().startsWith(root.getPath())){
429 //                    return true;
430 //                }
431 //                if(!t.isNodeType(MgnlNodeType.NT_COMPONENT)){
432 //                    return true;
433 //                }
434 //                return t.hasProperty("inheritable") && t.getProperty("inheritable").getBoolean();
435 //            } catch (RepositoryException e) {
436 //                log.warn("Failed to retrieve inheritable property for {}", t);
437 //                return false;
438 //            }
439         }
440     }
441 }