001/* 002 * Copyright 2019 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.data; 017 018import java.io.ByteArrayInputStream; 019import java.io.ByteArrayOutputStream; 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.FileNotFoundException; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.OutputStream; 027import java.time.ZonedDateTime; 028 029import org.ametys.plugins.repository.AmetysRepositoryException; 030import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 031 032/** 033 * Class representing a resource 034 */ 035public class Resource 036{ 037 /** Empty array */ 038 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 039 040 /** Max size for keeping temporary data in memory */ 041 private static final int MAX_BUFFER_SIZE = 0x10000; 042 043 /** The resource's mime type */ 044 protected String _mimeType; 045 046 /** The resource's encoding */ 047 protected String _encoding; 048 049 /** The resource's last modification date */ 050 protected ZonedDateTime _lastModificationDate; 051 052 /** Underlying tmp file */ 053 protected File _tmpFile; 054 055 /** Buffer for small-sized data */ 056 protected byte[] _buffer = EMPTY_BYTE_ARRAY; 057 058 /** The resource's repository data */ 059 protected RepositoryData _repositoryData; 060 061 /** 062 * Default constructor 063 */ 064 public Resource() 065 { 066 // Empty constructor 067 } 068 069 /** 070 * Constructor to use when reading the resource from the repository 071 * @param repositoryData the repository data containing the resource's data 072 */ 073 public Resource(RepositoryData repositoryData) 074 { 075 _repositoryData = repositoryData; 076 } 077 078 /** 079 * Retrieve the resource's data 080 * If the data has already been read, the stream is reseted so it can be read again from the start 081 * @return the resource's data, or <code>null</code> if there is no data 082 * @throws AmetysRepositoryException if an error occurs while reading the data in the repository 083 */ 084 public InputStream getInputStream() throws AmetysRepositoryException 085 { 086 if (_repositoryData != null) 087 { 088 if (_repositoryData.hasValue("data", "jcr")) 089 { 090 return _repositoryData.getStream("data", "jcr"); 091 } 092 else 093 { 094 return null; 095 } 096 } 097 else if (_tmpFile != null) 098 { 099 try 100 { 101 // this instance is backed by a temp file 102 return new FileInputStream(_tmpFile); 103 } 104 catch (FileNotFoundException e) 105 { 106 return null; 107 } 108 } 109 else if (_buffer.length > 0) 110 { 111 // this instance is backed by an in-memory buffer 112 return new ByteArrayInputStream(_buffer); 113 } 114 else 115 { 116 return null; 117 } 118 } 119 120 /** 121 * Retrieves the length of the resource's data 122 * @return the length of the resource's data 123 * @throws AmetysRepositoryException if an error occurs while reading the data in the repository 124 */ 125 public long getLength() throws AmetysRepositoryException 126 { 127 if (_repositoryData != null) 128 { 129 return _repositoryData.getStreamLength("data", "jcr"); 130 } 131 else if (_tmpFile != null) 132 { 133 return _tmpFile.length(); 134 } 135 else 136 { 137 return _buffer.length; 138 } 139 } 140 141 /** 142 * Sets the resource's data 143 * @param in the data to set 144 * @throws IOException if an error occurs while reading the input stream 145 */ 146 public void setInputStream(InputStream in) throws IOException 147 { 148 byte[] spoolBuffer = new byte[0x2000]; 149 int read; 150 int len = 0; 151 @SuppressWarnings("resource") 152 OutputStream out = null; 153 File spoolFile = null; 154 try 155 { 156 while ((read = in.read(spoolBuffer)) > 0) 157 { 158 if (out != null) 159 { 160 // spool to temporary file 161 out.write(spoolBuffer, 0, read); 162 len += read; 163 } 164 else if (len + read > MAX_BUFFER_SIZE) 165 { 166 // threshold for keeping data in memory exceeded; 167 // create temporary file and spool buffer contents 168 spoolFile = File.createTempFile("bin", null, null); 169 spoolFile.deleteOnExit(); 170 out = new FileOutputStream(spoolFile); 171 out.write(_buffer, 0, len); 172 out.write(spoolBuffer, 0, read); 173 _buffer = null; 174 len += read; 175 } 176 else 177 { 178 // reallocate new buffer and spool old buffer contents 179 byte[] newBuffer = new byte[len + read]; 180 System.arraycopy(_buffer, 0, newBuffer, 0, len); 181 System.arraycopy(spoolBuffer, 0, newBuffer, len, read); 182 _buffer = newBuffer; 183 len += read; 184 } 185 } 186 } 187 finally 188 { 189 if (out != null) 190 { 191 out.close(); 192 } 193 } 194 195 // init fields 196 if (_tmpFile != null) 197 { 198 _tmpFile.delete(); 199 } 200 _tmpFile = spoolFile; 201 _repositoryData = null; 202 } 203 204 /** 205 * Retrieves an output stream that allows to modify the resource's data 206 * @return the output stream 207 */ 208 public OutputStream getOutputStream() 209 { 210 return new ByteArrayOutputStream() 211 { 212 @Override 213 public void close() throws IOException 214 { 215 super.close(); 216 closeOutputStream(this); 217 } 218 }; 219 } 220 221 /** 222 * Closes the given {@link OutputStream} 223 * @param outputStream the {@link OutputStream} to close 224 * @throws IOException if an error occurs while registering the stream 225 */ 226 protected void closeOutputStream(ByteArrayOutputStream outputStream) throws IOException 227 { 228 _buffer = outputStream.toByteArray(); 229 if (_tmpFile != null) 230 { 231 _tmpFile.delete(); 232 } 233 _repositoryData = null; 234 } 235 236 /** 237 * Retrieves the mime type of the resource's data 238 * @return the mime type of the resource's data 239 */ 240 public String getMimeType() 241 { 242 return _mimeType; 243 } 244 245 /** 246 * Sets the mime type of the resource's data 247 * @param mimeType the mime type to set 248 */ 249 public void setMimeType(String mimeType) 250 { 251 _mimeType = mimeType; 252 } 253 254 /** 255 * Retrieves the encoding of the resource's data 256 * @return the encoding of the resource's data 257 */ 258 public String getEncoding() 259 { 260 return _encoding; 261 } 262 263 /** 264 * Sets the encoding of the resource's data 265 * @param encoding the encoding to set 266 */ 267 public void setEncoding(String encoding) 268 { 269 _encoding = encoding; 270 } 271 272 /** 273 * Retrieves the last modification date of the resource's data 274 * @return the last modification date of the resource's data 275 */ 276 public ZonedDateTime getLastModificationDate() 277 { 278 return _lastModificationDate; 279 } 280 281 /** 282 * Sets the last modification date of the resource's data 283 * @param lastModificationDate the last modification date to set 284 */ 285 public void setLastModificationDate(ZonedDateTime lastModificationDate) 286 { 287 _lastModificationDate = lastModificationDate; 288 } 289}