com.aperigeek.dropvault.web.dao.MongoFileService.java Source code

Java tutorial

Introduction

Here is the source code for com.aperigeek.dropvault.web.dao.MongoFileService.java

Source

/*  
 * This file is part of dropvault.
 *
 * dropvault is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * dropvault is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with dropvault.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aperigeek.dropvault.web.dao;

import com.aperigeek.dropvault.web.beans.Resource;
import com.aperigeek.dropvault.web.service.ContentExtractionService;
import com.aperigeek.dropvault.web.service.FileTypeDetectionService;
import com.aperigeek.dropvault.web.service.IndexException;
import com.aperigeek.dropvault.web.service.IndexService;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
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.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.security.KeyStore;
import java.security.KeyStore.SecretKeyEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import org.apache.commons.io.input.TeeInputStream;
import org.bson.types.ObjectId;

/**
 *
 * @author Vivien Barousse
 */
@Stateless
public class MongoFileService {

    // TODO: export to configuration file
    private static final File storageFolder = new File("/home/dropvault/storage");

    private static final File secretsFolder = new File("/home/dropvault/secret");

    static {
        if (!storageFolder.exists()) {
            storageFolder.mkdirs();
        }
        if (!secretsFolder.exists()) {
            secretsFolder.mkdirs();
        }
    }

    @EJB
    private MongoService mongo;

    @EJB
    private FileTypeDetectionService fileTypeDetectionService;

    @EJB
    private ContentExtractionService extractionService;

    @EJB
    private IndexService indexService;

    public Resource getRootFolder(String username) {
        DBCollection files = mongo.getDataBase().getCollection("files");

        DBObject filter = new BasicDBObject();
        filter.put("user", username);
        filter.put("root", true);

        DBObject root = files.findOne(filter);

        if (root == null) {
            BasicDBObject newRoot = new BasicDBObject();
            newRoot.append("type", Resource.ResourceType.FOLDER.toString());
            newRoot.append("name", username);
            newRoot.append("root", true);
            newRoot.append("user", username);
            newRoot.append("creationDate", new Date());
            newRoot.append("modificationDate", new Date());
            files.insert(newRoot);
            root = newRoot;
        }

        Resource res = buildResource(root);

        return res;
    }

    public List<Resource> getChildren(Resource resource) {
        DBCollection col = mongo.getDataBase().getCollection("files");

        DBObject filter = new BasicDBObject();
        filter.put("parent", resource.getId());

        List<Resource> children = new ArrayList<Resource>();

        DBCursor cursor = col.find(filter);
        while (cursor.hasNext()) {
            children.add(buildResource(cursor.next()));
        }

        return children;
    }

    // TODO: implement support for ..
    public Resource getChild(Resource resource, String name) {
        if ("".equals(name) || ".".equals(name)) {
            return resource;
        }

        DBCollection col = mongo.getDataBase().getCollection("files");

        DBObject filter = new BasicDBObject();
        filter.put("name", name);
        filter.put("parent", resource.getId());

        DBObject child = col.findOne(filter);

        Resource childRes = buildResource(child);

        return childRes;
    }

    // TODO: implement support for ..
    public Resource getResourceAt(Resource parent, String... path) throws ResourceNotFoundException {
        DBCollection col = mongo.getDataBase().getCollection("files");

        DBObject obj = null;
        ObjectId id = parent.getId();

        for (String name : path) {
            if ("".equals(name) || ".".equals(name)) {
                continue;
            }

            DBObject filter = new BasicDBObjectBuilder().add("name", name).add("parent", id).get();
            obj = col.findOne(filter);

            if (obj == null) {
                throw new ResourceNotFoundException();
            }

            id = (ObjectId) obj.get("_id");
        }

        return buildResource(obj);
    }

    public Resource mkcol(String username, String resource)
            throws ResourceAlreadyExistsException, ResourceNotFoundException {
        String[] path = resource.split("/");
        Resource parent = getResourceAt(getRootFolder(username), Arrays.copyOfRange(path, 0, path.length - 2));

        if (getChild(parent, path[path.length - 1]) != null) {
            throw new ResourceAlreadyExistsException();
        }

        DBCollection col = mongo.getDataBase().getCollection("files");

        DBObject obj = new BasicDBObject();
        obj.put("type", Resource.ResourceType.FOLDER.toString());
        obj.put("user", username);
        obj.put("name", path[path.length - 1]);
        obj.put("creationDate", new Date());
        obj.put("modificationDate", new Date());
        obj.put("parent", parent.getId());

        col.insert(obj);

        col.update(new BasicDBObject("_id", parent.getId()),
                new BasicDBObject("$set", new BasicDBObject("modificationDate", new Date())));

        return buildResource(obj);
    }

    public Resource getResource(String id) {
        ObjectId oid = new ObjectId(id);
        DBObject query = new BasicDBObject("_id", oid);
        DBCollection files = mongo.getDataBase().getCollection("files");
        DBObject result = files.findOne(query);
        return buildResource(result);
    }

