io.smartspaces.workbench.project.jdom.JdomProjectReader.java Source code

Java tutorial

Introduction

Here is the source code for io.smartspaces.workbench.project.jdom.JdomProjectReader.java

Source

/*
 * Copyright (C) 2016 Keith M. Hughes
 * Copyright (C) 2013 Google Inc.
 *
 * 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 io.smartspaces.workbench.project.jdom;

import io.smartspaces.SimpleSmartSpacesException;
import io.smartspaces.configuration.Configuration;
import io.smartspaces.resource.Version;
import io.smartspaces.resource.VersionRange;
import io.smartspaces.util.io.FileSupport;
import io.smartspaces.util.io.FileSupportImpl;
import io.smartspaces.workbench.SmartSpacesWorkbench;
import io.smartspaces.workbench.project.Project;
import io.smartspaces.workbench.project.ProjectDependency;
import io.smartspaces.workbench.project.ProjectDependency.ProjectDependencyLinking;
import io.smartspaces.workbench.project.ProjectDeployment;
import io.smartspaces.workbench.project.ProjectReader;
import io.smartspaces.workbench.project.activity.ActivityProjectConstituent;
import io.smartspaces.workbench.project.constituent.AssemblyComponentProjectConstituent;
import io.smartspaces.workbench.project.constituent.BundleContentProjectConstituent;
import io.smartspaces.workbench.project.constituent.ResourceComponentProjectConstituent;
import io.smartspaces.workbench.project.library.LibraryProjectConstituent;
import io.smartspaces.workbench.project.tasks.TasksProjectConstituent;

import org.jdom2.Attribute;
import org.jdom2.Element;
import org.jdom2.Namespace;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A {@link io.smartspaces.workbench.project.ProjectReader} based on JDOM.
 *
 * @author Keith M. Hughes
 */
public class JdomProjectReader extends JdomReader implements ProjectReader {

    /**
     * The configuration property that specifies which linking should be used for
     * project dependencies.
     */
    public static final String CONFIGURATION_NAME_PROJECT_DEPENDENCY_LINKING_DEFAULT = "smartspaces.workbench.dependency.linking.default";

    /**
     * The default version range for projects which do not specify a version
     * range.
     */
    public static final VersionRange SMARTSPACES_VERSION_RANGE_DEFAULT = new VersionRange(new Version(1, 0, 0),
            new Version(2, 0, 0), false);

    /**
     * Element name for a JDOM project specification.
     */
    public static final String PROJECT_ELEMENT_NAME_PROJECT = "project";

    /**
     * Element name for a group of projects.
     */
    public static final String PROJECT_GROUP_ELEMENT_NAME = "projects";

    /**
     * Add all the base constituent types to the static map.
     */
    {
        addConstituentType(new TasksProjectConstituent.TasksProjectConstituentBuilderFactory());
        addConstituentType(new ResourceComponentProjectConstituent.ProjectResourceConstituentBuilderFactory());
        addConstituentType(new ResourceComponentProjectConstituent.ProjectSourceConstituentBuilderFactory());
        addConstituentType(new AssemblyComponentProjectConstituent.ProjectAssemblyConstituentBuilderFactory());
        addConstituentType(new BundleContentProjectConstituent.BundleProjectConstituentBuilderFactory());
        addConstituentType(new ActivityProjectConstituent.ActivityProjectBuilderFactory());
        addConstituentType(new LibraryProjectConstituent.LibraryProjectBuilderFactory());
    }

    /**
     * The value in a project file for true.
     */
    public static final String PROJECT_VALUE_TRUE = "true";

    /**
     * The value in a project file for false.
     */
    public static final String PROJECT_VALUE_FALSE = "false";

    /**
     * The name of the project.
     */
    public static final String PROJECT_ELEMENT_NAME_NAME = "name";

    /**
     * The description of the project.
     */
    public static final String PROJECT_ELEMENT_NAME_DESCRIPTION = "description";

