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}