/**
 * Copyright (c) 2005-2012 EBM WebSourcing, 2007-2009 Capgemini Sud, 2012-2022 Linagora
 * 
 * This program/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 program/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.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program/library; If not, see http://www.gnu.org/licenses/
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.plugin.jbiplugin;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.WSDL4ComplexWsdlFactory;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.api.Description;
import org.ow2.easywsdl.extensions.wsdl4complexwsdl.api.WSDL4ComplexWsdlReader;
import org.ow2.petals.jbi.descriptor.extension.JBIDescriptorExtensionBuilder;
import org.ow2.petals.jbi.descriptor.original.JBIDescriptorBuilder;
import org.ow2.petals.jbi.descriptor.original.generated.ClassPath;
import org.ow2.petals.jbi.descriptor.original.generated.Component;
import org.ow2.petals.jbi.descriptor.original.generated.Component.SharedLibrary;
import org.ow2.petals.jbi.descriptor.original.generated.Consumes;
import org.ow2.petals.jbi.descriptor.original.generated.Identification;
import org.ow2.petals.jbi.descriptor.original.generated.Jbi;
import org.ow2.petals.jbi.descriptor.original.generated.Provides;
import org.ow2.petals.jbi.descriptor.original.generated.ServiceAssembly;
import org.ow2.petals.jbi.descriptor.original.generated.ServiceUnit;
import org.ow2.petals.jbi.descriptor.original.generated.Target;
import org.ow2.petals.plugin.jbiplugin.util.ZipUtil;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.ebmwebsourcing.easycommons.lang.UncheckedException;
import com.ebmwebsourcing.easycommons.xml.DOMHelper;

/**
 * Make a JBI Archive of a project.
 * 
 * @author Adrien Louis - EBM WebSourcing
 * @author Christophe Deneux - Capgemini Sud
 * @author Roland Naudin - EBM WebSourcing
 */
@Mojo(name = "jbi-package", requiresDependencyResolution = ResolutionScope.RUNTIME)
public class JBIPackageMojo extends JBIAbstractConfigurableMojo {

    /**
     * The name of the Jar package of the JBI artifact
     */
    @Parameter(required = true, defaultValue = "${project.build.directory}/${project.build.finalName}.jar")
    protected File artifactJar;
    
    /**
     * Set to <code>true</code> to have the jar installed and deployed along the zip.
     */
    @Parameter(required = true, defaultValue = "false")
    protected boolean attachJar = false;

    /**
     * Path which allows to provide a directory that will be scanned in order to add all additional JBI resources
     * located in previous directory to the generated jbi archive.
     */
    @Parameter(property = "additionalJBIResourceDirectory")
    protected File additionalJBIResourceDirectory;

    /**
     * Boolean which active the update of the JBI descriptor fields against the maven parameters
     */
    @Parameter(required = true, defaultValue = "true")
    protected boolean updateJBIXml = true;

    /**
     * <p>Sets the mapping pattern for all SU file names included in this service assembly.</p>
     * <p>Example of usage (caution to the usage of "$" and "$$"):
     * <code>&lt;serviceUnitFileNameMappingInSA&gt;$${artifactId}.$${extension}&lt;/serviceUnitFileNameMappingInSA&gt;</code>
     * </p>
     * <p>Some extra pattern are defined: "version.major" is the major part of the version.</p>
     */
    @Parameter(required = true, defaultValue = "$${artifactId}-$${version}.$${extension}")
    protected String serviceUnitFileNameMappingInSA;

    /**
     * <p>If the JBI update is activated, sets the mapping pattern for all SU names included in this service assembly.</p>
     * <p>Example of usage (caution to the usage of "$" and "$$"):
     * <code>&lt;serviceUnitNameMappingInSA&gt;$${artifactId}&lt;/serviceUnitNameMappingInSA&gt;</code>
     * </p>
     * <p>Some extra pattern are defined: "version.major" is the major part of the version.</p>
     */
    @Parameter(required = true, defaultValue = "$${artifactId}-$${version}")
    protected String serviceUnitNameMappingInSA;

    /**
     * <p>If the JBI update is activated, sets the mapping pattern of the Service Assembly.</p>
     * <p>Example of usage (caution to the usage of "$" and "$$"):
     * <code>&lt;serviceAssemblyNameMapping&gt;$${artifactId}&lt;/serviceAssemblyNameMapping&gt;</code>
     * </p>
     * <p>Some extra pattern are defined: "version.major" is the major part of the version.</p>
     */
    @Parameter(required = true, defaultValue = "$${artifactId}-$${version}")
    protected String serviceAssemblyNameMapping;

    /**
     * <p>If the JBI update is activated, sets the mapping pattern of the component name.</p>
     * <p>Example of usage (caution to the usage of "$" and "$$"):
     * <code>&lt;componentNameMapping&gt;$${artifactId}&lt;/componentNameMapping&gt;</code>
     * </p>
     * <p>Some extra pattern are defined: "version.major" is the major part of the version.</p>
     */
    @Parameter(required = true, defaultValue = "$${artifactId}")
    protected String componentNameMapping;

