Coverage Report - org.webmacro.resource.URLTemplateProvider
 
Classes in this File Line Coverage Branch Coverage Complexity
URLTemplateProvider
0%
0/148
0%
0/66
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  
 }