/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2012 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.packaging.internal;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.jgrapht.UndirectedGraph;
import org.jgrapht.alg.ConnectivityInspector;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleGraph;
import org.ow2.easybeans.component.util.Property;
import org.ow2.jonas.Version;
import org.ow2.jonas.addon.deploy.api.util.IAddonStructure;
import org.ow2.jonas.addon.deploy.impl.xml.JonasAddonDesc;
import org.ow2.jonas.addon.deploy.jonasaddon.v1.generated.JonasAddonType;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.jpaas.util.clouddescriptors.cloudapplication.CloudApplicationDesc;
import org.ow2.jonas.jpaas.util.clouddescriptors.cloudapplication.artefact.v1.generated.ArtefactDeployableType;
import org.ow2.jonas.jpaas.util.clouddescriptors.cloudapplication.artefact.v1.generated.RequirementsType;
import org.ow2.jonas.jpaas.util.clouddescriptors.cloudapplication.v1.generated.CloudApplicationType;
import org.ow2.jonas.jpaas.util.clouddescriptors.cloudapplication.v1.generated.DeployablesType;
import org.ow2.jonas.jpaas.util.clouddescriptors.cloudapplication.xml.v1.generated.XmlDeployableType;
import org.ow2.jonas.jpaas.util.clouddescriptors.deployment.DeploymentDesc;
import org.ow2.jonas.jpaas.util.clouddescriptors.deployment.v1.generated.DeploymentType;
import org.ow2.jonas.jpaas.util.clouddescriptors.environmenttemplate.EnvironmentTemplateDesc;
import org.ow2.jonas.jpaas.util.clouddescriptors.environmenttemplate.v1.generated.EnvironmentTemplateType;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.service.AbsServiceImpl;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.packaging.IPackagingManager;
import org.ow2.jonas.packaging.IPackagingStructure;
import org.ow2.jonas.service.ServiceException;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.plan.bindings.deploymentplan.Deployment;
import org.ow2.util.plan.bindings.deploymentplan.ExtendedDeploymentPlan;
import org.ow2.util.plan.bindings.deploymentplan.maven2.Maven2Deployment;

/**
 * Implements the packaging service.
 * @see org.ow2.jonas.packaging.IPackagingManager
 *
 * @author Mohammed Boukada
 */
public class PackagingManager extends AbsServiceImpl implements IPackagingManager, IPackagingStructure {

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(PackagingManager.class);

    /**
     * The JMX server.
     */
    private static JmxService jmxService = null;

    /**
     * Basename pattern
     */
    private static final Pattern BASENAME_PATTERN = Pattern.compile(".*?([^/]*)$");

    /**
     * Property pattern
     */
    private static final Pattern PROPERTY_PATTERN = Pattern.compile("\\(.*=.*\\)");

    /**
     * Collocated map
     */
    private Map<String, List<String>> collocated = new LinkedHashMap<String, List<String>>();

    /**
     * Not collocated map
     */
    private Map<String, List<String>> notCollocated = new LinkedHashMap<String, List<String>>();

    /**
     * Deployables
     */
    private Map<String, Object> deployables = new LinkedHashMap<String, Object>();

    /**
     * Collocated to property name
     */
    private static final String COLLOCATED_TO_PROPERTY_NAME = "collocated-to";

    /**
     * Not collocated to property name
     */
    private static final String NOT_COLLOCATED_TO_PROPERTY_NAME = "not-collocated-to";

    /**
     * Resources groupId
     */
    private static final String GROUP_ID = "org.ow2.jonas.jpaas";

    /**
     * Deployables graph
     */
    UndirectedGraph<String, DefaultEdge> graph = new SimpleGraph<String, DefaultEdge>(DefaultEdge.class);

    /**
     * JOnAS Base
     */
    private static final String JONAS_BASE = JProp.getJonasBase();