    /**
     * <p>If the JBI update is activated, sets the mapping pattern of the Shared Library name.</p>
     * <p>Example of usage (caution to the usage of "$" and "$$"):
     * <code>&lt;sharedLibraryNameMapping&gt;$${artifactId}&lt;/sharedLibraryNameMapping&gt;</code>
     * </p>
     * <p>Some extra pattern are defined: "version.major" is the major part of the version.</p>
     */
    @Parameter(required = true, defaultValue = "$${artifactId}")
    protected String sharedLibraryNameMapping;

    /**
     * List of entries to exclude (comma separated values)
     */
    @Parameter
    protected String toExcludes;

    /**
     * If the JBI update is activated, this parameter will enable/disable the generation of Petals extensions resulting
     * in a deployable service unit. If <code>false</code>, a standard service unit will be generated, otherwise a
     * deployable service unit will be packaged.
     */
    @Parameter(required = true, property = "isDeployableServiceUnit", defaultValue = "false")
    protected boolean isDeployableServiceUnit;

    /**
     * The JBI component to use to run the service unit. Required by service unit project.
     */
    @Parameter(required = false, defaultValue = "")
    protected String jbiComponentUsed;

    /**
     * Set of entries in the zip
     */
    private Set<String> entries = new HashSet<String>();

    /**
     * List of entries to exclude
     */
    private Set<String> entriesToExclude = new HashSet<String>();

    /**
     * Recurse a directory to add its content to a zip file. If updateJBIXml
     * file is true do not add the jbi.xml file to the archive (it will be
     * updated and added later).
     * 
     * @param zipOutputStream
     *            zip file
     * @param directory
     *            directory to recurse
     * @param entryDirectoryName
     *            directory to put the content in the zip
     * @throws Exception
     */
    private void recurseDirectory(final ZipOutputStream zipOutputStream, File directory, String entryDirectoryName)
            throws Exception {
        // Use "/" and not File.separator to have a directory level in the ZIP file
        final String entryDir = entryDirectoryName.equals("") ? "" : entryDirectoryName + "/";
        final FilenameFilter filter = new FilenameFilter() {

            @Override
            public boolean accept(final File dir, final String name) {
                // Avoid to add the '.svn', '.gitignore' and CVS directories to zip.
                return !name.startsWith(".") && !"csv".equalsIgnoreCase(name);
            }
        };
        for (final File file : directory.listFiles(filter)) {
            if (file.isFile()) {
                if (file.getName().endsWith(".wsdl") && this.downloadAndPackageWsdlResources) {

                    this.info(" Downloading & Packaging resources of WSDL: " + file.getName());

            		// We package all imported WSDL resources
            		final WSDL4ComplexWsdlReader reader = WSDL4ComplexWsdlFactory.newInstance().newWSDLReader();
					
					// It is needed to duplicate 'zipEntryInputStream', because the stream is closed during WSDL parsing and the input stream must not be closed
					final Description desc = reader.read(file.toURI().toURL());
					
					ZipUtil.addFile(zipOutputStream, this.configureWSDLServiceUnit(desc, new OnWsdlImport() {
						
						@Override
						public void onWsdlImport(final URI importURI, final Document importContent) throws IOException {
							
							final ByteArrayOutputStream baos = new ByteArrayOutputStream();
							DOMHelper.prettyPrint(importContent, baos);
							baos.close();
							
							try {
								// FIXME: If the WSDL import a local XSD, a ZipException will occur
								//        because of a duplicated entry. Only remote import should be added.
								ZipUtil.addFile(zipOutputStream, new ByteArrayInputStream(baos.toByteArray()), importURI.toString());
							}
							catch (final ZipException e) {
								JBIPackageMojo.this.info("Skip exception: " + e.getMessage());
							}
						}
					}), file.getName());
                    this.info("     " + file.getName() + " added to JBI archive");

                } else if (!"jbi.xml".equals(file.getName()) || !this.updateJBIXml) {
                    this.info("     " + file.getName() + " added to JBI archive");
                    final String filename;
                    if (file.getName().equals("jbi.xml")) {
                        // Use "/" and not File.separator to have a directory level in the ZIP file.
                        filename = "META-INF/" + file.getName();
                    } else {
                        filename = file.getName();
                    }

                    try (final FileInputStream inputStream = new FileInputStream(file)) {
                        ZipUtil.addFile(zipOutputStream, inputStream, entryDir + filename);
                    }
                }
            } else {
                this.debug("Directory entry: " + file.getName());
                recurseDirectory(zipOutputStream, file, entryDir + file.getName());
            }
        }
    }

    /**
     * Extract the list of jar to exclude from the archive
     * 
     */
    private void extractExcludeValues() {
        if (toExcludes != null) {
            for (String jar : toExcludes.split(",")) {
                // We remove the space
                entriesToExclude.add(jar.trim());
            }
        }
    }

