org.structr.web.common.FileHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.structr.web.common.FileHelper.java

Source

/**
 * Copyright (C) 2010-2016 Structr GmbH
 *
 * This file is part of Structr <http://structr.org>.
 *
 * Structr is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Structr 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Structr.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.structr.web.common;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.activation.MimetypesFileTypeMap;
import net.sf.jmimemagic.Magic;
import net.sf.jmimemagic.MagicException;
import net.sf.jmimemagic.MagicMatch;
import net.sf.jmimemagic.MagicMatchNotFoundException;
import net.sf.jmimemagic.MagicParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.structr.common.PathHelper;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.core.GraphObject;
import org.structr.core.Services;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.entity.AbstractNode;
import org.structr.core.entity.LinkedTreeNode;
import org.structr.core.property.PropertyMap;
import org.structr.util.Base64;
import org.structr.web.entity.AbstractFile;
import org.structr.web.entity.FileBase;
import org.structr.web.entity.Folder;

//~--- classes ----------------------------------------------------------------
/**
 * File utility class.
 *
 *
 */
public class FileHelper {

    private static final String UNKNOWN_MIME_TYPE = "application/octet-stream";
    private static final Logger logger = Logger.getLogger(FileHelper.class.getName());
    private static final MimetypesFileTypeMap mimeTypeMap = new MimetypesFileTypeMap(
            FileHelper.class.getResourceAsStream("/mime.types"));

    //~--- methods --------------------------------------------------------
    /**
     * Transform an existing file into the target class.
     *
     * @param <T>
     * @param securityContext
     * @param uuid
     * @param fileType
     * @return transformed file
     * @throws FrameworkException
     * @throws IOException
     */
    public static <T extends org.structr.dynamic.File> T transformFile(final SecurityContext securityContext,
            final String uuid, final Class<T> fileType) throws FrameworkException, IOException {

        AbstractFile existingFile = getFileByUuid(securityContext, uuid);

        if (existingFile != null) {

            existingFile.unlockReadOnlyPropertiesOnce();
            existingFile.setProperty(AbstractNode.type,
                    fileType == null ? org.structr.dynamic.File.class.getSimpleName() : fileType.getSimpleName());

            existingFile = getFileByUuid(securityContext, uuid);

            return (T) (fileType != null ? fileType.cast(existingFile) : (org.structr.dynamic.File) existingFile);
        }

        return null;
    }

    /**
     * Create a new image node from image data encoded in base64 format.
     *
     * If the given string is an uuid of an existing file, transform it into
     * the target class.
     *
     * @param <T>
     * @param securityContext
     * @param rawData
     * @param t defaults to File.class if null
     * @return file
     * @throws FrameworkException
     * @throws IOException
     */
    public static <T extends org.structr.dynamic.File> T createFileBase64(final SecurityContext securityContext,
            final String rawData, final Class<T> t) throws FrameworkException, IOException {

        Base64URIData uriData = new Base64URIData(rawData);

        return createFile(securityContext, uriData.getBinaryData(), uriData.getContentType(), t);

    }

    /**
     * Create a new file node from the given input stream
     *
     * @param <T>
     * @param securityContext
     * @param fileStream
     * @param contentType
     * @param fileType defaults to File.class if null
     * @param name
     * @return file
     * @throws FrameworkException
     * @throws IOException
     */
    public static <T extends org.structr.dynamic.File> T createFile(final SecurityContext securityContext,
            final InputStream fileStream, final String contentType, final Class<T> fileType, final String name)
            throws FrameworkException, IOException {

        final byte[] data = IOUtils.toByteArray(fileStream);
        return createFile(securityContext, data, contentType, fileType, name);

    }

    /**
     * Create a new file node from the given byte array
     *
     * @param <T>
     * @param securityContext
     * @param fileData
     * @param contentType if null, try to auto-detect content type
     * @param t
     * @param name
     * @return file
     * @throws FrameworkException
     * @throws IOException
     */
    public static <T extends org.structr.dynamic.File> T createFile(final SecurityContext securityContext,
            final byte[] fileData, final String contentType, final Class<T> t, final String name)
            throws FrameworkException, IOException {

        PropertyMap props = new PropertyMap();

        props.put(AbstractNode.name, name);

        T newFile = (T) StructrApp.getInstance(securityContext).create(t, props);

        setFileData(newFile, fileData, contentType);

        return newFile;
    }