    public Resource getResource(String username, String resource) throws ResourceNotFoundException {
        String[] path = resource.split("/");
        Resource parent = getResourceAt(getRootFolder(username), path);
        return parent;
    }

    public Resource getParent(Resource res) {
        DBCollection files = mongo.getDataBase().getCollection("files");
        DBObject resQuery = new BasicDBObject("_id", res.getId());
        DBObject dbRes = files.findOne(resQuery);
        DBObject dbParent = files.findOne(new BasicDBObject("_id", dbRes.get("parent")));
        return buildResource(dbParent);
    }

    public void put(final String username, String resource, InputStream data, long length, String contentType,
            final char[] password) throws ResourceNotFoundException, IOException {

        final String[] path = resource.split("/");
        Resource parent = getResourceAt(getRootFolder(username), Arrays.copyOfRange(path, 0, path.length - 2));

        DBCollection files = mongo.getDataBase().getCollection("files");
        DBCollection contents = mongo.getDataBase().getCollection("contents");

        ContentDetector contentDetector = null;
        if (contentType == null) {
            PipedInputStream pipeIn = new PipedInputStream();
            PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);
            TeeInputStream tee = new TeeInputStream(data, pipeOut, true);
            contentDetector = new ContentDetector(path[path.length - 1], pipeIn);
            contentDetector.start();
            data = tee;
        }

        final File dataFile = createDataFile(data, username, password);

