##### Coverage Report - org.webmacro.resource.URLTemplateProvider

Classes in this File | Line Coverage | Branch Coverage | Complexity
URLTemplateProvider |
0% | 0/148
0% | 0/66
| 5.846153846153846;5.846

1 |  |
     /*
2 |  |
      * Copyright (C) 1998-2000 Semiotek Inc.  All Rights Reserved.
3 |  |
      *
4 |  |
      * Redistribution and use in source and binary forms, with or without
5 |  |
      * modification, are permitted under the terms of either of the following
6 |  |
      * Open Source licenses:
7 |  |
      *
8 |  |
      * The GNU General Public License, version 2, or any later version, as
9 |  |
      * published by the Free Software Foundation
10 |  |
      * (http://www.fsf.org/copyleft/gpl.html);
11 |  |
      *
12 |  |
      *  or
13 |  |
      *
14 |  |
      * The Semiotek Public License (http://webmacro.org/LICENSE.)
15 |  |
      *
16 |  |
      * This software is provided "as is", with NO WARRANTY, not even the
17 |  |
      * implied warranties of fitness to purpose, or merchantability. You
18 |  |
      * assume all risks and liabilities associated with its use.
19 |  |
      *
20 |  |
      * See www.webmacro.org for more information on the WebMacro project.
21 |  |
      */
22 |  |
23 |  |
     package org.webmacro.resource;
24 |  |
25 |  |
     import java.io.FileNotFoundException;
26 |  |
     import java.io.IOException;
27 |  |
     import java.io.InputStream;
28 |  |
     import java.net.MalformedURLException;
29 |  |
     import java.net.URL;
30 |  |
     import java.util.HashMap;
31 |  |
     import java.util.StringTokenizer;
32 |  |
33 |  |
     import org.slf4j.Logger;
34 |  |
     import org.slf4j.LoggerFactory;
35 |  |
36 |  |
     import org.webmacro.Broker;
37 |  |
     import org.webmacro.InitException;
38 |  |
     import org.webmacro.NotFoundException;
39 |  |
     import org.webmacro.ResourceException;
40 |  |
     import org.webmacro.Template;
41 |  |
     import org.webmacro.util.Settings;
42 |  |
43 |  |
     /**
44 |  |
      *
45 |  |
      *
46 |  |
      * This is a "drop-in" replacement for the standard TemplateProvider in the
47 |  |
      * WebMacro distribution.  The primary benefit is to allow a template to be loaded
48 |  |
      * by a variety of means, without requiring an absolute path.  This should make
49 |  |
      * applications more portable.
50 |  |
      *
51 |  |
      * <h3>TemplatePath</h3>
52 |  |
      *
53 |  |
      *     <i>TemplatePath=path1[;path2;....]</i>
54 |  |
      * <p>
55 |  |
      * Each path should be a full URL specification or one of the special cases
56 |  |
      * listed below.  If the path cannot be interpreted, it will be tried as file:path
57 |  |
      *
58 |  |
      * <h3>Special cases</h3>
59 |  |
      *
60 |  |
      * <ul>
61 |  |
      *     <li><i>TemplatePath=classpath:/templates</i>
62 |  |
      *         Template will be loaded from the classpath.   Each directory on the
63 |  |
      * classpath will be tried in turn to locate the template.
64 |  |
      *
65 |  |
      *     <li><i>TemplatePath=ignore:</i>
66 |  |
      * Don't use the template path. All templates will be referenced
67 |  |
      * explicitly as full URL's.  A common case would be to generate a base URL
68 |  |
      * at runtime (e.g., from ServletContext.getResource("/") in JSDK 2.2+) and to
69 |  |
      * prepend this to the template path.
70 |  |
      *
71 |  |
      *     <li><i>TemplatePath=context:</i>
72 |  |
      *         reserved for future use (specifically for finding templates with a
73 |  |
      *        servlet context)
74 |  |
      * </ul>
75 |  |
      *
76 |  |
      * <h3>Locale support</h3>
77 |  |
      *
78 |  |
      * There is a limited locale based implemention here.  Template paths can contain
79 |  |
      * a string of the form {_aaa_bbb....} which will usually correspond to a
80 |  |
      * locale such as en_GB.
81 |  |
      * Thus <i>load("template{_en_GB}.wm")</i> will look for
82 |  |
      * <ul>
83 |  |
      *  <li>template_en_GB.wm</li>
84 |  |
      *  <li>template_en.wm</li>
85 |  |
      *  <li>template.wm</li>
86 |  |
      * </ul>
87 |  |
      *
88 |  |
      * in that order, returning the first one that exists.  This is implemented so that
89 |  |
      * requests for template_en_GB.wm & template_en_US.wm will both return the same template
90 |  |
      * template_en.wm assuming only the latter exists.  In other words, the template is
91 |  |
      * only parsed once.
92 |  |
      *
93 |  |
      * @see org.webmacro.resource.CachingProvider
94 |  |
      * @see org.webmacro.resource.TemplateProvider
95 |  |
      *
96 |  |
      * @since before 0.96
97 |  |
      * @author fergus
98 |  |
      */