    /**
     * @param jmxService JMX service to set.
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    @Override
    protected void doStart() throws ServiceException {
        // Load mbeans-descriptor.xml into the modeler registry
        // the meta-data located in META-INF/mbeans-descriptors.xml
        jmxService.loadDescriptors(getClass().getPackage().getName(), getClass().getClassLoader());

        try {
            jmxService.registerModelMBean(this, JonasObjectName.packaging(getDomainName()));
        } catch (Exception e) {
            throw new ServiceException("Cannot register 'packaging' service MBean", e);
        }
        logger.info("Packaging service management bean has been registered successfully");
    }

    @Override
    protected void doStop() throws ServiceException {

    }

    /**
     * Generate an addon
     * @param urlCloudApplication URL of cloud-application.xml
     * @param tenantId tenant identifier of the application
     */
    public List<String> generateAddon(URL urlCloudApplication, String tenantId) throws Exception {
         return generateAddon(readURL(urlCloudApplication), tenantId, "", "", "");
    }

    /**
     * Generate an addon
     * @param urlCloudApplication URL of cloud-application.xml
     * @param tenantId tenant identifier of the application
     * @param outputDir
     */
    public List<String> generateAddon(URL urlCloudApplication, String tenantId, String outputDir) throws Exception {
        return generateAddon(readURL(urlCloudApplication), tenantId, "", "", outputDir);
    }

    /**
     * Generate an addon
     * @param urlCloudApplication URL of cloud-application.xml
     * @param tenantId tenant identifier of the application
     * @param urlEnvironmentTemplate URL of environment-template.xml
     * @param urlMappingTopology URL of deployment.xml
     */
    public List<String> generateAddon(URL urlCloudApplication, String tenantId, URL urlEnvironmentTemplate, URL urlMappingTopology) throws Exception {
        return generateAddon(readURL(urlCloudApplication), tenantId, readURL(urlEnvironmentTemplate), readURL(urlMappingTopology), "");
    }

    /**
     * Generate an addon
     * @param urlCloudApplication URL of cloud-application.xml
     * @param tenantId tenant identifier of the application
     * @param urlEnvironmentTemplate URL of environment-template.xml
     * @param urlMappingTopology URL of deployment.xml
     * @param outputDir
     */
    public List<String> generateAddon(URL urlCloudApplication, String tenantId, URL urlEnvironmentTemplate, URL urlMappingTopology, String outputDir) throws Exception {
        return generateAddon(readURL(urlCloudApplication), tenantId, readURL(urlEnvironmentTemplate), readURL(urlMappingTopology), outputDir);
    }

    /**
     * Generate an addon
     * @param xmlCloudApplication cloud-application.xml content
     * @param tenantId tenant identifier of the application
     */
    public List<String> generateAddon(String xmlCloudApplication, String tenantId) throws Exception {
        return generateAddon(xmlCloudApplication, tenantId, "", "", "");
    }

    /**
     * Generate an addon
     * @param xmlCloudApplication cloud-application.xml content
     * @param tenantId tenant identifier of the application
     * @param outputDir
     */
    public List<String> generateAddon(String xmlCloudApplication, String tenantId, String outputDir) throws Exception {
        return generateAddon(xmlCloudApplication, tenantId, "", "", outputDir);
    }


    /**
     * Generate an addon
     * @param xmlCloudApplication cloud-application.xml content
     * @param tenantId tenant identifier of the application
     * @param xmlEnvironmentTemplate environment-template.xml content
     * @param xmlMappingTopology deployment.xml content
     * @return List of zips locations
     */
    public List<String> generateAddon(String xmlCloudApplication, String tenantId, String xmlEnvironmentTemplate,
                                      String xmlMappingTopology) throws Exception {
        return generateAddon(xmlCloudApplication, tenantId, xmlEnvironmentTemplate, xmlMappingTopology, "");
    }

