001/* 002 * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Bogdan Stefanescu 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.core.api.impl.blob; 021 022import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; 023import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 024 025import java.io.File; 026import java.io.FileInputStream; 027import java.io.FileOutputStream; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.OutputStream; 031import java.io.Serializable; 032import java.nio.file.AtomicMoveNotSupportedException; 033import java.nio.file.Files; 034import java.nio.file.Path; 035 036import org.apache.commons.io.IOUtils; 037import org.nuxeo.ecm.core.api.Blob; 038import org.nuxeo.runtime.api.Framework; 039 040/** 041 * A {@link Blob} backed by a {@link File}. 042 * <p> 043 * The backing file may be in a temporary location, which is the case if this {@link FileBlob} was constructed from an 044 * {@link InputStream} or from a file which was explicitly marked as temporary. In this case, the file may be renamed, 045 * or the file location may be changed to a non-temporary one. 046 */ 047public class FileBlob extends AbstractBlob implements Serializable { 048 049 private static final long serialVersionUID = 1L; 050 051 protected File file; 052 053 protected boolean isTemporary; 054 055 public FileBlob(File file) { 056 this(file, null, null, null, null); 057 } 058 059 public FileBlob(File file, String mimeType) { 060 this(file, mimeType, null, null, null); 061 } 062 063 public FileBlob(File file, String mimeType, String encoding) { 064 this(file, mimeType, encoding, null, null); 065 } 066 067 public FileBlob(File file, String mimeType, String encoding, String filename, String digest) { 068 if (file == null) { 069 throw new NullPointerException("null file"); 070 } 071 this.file = file; 072 this.mimeType = mimeType; 073 this.encoding = encoding; 074 this.digest = digest; 075 this.filename = filename != null ? filename : file.getName(); 076 } 077 078 /** 079 * Creates a {@link FileBlob} from an {@link InputStream}, by saving it to a temporary file. 080 * <p> 081 * The input stream is closed. 082 * 083 * @param in the input stream, which is closed after use 084 */ 085 public FileBlob(InputStream in) throws IOException { 086 this(in, null, null); 087 } 088 089 /** 090 * Creates a {@link FileBlob} from an {@link InputStream}, by saving it to a temporary file. 091 * <p> 092 * The input stream is closed. 093 * 094 * @param in the input stream, which is closed after use 095 * @param mimeType the MIME type 096 */ 097 public FileBlob(InputStream in, String mimeType) throws IOException { 098 this(in, mimeType, null); 099 } 100 101 /** 102 * Creates a {@link FileBlob} from an {@link InputStream}, by saving it to a temporary file. 103 * <p> 104 * The input stream is closed. 105 * 106 * @param in the input stream, which is closed after use 107 * @param mimeType the MIME type 108 * @param encoding the encoding 109 */ 110 public FileBlob(InputStream in, String mimeType, String encoding) throws IOException { 111 this(in, mimeType, encoding, null); 112 } 113 114 /** 115 * Creates a {@link FileBlob} from an {@link InputStream}, by saving it to a temporary file. 116 * <p> 117 * The input stream is closed. 118 * 119 * @param in the input stream, which is closed after use 120 * @param mimeType the MIME type 121 * @param encoding the encoding 122 * @param tmpDir the temporary directory for file creation 123 */ 124 public FileBlob(InputStream in, String mimeType, String encoding, File tmpDir) throws IOException { 125 if (in == null) { 126 throw new NullPointerException("null inputstream"); 127 } 128 this.mimeType = mimeType; 129 this.encoding = encoding; 130 isTemporary = true; 131 try { 132 file = File.createTempFile("nxblob-", ".tmp", tmpDir); 133 Framework.trackFile(file, file); 134 filename = file.getName(); 135 try (OutputStream out = new FileOutputStream(file)) { 136 IOUtils.copy(in, out); 137 } 138 } finally { 139 IOUtils.closeQuietly(in); 140 } 141 } 142 143 /** 144 * Creates a {@link FileBlob} with an empty temporary file with the given extension. 145 * 146 * @param ext the temporary file extension 147 * @return a file blob 148 * @since 7.2 149 */ 150 public FileBlob(String ext) throws IOException { 151 isTemporary = true; 152 file = Framework.createTempFile("nxblob-", ext); 153 Framework.trackFile(file, file); 154 filename = file.getName(); 155 } 156 157 @Override 158 public File getFile() { 159 return file; 160 } 161 162 @Override 163 public long getLength() { 164 return file.length(); 165 } 166 167 @Override 168 public InputStream getStream() throws IOException { 169 return new FileInputStream(file); 170 } 171 172 /** 173 * Checks whether this {@link FileBlob} is backed by a temporary file. 174 * 175 * @since 7.2 176 */ 177 public boolean isTemporary() { 178 return isTemporary; 179 } 180 181 /** 182 * Moves this blob's temporary file to a new non-temporary location. 183 * <p> 184 * The move is done as atomically as possible. 185 * 186 * @since 7.2 187 */ 188 public void moveTo(File dest) throws IOException { 189 if (!isTemporary) { 190 throw new IOException("Cannot move non-temporary file: " + file); 191 } 192 Path path = file.toPath(); 193 Path destPath = dest.toPath(); 194 try { 195 Files.move(path, destPath, ATOMIC_MOVE); 196 file = dest; 197 } catch (AtomicMoveNotSupportedException e) { 198 // Do a copy through a tmp file on the same filesystem then atomic rename 199 Path tmp = Files.createTempFile(destPath.getParent(), null, null); 200 try { 201 Files.copy(path, tmp, REPLACE_EXISTING); 202 Files.delete(path); 203 Files.move(tmp, destPath, ATOMIC_MOVE); 204 file = dest; 205 } catch (IOException ioe) { 206 // don't leave tmp file in case of error 207 Files.deleteIfExists(tmp); 208 throw ioe; 209 } 210 } 211 isTemporary = false; 212 } 213 214}