99 | 0 |
     final public class URLTemplateProvider extends CachingProvider
100 |  |
     {
101 | 0 |
         static Logger _log =  LoggerFactory.getLogger(URLTemplateProvider.class);
102 |  |
103 |  |
         /** CVS Revision tag.  */
104 |  |
         public static final String RCS = "@(#) $Id: org.webmacro.resource.URLTemplateProvider.html,v 1.1 2010/03/04 23:00:05 timp Exp $";
105 |  |
106 |  |
         // INITIALIZATION
107 |  |
108 | 0 |
         private Broker _broker = null;
109 |  |
110 |  |
         /** The default separator for TemplathPath
111 |  |
          */
112 | 0 |
         private static String _pathSeparator = ";";
113 |  |
114 | 0 |
         private String[] _templateDirectory = null;
115 |  |
116 |  |
         /**
117 |  |
          * URLs can contain strings like "{_AAA_BBB...}" to mimic
118 |  |
          * locale handling in ResourceBundles.
119 |  |
          * I think this can be improved upon - one idea would be to
120 |  |
          * fix the wm extension and then ask for (resource)+(locale_string).wm
121 |  |
          * We still have the problem of how to pass in the locale info, just as to
122 |  |
          * pass in the encoding.
123 |  |
          *
124 |  |
          */
125 |  |
126 |  |
         private static final String _OPEN = "{";
127 |  |
         private static final String _CLOSE = "}";
128 |  |
129 |  |
         private static final String CLASSPATH_PREFIX = "classpath:";
130 |  |
         private static final String CONTEXT_PREFIX = "context:";
131 |  |
         private static final String IGNORE_PREFIX = "ignore:";
132 |  |
133 | 0 |
         private static final int CLASSPATH_PREFIX_LENGTH
134 |  |
                 = CLASSPATH_PREFIX.length();
135 |  |
         //private static final int CONTEXT_PREFIX_LENGTH
136 |  |
         //        = CONTEXT_PREFIX.length();
137 |  |
138 |  |
139 |  |
         private static final String _TYPE = "template";
140 |  |
141 |  |
142 |  |
         /**
143 |  |
          *  _baseURL is just a placeholder for the moment.
144 |  |
          *  My hope is that we can pass in a context base at construction
145 |  |
          * time (e.g., the servlet context base)
146 |  |
          */
147 | 0 |
         private URL _baseURL = null;
148 |  |
149 | 0 |
         private final HashMap templateNameCache = new HashMap();
150 |  |
151 |  |
152 |  |
         /**
153 |  |
          * The value of TemplatePath in WebMacro inititialization file.
154 |  |
          * This is a semicolon separated string with individual values like
155 |  |
          * <ol>
156 |  |
          * <li> ignore: - ignore completely.  All request will be given by a full URL</li>
157 |  |
          * <li> classpath:[path].  Look for values on the system classpath, optionally
158 |  |
          * with some relative path. E.g., classpath:/templates/</li>
159 |  |
          * <li> *TODO* context:[path] load relative to some path in the current context
160 |  |
          * (typically a ServletContext) </li>
161 |  |
          * <li> [url] - look for templates relative to this location</li>
162 |  |
          * <li> [path] - Equivalent to file:[path]</li>
163 |  |
          * </ol>
164 |  |
          */
165 |  |
166 |  |
         private String _templatePath;
167 |  |
168 |  |
         /**
169 |  |
          * Supports the "template" type.  This is a straight replacement for the
170 |  |
          * default TemplateProvider
171 |  |
          * @return the template type.  Always the String "template"
172 |  |
          */
173 |  |
174 |  |
         final public String getType ()
175 |  |
         {
176 | 0 |
             return _TYPE;
177 |  |
         }
178 |  |
179 |  |
180 |  |
         /**
181 |  |
          * Create a new TemplateProvider that uses the specified directory
182 |  |
          * as the source for Template objects that it will return.
183 |  |
          *
184 |  |
          * @param b A broker
185 |  |
          * @param config Settings from the webmacro initialization file
186 |  |
          * @exception InitException thrown when the provider fails to initialize
187 |  |
          */
188 |  |
189 |  |
         public void init (Broker b, Settings config) throws InitException
190 |  |
         {
191 | 0 |
             super.init(b, config);
192 | 0 |
             _broker = b;
193 |  |
194 |  |
             try
195 |  |
             {
196 | 0 |
                 _templatePath = config.getSetting("TemplatePath");
197 | 0 |
                 StringTokenizer st =
198 |  |
                         new StringTokenizer(_templatePath, _pathSeparator);
199 | 0 |
                 _templateDirectory = new String[st.countTokens()];
200 |  |
                 int i;
201 | 0 |
                 for (i = 0; i < _templateDirectory.length; i++)
202 |  |
                 {
203 | 0 |
                     String dir = st.nextToken();
204 | 0 |
                     _templateDirectory[i] = dir;
205 |  |
                 }
206 |  |
207 |  |
             }
208 | 0 |
             catch (Exception e)
209 |  |
             {
210 | 0 |
                 throw new InitException("Could not initialize", e);
211 | 0 |
             }
212 | 0 |
         }
213 |  |
214 |  |
215 |  |
         /**
216 |  |
          * Grab a template based on its name, setting the request event to
217 |  |
          * contain it if we found it.
218 |  |
          * @param name The name of the  template to load
219 |  |
          * @throws NotFoundException if no matching template can be found
220 |  |
          * @throws ResourceException if template cannot be loaded
221 |  |
          * @return the requested resource
222 |  |
          */
223 |  |
224 |  |
         final public Object load (String name, CacheElement ce)
225 |  |
                 throws ResourceException
226 |  |
         {
227 | 0 |
             return load(name, _baseURL);
228 |  |
         }
229 |  |
230 |  |
         /**
231 |  |
          * Find the specified template in the directory managed by this
232 |  |
          * template store. Any path specified in the filename is relative
233 |  |
          * to the directory managed by the template store.
234 |  |
          * <p>
235 |  |
          * @param name relative to the current directory fo the store
236 |  |
          * @return a template matching that name, or null if one cannot be found
237 |  |
          */
238 |  |
239 |  |
         final public Object load (String name, URL base)
240 |  |
                 throws ResourceException
241 |  |
         {
242 | 0 |
             _log.debug("Load URLTemplate: (" + base + "," + name + ")");
243 |  |
             try
244 |  |
             {
245 | 0 |
                 Template _tmpl = null;
246 |  |
247 | 0 |
                 if (base == null)
248 |  |
                 {
249 | 0 |
                     _tmpl = getTemplate(name);
250 |  |
                 }
251 |  |
                 else
252 |  |
                 {
253 | 0 |
                     _tmpl = getTemplate(new URL(base, name));
254 |  |
                 }
255 |  |
256 | 0 |
                 if (_tmpl == null)
257 |  |
                 {
258 | 0 |
                     throw new NotFoundException(
259 |  |
                             this + " could not locate " + name + " on path " + _templatePath);
260 |  |
                 }
261 | 0 |
                 templateNameCache.put(name, _tmpl);
262 | 0 |
                 return _tmpl;
263 |  |
             }
264 | 0 |
             catch (IOException e)
265 |  |
             {
266 | 0 |
                 _log.debug(e.getClass().getName() + " " + e.getMessage());
267 | 0 |
                 throw new ResourceException(e.getMessage());
268 |  |
             }
269 |  |
         }
270 |  |
271 |  |
         /**
272 |  |
          * Always return false.  It is not possible to decide if an object
273 |  |
          * fetched from a URL should be reloaded or not.  Returning false
274 |  |
          * will cause the CachingProvider to load() only when it's cache
275 |  |
          * has expired.
276 |  |
          * @param name The name of the template to test
277 |  |
          * @return always returns false.
278 |  |
          *
279 |  |
          * ** TO DO **
280 |  |
          * Can do better than this for file: and jar: URLs
281 |  |
          *
282 |  |
          * jar urls are of the form
283 |  |
          *     jar:<url-of-jar>!<path-within-jar>
284 |  |
          * e.g.,
285 |  |
          *    jar:file:/path/my.jar!/templates/test.wm
286 |  |
          *
287 |  |
          * However, this might be an expensive operation.
288 |  |
          */
289 |  |
290 |  |
         // IMPLEMENTATION
291 |  |
292 |  |
         /**
293 |  |
          *
294 |  |
          */
295 |  |
296 |  |
         final public boolean shouldReload (String name)
297 |  |
         {
298 | 0 |
             URLTemplate tmpl = (URLTemplate) templateNameCache.get(name);
299 | 0 |
             return (tmpl == null) ? false : tmpl.shouldReload();
300 |  |
         }
301 |  |
302 |  |
         private final boolean exists (URL url)
303 |  |
         {
304 | 0 |
             InputStream _is = null;
305 |  |
             try
306 |  |
             {
307 | 0 |
                 _is = url.openStream();
308 | 0 |
                 System.out.println(url + " exists");
309 | 0 |
                 return true;
310 |  |
             }
311 | 0 |
             catch (IOException e)
312 |  |
             {
313 | 0 |
                 System.out.println(url + " does not exist");
314 | 0 |
                 return false;
315 |  |
             }
316 |  |
             finally
317 |  |
             {
318 | 0 |
                 if (_is != null)
319 |  |
                 {
320 |  |
                     try
321 |  |
                     {
322 | 0 |
                         _is.close();
323 |  |
                     }
324 | 0 |
                     catch (Exception ignore)
325 |  |
                     {
326 | 0 |
                     }
327 |  |
                 }
328 |  |
             }
329 |  |
         }
330 |  |
331 |  |
         /**
332 |  |
          * Load a template relative to the base.
333 |  |
          * @param path the relative or absolute URL-path of the template
334 |  |
          */
335 |  |
336 |  |
         final private Template getTemplate (URL path)
337 |  |
         {
338 | 0 |
             _log.debug("get:" + path);
339 |  |
             URLTemplate t;
340 | 0 |
             String pre = null;
341 | 0 |
             String mid = null;
342 | 0 |
             String post = null;
343 | 0 |
             String pathStr = path.toExternalForm();
344 |  |
             try
345 |  |
             {
346 | 0 |
                 String[] parts = parseLocalePath(pathStr);
347 |  |
348 | 0 |
                 String urlPath = null;
349 | 0 |
                 URL url = path;
350 | 0 |
                 if (parts == null)
351 |  |
                 {
352 | 0 |
                     urlPath = pathStr;
353 |  |
                 }
354 |  |
                 else
355 |  |
                 {
356 | 0 |
                     pre = parts[0];
357 | 0 |
                     mid = parts[1];
358 | 0 |
                     post = parts[2];
359 | 0 |
                     urlPath = pre + ((mid == null) ? "" : mid) + post;
360 | 0 |
                     url = new URL(urlPath);
361 |  |
                 }
362 |  |
363 | 0 |
                 if (urlPath.length() > 512)
364 |  |
                 {
365 | 0 |
                     throw new IllegalArgumentException("URL path too long: " + urlPath);
366 |  |
                 }
367 | 0 |
                 _log.debug("URLTemplateProvider: loading " + url +
368 |  |
                         "(" + path + "," + urlPath + ")");
369 |  |
370 | 0 |
                 t = new URLTemplate(_broker, url);
371 | 0 |
                 _log.debug("**PARSING " + url);
372 | 0 |
                 t.parse();
373 | 0 |
                 return t;
374 |  |
             }
375 | 0 |
             catch (IOException e)
376 |  |
             {
377 | 0 |
                 _log.debug(e.getClass().getName() + " " + e.getMessage());
378 |  |
                 // try the next locale
379 | 0 |
                 if (mid != null)
380 |  |
                 {
381 |  |
                     try
382 |  |
                     {
383 | 0 |
                         String p = buildPath(pre, mid, post);
384 | 0 |
                         return (Template) _broker.get(_TYPE, p);
385 |  |
                     }
386 | 0 |
                     catch (Exception ex)
387 |  |
                     {
388 | 0 |
                         _log.debug(ex.getClass().getName() + " " + ex.getMessage());
389 |  |
                         // ignore
390 |  |
                     }
391 |  |
                 }
392 | 0 |
                 _log.error("URLTemplateProvider(1): Could not load template: " + path, e);
393 |  |
             }
394 | 0 |
             catch (Exception e)
395 |  |
             {
396 | 0 |
                 _log.error("URLTemplateProvider(2): Could not load template: " + path, e);
397 | 0 |
             }
398 | 0 |
             _log.debug("URLTemplateProvider: " + path + " not found.");
399 | 0 |
             return null;
400 |  |
         }
401 |  |
402 |  |
         /**
403 |  |
          * Get the URL for a specified template.
404 |  |
          */
405 |  |
406 |  |
         private Template getTemplate (String path)
407 |  |
                 throws IOException
408 |  |
         {
409 | 0 |
             _log.debug("getTemplate: " + path);
410 | 0 |
             for (int i = 0; i < _templateDirectory.length; i++)
411 |  |
             {
412 | 0 |
                 String tPart = _templateDirectory[i];
413 | 0 |
                 URL url = null;
414 | 0 |
                 if (tPart.startsWith(CLASSPATH_PREFIX))
415 |  |
                 {
416 | 0 |
                     tPart = tPart.substring(CLASSPATH_PREFIX_LENGTH);
417 | 0 |
                     url = searchClasspath(join(tPart, path));
418 |  |
419 | 0 |
                     if (url == null)
420 |  |
                     {
421 | 0 |
                         throw new FileNotFoundException("Unable to locate " + path + " on classpath");
422 |  |
                     }
423 |  |
                 }
424 | 0 |
                 else if (tPart.startsWith(CONTEXT_PREFIX))
425 |  |
                 {
426 | 0 |
                     throw new IllegalStateException("Not implemented");
427 |  |
                 }
428 | 0 |
                 else if (tPart.startsWith(IGNORE_PREFIX))
429 |  |
                 {
430 | 0 |
                     url = new URL(path);
431 |  |
                 }
432 |  |
                 else
433 |  |
                 {
434 | 0 |
                     String s = join(tPart, path);
435 |  |
                     try
436 |  |
                     {
437 | 0 |
                         url = new URL(s);
438 |  |
                     }
439 | 0 |
                     catch (MalformedURLException e)
440 |  |
                     {
441 | 0 |
                         url = new URL("file", null, s);
442 | 0 |
                     }
443 |  |
                 }
444 | 0 |
                 if (exists(url))
445 |  |
                 {
446 | 0 |
                     return getTemplate(url);
447 |  |
                 }
448 |  |
             }
449 | 0 |
             return null;
450 |  |
         }
451 |  |
452 |  |
         // Utility Methods
453 |  |
454 |  |
         /**
455 |  |
          * The URL path separator.  Used by join().
456 |  |
          */
457 |  |
         private static final String _SEP = "/";
458 |  |
459 |  |
         /**
460 |  |
          * Join two parts of a URL string together, without duplicating
461 |  |
          * any "/" between them;
462 |  |
          */
463 |  |
464 |  |
         private final String join (String pre, String post)
465 |  |
         {
466 | 0 |
             _log.debug("Joining <" + pre + "> + <" + post + ">");
467 | 0 |
             if ((pre == null) || (pre.length() == 0)) return post;
468 | 0 |
             if ((post == null) || (post.length() == 0)) return pre;
469 | 0 |
             boolean first = pre.endsWith(_SEP);
470 | 0 |
             boolean second = post.startsWith(_SEP);
471 | 0 |
             if (first ^ second) return pre + post;
472 | 0 |
             if (first) return pre.substring(0, pre.length() - 1) + post;
473 | 0 |
             if (second) return first + post.substring(1);
474 | 0 |
             return pre + _SEP + post;
475 |  |
         }
476 |  |
477 |  |
         /**
478 |  |
          * Searches the SYSTEM classpath for a resource.
479 |  |
          * Probably Class.getSytemResource() might be better.
480 |  |
          * <p>
481 |  |
          * Ideally would like to be able to search the application
482 |  |
          * classpath, which is not necessarily the same (e.g., in servlet 2.2+)
483 |  |
          * so could pass in the application classloader.  But we have the same
484 |  |
          * problem as in other places where there is no easy way to pass extra
485 |  |
          * information with the request.
486 |  |
          *
487 |  |
          */
488 |  |
         private final URL searchClasspath (String resource)
489 |  |
         {
490 | 0 |
             _log.debug("Searching classpath for " + resource);
491 | 0 |
             URL url = null;
492 | 0 |
             ClassLoader cl = this.getClass().getClassLoader();
493 | 0 |
             if (cl != null)
494 |  |
             {
495 | 0 |
                 url = cl.getResource(resource);
496 |  |
             }
497 |  |
             else
498 |  |
             {
499 | 0 |
                 url = ClassLoader.getSystemResource(resource);
500 |  |
             }
501 |  |
502 | 0 |
             if (url != null)
503 |  |
             {
504 | 0 |
                 return url;
505 |  |
             }
506 |  |
             /*
507 |  |
              * look for locale specific resources AAAA{_xxxx_yyyyy_....}BBBBB
508 |  |
              */
509 | 0 |
             String[] parts = parseLocalePath(resource);
510 | 0 |
             if (parts != null)
511 |  |
             {
512 | 0 |
                 if (parts[1] != null)
513 |  |
                 {
514 | 0 |
                     resource = buildPath(parts[0], parts[1], parts[2]);
515 | 0 |
                     return searchClasspath(resource);
516 |  |
                 }
517 |  |
             }
518 | 0 |
             return null;
519 |  |
         }
520 |  |
521 |  |
         /**
522 |  |
          * Removes the last locale part from a string
523 |  |
          * <p>
524 |  |
          * e.g. "_en_GB" => "_en"
525 |  |
          */
526 |  |
         private final String stripLast (String s)
527 |  |
         {
528 | 0 |
             if (s == null) return null;
529 | 0 |
             int p = s.lastIndexOf("_");
530 | 0 |
             if (p < 0)
531 |  |
             {
532 | 0 |
                 return null;
533 |  |
             }
534 | 0 |
             String ret = s.substring(0, p);
535 | 0 |
             return ("".equals(ret)) ? null : ret;
536 |  |
         }
537 |  |
538 |  |
         /**
539 |  |
          * Builds up a new path from the form AAAAA{_B1_B2...._Bn-1}CCCCC
540 |  |
          * when given arguments AAAA, _B1_B2...._Bn, CCCC
541 |  |
          * <p>
542 |  |
          * e.g., "path/myfile","_en_GB",".wm" => "path/myfile{_en}.wm"
543 |  |
          */
544 |  |
         private final String buildPath (String pre, String mid, String post)
545 |  |
         {
546 | 0 |
             StringBuffer sb = new StringBuffer(pre);
547 | 0 |
             String stripped = stripLast(mid);
548 | 0 |
             if (stripped != null)
549 |  |
             {
550 | 0 |
                 sb.append(_OPEN);
551 | 0 |
                 sb.append(stripped);
552 | 0 |
                 sb.append(_CLOSE);
553 |  |
             }
554 | 0 |
             sb.append(post);
555 | 0 |
             return sb.toString();
556 |  |
         }
557 |  |
558 |  |
         /**
559 |  |
          * Looks for a string of the form AAA{BBB}CCC.
560 |  |
          * If found, returns [AAA,BBB,CCC], null otherwise
561 |  |
          *
562 |  |
          * This is used to strip out the "Locale" part of a resource name
563 |  |
          */
564 |  |
         private String[] parseLocalePath (String path)
565 |  |
         {
566 | 0 |
             int p1 = path.indexOf(_OPEN);
567 | 0 |
             int p2 = path.indexOf(_CLOSE);
568 | 0 |
             if ((p1 < 0) || (p2 < 0) || (p2 < p1))
569 |  |
             {
570 | 0 |
                 return null;
571 |  |
             }
572 | 0 |
             String pre = path.substring(0, p1);
573 | 0 |
             String mid = path.substring(p1 + 1, p2);
574 | 0 |
             String post = path.substring(p2 + 1);
575 | 0 |
             return new String[]{pre, mid, post};
576 |  |
         }
577 |  |
     }