    /**
     * The identifying name of the project.
     */
    public static final String PROJECT_ELEMENT_NAME_IDENTIFYING_NAME = "identifyingName";

    /**
     * The project attribute for the project type.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_PROJECT_TYPE = "type";

    /**
     * The project attribute for the project platform.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_PROJECT_PLATFORM = "platform";

    /**
     * The project attribute for the project language.
     */
    public static final String PROJECT_ATTRIBUTE_NAME_PROJECT_LANGUAGE = "language";

    /**
     * The project attribute for the version of Smart Spaces which is required.
     */
    public static final String PROJECT_ATTRIBUTE_NAME_PROJECT_SMART_SPACES_VERSION = "smartSpacesVersion";

    /**
     * The version of the project.
     */
    public static final String PROJECT_ELEMENT_NAME_VERSION = "version";

    /**
     * The base directory of the project.
     */
    public static final String PROJECT_ELEMENT_NAME_BASE_DIRECTORY = "baseDirectory";

    /**
     * Project definition file element name for resources.
     */
    private static final String PROJECT_ELEMENT_RESOURCES_NAME = "resources";

    /**
     * Project definition file element name for sources.
     */
    private static final String PROJECT_ELEMENT_NAME_SOURCES = "sources";

    /**
     * Project definition file element name for metadata.
     */
    private static final String PROJECT_ELEMENT_NAME_METADATA = "metadata";

    /**
     * Project definition file element name for metadata.
     */
    private static final String PROJECT_ELEMENT_NAME_METADATA_ITEM = "item";

    /**
     * Project definition file element name for the name of a metadata item.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_METADATA_ITEM_NAME = "name";

    /**
     * Project definition file element name for the value of a metadata item.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_METADATA_ITEM_VALUE = "value";

    /**
     * Project definition file element name for dependencies.
     */
    private static final String PROJECT_ELEMENT_NAME_DEPENDENCIES = "dependencies";

    /**
     * Project definition file element name for a dependency item.
     */
    private static final String PROJECT_ELEMENT_NAME_DEPENDENCY_ITEM = "dependency";

    /**
     * Project definition file attribute name for the identifying name of a
     * dependency.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_DEPENDENCY_ITEM_IDENTIFYING_NAME = "identifyingName";

    /**
     * Project definition file attribute name for the version range of a
     * dependency.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_DEPENDENCY_ITEM_VERSION = "version";

    /**
     * Project definition file attribute name for whether a dependency is
     * required.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_DEPENDENCY_ITEM_REQUIRED = "required";

    /**
     * Project definition file attribute default value for whether a dependency is
     * required.
     */
    private static final String PROJECT_ATTRIBUTE_VALUE_DEFAULT_DEPENDENCY_ITEM_REQUIRED = PROJECT_VALUE_TRUE;

    /**
     * Project definition file attribute name for whether a dependency is dynamic.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_DEPENDENCY_ITEM_DYNAMIC = "dynamic";

    /**
     * Project definition file attribute default value for whether a dependency is
     * dynamic.
     */
    private static final String PROJECT_ATTRIBUTE_VALUE_DEFAULT_DEPENDENCY_ITEM_DYNAMIC = PROJECT_VALUE_FALSE;

    /**
     * Project definition file element name for deployments.
     */
    private static final String PROJECT_ELEMENT_NAME_DEPLOYMENTS = "deployments";

    /**
     * Project definition file element name for a deployment item.
     */
    private static final String PROJECT_ELEMENT_NAME_DEPLOYMENT_ITEM = "deployment";

    /**
     * Project definition file attribute name for the type of a deployment.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_DEPLOYMENT_ITEM_TYPE = "type";

    /**
     * Project definition file attribute name for the method of a deployment.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_DEPLOYMENT_ITEM_METHOD = "method";

    /**
     * Project definition file attribute name for the location for a deployment.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_DEPLOYMENT_ITEM_LOCATION = "location";

    /**
     * Project definition file element name for configurations.
     */
    private static final String PROJECT_ELEMENT_NAME_CONFIGURATION = "configuration";