    @Override
    public void executeMojo() throws MojoExecutionException, MojoFailureException {

        this.debug("Configuration mode: "
                + (this.includeConfiguration ? "activated" : "unactivated"));
        if (this.includeConfiguration) {
            this.readConfigurationFiles();
        }

        this.attachedJBIArchive(project.getArtifact());
        
        if (this.attachJar && this.artifactJar.exists()) {
            this.projectHelper.attachArtifact(this.project, "jar", this.artifactJar);
        }

        extractExcludeValues();

        if (outputDirectory.exists()) {
            if (this.projectArtifact.getFile().exists()) {
                this.projectArtifact.getFile().delete();
            }
        } else {
            outputDirectory.mkdirs();
        }

        // SA and SL do not need a jbi directory nor a jbi.xml file
        if (!jbiDirectory.exists() && !PACKAGING_SA.equals(project.getPackaging())
                && !PACKAGING_SL.equals(project.getPackaging())) {
            final String msg = "JBI directory [" + jbiDirectory + "] does not exist.";
            this.error(msg);
            throw new MojoFailureException(msg);
        }
        switch (project.getPackaging()) {
            case PACKAGING_COMPONENT: packageComponent(); break;
            case PACKAGING_SA: packageServiceAssembly(); break;
            case PACKAGING_SU: packageServiceUnit(); break;
            case PACKAGING_SL: packageSharedLibrary(); break;
            default: throw new UncheckedException("Impossible case: "+ project.getPackaging());
        }
    }

