de.smartics.maven.plugin.jboss.modules.xml.ModuleXmlBuilder.java Source code

Java tutorial

Introduction

Here is the source code for de.smartics.maven.plugin.jboss.modules.xml.ModuleXmlBuilder.java

Source

/*
 * Copyright 2013-2015 smartics, Kronseder & Reiner GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.smartics.maven.plugin.jboss.modules.xml;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.graph.Dependency;

import de.smartics.maven.plugin.jboss.modules.descriptor.ApplyToDependencies;
import de.smartics.maven.plugin.jboss.modules.descriptor.ApplyToModule;
import de.smartics.maven.plugin.jboss.modules.descriptor.DependenciesDescriptor;
import de.smartics.maven.plugin.jboss.modules.descriptor.ModuleDescriptor;
import de.smartics.maven.plugin.jboss.modules.domain.ExecutionContext;
import de.smartics.maven.plugin.jboss.modules.domain.SlotStrategy;
import edu.emory.mathcs.backport.java.util.Collections;

/**
 * Creates <code>module.xml</code> descriptors for JBoss modules.
 */
public final class ModuleXmlBuilder {
    // ********************************* Fields *********************************

    // --- constants ------------------------------------------------------------

    /**
     * The default namespace for the <code>module.xml</code> descriptor.
     */
    public static final Namespace NS = Namespace.getNamespace("urn:jboss:module:1.1");

    // --- members --------------------------------------------------------------

    /**
     * The context and configuration to control the building of XML files.
     */
    private final ExecutionContext context;

    /**
     * The module to build.
     */
    private final ModuleDescriptor module;

    /**
     * The dependencies to reference.
     */
    private final Collection<Dependency> dependencies;

    /**
     * The XML document.
     */
    private final Document document;

    /**
     * The root element of the document.
     */
    private final Element root;

    /**
     * A helper class to parse XML fragments.
     */
    private final XmlFragmentParser xmlFragmentParser = new XmlFragmentParser();

    // ****************************** Initializer *******************************

    // ****************************** Constructors ******************************

    /**
     * Default constructor.
     *
     * @param context the context and configuration to control the building of XML
     *          files.
     * @param module the module to build.
     * @param dependencies the dependencies to reference.
     */
    public ModuleXmlBuilder(final ExecutionContext context, final ModuleDescriptor module,
            final Collection<Dependency> dependencies) {
        this.context = context;
        this.module = module;
        this.dependencies = dependencies;

        root = new Element("module", NS);
        root.setAttribute("name", module.getName());
        final String slot = calcSlot(context, module, dependencies);
        if (!SlotStrategy.MAIN_SLOT.equals(slot)) {
            root.setAttribute("slot", slot);
        }
        document = new Document(root);
    }

    // ****************************** Inner Classes *****************************

    /**
     * Helper to sort artifact lists.
     */
    private static final class SortElement implements Comparable<SortElement> {
        /**
         * The key used for sorting.
         */
        private final String key;

        /**
         * The dependency to be sorted.
         */
        private final Dependency dependency;

        private SortElement(final String key, final Dependency dependency) {
            this.key = key;
            this.dependency = dependency;
        }

        /**
         * Returns the hash code of the object.
         *
         * @return the hash code.
         */
        @Override
        public int hashCode() {
            return ObjectUtils.hashCode(key);
        }

        /**
         * Returns <code>true</code> if the given object is semantically equal to
         * the given object, <code>false</code> otherwise.
         *
         * @param object the instance to compare to.
         * @return <code>true</code> if the given object is semantically equal to
         *         the given object, <code>false</code> otherwise.
         */
        @Override
        public boolean equals(final Object object) {
            if (this == object) {
                return true;
            } else if (object == null || getClass() != object.getClass()) {
                return false;
            }

            final ModuleXmlBuilder.SortElement other = (ModuleXmlBuilder.SortElement) object;

            return ObjectUtils.equals(key, other.key);
        }

        @Override
        public int compareTo(final SortElement o) {
            return key.compareTo(o.key);
        }

        @Override
        public String toString() {
            return String.valueOf(key) + ": " + String.valueOf(dependency);
        }
    }

    // ********************************* Methods ********************************

    // --- init -----------------------------------------------------------------

    private static String calcSlot(final ExecutionContext context, final ModuleDescriptor module,
            final Collection<Dependency> dependencies) {
        final SlotStrategy strategy = context.getSlotStrategy();
        final String moduleSlot = module.getSlot();
        final String defaultSlot = context.getDefaultSlot();
        final Artifact artifact = calcArtifact(dependencies);
        final String slot = strategy.calcSlot(defaultSlot, moduleSlot, artifact);
        return slot;
    }

