001 // Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012 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.setStatus(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 }