    /**
     * Create a new file node from the given byte array
     *
     * @param <T>
     * @param securityContext
     * @param fileData
     * @param contentType
     * @param t defaults to File.class if null
     * @return file
     * @throws FrameworkException
     * @throws IOException
     */
    public static <T extends org.structr.dynamic.File> T createFile(final SecurityContext securityContext,
            final byte[] fileData, final String contentType, final Class<T> t)
            throws FrameworkException, IOException {

        return createFile(securityContext, fileData, contentType, t, null);

    }

    /**
     * Decodes base64-encoded raw data into binary data and writes it to the
     * given file.
     *
     * @param file
     * @param rawData
     * @throws FrameworkException
     * @throws IOException
     */
    public static void decodeAndSetFileData(final org.structr.dynamic.File file, final String rawData)
            throws FrameworkException, IOException {

        Base64URIData uriData = new Base64URIData(rawData);
        setFileData(file, uriData.getBinaryData(), uriData.getContentType());

    }

    /**
     * Write image data to the given file node and set checksum and size.
     *
     * @param file
     * @param fileData
     * @param contentType if null, try to auto-detect content type
     * @throws FrameworkException
     * @throws IOException
     */
    public static void setFileData(final org.structr.dynamic.File file, final byte[] fileData,
            final String contentType) throws FrameworkException, IOException {

        FileHelper.writeToFile(file, fileData);

        file.setProperty(org.structr.dynamic.File.contentType,
                contentType != null ? contentType : getContentMimeType(file));

        file.unlockReadOnlyPropertiesOnce();
        file.setProperty(org.structr.dynamic.File.checksum, FileHelper.getChecksum(file));

        file.unlockReadOnlyPropertiesOnce();
        file.setProperty(org.structr.dynamic.File.size, FileHelper.getSize(file));

        file.unlockReadOnlyPropertiesOnce();
        file.setProperty(org.structr.dynamic.File.version, 1);

    }

    /**
     * Update checksum content type and size of the given file
     *
     * @param file the file
     * @throws FrameworkException
     * @throws IOException
     */
    public static void updateMetadata(final org.structr.dynamic.File file) throws FrameworkException, IOException {

        file.setProperty(org.structr.dynamic.File.contentType, getContentMimeType(file));

        // checksum is read-only
        file.unlockReadOnlyPropertiesOnce();
        file.setProperty(org.structr.dynamic.File.checksum, FileHelper.getChecksum(file));

        // size is read-only
        file.unlockReadOnlyPropertiesOnce();
        file.setProperty(org.structr.dynamic.File.size, FileHelper.getSize(file));

    }

    //~--- get methods ----------------------------------------------------
    public static String getBase64String(final org.structr.dynamic.File file) {

        try {

            return Base64.encodeToString(IOUtils.toByteArray(file.getInputStream()), false);

        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Could not get base64 string from file ", ex);
        }

        return null;
    }

    //~--- inner classes --------------------------------------------------
    public static class Base64URIData {

        private String contentType = null;
        private String data = null;

        //~--- constructors -------------------------------------------
        public Base64URIData(final String rawData) {

            if (rawData.contains(",")) {

                String[] parts = StringUtils.split(rawData, ",");
                if (parts.length == 2) {

                    contentType = StringUtils.substringBetween(parts[0], "data:", ";base64");
                    data = parts[1];

                }

            } else {

                data = rawData;
            }

        }

        //~--- get methods --------------------------------------------
        public String getContentType() {

            return contentType;

        }

        public String getData() {

            return data;

        }

        public byte[] getBinaryData() {

            return Base64.decode(data);

        }

    }

    /**
     * Write binary data to a file and reference the file on disk at the
     * given file node
     *
     * @param fileNode
     * @param inStream
     * @throws FrameworkException
     * @throws IOException
     */
    public static void writeToFile(final org.structr.dynamic.File fileNode, final InputStream inStream)
            throws FrameworkException, IOException {

        writeToFile(fileNode, IOUtils.toByteArray(inStream));

    }

