com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPlugin.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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 software 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.xpn.xwiki.plugin.zipexplorer;

import java.io.ByteArrayInputStream;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.api.Api;
import com.xpn.xwiki.api.Attachment;
import com.xpn.xwiki.api.Document;
import com.xpn.xwiki.doc.XWikiAttachment;
import com.xpn.xwiki.objects.classes.ListItem;
import com.xpn.xwiki.plugin.XWikiDefaultPlugin;
import com.xpn.xwiki.plugin.XWikiPluginInterface;

/**
 * See {@link com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPluginAPI} for documentation.
 * 
 * @version $Id: 10ab36badf3356a9a35f8b6f28c4120811b16ea3 $
 * @deprecated the plugin technology is deprecated, consider rewriting as components
 */
@Deprecated
public class ZipExplorerPlugin extends XWikiDefaultPlugin {
    /**
     * Log object to log messages in this class.
     */
    private static final Logger LOG = LoggerFactory.getLogger(ZipExplorerPlugin.class);

    /**
     * Path separators for URL.
     * 
     * @todo Define this somewhere else as this is not specific to this plugin
     */
    private static final String URL_SEPARATOR = "/";

    /**
     * @param name the plugin name
     * @param className the plugin classname (used in logs for example)
     * @param context the XWiki Context
     *
     * @see XWikiDefaultPlugin#XWikiDefaultPlugin(String,String,com.xpn.xwiki.XWikiContext)
     */
    public ZipExplorerPlugin(String name, String className, XWikiContext context) {
        super(name, className, context);
        init(context);
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#getName()
     */
    @Override
    public String getName() {
        return "zipexplorer";
    }

    /**
     * {@inheritDoc}
     * 
     * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#getPluginApi
     */
    @Override
    public Api getPluginApi(XWikiPluginInterface plugin, XWikiContext context) {
        return new ZipExplorerPluginAPI((ZipExplorerPlugin) plugin, context);
    }

    /**
     * For ZIP URLs of the format <code>http://[...]/zipfile.zip/SomeDirectory/SomeFile.txt</code> return a new
     * attachment containing the file pointed to inside the ZIP. If the original attachment does not point to a ZIP file
     * or if it doesn't specify a location inside the ZIP then do nothing and return the original attachment.
     * 
     * @param attachment the original attachment
     * @param context the XWiki context, used to get the request URL corresponding to the download request
     * @return a new attachment pointing to the file pointed to by the URL inside the ZIP or the original attachment if
     *         the requested URL doesn't specify a file inside a ZIP
     * @see com.xpn.xwiki.plugin.XWikiDefaultPlugin#downloadAttachment
     */
    @Override
    public XWikiAttachment downloadAttachment(XWikiAttachment attachment, XWikiContext context) {
        String url = context.getRequest().getRequestURI();

        // Verify if we should return the original attachment. This will happen if the requested
        // download URL doesn't point to a zip or if the URL doesn't point to a file inside the ZIP.
        if (!isValidZipURL(url, context.getAction().trim())) {
            return attachment;
        }

        String filename = getFileLocationFromZipURL(url, context.getAction().trim());

        // Create the new attachment pointing to the file inside the ZIP
        XWikiAttachment newAttachment = new XWikiAttachment();
        newAttachment.setDoc(attachment.getDoc());
        newAttachment.setAuthor(attachment.getAuthor());
        newAttachment.setDate(attachment.getDate());

        InputStream stream = null;
        try {
            stream = new BufferedInputStream(attachment.getContentInputStream(context));

            if (!isZipFile(stream)) {
                return attachment;
            }

            ZipInputStream zis = new ZipInputStream(stream);
            ZipEntry entry;

            while ((entry = zis.getNextEntry()) != null) {
                String entryName = entry.getName();

                if (entryName.equals(filename)) {
                    newAttachment.setFilename(entryName);

                    if (entry.getSize() == -1) {
                        // Note: We're copying the content of the file in the ZIP in memory. This is
                        // potentially going to cause an error if the file's size is greater than the
                        // maximum size of a byte[] array in Java or if there's not enough memomry.
                        byte[] data = IOUtils.toByteArray(zis);

                        newAttachment.setContent(data);
                    } else {
                        newAttachment.setContent(zis, (int) entry.getSize());
                    }
                    break;
                }
            }
        } catch (XWikiException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(stream);
        }
        return newAttachment;
    }

    /**
     * @param document the document containing the ZIP file as an attachment
     * @param attachmentName the name under which the ZIP file is attached in the document
     * @param context not used
     * @return the list of file entries in the ZIP file attached under the passed attachment name inside the passed
     *         document
     * @see com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPluginAPI#getFileList
     */
    public List<String> getFileList(Document document, String attachmentName, XWikiContext context) {
        List<String> zipList = new ArrayList<String>();
        Attachment attachment = document.getAttachment(attachmentName);

        InputStream stream = null;
        try {
            stream = new ByteArrayInputStream(attachment.getContent());

            if (isZipFile(stream)) {
                ZipInputStream zis = new ZipInputStream(stream);
                ZipEntry entry;
                while ((entry = zis.getNextEntry()) != null) {
                    zipList.add(entry.getName());
                }
            }
        } catch (XWikiException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return zipList;
    }

    /**
     * Finds the ZIP attachment with passed name from the passed document matching and parse the ZIP to generate a list
     * of {@link com.xpn.xwiki.objects.classes.ListItem} elements representing a tree view of all directories and files
     * in the ZIP. For example the following zip: <code><pre>
     * zipfile.zip:
     *   Directory/File.txt
     *   File2.txt
     * </pre></code> generates the following ListItem list: <code><pre>
     *   { id = "Directory/", value = "Directory", parent = ""}
     *   { id = "Directory/File.txt", value = "File.txt", parent = "Directory/"}
     *   { id = "File2.txt", value = "File2.txt", parent = ""}
     * </pre></code>
     * 
     * @param document the document containing the ZIP file as an attachment
     * @param attachmentName the name under which the ZIP file is attached in the document
     * @param context not used
     * @return a tree view list of {@link com.xpn.xwiki.objects.classes.ListItem} elements representing the content of
     *         the ZIP file
     * @see com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPluginAPI#getFileTreeList
     */
    public List<ListItem> getFileTreeList(Document document, String attachmentName, XWikiContext context) {
        List<String> flatList = getFileList(document, attachmentName, context);
        Map<String, ListItem> fileTree = new HashMap<String, ListItem>();
        List<ListItem> res = new ArrayList<ListItem>();
        for (String url : flatList) {
            StringBuffer buf = new StringBuffer(url.length());
            String parentBuf = "";
            String[] aUrl = url.split(URL_SEPARATOR);
            for (int i = 0; i < aUrl.length; i++) {
                if (i == aUrl.length - 1 && !url.endsWith(URL_SEPARATOR)) {
                    buf.append(aUrl[i]);
                } else {
                    buf.append(aUrl[i] + URL_SEPARATOR);
                }
                ListItem item = new ListItem(buf.toString(), aUrl[i], parentBuf);
                if (!fileTree.containsKey(buf.toString())) {
                    res.add(item);
                }
                fileTree.put(buf.toString(), item);
                parentBuf = buf.toString();
            }
        }
        return res;
    }

    /**
     * @param document the document containing the ZIP file as an attachment
     * @param attachmentName the name under which the ZIP file is attached in the document
     * @param fileName the filename to concatenate at the end of the attachment URL
     * @param context not used
     * @return the attachment URL of the passed attachment located in the passed document to which the passed filename
     *         has been suffixed.
     * @see com.xpn.xwiki.plugin.zipexplorer.ZipExplorerPluginAPI#getFileLink
     */
    public String getFileLink(Document document, String attachmentName, String fileName, XWikiContext context) {
        return document.getAttachmentURL(attachmentName) + URL_SEPARATOR + fileName;
    }

    /**
     * @param url the URL to parse and from which to extract the relative file location
     * @param action the XWiki requested action (for example "download", "edit", "view", etc).
     * @return the relative file location of a file in the ZIP file pointed to by the passed URL. The ZIP URL must be of
     *         the format <code>http://[...]/zipfile.zip/SomeDirectory/SomeFile.txt</code>. With the example above this
     *         method would return <code>SomeDirectory/SomeFile.txt</code>. Return an empty string if the zip URL
     *         passed.
     * @todo There should a XWikiURL class possibly extended by a ZipXWikiURL class to handle URL manipulation. Once
     *       this exists remove this code. See http://jira.xwiki.org/jira/browse/XWIKI-437
     */
    protected String getFileLocationFromZipURL(String url, String action) {
        String path = url.substring(url.indexOf(URL_SEPARATOR + action));
        int pos = 0;
        for (int i = 0; pos > -1 && i < 4; i++) {
            pos = path.indexOf(URL_SEPARATOR, pos + 1);
        }
        if (pos == -1) {
            return "";
        }
        path = path.substring(pos + 1);

        // Unencode any encoding done by the browser on the URL. For example the browser will
        // encode spaces and other special characters.
        try {
            path = URLDecoder.decode(path, "UTF-8");
        } catch (IOException e) {
            // In case of error we log the error and continue with the undecoded URL.
            // TODO: Ideally this should rather fail fast but we have no exception handling
            // framework for scripting code. Change this when we have one.
            LOG.error("Failed to decode URL path [" + path + "]", e);
        }

        return path;
    }

    /**
     * @param filecontent the content of the file
     * @return true if the file is in zip format (.zip, .jar etc) or false otherwise
     */
    protected boolean isZipFile(InputStream filecontent) {
        int standardZipHeader = 0x504b0304;
        filecontent.mark(8);
        try {
            DataInputStream datastream = new DataInputStream(filecontent);
            int fileHeader = datastream.readInt();
            return (standardZipHeader == fileHeader);
        } catch (IOException e) {
            // The file doesn't have 4 bytes, so it isn't a zip file
        } finally {
            // Reset the input stream to the beginning. This may be needed for further reading the archive.
            try {
                filecontent.reset();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * @param url the ZIP URL to check
     * @param action the XWiki requested action (for example "download", "edit", "view", etc).
     * @return true if the ZIP URL points to a file inside the ZIP or false otherwise
     */
    protected boolean isValidZipURL(String url, String action) {
        boolean isValidZipURL = false;
        try {
            // TODO: There shouldn't be the need to do a trim() on an Action. Actually actions
            // should be enumerated types. See http://jira.xwiki.org/jira/browse/XWIKI-436
            String filenameInZip = getFileLocationFromZipURL(url, action);

            // TODO: Ideally we should also check to see if the URL points to a file and not to
            // a directory.
            if (filenameInZip.length() > 0) {
                isValidZipURL = true;
            }
        } catch (Exception e) {
            // TODO: This exception block should be removed and possible errors should be
            // handled in getFileLocationFromZipURL.
        }
        return isValidZipURL;
    }
}