    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    }