    /**
     * Project definition file attribute name for the name of a configuration
     * item.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_CONFIGURATION_ITEM_NAME = "name";

    /**
     * Project definition file attribute name for the value of a configuration
     * item.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_CONFIGURATION_ITEM_VALUE = "value";

    /**
     * Project definition file element name for a configuration item value.
     */
    private static final String PROJECT_ELEMENT_NAME_CONFIGURATION_ITEM_VALUE = "value";

    /**
     * The attribute name that specifies how to link a dependency.
     */
    private static final String PROJECT_ATTRIBUTE_NAME_PROJECT_DEPENDENCY_LINKING = "linking";

    /**
     * The default value for linking of dependencies.
     */
    private static final ProjectDependencyLinking PROJECT_DEPENDENCY_LINKING_DEFAULT = ProjectDependencyLinking.RUNTIME;

    /**
     * The file support to use.
     */
    private final FileSupport fileSupport = FileSupportImpl.INSTANCE;

    /**
     * Construct a project reader.
     *
     * @param workbench
     *          containing workbench instance
     */
    public JdomProjectReader(SmartSpacesWorkbench workbench) {
        super(workbench);
    }

    @Override
    public Project readProject(File projectFile) {
        Element rootElement = getRootElement(projectFile);

        Project project = makeProjectFromElement(rootElement);
        project.setBaseDirectory(projectFile.getParentFile());

        return project;
    }

    /**
     * Process an element and return a new project.
     *
     * @param projectElement
     *          element to process
     *
     * @return project representing the element
     */
    Project makeProjectFromElement(Element projectElement) {
        Namespace projectNamespace = projectElement.getNamespace();

        if (!PROJECT_ELEMENT_NAME_PROJECT.equals(projectElement.getName())) {
            throw new SimpleSmartSpacesException("Invalid project root element name " + projectElement.getName());
        }

        // When an xi:include statement is used, the included elements do not pick
        // up the default namespace.
        if (!Namespace.NO_NAMESPACE.equals(projectNamespace)) {
            getLog().info(String.format("Applying default namespace '%s' to project element tree",
                    projectNamespace.getURI()));
            applyDefaultNamespaceRecursively(projectElement, projectNamespace, true);
        }

        String projectType = getProjectType(projectElement);
        Project project = getWorkbench().getProjectTypeRegistry().newProject(projectType);

        project.setPlatform(getProjectPlatform(projectElement));

        processPrototypeChain(project, projectNamespace, projectElement);
        configureProjectFromElement(project, projectNamespace, projectElement);

        if (project.getSmartSpacesVersionRange() == null) {
            getLog().warn("Did not specify a range of needed Smart Spaces versions. Setting default to "
                    + SMARTSPACES_VERSION_RANGE_DEFAULT);
            project.setSmartSpacesVersionRange(SMARTSPACES_VERSION_RANGE_DEFAULT);
        }

        if (failure) {
            throw new SimpleSmartSpacesException("Project specification had errors");
        }

        return project;
    }

    /**
     * Recursively apply a default namespace to an element tree. This will log
     * instances that are changed at the root of a tree (but not elements
     * underneath that root).
     *
     * @param element
     *          target root element
     * @param namespace
     *          namespace to apply as default
     * @param shouldLog
     *          {@code true} if logging should be applied to any matches
     */
    void applyDefaultNamespaceRecursively(Element element, Namespace namespace, boolean shouldLog) {
        if (Namespace.NO_NAMESPACE.equals(element.getNamespace())) {
            if (shouldLog) {
                getLog().info(
                        String.format("Applying default namespace to element tree root '%s'", element.getName()));
                shouldLog = false;
            }
            element.setNamespace(namespace);
        }
        for (Element child : element.getChildren()) {
            applyDefaultNamespaceRecursively(child, namespace, shouldLog);
        }
    }

