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.functions;
35  
36  import info.magnolia.cms.beans.config.ServerConfiguration;
37  import info.magnolia.cms.core.MgnlNodeType;
38  import info.magnolia.cms.core.NodeData;
39  import info.magnolia.cms.i18n.I18nContentSupportFactory;
40  import info.magnolia.cms.util.ContentUtil;
41  import info.magnolia.cms.util.SiblingsHelper;
42  import info.magnolia.context.MgnlContext;
43  import info.magnolia.exception.RuntimeRepositoryException;
44  import info.magnolia.jcr.util.ContentMap;
45  import info.magnolia.jcr.util.NodeUtil;
46  import info.magnolia.jcr.util.PropertyUtil;
47  import info.magnolia.jcr.util.SessionUtil;
48  import info.magnolia.jcr.wrapper.HTMLEscapingNodeWrapper;
49  import info.magnolia.jcr.wrapper.InheritanceNodeWrapper;
50  import info.magnolia.link.LinkUtil;
51  import info.magnolia.objectfactory.Components;
52  import info.magnolia.repository.RepositoryConstants;
53  
54  import java.util.ArrayList;
55  import java.util.Collection;
56  import java.util.List;
57  
58  import javax.jcr.Node;
59  import javax.jcr.PathNotFoundException;
60  import javax.jcr.Property;
61  import javax.jcr.PropertyType;
62  import javax.jcr.RepositoryException;
63  
64  import org.apache.commons.lang.StringUtils;
65  
66  /**
67   * This is an object exposing a couple of methods useful for templates; it's exposed in templates as "cmsfn".
68   *
69   * @version $Id$
70   */
71  public class TemplatingFunctions {
72  
73      public Node asJCRNode(ContentMap contentMap) {
74          return contentMap == null ? null : contentMap.getJCRNode();
75      }
76  
77      public ContentMap asContentMap(Node content) {
78          return content == null ? null : new ContentMap(content);
79      }
80  
81      public List<Node> children(Node content) throws RepositoryException {
82          return content == null ? null : asNodeList(NodeUtil.getNodes(content, NodeUtil.EXCLUDE_META_DATA_FILTER));
83      }
84  
85      public List<Node> children(Node content, String nodeTypeName) throws RepositoryException {
86          return content == null ? null : asNodeList(NodeUtil.getNodes(content, nodeTypeName));
87      }
88  
89      public List<ContentMap> children(ContentMap content) throws RepositoryException {
90          return content == null ? null : asContentMapList(NodeUtil.getNodes(asJCRNode(content), NodeUtil.EXCLUDE_META_DATA_FILTER));
91      }
92  
93      public List<ContentMap> children(ContentMap content, String nodeTypeName) throws RepositoryException {
94          return content == null ? null : asContentMapList(NodeUtil.getNodes(asJCRNode(content), nodeTypeName));
95      }
96  
97      public ContentMap root(ContentMap contentMap) throws RepositoryException {
98          return contentMap == null ? null : asContentMap(this.root(contentMap.getJCRNode()));
99      }
100 
101     public ContentMap root(ContentMap contentMap, String nodeTypeName) throws RepositoryException {
102         return contentMap == null ? null : asContentMap(this.root(contentMap.getJCRNode(), nodeTypeName));
103     }
104 
105     public Node root(Node content) throws RepositoryException {
106         return this.root(content, null);
107     }
108 
109     public Node root(Node content, String nodeTypeName) throws RepositoryException {
110         if (content == null) {
111             return null;
112         }
113         if (nodeTypeName == null) {
114             return (Node) content.getAncestor(0);
115         }
116         if (isRoot(content) && content.isNodeType(nodeTypeName)) {
117             return content;
118         }
119 
120         Node parentNode = this.parent(content, nodeTypeName);
121         while (parent(parentNode, nodeTypeName) != null) {
122             parentNode = this.parent(parentNode, nodeTypeName);
123         }
124         return parentNode;
125     }
126 
127     public ContentMap parent(ContentMap contentMap) throws RepositoryException {
128         return contentMap == null ? null : asContentMap(this.parent(contentMap.getJCRNode()));
129     }
130 
131     public ContentMap parent(ContentMap contentMap, String nodeTypeName) throws RepositoryException {
132         return contentMap == null ? null : asContentMap(this.parent(contentMap.getJCRNode(), nodeTypeName));
133     }
134 
135     public Node parent(Node content) throws RepositoryException {
136         return this.parent(content, null);
137     }
138 
139     public Node parent(Node content, String nodeTypeName) throws RepositoryException {
140         if (content == null) {
141             return null;
142         }
143         if (isRoot(content)) {
144             return null;
145         }
146         if (nodeTypeName == null) {
147             return content.getParent();
148         }
149         Node parent = content.getParent();
150         while (!parent.isNodeType(nodeTypeName)) {
151             if (isRoot(parent)) {
152                 return null;
153             }
154             parent = parent.getParent();
155         }
156         return parent;
157     }
158 
159     /**
160      * Returns the page's {@link ContentMap} of the passed {@link ContentMap}. If the passed {@link ContentMap} represents a page, the passed {@link ContentMap} will be returned.
161      * If the passed {@link ContentMap} has no parent page at all, null is returned.
162      *
163      * @param content the {@link ContentMap} to get the page's {@link ContentMap} from.
164      * @return returns the page {@link ContentMap} of the passed content {@link ContentMap}.
165      * @throws RepositoryException
166      */
167     public ContentMap page(ContentMap content) throws RepositoryException {
168         return content == null ? null : asContentMap(page(content.getJCRNode()));
169     }
170 
171     /**
172      * Returns the page {@link Node} of the passed node. If the passed {@link Node} is a page, the passed {@link Node} will be returned.
173      * If the passed Node has no parent page at all, null is returned.
174      *
175      * @param content the {@link Node} to get the page from.
176      * @return returns the page {@link Node} of the passed content {@link Node}.
177      * @throws RepositoryException
178      */
179     public Node page(Node content) throws RepositoryException {
180         if (content == null) {
181             return null;
182         }
183         if (content.isNodeType(MgnlNodeType.NT_PAGE)) {
184             return content;
185         }
186         return parent(content, MgnlNodeType.NT_PAGE);
187     }
188 
189     public List<ContentMap> ancestors(ContentMap contentMap) throws RepositoryException {
190         return ancestors(contentMap, null);
191     }
192 
193     public List<ContentMap> ancestors(ContentMap contentMap, String nodeTypeName) throws RepositoryException {
194         List<Node> ancestorsAsNodes = this.ancestors(contentMap.getJCRNode(), nodeTypeName);
195         return asContentMapList(ancestorsAsNodes);
196     }
197 
198     public List<Node> ancestors(Node content) throws RepositoryException {
199         return content == null ? null : this.ancestors(content, null);
200     }
201 
202     public List<Node> ancestors(Node content, String nodeTypeName) throws RepositoryException {
203         if (content == null) {
204             return null;
205         }
206         List<Node> ancestors = new ArrayList<Node>();
207         int depth = content.getDepth();
208         for (int i = 1; i < depth; ++i) {
209             Node possibleAncestor = (Node) content.getAncestor(i);
210             if (nodeTypeName == null) {
211                 ancestors.add(possibleAncestor);
212             } else {
213                 if (possibleAncestor.isNodeType(nodeTypeName)) {
214                     ancestors.add(possibleAncestor);
215                 }
216             }
217         }
218         return ancestors;
219     }
220 
221     public Node inherit(Node content) throws RepositoryException {
222         return inherit(content,null);
223     }
224 
225     public Node inherit(Node content, String relPath) throws RepositoryException {
226         if (content == null) {
227             return null;
228         }
229         InheritanceNodeWrapper inheritedNode = new InheritanceNodeWrapper(content);
230 
231         if (StringUtils.isBlank(relPath)) {
232             return inheritedNode;
233         }
234 
235         try {
236             Node subNode = inheritedNode.getNode(relPath);
237             return NodeUtil.unwrap(subNode);
238         } catch (PathNotFoundException e) {
239             // TODO fgrilli: rethrow exception?
240         }
241         return null;
242     }
243 
244     public ContentMap inherit(ContentMap content) throws RepositoryException {
245         return inherit(content, null);
246     }
247 
248     public ContentMap inherit(ContentMap content, String relPath) throws RepositoryException {
249         if (content == null) {
250             return null;
251         }
252         Node node = inherit(content.getJCRNode(), relPath);
253         return node == null ? null : new ContentMap(node);
254     }
255 
256 
257     public Property inheritProperty(Node content, String relPath) throws RepositoryException {
258         if (content == null) {
259             return null;
260         }
261         if (StringUtils.isBlank(relPath)) {
262             throw new IllegalArgumentException("relative path cannot be null or empty");
263         }
264         InheritanceNodeWrapper inheritedNode = new InheritanceNodeWrapper(content);
265         try {
266             return inheritedNode.getProperty(relPath);
267 
268         } catch (PathNotFoundException e) {
269             // TODO fgrilli: rethrow exception?
270         } catch (RepositoryException e) {
271             // TODO fgrilli:rethrow exception?
272         }
273 
274         return null;
275     }
276 
277     public Property inheritProperty(ContentMap content, String relPath) throws RepositoryException {
278         if (content == null) {
279             return null;
280         }
281         return inheritProperty(content.getJCRNode(), relPath);
282     }
283 
284     public List<Node> inheritList(Node content, String relPath) throws RepositoryException {
285         if (content == null) {
286             return null;
287         }
288         if (StringUtils.isBlank(relPath)) {
289             throw new IllegalArgumentException("relative path cannot be null or empty");
290         }
291         InheritanceNodeWrapper inheritedNode = new InheritanceNodeWrapper(content);
292         Node subNode = inheritedNode.getNode(relPath);
293         return children(subNode);
294     }
295 
296     public List<ContentMap> inheritList(ContentMap content, String relPath) throws RepositoryException {
297         if (content == null) {
298             return null;
299         }
300         if (StringUtils.isBlank(relPath)) {
301             throw new IllegalArgumentException("relative path cannot be null or empty");
302         }
303         InheritanceNodeWrapper inheritedNode = new InheritanceNodeWrapper(asJCRNode(content));
304         Node subNode = inheritedNode.getNode(relPath);
305         return children(new ContentMap(subNode));
306 
307     }
308 
309     public boolean isInherited(Node content) {
310         if (content instanceof InheritanceNodeWrapper) {
311             return ((InheritanceNodeWrapper) content).isInherited();
312         }
313         return false;
314     }
315 
316     public boolean isInherited(ContentMap content) {
317         return isInherited(asJCRNode(content));
318     }
319 
320     public boolean isFromCurrentPage(Node content) {
321         return !isInherited(content);
322     }
323 
324     public boolean isFromCurrentPage(ContentMap content) {
325         return isFromCurrentPage(asJCRNode(content));
326     }
327 
328     /**
329      * Create link for the Node identified by nodeIdentifier in the specified workspace.
330      */
331     public String link(String workspace, String nodeIdentifier) {
332         try {
333             return LinkUtil.createLink(workspace, nodeIdentifier);
334         } catch (RepositoryException e) {
335             return null;
336         }
337     }
338 
339     /**
340      * FIXME Add a LinkUtil.createLink(Property property).... Dirty Hack.
341      * FIXME: Should be changed when a decision is made on SCRUM-525.
342      */
343     public String link(Property property) {
344         try {
345             Node parentNode = null;
346             String propertyName = null;
347             if (property.getType() == PropertyType.BINARY) {
348                 parentNode = property.getParent().getParent();
349                 propertyName = property.getParent().getName();
350             } else {
351                 parentNode = property.getParent();
352                 propertyName = property.getName();
353             }
354             NodeData equivNodeData = ContentUtil.asContent(parentNode).getNodeData(propertyName);
355             return LinkUtil.createLink(equivNodeData);
356         } catch (Exception e) {
357             return null;
358         }
359     }
360 
361     // TODO fgrilli: LinkUtil needs to be Node capable and not only Content. Switch to node based impl when SCRUM-242
362     // will be done.
363     public String link(Node content) {
364         return content == null ? null : LinkUtil.createLink(ContentUtil.asContent(content));
365     }
366 
367     public String link(ContentMap contentMap) throws RepositoryException {
368         return contentMap == null ? null : this.link(asJCRNode(contentMap));
369     }
370 
371     /**
372      * Get the language used currently.
373      * @return The language as a String.
374      */
375     public String language(){
376         return I18nContentSupportFactory.getI18nSupport().getLocale().toString();
377     }
378     /**
379      * Returns an external link prepended with <code>http://</code> in case the protocol is missing or an empty String
380      * if the link does not exist.
381      *
382      * @param content The node where the link property is stored on.
383      * @param linkPropertyName The property where the link value is stored in.
384      * @return The link prepended with <code>http://</code>
385      */
386     public String externalLink(Node content, String linkPropertyName) {
387         String externalLink = PropertyUtil.getString(content, linkPropertyName);
388         if (StringUtils.isBlank(externalLink)) {
389             return StringUtils.EMPTY;
390         }
391         if (!hasProtocol(externalLink)) {
392             externalLink = "http://" + externalLink;
393         }
394         return externalLink;
395     }
396 
397     /**
398      * Returns an external link prepended with <code>http://</code> in case the protocol is missing or an empty String
399      * if the link does not exist.
400      *
401      * @param content The node's map representation where the link property is stored on.
402      * @param linkPropertyName The property where the link value is stored in.
403      * @return The link prepended with <code>http://</code>
404      */
405     public String externalLink(ContentMap content, String linkPropertyName) {
406         return externalLink(asJCRNode(content), linkPropertyName);
407     }
408 
409     /**
410      * Return a link title based on the @param linkTitlePropertyName. When property @param linkTitlePropertyName is
411      * empty or null, the link itself is provided as the linkTitle (prepended with <code>http://</code>).
412      *
413      * @param content The node where the link property is stored on.
414      * @param linkPropertyName The property where the link value is stored in.
415      * @param linkTitlePropertyName The property where the link title value is stored
416      * @return the resolved link title value
417      */
418     public String externalLinkTitle(Node content, String linkPropertyName, String linkTitlePropertyName) {
419         String linkTitle = PropertyUtil.getString(content, linkTitlePropertyName);
420         if (StringUtils.isNotEmpty(linkTitle)) {
421             return linkTitle;
422         }
423         return externalLink(content, linkPropertyName);
424     }
425 
426     /**
427      * Return a link title based on the @param linkTitlePropertyName. When property @param linkTitlePropertyName is
428      * empty or null, the link itself is provided as the linkTitle (prepended with <code>http://</code>).
429      *
430      * @param content The node where the link property is stored on.
431      * @param linkPropertyName The property where the link value is stored in.
432      * @param linkTitlePropertyName The property where the link title value is stored
433      * @return the resolved link title value
434      */
435     public String externalLinkTitle(ContentMap content, String linkPropertyName, String linkTitlePropertyName) {
436         return externalLinkTitle(asJCRNode(content), linkPropertyName, linkTitlePropertyName);
437     }
438 
439     public boolean isEditMode() {
440         // TODO : see CmsFunctions.isEditMode, which checks a couple of other properties.
441         return isAuthorInstance() && !isPreviewMode();
442     }
443 
444     public boolean isPreviewMode() {
445         return MgnlContext.getAggregationState().isPreviewMode();
446     }
447 
448     public boolean isAuthorInstance() {
449         return Components.getComponent(ServerConfiguration.class).isAdmin();
450     }
451 
452     public boolean isPublicInstance() {
453         return !isAuthorInstance();
454     }
455 
456     /**
457      * Util method to create html attributes <code>name="value"</code>. If the value is empty an empty string will be returned.
458      * This is mainly helpful to avoid empty attributes.
459      */
460     public String createHtmlAttribute(String name, String value) {
461         value = StringUtils.trim(value);
462         if (StringUtils.isNotEmpty(value)) {
463             return new StringBuffer().append(name).append("=\"").append(value).append("\"").toString();
464         }
465         return StringUtils.EMPTY;
466     }
467 
468     /**
469      * Returns an instance of SiblingsHelper for the given node.
470      */
471     public SiblingsHelper siblings(Node node) throws RepositoryException {
472         return SiblingsHelper.of(ContentUtil.asContent(node));
473     }
474 
475     public SiblingsHelper siblings(ContentMap node) throws RepositoryException {
476         return siblings(asJCRNode(node));
477     }
478 
479     /**
480      * Return the Node for the Given Path
481      * from the website repository.
482      */
483     public Node content(String path){
484         return content(RepositoryConstants.WEBSITE, path);
485     }
486 
487     /**
488      * Return the Node for the Given Path
489      * from the given repository.
490      */
491     public Node content(String repository, String path){
492         return SessionUtil.getNode(repository, path);
493     }
494 
495     public List<ContentMap> asContentMapList(Collection<Node> nodeList) {
496         if (nodeList != null) {
497             List<ContentMap> contentMapList = new ArrayList<ContentMap>();
498             for (Node node : nodeList) {
499                 contentMapList.add(asContentMap(node));
500             }
501             return contentMapList;
502         }
503         return null;
504     }
505 
506     public List<Node> asNodeList(Collection<ContentMap> contentMapList) {
507         if (contentMapList != null) {
508             List<Node> nodeList = new ArrayList<Node>();
509             for (ContentMap node : contentMapList) {
510                 nodeList.add(node.getJCRNode());
511             }
512             return nodeList;
513         }
514         return null;
515     }
516 
517     // TODO fgrilli: should we unwrap children?
518     protected List<Node> asNodeList(Iterable<Node> nodes) {
519         List<Node> childList = new ArrayList<Node>();
520         for (Node child : nodes) {
521             childList.add(child);
522         }
523         return childList;
524     }
525 
526     // TODO fgrilli: should we unwrap children?
527     protected List<ContentMap> asContentMapList(Iterable<Node> nodes) {
528         List<ContentMap> childList = new ArrayList<ContentMap>();
529         for (Node child : nodes) {
530             childList.add(new ContentMap(child));
531         }
532         return childList;
533     }
534 
535     /**
536      * Checks if passed string has a <code>http://</code> protocol.
537      *
538      * @param link The link to check
539      * @return If @param link contains a <code>http://</code> protocol
540      */
541     private boolean hasProtocol(String link) {
542         return link != null && link.contains("://");
543     }
544 
545     /**
546      * Checks if the passed {@link Node} is the jcr root '/' of the workspace.
547      * @param content {@link Node} to check if its root.
548      * @return if @param content is the jcr workspace root.
549      * @throws RepositoryException
550      */
551     private boolean isRoot(Node content) throws RepositoryException {
552         return content.getDepth() == 0;
553     }
554 
555     /**
556      * Removes escaping of HTML on properties.
557      */
558     public ContentMap decode(ContentMap content){
559         return asContentMap(decode(content.getJCRNode()));
560     }
561 
562     /**
563      * Removes escaping of HTML on properties.
564      */
565     public Node decode(Node content){
566         if (content instanceof HTMLEscapingNodeWrapper) {
567             try {
568                 return ((HTMLEscapingNodeWrapper) content).getWrappedNode();
569             } catch (RepositoryException e) {
570                 throw new RuntimeRepositoryException(e);
571             }
572         }
573         return content;
574     }
575 
576     /**
577      * Adds escaping of HTML on properties as well as changing line breaks into &lt;br/&gt; tags.
578      */
579     public Node encode(Node content){
580         return content != null ? new HTMLEscapingNodeWrapper(content, true) : null;
581     }
582 
583 
584 }