    /**
     * Generate an addon
     * @param xmlCloudApplication cloud-application.xml content
     * @param tenantId tenant identifier of the application
     * @param xmlEnvironmentTemplate environment-template.xml content
     * @param xmlMappingTopology deployment.xml content
     * @param outputDir
     * @return List of zips locations
     */
    public List<String> generateAddon(String xmlCloudApplication, String tenantId, String xmlEnvironmentTemplate,
                                      String xmlMappingTopology, String outputDir) throws Exception {
        if (tenantId == null || "".equals(tenantId)) {
            logger.warn("The tenant identifier of this application is null !");
        }

        CloudApplicationDesc cloudApplicationDesc = new CloudApplicationDesc(xmlCloudApplication);
        CloudApplicationType cloudApplication = (CloudApplicationType) cloudApplicationDesc.getCloudApplication();

        EnvironmentTemplateDesc environmentTemplateDesc = null;
        EnvironmentTemplateType environmentTemplate = null;

        DeploymentDesc deploymentDesc = null;
        DeploymentType deploymentType = null;

        if (!"".equals(xmlEnvironmentTemplate)) {
            environmentTemplateDesc = new EnvironmentTemplateDesc(xmlEnvironmentTemplate);
            environmentTemplate = (EnvironmentTemplateType) environmentTemplateDesc.getEnvironmentTemplate();
        }
        if (!"".equals(xmlMappingTopology)) {
            deploymentDesc = new DeploymentDesc(xmlMappingTopology);
            deploymentType = (DeploymentType) deploymentDesc.getDeployment();
        }

        // Retrieve deployables
        DeployablesType deployables = cloudApplication.getDeployables();
        List<Object> listDeployables = deployables.getDeployables();

        for (Object deployable : listDeployables) {
            String id = null;
            List<String> listRequirements = null;
            if (deployable instanceof ArtefactDeployableType) {
                ArtefactDeployableType artefactDeployable = (ArtefactDeployableType) deployable;
                id = artefactDeployable.getId();
                RequirementsType requirements = artefactDeployable.getRequirements();
                listRequirements = requirements.getRequirement();
            } else if (deployable instanceof XmlDeployableType) {
                XmlDeployableType xmlDeployable = (XmlDeployableType) deployable;
                id = xmlDeployable.getId();
                org.ow2.jonas.jpaas.util.clouddescriptors.cloudapplication.xml.v1.generated.RequirementsType requirements =
                        xmlDeployable.getRequirements();
                listRequirements = requirements.getRequirement();
            }

            if (id != null && listRequirements != null) {
                this.deployables.put(id, deployable);
                this.collocated.put(id, getListOf(COLLOCATED_TO_PROPERTY_NAME, listRequirements));
                this.notCollocated.put(id, getListOf(NOT_COLLOCATED_TO_PROPERTY_NAME, listRequirements));
            }
        }

        // Compute number of zips to generate
        List<Set<String>> packages = makeZipPackages();
        System.gc();
        
        // Construct zip packages
        int i = 1;
        String zipNamePrefix = cloudApplication.getName() + "_";
        if (tenantId != null && !"".equals(tenantId)) {
            zipNamePrefix += tenantId + "_";
        }

        Map<String, List<Object>> zips = new LinkedHashMap<String, List<Object>>();
        for (Set<String> zipPackage : packages) {
            List<Object> zipDeployables = new LinkedList<Object>();
            zips.put(zipNamePrefix + i, zipDeployables);
            i++;

            // Get deployble object
            for (String deployable : zipPackage) {
                zipDeployables.add(this.deployables.get(deployable));
            }
        }

        List<String> zipsLocations = new LinkedList<String>();

        // Define OutPutDirectory
        String zipsDir;
        if ("".equals(outputDir)) {
            createWorkAddonsDir();
            zipsDir = JONAS_BASE + File.separator + ADDONS_DIR;
        } else {
            if (!(new File(outputDir).exists())) {
                logger.error("No such file or directory for the URL ''" + outputDir + "''");
            }
            zipsDir = outputDir;
        }

        for (Map.Entry<String, List<Object>> zip : zips.entrySet()) {
            
            String zipName = zipsDir + File.separator + zip.getKey() + ZIP_EXTENSION;
            ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipName));

