/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id$
*/
package org.eclipse.ecr.core.api.impl.blob;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.api.Blob;
import org.eclipse.ecr.core.api.ClientException;
import org.eclipse.ecr.core.api.CoreInstance;
import org.eclipse.ecr.core.api.CoreSession;
import org.eclipse.ecr.runtime.api.Framework;
import org.eclipse.ecr.runtime.services.streaming.StreamManager;
import org.eclipse.ecr.runtime.services.streaming.StreamSource;
import org.nuxeo.common.utils.FileUtils;
/**
* TODO: describe what this blob is and does.
* <p>
* This blob has the limitation you will find in all stream blobs.
* <p>
* Once the stream was acquired there is no more guarantee that another
* getStream() call will return a valid stream.
* <p>
* This could be fixed by using a temp file to store the stream.
*
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class LazyBlob extends DefaultStreamBlob implements Serializable {
private static final long serialVersionUID = -6138173743804682559L;
private static final Log log = LogFactory.getLog(LazyBlob.class);
public static final InputStream EMPTY_INPUT_STREAM = new EmptyInputStream();
private static final Random RANDOM = new Random();
private static final File TMP_DIR;
// the session id
private String sid;
// the repository name
private final String repositoryName;
// the content property path
private final String dataKey;
private transient InputStream in;
private transient File file;
private final long length;
static {
TMP_DIR = new File(Framework.getRuntime().getHome(), "tmp/blobs");
TMP_DIR.mkdirs();
}
public LazyBlob(InputStream in, String encoding, String mimeType,
String sid, String dataKey, String repositoryName, String filename,
String digest, long length) {
this.in = in == null ? EMPTY_INPUT_STREAM : in;
this.encoding = encoding;
this.mimeType = mimeType;
this.sid = sid;
this.dataKey = dataKey;
this.repositoryName = repositoryName;
this.filename = filename;
this.digest = digest;
this.length = length;
}
public LazyBlob(InputStream in, String encoding, String mimeType,
String sid, String dataKey, String repositoryName) {
this(in, encoding, mimeType, sid, dataKey, repositoryName, null, null, -1);
}
@Override
public long getLength() {
return length;
}
public String getSid() {
return sid;
}
public String getDataKey() {
return dataKey;
}
public String getRepositoryName() {
return repositoryName;
}
public void reset() {
FileUtils.close(in);
in = null;
}
/**
* Returns a Nuxeo Core session.
* <p>
* XXX: complete this sentence.
* core instance and grab the content back. Thus this method will try out to
* create a new session if the sid is not accurate anymore. If the session
* has been disconnect it won't be possible to reconnect on a XXX(???).
*
* @return a Nuxeo Core session instance.
* @throws ClientException
*/
private CoreSession getClient() throws ClientException {
CoreSession client = null;
if (sid != null) {
client = CoreInstance.getInstance().getSession(sid);
}
if (client == null) {
if (repositoryName != null) {
// We Will use the null value as a flag to know if whether or
// not we will have to close the new opened connection.
sid = null;
Map<String, Serializable> ctx = new HashMap<String, Serializable>();
client = CoreInstance.getInstance().open(repositoryName, ctx);
} else {
throw new ClientException(
"Cannot reconnect to a Nuxeo core instance... No repository name provided...");
}
}
return client;
}
@Override
public InputStream getStream() throws IOException {
// Get the client.
CoreSession client;
try {
client = getClient();
} catch (ClientException ce) {
throw new IOException(ce.getMessage());
}
if (in == null) {
// this should be a remote invocation
StreamManager sm = Framework.getLocalService(StreamManager.class);
String uri = null;
try {
if (sm == null) {
throw new IOException("No Streaming service was registered");
}
uri = client.getStreamURI(dataKey);
StreamSource src = sm.getStream(uri);
file = new File(TMP_DIR, Long.toHexString(RANDOM.nextLong()));
file.deleteOnExit();
src.copyTo(file); // persist the content
in = new FileInputStream(file);
} catch (IOException e) {
throw e;
} catch (Exception e) {
log.error(e);
throw new IOException("Failed to load lazy content for: "
+ dataKey);
} finally {
// destroy the remote blob and close any opened stream on the server
if (uri != null) {
sm.removeStream(uri); // destroy the remote stream
}
}
} else if (file != null) {
in = new FileInputStream(file);
}
// Close the session because it means we opened a new one.
if (sid == null) {
CoreInstance.getInstance().close(client);
}
return in;
}
@Override
public boolean isPersistent() {
return false;
}
@Override
public Blob persist() throws IOException {
// NXP-3190: fetch it first in case it's not initialized
if (in == null) {
getStream();
}
// optimize -> when the underlying stream is an SerializableINputStream
// this can be optimized by reusing the temp file
// of the underlying stream
return new FileBlob(in, mimeType, encoding);
}
@Override
protected void finalize() {
if (file != null) {
FileUtils.close(in);
file.delete();
}
}
protected static boolean equalValues(Object first, Object second) {
if (first == null) {
return second == null;
} else {
return first.equals(second);
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof LazyBlob)) {
return false;
}
LazyBlob other = (LazyBlob) obj;
boolean encodingEquals = equalValues(encoding, other.encoding);
if (!encodingEquals) {
return false;
}
boolean mimetypeEquals = equalValues(mimeType, other.mimeType);
if (!mimetypeEquals) {
return false;
}
boolean sidEquals = equalValues(sid, other.sid);
if (!sidEquals) {
return false;
}
boolean dataKeyEquals = equalValues(dataKey, other.dataKey);
if (!dataKeyEquals) {
return false;
}
return equalValues(repositoryName, other.repositoryName);
}
@Override
public int hashCode() {
int result = sid != null ? sid.hashCode() : 0;
result = 31 * result + (repositoryName != null ? repositoryName.hashCode() : 0);
result = 31 * result + (dataKey != null ? dataKey.hashCode() : 0);
result = 31 * result + (encoding != null ? encoding.hashCode() : 0);
result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0);
return result;
}
// private void readObject(ObjectInputStream in)
// throws ClassNotFoundException, IOException {
// in.defaultReadObject();
// }
//
// private void writeObject(ObjectOutputStream out) throws IOException {
// out.defaultWriteObject();
// }
public static class EmptyInputStream extends InputStream {
@Override
public int read() throws IOException {
return -1;
}
}
}