View Javadoc
1   /**
2    * This file Copyright (c) 2016-2018 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.modules.resources.templating;
35  
36  import info.magnolia.module.resources.ResourceLinker;
37  import info.magnolia.resourceloader.Resource;
38  import info.magnolia.resourceloader.ResourceOrigin;
39  import info.magnolia.resourceloader.ResourceVisitor;
40  
41  import java.util.Arrays;
42  import java.util.LinkedHashMap;
43  import java.util.LinkedList;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.regex.PatternSyntaxException;
47  
48  import javax.inject.Inject;
49  
50  /**
51   * Templating functions for creating links to web resources such as css and js files by given patterns.
52   *
53   * <p>Provide a single pattern or a list of patterns to match files from all resources. Use methods {@link #css(String)},
54   * {@link #css(List)}, {@link #cachedCss(String)}, {@link #cachedCss(List)}, {@link #js(String)}, {@link #js(List)},
55   * {@link #cachedJs(String)} or {@link #cachedJs(List)} demanding on your needs.</p>
56   *
57   * <p>The methods create printable html tags such as the followings:<br/>
58   * <code>
59   * &lt;script src="/.resources/travel-demo-theme/js/html5shiv.js"&gt;&lt;/script&gt;<br/>
60   * &lt;link rel="stylesheet" type="text/css" href="/.resources/foobar/webresources/css/a.css"/&gt;<br/>
61   * &lt;link rel="stylesheet" type="text/css" media="all" href="/.resources/foobar/webresources/css/a~2016-12-08-02-56-06-000~cache.css"/&gt;
62   * </code></p>
63   *
64   * <p>Example calls:
65   * <pre>
66   * ${resfn.css("/travel-demo-theme.*css")} (single pattern)
67   * ${resfn.js("/travel-demo-theme.*js")} (single pattern)
68   * ${resfn.css(["/foobar.*css", ".*magnolia.*css"])} (pattern list)
69   * ${resfn.css(["/foobar.*css", ".*magnolia.*css"], "media='all'")} (pattern list and other properties argument with single quotes)
70   * ${resfn.css(["/foobar.*css", ".*magnolia.*css"], "media=\"all\"")} (pattern list and other properties argument with escaped double quotes)
71   * ${resfn.cachedJs(["/foobar/.*.js", "/travel-demo/.*.js"])} (call to cached files)
72   * </pre></p>
73   *
74   * <p>This templating functions class originates from the project <i>neat-resources</i> by rah003@github.com.</p>
75   *
76   * @see <a href="https://documentation.magnolia-cms.com/display/DOCS/resfn">https://documentation.magnolia-cms.com/display/DOCS/resfn</a>
77   */
78  public class ResourcesTemplatingFunctions {
79  
80      public static final String NAME = "resfn";
81  
82      protected static final String CSS_SUFFIX = "\"/>";
83      protected static final String JS_PREFIX = "<script src=\"";
84      protected static final String JS_SUFFIX = "\"></script>";
85  
86      private final ResourceOrigin origin;
87      private final ResourceLinker linker;
88  
89      @Inject
90      public ResourcesTemplatingFunctions(ResourceOrigin resourceOrigin, ResourceLinker linker) {
91          this.origin = resourceOrigin;
92          this.linker = linker;
93      }
94  
95      /**
96       * Generates css links for all css files matching the provided pattern.
97       */
98      public String css(String pattern) {
99          return css(Arrays.asList(pattern));
100     }
101 
102     /**
103      * Generates css links for all css files matching the provided pattern; css links will contain the provided other attributes.
104      */
105     public String css(String pattern, String otherAttributes) {
106         return css(Arrays.asList(pattern), otherAttributes);
107     }
108 
109     /**
110      * Generates css links for all css files matching the provided pattern(s).
111      */
112     public String css(final List<String> patterns) {
113         return css(patterns, null);
114     }
115 
116     /**
117      * Generates css links for all css files matching the provided pattern(s); css links will contain the provided other attributes.
118      */
119     public String css(List<String> patterns, String otherAttributes) {
120         return generate(patterns, getCssPrefix(otherAttributes), CSS_SUFFIX, false);
121     }
122 
123     /**
124      * Generates cached css links for all css files matching the provided pattern.
125      */
126     public String cachedCss(String pattern) {
127         return cachedCss(Arrays.asList(pattern));
128     }
129 
130     /**
131      * Generates cached css links for all css files matching the provided pattern; css links will contain the provided other attributes.
132      */
133     public String cachedCss(String pattern, String otherAttributes) {
134         return cachedCss(Arrays.asList(pattern), otherAttributes);
135     }
136 
137     /**
138      * Generates cached css links for all css files matching the provided pattern(s).
139      */
140     public String cachedCss(final List<String> patterns) {
141         return cachedCss(patterns, null);
142     }
143 
144     /**
145      * Generates cached css links for all css files matching the provided pattern(s); css links will contain the provided other attributes.
146      */
147     public String cachedCss(List<String> patterns, String otherAttributes) {
148         return generate(patterns, getCssPrefix(otherAttributes), CSS_SUFFIX, true);
149     }
150 
151     /**
152      * Generates js links for all js files matching the provided pattern.
153      */
154     public String js(String pattern) {
155         return js(Arrays.asList(pattern));
156     }
157 
158     /**
159      * Generates js links for all js files matching provided pattern(s).
160      */
161     public String js(final List<String> patterns) {
162         return generate(patterns, JS_PREFIX, JS_SUFFIX, false);
163     }
164 
165     /**
166      * Generates cached js links for all js files matching the provided pattern.
167      */
168     public String cachedJs(String pattern) {
169         return cachedJs(Arrays.asList(pattern));
170     }
171 
172     /**
173      * Generates cached js links for all js files matching the provided pattern(s).
174      */
175     public String cachedJs(final List<String> patterns) {
176         return generate(patterns, JS_PREFIX, JS_SUFFIX, true);
177     }
178 
179     /**
180      * Generates the links to .css or .js files. Returns all "links" as printable html code.
181      */
182     private String generate(List<String> patterns, String prefix, String suffix, final boolean isCached) {
183         // yes, order matters!
184         Map<String, List<String>> intermediates = new LinkedHashMap<>();
185         patterns.forEach(pattern -> intermediates.put(pattern, new LinkedList<>()));
186         origin.traverseWith(new ResourceVisitor() {
187 
188             @Override
189             public void visitFile(final Resource resource) {
190                 patterns.stream()
191                         .filter(pattern -> matchSafely(resource.getPath(), pattern))
192                         .map(intermediates::get)
193                         .forEach(results -> results.add(prefix + linker.linkTo(resource.getPath(), isCached) + suffix));
194             }
195 
196             private boolean matchSafely(String resource, String pattern) {
197                 try {
198                     return resource.matches(pattern);
199                 } catch (PatternSyntaxException e) {
200                     // ignore
201                 }
202                 return false;
203             }
204 
205             @Override
206             public boolean visitDirectory(Resource resource) {
207                 return true;
208             }
209         });
210 
211         StringBuilder results = new StringBuilder();
212         List<String> hits = new LinkedList<>();
213         // de dup (first match wins)
214         intermediates.values()
215                 .forEach(patternResults -> patternResults.stream()
216                         .filter(potentialHit -> !hits.contains(potentialHit))
217                         .forEach(hit -> {
218                             results.append(hit).append("\n");
219                             hits.add(hit);
220                         }));
221 
222         return results.toString();
223     }
224 
225     private static String getCssPrefix(String otherAttributes) {
226         return "<link rel=\"stylesheet\" type=\"text/css\" " + (otherAttributes == null ? "" : otherAttributes + " ") + "href=\"";
227     }
228 
229 }