    /**
     * Create a service-unit package.
     */
    private void packageComponent() throws MojoExecutionException {

        this.info("Start building JBI component archive " + this.projectArtifact.getFile().getAbsolutePath());
        try (ZipOutputStream zipOutputStream = new ZipOutputStream(
                new FileOutputStream(this.projectArtifact.getFile()))) {

            // Add the jbi files
            recurseDirectory(zipOutputStream, jbiDirectory, "");
            zipOutputStream.flush();

            // Add the component jar
            addArtifactResources(zipOutputStream);

            // Add the component dependencies
            addDependencies(zipOutputStream);

            // Add additional JBI resources
            addJBIResources(zipOutputStream);

            // Update the JBI XML file
            updateComponentJBIXmlFile(zipOutputStream);

            zipOutputStream.flush();

            this.info("JBI component archive building done.");
        } catch (final Exception e) {
            throw new MojoExecutionException(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Create a service-unit package.
     */
    private void packageServiceUnit() throws MojoExecutionException {

        this.info("Start building JBI service-unit archive " + project.getArtifact().getFile().getAbsolutePath());

        // JBI Archive creation
        try (final ZipOutputStream zipOutputStream = new ZipOutputStream(
                new FileOutputStream(project.getArtifact().getFile()))) {
            // Load the JBI descriptor
            final Jbi jbiDescriptor;
            try (final InputStream jbiDescriptorToUpdateInputStream = new FileInputStream(
                    jbiDirectory + File.separator + "jbi.xml")) {
                jbiDescriptor = JBIDescriptorBuilder.getInstance()
                        .buildJavaJBIDescriptor(jbiDescriptorToUpdateInputStream);
            }

            if (this.updateJBIXml) {
                // Update SU extensions according to project properties
                for (Consumes consumes : jbiDescriptor.getServices().getConsumes()) {
                    List<Element> nodeList = consumes.getAnyOrAny();
                    evaluateElementList(nodeList);
                }
                for (Provides provides : jbiDescriptor.getServices().getProvides()) {
                    List<Element> nodeList = provides.getAnyOrAny();
                    evaluateElementList(nodeList);
                }

                final Artifact serviceUnitComponentArtifact = this.getTargetComponentFromDependencies(this.project,
                        // Note: parameter 'updateJBIXml' must be set to 'true' to be able to write the target component
                        // as extension in the service unit JBI descriptor. That's why we can consider the JBI component
                        // to use as not set if parameter 'updateJBIXml' is set to 'false'.
                        this.updateJBIXml ? this.jbiComponentUsed : null);
                if (serviceUnitComponentArtifact != null) {
                    JBIDescriptorExtensionBuilder.getInstance().setServiceUnitTargetComponentAsGA(jbiDescriptor,
                            serviceUnitComponentArtifact.getGroupId() + ":"
                                    + serviceUnitComponentArtifact.getArtifactId());
                }

                if (this.isDeployableServiceUnit) {

                    if (serviceUnitComponentArtifact != null) {
                        final org.ow2.petals.jbi.descriptor.extension.generated.Identification suIdent = new org.ow2.petals.jbi.descriptor.extension.generated.Identification();
                        suIdent.setName(
                                this.evaluateJBIIdentifier(this.serviceUnitNameMappingInSA, this.projectArtifact));
                        suIdent.setDescription(this.project.getDescription());
                        JBIDescriptorExtensionBuilder.getInstance()
                                .setDeployableServiceUnitIdentification(jbiDescriptor, suIdent);

                        final Jbi jbiComponentDescriptor = this.readJbiDescriptor(serviceUnitComponentArtifact);
                        JBIDescriptorExtensionBuilder.getInstance().setDeployableServiceUnitTargetComponent(
                                jbiDescriptor,
                                this.evaluateJBIIdentifier(
                                        jbiComponentDescriptor.getComponent().getIdentification().getName(),
                                        serviceUnitComponentArtifact));
                    } else {
                        // No component artifact is found, a standard service unit JBI descriptor will be
                        // generated.
                        this.info(
                                "No component defined in the POM file of the service unit. It will be not deployable.");
                    }
                }

                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                JBIDescriptorBuilder.getInstance().writeXMLJBIdescriptor(jbiDescriptor, baos);
                this.debug("Generated JBI Descriptor:\n" + baos.toString());
                // Add the jbi.xml file
                final InputStream jbiDescriptorInputStream = new ByteArrayInputStream(baos.toByteArray());
                ZipUtil.addFile(zipOutputStream, jbiDescriptorInputStream,
                        JBIDescriptorBuilder.JBI_DESCRIPTOR_RESOURCE_IN_ARCHIVE);
                this.info("\tJBI descriptor added to JBI Service Unit archive");
            }

            // Add the jbi files
            recurseDirectory(zipOutputStream, jbiDirectory, "");
            zipOutputStream.flush();

            // Add the service unit resources
            addArtifactResources(zipOutputStream);

            // Add the service unit dependencies
            addDependencies(zipOutputStream);

            // Add additional JBI resources
            addJBIResources(zipOutputStream);
        } catch (ZipException e) {
            // this is thrown if the zip is empty on close, and it is ok for a SU zip to be.
        } catch (Exception e) {
            throw new MojoExecutionException(e.getLocalizedMessage(), e);
        }

        this.info("JBI Service unit archive building done.");
    }

    /**
     * Create a service-assembly package.
     */
    private void packageServiceAssembly() throws MojoExecutionException {

        this.info("Start building JBI service-assembly archive "
                + project.getArtifact().getFile().getAbsolutePath());

        // JBI Archive creation
        try (final ZipOutputStream zipOutputStream = new ZipOutputStream(
                new FileOutputStream(project.getArtifact().getFile()))) {

            // Load the JBI descriptor
            final File jbiDescriptorFile = new File(jbiDirectory + File.separator + "jbi.xml");
            final Jbi jbiDescriptor;
            if (jbiDescriptorFile.exists()) {
                try (InputStream jbiDescriptorToUpdateInputStream = new FileInputStream(jbiDescriptorFile)) {
                    jbiDescriptor = JBIDescriptorBuilder.getInstance()
                            .buildJavaJBIDescriptor(jbiDescriptorToUpdateInputStream);
                }
            } else {
                jbiDescriptor = new Jbi();
                jbiDescriptor.setVersion(BigDecimal.valueOf(1.0));
                jbiDescriptor.setServiceAssembly(new ServiceAssembly());
                jbiDescriptor.getServiceAssembly().setIdentification(new Identification());
            }

            if (this.updateJBIXml || !jbiDescriptorFile.exists()) {

                if (jbiDescriptor.getServiceAssembly() == null) {
                    throw new MojoExecutionException("No service-assembly in the JBI descriptor.");
                }
                if (jbiDescriptor.getServiceAssembly().getIdentification() == null) {
                    throw new MojoExecutionException("No identification in the JBI descriptor.");
                }

                String artifactName = this.evaluateJBIIdentifier(this.serviceAssemblyNameMapping,
                        this.project.getArtifact());

                jbiDescriptor.getServiceAssembly().getIdentification().setName(artifactName);

                final String description = this.project.getDescription();
                if (description != null) {
                    jbiDescriptor.getServiceAssembly().getIdentification().setDescription(description);
                } else {
                    jbiDescriptor.getServiceAssembly().getIdentification().setDescription("");
                }

                jbiDescriptor.getServiceAssembly().getServiceUnit().clear();
            }

            // Add the service unit dependencies files
            List<Dependency> dependencies = project.getDependencies();
            for (Dependency dependency : dependencies) {

                if (PACKAGING_SU.equals(dependency.getType())) {
                    Artifact serviceUnitArtifact = createDependencyArtifact(dependency);

                    this.artifactResolver.resolve(serviceUnitArtifact, this.project.getRemoteArtifactRepositories(),
                            this.localRepository);

                    MavenProject serviceUnitProject = this.mavenProjectBuilder.buildFromRepository(serviceUnitArtifact,
                            this.project.getRemoteArtifactRepositories(), this.localRepository);

                    final String zipDependencyFileName = serviceUnitArtifact.getFile().getName();

                    if (this.updateJBIXml || !jbiDescriptorFile.exists()) {

                        ServiceUnit serviceUnit = new ServiceUnit();
                        Identification identification = new Identification();
                        identification.setName(this.evaluateJBIIdentifier(this.serviceUnitNameMappingInSA,
                                serviceUnitProject.getArtifact()));

                        final String projectDescription = serviceUnitProject.getDescription();
                        if (projectDescription != null) {
                            identification.setDescription(projectDescription);
                        } else {
                            identification.setDescription("");
                        }
                        serviceUnit.setIdentification(identification);

                        // We find the target component name following dependencies
                        final Artifact serviceUnitComponentArtifact = this
                                .getTargetComponentFromDependencies(serviceUnitProject,
                                        this.extractComponentToUseFromServiceUnitJbiDescr(serviceUnitArtifact));
                        if (serviceUnitComponentArtifact == null) {
                            throw new MojoExecutionException("No component defined in SU as dependency.");
                        }

                        final Jbi jbiComponentDescriptor = this.readJbiDescriptor(serviceUnitComponentArtifact);
                        this.debug("Component name: "
                                + jbiComponentDescriptor.getComponent().getIdentification().getName());
                        final Target target = new Target();

                        target.setComponentName(this.evaluateJBIIdentifier(
                                jbiComponentDescriptor.getComponent().getIdentification().getName(),
                                serviceUnitComponentArtifact));

                        target.setArtifactsZip(zipDependencyFileName);
                        serviceUnit.setTarget(target);

                        jbiDescriptor.getServiceAssembly().getServiceUnit().add(serviceUnit);
                    }

                    this.debug("SU artifact file: " + serviceUnitArtifact.getFile());
                    try (FileInputStream inputStream = new FileInputStream(serviceUnitArtifact.getFile())) {
                        ZipUtil.addFile(zipOutputStream, inputStream, zipDependencyFileName);
                    }

                    this.info("\t" + serviceUnitArtifact.getFile().getName()
                            + " added to JBI Service Assembly archive (as " + zipDependencyFileName + ")");
                }
            }

            final InputStream jbiDescriptorInputStream;
            if (this.updateJBIXml || !jbiDescriptorFile.exists()) {
                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                JBIDescriptorBuilder.getInstance().writeXMLJBIdescriptor(jbiDescriptor, baos);
                this.debug("Generated JBI Descriptor:\n" + baos.toString());
                jbiDescriptorInputStream = new ByteArrayInputStream(baos.toByteArray());
            } else {
                jbiDescriptorInputStream = new FileInputStream(jbiDescriptorFile);
            }

            // Add the jbi.xml file
            try {
                ZipUtil.addFile(zipOutputStream, jbiDescriptorInputStream,
                        JBIDescriptorBuilder.JBI_DESCRIPTOR_RESOURCE_IN_ARCHIVE);
            } finally {
                jbiDescriptorInputStream.close();
            }
            this.info("\tJBI descriptor added to JBI Service Assembly archive");

            // Add additional JBI resources
            addJBIResources(zipOutputStream);

            this.info("JBI Service Assembly archive building done.");

        } catch (Exception e) {
            throw new MojoExecutionException(e.getLocalizedMessage(), e);
        }
    }

    /**
     * @param serviceUnitProject
     *            The {@link MavenProject} of the service unit.
     * @param jbiComponentToUsed
     *            The target component to used explicitly set at service unit level.
     * @return The {@link Artifact} associated to the target component on which a service unit must be deployed. Or
     *         <code>null</code> if no target component is found.
     * @throws MojoExecutionException
     */
    private Artifact getTargetComponentFromDependencies(final MavenProject serviceUnitProject,
            final String jbiComponentToUsed) throws MojoExecutionException, MojoFailureException {
        Artifact serviceUnitComponentArtifact = null;
        final List<Dependency> serviceUnitDependencies = serviceUnitProject.getDependencies();
        for (final Dependency serviceUnitDependency : serviceUnitDependencies) {
            this.debug("SU dependency: " + serviceUnitDependency);
            if (PACKAGING_COMPONENT.equals(serviceUnitDependency.getType())) {
                // We have found the component
                this.debug("Component dependency found: " + serviceUnitDependency);

                try {
                    if ((jbiComponentToUsed != null && !jbiComponentToUsed.trim().isEmpty()
                            && jbiComponentToUsed.trim().equals(
                                    serviceUnitDependency.getGroupId() + ":" + serviceUnitDependency.getArtifactId()))
                         || jbiComponentToUsed == null
                         || jbiComponentToUsed.trim().isEmpty()) {
                        
                        if (serviceUnitComponentArtifact == null) {
                            serviceUnitComponentArtifact = createDependencyArtifact(serviceUnitDependency);
                        } else {
                            // Should not occur because should be detected by goal 'jbi-validate' before to package the artifact
                            final String msg = String.format(
                                    "Several JBI component dependencies are declared for the JBI service unit '%s'. You must use parameters 'jbiComponentsUsed' and 'updateJBIXml' to set the right JBI component to use to run this service unit.",
                                    serviceUnitProject.getArtifactId());
                            this.error(msg);
                            throw new MojoFailureException(msg);
                        }

                    }
                } catch (final InvalidVersionSpecificationException e) {
                    throw new MojoExecutionException(e.getLocalizedMessage(), e);
                }
            }
        }

        if (serviceUnitComponentArtifact != null) {
            try {
                this.artifactResolver.resolve(serviceUnitComponentArtifact,
                        this.project.getRemoteArtifactRepositories(), this.localRepository);

                this.debug("Target component found: " + serviceUnitComponentArtifact);

                return serviceUnitComponentArtifact;

            } catch (ArtifactResolutionException e) {
                throw new MojoExecutionException(e.getLocalizedMessage(), e);
            } catch (ArtifactNotFoundException e) {
                throw new MojoExecutionException(e.getLocalizedMessage(), e);
            }
        } else {
            // No target component found
            return null;
        }
    }

    /**
     * Create a shared-library package.
     */
    private void packageSharedLibrary() throws MojoExecutionException {

            this.info("Start building JBI shared-library archive "
                    + project.getArtifact().getFile().getAbsolutePath());

        // JBI Archive creation
        try (final ZipOutputStream zipOutputStream = new ZipOutputStream(
                new FileOutputStream(project.getArtifact().getFile()))) {
            // Add the jbi files
            if (jbiDirectory.isDirectory()) {
                recurseDirectory(zipOutputStream, jbiDirectory, "");
                zipOutputStream.flush();
            }

            // Add the shared-library jar
            addArtifactResources(zipOutputStream);

            // Add the shared library dependencies
            addDependencies(zipOutputStream);

            // Add additional JBI resources
            addJBIResources(zipOutputStream);

            // Update the JBI XML file
            updateSharedLibraryJBIXmlFile(zipOutputStream);

            this.info("JBI Shared library archive building done.");

        } catch (final Exception e) {
            throw new MojoExecutionException(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Add all resources located in a additional JBI resource directory, in
     * current JBI archive.
     * 
     * @param zipOutputStream
     *            The JBI archive
     */
    private void addJBIResources(ZipOutputStream zipOutputStream) throws IOException {
        if (this.additionalJBIResourceDirectory != null) {
            if (this.additionalJBIResourceDirectory.isDirectory()) {
                File[] additionnalJBIFiles = this.additionalJBIResourceDirectory.listFiles();
                for (File additionnalJBIFile : additionnalJBIFiles) {

                    if (additionnalJBIFile.isFile()) {
                        zipFile(zipOutputStream, additionnalJBIFile);
                        this.info("Additional JBI resource [" + additionnalJBIFile
                                + "] has been added to packaging as a file.");
                    } else {
                        zipDirectory(zipOutputStream, additionnalJBIFile, "");
                        this.info("Additional JBI resource [" + additionnalJBIFile
                                + "] has been added to packaging as a directory.");
                    }
                }
            }
        }
    }

    /**
     * Add a directory to a zip stream, if not already zipped
     * 
     * @param zipOutputStream
     * @param file
     */
    private void zipDirectory(ZipOutputStream zipOutputStream, File file, String hierarchyPath) {

        try {
            ZipUtil.addDirectory(zipOutputStream, hierarchyPath + "/" + file.getName());
            this.info("\t" + file.getName() + " directory added to JBI archive");
            for (final File subFile : file.listFiles()) {
                if (subFile.isFile()) {
                    zipFileInDirectory(zipOutputStream, subFile,
                            hierarchyPath + "/" + file.getName());
                    this.info("Additional JBI resource [" + subFile
                            + "] has been added to packaging as a file.");
                } else {
                    zipDirectory(zipOutputStream, subFile, hierarchyPath + "/" + file.getName());
                    this.info("Additional JBI resource [" + subFile
                            + "] has been added to packaging as a directory.");
                }
            }
        } catch (final IOException e) {
            entriesToExclude.add(file.getName());
            this.error("Problem while adding " + file.getName() + " directory to JBI archive", e);
        }
    }

    private void zipFileInDirectory(ZipOutputStream zipOutputStream, File subFile,
            String hierarchyPath) {
        try (FileInputStream inputStream = new FileInputStream(subFile)) {
            ZipUtil.addFile(zipOutputStream, inputStream, hierarchyPath + "/"
                    + subFile.getName());
            this.info("\t" + subFile.getName() + " directory added to JBI archive");
        } catch (final IOException e) {
            entriesToExclude.add(subFile.getName());
            this.error("Problem while adding " + subFile.getName() + " directory to JBI archive", e);
        }
    }

    /**
     * Add the Java resources (classes and resources) in the JBI archive.
     * 
     * @param zipOutputStream
     *            The JBI archive
     */
    private void addArtifactResources(ZipOutputStream zipOutputStream) throws IOException {
        if (artifactJar.exists()) {
            entries.add(artifactJar.getName());
            zipFile(zipOutputStream, artifactJar);
        }
    }

    /**
     * Add required dependencies in the JBI archive. excluded dependencies are : <br>
     * test/provided scope, component/SharedLib type, and optional dependencies
     * 
     * @param zipOutputStream
     *            The JBI archive
     * @throws MojoExecutionException
     */
    @SuppressWarnings("unchecked")
    private void addDependencies(ZipOutputStream zipOutputStream) throws IOException,
            MojoExecutionException {
        Set<Artifact> wholeArtifactsSet = new HashSet<Artifact>();
        wholeArtifactsSet.addAll(project.getArtifacts());

        final Iterator<Artifact> itArtifacts = wholeArtifactsSet.iterator();
        while (itArtifacts.hasNext()) {
            final Artifact artifact = itArtifacts.next();
            if (artifact.getScope().equals("compile") || artifact.getScope().equals("runtime")) {
                addArtifact(artifact, wholeArtifactsSet, zipOutputStream,
                        new ArrayList<Exclusion>());
            }
        }

    }

    private void addArtifact(final Artifact dependencyArtifact,
            final Set<Artifact> wholeArtifactFiles, final ZipOutputStream zipOutputStream,
            final List<Exclusion> parentDependencyExclusions) throws IOException,
            MojoExecutionException {
        this.entries.add(dependencyArtifact.getFile().getName());
        this.zipFile(zipOutputStream, dependencyArtifact.getFile());
    }

    private void updateComponentJBIXmlFile(final ZipOutputStream zipOutputStream) throws MojoExecutionException {

        if (!updateJBIXml) {
            return;
        }

        try (final InputStream jbiDescriptorToUpdateInputStream = new FileInputStream(
                jbiDirectory + File.separator + "jbi.xml")) {
            final Jbi jbiDescriptor = JBIDescriptorBuilder.getInstance().buildJavaJBIDescriptor(
                    jbiDescriptorToUpdateInputStream);
            Component component = jbiDescriptor.getComponent();

            // set the component identification
            String artifactName = this.evaluateJBIIdentifier(this.componentNameMapping,
                    this.project.getArtifact());
            component.getIdentification().setName(artifactName);
            final String description = this.project.getDescription();
            if (description != null && description.length() > 0) {
                component.getIdentification().setDescription(description);
            } else {
                this.info("Entry 'description' not updated in the JBI descriptor because it is not set in POM file.");
            }

            // set the component classpath according to dependencies: We add all
            // dependencies not already included in the classpath
            {
                final Set<String> elemsToAdd = new HashSet<>(entries);
                final List<String> compoClassPath = component.getComponentClassPath()
                        .getPathElement();
                for (String elem : compoClassPath) {
                    if (elemsToAdd.contains(elem)) {
                        elemsToAdd.remove(elem);
                    }

                }
                compoClassPath.addAll(elemsToAdd);

                // Empty entry in the component classpath is not authorized
                final Iterator<String> itCompoClassPath = compoClassPath.iterator();
                while (itCompoClassPath.hasNext()) {
                    final String entry = itCompoClassPath.next();
                    if (entry.trim().length() == 0) {
                        itCompoClassPath.remove();
                    }
                }
            }

            // set the bootstrap classpath according to dependencies: We add all
            // dependencies not already included in the classpath
            {
                final Set<String> elemsToAdd = new HashSet<>(entries);
                final List<String> bootClassPath = component.getBootstrapClassPath()
                        .getPathElement();
                for (String elem : bootClassPath) {
                    if (elemsToAdd.contains(elem)) {
                        elemsToAdd.remove(elem);
                    }
                }
                bootClassPath.addAll(elemsToAdd);

                // Empty entry in the component classpath is not authorized
                final Iterator<String> itBootClassPath = bootClassPath.iterator();
                while (itBootClassPath.hasNext()) {
                    final String entry = itBootClassPath.next();
                    if (entry.trim().length() == 0) {
                        itBootClassPath.remove();
                    }
                }
            }

            // We update the shared-libraries list according to the
            // configuration and mapping rules
            this.info("Update shared libraries.");
            final List<SharedLibrary> sharedLibraries = this.evaluateSharedLibraries(this.project
                    .getArtifact());
            component.getSharedLibraryList().clear();
            for (final SharedLibrary sharedLibrary : sharedLibraries) {
                component.getSharedLibraryList().add(sharedLibrary);
                this.info("Shared library added: " + sharedLibrary.getContent());
            }

            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            JBIDescriptorBuilder.getInstance().writeXMLJBIdescriptor(jbiDescriptor, baos);
            final InputStream jbiDescriptorInputStream = new ByteArrayInputStream(baos.toByteArray());
            ZipUtil.addFile(zipOutputStream, jbiDescriptorInputStream,
                    JBIDescriptorBuilder.JBI_DESCRIPTOR_RESOURCE_IN_ARCHIVE);

        } catch (Exception e) {
            throw new MojoExecutionException("Problem while updating jbi.xml file.", e);
        }
    }

    /**
     * Update the JBI xml file to added all the dependencies of a shared library
     * JBI archive.
     * 
     * @param zipOutputStream
     * @throws MojoExecutionException
     */
    private void updateSharedLibraryJBIXmlFile(final ZipOutputStream zipOutputStream) throws MojoExecutionException {

        final File jbiDescriptorFile = new File(jbiDirectory + File.separator + "jbi.xml");

        if (!updateJBIXml && jbiDescriptorFile.exists()) {
            return;
        }

        final Jbi jbiDescriptor;
        if (jbiDescriptorFile.exists()) {
            try (final InputStream is = new FileInputStream(jbiDescriptorFile)) {
                jbiDescriptor = JBIDescriptorBuilder.getInstance().buildJavaJBIDescriptor(is);
            } catch (Exception e) {
                throw new MojoExecutionException("Problem while reading jbi.xml file", e);
            }
        } else {
            jbiDescriptor = new Jbi();
            jbiDescriptor.setVersion(BigDecimal.valueOf(1.0));
        }

        if (jbiDescriptor.getSharedLibrary() == null) {
            jbiDescriptor.setSharedLibrary(new org.ow2.petals.jbi.descriptor.original.generated.Jbi.SharedLibrary());
        }

        final org.ow2.petals.jbi.descriptor.original.generated.Jbi.SharedLibrary sharedLibrary = jbiDescriptor
                .getSharedLibrary();

        if (sharedLibrary.getIdentification() == null) {
            sharedLibrary.setIdentification(new Identification());
        }

        if (sharedLibrary.getSharedLibraryClassPath() == null) {
            sharedLibrary.setSharedLibraryClassPath(new ClassPath());
        }

        try {
            // add SL name if not already specified
            final String artifactName = this.evaluateJBIIdentifier(this.sharedLibraryNameMapping,
                    this.project.getArtifact());
            sharedLibrary.getIdentification().setName(artifactName);
            final String description = this.project.getDescription();
            if (description != null && description.length() > 0) {
                sharedLibrary.getIdentification().setDescription(description);
            } else {
                this.info("Entry 'description' not updated in the JBI descriptor because it is not set in POM file.");
            }
            // add SL version if not already specified
            sharedLibrary.setVersion(this.evaluateJBIVersion(this.project.getVersion(),
                    this.project.getArtifact()));

            // Set the SL classpath
            final Set<String> elemsToAdd = new HashSet<>(entries);
            final List<String> slClassPath = sharedLibrary.getSharedLibraryClassPath()
                    .getPathElement();
            for (String elem : slClassPath) {
                if (elemsToAdd.contains(elem)) {
                    elemsToAdd.remove(elem);
                }
            }
            elemsToAdd.add(artifactJar.getName());
            slClassPath.clear();
            slClassPath.addAll(elemsToAdd);

            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            JBIDescriptorBuilder.getInstance().writeXMLJBIdescriptor(jbiDescriptor, baos);
            final InputStream jbiDescriptorInputStream = new ByteArrayInputStream(baos.toByteArray());
            ZipUtil.addFile(zipOutputStream, jbiDescriptorInputStream,
                    JBIDescriptorBuilder.JBI_DESCRIPTOR_RESOURCE_IN_ARCHIVE);

        } catch (Exception e) {
            throw new MojoExecutionException("Problem while updating jbi.xml file.", e);
        }
    }

    /**
     * Add a file to a zip stream, if not already zipped
     * 
     * @param zipOutputStream
     * @param file
     */
    private void zipFile(ZipOutputStream zipOutputStream, File file) {

        try (FileInputStream inputStream = new FileInputStream(file)) {
            ZipUtil.addFile(zipOutputStream, inputStream, file.getName());
            this.info("\t" + file.getName() + " file added to JBI archive");
        } catch (final IOException e) {
            entriesToExclude.add(file.getName());
            this.error("Problem while adding " + file.getName() + " file to JBI archive", e);
        }
    }

    /**
     * Evaluate the given nodeList.
     * 
     * @param nodeList
     * @throws DOMException
     * @throws InterpolationException
     */
    private void evaluateElementList(final List<Element> nodeList) throws DOMException, InterpolationException {

        for (Element elt : nodeList) {
            evaluateNodeList(elt.getChildNodes());
        }
    }

    private void evaluateNodeList(final NodeList nodeList) throws DOMException, InterpolationException {

        for (int i = 0; i < nodeList.getLength(); i++) {
            if (nodeList.item(i).getNodeType() == Node.TEXT_NODE
                    && !nodeList.item(i).getTextContent().trim().equals("")) {
                String mappedText = evaluateFileNameMapping(nodeList.item(i).getTextContent(),
                        this.project.getArtifact(), this.project.getProperties());
                nodeList.item(i).setTextContent(mappedText);
            } else if (nodeList.item(i).getChildNodes() != null) {
                evaluateNodeList(nodeList.item(i).getChildNodes());
            }
        }
    }

    @Override
    protected MavenProject getMavenProject() {
        return this.project;
    }
}
