View Javadoc
1   /**
2    * This file Copyright (c) 2016-2017 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      /**
97       * Generates css links for all css files matching the provided pattern.
98       */
99      public String css(String pattern) {
100         return css(Arrays.asList(new String[] { pattern }));
101     }
102 
103     /**
104      * Generates css links for all css files matching the provided pattern; css links will contain the provided other attributes.
105      */
106     public String css(String pattern, String otherAttributes) {
107         return css(Arrays.asList(new String[] { pattern }), otherAttributes);
108     }
109 
110     /**
111      * Generates css links for all css files matching the provided pattern(s).
112      */
113     public String css(final List<String> patterns) {
114         return css(patterns, null);
115     }
116 
117     /**
118      * Generates css links for all css files matching the provided pattern(s); css links will contain the provided other attributes.
119      */
120     public String css(List<String> patterns, String otherAttributes) {
121         return generate(patterns, getCssPrefix(otherAttributes), CSS_SUFFIX, false);
122     }
123 
124     /**
125      * Generates cached css links for all css files matching the provided pattern.
126      */
127     public String cachedCss(String pattern) {
128         return cachedCss(Arrays.asList(new String[] { pattern }));
129     }
130 
131     /**
132      * Generates cached css links for all css files matching the provided pattern; css links will contain the provided other attributes.
133      */
134     public String cachedCss(String pattern, String otherAttributes) {
135         return cachedCss(Arrays.asList(new String[] { pattern }), otherAttributes);
136     }
137 
138     /**
139      * Generates cached css links for all css files matching the provided pattern(s).
140      */
141     public String cachedCss(final List<String> patterns) {
142         return cachedCss(patterns, null);
143     }
144 
145     /**
146      * Generates cached css links for all css files matching the provided pattern(s); css links will contain the provided other attributes.
147      */
148     public String cachedCss(List<String> patterns, String otherAttributes) {
149         return generate(patterns, getCssPrefix(otherAttributes), CSS_SUFFIX, true);
150     }
151 
152     /**
153      * Generates js links for all js files matching the provided pattern.
154      */
155     public String js(String pattern) {
156         return js(Arrays.asList(new String[] { pattern }));
157     }
158 
159     /**
160      * Generates js links for all js files matching provided pattern(s).
161      */
162     public String js(final List<String> patterns) {
163         return generate(patterns, JS_PREFIX, JS_SUFFIX, false);
164     }
165 
166     /**
167      * Generates cached js links for all js files matching the provided pattern.
168      */
169     public String cachedJs(String pattern) {
170         return cachedJs(Arrays.asList(new String[] { pattern }));
171     }
172 
173     /**
174      * Generates cached js links for all js files matching the provided pattern(s).
175      */
176     public String cachedJs(final List<String> patterns) {
177         return generate(patterns, JS_PREFIX, JS_SUFFIX, true);
178     }
179 
180     /**
181      * Generates the links to .css or .js files. Returns all "links" as printable html code.
182      */
183     private String generate(List<String> patterns, String prefix, String suffix, final boolean isCached) {
184         // yes, order matters!
185         Map<String, List<String>> intermediates = new LinkedHashMap<>();
186         patterns.stream()
187                 .forEach(pattern -> intermediates.put(pattern, new LinkedList<>()));
188         origin.traverseWith(new ResourceVisitor() {
189 
190             @Override
191             public void visitFile(final Resource resource) {
192                 patterns.stream()
193                         .filter(pattern -> matchSafely(resource.getPath(), pattern))
194                         .map(pattern -> intermediates.get(pattern))
195                         .forEach(results -> results.add(prefix + linker.linkTo(resource.getPath(), isCached) + suffix));
196             }
197 
198             private boolean matchSafely(String resource, String pattern) {
199                 try {
200                     return resource.matches(pattern);
201                 } catch (PatternSyntaxException e) {
202                     // ignore
203                 }
204                 return false;
205             }
206 
207             @Override
208             public boolean visitDirectory(Resource resource) {
209                 return true;
210             }
211         });
212 
213         StringBuilder results = new StringBuilder();
214         List<String> hits = new LinkedList<>();
215         // de dup (first match wins)
216         intermediates.values().stream()
217                 .forEach(patternResults -> patternResults.stream()
218                         .filter(potentialHit -> !hits.contains(potentialHit))
219                         .forEach(hit -> {
220                             results.append(hit).append("\n");
221                             hits.add(hit);
222                         }));
223 
224         return results.toString();
225     }
226 
227     private static String getCssPrefix(String otherAttributes) {
228         return "<link rel=\"stylesheet\" type=\"text/css\" " + (otherAttributes == null ? "" : otherAttributes + " ") + "href=\"";
229     }
230 
231 }