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