org.opencms.ade.upload.CmsUploadBean.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.ade.upload.CmsUploadBean.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.ade.upload;

import org.opencms.ade.upload.shared.I_CmsUploadConstants;
import org.opencms.db.CmsDbSqlException;
import org.opencms.db.CmsImportFolder;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.types.CmsResourceTypePlain;
import org.opencms.i18n.CmsMessages;
import org.opencms.json.JSONException;
import org.opencms.json.JSONObject;
import org.opencms.jsp.CmsJspBean;
import org.opencms.loader.CmsLoaderException;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsSecurityException;
import org.opencms.util.CmsCollectionsGenericWrapper;
import org.opencms.util.CmsRequestUtil;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.log4j.spi.ThrowableInformation;

/**
 * Bean to be used in JSP scriptlet code that provides 
 * access to the upload functionality.<p>
 * 
 * @since 8.0.0 
 */
public class CmsUploadBean extends CmsJspBean {

    /** The default upload timeout. */
    public static final int DEFAULT_UPLOAD_TIMEOUT = 20000;

    /** Key name for the session attribute that stores the id of the current listener. */
    public static final String SESSION_ATTRIBUTE_LISTENER_ID = "__CmsUploadBean.LISTENER";

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsUploadBean.class);

    /** A static map of all listeners. */
    private static Map<CmsUUID, CmsUploadListener> m_listeners = new HashMap<CmsUUID, CmsUploadListener>();

    /** The gwt message bundle. */
    private CmsMessages m_bundle = org.opencms.ade.upload.Messages.get().getBundle();

    /** Signals that the start method is called. */
    private boolean m_called;

    /** A list of the file items to upload. */
    private List<FileItem> m_multiPartFileItems;

    /** The map of parameters read from the current request. */
    private Map<String, String[]> m_parameterMap;

    /** The names of the resources that have been created successfully. */
    private Set<String> m_resourcesCreated = new HashSet<String>();

    /** The server side upload delay. */
    private int m_uploadDelay;

    /**
     * Constructor, with parameters.<p>
     * 
     * @param context the JSP page context object
     * @param req the JSP request 
     * @param res the JSP response 
     */
    public CmsUploadBean(PageContext context, HttpServletRequest req, HttpServletResponse res) {

        super();
        init(context, req, res);
    }

    /**
     * Returns the listener for given CmsUUID.<p>
     * 
     * @param listenerId the uuid
     * 
     * @return the according listener
     */
    public static CmsUploadListener getCurrentListener(CmsUUID listenerId) {

        return m_listeners.get(listenerId);
    }

    /**
     * Returns the VFS path for the given filename and folder.<p>
     * 
     * @param cms the cms object
     * @param fileName the filename to combine with the folder
     * @param folder the folder to combine with the filename
     * 
     * @return the VFS path for the given filename and folder
     */
    public static String getNewResourceName(CmsObject cms, String fileName, String folder) {

        String newResname = CmsResource.getName(fileName.replace('\\', '/'));
        newResname = cms.getRequestContext().getFileTranslator().translateResource(newResname);
        newResname = folder + newResname;
        return newResname;
    }

    /**
     * Sets the uploadDelay.<p>
     *
     * @param uploadDelay the uploadDelay to set
     */
    public void setUploadDelay(int uploadDelay) {

        m_uploadDelay = uploadDelay;
    }

    /**
     * Starts the upload.<p>
     * 
     * @return the response String (JSON)
     */
    public String start() {

        // ensure that this method can only be called once
        if (m_called) {
            throw new UnsupportedOperationException();
        }
        m_called = true;

        // create a upload listener
        CmsUploadListener listener = createListener();
        try {
            // try to parse the request
            parseRequest(listener);
            // try to create the resources on the VFS
            createResources(listener);
            // trigger update offline indexes, important for gallery search
            OpenCms.getSearchManager().updateOfflineIndexes(2500);
        } catch (CmsException e) {
            // an error occurred while creating the resources on the VFS, create a special error message
            LOG.error(e.getMessage(), e);
            return generateResponse(Boolean.FALSE, getCreationErrorMessage(), formatStackTrace(e));
        } catch (CmsUploadException e) {
            // an expected error occurred while parsing the request, the error message is already set in the exception
            LOG.debug(e.getMessage(), e);
            return generateResponse(Boolean.FALSE, e.getMessage(), formatStackTrace(e));
        } catch (Exception e) {
            // an unexpected error occurred while parsing the request, create a non-specific error message 
            LOG.error(e.getMessage(), e);
            String message = m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_UNEXPECTED_0);
            return generateResponse(Boolean.FALSE, message, formatStackTrace(e));
        } finally {
            removeListener(listener.getId());
        }
        // the upload was successful inform the user about success
        return generateResponse(Boolean.TRUE, m_bundle.key(org.opencms.ade.upload.Messages.LOG_UPLOAD_SUCCESS_0),
                "");
    }

    /**
     * Creates a upload listener and puts it into the static map.<p>
     * 
     * @return the listener
     */
    private CmsUploadListener createListener() {

        CmsUploadListener listener = new CmsUploadListener(getRequest().getContentLength());
        listener.setDelay(m_uploadDelay);
        m_listeners.put(listener.getId(), listener);
        getRequest().getSession().setAttribute(SESSION_ATTRIBUTE_LISTENER_ID, listener.getId());
        return listener;
    }

    /**
     * Creates the resources.<p>
     * @param listener the listener
     * 
     * @throws CmsException if something goes wrong 
     * @throws UnsupportedEncodingException 
     */
    private void createResources(CmsUploadListener listener) throws CmsException, UnsupportedEncodingException {

        // get the target folder
        String targetFolder = getTargetFolder();

        List<String> filesToUnzip = getFilesToUnzip();

        // iterate over the list of files to upload and create each single resource
        for (FileItem fileItem : m_multiPartFileItems) {
            if ((fileItem != null) && (!fileItem.isFormField())) {
                // read the content of the file
                byte[] content = fileItem.get();
                fileItem.delete();

                // determine the new resource name
                String fileName = m_parameterMap
                        .get(fileItem.getFieldName() + I_CmsUploadConstants.UPLOAD_FILENAME_ENCODED_SUFFIX)[0];
                fileName = URLDecoder.decode(fileName, "UTF-8");

                if (filesToUnzip.contains(CmsResource.getName(fileName.replace('\\', '/')))) {
                    // import the zip
                    CmsImportFolder importZip = new CmsImportFolder();
                    try {
                        importZip.importZip(content, targetFolder, getCmsObject(), false);
                    } finally {
                        // get the created resource names
                        m_resourcesCreated.addAll(importZip.getCreatedResourceNames());
                    }
                } else {
                    // create the resource
                    String newResname = createSingleResource(fileName, targetFolder, content);
                    // add the name of the created resource to the list of successful created resources
                    m_resourcesCreated.add(newResname);
                }

                if (listener.isCanceled()) {
                    throw listener.getException();
                }
            }
        }
    }

    /**
     * Creates a single resource and returns the site path of the new resource.<p>
     * 
     * @param fileName the name of the resource to create
     * @param targetFolder the folder to store the new resource
     * @param content the content of the resource to create
     * 
     * @return the new resource site path
     * 
     * @throws CmsException if something goes wrong
     * @throws CmsLoaderException if something goes wrong
     * @throws CmsDbSqlException if something goes wrong
     */
    private String createSingleResource(String fileName, String targetFolder, byte[] content)
            throws CmsException, CmsLoaderException, CmsDbSqlException {

        String newResname = getNewResourceName(getCmsObject(), fileName, targetFolder);

        // determine Title property value to set on new resource
        String title = fileName;
        if (title.lastIndexOf('.') != -1) {
            title = title.substring(0, title.lastIndexOf('.'));
        }

        // fileName really shouldn't contain the full path, but for some reason it does sometimes when the client is 
        // running on IE7, so we eliminate anything before and including the last slash or backslash in the title 
        // before setting it as a property.

        int backslashIndex = title.lastIndexOf('\\');
        if (backslashIndex != -1) {
            title = title.substring(backslashIndex + 1);
        }

        int slashIndex = title.lastIndexOf('/');
        if (slashIndex != -1) {
            title = title.substring(slashIndex + 1);
        }

        List<CmsProperty> properties = new ArrayList<CmsProperty>(1);
        CmsProperty titleProp = new CmsProperty();
        titleProp.setName(CmsPropertyDefinition.PROPERTY_TITLE);
        if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) {
            titleProp.setStructureValue(title);
        } else {
            titleProp.setResourceValue(title);
        }
        properties.add(titleProp);

        int plainId = OpenCms.getResourceManager().getResourceType(CmsResourceTypePlain.getStaticTypeName())
                .getTypeId();
        if (!getCmsObject().existsResource(newResname, CmsResourceFilter.IGNORE_EXPIRATION)) {
            // if the resource does not exist, create it
            try {
                // create the resource
                int resTypeId = OpenCms.getResourceManager().getDefaultTypeForName(newResname).getTypeId();
                getCmsObject().createResource(newResname, resTypeId, content, properties);
            } catch (CmsSecurityException e) {
                // in case of not enough permissions, try to create a plain text file
                getCmsObject().createResource(newResname, plainId, content, properties);
            } catch (CmsDbSqlException sqlExc) {
                // SQL error, probably the file is too large for the database settings, delete file
                getCmsObject().lockResource(newResname);
                getCmsObject().deleteResource(newResname, CmsResource.DELETE_PRESERVE_SIBLINGS);
                throw sqlExc;
            }
        } else {
            // if the resource already exists, replace it
            CmsResource res = getCmsObject().readResource(newResname, CmsResourceFilter.ALL);
            if (!getCmsObject().getLock(res).isOwnedBy(getCmsObject().getRequestContext().getCurrentUser())) {
                getCmsObject().lockResource(res);
            }
            CmsFile file = getCmsObject().readFile(res);
            byte[] contents = file.getContents();
            try {
                getCmsObject().replaceResource(newResname, res.getTypeId(), content, null);
            } catch (CmsDbSqlException sqlExc) {
                // SQL error, probably the file is too large for the database settings, restore content
                file.setContents(contents);
                getCmsObject().writeFile(file);
                throw sqlExc;
            }
        }
        return newResname;
    }

    /**
     * Returns the stacktrace of the given exception as String.<p>
     * 
     * @param e the exception
     * 
     * @return the stacktrace as String
     */
    private String formatStackTrace(Exception e) {

        StringBuffer result = new StringBuffer(64);
        for (String s : new ThrowableInformation(e).getThrowableStrRep()) {
            result.append(s + "<br />\n");
        }
        return result.toString();
    }

    /**
     * Generates a JSON object and returns its String representation for the response.<p>
     * 
     * @param success <code>true</code> if the upload was successful
     * @param message the message to display
     * @param stacktrace the stack trace in case of an error
     * 
     * @return the the response String
     */
    private String generateResponse(Boolean success, String message, String stacktrace) {

        JSONObject result = new JSONObject();
        try {
            result.put(I_CmsUploadConstants.KEY_SUCCESS, success);
            result.put(I_CmsUploadConstants.KEY_MESSAGE, message);
            result.put(I_CmsUploadConstants.KEY_STACKTRACE, stacktrace);
            result.put(I_CmsUploadConstants.KEY_REQUEST_SIZE, getRequest().getContentLength());
        } catch (JSONException e) {
            LOG.error(m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_JSON_0), e);
        }
        return result.toString();
    }

    /**
     * Returns the error message if an error occurred during the creation of resources in the VFS.<p>
     * 
     * @return the error message
     */
    private String getCreationErrorMessage() {

        String message = new String();
        if (!m_resourcesCreated.isEmpty()) {
            // some resources have been created, tell the user which resources were created successfully
            StringBuffer buf = new StringBuffer(64);
            for (String name : m_resourcesCreated) {
                buf.append("<br />");
                buf.append(name);
            }
            message = m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_CREATING_1, buf.toString());
        } else {
            // no resources have been created on the VFS
            message = m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_CREATING_0);
        }
        return message;
    }

    /**
     * Gets the list of file names that should be unziped.<p>
     * 
     * @return the list of file names that should be unziped
     * 
     * @throws UnsupportedEncodingException if something goes wrong
     */
    private List<String> getFilesToUnzip() throws UnsupportedEncodingException {

        if (m_parameterMap.get(I_CmsUploadConstants.UPLOAD_UNZIP_FILES_FIELD_NAME) != null) {
            String[] filesToUnzip = m_parameterMap.get(I_CmsUploadConstants.UPLOAD_UNZIP_FILES_FIELD_NAME);
            if (filesToUnzip != null) {
                List<String> result = new ArrayList<String>();
                for (String filename : filesToUnzip) {
                    result.add(URLDecoder.decode(filename, "UTF-8"));
                }
                return result;
            }
        }
        return Collections.emptyList();
    }

    /**
     * Returns the target folder for the new resource, 
     * if the given folder does not exist root folder
     * of the current site is returned.<p>
     * 
     * @return the target folder for the new resource
     * 
     * @throws CmsException if something goes wrong
     */
    private String getTargetFolder() throws CmsException {

        // get the target folder on the vfs
        CmsResource target = getCmsObject().readResource("/", CmsResourceFilter.IGNORE_EXPIRATION);
        if (m_parameterMap.get(I_CmsUploadConstants.UPLOAD_TARGET_FOLDER_FIELD_NAME) != null) {
            String targetFolder = m_parameterMap.get(I_CmsUploadConstants.UPLOAD_TARGET_FOLDER_FIELD_NAME)[0];
            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(targetFolder)) {
                if (getCmsObject().existsResource(targetFolder)) {
                    CmsResource tmpTarget = getCmsObject().readResource(targetFolder,
                            CmsResourceFilter.IGNORE_EXPIRATION);
                    if (tmpTarget.isFolder()) {
                        target = tmpTarget;
                    }
                }
            }
        }
        String targetFolder = getCmsObject().getRequestContext().removeSiteRoot(target.getRootPath());
        if (!targetFolder.endsWith("/")) {
            // add folder separator to currentFolder
            targetFolder += "/";
        }
        return targetFolder;
    }

    /**
     * Parses the request.<p>
     * 
     * Stores the file items and the request parameters in a local variable if present.<p>
     * 
     * @param listener the upload listener 
     * 
     * @throws Exception if anything goes wrong
     */
    private void parseRequest(CmsUploadListener listener) throws Exception {

        // check if the request is a multipart request
        if (!ServletFileUpload.isMultipartContent(getRequest())) {
            // no multipart request: Abort the upload
            throw new CmsUploadException(m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_NO_MULTIPART_0));
        }

        // this was indeed a multipart form request, read the files
        m_multiPartFileItems = readMultipartFileItems(listener);

        // check if there were any multipart file items in the request
        if ((m_multiPartFileItems == null) || m_multiPartFileItems.isEmpty()) {
            // no file items found stop process
            throw new CmsUploadException(m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_NO_FILEITEMS_0));
        }

        // there are file items in the request, get the request parameters
        m_parameterMap = CmsRequestUtil.readParameterMapFromMultiPart(
                getCmsObject().getRequestContext().getEncoding(), m_multiPartFileItems);

        listener.setFinished(true);
    }

    /**
     * Parses a request of the form <code>multipart/form-data</code>.<p>
     * 
     * The result list will contain items of type <code>{@link FileItem}</code>.
     * If the request has no file items, then <code>null</code> is returned.<p>
     * 
     * @param listener the upload listener
     * 
     * @return the list of <code>{@link FileItem}</code> extracted from the multipart request,
     *      or <code>null</code> if the request has no file items
     *      
     * @throws Exception if anything goes wrong
     */
    private List<FileItem> readMultipartFileItems(CmsUploadListener listener) throws Exception {

        DiskFileItemFactory factory = new DiskFileItemFactory();
        // maximum size that will be stored in memory
        factory.setSizeThreshold(4096);
        // the location for saving data that is larger than the threshold
        factory.setRepository(new File(OpenCms.getSystemInfo().getPackagesRfsPath()));

        // create a file upload servlet
        ServletFileUpload fu = new ServletFileUpload(factory);
        // set the listener
        fu.setProgressListener(listener);
        // set encoding to correctly handle special chars (e.g. in filenames)
        fu.setHeaderEncoding(getRequest().getCharacterEncoding());
        // set the maximum size for a single file (value is in bytes)
        long maxFileSizeBytes = OpenCms.getWorkplaceManager().getFileBytesMaxUploadSize(getCmsObject());
        if (maxFileSizeBytes > 0) {
            fu.setFileSizeMax(maxFileSizeBytes);
        }

        // try to parse the request
        try {
            return CmsCollectionsGenericWrapper.list(fu.parseRequest(getRequest()));
        } catch (SizeLimitExceededException e) {
            // request size is larger than maximum allowed request size, throw an error
            Integer actualSize = new Integer((int) (e.getActualSize() / 1024));
            Integer maxSize = new Integer((int) (e.getPermittedSize() / 1024));
            throw new CmsUploadException(m_bundle
                    .key(org.opencms.ade.upload.Messages.ERR_UPLOAD_REQUEST_SIZE_LIMIT_2, actualSize, maxSize), e);
        } catch (FileSizeLimitExceededException e) {
            // file size is larger than maximum allowed file size, throw an error
            Integer actualSize = new Integer((int) (e.getActualSize() / 1024));
            Integer maxSize = new Integer((int) (e.getPermittedSize() / 1024));
            throw new CmsUploadException(m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_FILE_SIZE_LIMIT_3,
                    actualSize, e.getFileName(), maxSize), e);
        }
    }

    /**
     * Remove the listener active in this session.
     * 
     * @param listenerId the id of the listener to remove
     */
    private void removeListener(CmsUUID listenerId) {

        getRequest().getSession().removeAttribute(SESSION_ATTRIBUTE_LISTENER_ID);
        m_listeners.remove(listenerId);
    }
}