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.resource;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.nio.file.Files;
022import java.nio.file.Path;
023import java.nio.file.Paths;
024import java.nio.file.attribute.BasicFileAttributes;
025
026import org.apache.wicket.Application;
027import org.apache.wicket.WicketRuntimeException;
028import org.apache.wicket.model.LoadableDetachableModel;
029import org.apache.wicket.request.cycle.RequestCycle;
030import org.apache.wicket.request.resource.AbstractResource;
031import org.apache.wicket.request.resource.PartWriterCallback;
032
033/**
034 * Used to provide resources based on the on Java NIO FileSystem API.<br>
035 * <br>
036 * For more information see {@link FileSystemResourceReference}
037 * 
038 * @author Tobias Soloschenko
039 *
040 */
041public class FileSystemResource extends AbstractResource
042{
043        private static final long serialVersionUID = 1L;
044
045        private LoadableDetachableModel<Path> path;
046
047        /**
048         * Creates a new file system resource based on the given path
049         * 
050         * @param path
051         *            the path to be read for the resource
052         */
053        public FileSystemResource(Path path)
054        {
055                this.path = new PathModel(path);
056        }
057
058        /**
059         * Creates a new file system resource
060         * 
061         */
062        public FileSystemResource()
063        {
064                this(null);
065        }
066
067        /**
068         * Creates a new resource response and reads the given path
069         */
070        @Override
071        protected ResourceResponse newResourceResponse(Attributes attributes)
072        {
073                return createResourceResponse(attributes, getPath());
074        }
075
076        @Override
077        public void respond(Attributes attributes)
078        {
079                try
080                {
081                        super.respond(attributes);
082                }
083                finally
084                {
085                        if (path != null)
086                        {
087                                path.detach();
088                        }
089                }
090        }
091
092        /**
093         * Creates a resource response based on the given attributes
094         * 
095         * @param path
096         *            the path to create the resource response with
097         * @param attributes
098         *            request attributes
099         * @return the actual resource response
100         */
101        protected ResourceResponse createResourceResponse(Attributes attributes, Path path)
102        {
103                try
104                {
105                        if (path == null)
106                        {
107                                throw new WicketRuntimeException(
108                                        "Please override #newResourceResponse() and provide a path if using a constructor which doesn't take one as argument.");
109                        }
110                        this.path = new PathModel(path);
111                        long size = getSize();
112                        ResourceResponse resourceResponse = new ResourceResponse();
113                        resourceResponse.setContentType(getMimeType());
114                        resourceResponse.setAcceptRange(ContentRangeType.BYTES);
115                        resourceResponse.setContentLength(size);
116                        if (path.getFileName() != null) {
117                                resourceResponse.setFileName(path.getFileName().toString());
118                        }
119                        RequestCycle cycle = RequestCycle.get();
120                        Long startbyte = cycle.getMetaData(CONTENT_RANGE_STARTBYTE);
121                        Long endbyte = cycle.getMetaData(CONTENT_RANGE_ENDBYTE);
122                        resourceResponse.setWriteCallback(
123                                new PartWriterCallback(getInputStream(), size, startbyte, endbyte).setClose(true));
124                        return resourceResponse;
125                }
126                catch (IOException e)
127                {
128                        throw new WicketRuntimeException(
129                                "An error occurred while processing the media resource response", e);
130                }
131        }
132
133        /**
134         * Gets the size of the resource
135         * 
136         * @return the size of the resource
137         * @throws IOException
138         *             if the size attribute can't be read
139         */
140        protected long getSize() throws IOException
141        {
142                return Files.readAttributes(getPath(), BasicFileAttributes.class).size();
143        }
144
145        /**
146         * Gets the mime type to be used for the response it first uses the URL connection to get the
147         * mime type and after this the FileTypeDetector SPI is used.
148         * 
149         * @return the mime type to be used for the response
150         * @throws IOException
151         *             if the mime type couldn't be resolved
152         */
153        protected String getMimeType() throws IOException
154        {
155                final Path _path = getPath();
156                String mimeType = null;
157                if (Application.exists())
158                {
159                        mimeType = Application.get().getMimeType(_path.getFileName().toString());
160                }
161                if (mimeType == null)
162                {
163                        mimeType = Files.probeContentType(_path);
164                }
165                return mimeType;
166        }
167
168        /**
169         * Gets the input stream of the given path
170         * 
171         * @return the input stream of the given path
172         * @throws IOException
173         *             if there is an exception while receiving the input stream
174         */
175        protected InputStream getInputStream() throws IOException
176        {
177                return Files.newInputStream(getPath());
178        }
179
180        private Path getPath()
181        {
182                return path.getObject();
183        }
184
185        private static class PathModel extends LoadableDetachableModel<Path>
186        {
187                private static final long serialVersionUID = 1L;
188                private final String pathAsString;
189
190                public PathModel(Path path)
191                {
192                        super(path);
193                        this.pathAsString = path == null ? null : path.toString();
194                }
195
196                @Override
197                protected Path load()
198                {
199                        return pathAsString == null ? null : Paths.get(pathAsString);
200                }
201        }
202}