Clover coverage report - Code Coverage for tapestry release 4.0.2
Coverage timestamp: Thu Apr 13 2006 10:52:06 EDT
file stats: LOC: 325   Methods: 12
NCLOC: 168   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
AssetService.java 44.4% 80.9% 100% 76.5%
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.IOException;
 19    import java.io.InputStream;
 20    import java.io.OutputStream;
 21    import java.net.URL;
 22    import java.net.URLConnection;
 23    import java.util.HashMap;
 24    import java.util.Map;
 25   
 26    import javax.servlet.http.HttpServletResponse;
 27   
 28    import org.apache.hivemind.ApplicationRuntimeException;
 29    import org.apache.hivemind.ClassResolver;
 30    import org.apache.hivemind.util.Defense;
 31    import org.apache.hivemind.util.IOUtils;
 32    import org.apache.tapestry.IRequestCycle;
 33    import org.apache.tapestry.Tapestry;
 34    import org.apache.tapestry.engine.IEngineService;
 35    import org.apache.tapestry.engine.ILink;
 36    import org.apache.tapestry.error.RequestExceptionReporter;
 37    import org.apache.tapestry.services.LinkFactory;
 38    import org.apache.tapestry.services.ServiceConstants;
 39    import org.apache.tapestry.util.ContentType;
 40    import org.apache.tapestry.web.WebContext;
 41    import org.apache.tapestry.web.WebRequest;
 42    import org.apache.tapestry.web.WebResponse;
 43   
 44    /**
 45    * A service for building URLs to and accessing {@link org.apache.tapestry.IAsset}s. Most of the
 46    * work is deferred to the {@link org.apache.tapestry.IAsset}instance.
 47    * <p>
 48    * The retrieval part is directly linked to {@link PrivateAsset}. The service responds to a URL
 49    * that encodes the path of a resource within the classpath. The {@link #service(IRequestCycle)}
 50    * method reads the resource and streams it out.
 51    * <p>
 52    * TBD: Security issues. Should only be able to retrieve a resource that was previously registerred
 53    * in some way ... otherwise, hackers will be able to suck out the .class files of the application!
 54    *
 55    * @author Howard Lewis Ship
 56    */
 57   
 58    public class AssetService implements IEngineService
 59    {
 60   
 61    /** @since 4.0 */
 62    private ClassResolver _classResolver;
 63   
 64    /** @since 4.0 */
 65    private LinkFactory _linkFactory;
 66   
 67    /** @since 4.0 */
 68    private WebContext _context;
 69   
 70    /** @since 4.0 */
 71   
 72    private WebRequest _request;
 73   
 74    /** @since 4.0 */
 75    private WebResponse _response;
 76   
 77    /** @since 4.0 */
 78    private ResourceDigestSource _digestSource;
 79   
 80    /**
 81    * Defaults MIME types, by extension, used when the servlet container doesn't provide MIME
 82    * types. ServletExec Debugger, for example, fails to provide these.
 83    */
 84   
 85    private final static Map _mimeTypes;
 86   
 87    static
 88    {
 89  1 _mimeTypes = new HashMap(17);
 90  1 _mimeTypes.put("css", "text/css");
 91  1 _mimeTypes.put("gif", "image/gif");
 92  1 _mimeTypes.put("jpg", "image/jpeg");
 93  1 _mimeTypes.put("jpeg", "image/jpeg");
 94  1 _mimeTypes.put("htm", "text/html");
 95  1 _mimeTypes.put("html", "text/html");
 96    }
 97   
 98    private static final int BUFFER_SIZE = 10240;
 99   
 100    /**
 101    * Startup time for this service; used to set the Last-Modified response header.
 102    *
 103    * @since 4.0
 104    */
 105   
 106    private final long _startupTime = System.currentTimeMillis();
 107   
 108    /**
 109    * Time vended assets expire. Since a change in asset content is a change in asset URI, we want
 110    * them to not expire ... but a year will do.
 111    */
 112   
 113    private final long _expireTime = _startupTime + 365 * 24 * 60 * 60 * 1000;
 114   
 115    /** @since 4.0 */
 116   
 117    private RequestExceptionReporter _exceptionReporter;
 118   
 119    /**
 120    * Query parameter that stores the path to the resource (with a leading slash).
 121    *
 122    * @since 4.0
 123    */
 124   
 125    public static final String PATH = "path";
 126   
 127    /**
 128    * Query parameter that stores the digest for the file; this is used to authenticate that the
 129    * client is allowed to access the file.
 130    *
 131    * @since 4.0
 132    */
 133   
 134    public static final String DIGEST = "digest";
 135   
 136    /**
 137    * Builds a {@link ILink}for a {@link PrivateAsset}.
 138    * <p>
 139    * A single parameter is expected, the resource path of the asset (which is expected to start
 140    * with a leading slash).
 141    */
 142   
 143  73 public ILink getLink(boolean post, Object parameter)
 144    {
 145  73 Defense.isAssignable(parameter, String.class, "parameter");
 146   
 147  73 String path = (String) parameter;
 148   
 149  73 String digest = _digestSource.getDigestForResource(path);
 150   
 151  73 Map parameters = new HashMap();
 152   
 153  73 parameters.put(ServiceConstants.SERVICE, getName());
 154  73 parameters.put(PATH, path);
 155  73 parameters.put(DIGEST, digest);
 156   
 157    // Service is stateless, which is the exception to the rule.
 158   
 159  73 return _linkFactory.constructLink(this, post, parameters, false);
 160    }
 161   
 162  171 public String getName()
 163    {
 164  171 return Tapestry.ASSET_SERVICE;
 165    }
 166   
 167  1 private String getMimeType(String path)
 168    {
 169  1 String result = _context.getMimeType(path);
 170   
 171  1 if (result == null)
 172    {
 173  0 int dotx = path.lastIndexOf('.');
 174  0 if (dotx > -1) {
 175  0 String key = path.substring(dotx + 1).toLowerCase();
 176  0 result = (String) _mimeTypes.get(key);
 177    }
 178   
 179  0 if (result == null)
 180  0 result = "text/plain";
 181    }
 182   
 183  1 return result;
 184    }
 185   
 186    /**
 187    * Retrieves a resource from the classpath and returns it to the client in a binary output
 188    * stream.
 189    */
 190   
 191  1 public void service(IRequestCycle cycle) throws IOException
 192    {
 193  1 String path = cycle.getParameter(PATH);
 194  1 String md5Digest = cycle.getParameter(DIGEST);
 195   
 196  1 try
 197    {
 198  1 if (!_digestSource.getDigestForResource(path).equals(md5Digest))
 199    {
 200  0 _response.sendError(HttpServletResponse.SC_FORBIDDEN, AssetMessages
 201    .md5Mismatch(path));
 202  0 return;
 203    }
 204   
 205    // If they were vended an asset in the past then it must be up-to date.
 206    // Asset URIs change if the underlying file is modified.
 207   
 208  1 if (_request.getHeader("If-Modified-Since") != null)
 209    {
 210  0 _response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
 211  0 return;
 212    }
 213   
 214  1 URL resourceURL = _classResolver.getResource(path);
 215   
 216  1 if (resourceURL == null)
 217  0 throw new ApplicationRuntimeException(AssetMessages.noSuchResource(path));
 218   
 219  1 URLConnection resourceConnection = resourceURL.openConnection();
 220   
 221  1 writeAssetContent(cycle, path, resourceConnection);
 222    }
 223    catch (Throwable ex)
 224    {
 225  0 _exceptionReporter.reportRequestException(AssetMessages.exceptionReportTitle(path), ex);
 226    }
 227   
 228    }
 229   
 230    /** @since 2.2 */
 231   
 232  1 private void writeAssetContent(IRequestCycle cycle, String resourcePath,
 233    URLConnection resourceConnection) throws IOException
 234    {
 235  1 InputStream input = null;
 236   
 237  1 try
 238    {
 239    // Getting the content type and length is very dependant
 240    // on support from the application server (represented
 241    // here by the servletContext).
 242   
 243  1 String contentType = getMimeType(resourcePath);
 244  1 int contentLength = resourceConnection.getContentLength();
 245   
 246  1 if (contentLength > 0)
 247  1 _response.setContentLength(contentLength);
 248   
 249  1 _response.setDateHeader("Last-Modified", _startupTime);
 250  1 _response.setDateHeader("Expires", _expireTime);
 251   
 252    // Set the content type. If the servlet container doesn't
 253    // provide it, try and guess it by the extension.
 254   
 255  1 if (contentType == null || contentType.length() == 0)
 256  0 contentType = getMimeType(resourcePath);
 257   
 258  1 OutputStream output = _response.getOutputStream(new ContentType(contentType));
 259   
 260  1 input = new BufferedInputStream(resourceConnection.getInputStream());
 261   
 262  1 byte[] buffer = new byte[BUFFER_SIZE];
 263   
 264  1 while (true)
 265    {
 266  2 int bytesRead = input.read(buffer);
 267   
 268  2 if (bytesRead < 0)
 269  1 break;
 270   
 271  1 output.write(buffer, 0, bytesRead);
 272    }
 273   
 274  1 input.close();
 275  1 input = null;
 276    }
 277    finally
 278    {
 279  1 IOUtils.close(input);
 280    }
 281    }
 282   
 283    /** @since 4.0 */
 284   
 285  25 public void setExceptionReporter(RequestExceptionReporter exceptionReporter)
 286    {
 287  25 _exceptionReporter = exceptionReporter;
 288    }
 289   
 290    /** @since 4.0 */
 291  25 public void setLinkFactory(LinkFactory linkFactory)
 292    {
 293  25 _linkFactory = linkFactory;
 294    }
 295   
 296    /** @since 4.0 */
 297  25 public void setClassResolver(ClassResolver classResolver)
 298    {
 299  25 _classResolver = classResolver;
 300    }
 301   
 302    /** @since 4.0 */
 303  25 public void setContext(WebContext context)
 304    {
 305  25 _context = context;
 306    }
 307   
 308    /** @since 4.0 */
 309  25 public void setResponse(WebResponse response)
 310    {
 311  25 _response = response;
 312    }
 313   
 314    /** @since 4.0 */
 315  25 public void setDigestSource(ResourceDigestSource md5Source)
 316    {
 317  25 _digestSource = md5Source;
 318    }
 319   
 320    /** @since 4.0 */
 321  25 public void setRequest(WebRequest request)
 322    {
 323  25 _request = request;
 324    }
 325    }