KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tapestry > asset > AssetService


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 JavaDoc;
18 import java.io.IOException JavaDoc;
19 import java.io.InputStream JavaDoc;
20 import java.io.OutputStream JavaDoc;
21 import java.net.URL JavaDoc;
22 import java.net.URLConnection JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import javax.servlet.http.HttpServletResponse JavaDoc;
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 JavaDoc _mimeTypes;
90
91     static
92     {
93         _mimeTypes = new HashMap JavaDoc(17);
94         _mimeTypes.put("css", "text/css");
95         _mimeTypes.put("gif", "image/gif");
96         _mimeTypes.put("jpg", "image/jpeg");
97         _mimeTypes.put("jpeg", "image/jpeg");
98         _mimeTypes.put("htm", "text/html");
99         _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 JavaDoc 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 JavaDoc 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     public ILink getLink(IRequestCycle cycle, Object JavaDoc parameter)
148     {
149         Defense.isAssignable(parameter, String JavaDoc.class, "parameter");
150
151         String JavaDoc path = (String JavaDoc) parameter;
152
153         String JavaDoc externalURL = _assetExternalizer.getURL(path);
154
155         if (externalURL != null)
156             return new StaticLink(externalURL);
157
158         String JavaDoc digest = _digestSource.getDigestForResource(path);
159
160         Map JavaDoc parameters = new HashMap JavaDoc();
161
162         parameters.put(ServiceConstants.SERVICE, Tapestry.ASSET_SERVICE);
163         parameters.put(PATH, path);
164         parameters.put(DIGEST, digest);
165
166         // Service is stateless, which is the exception to the rule.
167

168         return _linkFactory.constructLink(cycle, parameters, false);
169     }
170
171     public String JavaDoc getName()
172     {
173         return Tapestry.ASSET_SERVICE;
174     }
175
176     private String JavaDoc getMimeType(String JavaDoc path)
177     {
178         String JavaDoc result = _context.getMimeType(path);
179
180         if (result == null)
181         {
182             int dotx = path.lastIndexOf('.');
183             String JavaDoc key = path.substring(dotx + 1).toLowerCase();
184
185             result = (String JavaDoc) _mimeTypes.get(key);
186
187             if (result == null)
188                 result = "text/plain";
189         }
190
191         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     public void service(IRequestCycle cycle) throws IOException JavaDoc
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         if (_request.getHeader("If-Modified-Since") != null)
207         {
208             _response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
209             return;
210         }
211
212         String JavaDoc path = cycle.getParameter(PATH);
213         String JavaDoc md5 = cycle.getParameter(DIGEST);
214
215         try
216         {
217             if (!_digestSource.getDigestForResource(path).equals(md5))
218                 throw new ApplicationRuntimeException(AssetMessages.md5Mismatch(path));
219
220             URL JavaDoc resourceURL = _classResolver.getResource(path);
221
222             if (resourceURL == null)
223                 throw new ApplicationRuntimeException(AssetMessages.noSuchResource(path));
224
225             URLConnection JavaDoc resourceConnection = resourceURL.openConnection();
226
227             writeAssetContent(cycle, path, resourceConnection);
228         }
229         catch (Throwable JavaDoc ex)
230         {
231             _exceptionReporter.reportRequestException(AssetMessages.exceptionReportTitle(path), ex);
232         }
233
234     }
235
236     /** @since 2.2 */
237
238     private void writeAssetContent(IRequestCycle cycle, String JavaDoc resourcePath,
239             URLConnection JavaDoc resourceConnection) throws IOException JavaDoc
240     {
241         InputStream JavaDoc input = null;
242
243         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             String JavaDoc contentType = getMimeType(resourcePath);
250             int contentLength = resourceConnection.getContentLength();
251
252             if (contentLength > 0)
253                 _response.setContentLength(contentLength);
254
255             _response.setDateHeader("Last-Modified", _startupTime);
256             _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             if (contentType == null || contentType.length() == 0)
262                 contentType = getMimeType(resourcePath);
263
264             OutputStream JavaDoc output = _response.getOutputStream(new ContentType(contentType));
265
266             input = new BufferedInputStream JavaDoc(resourceConnection.getInputStream());
267
268             byte[] buffer = new byte[BUFFER_SIZE];
269
270             while (true)
271             {
272                 int bytesRead = input.read(buffer);
273
274                 if (bytesRead < 0)
275                     break;
276
277                 output.write(buffer, 0, bytesRead);
278             }
279
280             input.close();
281             input = null;
282         }
283         finally
284         {
285             IOUtils.close(input);
286         }
287     }
288
289     /** @since 4.0 */
290
291     public void setExceptionReporter(RequestExceptionReporter exceptionReporter)
292     {
293         _exceptionReporter = exceptionReporter;
294     }
295
296     /** @since 4.0 */
297     public void setAssetExternalizer(AssetExternalizer assetExternalizer)
298     {
299         _assetExternalizer = assetExternalizer;
300     }
301
302     /** @since 4.0 */
303     public void setLinkFactory(LinkFactory linkFactory)
304     {
305         _linkFactory = linkFactory;
306     }
307
308     /** @since 4.0 */
309     public void setClassResolver(ClassResolver classResolver)
310     {
311         _classResolver = classResolver;
312     }
313
314     /** @since 4.0 */
315     public void setContext(WebContext context)
316     {
317         _context = context;
318     }
319
320     /** @since 4.0 */
321     public void setResponse(WebResponse response)
322     {
323         _response = response;
324     }
325
326     /** @since 4.0 */
327     public void setDigestSource(ResourceDigestSource md5Source)
328     {
329         _digestSource = md5Source;
330     }
331
332     /** @since 4.0 */
333     public void setRequest(WebRequest request)
334     {
335         _request = request;
336     }
337 }
Popular Tags