1 // Copyright 2006, 2008 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.tapestry5.internal.services;
16
17 import org.apache.tapestry5.ioc.Resource;
18 import org.apache.tapestry5.ioc.internal.util.ClasspathResource;
19 import org.apache.tapestry5.services.ClasspathAssetAliasManager;
20 import org.apache.tapestry5.services.Dispatcher;
21 import org.apache.tapestry5.services.Request;
22 import org.apache.tapestry5.services.Response;
23
24 import javax.servlet.http.HttpServletResponse;
25 import java.io.IOException;
26
27 /**
28 * Recognizes requests where the path begins with "/asset/" and delivers the content therein as a bytestream. Also
29 * handles requests that are simply polling for a change to the file.
30 *
31 * @see ResourceStreamer
32 * @see ClasspathAssetAliasManager
33 * @see ResourceCache
34 */
35 public class AssetDispatcher implements Dispatcher
36 {
37 private final ResourceStreamer streamer;
38
39 private final ClasspathAssetAliasManager aliasManager;
40
41 private final ResourceCache resourceCache;
42
43 static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since";
44
45 public AssetDispatcher(ResourceStreamer streamer, ClasspathAssetAliasManager aliasManager,
46 ResourceCache resourceCache)
47 {
48 this.streamer = streamer;
49 this.aliasManager = aliasManager;
50 this.resourceCache = resourceCache;
51 }
52
53 public boolean dispatch(Request request, Response response) throws IOException
54 {
55 String path = request.getPath();
56
57 // Remember that the request path does not include the context path, so we can simply start
58 // looking for the asset path prefix right off the bat.
59
60 if (!path.startsWith(RequestConstants.ASSET_PATH_PREFIX)) return false;
61
62 // ClassLoaders like their paths to start with a leading slash.
63
64 String resourcePath = aliasManager.toResourcePath(path);
65
66 Resource resource = findResourceAndValidateDigest(response, resourcePath);
67
68 if (resource == null) return true;
69
70 if (!resource.exists())
71 {
72 response.sendError(HttpServletResponse.SC_NOT_FOUND, ServicesMessages
73 .assetDoesNotExist(resource));
74 return true;
75 }
76
77 long ifModifiedSince = 0;
78
79 try
80 {
81 ifModifiedSince = request.getDateHeader(IF_MODIFIED_SINCE_HEADER);
82 }
83 catch (IllegalArgumentException ex)
84 {
85 // Simulate the header being missing if it is poorly formatted.
86
87 ifModifiedSince = -1;
88 }
89
90 if (ifModifiedSince > 0)
91 {
92 long modified = resourceCache.getTimeModified(resource);
93
94 if (ifModifiedSince >= modified)
95 {
96 response.sendError(HttpServletResponse.SC_NOT_MODIFIED, "");
97 return true;
98 }
99 }
100
101 streamer.streamResource(resource);
102
103 return true;
104 }
105
106 /**
107 * @param response used to send errors back to the client
108 * @param resourcePath the path to the requested resource, from the request
109 * @return the resource for the path, with the digest stripped out of the URL, or null if the digest is invalid (and
110 * an error has been sent back to the client)
111 * @throws IOException
112 */
113 private Resource findResourceAndValidateDigest(Response response, String resourcePath) throws IOException
114 {
115 Resource resource = new ClasspathResource(resourcePath);
116
117 if (!resourceCache.requiresDigest(resource)) return resource;
118
119 String file = resource.getFile();
120
121 // Somehow this code got real ugly, but it's all about preventing NPEs when a resource
122 // that should have a digest doesn't.
123
124 boolean valid = false;
125 Resource result = resource;
126
127 int lastdotx = file.lastIndexOf('.');
128
129 if (lastdotx > 0)
130 {
131 int prevdotx = file.lastIndexOf('.', lastdotx - 1);
132
133 if (prevdotx > 0)
134 {
135
136 String requestDigest = file.substring(prevdotx + 1, lastdotx);
137
138 // Strip the digest out of the file name.
139
140 String realFile = file.substring(0, prevdotx) + file.substring(lastdotx);
141
142 result = resource.forFile(realFile);
143
144 String actualDigest = resourceCache.getDigest(result);
145
146 valid = requestDigest.equals(actualDigest);
147 }
148 }
149
150 if (!valid)
151 {
152 response.sendError(HttpServletResponse.SC_FORBIDDEN, ServicesMessages
153 .wrongAssetDigest(result));
154 result = null;
155 }
156
157 return result;
158 }
159 }