    /**
     * Write binary data to a file and reference the file on disk at the
     * given file node
     *
     * @param fileNode
     * @param data
     * @throws FrameworkException
     * @throws IOException
     * @return the file on disk
     */
    public static File writeToFile(final org.structr.dynamic.File fileNode, final byte[] data)
            throws FrameworkException, IOException {

        String id = fileNode.getProperty(GraphObject.id);
        if (id == null) {

            final String newUuid = UUID.randomUUID().toString().replaceAll("[\\-]+", "");
            id = newUuid;

            fileNode.unlockReadOnlyPropertiesOnce();
            fileNode.setProperty(GraphObject.id, newUuid);
        }

        fileNode.unlockReadOnlyPropertiesOnce();
        fileNode.setProperty(org.structr.dynamic.File.relativeFilePath,
                org.structr.dynamic.File.getDirectoryPath(id) + "/" + id);

        final String filesPath = Services.getInstance().getConfigurationValue(Services.FILES_PATH);

        java.io.File fileOnDisk = new java.io.File(filesPath + "/" + fileNode.getRelativeFilePath());

        fileOnDisk.getParentFile().mkdirs();
        FileUtils.writeByteArrayToFile(fileOnDisk, data);

        return fileOnDisk;

    }

    //~--- get methods ----------------------------------------------------
    /**
     * Return mime type of given file
     *
     * @param file
     * @return content type
     * @throws java.io.IOException
     */
    public static String getContentMimeType(final org.structr.web.entity.FileBase file) throws IOException {
        return getContentMimeType(file.getFileOnDisk(), file.getProperty(AbstractNode.name));
    }

    /**
     * Return mime type of given file
     *
     * @param file
     * @param name
     * @return content type
     * @throws java.io.IOException
     */
    public static String getContentMimeType(final java.io.File file, final String name) throws IOException {

        String mimeType;

        // try name first, if not null
        if (name != null) {
            mimeType = mimeTypeMap.getContentType(name);
            if (mimeType != null && !UNKNOWN_MIME_TYPE.equals(mimeType)) {
                return mimeType;
            }
        }

        // then file content
        mimeType = Files.probeContentType(file.toPath());
        if (mimeType != null && !UNKNOWN_MIME_TYPE.equals(mimeType)) {

            return mimeType;
        }

        // fallback: jmimemagic
        try {
            final MagicMatch match = Magic.getMagicMatch(file, false, true);
            if (match != null) {

                return match.getMimeType();
            }

        } catch (MagicParseException | MagicMatchNotFoundException | MagicException ignore) {
            // mex.printStackTrace();
        }

        // no success :(
        return UNKNOWN_MIME_TYPE;
    }

    /**
     * Calculate CRC32 checksum of given file
     *
     * @param file
     * @return checksum
     */
    public static Long getChecksum(final FileBase file) {

        String relativeFilePath = file.getRelativeFilePath();

        if (relativeFilePath != null) {

            String filePath = getFilePath(relativeFilePath);

            try {

                java.io.File fileOnDisk = new java.io.File(filePath);
                Long checksum = FileUtils.checksumCRC32(fileOnDisk);

                logger.log(Level.FINE, "Checksum of file {0} ({1}): {2}",
                        new Object[] { file.getUuid(), filePath, checksum });

                return checksum;

            } catch (IOException ex) {

                logger.log(Level.WARNING, "Could not calculate checksum of file {0}", filePath);

            }

        }

        return null;

    }

    /**
     * Return size of file on disk, or -1 if not possible
     *
     * @param file
     * @return size
     */
    public static long getSize(final FileBase file) {

        String path = file.getRelativeFilePath();

        if (path != null) {

            String filePath = getFilePath(path);

            try {

                java.io.File fileOnDisk = new java.io.File(filePath);
                long fileSize = fileOnDisk.length();

                logger.log(Level.FINE, "File size of node {0} ({1}): {2}",
                        new Object[] { file.getUuid(), filePath, fileSize });

                return fileSize;

            } catch (Exception ex) {

                logger.log(Level.WARNING, "Could not calculate file size{0}", filePath);

            }

        }

        return -1;

    }

    /**
     * Find a file by its absolute ancestor path.
     *
     * File may not be hidden or deleted.
     *
     * @param securityContext
     * @param absolutePath
     * @return file
     */
    public static AbstractFile getFileByAbsolutePath(final SecurityContext securityContext,
            final String absolutePath) {

        String[] parts = PathHelper.getParts(absolutePath);

        if (parts == null || parts.length == 0) {
            return null;
        }

        // Find root folder
        if (parts[0].length() == 0) {
            return null;
        }

        AbstractFile currentFile = getFirstRootFileByName(securityContext, parts[0]);
        if (currentFile == null) {
            return null;
        }

        for (int i = 1; i < parts.length; i++) {

            List<AbstractFile> children = currentFile.getProperty(AbstractFile.children);

            currentFile = null;

            for (AbstractFile child : children) {

                if (child.getProperty(AbstractFile.name).equals(parts[i])) {

                    // Child with matching name found
                    currentFile = child;
                    break;
                }

            }

            if (currentFile == null) {
                return null;
            }

        }

        return currentFile;

    }

