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 */ 083 public InputStream getInputStream() 084 { 085 if (_repositoryData != null) 086 { 087 if (_repositoryData.hasValue("data", "jcr")) 088 { 089 return _repositoryData.getStream("data", "jcr"); 090 } 091 else 092 { 093 return null; 094 } 095 } 096 else if (_tmpFile != null) 097 { 098 try 099 { 100 // this instance is backed by a temp file 101 return new FileInputStream(_tmpFile); 102 } 103 catch (FileNotFoundException e) 104 { 105 return null; 106 } 107 } 108 else if (_buffer.length > 0) 109 { 110 // this instance is backed by an in-memory buffer 111 return new ByteArrayInputStream(_buffer); 112 } 113 else 114 { 115 return null; 116 } 117 } 118 119 /** 120 * Retrieves the length of the resource's data 121 * @return the length of the resource's data 122 * @throws AmetysRepositoryException if an error occurs while reading the data in the repository 123 */ 124 public long getLength() throws AmetysRepositoryException 125 { 126 if (_repositoryData != null) 127 { 128 return _repositoryData.getStreamLength("data", "jcr"); 129 } 130 else if (_tmpFile != null) 131 { 132 return _tmpFile.length(); 133 } 134 else 135 { 136 return _buffer.length; 137 } 138 } 139 140 /** 141 * Sets the resource's data 142 * @param in the data to set 143 * @throws IOException if an error occurs while reading the input stream 144 */ 145 public void setInputStream(InputStream in) throws IOException 146 { 147 byte[] spoolBuffer = new byte[0x2000]; 148 int read; 149 int len = 0; 150 @SuppressWarnings("resource") 151 OutputStream out = null; 152 File spoolFile = null; 153 try 154 { 155 while ((read = in.read(spoolBuffer)) > 0) 156 { 157 if (out != null) 158 { 159 // spool to temporary file 160 out.write(spoolBuffer, 0, read); 161 len += read; 162 } 163 else if (len + read > MAX_BUFFER_SIZE) 164 { 165 // threshold for keeping data in memory exceeded; 166 // create temporary file and spool buffer contents 167 spoolFile = File.createTempFile("bin", null, null); 168 spoolFile.deleteOnExit(); 169 out = new FileOutputStream(spoolFile); 170 out.write(_buffer, 0, len); 171 out.write(spoolBuffer, 0, read); 172 _buffer = null; 173 len += read; 174 } 175 else 176 { 177 // reallocate new buffer and spool old buffer contents 178 byte[] newBuffer = new byte[len + read]; 179 System.arraycopy(_buffer, 0, newBuffer, 0, len); 180 System.arraycopy(spoolBuffer, 0, newBuffer, len, read); 181 _buffer = newBuffer; 182 len += read; 183 } 184 } 185 } 186 finally 187 { 188 if (out != null) 189 { 190 out.close(); 191 } 192 } 193 194 // init fields 195 if (_tmpFile != null) 196 { 197 _tmpFile.delete(); 198 } 199 _tmpFile = spoolFile; 200 _repositoryData = null; 201 } 202 203 /** 204 * Retrieves an output stream that allows to modify the resource's data 205 * @return the output stream 206 */ 207 public OutputStream getOutputStream() 208 { 209 return new ByteArrayOutputStream() 210 { 211 @Override 212 public void close() throws IOException 213 { 214 super.close(); 215 closeOutputStream(this); 216 } 217 }; 218 } 219 220 /** 221 * Closes the given {@link OutputStream} 222 * @param outputStream the {@link OutputStream} to close 223 * @throws IOException if an error occurs while registering the stream 224 */ 225 protected void closeOutputStream(ByteArrayOutputStream outputStream) throws IOException 226 { 227 _buffer = outputStream.toByteArray(); 228 if (_tmpFile != null) 229 { 230 _tmpFile.delete(); 231 } 232 _repositoryData = null; 233 } 234 235 /** 236 * Retrieves the mime type of the resource's data 237 * @return the mime type of the resource's data 238 */ 239 public String getMimeType() 240 { 241 return _mimeType; 242 } 243 244 /** 245 * Sets the mime type of the resource's data 246 * @param mimeType the mime type to set 247 */ 248 public void setMimeType(String mimeType) 249 { 250 _mimeType = mimeType; 251 } 252 253 /** 254 * Retrieves the encoding of the resource's data 255 * @return the encoding of the resource's data 256 */ 257 public String getEncoding() 258 { 259 return _encoding; 260 } 261 262 /** 263 * Sets the encoding of the resource's data 264 * @param encoding the encoding to set 265 */ 266 public void setEncoding(String encoding) 267 { 268 _encoding = encoding; 269 } 270 271 /** 272 * Retrieves the last modification date of the resource's data 273 * @return the last modification date of the resource's data 274 */ 275 public ZonedDateTime getLastModificationDate() 276 { 277 return _lastModificationDate; 278 } 279 280 /** 281 * Sets the last modification date of the resource's data 282 * @param lastModificationDate the last modification date to set 283 */ 284 public void setLastModificationDate(ZonedDateTime lastModificationDate) 285 { 286 _lastModificationDate = lastModificationDate; 287 } 288}