    /**
     * Process the prototype chain for the given element.
     *
     * @param project
     *          project where the results go
     * @param projectNamespace
     *          XML namespace for project elements
     * @param projectElement
     *          prototype chain root to follow
     */
    private void processPrototypeChain(Project project, Namespace projectNamespace, Element projectElement) {
        if (getJdomPrototypeProcessor() != null) {
            List<Element> prototypeChain = getJdomPrototypeProcessor().getPrototypeChain(projectElement);
            for (Element prototype : prototypeChain) {
                configureProjectFromElement(project, projectNamespace, prototype);
            }

            // Remove the not-useful prototype's name, since it would incorrectly be
            // naming this element.
            project.getAttributes().remove(JdomPrototypeProcessor.PROTOTYPE_NAME_ATTRIBUTE);
        }
    }

    /**
     * Configure a project given an element.
     *
     * @param project
     *          project to configure
     * @param projectNamespace
     *          XML namespace for the project element
     * @param projectElement
     *          input element
     */
    private void configureProjectFromElement(Project project, Namespace projectNamespace, Element projectElement) {
        getProjectAttributes(project, projectElement);
        getMainData(project, projectNamespace, projectElement);
        getMetadata(project, projectNamespace, projectElement);
        getDependencies(project, projectNamespace, projectElement);
        getConfiguration(project, projectNamespace, projectElement);

        project.addResources(getContainerConstituents(projectNamespace,
                projectElement.getChild(PROJECT_ELEMENT_RESOURCES_NAME, projectNamespace), project));
        project.addSources(getContainerConstituents(projectNamespace,
                projectElement.getChild(PROJECT_ELEMENT_NAME_SOURCES, projectNamespace), project));

        project.addExtraConstituents(getContainerConstituents(projectNamespace,
                projectElement.getChild(PROJECT_ELEMENT_NAME_TEMPLATES, projectNamespace), project));

        project.addExtraConstituents(getIndividualConstituent(projectNamespace,
                projectElement.getChild(ActivityProjectConstituent.ACTIVITY_ELEMENT, projectNamespace), project));
        project.addExtraConstituents(getIndividualConstituent(projectNamespace,
                projectElement.getChild(LibraryProjectConstituent.LIBRARY_ELEMENT, projectNamespace), project));
        project.addExtraConstituents(getIndividualConstituent(projectNamespace,
                projectElement.getChild(TasksProjectConstituent.ELEMENT_NAME_TASKS, projectNamespace), project));

        getDeployments(project, projectNamespace, projectElement);
    }

    /**
     * Get the project type from the root element.
     *
     * @param rootElement
     *          the root element of the XML doc
     *
     * @return the project type
     */
    private String getProjectType(Element rootElement) {
        return getRequiredAttributeValue(rootElement, PROJECT_ATTRIBUTE_NAME_PROJECT_TYPE);
    }

    /**
     * Get the project platform from the root element.
     *
     * @param rootElement
     *          the root element of the XML doc
     *
     * @return the project platform
     */
    private String getProjectPlatform(Element rootElement) {
        return getAttributeValue(rootElement, PROJECT_ATTRIBUTE_NAME_PROJECT_PLATFORM, null);
    }

