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