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