View Javadoc
1   /**
2    * This file Copyright (c) 2011-2015 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.NodeData;
39  import info.magnolia.cms.i18n.I18nContentSupportFactory;
40  import info.magnolia.cms.util.ContentUtil;
41  import info.magnolia.cms.util.QueryUtil;
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.MetaDataUtil;
46  import info.magnolia.jcr.util.NodeTypes;
47  import info.magnolia.jcr.util.NodeUtil;
48  import info.magnolia.jcr.util.PropertyUtil;
49  import info.magnolia.jcr.util.SessionUtil;
50  import info.magnolia.jcr.wrapper.HTMLEscapingNodeWrapper;
51  import info.magnolia.link.LinkUtil;
52  import info.magnolia.objectfactory.Components;
53  import info.magnolia.rendering.template.configured.ConfiguredInheritance;
54  import info.magnolia.repository.RepositoryConstants;
55  import info.magnolia.templating.inheritance.DefaultInheritanceContentDecorator;
56  
57  import java.net.URI;
58  import java.net.URISyntaxException;
59  import java.util.ArrayList;
60  import java.util.Arrays;
61  import java.util.Calendar;
62  import java.util.Collection;
63  import java.util.List;
64  
65  import javax.inject.Inject;
66  import javax.inject.Provider;
67  import javax.jcr.Node;
68  import javax.jcr.PathNotFoundException;
69  import javax.jcr.Property;
70  import javax.jcr.PropertyType;
71  import javax.jcr.RepositoryException;
72  
73  import org.apache.commons.lang3.StringUtils;
74  import org.apache.jackrabbit.util.ISO8601;
75  import org.slf4j.Logger;
76  import org.slf4j.LoggerFactory;
77  
78  /**
79   * An object exposing several methods useful for templates. It is exposed in templates as <code>cmsfn</code>.
80   */
81  public class TemplatingFunctions {
82  
83      private static final Logger log = LoggerFactory.getLogger(TemplatingFunctions.class);
84  
85      private final Provider<AggregationState> aggregationStateProvider;
86  
87      //TODO: To review with Philipp. Should not use Provider, but has deep impact on CategorizationSupportImpl PageSyndicator CategorySyndicator....
88      @Inject
89      public TemplatingFunctions(Provider<AggregationState> aggregationStateProvider) {
90          this.aggregationStateProvider = aggregationStateProvider;
91      }
92  
93  
94      public Node asJCRNode(ContentMap contentMap) {
95          return contentMap == null ? null : contentMap.getJCRNode();
96      }
97  
98      public ContentMap asContentMap(Node content) {
99          return content == null ? null : new ContentMap(content);
100     }
101 
102     public List<Node> children(Node content) throws RepositoryException {
103         return content == null ? null : asNodeList(NodeUtil.getNodes(content, NodeUtil.EXCLUDE_META_DATA_FILTER));
104     }
105 
106     public List<Node> children(Node content, String nodeTypeName) throws RepositoryException {
107         return content == null ? null : asNodeList(NodeUtil.getNodes(content, nodeTypeName));
108     }
109 
110     public List<ContentMap> children(ContentMap content) throws RepositoryException {
111         return content == null ? null : asContentMapList(NodeUtil.getNodes(asJCRNode(content), NodeUtil.EXCLUDE_META_DATA_FILTER));
112     }
113 
114     public List<ContentMap> children(ContentMap content, String nodeTypeName) throws RepositoryException {
115         return content == null ? null : asContentMapList(NodeUtil.getNodes(asJCRNode(content), nodeTypeName));
116     }
117 
118     public ContentMap root(ContentMap contentMap) throws RepositoryException {
119         return contentMap == null ? null : asContentMap(this.root(contentMap.getJCRNode()));
120     }
121 
122     public ContentMap root(ContentMap contentMap, String nodeTypeName) throws RepositoryException {
123         return contentMap == null ? null : asContentMap(this.root(contentMap.getJCRNode(), nodeTypeName));
124     }
125 
126     public Node root(Node content) throws RepositoryException {
127         return this.root(content, null);
128     }
129 
130     public Node root(Node content, String nodeTypeName) throws RepositoryException {
131         if (content == null) {
132             return null;
133         }
134         if (nodeTypeName == null) {
135             return (Node) content.getAncestor(0);
136         }
137         if (isRoot(content) && content.isNodeType(nodeTypeName)) {
138             return content;
139         }
140 
141         Node parentNode = this.parent(content, nodeTypeName);
142         while (parent(parentNode, nodeTypeName) != null) {
143             parentNode = this.parent(parentNode, nodeTypeName);
144         }
145         return parentNode;
146     }
147 
148     public ContentMap parent(ContentMap contentMap) throws RepositoryException {
149         return contentMap == null ? null : asContentMap(this.parent(contentMap.getJCRNode()));
150     }
151 
152     public ContentMap parent(ContentMap contentMap, String nodeTypeName) throws RepositoryException {
153         return contentMap == null ? null : asContentMap(this.parent(contentMap.getJCRNode(), nodeTypeName));
154     }
155 
156     public Node parent(Node content) throws RepositoryException {
157         return this.parent(content, null);
158     }
159 
160     public Node parent(Node content, String nodeTypeName) throws RepositoryException {
161         if (content == null) {
162             return null;
163         }
164         if (isRoot(content)) {
165             return null;
166         }
167         if (nodeTypeName == null) {
168             return content.getParent();
169         }
170         Node parent = content.getParent();
171         while (!parent.isNodeType(nodeTypeName)) {
172             if (isRoot(parent)) {
173                 return null;
174             }
175             parent = parent.getParent();
176         }
177         return parent;
178     }
179 
180     /**
181      * 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.
182      * If the passed {@link ContentMap} has no parent page at all, null is returned.
183      *
184      * @param content the {@link ContentMap} to get the page's {@link ContentMap} from.
185      * @return returns the page {@link ContentMap} of the passed content {@link ContentMap}.
186      */
187     public ContentMap page(ContentMap content) throws RepositoryException {
188         return content == null ? null : asContentMap(page(content.getJCRNode()));
189     }
190 
191     /**
192      * Returns the page {@link Node} of the passed node. If the passed {@link Node} is a page, the passed {@link Node} will be returned.
193      * If the passed Node has no parent page at all, null is returned.
194      *
195      * @param content the {@link Node} to get the page from.
196      * @return returns the page {@link Node} of the passed content {@link Node}.
197      */
198     public Node page(Node content) throws RepositoryException {
199         if (content == null) {
200             return null;
201         }
202         if (content.isNodeType(NodeTypes.Page.NAME)) {
203             return content;
204         }
205         return parent(content, NodeTypes.Page.NAME);
206     }
207 
208     public List<ContentMap> ancestors(ContentMap contentMap) throws RepositoryException {
209         return ancestors(contentMap, null);
210     }
211 
212     public List<ContentMap> ancestors(ContentMap contentMap, String nodeTypeName) throws RepositoryException {
213         List<Node> ancestorsAsNodes = this.ancestors(contentMap.getJCRNode(), nodeTypeName);
214         return asContentMapList(ancestorsAsNodes);
215     }
216 
217     public List<Node> ancestors(Node content) throws RepositoryException {
218         return content == null ? null : this.ancestors(content, null);
219     }
220 
221     public List<Node> ancestors(Node content, String nodeTypeName) throws RepositoryException {
222         if (content == null) {
223             return null;
224         }
225         List<Node> ancestors = new ArrayList<Node>();
226         int depth = content.getDepth();
227         for (int i = 1; i < depth; ++i) {
228             Node possibleAncestor = (Node) content.getAncestor(i);
229             if (nodeTypeName == null) {
230                 ancestors.add(possibleAncestor);
231             } else {
232                 if (possibleAncestor.isNodeType(nodeTypeName)) {
233                     ancestors.add(possibleAncestor);
234                 }
235             }
236         }
237         return ancestors;
238     }
239 
240     public Node inherit(Node content) throws RepositoryException {
241         return inherit(content, null);
242     }
243 
244     public Node inherit(Node content, String relPath) throws RepositoryException {
245         return inherit(content, relPath, StringUtils.EMPTY, ConfiguredInheritance.COMPONENTS_FILTERED, ConfiguredInheritance.PROPERTIES_ALL);
246     }
247 
248     public Node inherit(Node content, String relPath, String nodeTypes, String nodeInheritance, String propertyInheritance) throws RepositoryException {
249         if (content == null) {
250             return null;
251         }
252 
253         ConfiguredInheritance configuredInheritance = new ConfiguredInheritance();
254         if (StringUtils.isNotEmpty(nodeTypes)) {
255             configuredInheritance.setNodeTypes(Arrays.asList(StringUtils.split(nodeTypes, ",")));
256         }
257         if (StringUtils.isNotEmpty(nodeInheritance)) {
258             configuredInheritance.setComponents(nodeInheritance);
259         }
260         if (StringUtils.isNotEmpty(propertyInheritance)) {
261             configuredInheritance.setProperties(propertyInheritance);
262         }
263 
264         Node inheritedNode = wrapForInheritance(content, configuredInheritance);
265 
266         if (StringUtils.isBlank(relPath)) {
267             return inheritedNode;
268         }
269 
270         try {
271             Node subNode = inheritedNode.getNode(relPath);
272             return NodeUtil.unwrap(subNode);
273         } catch (PathNotFoundException e) {
274             // TODO fgrilli: rethrow exception?
275         }
276         return null;
277     }
278 
279     public ContentMap inherit(ContentMap content) throws RepositoryException {
280         return inherit(content, null);
281     }
282 
283     public ContentMap inherit(ContentMap content, String relPath) throws RepositoryException {
284         if (content == null) {
285             return null;
286         }
287         Node node = inherit(content.getJCRNode(), relPath);
288         return node == null ? null : new ContentMap(node);
289     }
290 
291     public ContentMap inherit(ContentMap content, String relPath, String nodeTypes, String nodeInheritance, String propertyInheritance) throws RepositoryException {
292         if (content == null) {
293             return null;
294         }
295         Node node = inherit(content.getJCRNode(), relPath, nodeTypes, nodeInheritance, propertyInheritance);
296         return node == null ? null : new ContentMap(node);
297     }
298 
299     public Property inheritProperty(Node content, String relPath) throws RepositoryException {
300         if (content == null) {
301             return null;
302         }
303         if (StringUtils.isBlank(relPath)) {
304             throw new IllegalArgumentException("relative path cannot be null or empty");
305         }
306         try {
307             Node inheritedNode = wrapForInheritance(content, new ConfiguredInheritance());
308             return inheritedNode.getProperty(relPath);
309 
310         } catch (PathNotFoundException e) {
311             // TODO fgrilli: rethrow exception?
312         } catch (RepositoryException e) {
313             // TODO fgrilli:rethrow exception?
314         }
315 
316         return null;
317     }
318 
319     public Property inheritProperty(ContentMap content, String relPath) throws RepositoryException {
320         if (content == null) {
321             return null;
322         }
323         return inheritProperty(content.getJCRNode(), relPath);
324     }
325 
326     public List<Node> inheritList(Node content, String relPath) throws RepositoryException {
327         if (content == null) {
328             return null;
329         }
330         if (StringUtils.isBlank(relPath)) {
331             throw new IllegalArgumentException("relative path cannot be null or empty");
332         }
333         Node inheritedNode = wrapForInheritance(content, new ConfiguredInheritance());
334         Node subNode = inheritedNode.getNode(relPath);
335         return children(subNode);
336     }
337 
338     public List<ContentMap> inheritList(ContentMap content, String relPath) throws RepositoryException {
339         if (content == null) {
340             return null;
341         }
342         if (StringUtils.isBlank(relPath)) {
343             throw new IllegalArgumentException("relative path cannot be null or empty");
344         }
345         Node node = asJCRNode(content);
346         Node inheritedNode = wrapForInheritance(node, new ConfiguredInheritance());
347         Node subNode = inheritedNode.getNode(relPath);
348         return children(new ContentMap(subNode));
349     }
350 
351     public boolean isInherited(Node content) {
352         if (content instanceof InheritanceNodeWrapper) {
353             return ((InheritanceNodeWrapper) content).isInherited();
354         }
355         return false;
356     }
357 
358     public boolean isInherited(ContentMap content) {
359         return isInherited(asJCRNode(content));
360     }
361 
362     public boolean isFromCurrentPage(Node content) {
363         return !isInherited(content);
364     }
365 
366     public boolean isFromCurrentPage(ContentMap content) {
367         return isFromCurrentPage(asJCRNode(content));
368     }
369 
370     /**
371      * Create link for the Node identified by nodeIdentifier in the specified workspace.
372      */
373     public String link(String workspace, String nodeIdentifier) {
374         try {
375             return LinkUtil.createLink(workspace, nodeIdentifier);
376         } catch (RepositoryException e) {
377             return null;
378         }
379     }
380 
381     /**
382      * There should be no real reason to use this method except to produce link to binary content stored in jcr:data property in which case one should call {@link #link(Node)} while passing parent node as a parameter. In case you find other valid reason to use this method, please raise it in a forum discussion or create issue. Otherwise this method will be removed in the future.
383      *
384      * @deprecated since 4.5.4. There is no valid use case for this method.
385      */
386     @Deprecated
387     public String link(Property property) {
388         try {
389             Node parentNode = null;
390             String propertyName = null;
391             if (property.getType() == PropertyType.BINARY) {
392                 parentNode = property.getParent().getParent();
393                 propertyName = property.getParent().getName();
394             } else {
395                 parentNode = property.getParent();
396                 propertyName = property.getName();
397             }
398             NodeData equivNodeData = ContentUtil.asContent(parentNode).getNodeData(propertyName);
399             return LinkUtil.createLink(equivNodeData);
400         } catch (Exception e) {
401             return null;
402         }
403     }
404 
405     public String link(Node content) {
406         return content == null ? null : LinkUtil.createLink(content);
407     }
408 
409     public String link(ContentMap contentMap) throws RepositoryException {
410         return contentMap == null ? null : this.link(asJCRNode(contentMap));
411     }
412 
413     /**
414      * Returns the querystring and fragment of a url, or an empty string if there are none.
415      */
416     public String getQueryStringAndFragment(String url){
417         String result = "";
418         try {
419             URI uri = new URI(url);
420             String query = uri.getQuery();
421             String fragment = uri.getFragment();
422 
423             if (StringUtils.isNotEmpty(query)){
424                 result += "?" + query;
425             }
426             if (StringUtils.isNotEmpty(fragment)){
427                 result += "#" + fragment;
428             }
429         } catch (URISyntaxException e) {
430             log.warn("URL cannot be parsed. {0}, {1}", url, e.getMessage());
431             return StringUtils.EMPTY;
432         }
433         return result;
434     }
435 
436     /**
437      * Get the language used currently.
438      * @return The language as a String.
439      */
440     public String language(){
441         return I18nContentSupportFactory.getI18nSupport().getLocale().toString();
442     }
443     /**
444      * Returns an external link prepended with <code>http://</code> in case the protocol is missing or an empty String
445      * if the link does not exist.
446      *
447      * @param content The node where the link property is stored on.
448      * @param linkPropertyName The property where the link value is stored in.
449      * @return The link prepended with <code>http://</code>;
450      */
451     public String externalLink(Node content, String linkPropertyName) {
452         String externalLink = PropertyUtil.getString(content, linkPropertyName);
453         if (StringUtils.isBlank(externalLink)) {
454             return StringUtils.EMPTY;
455         }
456         if (!hasProtocol(externalLink)) {
457             externalLink = "http://" + externalLink;
458         }
459         return externalLink;
460     }
461 
462     /**
463      * Returns an external link prepended with <code>http://</code> in case the protocol is missing or an empty String
464      * if the link does not exist.
465      *
466      * @param content The node's map representation where the link property is stored on.
467      * @param linkPropertyName The property where the link value is stored in.
468      * @return The link prepended with <code>http://</code>;
469      */
470     public String externalLink(ContentMap content, String linkPropertyName) {
471         return externalLink(asJCRNode(content), linkPropertyName);
472     }
473 
474     /**
475      * Return a link title based on the @param linkTitlePropertyName. When property @param linkTitlePropertyName is
476      * empty or null, the link itself is provided as the linkTitle (prepended with <code>http://</code>).
477      *
478      * @param content The node where the link property is stored on.
479      * @param linkPropertyName The property where the link value is stored in.
480      * @param linkTitlePropertyName The property where the link title value is stored
481      * @return the resolved link title value
482      */
483     public String externalLinkTitle(Node content, String linkPropertyName, String linkTitlePropertyName) {
484         String linkTitle = PropertyUtil.getString(content, linkTitlePropertyName);
485         if (StringUtils.isNotEmpty(linkTitle)) {
486             return linkTitle;
487         }
488         return externalLink(content, linkPropertyName);
489     }
490 
491     /**
492      * Return a link title based on the @param linkTitlePropertyName. When property @param linkTitlePropertyName is
493      * empty or null, the link itself is provided as the linkTitle (prepended with <code>http://</code>).
494      *
495      * @param content The node where the link property is stored on.
496      * @param linkPropertyName The property where the link value is stored in.
497      * @param linkTitlePropertyName The property where the link title value is stored
498      * @return the resolved link title value
499      */
500     public String externalLinkTitle(ContentMap content, String linkPropertyName, String linkTitlePropertyName) {
501         return externalLinkTitle(asJCRNode(content), linkPropertyName, linkTitlePropertyName);
502     }
503 
504     public boolean isEditMode() {
505         // TODO : see CmsFunctions.isEditMode, which checks a couple of other properties.
506         return isAuthorInstance() && !isPreviewMode();
507     }
508 
509     public boolean isPreviewMode() {
510         return this.aggregationStateProvider.get().isPreviewMode();
511     }
512 
513     public boolean isAuthorInstance() {
514         return Components.getComponent(ServerConfiguration.class).isAdmin();
515     }
516 
517     public boolean isPublicInstance() {
518         return !isAuthorInstance();
519     }
520 
521     /**
522      * Util method to create html attributes <code>name="value"</code>. If the value is empty an empty string will be returned.
523      * This is mainly helpful to avoid empty attributes.
524      */
525     public String createHtmlAttribute(String name, String value) {
526         value = StringUtils.trim(value);
527         if (StringUtils.isNotEmpty(value)) {
528             return new StringBuffer().append(name).append("=\"").append(value).append("\"").toString();
529         }
530         return StringUtils.EMPTY;
531     }
532 
533     /**
534      * Returns an instance of SiblingsHelper for the given node.
535      */
536     public SiblingsHelper siblings(Node node) throws RepositoryException {
537         return SiblingsHelper.of(ContentUtil.asContent(node));
538     }
539 
540     public SiblingsHelper siblings(ContentMap node) throws RepositoryException {
541         return siblings(asJCRNode(node));
542     }
543 
544     /**
545      * Return the Node for the given path from the website repository.
546      *
547      * @deprecated since 5.3.3 use {@link #contentByPath(String)} or {@link #nodeByPath(String)} instead
548      */
549     public Node content(String path){
550         return content(RepositoryConstants.WEBSITE, path);
551     }
552 
553     /**
554      * Return the Node for the given path from the given repository.
555      * @deprecated since 5.3.3 use {@link #contentByPath(String, String)} or {@link #nodeByPath(String, String)} instead
556      */
557     public Node content(String repository, String path){
558         return SessionUtil.getNode(repository, path);
559     }
560 
561     /**
562      * Return the Node by the given identifier from the website repository.
563      *
564      * @deprecated since 5.3.3 use {@link #contentById(String)} or {@link #nodeById(String) instead
565      */
566     public Node contentByIdentifier(String id){
567         return contentByIdentifier(RepositoryConstants.WEBSITE, id);
568     }
569 
570     /**
571      * Return the Node by the given identifier from the given repository.
572      *
573      * @deprecated since 5.3.3 use {@link #contentById(String, String)} or {@link #contentByPath(String, String )} instead
574      */
575     public Node contentByIdentifier(String repository, String id) {
576         return SessionUtil.getNodeByIdentifier(repository, id);
577     }
578 
579     /**
580      * Return the ContentMap for the given path from the website repository.
581      */
582     public ContentMap contentByPath(String path) {
583         return contentByPath(path, RepositoryConstants.WEBSITE);
584     }
585 
586     /**
587      * Return the ContentMap for the given path from the given repository.
588      */
589     public ContentMap contentByPath(String path, String workspace) {
590         return asContentMap(nodeByPath(path, workspace));
591     }
592 
593     /**
594      * Return the ContentMap by the given identifier from the website repository.
595      */
596     public ContentMap contentById(String id) {
597         return contentById(id, RepositoryConstants.WEBSITE);
598     }
599 
600     /**
601      * Return the ContentMap by the given identifier from the given repository.
602      */
603     public ContentMap contentById(String id, String workspace) {
604         return asContentMap(SessionUtil.getNodeByIdentifier(workspace, id));
605     }
606 
607     /**
608      * Return the Node for the given path from the website repository.
609      */
610     public Node nodeByPath(String path) {
611         return nodeByPath(path, RepositoryConstants.WEBSITE);
612     }
613 
614     /**
615      * Return the Node for the given path from the given repository.
616      */
617     public Node nodeByPath(String path, String workspace) {
618         try {
619             String pathToNode = new URI(path).getPath();
620             return SessionUtil.getNode(workspace, pathToNode);
621         } catch (URISyntaxException e) {
622             log.warn("Path cannot be parsed. {0}, {1}", path, e.getMessage());
623             return null;
624         }
625     }
626 
627     /**
628      * Return the Node by the given identifier from the website repository.
629      */
630     public Node nodeById(String id) {
631         return nodeById(id, RepositoryConstants.WEBSITE);
632     }
633 
634     /**
635      * Return the Node by the given identifier from the given repository.
636      */
637     public Node nodeById(String id, String workspace) {
638         return SessionUtil.getNodeByIdentifier(workspace, id);
639     }
640 
641     public List<ContentMap> asContentMapList(Collection<Node> nodeList) {
642         if (nodeList != null) {
643             List<ContentMap> contentMapList = new ArrayList<ContentMap>();
644             for (Node node : nodeList) {
645                 contentMapList.add(asContentMap(node));
646             }
647             return contentMapList;
648         }
649         return null;
650     }
651 
652     public List<Node> asNodeList(Collection<ContentMap> contentMapList) {
653         if (contentMapList != null) {
654             List<Node> nodeList = new ArrayList<Node>();
655             for (ContentMap node : contentMapList) {
656                 nodeList.add(node.getJCRNode());
657             }
658             return nodeList;
659         }
660         return null;
661     }
662 
663     // TODO fgrilli: should we unwrap children?
664     protected List<Node> asNodeList(Iterable<Node> nodes) {
665         List<Node> childList = new ArrayList<Node>();
666         for (Node child : nodes) {
667             childList.add(child);
668         }
669         return childList;
670     }
671 
672     // TODO fgrilli: should we unwrap children?
673     protected List<ContentMap> asContentMapList(Iterable<Node> nodes) {
674         List<ContentMap> childList = new ArrayList<ContentMap>();
675         for (Node child : nodes) {
676             childList.add(new ContentMap(child));
677         }
678         return childList;
679     }
680 
681     /**
682      * Checks if passed string has a <code>http://</code> protocol.
683      *
684      * @param link The link to check
685      * @return If @param link contains a <code>http://</code> protocol
686      */
687     private boolean hasProtocol(String link) {
688         return link != null && link.contains("://");
689     }
690 
691     /**
692      * Checks if the passed {@link Node} is the jcr root '/' of the workspace.
693      * @param content {@link Node} to check if its root.
694      * @return if @param content is the jcr workspace root.
695      */
696     private boolean isRoot(Node content) throws RepositoryException {
697         return content.getDepth() == 0;
698     }
699 
700     /**
701      * Removes escaping of HTML on properties.
702      */
703     public ContentMap decode(ContentMap content){
704         return asContentMap(decode(content.getJCRNode()));
705     }
706 
707     /**
708      * Removes escaping of HTML on properties.
709      */
710     public Node decode(Node content) {
711         return NodeUtil.deepUnwrap(content, HTMLEscapingNodeWrapper.class);
712     }
713 
714     /**
715      * Adds escaping of HTML on properties as well as changing line breaks into &lt;br/&gt; tags.
716      */
717     public ContentMap encode(ContentMap content) {
718         return content != null ? new ContentMap(new HTMLEscapingNodeWrapper(content.getJCRNode(), true)) : null;
719     }
720 
721     /**
722      * Adds escaping of HTML on properties as well as changing line breaks into &lt;br/&gt; tags.
723      */
724     public Node encode(Node content) {
725         return content != null ? new HTMLEscapingNodeWrapper(content, true) : null;
726     }
727 
728     private Node wrapForInheritance(Node destination, ConfiguredInheritance configuredInheritance) throws RepositoryException {
729         configuredInheritance.setEnabled(true);
730         return new DefaultInheritanceContentDecorator(destination, configuredInheritance).wrapNode(destination);
731     }
732 
733     /**
734      * 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.
735      */
736     public String metaData(Node content, String property){
737 
738         Object returnValue;
739         try {
740             if (property.equals(NodeTypes.Created.CREATED)) {
741                 returnValue = NodeTypes.Created.getCreated(content);
742             } else if (property.equals(NodeTypes.Created.CREATED_BY)) {
743                 returnValue = NodeTypes.Created.getCreatedBy(content);
744             } else if (property.equals(NodeTypes.LastModified.LAST_MODIFIED)) {
745                 returnValue = NodeTypes.LastModified.getLastModified(content);
746             } else if (property.equals(NodeTypes.LastModified.LAST_MODIFIED_BY)) {
747                 returnValue = NodeTypes.LastModified.getLastModifiedBy(content);
748             } else if (property.equals(NodeTypes.Renderable.TEMPLATE)) {
749                 returnValue = NodeTypes.Renderable.getTemplate(content);
750             } else if (property.equals(NodeTypes.Activatable.LAST_ACTIVATED)) {
751                 returnValue = NodeTypes.Activatable.getLastActivated(content);
752             } else if (property.equals(NodeTypes.Activatable.LAST_ACTIVATED_BY)) {
753                 returnValue = NodeTypes.Activatable.getLastActivatedBy(content);
754             } else if (property.equals(NodeTypes.Activatable.ACTIVATION_STATUS)) {
755                 returnValue = NodeTypes.Activatable.getActivationStatus(content);
756             } else if (property.equals(NodeTypes.Deleted.DELETED)) {
757                 returnValue = NodeTypes.Deleted.getDeleted(content);
758             } else if (property.equals(NodeTypes.Deleted.DELETED_BY)) {
759                 returnValue = NodeTypes.Deleted.getDeletedBy(content);
760             } else if (property.equals(NodeTypes.Deleted.COMMENT)) {
761                 // Since NodeTypes.Deleted.COMMENT and NodeTypes.Versionable.COMMENT have identical names this will work for both
762                 returnValue = NodeTypes.Deleted.getComment(content);
763             } else {
764 
765                 // Try to get the value using one of the deprecated names in MetaData.
766                 // This throws an IllegalArgumentException if its not one of those constants
767                 returnValue = MetaDataUtil.getMetaData(content).getStringProperty(property);
768 
769                 // If no exception was thrown then warn that a legacy constant was used
770                 log.warn("Deprecated constant [{}] used to query for meta data property on node [{}]", property, NodeUtil.getPathIfPossible(content));
771             }
772         } catch (RepositoryException e) {
773             return "";
774         }
775 
776         return returnValue instanceof Calendar ? ISO8601.format((Calendar) returnValue) : returnValue.toString();
777     }
778 
779     /**
780      * @see {@link TemplatingFunctions#metaData(Node, String)}.
781      */
782     public String metaData(ContentMap content, String property){
783         return metaData(content.getJCRNode(), property);
784     }
785 
786     /**
787      * Executes query and returns result as Collection of Nodes.
788      *
789      * @param statement has to be in formal form for chosen language
790      */
791     public Collection<Node> search(String workspace, String statement, String language, String returnItemType){
792         try {
793             return NodeUtil.getCollectionFromNodeIterator(QueryUtil.search(workspace, statement, language, returnItemType));
794         } catch (Exception e) {
795             log.error(e.getMessage(), e);
796         }
797         return null;
798     }
799 
800     /**
801      * Executes simple SQL2 query and returns result as Collection of Nodes.
802      *
803      * @param statement should be set of labels target has to contain inserted as one string each separated by comma
804      * @param startPath can be inserted, for results without limitation set it to slash
805      */
806     public Collection<Node> simpleSearch(String workspace, String statement, String returnItemType, String startPath){
807         if(StringUtils.isEmpty(statement)){
808             log.error("Cannot search with empty statement.");
809             return null;
810         }
811         String query = QueryUtil.buildQuery(statement, startPath);
812         try {
813             return NodeUtil.getCollectionFromNodeIterator(QueryUtil.search(workspace, query, "JCR-SQL2", returnItemType));
814         } catch (Exception e) {
815             log.error(e.getMessage(), e);
816         }
817         return null;
818     }
819 }