        if (contentDetector != null) {
            try {
                contentDetector.join();
                contentType = contentDetector.getContentType();
            } catch (InterruptedException ex) {
                Logger.getLogger(MongoFileService.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        Resource child = getChild(parent, path[path.length - 1]);
        if (child != null) {
            DBObject filter = new BasicDBObject();
            filter.put("_id", child.getId());
            DBObject update = new BasicDBObject("modificationDate", new Date());
            update.put("contentLength", length);
            update.put("contentType", contentType);
            files.update(filter, new BasicDBObject("$set", update));

            contents.update(new BasicDBObject("resource", child.getId()),
                    new BasicDBObject("$set", new BasicDBObject("file", dataFile.getAbsolutePath())));
        } else {
            DBObject childObj = new BasicDBObject();
            ObjectId objId = new ObjectId();
            childObj.put("_id", objId);
            childObj.put("user", username);
            childObj.put("name", path[path.length - 1]);
            childObj.put("parent", parent.getId());
            childObj.put("type", Resource.ResourceType.FILE.toString());
            childObj.put("creationDate", new Date());
            childObj.put("modificationDate", new Date());
            childObj.put("contentType", contentType);
            childObj.put("contentLength", length);

            files.insert(childObj);

            DBObject content = new BasicDBObject();
            content.put("resource", objId);
            content.put("file", dataFile.getAbsolutePath());

            contents.insert(content);

            files.update(new BasicDBObject("_id", parent.getId()),
                    new BasicDBObject("$set", new BasicDBObject("modificationDate", new Date())));

            child = buildResource(childObj);
        }

        final String fContentType = contentType;
        final Resource fChild = child;
        new Thread() {
            public void run() {
                try {
                    Map<String, String> metadata = extractionService.extractContent(path[path.length - 1],
                            readFile(dataFile, username, password), fContentType);

                    metadata.put("name", path[path.length - 1]);

                    indexService.remove(username, new String(password), fChild.getId().toString());
                    indexService.index(username, new String(password), fChild.getId().toString(), metadata);
                } catch (Exception ex) {
                    Logger.getLogger(MongoFileService.class.getName()).log(Level.SEVERE,
                            "Index failed for " + path[path.length - 1], ex);
                }
            }
        }.start();
    }

    public void move(String username, Resource source, String dest) throws ResourceNotFoundException {
        String[] path = dest.split("/");
        Resource parent = getRootFolder(username);
        for (int i = 0; i < path.length - 1; i++) {
            parent = getChild(parent, path[i]);
            if (parent == null) {
                throw new ResourceNotFoundException();
            }
        }

        DBCollection files = mongo.getDataBase().getCollection("files");

        DBObject update = new BasicDBObject("$set", new BasicDBObjectBuilder().append("parent", parent.getId())
                .append("name", path[path.length - 1]).get());

        DBObject filter = new BasicDBObject("_id", source.getId());

        DBObject current = files.findOne(filter);
        files.update(new BasicDBObject("_id", (ObjectId) current.get("parent")),
                new BasicDBObject("$set", new BasicDBObject("modificationDate", new Date())));

        files.update(filter, update);

        files.update(new BasicDBObject("_id", parent.getId()),
                new BasicDBObject("$set", new BasicDBObject("modificationDate", new Date())));
    }

    public InputStream get(String username, Resource resource, char[] password) throws IOException {
        DBCollection col = mongo.getDataBase().getCollection("contents");

        DBObject filter = new BasicDBObject();
        filter.put("resource", resource.getId());

        DBObject result = col.findOne(filter);
        if (result.containsField("file")) {
            String fileName = (String) result.get("file");
            File dataFile = new File(fileName);
            return readFile(dataFile, username, password);
        } else {
            byte[] binary = (byte[]) result.get("binary");
            return new ByteArrayInputStream(binary);
        }
    }

    public void delete(String username, String password, Resource resource) {
        DBCollection files = mongo.getDataBase().getCollection("files");
        DBCollection contents = mongo.getDataBase().getCollection("contents");

        for (Resource child : getChildren(resource)) {
            delete(username, password, child);
        }

        DBObject filter = new BasicDBObject("_id", resource.getId());

        DBObject current = files.findOne(filter);
        files.update(new BasicDBObject("_id", (ObjectId) current.get("parent")),
                new BasicDBObject("$set", new BasicDBObject("modificationDate", new Date())));

        files.remove(filter);
        contents.remove(new BasicDBObject("resource", resource.getId()));

        try {
            indexService.remove(username, password, resource.getId().toString());
        } catch (IndexException ex) {
            Logger.getLogger(MongoFileService.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    protected Resource buildResource(DBObject obj) {
        if (obj == null) {
            return null;
        }

        Resource childRes = new Resource((ObjectId) obj.get("_id"), (String) obj.get("name"),
                (Date) obj.get("creationDate"), (Date) obj.get("modificationDate"));

        if ("FILE".equals(obj.get("type"))) {
            childRes.setType(Resource.ResourceType.FILE);
            childRes.setContentLength(((Number) obj.get("contentLength")).intValue()); // TODO: move to longValue
            childRes.setContentType((String) obj.get("contentType"));
        }

        return childRes;
    }

    protected InputStream readFile(File file, String username, char[] password) throws IOException {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            InputStream fIn = new BufferedInputStream(new FileInputStream(file));

            Cipher cipher = Cipher.getInstance("Blowfish");
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(username, password));

            CipherInputStream in = new CipherInputStream(fIn, cipher);

            return in;
        } catch (Exception ex) {
            // TODO: better exception handling
            Logger.getAnonymousLogger().log(Level.SEVERE, "ERROR", ex);
            throw new RuntimeException(ex);
        }
    }

    protected File createDataFile(InputStream data, String username, char[] password) throws IOException {
        try {
            String fileName = UUID.randomUUID().toString();

            File folder = new File(storageFolder, username);
            folder = new File(folder, fileName.substring(0, 2));
            folder.mkdirs();

            File file = new File(folder, fileName);

            Cipher cipher = Cipher.getInstance("Blowfish");
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(username, password));

            OutputStream fOut = new BufferedOutputStream(new FileOutputStream(file));
            CipherOutputStream out = new CipherOutputStream(fOut, cipher);

            InputStream in = new BufferedInputStream(data);
            byte[] buffer = new byte[2048];
            int readed;
            while ((readed = in.read(buffer)) != -1) {
                out.write(buffer, 0, readed);
            }
            in.close();

            out.flush();
            out.close();
            fOut.flush();
            fOut.close();

            return file;
        } catch (Exception ex) {
            // TODO: better exception handling
            Logger.getAnonymousLogger().log(Level.SEVERE, "ERROR", ex);
            throw new RuntimeException(ex);
        } finally {
            data.close();
        }
    }

    protected SecretKey getSecretKey(String username, char[] password) {
        try {
            KeyStore store = getKeyStore(username, password);
            SecretKeyEntry entry = (SecretKeyEntry) store.getEntry(username,
                    new KeyStore.PasswordProtection(password));
            return entry.getSecretKey();
        } catch (Exception ex) {
            // TODO: better exception handling
            throw new RuntimeException(ex);
        }
    }

    protected KeyStore getKeyStore(String username, char[] password) {
        try {
            File keyStoreFile = new File(secretsFolder, username + ".jks");
            KeyStore keyStore = KeyStore.getInstance("JCEKS");
            if (keyStoreFile.exists()) {
                keyStore.load(new FileInputStream(keyStoreFile), password);
                return keyStore;
            } else {
                KeyGenerator gen = KeyGenerator.getInstance("Blowfish");
                SecretKey key = gen.generateKey();

                keyStore.load(null, password);
                keyStore.setEntry(username, new SecretKeyEntry(key), new KeyStore.PasswordProtection(password));

                keyStore.store(new FileOutputStream(keyStoreFile), password);

                return keyStore;
            }
        } catch (Exception ex) {
            // TODO: better exception handling
            Logger.getAnonymousLogger().log(Level.SEVERE, "ERROR", ex);
            throw new RuntimeException(ex);
        }
    }

    private class ContentDetector extends Thread {

        private String fileName;

        private InputStream in;

        private String contentType;

        public ContentDetector(String fileName, InputStream in) {
            this.fileName = fileName;
            this.in = in;
        }

        @Override
        public void run() {
            contentType = fileTypeDetectionService.detectFileType(fileName, in);
            byte[] buffer = new byte[2048];
            try {
                // Discard bytes in the stream that were not consumed by Tika
                while (in.read(buffer) != -1)
                    ;
            } catch (IOException ex) {
                Logger.getLogger(MongoFileService.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        public String getContentType() {
            return contentType;
        }
    }

}