org.xwiki.xar.XarPackage.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.xar.XarPackage.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 org.xwiki.xar;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xwiki.model.EntityType;
import org.xwiki.model.internal.reference.DefaultSymbolScheme;
import org.xwiki.model.internal.reference.LocalStringEntityReferenceSerializer;
import org.xwiki.model.reference.LocalDocumentReference;
import org.xwiki.xar.internal.XarUtils;
import org.xwiki.xar.internal.model.XarModel;

import javanet.staxutils.IndentingXMLStreamWriter;

/**
 * Manipulate package.xml XAR package file.
 * 
 * @version $Id: 3b4865efd5d02c21f1726919a252ad7e67c97f53 $
 * @since 5.4RC1
 */
public class XarPackage {
    private static final LocalStringEntityReferenceSerializer TOSTRING_SERIALIZER = new LocalStringEntityReferenceSerializer(
            new DefaultSymbolScheme());

    /**
     * Get all entries found in a XAR file.
     * 
     * @param file the XAR file
     * @return the entries of the passed XAR file
     * @throws XarException when failing to parse the XAR package
     * @throws IOException when failing to read the file
     */
    public static Collection<XarEntry> getEntries(File file) throws XarException, IOException {
        XarPackage xarPackage = new XarPackage(file);

        return xarPackage.getEntries();
    }

    /**
     * @see #getPackageExtensionId()
     */
    private String packageExtensionId;

    /**
     * @see #isPackagePreserveVersion()
     */
    private boolean packagePreserveVersion = true;

    /**
     * @see #getPackageName()
     */
    private String packageName;

    /**
     * @see #getPackageDescription()
     */
    private String packageDescription;

    /**
     * @see #getPackageLicense()
     */
    private String packageLicense;

    /**
     * @see #getPackageAuthor()
     */
    private String packageAuthor;

    /**
     * @see #getPackageVersion()
     */
    private String packageVersion;

    /**
     * @see #isPackageBackupPack()
     */
    private boolean packageBackupPack;

    private final Map<LocalDocumentReference, XarEntry> packageFiles = new LinkedHashMap<LocalDocumentReference, XarEntry>();

    private final Map<LocalDocumentReference, XarEntry> entries = new LinkedHashMap<LocalDocumentReference, XarEntry>();

    /**
     * Default constructor.
     */
    public XarPackage() {

    }

    /**
     * @param zipFile the XAR file as a {@link ZipFile}
     * @throws XarException when failing to parse the XAR package
     * @throws IOException when failing to read the file
     */
    public XarPackage(ZipFile zipFile) throws XarException, IOException {
        read(zipFile);
    }

    /**
     * @param file the XAR file
     * @throws IOException when failing to read the file
     * @throws XarException when failing to parse the XAR package
     */
    public XarPackage(File file) throws IOException, XarException {
        ZipFile zipFile = new ZipFile(file);

        try {
            read(zipFile);
        } finally {
            zipFile.close();
        }
    }

    /**
     * @param xarStream an input stream the the XAR file
     * @throws IOException when failing to read the file
     * @throws XarException when failing to parse the XAR package
     */
    public XarPackage(InputStream xarStream) throws IOException, XarException {
        read(xarStream);
    }

    /**
     * @param entries the entries in the XAR file
     */
    public XarPackage(Collection<XarEntry> entries) {
        for (XarEntry entry : entries) {
            this.entries.put(entry, entry);
        }
    }

    /**
     * Find and add the entries located in the passed XAR file.
     * 
     * @param xarStream an input stream to a XAR file
     * @throws IOException when failing to read the file
     * @throws XarException when failing to parse the XAR package
     */
    public void read(InputStream xarStream) throws IOException, XarException {
        ZipArchiveInputStream zis = new ZipArchiveInputStream(xarStream, "UTF-8", false);

        try {
            for (ZipArchiveEntry entry = zis.getNextZipEntry(); entry != null; entry = zis.getNextZipEntry()) {
                if (!entry.isDirectory() && zis.canReadEntryData(entry)) {
                    readEntry(zis, entry);
                }
            }
        } finally {
            zis.close();
        }
    }

    /**
     * Find and add the entries located in the passed XAR file.
     * 
     * @param zipFile the XAR file
     * @throws IOException when failing to read the file
     * @throws XarException when failing to parse the XAR package
     */
    public void read(ZipFile zipFile) throws IOException, XarException {
        Enumeration<ZipArchiveEntry> zipEntries = zipFile.getEntries();

        while (zipEntries.hasMoreElements()) {
            ZipArchiveEntry entry = zipEntries.nextElement();

            if (!entry.isDirectory()) {
                InputStream stream = zipFile.getInputStream(entry);

                try {
                    readEntry(stream, entry);
                } finally {
                    stream.close();
                }
            }
        }
    }

