Clover coverage report - Code Coverage for tapestry release 4.0-beta-2
Coverage timestamp: Sat Jul 9 2005 22:02:17 EDT
file stats: LOC: 291   Methods: 8
NCLOC: 136   Classes: 1
30 day Evaluation License registered to hlship@comcast.net Your 30 day evaluation period has expired. Please visit http://www.cenqua.com to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
AssetExternalizerImpl.java 9.1% 14.3% 62.5% 17.4%
coverage coverage
 1    // Copyright 2004, 2005 The Apache Software Foundation
 2    //
 3    // Licensed under the Apache License, Version 2.0 (the "License");
 4    // you may not use this file except in compliance with the License.
 5    // You may obtain a copy of the License at
 6    //
 7    // http://www.apache.org/licenses/LICENSE-2.0
 8    //
 9    // Unless required by applicable law or agreed to in writing, software
 10    // distributed under the License is distributed on an "AS IS" BASIS,
 11    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12    // See the License for the specific language governing permissions and
 13    // limitations under the License.
 14   
 15    package org.apache.tapestry.asset;
 16   
 17    import java.io.BufferedInputStream;
 18    import java.io.BufferedOutputStream;
 19    import java.io.File;
 20    import java.io.FileOutputStream;
 21    import java.io.IOException;
 22    import java.io.InputStream;
 23    import java.io.OutputStream;
 24    import java.net.URL;
 25    import java.util.HashMap;
 26    import java.util.Map;
 27   
 28    import javax.servlet.ServletContext;
 29   
 30    import org.apache.commons.logging.Log;
 31    import org.apache.hivemind.ApplicationRuntimeException;
 32    import org.apache.hivemind.ClassResolver;
 33    import org.apache.tapestry.Tapestry;
 34    import org.apache.tapestry.engine.IPropertySource;
 35    import org.apache.tapestry.util.StringSplitter;
 36   
 37    /**
 38    * Implementation of the {@link org.apache.tapestry.asset.AssetExternalizer}service interface.
 39    * Responsible for copying assets from the classpath to an external directory that is visible to the
 40    * web server. The externalizer is stored inside the {@link ServletContext}as a named attribute.
 41    * <p>
 42    * The externalizer uses the name
 43    * <code>org.apache.tapestry.AssetExternalizer.<i>application name</i>
 44    * </code>. It configures
 45    * itself using two additional properties (searching in
 46    * {@link org.apache.tapestry.IEngine#getPropertySource()}. <table border=1>
 47    * <tr>
 48    * <th>Parameter</th>
 49    * <th>Description</th>
 50    * </tr>
 51    * <tr valign=top>
 52    * <td><code>org.apache.tapestry.asset.dir</code></td>
 53    * <td>The directory to which assets will be copied.</td>
 54    * </tr>
 55    * <tr valign=top>
 56    * <td><code>org.apache.tapestry.asset.URL</code></td>
 57    * <td>The corresponding URL for the asset directory.</td>
 58    * </tr>
 59    * </table>
 60    * <p>
 61    * If either of these parameters is null, then no externalization occurs. Private assets will still
 62    * be available, just less efficiently, as the application will be invoked via its servlet and,
 63    * ultimately, the {@link AssetService}will need to retrieve the asset.
 64    * <p>
 65    * Assets maintain thier directory structure when copied. For example, an asset with a resource path
 66    * of <code>/com/skunkworx/Banner.gif</code> would be copied to the file system as
 67    * <code><i>dir</i>/com/skunkworx/Banner.gif</code> and would have a URL of
 68    * <code><i>URL</i>/com/skunkworx/Banner.gif</code>.
 69    * <p>
 70    * The externalizer will create any directories as needed.
 71    * <p>
 72    * The externalizer will not overwrite existing files. When a new version of the application is
 73    * deployed with changed assets, there are two deployment stategies:
 74    * <ul>
 75    * <li>Delete the existing asset directory and allow the externalizer to recreate and repopulate
 76    * it.
 77    * <li>Change the asset directory and URL, allowing the old and new assets to exist side-by-side.
 78    * </ul>
 79    * <p>
 80    * When using the second approach, it is best to use a directory that has a version number in it,
 81    * for example, <code>D:/inetpub/assets/0</code> mapped to the URL <code>/assets/0</code>. When
 82    * a new version of the application is deployed, the trailing version number is incremented from 0
 83    * to 1.
 84    *
 85    * @author Howard Lewis Ship
 86    */
 87   
 88    public class AssetExternalizerImpl implements AssetExternalizer
 89    {
 90    /** @since 4.0 */
 91    private Log _log;
 92   
 93    private ClassResolver _resolver;
 94   
 95    /** @since 4.0 */
 96    private IPropertySource _propertySource;
 97   
 98    private File _assetDir;
 99   
 100    private String _URL;
 101   
 102    /**
 103    * A map from resource path (as a String) to final URL (as a String).
 104    */
 105   
 106    private Map _resources = new HashMap();
 107   
 108    private static final int BUFFER_SIZE = 2048;
 109   
 110  27 public void initializeService()
 111    {
 112  27 String directory = _propertySource.getPropertyValue("org.apache.tapestry.asset.dir");
 113   
 114  27 if (directory == null)
 115  27 return;
 116   
 117  0 _URL = _propertySource.getPropertyValue("org.apache.tapestry.asset.URL");
 118   
 119  0 if (_URL == null)
 120  0 return;
 121   
 122  0 _assetDir = new File(directory);
 123   
 124  0 _log.debug("Initialized with directory " + _assetDir + " mapped to " + _URL);
 125    }
 126   
 127  0 protected void externalize(String resourcePath) throws IOException
 128    {
 129  0 if (_log.isDebugEnabled())
 130  0 _log.debug("Externalizing " + resourcePath);
 131   
 132  0 File file = _assetDir;
 133   
 134    // Resources are always split by the unix seperator, even on Win32.
 135   
 136  0 StringSplitter splitter = new StringSplitter('/');
 137   
 138  0 String[] path = splitter.splitToArray(resourcePath);
 139   
 140    // The path is expected to start with a leading slash, but the StringSplitter
 141    // will ignore that leading slash.
 142   
 143  0 for (int i = 0; i < path.length - 1; i++)
 144    {
 145    // Doing it this way makes sure the path seperators are right.
 146   
 147  0 file = new File(file, path[i]);
 148    }
 149   
 150    // Make sure the directories exist.
 151   
 152  0 file.mkdirs();
 153   
 154  0 file = new File(file, path[path.length - 1]);
 155   
 156    // If the file exists, then assume all is well. This is OK for development,
 157    // but there may be multithreading (or even multiprocess) race conditions
 158    // around the creation of the file.
 159   
 160  0 if (file.exists())
 161  0 return;
 162   
 163    // Get the resource and copy it to the file.
 164   
 165  0 URL inputURL = _resolver.getResource(resourcePath);
 166  0 if (inputURL == null)
 167  0 throw new IOException(Tapestry.format("missing-resource", resourcePath));
 168   
 169  0 InputStream in = null;
 170  0 OutputStream out = null;
 171   
 172  0 try
 173    {
 174  0 in = new BufferedInputStream(inputURL.openStream());
 175   
 176  0 out = new BufferedOutputStream(new FileOutputStream(file));
 177   
 178  0 byte[] buffer = new byte[BUFFER_SIZE];
 179   
 180  0 while (true)
 181    {
 182  0 int bytesRead = in.read(buffer, 0, BUFFER_SIZE);
 183  0 if (bytesRead < 0)
 184  0 break;
 185   
 186  0 out.write(buffer, 0, bytesRead);
 187    }
 188    }
 189    finally
 190    {
 191  0 close(in);
 192  0 close(out);
 193    }
 194   
 195    // The file is copied!
 196    }
 197   
 198  0 private void close(InputStream in)
 199    {
 200  0 if (in != null)
 201  0 try
 202    {
 203  0 in.close();
 204    }
 205    catch (IOException ex)
 206    {
 207    // Ignore.
 208    }
 209    }
 210   
 211  0 private void close(OutputStream out)
 212    {
 213  0 if (out != null)
 214   
 215  0 try
 216    {
 217  0 out.close();
 218    }
 219    catch (IOException ex)
 220    {
 221    // Ignore
 222    }
 223    }
 224   
 225    /**
 226    * Gets the URL to a private resource. If the resource was previously copied out of the
 227    * classpath, the previously generated URL is returned.
 228    * <p>
 229    * If the asset directory and URL are not configured, then returns null.
 230    * <p>
 231    * Otherwise, the asset is copied out to the asset directory, the URL is constructed (and
 232    * recorded for later) and the URL is returned.
 233    * <p>
 234    * This method is not explicitly synchronized but should work multi-threaded. It synchronizes on
 235    * the internal <code>Map</code> used to map resource paths to URLs.
 236    *
 237    * @param resourcePath
 238    * The full path of the resource within the classpath. This is expected to include a
 239    * leading slash. For example: <code>/com/skunkworx/Banner.gif</code>.
 240    */
 241   
 242  77 public String getURL(String resourcePath)
 243    {
 244  77 if (_assetDir == null)
 245  77 return null;
 246   
 247  0 synchronized (_resources)
 248    {
 249  0 String result = (String) _resources.get(resourcePath);
 250   
 251  0 if (result != null)
 252  0 return result;
 253   
 254  0 try
 255    {
 256  0 externalize(resourcePath);
 257    }
 258    catch (IOException ex)
 259    {
 260  0 throw new ApplicationRuntimeException(Tapestry.format(
 261    "AssetExternalizer.externalize-failure",
 262    resourcePath,
 263    _assetDir), ex);
 264    }
 265   
 266  0 result = _URL + resourcePath;
 267   
 268  0 _resources.put(resourcePath, result);
 269   
 270  0 return result;
 271    }
 272    }
 273   
 274    /** @since 4.0 */
 275  27 public void setLog(Log log)
 276    {
 277  27 _log = log;
 278    }
 279   
 280    /** @since 4.0 */
 281  27 public void setClassResolver(ClassResolver resolver)
 282    {
 283  27 _resolver = resolver;
 284    }
 285   
 286    /** since 4.0 */
 287  27 public void setPropertySource(IPropertySource propertySource)
 288    {
 289  27 _propertySource = propertySource;
 290    }
 291    }