001 // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.tapestry5.internal.services;
016
017 import org.apache.tapestry5.SymbolConstants;
018 import org.apache.tapestry5.internal.IOOperation;
019 import org.apache.tapestry5.internal.InternalConstants;
020 import org.apache.tapestry5.internal.TapestryInternalUtils;
021 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
022 import org.apache.tapestry5.ioc.OperationTracker;
023 import org.apache.tapestry5.ioc.Resource;
024 import org.apache.tapestry5.ioc.annotations.Symbol;
025 import org.apache.tapestry5.services.Request;
026 import org.apache.tapestry5.services.Response;
027 import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
028 import org.apache.tapestry5.services.assets.CompressionStatus;
029 import org.apache.tapestry5.services.assets.StreamableResource;
030 import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
031 import org.apache.tapestry5.services.assets.StreamableResourceSource;
032
033 import javax.servlet.http.HttpServletResponse;
034 import java.io.IOException;
035 import java.io.OutputStream;
036
037 public class ResourceStreamerImpl implements ResourceStreamer
038 {
039 static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since";
040
041 private final Request request;
042
043 private final Response response;
044
045 private final StreamableResourceSource streamableResourceSource;
046
047 private final ResponseCompressionAnalyzer analyzer;
048
049 private final boolean productionMode;
050
051 private final OperationTracker tracker;
052
053 private final ResourceChangeTracker resourceChangeTracker;
054
055 public ResourceStreamerImpl(Request request,
056
057 Response response,
058
059 StreamableResourceSource streamableResourceSource,
060
061 ResponseCompressionAnalyzer analyzer,
062
063 OperationTracker tracker,
064
065 @Symbol(SymbolConstants.PRODUCTION_MODE)
066 boolean productionMode, ResourceChangeTracker resourceChangeTracker)
067 {
068 this.request = request;
069 this.response = response;
070 this.streamableResourceSource = streamableResourceSource;
071
072 this.analyzer = analyzer;
073 this.tracker = tracker;
074 this.productionMode = productionMode;
075 this.resourceChangeTracker = resourceChangeTracker;
076 }
077
078 public void streamResource(final Resource resource) throws IOException
079 {
080 if (!resource.exists())
081 {
082 response.sendError(HttpServletResponse.SC_NOT_FOUND, ServicesMessages.assetDoesNotExist(resource));
083 return;
084 }
085
086 TapestryInternalUtils.performIO(tracker, String.format("Streaming %s", resource), new IOOperation()
087 {
088 public void perform() throws IOException
089 {
090 StreamableResourceProcessing processing = analyzer.isGZipSupported() ? StreamableResourceProcessing.COMPRESSION_ENABLED
091 : StreamableResourceProcessing.COMPRESSION_DISABLED;
092
093 StreamableResource streamable = streamableResourceSource.getStreamableResource(resource, processing, resourceChangeTracker);
094
095 streamResource(streamable);
096 }
097 });
098 }
099
100 public void streamResource(StreamableResource streamable) throws IOException
101 {
102 long lastModified = streamable.getLastModified();
103
104 long ifModifiedSince = 0;
105
106 try
107 {
108 ifModifiedSince = request.getDateHeader(IF_MODIFIED_SINCE_HEADER);
109 } catch (IllegalArgumentException ex)
110 {
111 // Simulate the header being missing if it is poorly formatted.
112
113 ifModifiedSince = -1;
114 }
115
116 if (ifModifiedSince > 0)
117 {
118 if (ifModifiedSince >= lastModified)
119 {
120 response.sendError(HttpServletResponse.SC_NOT_MODIFIED, "");
121 return;
122 }
123 }
124
125 // Prevent the upstream code from compressing when we don't want to.
126
127 response.disableCompression();
128
129 response.setDateHeader("Last-Modified", lastModified);
130
131 if (productionMode)
132 {
133 response.setDateHeader("Expires", lastModified + InternalConstants.TEN_YEARS);
134 }
135
136 response.setContentLength(streamable.getSize());
137
138 if (streamable.getCompression() == CompressionStatus.COMPRESSED)
139 {
140 response.setHeader(InternalConstants.CONTENT_ENCODING_HEADER, InternalConstants.GZIP_CONTENT_ENCODING);
141 }
142
143 OutputStream os = response.getOutputStream(streamable.getContentType());
144
145 streamable.streamTo(os);
146
147 os.close();
148 }
149 }