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