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 java.io.File; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.InputStreamReader; 027import java.io.OutputStream; 028import java.io.Reader; 029import java.io.Serializable; 030import java.nio.file.Files; 031 032import org.apache.commons.io.IOUtils; 033import org.apache.commons.lang.ObjectUtils; 034import org.apache.commons.lang.builder.HashCodeBuilder; 035import org.nuxeo.ecm.core.api.Blob; 036import org.nuxeo.ecm.core.api.CloseableFile; 037import org.nuxeo.runtime.api.Framework; 038 039/** 040 * Abstract implementation of a {@link Blob} storing the information other than the byte stream. 041 */ 042public abstract class AbstractBlob implements Blob, Serializable { 043 044 private static final long serialVersionUID = 1L; 045 046 public static final String UTF_8 = "UTF-8"; 047 048 public static final String TEXT_PLAIN = "text/plain"; 049 050 protected String mimeType; 051 052 protected String encoding; 053 054 protected String filename; 055 056 protected String digest; 057 058 @Override 059 public String getMimeType() { 060 return mimeType; 061 } 062 063 @Override 064 public String getEncoding() { 065 return encoding; 066 } 067 068 @Override 069 public String getFilename() { 070 return filename; 071 } 072 073 @Override 074 public String getDigestAlgorithm() { 075 return null; 076 } 077 078 @Override 079 public String getDigest() { 080 return digest; 081 } 082 083 @Override 084 public void setMimeType(String mimeType) { 085 this.mimeType = mimeType; 086 } 087 088 @Override 089 public void setEncoding(String encoding) { 090 this.encoding = encoding; 091 } 092 093 @Override 094 public void setFilename(String filename) { 095 this.filename = filename; 096 } 097 098 @Override 099 public void setDigest(String digest) { 100 this.digest = digest; 101 } 102 103 @Override 104 public File getFile() { 105 return null; 106 } 107 108 @Override 109 public byte[] getByteArray() throws IOException { 110 try (InputStream in = getStream()) { 111 return IOUtils.toByteArray(in); 112 } 113 } 114 115 @Override 116 public String getString() throws IOException { 117 try (Reader reader = new InputStreamReader(getStream(), getEncoding() == null ? UTF_8 : getEncoding())) { 118 return IOUtils.toString(reader); 119 } 120 } 121 122 @Override 123 public long getLength() { 124 return -1; 125 } 126 127 @Override 128 public CloseableFile getCloseableFile() throws IOException { 129 return getCloseableFile(null); 130 } 131 132 @Override 133 public CloseableFile getCloseableFile(String ext) throws IOException { 134 File file = getFile(); 135 if (file != null && (ext == null || file.getName().endsWith(ext))) { 136 return new CloseableFile(file, false); 137 } 138 File tmp = Framework.createTempFile("nxblob-", ext); 139 tmp.delete(); 140 if (file != null) { 141 // attempt to create a symbolic link, which would be cheaper than a copy 142 try { 143 Files.createSymbolicLink(tmp.toPath(), file.toPath().toAbsolutePath()); 144 } catch (IOException | UnsupportedOperationException e) { 145 // symbolic link not supported, do a copy instead 146 Files.copy(file.toPath(), tmp.toPath()); 147 } 148 } else { 149 try (InputStream in = getStream()) { 150 Files.copy(in, tmp.toPath()); 151 } 152 } 153 Framework.trackFile(tmp, tmp); 154 return new CloseableFile(tmp, true); 155 } 156 157 @Override 158 public void transferTo(OutputStream out) throws IOException { 159 try (InputStream in = getStream()) { 160 IOUtils.copy(in, out); 161 } 162 } 163 164 @Override 165 public void transferTo(File file) throws IOException { 166 try (OutputStream out = new FileOutputStream(file)) { 167 transferTo(out); 168 } 169 } 170 171 @Override 172 public boolean equals(Object object) { 173 if (object == this) { 174 return true; 175 } 176 if (!(object instanceof Blob)) { 177 return false; 178 } 179 Blob other = (Blob) object; 180 if (!ObjectUtils.equals(getFilename(), other.getFilename())) { 181 return false; 182 } 183 if (!ObjectUtils.equals(getMimeType(), other.getMimeType())) { 184 return false; 185 } 186 if (!ObjectUtils.equals(getEncoding(), other.getEncoding())) { 187 return false; 188 } 189 // ignore null digests, they are sometimes lazily computed 190 // therefore mutable 191 String digest = getDigest(); 192 String otherDigest = other.getDigest(); 193 if (digest != null && otherDigest != null && !digest.equals(otherDigest)) { 194 return false; 195 } 196 // compare streams 197 return equalsStream(other); 198 } 199 200 // overridden by StorageBlob for improved performance 201 protected boolean equalsStream(Blob other) { 202 InputStream is = null; 203 InputStream ois = null; 204 try { 205 is = getStream(); 206 ois = other.getStream(); 207 return IOUtils.contentEquals(is, ois); 208 } catch (IOException e) { 209 throw new RuntimeException(e); 210 } finally { 211 IOUtils.closeQuietly(is); 212 IOUtils.closeQuietly(ois); 213 } 214 } 215 216 // we don't implement a complex hashCode as we don't expect 217 // to put blobs as hashmap keys 218 @Override 219 public int hashCode() { 220 return new HashCodeBuilder() // 221 .append(getFilename()) // 222 .append(getMimeType()) // 223 .append(getEncoding()) // 224 .toHashCode(); 225 } 226 227}