    /**
     * Get the main data from the document.
     *
     * @param project
     *          the project description whose data is being read
     * @param projectNamespace
     *          XML namespace for the project
     * @param rootElement
     *          root element of the XML DOM containing the project data
     */
    private void getMainData(Project project, Namespace projectNamespace, Element rootElement) {
        project.setName(
                getChildTextTrimmed(rootElement, projectNamespace, PROJECT_ELEMENT_NAME_NAME, project.getName()));
        project.setDescription(getChildTextTrimmed(rootElement, projectNamespace, PROJECT_ELEMENT_NAME_DESCRIPTION,
                project.getDescription()));
        project.setIdentifyingName(getChildTextTrimmed(rootElement, projectNamespace,
                PROJECT_ELEMENT_NAME_IDENTIFYING_NAME, project.getIdentifyingName()));

        String version = getChildTextTrimmed(rootElement, projectNamespace, PROJECT_ELEMENT_NAME_VERSION);
        if (version != null) {
            project.setVersion(Version.parseVersion(version));
        }

        String baseDirectoryPath = getChildTextTrimmed(rootElement, projectNamespace,
                PROJECT_ELEMENT_NAME_BASE_DIRECTORY);
        if (baseDirectoryPath != null) {
            project.setBaseDirectory(fileSupport.newFile(baseDirectoryPath));
        }

        String language = getAttributeValue(rootElement, PROJECT_ATTRIBUTE_NAME_PROJECT_LANGUAGE,
                project.getLanguage());
        if (language != null) {
            language = language.toLowerCase();
        }
        project.setLanguage(language);

        String smartSpacesVersionRangeAttribute = getAttributeValue(rootElement,
                PROJECT_ATTRIBUTE_NAME_PROJECT_SMART_SPACES_VERSION, null);

        if (smartSpacesVersionRangeAttribute != null) {
            VersionRange versionRange = VersionRange.parseVersionRange(smartSpacesVersionRangeAttribute);
            project.setSmartSpacesVersionRange(versionRange);
        }
    }

    /**
     * Get any attributes attached to the project.
     *
     * @param project
     *          the project description whose data is being read
     * @param rootElement
     *          root element of the XML DOM containing the project data
     */
    private void getProjectAttributes(Project project, Element rootElement) {
        @SuppressWarnings("unchecked")
        List<Attribute> attributes = rootElement.getAttributes();
        for (Attribute attribute : attributes) {
            project.addAttribute(attribute.getName(), attribute.getValue());
        }
    }

    /**
     * Get the metadata from the document.
     *
     * @param project
     *          the project description whose data is being read
     * @param projectNamespace
     *          XML namespace for the project
     * @param rootElement
     *          root element of the XML DOM containing the project data
     */
    private void getMetadata(Project project, Namespace projectNamespace, Element rootElement) {
        Element metadataElement = rootElement.getChild(PROJECT_ELEMENT_NAME_METADATA, projectNamespace);

        if (metadataElement != null) {
            @SuppressWarnings("unchecked")
            List<Element> itemElements = metadataElement.getChildren(PROJECT_ELEMENT_NAME_METADATA_ITEM,
                    projectNamespace);

            Map<String, Object> metadata = new HashMap<>();
            for (Element itemElement : itemElements) {
                String name = getRequiredAttributeValue(itemElement, PROJECT_ATTRIBUTE_NAME_METADATA_ITEM_NAME);
                String value = getRequiredAttributeValue(itemElement, PROJECT_ATTRIBUTE_NAME_METADATA_ITEM_VALUE);
                metadata.put(name, value);
            }

            project.setMetadata(metadata);
        }
    }

    /**
     * Get the configuration from the document.
     *
     * @param project
     *          the project description whose data is being read
     * @param projectNamespace
     *          XML namespace for the project
     * @param rootElement
     *          root element of the XML DOM containing the project data
     */
    private void getConfiguration(Project project, Namespace projectNamespace, Element rootElement) {
        Element configurationElement = rootElement.getChild(PROJECT_ELEMENT_NAME_CONFIGURATION, projectNamespace);

        if (configurationElement != null) {
            @SuppressWarnings("unchecked")
            List<Element> propertyElements = configurationElement
                    .getChildren(ActivityProjectConstituent.PROPERTY_ELEMENT_NAME, projectNamespace);

            Configuration configuration = project.getConfiguration();
            for (Element propertyElement : propertyElements) {
                String name = getRequiredAttributeValue(propertyElement,
                        PROJECT_ATTRIBUTE_NAME_CONFIGURATION_ITEM_NAME);
                if (name == null) {
                    getWorkbench().getLog().warn("Configuration property does not have a name");
                    continue;
                }

                String value = getConfigurationValue(projectNamespace, propertyElement, name);
                if (value == null) {
                    getWorkbench().getLog()
                            .warn(String.format("Configuration property %s does not have a value", name));
                    continue;
                }

                configuration.setProperty(name, value);
            }
        }
    }

