001: // Copyright 2006 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.internal.services;
016:
017: import java.io.IOException;
018: import java.net.URL;
019:
020: import javax.servlet.http.HttpServletResponse;
021:
022: import org.apache.tapestry.TapestryConstants;
023: import org.apache.tapestry.ioc.Resource;
024: import org.apache.tapestry.ioc.internal.util.ClasspathResource;
025: import org.apache.tapestry.services.ClasspathAssetAliasManager;
026: import org.apache.tapestry.services.Dispatcher;
027: import org.apache.tapestry.services.Request;
028: import org.apache.tapestry.services.Response;
029:
030: /**
031: * Recognizes requests where the path begins with "/asset/" and delivers the content therein as a
032: * bytestream. Also handles requests that are simply polling for a change to the file.
033: *
034: * @see ResourceStreamer
035: * @see ClasspathAssetAliasManager
036: * @see ResourceCache
037: */
038: public class AssetDispatcher implements Dispatcher {
039: private final ResourceStreamer _streamer;
040:
041: private final ClasspathAssetAliasManager _aliasManager;
042:
043: private final ResourceCache _resourceCache;
044:
045: static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since";
046:
047: public AssetDispatcher(ResourceStreamer streamer,
048: ClasspathAssetAliasManager aliasManager,
049: ResourceCache resourceCache) {
050: _streamer = streamer;
051: _aliasManager = aliasManager;
052: _resourceCache = resourceCache;
053: }
054:
055: public boolean dispatch(Request request, Response response)
056: throws IOException {
057: String path = request.getPath();
058:
059: // Remember that the request path does not include the context path, so we can simply start
060: // looking for the asset path prefix right off the bat.
061:
062: if (!path.startsWith(TapestryConstants.ASSET_PATH_PREFIX))
063: return false;
064:
065: // ClassLoaders like their paths to start with a leading slash.
066:
067: String resourcePath = _aliasManager.toResourcePath(path);
068:
069: Resource resource = findResourceAndValidateDigest(response,
070: resourcePath);
071:
072: if (resource == null)
073: return true;
074:
075: URL url = resource.toURL();
076:
077: if (url == null) {
078: response.sendError(HttpServletResponse.SC_NOT_FOUND,
079: ServicesMessages.assetDoesNotExist(resource));
080: return true;
081: }
082:
083: long ifModifiedSince = request
084: .getDateHeader(IF_MODIFIED_SINCE_HEADER);
085: if (ifModifiedSince > 0) {
086: long modified = _resourceCache.getTimeModified(resource);
087: if (ifModifiedSince >= modified) {
088: response.sendError(HttpServletResponse.SC_NOT_MODIFIED,
089: "");
090: return true;
091: }
092: }
093:
094: _streamer.streamResource(resource);
095:
096: return true;
097: }
098:
099: /**
100: * @param response
101: * used to send errors back to the client
102: * @param resourcePath
103: * the path to the requested resource, from the request
104: * @return the resource for the path, with the digest stripped out of the URL, or null if the
105: * digest is invalid (and an error has been sent back to the client)
106: * @throws IOException
107: */
108: private Resource findResourceAndValidateDigest(Response response,
109: String resourcePath) throws IOException {
110: Resource resource = new ClasspathResource(resourcePath);
111:
112: if (!_resourceCache.requiresDigest(resource))
113: return resource;
114:
115: String file = resource.getFile();
116:
117: // Somehow this code got real ugly, but it's all about preventing NPEs when a resource
118: // that should have a digest doesn't.
119:
120: boolean valid = false;
121: Resource result = resource;
122:
123: int lastdotx = file.lastIndexOf('.');
124:
125: if (lastdotx > 0) {
126: int prevdotx = file.lastIndexOf('.', lastdotx - 1);
127:
128: if (prevdotx > 0) {
129:
130: String requestDigest = file.substring(prevdotx + 1,
131: lastdotx);
132:
133: // Strip the digest out of the file name.
134:
135: String realFile = file.substring(0, prevdotx)
136: + file.substring(lastdotx);
137:
138: result = resource.forFile(realFile);
139:
140: String actualDigest = _resourceCache.getDigest(result);
141:
142: valid = requestDigest.equals(actualDigest);
143: }
144: }
145:
146: if (!valid) {
147: response.sendError(HttpServletResponse.SC_FORBIDDEN,
148: ServicesMessages.wrongAssetDigest(result));
149: result = null;
150: }
151:
152: return result;
153: }
154: }
|