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