    /**
     * Get the value for a configuration property.
     *
     * @param projectNamespace
     *          the XML namespace for the property
     * @param propertyElement
     *          the XML element for the property
     * @param propertyName
     *          the name of the property
     *
     * @return the configuration value for the property
     */
    private String getConfigurationValue(Namespace projectNamespace, Element propertyElement, String propertyName) {
        String valueAttribute = propertyElement.getAttributeValue(PROJECT_ATTRIBUTE_NAME_CONFIGURATION_ITEM_VALUE);
        String valueChild = propertyElement.getChildTextNormalize(PROJECT_ELEMENT_NAME_CONFIGURATION_ITEM_VALUE,
                projectNamespace);

        if (valueAttribute != null) {
            if (valueChild != null) {
                getWorkbench().getLog().warn(String.format(
                        "Configuration property %s has both an attribute and child element giving the value. "
                                + "The child element is being used.",
                        propertyName));
                return valueChild;
            } else {
                return valueAttribute;
            }
        } else {
            return valueChild;
        }
    }

    /**
     * Get the resources from the document.
     *
     * @param project
     *          the project description whose data is being read
     * @param projectNamespace
     *          namespace for the project
     * @param rootElement
     *          root element of the XML DOM containing the project data
     */
    private void getDependencies(Project project, Namespace projectNamespace, Element rootElement) {
        Element dependenciesElement = rootElement.getChild(PROJECT_ELEMENT_NAME_DEPENDENCIES, projectNamespace);
        if (dependenciesElement != null) {
            ProjectDependencyLinking defaultLinking = getDependencyLinkingAttribute(dependenciesElement,
                    getDefaultProjectDependencyLinking());
            if (defaultLinking == null) {
                return;
            }

            @SuppressWarnings("unchecked")
            List<Element> dependencyElements = dependenciesElement.getChildren(PROJECT_ELEMENT_NAME_DEPENDENCY_ITEM,
                    projectNamespace);

            for (Element dependencyElement : dependencyElements) {
                ProjectDependency dependency = getDependency(dependencyElement, defaultLinking);
                if (dependency != null) {
                    project.addDependency(dependency);
                }
            }
        }
    }

    /**
     * Get the default linking for project dependencies.
     *
     * @return the default linking to use
     */
    private ProjectDependencyLinking getDefaultProjectDependencyLinking() {
        return ProjectDependencyLinking.valueOf(getWorkbench().getWorkbenchConfig().getPropertyString(
                CONFIGURATION_NAME_PROJECT_DEPENDENCY_LINKING_DEFAULT,
                PROJECT_DEPENDENCY_LINKING_DEFAULT.toString()));
    }

    /**
     * Get the project dependency link attribute value for the given element.
     *
     * @param dependencyElement
     *          the dependency element
     * @param defaultLinking
     *          the default linking value if none is specified
     *
     * @return the linking value to use or {@code null} if the linkage value was
     *         not legal
     */
    private ProjectDependencyLinking getDependencyLinkingAttribute(Element dependencyElement,
            ProjectDependencyLinking defaultLinking) {
        String linkingString = dependencyElement
                .getAttributeValue(PROJECT_ATTRIBUTE_NAME_PROJECT_DEPENDENCY_LINKING, defaultLinking.toString());
        ProjectDependencyLinking linking = ProjectDependencyLinking.valueOf(linkingString.toUpperCase());
        if (linking == null) {
            addError(String.format("Unknown project dependency linking %s", linkingString));
        }

        return linking;
    }

