001/*
002 * Copyright (c) 2006-2014 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 *     Nuxeo - initial API and implementation
011 *
012 */
013
014package org.nuxeo.ecm.core.api;
015
016import java.io.BufferedInputStream;
017import java.io.ByteArrayInputStream;
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.FileOutputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.ObjectInputStream;
024import java.io.ObjectOutputStream;
025import java.io.OutputStream;
026import java.io.Serializable;
027
028import org.apache.commons.io.IOUtils;
029import org.nuxeo.runtime.api.Framework;
030
031/**
032 * A serializable input stream.
033 * <p>
034 * Note: The stream is closed after the object is serialized.
035 *
036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
037 */
038public class SerializableInputStream extends InputStream implements Serializable {
039
040    public static final int IN_MEM_LIMIT = 1024 * 64;
041
042    private static final long serialVersionUID = -2816387281878881614L;
043
044    private transient File file;
045
046    private transient InputStream in;
047
048    public SerializableInputStream(InputStream in) {
049        this.in = in;
050    }
051
052    public SerializableInputStream(byte[] content) {
053        in = new ByteArrayInputStream(content);
054    }
055
056    public SerializableInputStream(String content) {
057        this(content.getBytes());
058    }
059
060    @Override
061    public int available() throws IOException {
062        return in.available();
063    }
064
065    @Override
066    public void close() throws IOException {
067        in.close();
068    }
069
070    @Override
071    public synchronized void mark(int readlimit) {
072        in.mark(readlimit);
073    }
074
075    @Override
076    public boolean markSupported() {
077        return in.markSupported();
078    }
079
080    @Override
081    public int read() throws IOException {
082        return in.read();
083    }
084
085    @Override
086    public int read(byte[] b, int off, int len) throws IOException {
087        return in.read(b, off, len);
088    }
089
090    @Override
091    public int read(byte[] b) throws IOException {
092        return in.read(b);
093    }
094
095    @Override
096    public synchronized void reset() throws IOException {
097        in.reset();
098    }
099
100    public File getTempFile() {
101        return file;
102    }
103
104    public InputStream reopen() throws IOException {
105        if (!canReopen()) {
106            throw new IOException("Cannot reopen non persistent stream");
107        }
108        return new BufferedInputStream(new FileInputStream(file));
109    }
110
111    public boolean canReopen() {
112        return file != null && file.isFile();
113    }
114
115    @Override
116    public long skip(long n) throws IOException {
117        return in.skip(n);
118    }
119
120    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
121        // always perform the default de-serialization first
122        in.defaultReadObject();
123        // create a temp file where we will put the blob content
124        file = File.createTempFile("SerializableIS-", ".tmp");
125        Framework.trackFile(file, file);
126        OutputStream out = null;
127        try {
128            out = new FileOutputStream(file);
129            byte[] buffer = new byte[IN_MEM_LIMIT];
130            int read;
131            int bytes = in.readInt();
132            while (bytes > -1 && (read = in.read(buffer, 0, bytes)) != -1) {
133                out.write(buffer, 0, read);
134                bytes -= read;
135                if (bytes == 0) {
136                    bytes = in.readInt();
137                }
138            }
139        } finally {
140            if (out != null) {
141                out.close();
142            }
143            if (file.isFile()) {
144                this.in = new BufferedInputStream(new FileInputStream(file));
145            }
146        }
147    }
148
149    private void writeObject(ObjectOutputStream out) throws IOException {
150        out.defaultWriteObject();
151        // write content
152        if (in == null) {
153            return;
154        }
155        try {
156            int read;
157            byte[] buf = new byte[IN_MEM_LIMIT];
158            while ((read = in.read(buf)) != -1) {
159                // next follows a chunk of 'read' bytes
160                out.writeInt(read);
161                out.write(buf, 0, read);
162            }
163            out.writeInt(-1); // EOF
164        } finally {
165            if (in != null) {
166                in.close();
167            }
168        }
169    }
170
171    @Override
172    protected void finalize() throws Throwable {
173        IOUtils.closeQuietly(in);
174        if (file != null) {
175            file.delete();
176        }
177        super.finalize();
178    }
179
180}