    private static Artifact calcArtifact(final Collection<Dependency> dependencies) {
        if (dependencies != null && !dependencies.isEmpty()) {
            final Dependency dependency = dependencies.iterator().next();
            final Artifact artifact = dependency.getArtifact();
            return artifact;
        }
        return null;
    }

    // --- get&set --------------------------------------------------------------

    // --- business -------------------------------------------------------------

    /**
     * Builds the document.
     *
     * @return the XML document.
     */
    public Document build() {
        addMainClass(module);
        addProperties(module);
        addResources(module, dependencies);
        addDependencies(module, dependencies);
        addExports(module);

        return document;
    }

    private void addMainClass(final ModuleDescriptor module) {
        final String xml = module.getApplyToModule().getMainClassXml();
        if (xml != null) {
            final Element element = xmlFragmentParser.parse(xml);
            root.addContent(element);
        }
    }

    private void addProperties(final ModuleDescriptor module) {
        final List<String> xmls = module.getApplyToModule().getPropertiesXml();
        if (xmls.isEmpty()) {
            return;
        }

        final Element propertiesElement = new Element("properties", NS);
        for (final String xml : xmls) {
            final Element element = xmlFragmentParser.parse(xml);
            propertiesElement.addContent(element);
        }
        root.addContent(propertiesElement);
    }

    private void addResources(ModuleDescriptor module, final Collection<Dependency> dependencies) {
        final Element resources = new Element("resources", NS);

        List<String> resourceRootsXml = module.getApplyToModule().getResourceRootsXml();
        for (final String xml : resourceRootsXml) {
            final Element element = xmlFragmentParser.parse(xml);
            resources.addContent(element);
        }

        if (!dependencies.isEmpty()) {

            final List<SortElement> sorted = createSortedResources(dependencies);
            for (final SortElement element : sorted) {
                final Element resource = new Element("resource-root", NS);
                final String fileName = element.key;
                resource.setAttribute("path", fileName);
                resources.addContent(resource);
            }
        }

        if (!resources.getChildren().isEmpty()) {
            root.addContent(resources);
        }
    }

    private List<SortElement> createSortedResources(final Collection<Dependency> dependencies) {
        final List<SortElement> sorted = new ArrayList<SortElement>(dependencies.size());
        for (final Dependency dependency : dependencies) {
            final Artifact artifact = dependency.getArtifact();
            final File file = artifact.getFile();
            if (file != null) {
                final String fileName = file.getName();
                sorted.add(new SortElement(fileName, dependency));
            }
        }
        Collections.sort(sorted);
        return sorted;
    }

    private void addDependencies(final ModuleDescriptor module, final Collection<Dependency> dependencies) {
        final ApplyToModule applyToModule = module.getApplyToModule();
        final List<String> staticDependencies = applyToModule.getDependenciesXml();
        if (!(dependencies.isEmpty() && staticDependencies.isEmpty())) {
            final Element dependenciesElement = new Element("dependencies", NS);

            addStaticDependencies(staticDependencies, dependenciesElement);
            addResolvedDependencies(module, dependencies, dependenciesElement);

            root.addContent(dependenciesElement);
        }
    }

    private void addResolvedDependencies(final ModuleDescriptor module, final Collection<Dependency> dependencies,
            final Element dependenciesElement) {
        final Set<SortElement> sorted = createSortedDependencies(module, dependencies);

        final ApplyToDependencies apply = module.getApplyToDependencies();

        for (final SortElement element : sorted) {
            final String name = element.key;
            final Element moduleElement = new Element("module", NS);
            moduleElement.setAttribute("name", name);

            final DependenciesDescriptor dd = apply.getDescriptorThatMatches(name);

            if (isIncludableDependency(element, dd)) {
                handleOptional(element, moduleElement, dd);
                handleExport(moduleElement, dd);
                handleServices(moduleElement, dd);
                handleSlot(module, element, moduleElement);
                dependenciesElement.addContent(moduleElement);
            }
        }
    }

    private boolean isIncludableDependency(final SortElement element, final DependenciesDescriptor dd) {
        /*
         * A dependency is considered NOT valid for inclusion within a module if:
         *   - The dependency is flagged as 'skipped'
         *   - The dependency is optional and the ignoreOptionalDependencies property is true
         */

        boolean isSkipped = dd.getSkip() != null && dd.getSkip() == true;
        boolean isOptional = (dd.getOptional() != null && dd.getOptional() == true)
                || element.dependency.isOptional();

        if (isSkipped) {
            return false;
        }

        if (isOptional && context.isIgnoreOptionalDependencies()) {
            return false;
        }

        return true;
    }