            // Create deployment plan
            ExtendedDeploymentPlan deploymentPlan = new ExtendedDeploymentPlan();
            List<Deployment> deployments = deploymentPlan.getDeployments();

            // Create a buffer for reading the files
            byte[] buf = new byte[1024];

            try {
                // Retrieve deployables for this zip file
                for (Object deployable : zip.getValue()) {
                    String zipEntryName = REPOSITORY_DIR + File.separator + groupIdPath(GROUP_ID) + File.separator;
                    String artifact = null;
                    String type = null;
                    byte[] content = null;

                    if (deployable instanceof ArtefactDeployableType) {
                        // Read artefact deployable bytes
                        String path = ((ArtefactDeployableType) deployable).getLocation();
                        File file = new File(path);
                        if (!file.exists()) {
                            throw new Exception("File '" + file.getPath() + "' was not found.");
                        }
                        byte[] b = new byte[(int) file.length()];
                        FileInputStream in = new FileInputStream(file.getPath());
                        in.read(b);
                        content = b;

                        artifact = updateArtefactDeployableName(((ArtefactDeployableType) deployable).getName(), tenantId);

                        type = getExtension(file.getPath());

                    } else if (deployable instanceof XmlDeployableType) {
                        // Read Xml deployable bytes
                        content = ((XmlDeployableType) deployable).getXmlContent().getBytes();

                        artifact = ((XmlDeployableType) deployable).getName();

                        type = "xml";
                    }

                    if (artifact != null && type != null && content != null) {
                        // Set zip entry name
                        zipEntryName += artifact + File.separator + cloudApplication.getVersion() + File.separator +
                                updateDeployableZipEntryName(artifact, cloudApplication.getVersion(), type);

                        // Add zip entry
                        out.putNextEntry(new ZipEntry(zipEntryName));

                        // Transfer bytes from the string to the ZIP file
                        out.write(content, 0, content.length);

                        // Complete the entry
                        out.closeEntry();

                        // Add it to the deployment plan
                        Maven2Deployment maven2Deployment = new Maven2Deployment();
                        maven2Deployment.setGroupId(GROUP_ID);
                        maven2Deployment.setArtifactId(artifact);
                        maven2Deployment.setVersion(cloudApplication.getVersion());
                        maven2Deployment.setType(type);
                        maven2Deployment.setIdAttr(basename(zipEntryName));
                        deployments.add(maven2Deployment);
                    } else {
                        throw new Exception("Cannot create zip entry '" + zipEntryName + "'.");
                    }
                }

                // Write deployment plan
                org.ow2.util.plan.bindings.deploymentplan.ObjectFactory depPlanObjectFactory =
                        new org.ow2.util.plan.bindings.deploymentplan.ObjectFactory();
                String deploymentPlanFileName = DEPLOY_DIRECTORY + File.separator + DEPLOYMENT_PLAN_PREFIX;
                if (tenantId != null && !"".equals(tenantId)) {
                    deploymentPlanFileName += tenantId;
                }
                deploymentPlanFileName += XML_EXTENSION;

                out.putNextEntry(new ZipEntry(deploymentPlanFileName));
                generateXml(depPlanObjectFactory.createDeploymentPlan(deploymentPlan), out, ExtendedDeploymentPlan.class);
                out.closeEntry();

                // Generate jonas-addon.xml file
                makeJonasAddon(cloudApplication, tenantId, out);

                // Add cloud descriptors
                // Start by cloud-application.xml ...
                String cloudApplicationFileName = DEPLOY_DIRECTORY + File.separator + CLOUD_APPLICATION;
                if (tenantId != null && !"".equals(tenantId)) {
                    cloudApplicationFileName += "_" + tenantId;
                }
                cloudApplicationFileName += XML_EXTENSION;
                out.putNextEntry(new ZipEntry(cloudApplicationFileName));
                out.write(xmlCloudApplication.getBytes());
                out.closeEntry();

                // then environment-template.xml ...
                if (!"".equals(xmlEnvironmentTemplate)) {
                    String environmentTemplateFileName = DEPLOY_DIRECTORY + File.separator + ENVRIONMENT_TEMPLATE;
                    if (tenantId != null && !"".equals(tenantId)) {
                        environmentTemplateFileName += "_" + tenantId;
                    }
                    environmentTemplateFileName += XML_EXTENSION;
                    out.putNextEntry(new ZipEntry(environmentTemplateFileName));
                    out.write(xmlEnvironmentTemplate.getBytes());
                    out.closeEntry();
                }

                // And finally, add deployment.xml
                if (!"".equals(xmlMappingTopology)) {
                    String deploymentFileName = DEPLOY_DIRECTORY + File.separator + DEPLOYMENT;
                    if (tenantId != null && !"".equals(tenantId)) {
                        deploymentFileName += "_" + tenantId;
                    }
                    deploymentFileName += XML_EXTENSION;
                    out.putNextEntry(new ZipEntry(deploymentFileName));
                    out.write(xmlMappingTopology.getBytes());
                    out.closeEntry();
                }

                logger.info("Addon generated '" + zipName + "'.");
            } finally {
                out.close();
            }
            zipsLocations.add(zipName);
        }
        return zipsLocations;
    }

    /**
     * Get list of collocated or not collocated deployable
     * @param p name of property (collocated-to or not-collocated-to)
     * @param requirements list of requirements
     * @return
     */
    private List<String> getListOf(String p, List<String> requirements) {
        List<String> list = new LinkedList<String>();
        for (String requirement : requirements) {
            if (isProperty(requirement)) {
                Property property = getProperty(requirement);
                if (p.equals(property.getName())) {
                    list.add(property.getValue());
                }
            }
        }
        return list;
    }

    /**
     * Whether the requirement match LDAP query syntax
     * @param requirement
     * @return
     */
    private boolean isProperty(String requirement) {
        return PROPERTY_PATTERN.matcher(requirement).matches();
    }

    /**
     * Get the property
     * @param requirement
     * @return
     */
    private Property getProperty(String requirement) {
        Property property = new Property();
        String name = requirement.substring(requirement.indexOf('(') + 1, requirement.indexOf('='));
        String value = requirement.substring(requirement.indexOf('=') + 1, requirement.indexOf(')'));
        property.setName(name);
        property.setValue(value);
        return property;
    }

    /**
     * Compute zip packages content
     * @return list of packages
     * @throws Exception
     */
    private List<Set<String>> makeZipPackages() throws Exception {

        // Construct a graph of deployables
        for (Map.Entry<String, List<String>> entry : collocated.entrySet()) {
            addVertex(entry.getKey(), entry.getValue());
        }

        // Get connected components
        List<Set<String>> connectedComponents = new ConnectivityInspector(graph).connectedSets();

        // check collocation compatibility
        for (Set<String> connectedComponent : connectedComponents) {
            checkConstraintsCompatibility(connectedComponent);
        }

        // Try to merge connected components
        connectedComponentsGarbageCollector();
        
        // Gets the new connected components after removing not collocated deployables
        return new ConnectivityInspector(graph).connectedSets();
    }

    /**
     * Add a vertex and its edges to the graph
     * @param vertex
     * @param edges
     */
    private void addVertex(String vertex, List<String> edges) throws Exception {
        // add vertices if not exists
        if (!graph.containsVertex(vertex)) {
            graph.addVertex(vertex);
        }
        for (String edgeVertex : edges) {
            if (deployables.get(vertex) == null) {
                throw new Exception("Deployable with the name '" + vertex + "' was not found.");
            }
            if (!graph.containsVertex(edgeVertex)){
                graph.addVertex(edgeVertex);
            }
        }

        // add edges
        for (String edge : edges) {
            graph.addEdge(vertex, edge);
        }
    }

    /**
     * Check if collocated and not-collocated constraints are compatibles
     * @param connectedComponent
     * @throws Exception
     */
    private void checkConstraintsCompatibility(Set<String> connectedComponent) throws Exception {
        ConnectivityInspector connectivityInspector = new ConnectivityInspector(graph);
        for (String vertex : connectedComponent) {
            if (deployables.get(vertex) == null) {
                throw new Exception("Deployable with the name '" + vertex + "' was not found.");
            }
            List<String> notCollocatedDeployables = notCollocated.get(vertex);
            for (String notCollocatedDeployable : notCollocatedDeployables) {
                if (connectivityInspector.pathExists(vertex, notCollocatedDeployable)) {
                    throw new Exception("Wrong constraint. '" + vertex + "' and '" + notCollocatedDeployable + "' cannot " +
                            "be collocated");
                }
            }
        }
    }

    /**
     * Try to merge connected components to reduce their number
     * @throws Exception
     */
    private void connectedComponentsGarbageCollector() throws Exception {
        ConnectivityInspector connectivityInspector = new ConnectivityInspector(graph);
        List<Set<String>> connectedComponents = connectivityInspector.connectedSets();
        if (connectedComponents.size() > 1) {
            boolean merge = false;
            int i = 0;
            while (!merge && i < connectedComponents.size()) {
                int j = i + 1;
                List<String> toCollocate = new LinkedList<String>(connectedComponents.get(i));
                while (!merge && j < connectedComponents.size()) {
                    List<String> toTest = new LinkedList<String>(connectedComponents.get(j));
                    if (canCollocate(toCollocate, toTest)) {
                        merge = true;
                        addVertex(toCollocate.get(0), toTest);
                    }
                    j++;
                }
                i++;
            }

            if (merge) {
                connectedComponentsGarbageCollector();
            }
        }
    }

    /**
     * Test if two connected component can collocate
     * @param listToCollocate
     * @param listToTest
     * @return
     */
    private boolean canCollocate(List<String> listToCollocate, List<String> listToTest) {
        for (String elementOnListToCollocate : listToCollocate) {
            List<String> elementNotCollocated = this.notCollocated.get(elementOnListToCollocate);
            for (String elementOnListToTest : listToTest) {
                if (elementNotCollocated.contains(elementOnListToTest)) {
                    return false;
                }
                List<String> elementTestNotCollocated = this.notCollocated.get(elementOnListToTest);
                if (elementTestNotCollocated.contains(elementOnListToCollocate)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Make jonas-addon.xml file
     * @param cloudApplication
     * @param tenantId
     * @param out
     * @throws Exception
     */
    private void makeJonasAddon(CloudApplicationType cloudApplication, String tenantId, ZipOutputStream out) throws Exception {
        JonasAddonDesc jonasAddonDesc = new JonasAddonDesc();
        JonasAddonType jonasAddon = new JonasAddonType();
        if (tenantId != null && !"".equals(tenantId)) {
            jonasAddon.setName(cloudApplication.getName() + "_" + tenantId);
            jonasAddon.setTenantId(tenantId);
        } else {
            jonasAddon.setName(cloudApplication.getName());
        }
        if (cloudApplication.getInstance() != null && !"".equals(cloudApplication.getInstance())) {
            jonasAddon.setInstance(cloudApplication.getInstance());
        }
        if (cloudApplication.getDescription() != null && !"".equals(cloudApplication.getDescription())) {
            jonasAddon.setDescription(cloudApplication.getDescription());
        }
        jonasAddon.setAuthor(AUTHOR);
        jonasAddon.setLicence(LICENCE);
        jonasAddon.setJonasVersion("[" + Version.getNumber() + "]");
        jonasAddon.setJvmVersion(JVM_VERSIONS);
        if (cloudApplication.getCapabilities().getService() != null && !"".equals(cloudApplication.getCapabilities().getService())) {
            jonasAddon.setProvides(cloudApplication.getCapabilities().getService());
        } else {
            jonasAddon.setProvides("");
        }
        jonasAddon.setRequirements("");

        // Add jonas-addon as zip entry to output stream
        String jonasAddonFileName = IAddonStructure.JONAS_ADDON_METADATA_ZIP_ENTRY;

        out.putNextEntry(new ZipEntry(jonasAddonFileName));

        // Transfer xml content to the ZIP file
        org.ow2.jonas.addon.deploy.jonasaddon.v1.generated.ObjectFactory objectFactory =
                new org.ow2.jonas.addon.deploy.jonasaddon.v1.generated.ObjectFactory();
        jonasAddonDesc.generateJonasAddon(objectFactory.createJonasAddon(jonasAddon), out);

        // Complete the entry
        out.closeEntry();
    }
    
    /**
     * Generate xml
     * @param jaxbElement root element
     * @param out output file
     * @throws IOException
     * @throws JAXBException
     */
    private static void generateXml(JAXBElement<?> jaxbElement, ZipOutputStream out, Class<?> clazz) throws IOException, JAXBException {
        JAXBContext jc = JAXBContext.newInstance(clazz);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(jaxbElement, out);
    }

    /**
     * Read url content
     * @param url
     * @return content
     * @throws java.io.IOException
     */
    private static String readURL(final URL url) throws IOException {
        URLConnection connection = url.openConnection();
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        } catch (FileNotFoundException e) {
            logger.error("No such file or directory for the URL ''" + url +"''");
        }

        try {
            StringBuilder sb = new StringBuilder();
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                if (sb.length() > 0) {
                    sb.append('\n');
                }
                sb.append(inputLine);
            }

            return sb.toString();
        } finally {
            in.close();
        }
    }

    /**
     * Get basename from url
     * @param url
     * @return
     */
    private static String basename(String url) {
        Matcher matcher = BASENAME_PATTERN.matcher(url);
        if (matcher.matches()) {
            return matcher.group(1);
        } else {
            throw new IllegalArgumentException("Can't parse " + url);
        }
    }

    /**
     * @param groupId
     * @return groupId path
     */
    private static String groupIdPath(String groupId) {
        return groupId.replace(".", File.separator);
    }

    /**
     * Customize Artefact deployable name by adding tenantId
     * @param name
     * @param tenantId
     * @return
     */
    private String updateArtefactDeployableName(String name, String tenantId) {
        if (tenantId != null && !"".equals(tenantId)) {
            return name + "_" + tenantId;
        }
        return name;
    }

    /**
     * Update deployable name
     * @param artifact
     * @param version
     * @param extension
     * @return
     */
    private String updateDeployableZipEntryName(String artifact, String version, String extension) {
        return artifact + "-" + version + "." + extension;
    }

    /**
     * Gets file extension
     * @param filename
     * @return
     */
    private static String getExtension(String filename) {
        return filename.substring(filename.lastIndexOf('.') + 1);
    }

    /**
     * Create addons directory ($JONAS_BASE/work/addons/)
     * @throws Exception
     */
    private void createWorkAddonsDir() throws Exception {
        // create work dir
        File work = new File(JONAS_BASE + File.separator + WORK_DIRECTORY);
        if (!work.exists()) {
            if (!work.mkdir()) {
                throw new Exception("Fail when creating '" + WORK_DIRECTORY +"' directory");
            }
        }
        
        // create addons dir
        File addons = new File(JONAS_BASE + File.separator + ADDONS_DIR);
        if (!addons.exists()) {
            if (!addons.mkdir()) {
                throw new Exception("Fail when creating '" + ADDONS_DIR +"' directory");
            }
        }
    }
}
