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