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
015package org.apache.tapestry5.internal.services;
016
017import org.apache.tapestry5.SymbolConstants;
018import org.apache.tapestry5.internal.IOOperation;
019import org.apache.tapestry5.internal.InternalConstants;
020import org.apache.tapestry5.internal.TapestryInternalUtils;
021import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
022import org.apache.tapestry5.ioc.OperationTracker;
023import org.apache.tapestry5.ioc.Resource;
024import org.apache.tapestry5.ioc.annotations.Symbol;
025import org.apache.tapestry5.services.Request;
026import org.apache.tapestry5.services.Response;
027import org.apache.tapestry5.services.ResponseCompressionAnalyzer;
028import org.apache.tapestry5.services.assets.CompressionStatus;
029import org.apache.tapestry5.services.assets.StreamableResource;
030import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
031import org.apache.tapestry5.services.assets.StreamableResourceSource;
032
033import javax.servlet.http.HttpServletResponse;
034import java.io.IOException;
035import java.io.OutputStream;
036
037public 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}