All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.broadleafcommerce.common.file.service.FileSystemFileServiceProvider Maven / Gradle / Ivy

/*
 * #%L
 * BroadleafCommerce Common Libraries
 * %%
 * Copyright (C) 2009 - 2013 Broadleaf Commerce
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package org.broadleafcommerce.common.file.service;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.file.FileServiceException;
import org.broadleafcommerce.common.file.domain.FileWorkArea;
import org.broadleafcommerce.common.file.service.type.FileApplicationType;
import org.broadleafcommerce.common.site.domain.Site;
import org.broadleafcommerce.common.web.BroadleafRequestContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Default implementation of FileServiceProvider that uses the local file system to store files created by Broadleaf
 * components.
 * 
 * This Provider can only be used in production systems that run on a single server or those that have a shared filesystem
 * mounted to the application servers.
 * 
 * @author bpolster
 *
 */
@Service("blDefaultFileServiceProvider")
public class FileSystemFileServiceProvider implements FileServiceProvider {

    @Value("${asset.server.file.system.path}")
    protected String fileSystemBaseDirectory;

    @Value("${asset.server.max.generated.file.system.directories}")
    protected int maxGeneratedDirectoryDepth;

    private static final String DEFAULT_STORAGE_DIRECTORY = System.getProperty("java.io.tmpdir");

    private static final Log LOG = LogFactory.getLog(FileSystemFileServiceProvider.class);

    // Allows for small errors in the configuration (e.g. no trailing slash or whitespace).
    protected String baseDirectory;

    @Override
    public File getResource(String url) {
        return getResource(url, FileApplicationType.ALL);
    }

    @Override
    public File getResource(String url, FileApplicationType applicationType) {
        String fileName = buildResourceName(url);
        String filePath = FilenameUtils.normalize(getBaseDirectory() + File.separator + fileName);
        return new File(filePath);
    }

    @Override
    public void addOrUpdateResources(FileWorkArea area, List files, boolean removeResourcesFromWorkArea) {
        for (File srcFile : files) {
            if (!srcFile.getAbsolutePath().startsWith(area.getFilePathLocation())) {
                throw new FileServiceException("Attempt to update file " + srcFile.getAbsolutePath() +
                        " that is not in the passed in WorkArea " + area.getFilePathLocation());
            }

            String fileName = srcFile.getAbsolutePath().substring(area.getFilePathLocation().length());
            
            // before building the resource name, convert the file path to a url-like path
            String url = FilenameUtils.separatorsToUnix(fileName);
            String resourceName = buildResourceName(url);
            String destinationFilePath = FilenameUtils.normalize(getBaseDirectory() + File.separator + resourceName);
            File destFile = new File(destinationFilePath);
            if (!destFile.getParentFile().exists()) {
                destFile.getParentFile().mkdirs();
            }
            
            try {
                if (removeResourcesFromWorkArea) {
                    if (destFile.exists()) {
                        FileUtils.deleteQuietly(destFile);
                    }
                    FileUtils.moveFile(srcFile, destFile);
                } else {
                    FileUtils.copyFile(srcFile, destFile);
                }
            } catch (IOException ioe) {
                throw new FileServiceException("Error copying resource named " + fileName + " from workArea " +
                        area.getFilePathLocation() + " to " + resourceName, ioe);
            }
        }
    }

    @Override
    public boolean removeResource(String name) {
        String resourceName = buildResourceName(name);
        String filePathToRemove = FilenameUtils.normalize(getBaseDirectory() + File.separator + resourceName);
        File fileToRemove = new File(filePathToRemove);
        return fileToRemove.delete();
    }

    /**
     * Stores the file on the file-system by performing an MD5 hash of the 
     * the passed in fileName
     * 
     * To ensure that files can be stored and accessed in an efficient manner, the 
     * system creates directories based on the characters in the hash.   
     * 
     * For example, if the URL is /product/myproductimage.jpg, then the MD5 would be
     * 35ec52a8dbd8cf3e2c650495001fe55f resulting in the following file on the filesystem
     * {assetFileSystemPath}/35/ec/myproductimage.jpg.  
     * 
     * The hash for the filename will include a beginning slash before performing the MD5.   This
     * is done largely for backward compatibility with similar functionality in BLC 3.0.0.
     * 
     * This algorithm has the following benefits:
     * - Efficient file-system storage with
     * - Balanced tree of files that supports 10 million files
     * 
     * If support for more files is needed, implementors should consider one of the following approaches:
     * 1.  Overriding the maxGeneratedFileSystemDirectories property from its default of 2 to 3
     * 2.  Overriding this method to introduce an alternate approach
     * 
     * @param url The URL used to represent an asset for which a name on the fileSystem is desired.
     * 
     * @return
     */
    protected String buildResourceName(String url) {
        // Create directories based on hash
        String fileHash = null;
        // Intentionally not using File.separator here since URLs should always end with /
        if (!url.startsWith("/")) {
            fileHash = DigestUtils.md5Hex("/" + url);
        } else {
            fileHash = DigestUtils.md5Hex(url);
        }

        String resourceName = "";
        for (int i = 0; i < maxGeneratedDirectoryDepth; i++) {
            if (i == 4) {
                LOG.warn("Property maxGeneratedDirectoryDepth set to high, ignoring values past 4 - value set to" +
                        maxGeneratedDirectoryDepth);
                break;
            }
            resourceName = FilenameUtils.concat(resourceName, fileHash.substring(i * 2, (i + 1) * 2));
        }

        // use the filename from the URL which is everything after the last slash
        return FilenameUtils.concat(resourceName, FilenameUtils.getName(url));
    }

    /**
     * Returns a base directory (unique for each tenant in a multi-tenant installation.
     * Creates the directory if it does not already exist.
     */
    protected String getBaseDirectory() {
        if (baseDirectory == null) {
            if (StringUtils.isNotBlank(fileSystemBaseDirectory)) {
                baseDirectory = fileSystemBaseDirectory;
            } else {
                baseDirectory = DEFAULT_STORAGE_DIRECTORY;
            }
        }

        return getSiteDirectory(baseDirectory);
    }

    /**
     * Creates a unique directory on the file system for each site.
     * Each site may be in one of 255 base directories.   This model efficiently supports up to 65,000 sites
     * served from a single file system based on most OS systems ability to quickly access files as long
     * as there are not more than 255 directories.
     * 
     * @param The starting directory for local files which must end with a '/';
     */
    protected String getSiteDirectory(String baseDirectory) {
        BroadleafRequestContext brc = BroadleafRequestContext.getBroadleafRequestContext();
        if (brc != null) {
            Site site = brc.getSite();
            if (site != null) {
                String siteDirectory = "site-" + site.getId();
                String siteHash = DigestUtils.md5Hex(siteDirectory);
                String sitePath = FilenameUtils.concat(siteHash.substring(0, 2), siteDirectory);
                return FilenameUtils.concat(baseDirectory, sitePath);
            }
        }

        return baseDirectory;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy