001/*
002 * (C) Copyright 2006-2018 Nuxeo (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;
031import java.util.Objects;
032
033import org.apache.commons.io.IOUtils;
034import org.apache.commons.lang3.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 (!Objects.equals(getFilename(), other.getFilename())) {
181            return false;
182        }
183        if (!Objects.equals(getMimeType(), other.getMimeType())) {
184            return false;
185        }
186        if (!Objects.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}