    /**
     * Get an project dependency from the dependency element.
     *
     * @param dependencyElement
     *          the element containing the data
     * @param defaultLinking
     *          the default linking for project dependencies
     *
     * @return the dependency found in the element, or {@code null} if errors
     */
    private ProjectDependency getDependency(Element dependencyElement, ProjectDependencyLinking defaultLinking) {
        String identifyingName = getAttributeValue(dependencyElement,
                PROJECT_ATTRIBUTE_NAME_DEPENDENCY_ITEM_IDENTIFYING_NAME);
        if (identifyingName == null) {
            addError("project.xml <dependency> has no identifyingName");
            return null;
        }

        VersionRange version = null;
        String versionStr = getAttributeValue(dependencyElement, PROJECT_ATTRIBUTE_NAME_DEPENDENCY_ITEM_VERSION);
        if (versionStr != null) {
            if (Character.isDigit(versionStr.charAt(0))) {
                getWorkbench().getLog()
                        .warn(String.format(
                                "A version value of %s was specified that gives a range of [%s, infinity). "
                                        + "If an exact match is wanted, use =%s",
                                versionStr, versionStr, versionStr));
            }
            version = VersionRange.parseVersionRange(versionStr);
        } else {
            addError("project.xml <dependency> has no version attribute");
            return null;
        }

        boolean required = PROJECT_VALUE_TRUE
                .equals(getAttributeValue(dependencyElement, PROJECT_ATTRIBUTE_NAME_DEPENDENCY_ITEM_REQUIRED,
                        PROJECT_ATTRIBUTE_VALUE_DEFAULT_DEPENDENCY_ITEM_REQUIRED));
        boolean dynamic = PROJECT_VALUE_TRUE
                .equals(getAttributeValue(dependencyElement, PROJECT_ATTRIBUTE_NAME_DEPENDENCY_ITEM_DYNAMIC,
                        PROJECT_ATTRIBUTE_VALUE_DEFAULT_DEPENDENCY_ITEM_DYNAMIC));

        ProjectDependencyLinking linking = getDependencyLinkingAttribute(dependencyElement, defaultLinking);
        if (linking == null) {
            return null;
        }

        ProjectDependency dependency = new ProjectDependency();

        dependency.setIdentifyingName(identifyingName);
        dependency.setVersionRange(version);
        dependency.setRequired(required);
        dependency.setDynamic(dynamic);
        dependency.setLinking(linking);

        return dependency;
    }

    /**
     * Get the deployments from the document.
     *
     * @param project
     *          the project description whose data is being read
     * @param projectNamespace
     *          XML namespace for the project
     * @param rootElement
     *          root element of the XML DOM containing the project data
     */
    private void getDeployments(Project project, Namespace projectNamespace, Element rootElement) {
        Element deploymentsElement = rootElement.getChild(PROJECT_ELEMENT_NAME_DEPLOYMENTS, projectNamespace);

        if (deploymentsElement != null) {
            @SuppressWarnings("unchecked")
            List<Element> deploymentElements = deploymentsElement.getChildren(PROJECT_ELEMENT_NAME_DEPLOYMENT_ITEM,
                    projectNamespace);

            for (Element deploymentElement : deploymentElements) {
                ProjectDeployment deployment = getDeployment(deploymentElement);
                if (deployment != null) {
                    project.addDeployment(deployment);
                }
            }
        }
    }

    /**
     * Get an project deployment from the deployment element.
     *
     * @param deploymentElement
     *          the element containing the data
     *
     * @return the deployment found in the element
     */
    private ProjectDeployment getDeployment(Element deploymentElement) {
        String type = getRequiredAttributeValue(deploymentElement, PROJECT_ATTRIBUTE_NAME_DEPLOYMENT_ITEM_TYPE);
        String method = getAttributeValue(deploymentElement, PROJECT_ATTRIBUTE_NAME_DEPLOYMENT_ITEM_METHOD);
        String location = getAttributeValue(deploymentElement, PROJECT_ATTRIBUTE_NAME_DEPLOYMENT_ITEM_LOCATION);

        // TODO(keith): Enumerate all possible errors

        return new ProjectDeployment(type, method, location);
    }
}