    private void handleOptional(final SortElement element, final Element moduleElement,
            final DependenciesDescriptor dd) {
        final Boolean ddOptional = dd.getOptional();
        if ((ddOptional != null && ddOptional) || (ddOptional == null || element.dependency.isOptional())) {
            moduleElement.setAttribute("optional", "true");
        }
    }

    private void handleExport(final Element moduleElement, final DependenciesDescriptor dd) {
        final Boolean ddExport = dd.getExport();
        if (ddExport != null && ddExport) {
            moduleElement.setAttribute("export", "true");
        }
    }

    private void handleServices(final Element moduleElement, final DependenciesDescriptor dd) {
        final String services = dd.getServices();
        if (services != null && !"none".equals(services)) {
            moduleElement.setAttribute("services", services);
        }
    }

    private void handleSlot(final ModuleDescriptor module, final SortElement element, final Element moduleElement) {
        final SlotStrategy slotStrategy = context.getSlotStrategy();
        final Dependency dependency = element.dependency;
        final String defaultSlot = calcDefaultSlot(module, dependency);
        final String slot = slotStrategy.calcSlot(dependency.getArtifact(), defaultSlot);
        if (!SlotStrategy.MAIN_SLOT.equals(slot)) {
            moduleElement.setAttribute("slot", slot);
        }
    }

    private Set<SortElement> createSortedDependencies(final ModuleDescriptor module,
            final Collection<Dependency> dependencies) {
        final Set<SortElement> sorted = new TreeSet<SortElement>();
        for (final Dependency dependency : dependencies) {
            final List<Dependency> resolvedDependencies = context.resolve(dependency);
            addSortedDependencies(sorted, module, resolvedDependencies);
        }
        return sorted;
    }

    private String calcDefaultSlot(final ModuleDescriptor module, final Dependency dependency) {
        final ModuleDescriptor depModule = context.getModule(dependency);
        final String depModuleSlot = depModule.getSlot();
        if (StringUtils.isNotBlank(depModuleSlot)) {
            return depModuleSlot;
        }

        final boolean inheritSlot = module.getDirectives().getInheritSlot();
        if (inheritSlot) {
            final String moduleSlot = module.getSlot();
            if (StringUtils.isNotBlank(moduleSlot)) {
                return moduleSlot;
            }
        }

        final String defaultSlot = context.getDefaultSlot();
        return defaultSlot;
    }

    // CHECKSTYLE:OFF
    private void addStaticDependencies(final List<String> staticDependencies, final Element dependenciesElement) {
        if (!staticDependencies.isEmpty()) {
            for (final String xml : staticDependencies) {
                final Element element = xmlFragmentParser.parse(xml);
                dependenciesElement.addContent(element);
            }
        }
    }

    // CHECKSTYLE:ON

    private void addSortedDependencies(final Set<SortElement> sorted, final ModuleDescriptor owningModule,
            final List<Dependency> dependencies) {
        for (final Dependency dependency : dependencies) {
            try {
                final ModuleDescriptor module = context.getModule(dependency);
                final String name = module.getName();
                if (!name.equals(owningModule.getName())) {
                    /*
                     * It's possible for a module to have resources that share the same dependencies. Potentially these
                     * dependencies could be declared differently in their respective POM files. E.g one resource may specify the
                     * dependency as optional and another resource may declare it as being mandatory.
                     *
                     * In this scenario, always assume that the dependency should be mandatory.
                     */
                    final SortElement e = new SortElement(name, dependency);
                    if (sorted.contains(e)) {
                        final Iterator<SortElement> iter = sorted.iterator();
                        while (iter.hasNext()) {
                            final SortElement current = iter.next();

                            // We are processing a non-optional dependency that has already been added to the set as optional
                            if (current.equals(e) && current.dependency.isOptional() && !dependency.isOptional()) {
                                // Remove so that it can be replaced later with a non-optional dependency
                                sorted.remove(current);
                                break;
                            }
                        }
                    }
                    sorted.add(e);
                }
            } catch (final IllegalArgumentException e) {
                context.getLog().error(String.format("Skipping '%s' referenced from module '%s'.",
                        dependency.getArtifact().getArtifactId(), owningModule.getName()));
            }
        }
    }

    private void addExports(final ModuleDescriptor module2) {
        final String xml = module.getApplyToModule().getExportsXml();
        if (xml != null) {
            final Element element = xmlFragmentParser.parse(xml);
            root.addContent(element);
        }
    }

    // --- object basics --------------------------------------------------------

}