
org.xwiki.tool.xar.XARMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xwiki-commons-tool-xar-plugin Show documentation
Show all versions of xwiki-commons-tool-xar-plugin Show documentation
XWiki Commons - Tools - XAR Tools - XAR Plugin
/*
* 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.tool.xar;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.OutputStream;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.codehaus.plexus.archiver.ArchiveEntry;
import org.codehaus.plexus.archiver.zip.ZipArchiver;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.dom.DOMDocument;
import org.dom4j.dom.DOMElement;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.xwiki.tool.xar.internal.XWikiDocument;
/**
* Gather all resources in a XAR file (which is actually a ZIP file). Also generates a XAR descriptor if none is
* provided.
*
* @version $Id: 0e4ff02642812ad41c9edd0106c077b3f752945d $
*/
@Mojo(
name = "xar",
defaultPhase = LifecyclePhase.PACKAGE,
requiresDependencyResolution = ResolutionScope.COMPILE,
threadSafe = true
)
public class XARMojo extends AbstractXARMojo
{
/**
* Indicate if XAR dependencies should be included in the produced XAR package.
*/
@Parameter(property = "includeDependencies", defaultValue = "false")
private boolean includeDependencies;
/**
* List of XML transformations to execute on the XML files.
*/
@Parameter
private List transformations;
@Override
public void execute() throws MojoExecutionException
{
if (this.project.getResources().size() < 1) {
getLog().warn("No XAR created as no resources were found");
return;
}
try {
performArchive();
} catch (Exception e) {
throw new MojoExecutionException("Error while creating XAR file", e);
}
}
/**
* Create the XAR by zipping the resource files.
*
* @throws Exception if the zipping failed for some reason
*/
private void performArchive() throws Exception
{
// The source dir points to the target/classes directory where the Maven resources plugin
// has copied the XAR files during the process-resources phase.
// For package.xml, however, we look in the resources directory (i.e. src/main/resources).
File sourceDir = new File(this.project.getBuild().getOutputDirectory());
// Check that there are files in the source dir
if (sourceDir.listFiles() == null) {
throw new Exception(String.format(
"No XAR XML files found in [%s]. Has the Maven Resource plugin be called?", sourceDir));
}
File xarFile = new File(this.project.getBuild().getDirectory(), this.project.getArtifactId()
+ "-" + this.project.getVersion() + ".xar");
ZipArchiver archiver = new ZipArchiver();
archiver.setEncoding(this.encoding);
archiver.setDestFile(xarFile);
archiver.setIncludeEmptyDirs(false);
archiver.setCompress(true);
if (this.includeDependencies) {
// Unzip dependent XARs on top of this project's XML documents but without overwriting
// existing files since we want this project's files to be used if they override a file
// present in a XAR dependency.
unpackDependentXARs();
}
// Perform XML transformations
performTransformations();
// If no package.xml can be found at the top level of the current project, generate one
// otherwise, try to use the existing one
File resourcesDir = getResourcesDirectory();
if (!hasPackageXmlFile(resourcesDir)) {
// Add files and generate the package.xml file
addFilesToArchive(archiver, sourceDir);
} else {
File packageXml = new File(resourcesDir, PACKAGE_XML);
addFilesToArchive(archiver, sourceDir, packageXml);
}
archiver.createArchive();
this.project.getArtifact().setFile(xarFile);
}
private boolean hasPackageXmlFile(File resourcesDir) throws MojoExecutionException
{
// If the src/main/resources directory doesn't exist then we consider there isn't any package.xml file...
if (!resourcesDir.exists()) {
return false;
}
FilenameFilter packageXmlFiler = new FilenameFilter()
{
@Override
public boolean accept(File dir, String name)
{
return (name.equals(PACKAGE_XML));
}
};
String[] fileNames = resourcesDir.list(packageXmlFiler);
if (fileNames == null) {
throw new MojoExecutionException(String.format("Couldn't get list of files in resources dir [%s]",
resourcesDir));
}
return fileNames.length != 0;
}
private void performTransformations() throws Exception
{
if (this.transformations == null) {
return;
}
// Copy XML pages from dependent XAR if we modify them.
unpackTransformedXARs();
SAXReader reader = new SAXReader();
// For each defined file, perform the transformation asked
for (Transformation transformation : this.transformations) {
File file = new File(this.project.getBuild().getOutputDirectory(), transformation.getFile());
Document document = reader.read(file);
Node node = document.selectSingleNode(transformation.getXpath());
String value = transformation.getValue();
if (!StringUtils.isEmpty(value)) {
// Get the current value at node and replace $1 with it (if any)
String currentValue = node.getText();
node.setText(value.replace("$1", currentValue));
}
// Write the modified file to disk
XMLWriter writer = new XMLWriter(new FileOutputStream(file));
writer.write(document);
writer.flush();
writer.close();
}
}
private void unpackTransformedXARs() throws MojoExecutionException
{
for (Transformation transformation : this.transformations) {
Set artifacts = this.project.getArtifacts();
if (artifacts != null) {
for (Artifact artifact : artifacts) {
if (!artifact.isOptional()) {
if ("xar".equals(artifact.getType())) {
String id = String.format("%s:%s", artifact.getGroupId(), artifact.getArtifactId());
if (id.equals(transformation.getArtifact())) {
unpackXARToOutputDirectory(artifact,
new String[] {transformation.getFile()}, new String[] {});
}
}
}
}
}
}
}
/**
* Unpack XAR dependencies before pack then into it.
*
* @throws MojoExecutionException error when unpack dependencies.
*/
private void unpackDependentXARs() throws MojoExecutionException
{
Set artifacts = this.project.getArtifacts();
if (artifacts != null) {
for (Artifact artifact : artifacts) {
if (!artifact.isOptional()) {
if ("xar".equals(artifact.getType())) {
unpackXARToOutputDirectory(artifact, getIncludes(), getExcludes());
}
}
}
}
}
/**
* Create and add package configuration file to the package.
*
* @param packageFile the package when to add configuration file.
* @param files the files in the package.
* @throws Exception error when writing the configuration file.
*/
private void generatePackageXml(File packageFile, Collection files) throws Exception
{
getLog().info(String.format("Generating package.xml descriptor at [%s]", packageFile.getPath()));
OutputFormat outputFormat = new OutputFormat("", true);
outputFormat.setEncoding(this.encoding);
OutputStream out = new FileOutputStream(packageFile);
XMLWriter writer = new XMLWriter(out, outputFormat);
writer.write(toXML(files));
writer.close();
out.close();
}
/**
* Generate a DOM4J Document containing the generated XML.
*
* @param files the list of files that we want to include in the generated package XML file.
* @return the DOM4J Document containing the generated XML
*/
private Document toXML(Collection files)
{
Document doc = new DOMDocument();
Element packageElement = new DOMElement("package");
doc.setRootElement(packageElement);
Element infoElement = new DOMElement("infos");
packageElement.add(infoElement);
addInfoElements(infoElement);
Element filesElement = new DOMElement(FILES_TAG);
packageElement.add(filesElement);
addFileElements(files, filesElement);
return doc;
}
/**
* Add all the XML elements under the <info> element (name, description, license, author, version and whether
* it's a backup pack or not).
*
* @param infoElement the info element to which to add to
*/
private void addInfoElements(Element infoElement)
{
Element el = new DOMElement("name");
el.addText(this.project.getName());
infoElement.add(el);
el = new DOMElement("description");
String description = this.project.getDescription();
if (description == null) {
el.addText("");
} else {
el.addText(description);
}
infoElement.add(el);
el = new DOMElement("licence");
el.addText("");
infoElement.add(el);
el = new DOMElement("author");
el.addText("XWiki.Admin");
infoElement.add(el);
el = new DOMElement("extensionId");
el.addText(this.project.getGroupId() + ':' + this.project.getArtifactId());
infoElement.add(el);
el = new DOMElement("version");
el.addText(this.project.getVersion());
infoElement.add(el);
el = new DOMElement("backupPack");
el.addText("false");
infoElement.add(el);
}
/**
* Add all the XML elements under the <files> element (the list of files present in the XAR).
*
* @param files the list of files that we want to include in the generated package XML file.
* @param filesElement the files element to which to add to
*/
private void addFileElements(Collection files, Element filesElement)
{
for (ArchiveEntry entry : files) {
// Don't add files in META-INF to the package.xml file
if (entry.getName().indexOf("META-INF") == -1) {
XWikiDocument xdoc = getDocFromXML(entry.getFile());
if (xdoc != null) {
String reference = xdoc.getReference();
Element element = new DOMElement(FILE_TAG);
element.setText(reference);
element.addAttribute("language", xdoc.getLocale());
element.addAttribute("defaultAction", "0");
filesElement.add(element);
}
}
}
}
/**
* Gets the list of document names from a 'package.xml'-like document.
*
* @param file the XML document to parse
* @return the list of document names contained in the XML document
* @throws Exception if the XML document is invalid or it contains no document list or it doesn't exist
*/
protected static Collection getDocumentNamesFromXML(File file) throws Exception
{
SAXReader reader = new SAXReader();
Document domdoc;
domdoc = reader.read(file);
Element filesElement = domdoc.getRootElement().element(FILES_TAG);
if (filesElement == null) {
throw new Exception("The supplied document contains no document list ");
}
Collection result = new LinkedList<>();
Collection elements = filesElement.elements(FILE_TAG);
for (Object item : elements) {
if (item instanceof Element) {
Element currentElement = (Element) item;
String documentName = currentElement.getText();
result.add(documentName);
}
}
return result;
}
/**
* Adds the files from a specific directory to an archive. It also builds a package.xml file based on that content
* which is also added to the archive.
*
* @param archiver the archive in which the files will be added
* @param sourceDir the directory whose contents will be added to the archive
* @throws Exception if the files cannot be added to the archive
*/
private void addFilesToArchive(ZipArchiver archiver, File sourceDir) throws Exception
{
File generatedPackageFile = new File(sourceDir, PACKAGE_XML);
if (generatedPackageFile.exists()) {
generatedPackageFile.delete();
}
archiver.addDirectory(sourceDir, getIncludes(), getExcludes());
generatePackageXml(generatedPackageFile, archiver.getFiles().values());
archiver.addFile(generatedPackageFile, PACKAGE_XML);
}
/**
* Adds files from a specific directory to an archive. It uses an existing package.xml to filter the files to be
* added.
*
* @param archiver the archive in which the files will be added
* @param sourceDir the directory whose contents will be added to the archive
* @param packageXml the corresponding package.xml file
* @throws Exception if the files cannot be added to the archive
*/
private void addFilesToArchive(ZipArchiver archiver, File sourceDir, File packageXml) throws Exception
{
Collection documentNames;
getLog().info(String.format("Using the existing package.xml descriptor at [%s]", packageXml.getPath()));
try {
documentNames = getDocumentNamesFromXML(packageXml);
} catch (Exception e) {
getLog().error(String.format("The existing [%s] is invalid.", PACKAGE_XML));
throw e;
}
// Next, we scan the hole directory and subdirectories for documents.
Queue fileQueue = new LinkedList();
addContentsToQueue(fileQueue, sourceDir);
while (!fileQueue.isEmpty() && !documentNames.isEmpty()) {
File currentFile = fileQueue.poll();
if (currentFile.isDirectory()) {
addContentsToQueue(fileQueue, currentFile);
} else {
String documentReference = XWikiDocument.getReference(currentFile);
if (documentNames.contains(documentReference)) {
// building the path the current file will have within the archive
//
// Note: DO NOT USE String.split since it requires a regexp. Under Windows XP, the FileSeparator is
// '\' when not escaped is a special character of the regexp
// String archivedFilePath =
// currentFile.getAbsolutePath().split(sourceDir.getAbsolutePath() + File.separator)[1];
String archivedFilePath = currentFile.getAbsolutePath().substring(
(sourceDir.getAbsolutePath() + File.separator).length());
archivedFilePath = archivedFilePath.replace(File.separatorChar, '/');
archiver.addFile(currentFile, archivedFilePath);
documentNames.remove(documentReference);
}
}
}
if (!documentNames.isEmpty()) {
StringBuilder errorMessage = new StringBuilder("The following documents could not be found: ");
for (String name : documentNames) {
errorMessage.append(name);
errorMessage.append(" ");
}
throw new Exception(errorMessage.toString());
}
archiver.addFile(packageXml, PACKAGE_XML);
}
/**
* Adds the contents of a specific directory to a queue of files.
*
* @param fileQueue the queue of files
* @param sourceDir the directory to be scanned
*/
private static void addContentsToQueue(Queue fileQueue, File sourceDir) throws MojoExecutionException
{
File[] files = sourceDir.listFiles();
if (files != null) {
for (File currentFile : files) {
fileQueue.add(currentFile);
}
} else {
throw new MojoExecutionException(String.format("Couldn't get list of files in source dir [%s]", sourceDir));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy