View Javadoc

1   /**
2    * This file Copyright (c) 2008-2012 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.module.templatingkit.functions;
35  
36  import info.magnolia.context.MgnlContext;
37  import info.magnolia.dam.Dam;
38  import info.magnolia.dam.asset.Asset;
39  import info.magnolia.dam.asset.DamException;
40  import info.magnolia.dam.asset.DamAsset;
41  import info.magnolia.dam.asset.variation.AssetVariationProvider;
42  import info.magnolia.dam.asset.variation.NoSuchVariationException;
43  import info.magnolia.jcr.RuntimeRepositoryException;
44  import info.magnolia.jcr.util.ContentMap;
45  import info.magnolia.jcr.util.NodeTypes;
46  import info.magnolia.jcr.util.NodeUtil;
47  import info.magnolia.jcr.util.PropertyUtil;
48  import info.magnolia.jcr.wrapper.I18nNodeWrapper;
49  import info.magnolia.module.templatingkit.STKModule;
50  import info.magnolia.module.templatingkit.categorization.CategorizationSupport;
51  import info.magnolia.module.templatingkit.dam.DAMSupport;
52  import info.magnolia.module.templatingkit.sites.Site;
53  import info.magnolia.module.templatingkit.sites.SiteManager;
54  import info.magnolia.module.templatingkit.style.CssSelectorBuilder;
55  import info.magnolia.module.templatingkit.style.Theme;
56  import info.magnolia.module.templatingkit.style.ThemeImpl;
57  import info.magnolia.module.templatingkit.templates.AbstractSTKTemplateModel;
58  import info.magnolia.module.templatingkit.templates.category.TemplateCategory;
59  import info.magnolia.module.templatingkit.templates.category.TemplateCategoryUtil;
60  import info.magnolia.rendering.template.TemplateDefinition;
61  import info.magnolia.repository.RepositoryConstants;
62  import info.magnolia.templating.functions.TemplatingFunctions;
63  
64  import java.util.ArrayList;
65  import java.util.List;
66  import java.util.Set;
67  
68  import javax.inject.Inject;
69  import javax.inject.Provider;
70  import javax.inject.Singleton;
71  import javax.jcr.Node;
72  import javax.jcr.RepositoryException;
73  
74  import org.apache.commons.lang.StringUtils;
75  import org.apache.commons.lang.mutable.MutableInt;
76  import org.slf4j.Logger;
77  import org.slf4j.LoggerFactory;
78  
79  /**
80   * This is an component exposing a couple of methods useful for STK templates.
81   * It's exposed in templates as "stkfn".
82   */
83  @Singleton
84  public class STKTemplatingFunctions {
85  
86      private static Logger log = LoggerFactory.getLogger(STKTemplatingFunctions.class);
87  
88      public static final String SITEROOT_TEMPLATE_CATEGORY = TemplateCategory.HOME;
89  
90      protected static final String TITLE_PROP_NAME = "title";
91      protected static final String SITE_TITLE_PROP_NAME = "siteTitle";
92  
93      // Injected variables
94      private CssSelectorBuilder cssSelectorBuilder;
95      private CategorizationSupport categorizationSupport;
96      private final SiteManager siteManager;
97      private final TemplatingFunctions templatingFunctions;
98      private final Provider<STKModule> moduleProvider;
99  
100     private final Dam dam;
101     private final AssetVariationProvider variationProvider;
102     @Inject
103     public STKTemplatingFunctions(TemplatingFunctions templatingFunctions, CssSelectorBuilder cssSelectorBuilder,
104             CategorizationSupport categorizationSupport, SiteManager siteManager, Provider<STKModule> moduleProvider,
105             Dam dam, AssetVariationProvider variationProvider) {
106         this.templatingFunctions = templatingFunctions;
107         this.cssSelectorBuilder = cssSelectorBuilder;
108         this.categorizationSupport = categorizationSupport;
109         this.siteManager = siteManager;
110         this.moduleProvider = moduleProvider;
111 
112         this.dam = dam;
113         this.variationProvider = variationProvider;
114     }
115 
116     /**
117      * Method used to get the SearchPageLink. This Search page Link is defined
118      * on the Main PageInfo/Site configuration (Search Page Path to search
119      * result page). Method is used from a STKComponent and STKArea Model.
120      *
121      * @param content
122      *            : Current Content node.
123      * @return relative path to the defined result page i.e.
124      *         '/magnolia-empty-webapp/demo-project/service/search-result.html'
125      */
126     public String searchPageLink(Node content) {
127         String searchResultPageUUID = PropertyUtil.getString(siteRoot(content), "searchUUID");
128         return (StringUtils.isEmpty(searchResultPageUUID)) ? null : templatingFunctions.link(RepositoryConstants.WEBSITE,
129                 searchResultPageUUID);
130     }
131 
132     public String searchPageLink(ContentMap content) {
133         return searchPageLink(content.getJCRNode());
134     }
135 
136     /**
137      * Return the Home Site Name = the physical node's object name.
138      */
139     public String homeName(Node content) {
140         return NodeUtil.getName(siteRoot(content));
141     }
142 
143     public String homeName(ContentMap content) {
144         return homeName(content.getJCRNode());
145     }
146 
147     /**
148      * Return the HomeTitle value. Referred by the property 'name' of the root
149      * Node
150      */
151     public String homeTitle(Node content) {
152         return PropertyUtil.getString(siteRoot(content), TITLE_PROP_NAME);
153     }
154 
155     public String homeTitle(ContentMap content) {
156         return homeTitle(content.getJCRNode());
157     }
158 
159     /**
160      * Return the SiteTitle value. Referred by the property 'siteTitle' of the
161      * root Node
162      */
163     public String siteTitle(Node content) {
164         return PropertyUtil.getString(siteRoot(content), SITE_TITLE_PROP_NAME);
165     }
166 
167     public String siteTitle(ContentMap content) {
168         return siteTitle(content.getJCRNode());
169     }
170 
171     /**
172      * Return the home link of this site.
173      */
174     public String homeLink(Node content) {
175         return templatingFunctions.link(siteRoot(content));
176     }
177 
178     public String homeLink(ContentMap content) {
179         return homeLink(content.getJCRNode());
180     }
181 
182     public Site site() {
183         return siteManager.getCurrentSite();
184     }
185 
186     /**
187      * Returns the according {@link Site} of the passed content {@link Node}.
188      *
189      * @param content
190      *            The {@link Node} to determine its {@link Site} from
191      * @return {@link Site} The according {@link Site} of the passed @param
192      *         content
193      */
194     public Site site(Node content) {
195         try {
196             return (content == null) ? null : siteManager.getAssignedSite(content);
197         } catch (IllegalStateException e) {
198             log.error("Unable to access assigned site ({}) until following issue is resolved: {}", content.toString(), e.getMessage());
199             return null;
200         }
201     }
202 
203     // TODO dlipp/had: is that a good place to keep that code - you might have
204     // to access it from non-template related code. Maybe we shouldn't
205     // completely drop STKUtil but
206     // keep it for common stuff.
207     public Theme theme(Site site) {
208         final String theme = site.getTheme().getName();
209         return StringUtils.isBlank(theme) ? new ThemeImpl() : moduleProvider.get().getTheme(theme);
210 
211     }
212 
213     /**
214      * Returns the according {@link Site} of the passed content
215      * {@link ContentMap}.
216      *
217      * @param content
218      *            The {@link ContentMap} to determine its {@link Site} from
219      * @return {@link Site} The according {@link Site} of the passed @param
220      *         content
221      */
222     public Site site(ContentMap content) {
223         return site(content.getJCRNode());
224     }
225 
226     /**
227      * Returns the site's root {@link Node} of the @param content {@link Node}.
228      * The root {@link Node} is defined as the page {@link Node} having template
229      * category TemplateCategory.HOME. If no ancestor page exists with category
230      * TemplateCategory.HOME, the JCR workspace root is returned.
231      *
232      * @param content
233      *            The {@link Node} to determine its site root
234      * @return {@link Node} The site root {@link Node} of the passed content
235      *         {@link Node}
236      */
237     public Node siteRoot(Node content) {
238         return this.siteRoot(content, SITEROOT_TEMPLATE_CATEGORY);
239     }
240 
241     /**
242      * Returns the site's root {@link ContentMap} of the @param content
243      * {@link ContentMap}. The root {@link ContentMap} is defined as the page
244      * {@link Node} having template category TemplateCategory.HOME. If no
245      * ancestor page exists with category TemplateCategory.HOME, the JCR
246      * workspace root is returned.
247      *
248      * @param content
249      *            The {@link ContentMap} to determine its site root
250      * @return {@link ContentMap} The site root {@link ContentMap} of the passed @param
251      *         content
252      */
253     public ContentMap siteRoot(ContentMap content) {
254         return this.siteRoot(content, SITEROOT_TEMPLATE_CATEGORY);
255     }
256 
257     /**
258      * Returns the site's root {@link Node} of the passed @param content
259      * {@link Node}. The root {@link Node} is defined as the page {@link Node}
260      * with template its category as @param siteRootTemplateCategory. If no
261      * ancestor page exists with category TemplateCategory.HOME, the JCR
262      * workspace root is returned.
263      *
264      * @param content
265      *            The {@link Node} to determine its site root
266      * @param siteRootTemplateCategory
267      *            The {@link TemplateCategory} value of the site root to detect.
268      *            If null or EMPTY is passed, the default value
269      *            SITEROOT_TEMPLATE_CATEGORY is used instead.
270      * @return {@link Node} The site root {@link Node} of the passed content
271      *         {@link Node}
272      */
273     public ContentMap siteRoot(ContentMap content, String siteRootTemplateCategory) {
274         return templatingFunctions.asContentMap(siteRoot(content.getJCRNode(), siteRootTemplateCategory));
275     }
276 
277     /**
278      *
279      * Returns the site's root {@link Node} of the passed @param content
280      * {@link Node}. The root {@link Node} is defined as the page {@link Node}
281      * with template its category as @param siteRootTemplateCategory. If no
282      * ancestor page exists with category TemplateCategory.HOME, the JCR
283      * workspace root is returned.
284      *
285      * @param content
286      *            The {@link Node} to determine its site root
287      * @param siteRootTemplateCategory
288      *            The {@link TemplateCategory} value of the site root to detect.
289      *            If null or EMPTY is passed, the default value
290      *            SITEROOT_TEMPLATE_CATEGORY is used instead.
291      * @return {@link Node} The site root {@link Node} of the passed content
292      *         {@link Node}
293      */
294     public Node siteRoot(Node content, String siteRootTemplateCategory) {
295         if (StringUtils.isEmpty(siteRootTemplateCategory)) {
296             siteRootTemplateCategory = SITEROOT_TEMPLATE_CATEGORY;
297         }
298         try {
299             Node page = this.templatingFunctions.page(content);
300             Node root = TemplateCategoryUtil.findParentWithTemplateCategory(page, siteRootTemplateCategory);
301             return (root == null) ? (Node) page.getAncestor(0) : root;
302         } catch (RepositoryException e) {
303             throw new RuntimeException("Can't access site root.", e);
304         }
305     }
306 
307     public List<ContentMap> ancestorsInSite(ContentMap content) throws RepositoryException {
308         return ancestorsInSite(content, null);
309     }
310 
311     public List<Node> ancestorsInSite(Node content) throws RepositoryException {
312         return ancestorsInSite(content, null);
313     }
314 
315     public List<ContentMap> ancestorsInSite(ContentMap content, String nodeTypeName) throws RepositoryException {
316         return templatingFunctions.asContentMapList(ancestorsInSite(content.getJCRNode(), nodeTypeName));
317     }
318 
319     public List<Node> ancestorsInSite(Node content, String nodeTypeName) throws RepositoryException {
320         List<Node> allAncestors = templatingFunctions.ancestors(content, nodeTypeName);
321         Node siteRoot = siteRoot(content);
322 
323         List<Node> ancestoresInSite = new ArrayList<Node>();
324         for (Node current : allAncestors) {
325             if (current.getDepth() >= siteRoot.getDepth()) {
326                 ancestoresInSite.add(current);
327             }
328         }
329         return ancestoresInSite;
330     }
331 
332     /**
333      * FIXME review: should this function be provided by this class?
334      */
335     public String getAssetLink(Node content, String nodeDataPrefix) {
336         return getAssetLink(content, nodeDataPrefix, DAMSupport.VARIATION_ORIGINAL);
337     }
338 
339     /**
340      * FIXME review: should this function be provided by this class?
341      */
342     public String getAssetLink(ContentMap content, String nodeDataPrefix) {
343         return getAssetLink(content.getJCRNode(), nodeDataPrefix);
344     }
345 
346     public String getAssetLink(ContentMap content, String nodeDataPrefix, AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
347         return getAssetLink(content.getJCRNode(), nodeDataPrefix, resolveImageVariationName(model));
348     }
349 
350     public String getAssetLink(ContentMap content, String nodeDataPrefix, String variationName) {
351         return getAssetLink(content.getJCRNode(), nodeDataPrefix, variationName);
352     }
353 
354     public String getAssetLink(Node content, String nodeDataPrefix, String variationName) {
355         final Asset asset = getAsset(content, nodeDataPrefix, variationName);
356         if (asset != null) {
357             try {
358                 return asset.getLink();
359             } catch (DamException e) {
360                 log.warn("Failed to get asset link with exception: " + e.getMessage(), e);
361                 return null;
362             }
363         }
364         return null;
365     }
366 
367     public String resolveImageVariationName(AbstractSTKTemplateModel<? extends TemplateDefinition> model) {
368         final String cssSelector = cssSelectorBuilder.createCssSelector(model);
369         try {
370             return theme(site(model.getNode())).getImaging().resolveVariationName(cssSelector);
371         } catch (NoSuchVariationException e) {
372             log.warn(e.getMessage());
373             return DAMSupport.VARIATION_ORIGINAL;
374         }
375     }
376 
377     public Asset getAssetVariation(Asset original, String variationName) {
378         if (original instanceof DamAsset) {
379             return variationProvider.createVariation(original, variationName);
380         }
381 
382         if (!variationName.equals(DAMSupport.VARIATION_ORIGINAL)) {
383             try {
384                 return original.getVariation(variationName);
385             } catch (NoSuchVariationException e) {
386                 log.warn("Can't find the variation " + variationName + " for asset [" + original + "]. Will use the original instead.");
387             } catch (DamException e) {
388                 log.warn("Failed to get asset variation " + variationName + " with exception: " + e.getMessage(), e);
389                 return null;
390             }
391         }
392         return original;
393     }
394 
395     /**
396      * FIXME review: should this function be provided by this class? FIXME
397      * I18nContentWrapper --> I18nNodeWrapper
398      */
399     public Node wrap(Node content) {
400         return (content == null) ? null : templatingFunctions.encode(new I18nNodeWrapper(content));
401     }
402 
403     /**
404      * FIXME review: should this function be provided by this class?
405      */
406     public List<ContentMap> getCategories(Node page) {
407         return categorizationSupport.getCategories(page);
408     }
409 
410     /**
411      * FIXME review: should this function be provided by this class?
412      */
413     public String getCategoryLink(Node page, String categoryName) {
414         return categorizationSupport.getCategoryLink(page, categoryName);
415     }
416 
417     /**
418      * FIXME review: should this function be provided by this class?
419      */
420     public List<Node> getContentListByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory)
421             throws RepositoryException {
422         return getContentListByTemplateCategorySubCategory(siteRoot, category, subCategory, Integer.MAX_VALUE, null, null);
423     }
424 
425     /**
426      * FIXME review: should this function be provided by this class?
427      */
428     public static List<Node> getContentListByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory,
429             int maxResultSize, String andClause, String orderBy) throws RepositoryException {
430         return TemplateCategoryUtil.getContentListByTemplateCategorySubCategory(siteRoot, category, subCategory, maxResultSize, andClause,
431                 orderBy);
432     }
433 
434     /**
435      * FIXME review: should this function be provided by this class?
436      */
437     public Node getNearestContentByTemplateCategorySubCategory(Node siteRoot, String category, String subCategory, Node current)
438             throws RepositoryException {
439         return TemplateCategoryUtil.getNearestContentByTemplateCategorySubCategory(siteRoot, category, subCategory, current);
440     }
441 
442     /**
443      * FIXME review: should this function be provided by this class?
444      */
445     public static List<Node> getContentListByTemplateName(Node searchRoot, String templateName) throws RepositoryException {
446         return TemplateCategoryUtil.getContentListByTemplateName(searchRoot, templateName);
447     }
448 
449     /**
450      * FIXME review: should this function be provided by this class?
451      */
452     public static List<Node> getContentListByTemplateName(Node searchRoot, String templateName, int maxResultSize, String andClause,
453             String orderByClause) throws RepositoryException {
454         return TemplateCategoryUtil.getContentListByTemplateName(searchRoot, templateName, maxResultSize, andClause, orderByClause);
455     }
456 
457     /**
458      * FIXME review: should this function be provided by this class?
459      */
460     public static List<Node> getContentListByTemplateNames(Node searchRoot, Set<String> templateIds, int maxResultSize, String andClause,
461             String orderByClause) throws RepositoryException {
462         return TemplateCategoryUtil.getContentListByTemplateNames(searchRoot, templateIds, maxResultSize, andClause, orderByClause);
463     }
464 
465     /**
466      * Returns a {@link Node} object which is referenced by its id, stored in
467      * the @param propertyName.
468      *
469      * @param content
470      *            the node with a property containing the referenced id value.
471      * @param idPropertyName
472      *            The name of the property which contains the id of the
473      *            referenced {@link Node}.
474      * @param referencedWorkspace
475      *            The workspace in which the referenced {@link Node} exists.
476      * @return the referenced {@link Node}
477      */
478     public Node getReferencedContent(Node content, String idPropertyName, String referencedWorkspace) {
479         try {
480             if (content.hasProperty(idPropertyName)) {
481                 String identifier = PropertyUtil.getString(content, idPropertyName);
482                 return wrap(NodeUtil.getNodeByIdentifier(referencedWorkspace, identifier));
483             }
484         } catch (RepositoryException e) {
485             log.error("can't read value '" + idPropertyName + "' of the Node '" + content.toString() + "'.", e);
486             throw new RuntimeRepositoryException(e);
487         }
488         return null;
489     }
490 
491     public Asset getAsset(Node content, String nodeDataPrefix, String variationName) {
492         final Asset damAsset = dam != null ? dam.getAsset(content, nodeDataPrefix) : null;
493 
494         if (damAsset != null) {
495             ((DamAsset) damAsset).setVariationProvider(variationProvider);
496             try {
497                 return damAsset.getVariation(variationName);
498             } catch (NoSuchVariationException e) {
499                 log.warn("Failed to get variation.", e);
500             } catch (DamException e) {
501                 log.warn("Failed to get asset.", e);
502             }
503         }
504         return null;
505     }
506 
507     public Asset getAsset(Node content, String nodeDataPrefix) {
508         return getAsset(content, nodeDataPrefix, DAMSupport.VARIATION_ORIGINAL);
509     }
510 
511     public Asset getAsset(ContentMap content, String nodeDataPrefix) {
512         return getAsset(content.getJCRNode(), nodeDataPrefix);
513     }
514 
515     /**
516      * Return a Node List with a maximum number or result less or equal to
517      * maxResults.
518      */
519     public List<Node> cutList(List<Node> itemsList, final int maxResults) {
520         if (itemsList.size() > maxResults) {
521             return itemsList.subList(0, maxResults);
522         }
523         return itemsList;
524     }
525 
526     // TODO : use WordUtils.abbreviate and/or add tests
527     public String abbreviateString(String stringToCut, int size) {
528         return abbreviateString(stringToCut, size, " ...");
529     }
530 
531     public String abbreviateString(String stringToCut, int size, String closureString) {
532         if (stringToCut.length() > size) {
533             int sizeMinusClosue = size - closureString.length();
534             String cuttedString = StringUtils.left(stringToCut, sizeMinusClosue);
535 
536             // If the character after the cut was a space, no word was cutted ->
537             // no cutting on last space needed.
538             String firstCharAfterCut = stringToCut.substring(sizeMinusClosue, sizeMinusClosue + 1);
539             if (!firstCharAfterCut.equals(" ")) {
540                 cuttedString = StringUtils.substringBeforeLast(cuttedString, " ");
541             }
542 
543             return cuttedString + closureString;
544 
545         }
546         return stringToCut;
547     }
548 
549     /**
550      * Increases the context bound counter and returns the value. This is for
551      * instance useful to create unique page wide ids.
552      */
553     public int count(String name) {
554         String attributeName = STKTemplatingFunctions.class.getName() + name;
555         if (!MgnlContext.hasAttribute(attributeName)) {
556             MgnlContext.setAttribute(attributeName, new MutableInt(0));
557         }
558         MutableInt counter = (MutableInt) MgnlContext.getAttribute(attributeName);
559         counter.increment();
560         return counter.intValue();
561     }
562 
563     public String getDivIdAbbreviation(String divID) {
564         return getDivIdAbbreviation(divID, "-");
565     }
566 
567     public String getDivIdAbbreviation(String divID, String delimiter) {
568         String result = StringUtils.EMPTY;
569         if (StringUtils.isNotEmpty(divID) && StringUtils.isNotEmpty(delimiter)) {
570             String[] values = divID.split("\\" + delimiter);
571             for (String value : values) {
572                 result += value.substring(0, 1);
573             }
574         }
575         return result;
576     }
577 
578     /**
579      * Return a property from the metaData of the node.
580      */
581     public String metaDataProperty(Node content, String property) {
582         return templatingFunctions.metaData(content, property);
583     }
584 
585     public String metaDataProperty(ContentMap content, String property){
586         return templatingFunctions.metaData(content, property);
587     }
588 
589     /**
590      * Return the template id associated with this content.
591      */
592 
593     public String metaDataTemplate(Node content){
594         try {
595             return NodeTypes.Renderable.getTemplate(content);
596         } catch (RepositoryException e) {
597             return null;
598         }
599     }
600 
601     public String metaDataTemplate(ContentMap content){
602         return metaDataTemplate(content.getJCRNode());
603     }
604 
605     /**
606      * For test purpose only.
607      */
608     public void setCssSelectorBuilder(CssSelectorBuilder cssSelectorBuilder) {
609         this.cssSelectorBuilder = cssSelectorBuilder;
610     }
611 
612     /**
613      * For test purpose only.
614      */
615     public void setCategorizationSupport(CategorizationSupport categorizationSupport) {
616         this.categorizationSupport = categorizationSupport;
617     }
618 
619 }