    private void readEntry(InputStream stream, ZipArchiveEntry entry) throws XarException, IOException {
        if (entry.getName().equals(XarModel.PATH_PACKAGE)) {
            readDescriptor(stream);
        } else {
            LocalDocumentReference reference = XarUtils.getReference(stream);

            // Get current action associated to the document
            int defaultAction = getDefaultAction(reference);

            // Create entry
            XarEntry xarEntry = new XarEntry(reference, entry.getName(), defaultAction);

            // Register entry
            this.entries.put(xarEntry, xarEntry);

            // Update existing package file entry name
            updatePackageFileEntryName(xarEntry);
        }
    }

    /**
     * @return the identifier of the extension stored in the XAR package
     */
    public String getPackageExtensionId() {
        return this.packageExtensionId;
    }

    /**
     * @param packageExtensionId the identifier of the extension stored in the XAR package
     */
    public void setPackageExtensionId(String packageExtensionId) {
        this.packageExtensionId = packageExtensionId;
    }

    /**
     * @return true if the history should be preserved by default
     */
    public boolean isPackagePreserveVersion() {
        return this.packagePreserveVersion;
    }

    /**
     * @param preserveVersion true if the history should be preserved by default
     * @deprecated since 7.2M1, use {@link #setPackagePreserveVersion(boolean)} instead
     */
    @Deprecated
    public void setPreserveVersion(boolean preserveVersion) {
        this.packagePreserveVersion = preserveVersion;
    }

    /**
     * @param packagePreserveVersion true if the history should be preserved by default
     */
    public void setPackagePreserveVersion(boolean packagePreserveVersion) {
        this.packagePreserveVersion = packagePreserveVersion;
    }

    /**
     * @return the name of the package
     */
    public String getPackageName() {
        return this.packageName;
    }