    public static AbstractFile getFileByUuid(final SecurityContext securityContext, final String uuid) {

        logger.log(Level.FINE, "Search for file with uuid: {0}", uuid);

        try {
            return StructrApp.getInstance(securityContext).get(AbstractFile.class, uuid);

        } catch (FrameworkException fex) {

            logger.log(Level.WARNING, "Unable to find a file by UUID {0}: {1}",
                    new Object[] { uuid, fex.getMessage() });
        }

        return null;
    }

    public static AbstractFile getFirstFileByName(final SecurityContext securityContext, final String name) {

        logger.log(Level.FINE, "Search for file with name: {0}", name);

        try {
            return StructrApp.getInstance(securityContext).nodeQuery(AbstractFile.class).andName(name).getFirst();

        } catch (FrameworkException fex) {

            logger.log(Level.WARNING, "Unable to find a file for name {0}: {1}",
                    new Object[] { name, fex.getMessage() });
        }

        return null;
    }

    /**
     * Find the first file with given name on root level (without parent folder).
     *
     * @param securityContext
     * @param name
     * @return file
     */
    public static AbstractFile getFirstRootFileByName(final SecurityContext securityContext, final String name) {

        logger.log(Level.FINE, "Search for file with name: {0}", name);

        try {
            final List<AbstractFile> files = StructrApp.getInstance(securityContext).nodeQuery(AbstractFile.class)
                    .andName(name).getAsList();

            for (final AbstractFile file : files) {

                if (file.getProperty(AbstractFile.parent) == null) {
                    return file;
                }

            }

        } catch (FrameworkException fex) {

            logger.log(Level.WARNING, "Unable to find a file for name {0}: {1}",
                    new Object[] { name, fex.getMessage() });
        }

        return null;
    }

    /**
     * Return the virtual folder path of any
     * {@link File} or
     * {@link org.structr.web.entity.Folder}
     *
     * @param file
     * @return path
     */
    public static String getFolderPath(final AbstractFile file) {

        LinkedTreeNode parentFolder = file.getProperty(AbstractFile.parent);

        String folderPath = file.getProperty(AbstractFile.name);

        if (folderPath == null) {
            folderPath = file.getProperty(GraphObject.id);
        }

        while (parentFolder != null) {
            folderPath = parentFolder.getName().concat("/").concat(folderPath);
            parentFolder = parentFolder.getProperty(AbstractFile.parent);
        }

        return "/".concat(folderPath);
    }

    public static String getFilePath(final String... pathParts) {

        String filePath = Services.getInstance().getConfigurationValue(Services.FILES_PATH);
        StringBuilder returnPath = new StringBuilder();

        returnPath.append(filePath);
        returnPath.append(filePath.endsWith("/") ? "" : "/");

        for (String pathPart : pathParts) {
            returnPath.append(pathPart);
        }

        return returnPath.toString();
    }

    /**
     * Create one folder per path item and return the last folder.
     *
     * F.e.: /a/b/c => Folder["name":"a"] --HAS_CHILD--> Folder["name":"b"]
     * --HAS_CHILD--> Folder["name":"c"], returns Folder["name":"c"]
     *
     * @param securityContext
     * @param path
     * @return folder
     * @throws FrameworkException
     */
    public static Folder createFolderPath(final SecurityContext securityContext, final String path)
            throws FrameworkException {

        App app = StructrApp.getInstance(securityContext);

        if (path == null) {

            return null;
        }

        Folder folder = (Folder) FileHelper.getFileByAbsolutePath(securityContext, path);

        if (folder != null) {
            return folder;
        }

        String[] parts = PathHelper.getParts(path);
        String partialPath = "";

        for (String part : parts) {

            // ignore ".." and "." in paths
            if ("..".equals(part) || ".".equals(part)) {
                continue;
            }

            Folder parent = folder;

            partialPath += PathHelper.PATH_SEP + part;
            folder = (Folder) FileHelper.getFileByAbsolutePath(securityContext, partialPath);

            if (folder == null) {

                folder = app.create(Folder.class, part);

            }

            if (parent != null) {

                folder.setProperty(AbstractFile.parent, parent);

            }

            //         if (folder != null && parent != null) {
            //            app.create(parent, folder, Folders.class);
            //            folder.updateInIndex();
            //         }

        }

        return folder;

    }

    public static String getDateString() {
        return new SimpleDateFormat("yyyy-MM-dd-HHmmss").format(new Date());
    }
}