org.nuxeo.ecm.core.TestSQLRepositoryDirectBlob.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.core.TestSQLRepositoryDirectBlob.java

Source

/*
 * 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.nuxeo.ecm.core;

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.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.After;
import org.junit.Test;

import static org.junit.Assert.*;

import org.nuxeo.common.Environment;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
import org.nuxeo.ecm.core.api.impl.blob.StreamingBlob;
import org.nuxeo.ecm.core.storage.StorageBlob;
import org.nuxeo.ecm.core.storage.binary.Binary;
import org.nuxeo.ecm.core.storage.binary.BinaryManager;
import org.nuxeo.ecm.core.storage.binary.BinaryManagerDescriptor;
import org.nuxeo.ecm.core.storage.binary.BinaryManagerService;
import org.nuxeo.ecm.core.storage.binary.DefaultBinaryManager;
import org.nuxeo.ecm.core.storage.binary.LocalBinaryManager;
import org.nuxeo.ecm.core.storage.sql.SQLRepositoryTestCase;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.services.streaming.FileSource;

/**
 * Sample test showing how to use a direct access to the binaries storage.
 *
 * @author Florent Guillaume
 */
public class TestSQLRepositoryDirectBlob extends SQLRepositoryTestCase {

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();
        deployContrib("org.nuxeo.ecm.core.test.tests", "OSGI-INF/test-repo-core-types-contrib.xml");
        openSession();
    }

    @Override
    @After
    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 -----
    @Test
    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 BinaryManagerDescriptor());
        Binary binary = binaryManager.getBinary(digest);
        assertNotNull("Missing file for digest: " + digest, binary);
        String filename = "doc.txt";
        Blob blob = new StorageBlob(binary, filename, "text/plain", "utf-8", binary.getDigest(),
                binary.getLength());
        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"));

        binaryManager.close();
    }

    @Test
    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 BinaryManagerDescriptor());
        Binary binary = binaryManager.getBinary(digest);
        assertNotNull("Missing file for digest: " + digest, binary);

        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));

        binaryManager.close();
    }

    protected static class TmpStreamingBlob extends StreamingBlob {
        private static final long serialVersionUID = 1L;

        public TmpStreamingBlob(FileSource src) {
            super(src);
        }

        @Override
        public boolean isTemporary() {
            return true;
        }
    }

    @Test
    public void testBinaryManagerTmpFileMoveNotCopy() throws Exception {
        BinaryManagerService bms = Framework.getLocalService(BinaryManagerService.class);
        LocalBinaryManager binaryManager = (LocalBinaryManager) bms.getBinaryManager(session.getRepositoryName());

        // tmp file in binary manager filesystem (not in tmp but still works)
        File file = File.createTempFile("test-", ".data", binaryManager.getStorageDir());
        FileOutputStream out = new FileOutputStream(file);
        IOUtils.copy(new ByteArrayInputStream("abcd\n".getBytes("UTF-8")), out);
        out.close();

        // create blob
        FileSource fileSource = new FileSource(file);
        Blob blob = new TmpStreamingBlob(fileSource);

        // set in doc
        DocumentModel doc = new DocumentModelImpl("/", "myfile", "File");
        doc.setPropertyValue("file:content", (Serializable) blob);
        doc = session.createDocument(doc);
        session.save();

        assertFalse(file.exists());
        assertTrue(fileSource.getFile().exists());
    }

}

/**
 * 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);
    }

}