    /**
     * @param packageName the name of the package
     */
    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }

    /**
     * @return the description of package
     */
    public String getPackageDescription() {
        return this.packageDescription;
    }

    /**
     * @param packageDescription the description of package
     */
    public void setPackageDescription(String packageDescription) {
        this.packageDescription = packageDescription;
    }

    /**
     * @return the license of the package
     */
    public String getPackageLicense() {
        return this.packageLicense;
    }

    /**
     * @param packageLicense the license of the package
     */
    public void setPackageLicense(String packageLicense) {
        this.packageLicense = packageLicense;
    }

    /**
     * @return the author of the package
     */
    public String getPackageAuthor() {
        return this.packageAuthor;
    }

    /**
     * @param packageAuthor the author of the package
     */
    public void setPackageAuthor(String packageAuthor) {
        this.packageAuthor = packageAuthor;
    }

    /**
     * @return the version of the package
     */
    public String getPackageVersion() {
        return this.packageVersion;
    }

    /**
     * @param packageVersion the version of the package
     */
    public void setPackageVersion(String packageVersion) {
        this.packageVersion = packageVersion;
    }

    /**
     * @return true of the package is a backup
     */
    public boolean isPackageBackupPack() {
        return this.packageBackupPack;
    }

    /**
     * @param packageBackupPack true of the package is a backup
     */
    public void setPackageBackupPack(boolean packageBackupPack) {
        this.packageBackupPack = packageBackupPack;
    }

    /**
     * @return the entries listed in the package descriptor
     * @since 7.2M1
     */
    public Collection<XarEntry> getPackageFiles() {
        return this.packageFiles.values();
    }

    /**
     * Add a new entry to the package.
     * 
     * @param reference the entry reference since 7.2M1
     * @param action the default action associated to this XAR (not used at the moment)
     */
    public void addPackageFile(LocalDocumentReference reference, int action) {
        this.packageFiles.put(reference, new XarEntry(reference, null, action));
    }

    /**
     * Add a new entry to the package.
     * 
     * @param reference the entry reference
     * @deprecated since 7.2M1, use {@link #addPackageFile(LocalDocumentReference, int)} instead
     */
    @Deprecated
    public void addEntry(LocalDocumentReference reference) {
        addEntry(reference, null);
    }

    /**
     * Add a new entry to the package.
     * 
     * @param reference the entry reference
     * @param entryName the name of the entry (ZIP style)
     * @since 7.2M1
     */
    public void addEntry(LocalDocumentReference reference, String entryName) {
        addEntry(reference, entryName, XarModel.ACTION_OVERWRITE);
    }

    /**
     * Add a new entry to the package.
     * 
     * @param reference the entry reference
     * @param entryName the name of the entry (ZIP style)
     * @param action the default action associated to this XAR (not used at the moment)
     * @since 7.2M1
     */
    public void addEntry(LocalDocumentReference reference, String entryName, int action) {
        XarEntry entry = new XarEntry(reference, entryName, action);

        this.entries.put(reference, entry);
        this.packageFiles.put(reference, entry);
    }

    /**
     * @return the entries of the package
     */
    public Collection<XarEntry> getEntries() {
        return this.entries.values();
    }

    /**
     * @param reference the reference of the document
     * @return the entry associated to the passage reference
     */
    public XarEntry getEntry(LocalDocumentReference reference) {
        return this.entries.get(reference);
    }

    /**
     * Read a XML descriptor of a XAR package (usually names package.xml).
     * 
     * @param stream the input stream to the XML file to parse
     * @throws XarException when failing to parse the descriptor
     * @throws IOException when failing to read the file
     */
    public void readDescriptor(InputStream stream) throws XarException, IOException {
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();

        DocumentBuilder dBuilder;
        try {
            dBuilder = dbFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new XarException("Failed to create a new Document builder", e);
        }

        Document doc;
        try {
            // DocumentBuilder#parse close the passed stream which is not what we want
            doc = dBuilder.parse(new CloseShieldInputStream(stream));
        } catch (SAXException e) {
            throw new XarException("Failed to parse XML document", e);
        }

        // Normalize the document
        doc.getDocumentElement().normalize();

        // Read the document
        NodeList children = doc.getChildNodes();
        for (int i = 0; i < children.getLength(); ++i) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                if (element.getTagName().equals(XarModel.ELEMENT_PACKAGE)) {
                    readDescriptorPackage(element);
                    break;
                }
            }
        }
    }

    private void readDescriptorPackage(Element packageElement) {
        NodeList children = packageElement.getChildNodes();
        for (int i = 0; i < children.getLength(); ++i) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                if (element.getTagName().equals(XarModel.ELEMENT_INFOS)) {
                    readDescriptorInfos(element);
                } else if (element.getTagName().equals(XarModel.ELEMENT_FILES)) {
                    readDescriptorFiles(element);
                }
            }
        }
    }

    private void readDescriptorInfos(Element infos) {
        this.packageExtensionId = getElementText(infos, XarModel.ELEMENT_INFOS_EXTENSIONID, true);
        this.packageVersion = getElementText(infos, XarModel.ELEMENT_INFOS_VERSION, false);
        this.packageName = getElementText(infos, XarModel.ELEMENT_INFOS_NAME, false);
        this.packageDescription = getElementText(infos, XarModel.ELEMENT_INFOS_DESCRIPTION, false);
        this.packageLicense = getElementText(infos, XarModel.ELEMENT_INFOS_LICENSE, false);
        this.packageAuthor = getElementText(infos, XarModel.ELEMENT_INFOS_AUTHOR, false);
        this.packageBackupPack = Boolean.valueOf(getElementText(infos, XarModel.ELEMENT_INFOS_ISBACKUPPACK, false))
                .booleanValue();
        this.packagePreserveVersion = Boolean
                .valueOf(getElementText(infos, XarModel.ELEMENT_INFOS_ISPRESERVEVERSION, false)).booleanValue();
    }

    private void readDescriptorFiles(Element files) {
        NodeList children = files.getChildNodes();
        for (int i = 0; i < children.getLength(); ++i) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                if (element.getTagName().equals(XarModel.ELEMENT_FILES_FILE)) {
                    String localeString = element.getAttribute(XarModel.ATTRIBUTE_LOCALE);
                    String defaultActionString = element.getAttribute(XarModel.ATTRIBUTE_DEFAULTACTION);
                    String referenceString = element.getTextContent();

                    // Parse reference
                    LocalDocumentReference reference = new LocalDocumentReference(
                            XarUtils.RESOLVER.resolve(referenceString, EntityType.DOCUMENT),
                            LocaleUtils.toLocale(localeString));

                    // Parse default action
                    int defaultAction = Integer.valueOf(defaultActionString);

                    // Get entry name associated to the document
                    String entryName = getEntryName(reference);

                    // Create entry
                    XarEntry packageFile = new XarEntry(reference, entryName, defaultAction);

                    // Register package file entry
                    this.packageFiles.put(packageFile, packageFile);

                    // Update existing entry default action
                    updateEntryDefaultAction(packageFile);
                }
            }
        }
    }

    private String getEntryName(LocalDocumentReference reference) {
        String entryName = null;

        XarEntry entry = this.entries.get(reference);
        if (entry != null) {
            entryName = entry.getEntryName();
        }

        return entryName;
    }

    private int getDefaultAction(LocalDocumentReference reference) {
        int defaultAction = XarModel.ACTION_SKIP;

        XarEntry packageFile = this.packageFiles.get(reference);
        if (packageFile != null) {
            defaultAction = packageFile.getDefaultAction();
        }

        return defaultAction;
    }

    private void updateEntryDefaultAction(XarEntry packageFile) {
        if (this.entries.containsKey(packageFile)) {
            this.entries.put(packageFile, packageFile);
        }
    }

    private void updatePackageFileEntryName(XarEntry xarEntry) {
        if (this.packageFiles.containsKey(xarEntry)) {
            this.packageFiles.put(xarEntry, xarEntry);
        }
    }

    private String getElementText(Element element, String tagName, boolean ignoreEmpty) {
        NodeList nList = element.getElementsByTagName(tagName);

        String value = nList.getLength() > 0 ? nList.item(0).getTextContent() : null;

        if (value != null && ignoreEmpty && StringUtils.isEmpty(value)) {
            value = null;
        }

        return value;
    }

    /**
     * Write and add the package descriptor to the passed ZIP stream.
     * 
     * @param zipStream the ZIP stream in which to write
     * @param encoding the encoding to use to write the descriptor
     * @throws XarException when failing to parse the descriptor
     * @throws IOException when failing to read the file
     */
    public void write(ZipArchiveOutputStream zipStream, String encoding) throws XarException, IOException {
        ZipArchiveEntry zipentry = new ZipArchiveEntry(XarModel.PATH_PACKAGE);
        zipStream.putArchiveEntry(zipentry);

        try {
            write((OutputStream) zipStream, encoding);
        } finally {
            zipStream.closeArchiveEntry();
        }
    }

    /**
     * Write the package descriptor to the passed stream as XML.
     * 
     * @param stream the stream to the resulting XML file
     * @param encoding the encoding to use to write the descriptor
     * @throws XarException when failing to parse the descriptor
     * @throws IOException when failing to read the file
     */
    public void write(OutputStream stream, String encoding) throws XarException, IOException {
        XMLStreamWriter writer;
        try {
            writer = XMLOutputFactory.newInstance().createXMLStreamWriter(stream, encoding);
        } catch (Exception e) {
            throw new XarException("Failed to create an instance of XML stream writer", e);
        }

        writer = new IndentingXMLStreamWriter(writer);

        try {
            writer.writeStartDocument(encoding, "1.0");
            write(writer);
            writer.writeEndDocument();

            writer.flush();
        } catch (Exception e) {
            throw new XarException("Failed to write XML", e);
        } finally {
            try {
                writer.close();
            } catch (XMLStreamException e) {
                throw new XarException("Failed to close XML writer", e);
            }
        }
    }

    /**
     * Write the package descriptor to the passed XML stream.
     * 
     * @param writer the XML stream where to write
     * @throws XMLStreamException when failing to write the file
     */
    public void write(XMLStreamWriter writer) throws XMLStreamException {
        writer.writeStartElement(XarModel.ELEMENT_PACKAGE);

        writer.writeStartElement(XarModel.ELEMENT_INFOS);
        writeElement(writer, XarModel.ELEMENT_INFOS_NAME, getPackageName(), true);
        writeElement(writer, XarModel.ELEMENT_INFOS_DESCRIPTION, getPackageDescription(), true);
        writeElement(writer, XarModel.ELEMENT_INFOS_LICENSE, getPackageLicense(), true);
        writeElement(writer, XarModel.ELEMENT_INFOS_AUTHOR, getPackageAuthor(), true);
        writeElement(writer, XarModel.ELEMENT_INFOS_VERSION, getPackageVersion(), true);
        writeElement(writer, XarModel.ELEMENT_INFOS_ISBACKUPPACK, String.valueOf(isPackageBackupPack()), true);
        writeElement(writer, XarModel.ELEMENT_INFOS_ISPRESERVEVERSION, String.valueOf(isPackagePreserveVersion()),
                true);
        writeElement(writer, XarModel.ELEMENT_INFOS_EXTENSIONID, getPackageExtensionId(), false);
        writer.writeEndElement();

        writer.writeStartElement(XarModel.ELEMENT_FILES);
        for (XarEntry entry : this.entries.values()) {
            writer.writeStartElement(XarModel.ELEMENT_FILES_FILE);
            writer.writeAttribute(XarModel.ATTRIBUTE_DEFAULTACTION, String.valueOf(entry.getDefaultAction()));
            writer.writeAttribute(XarModel.ATTRIBUTE_LOCALE, Objects.toString(entry.getLocale(), ""));
            writer.writeCharacters(TOSTRING_SERIALIZER.serialize(entry));
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeElement(XMLStreamWriter streamWriter, String localName, String value, boolean emptyIfNull)
            throws XMLStreamException {
        if (value != null) {
            if (value.isEmpty()) {
                streamWriter.writeEmptyElement(localName);
            } else {
                streamWriter.writeStartElement(localName);
                streamWriter.writeCharacters(value);
                streamWriter.writeEndElement();
            }
        } else if (emptyIfNull) {
            streamWriter.writeEmptyElement(localName);
        }
    }
}