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    }