/*
* 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:
* Florent Guillaume, jcarsique
*/
package org.eclipse.ecr.core.storage.sql;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.nuxeo.common.Environment;
import org.eclipse.ecr.core.api.Blob;
import org.eclipse.ecr.core.api.DocumentModel;
import org.eclipse.ecr.core.api.impl.DocumentModelImpl;
import org.eclipse.ecr.core.storage.sql.coremodel.SQLBlob;
import org.eclipse.ecr.core.storage.sql.testlib.SQLRepositoryTestCase;
/**
* Sample test showing how to use a direct access to the binaries storage.
*
* @author Florent Guillaume
*/
public class TestSQLRepositoryDirectBlob extends SQLRepositoryTestCase {
public static final String TEST_BUNDLE = "org.eclipse.ecr.core.storage.sql.test";
public TestSQLRepositoryDirectBlob(String name) {
super(name);
}
@Override
public void setUp() throws Exception {
super.setUp();
deployContrib(TEST_BUNDLE, "OSGI-INF/test-repo-core-types-contrib.xml");
openSession();
}
@Override
public void tearDown() throws Exception {
session.cancel();
closeSession();
super.tearDown();
}
// ----- Third-party application -----
/** The application that creates a file. */
public String createFile() throws Exception {
FileManager fileMaker = new FileManager();
// get the tmp dir where to create files
File tmpDir = fileMaker.getTmpDir();
// third-party application creates a file there
File file = File.createTempFile("myapp", null, tmpDir);
FileOutputStream out = new FileOutputStream(file);
out.write("this is a file".getBytes("UTF-8"));
out.close();
// then it moves the tmp file to the binaries storage, and gets the
// digest
String digest = fileMaker.moveTmpFileToBinaries(file);
return digest;
}
// ----- Nuxeo application -----
public void testDirectBlob() throws Exception {
DocumentModel folder = session.getRootDocument();
DocumentModel file = new DocumentModelImpl(folder.getPathAsString(),
"filea", "File");
file = session.createDocument(file);
session.save();
/*
* 1. A third-party application returns a digest for a created file.
*/
String digest = createFile();
/*
* 2. Later, create and use the blob for this digest.
*/
BinaryManager binaryManager = new DefaultBinaryManager();
binaryManager.initialize(new RepositoryDescriptor());
Binary binary = binaryManager.getBinary(digest);
if (binary == null) {
throw new RuntimeException("Missing file for digest: " + digest);
}
String filename = "doc.txt";
Blob blob = new SQLBlob(binary, filename, "text/plain", "utf-8",
binary.getDigest());
file.setProperty("file", "filename", filename);
file.setProperty("file", "content", blob);
session.saveDocument(file);
session.save();
/*
* 3. Check the retrieved doc.
*/
String expected = "this is a file";
file = session.getDocument(file.getRef());
blob = (Blob) file.getProperty("file", "content");
assertEquals("doc.txt", blob.getFilename());
assertEquals(expected.length(), blob.getLength());
assertEquals("utf-8", blob.getEncoding());
assertEquals("text/plain", blob.getMimeType());
assertEquals(expected, blob.getString());
/*
* remove attached file
*/
file.setProperty("file", "content", null);
file = session.saveDocument(file);
session.save();
assertNull(file.getProperty("file", "content"));
}
public void testBinarySerialization() throws Exception {
DocumentModel folder = session.getRootDocument();
DocumentModel file = new DocumentModelImpl(folder.getPathAsString(),
"filea", "File");
file = session.createDocument(file);
session.save();
// create a binary instance pointing to some content stored on the
// filesystem
String digest = createFile();
BinaryManager binaryManager = new DefaultBinaryManager();
binaryManager.initialize(new RepositoryDescriptor());
Binary binary = binaryManager.getBinary(digest);
if (binary == null) {
throw new RuntimeException("Missing file for digest: " + digest);
}
String expected = "this is a file";
byte[] observedContent = new byte[expected.length()];
assertEquals(digest, binary.getDigest());
assertEquals(expected.length(), binary.getLength());
assertEquals(expected.length(),
binary.getStream().read(observedContent));
assertEquals(expected, new String(observedContent));
// serialize and deserialize the binary instance
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(binary);
out.flush();
out.close();
// Make an input stream from the byte array and read
// a copy of the object back in.
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
bos.toByteArray()));
Binary binaryCopy = (Binary) in.readObject();
observedContent = new byte[expected.length()];
assertEquals(digest, binaryCopy.getDigest());
assertEquals(expected.length(), binaryCopy.getLength());
assertEquals(expected.length(), binaryCopy.getStream().read(
observedContent));
assertEquals(expected, new String(observedContent));
}
}
/**
* Class doing a simplified version of what the binaries storage does.
* <p>
* In a real application, change the constructor to pass the rootDir as a
* parameter or use configuration.
*
* @author Florent Guillaume
*/
class FileManager {
/*
* These parameters have to be the same as the one from the binaries
* storage.
*/
public static final String DIGEST_ALGORITHM = "MD5";
public static final int DEPTH = 2;
protected final File tmpDir;
protected final File dataDir;
public FileManager() {
// from inside Nuxeo components, this can be used
// otherwise use a hardcoded string or parameter to that directory
File rootDir = new File(Environment.getDefault().getData(), "binaries");
tmpDir = new File(rootDir, "tmp");
dataDir = new File(rootDir, "data");
tmpDir.mkdirs();
dataDir.mkdirs();
}
public File getTmpDir() {
return tmpDir;
}
public String moveTmpFileToBinaries(File file) throws IOException {
// digest the file
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw (IOException) new IOException().initCause(e);
}
FileInputStream in = new FileInputStream(file);
try {
byte[] buf = new byte[4096];
int n;
while ((n = in.read(buf)) != -1) {
messageDigest.update(buf, 0, n);
}
} finally {
in.close();
}
String digest = toHexString(messageDigest.digest());
// move the file to its final location
File dest = getFileForDigest(digest, dataDir);
file.renameTo(dest); // atomic move, fails if already there
file.delete(); // fails if the move was successful
if (!dest.exists()) {
throw new IOException("Could not create file: " + dest);
}
return digest;
}
protected static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
protected static String toHexString(byte[] data) {
StringBuilder buf = new StringBuilder(2 * data.length);
for (byte b : data) {
buf.append(HEX_DIGITS[(0xF0 & b) >> 4]);
buf.append(HEX_DIGITS[0x0F & b]);
}
return buf.toString();
}
protected static File getFileForDigest(String digest, File dataDir) {
StringBuilder buf = new StringBuilder(3 * DEPTH - 1);
for (int i = 0; i < DEPTH; i++) {
if (i != 0) {
buf.append(File.separatorChar);
}
buf.append(digest.substring(2 * i, 2 * i + 2));
}
File dir = new File(dataDir, buf.toString());
dir.mkdirs();
return new File(dir, digest);
}
}