001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.request.resource;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.time.Duration;
022import java.time.Instant;
023import javax.servlet.http.HttpServletResponse;
024import org.apache.wicket.Application;
025import org.apache.wicket.util.lang.Bytes;
026import org.apache.wicket.util.lang.Checks;
027import org.apache.wicket.util.resource.IResourceStream;
028import org.apache.wicket.util.resource.IResourceStreamWriter;
029import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033
034/**
035 * A {@link AbstractResource resource} that loads its data from {@link IResourceStream}
036 */
037public class ResourceStreamResource extends AbstractResource
038{
039        private static final long serialVersionUID = 1L;
040
041        private static final Logger logger = LoggerFactory.getLogger(ResourceStreamResource.class);
042
043        private final IResourceStream stream;
044        private String fileName;
045        private ContentDisposition contentDisposition = ContentDisposition.INLINE;
046        private String textEncoding;
047
048        private Duration cacheDuration;
049
050        /**
051         * Constructor.
052         */
053        public ResourceStreamResource()
054        {
055                this(null);
056        }
057
058        /**
059         * Constructor.
060         * 
061         * @param stream
062         *      the resource stream to read from
063         */
064        public ResourceStreamResource(IResourceStream stream)
065        {
066                this.stream = stream;
067        }
068
069        /**
070         * @param fileName
071         * @return this object, for chaining
072         */
073        public ResourceStreamResource setFileName(String fileName)
074        {
075                this.fileName = fileName;
076                return this;
077        }
078
079        /**
080         * @param contentDisposition
081         * @return this object, for chaining
082         */
083        public ResourceStreamResource setContentDisposition(ContentDisposition contentDisposition)
084        {
085                this.contentDisposition = contentDisposition;
086                return this;
087        }
088
089        /**
090         * @param textEncoding
091         * @return this object, for chaining
092         */
093        public ResourceStreamResource setTextEncoding(String textEncoding)
094        {
095                this.textEncoding = textEncoding;
096                return this;
097        }
098
099        /**
100         * @return the duration for which the resource will be cached by the browser
101         */
102        public Duration getCacheDuration()
103        {
104                return cacheDuration;
105        }
106
107        /**
108         * @param cacheDuration
109         *            the duration for which the resource will be cached by the browser
110         * @return this object, for chaining
111         */
112        public ResourceStreamResource setCacheDuration(Duration cacheDuration)
113        {
114                this.cacheDuration = cacheDuration;
115                return this;
116        }
117
118        /**
119         * Lazy or dynamic initialization of the wrapped IResourceStream(Writer)
120         *
121         * @param attributes
122         *          The request attributes
123         * @return the underlying IResourceStream. May be {@code null}.
124         */
125        protected IResourceStream getResourceStream(Attributes attributes)
126        {
127                return stream;
128        }
129
130        private IResourceStream internalGetResourceStream(Attributes attributes)
131        {
132                final IResourceStream resourceStream = getResourceStream(attributes);
133                Checks.notNull(resourceStream, "%s#getResourceStream(attributes) should not return null!", getClass().getName());
134                return resourceStream;
135        }
136
137        @Override
138        protected ResourceResponse newResourceResponse(Attributes attributes)
139        {
140                final IResourceStream resourceStream = internalGetResourceStream(attributes);
141                ResourceResponse data = new ResourceResponse();
142                Instant lastModifiedTime = resourceStream.lastModifiedTime();
143                if (lastModifiedTime != null)
144                {
145                        data.setLastModified(lastModifiedTime);
146                }
147
148                if (cacheDuration != null)
149                {
150                        data.setCacheDuration(cacheDuration);
151                }
152
153                // performance check; don't bother to do anything if the resource is still cached by client
154                if (data.dataNeedsToBeWritten(attributes))
155                {
156                        InputStream inputStream = null;
157                        if (resourceStream instanceof IResourceStreamWriter == false)
158                        {
159                                try
160                                {
161                                        inputStream = resourceStream.getInputStream();
162                                }
163                                catch (ResourceStreamNotFoundException e)
164                                {
165                                        data.setError(HttpServletResponse.SC_NOT_FOUND);
166                                        close(resourceStream);
167                                }
168                        }
169
170                        data.setContentDisposition(contentDisposition);
171                        Bytes length = resourceStream.length();
172                        if (length != null)
173                        {
174                                data.setContentLength(length.bytes());
175                        }
176                        data.setFileName(fileName);
177
178                        String contentType = resourceStream.getContentType();
179                        if (contentType == null && fileName != null && Application.exists())
180                        {
181                                contentType = Application.get().getMimeType(fileName);
182                        }
183                        data.setContentType(contentType);
184                        data.setTextEncoding(textEncoding);
185
186                        if (resourceStream instanceof IResourceStreamWriter)
187                        {
188                                data.setWriteCallback(new WriteCallback()
189                                {
190                                        @Override
191                                        public void writeData(Attributes attributes) throws IOException
192                                        {
193                                                ((IResourceStreamWriter)resourceStream).write(attributes.getResponse().getOutputStream());
194                                                close(resourceStream);
195                                        }
196                                });
197                        }
198                        else
199                        {
200                                final InputStream s = inputStream;
201                                data.setWriteCallback(new WriteCallback()
202                                {
203                                        @Override
204                                        public void writeData(Attributes attributes) throws IOException
205                                        {
206                                                try
207                                                {
208                                                        writeStream(attributes, s);
209                                                }
210                                                finally
211                                                {
212                                                        close(resourceStream);
213                                                }
214                                        }
215                                });
216                        }
217                }
218
219                return data;
220        }
221
222        private void close(IResourceStream stream)
223        {
224                try
225                {
226                        stream.close();
227                }
228                catch (IOException e)
229                {
230                        logger.error("Couldn't close ResourceStream", e);
231                }
232        }
233}