org.opentravel.schemacompiler.repository.ProjectManager.java Source code

Java tutorial

Introduction

Here is the source code for org.opentravel.schemacompiler.repository.ProjectManager.java

Source

/**
 * Copyright (C) 2014 OpenTravel Alliance (info@opentravel.org)
 *
 * 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 org.opentravel.schemacompiler.repository;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBElement;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opentravel.ns.ota2.project_v01_00.ManagedProjectItemType;
import org.opentravel.ns.ota2.project_v01_00.ProjectItemType;
import org.opentravel.ns.ota2.project_v01_00.ProjectType;
import org.opentravel.ns.ota2.project_v01_00.RepositoryRefType;
import org.opentravel.ns.ota2.project_v01_00.UnmanagedProjectItemType;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.LibraryInfoType;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.RepositoryPermission;
import org.opentravel.schemacompiler.codegen.CodeGenerationFilter;
import org.opentravel.schemacompiler.codegen.impl.DependencyFilterBuilder;
import org.opentravel.schemacompiler.event.ModelEventBuilder;
import org.opentravel.schemacompiler.event.ModelEventListener;
import org.opentravel.schemacompiler.event.ModelEventType;
import org.opentravel.schemacompiler.event.OwnershipEvent;
import org.opentravel.schemacompiler.ic.ImportManagementIntegrityChecker;
import org.opentravel.schemacompiler.ic.LibraryRemovedIntegrityChecker;
import org.opentravel.schemacompiler.loader.LibraryLoaderException;
import org.opentravel.schemacompiler.loader.LibraryModelLoader;
import org.opentravel.schemacompiler.loader.LibraryNamespaceResolver;
import org.opentravel.schemacompiler.loader.LoaderValidationMessageKeys;
import org.opentravel.schemacompiler.loader.impl.DefaultLibraryNamespaceResolver;
import org.opentravel.schemacompiler.loader.impl.FileValidationSource;
import org.opentravel.schemacompiler.loader.impl.LibraryStreamInputSource;
import org.opentravel.schemacompiler.loader.impl.LibraryValidationSource;
import org.opentravel.schemacompiler.model.AbstractLibrary;
import org.opentravel.schemacompiler.model.BuiltInLibrary;
import org.opentravel.schemacompiler.model.TLInclude;
import org.opentravel.schemacompiler.model.TLLibrary;
import org.opentravel.schemacompiler.model.TLLibraryStatus;
import org.opentravel.schemacompiler.model.TLModel;
import org.opentravel.schemacompiler.model.TLNamespaceImport;
import org.opentravel.schemacompiler.repository.impl.BuiltInProject;
import org.opentravel.schemacompiler.repository.impl.ProjectFileUtils;
import org.opentravel.schemacompiler.repository.impl.ProjectItemDependencyNavigator;
import org.opentravel.schemacompiler.repository.impl.ProjectItemImpl;
import org.opentravel.schemacompiler.saver.LibraryModelSaver;
import org.opentravel.schemacompiler.saver.LibrarySaveException;
import org.opentravel.schemacompiler.util.ExceptionUtils;
import org.opentravel.schemacompiler.util.URLUtils;
import org.opentravel.schemacompiler.validate.FindingType;
import org.opentravel.schemacompiler.validate.ValidationFindings;
import org.opentravel.schemacompiler.validate.ValidatorFactory;
import org.opentravel.schemacompiler.validate.impl.TLModelValidator;
import org.opentravel.schemacompiler.version.MajorVersionHelper;
import org.opentravel.schemacompiler.version.VersionScheme;
import org.opentravel.schemacompiler.version.VersionSchemeException;
import org.opentravel.schemacompiler.version.VersionSchemeFactory;

/**
 * Maintains the list of all project and project-items, and orchestrates the actions required to
 * manages changes.
 * 
 * @author S. Livezey
 */
public final class ProjectManager {

    private static Log log = LogFactory.getLog(ProjectManager.class);

    private static Map<TLModel, ProjectManager> instanceMap = new HashMap<TLModel, ProjectManager>();

    private RepositoryManager repositoryManager;
    private List<Project> projects = new ArrayList<Project>();
    private List<ProjectItem> projectItems = new ArrayList<ProjectItem>();
    private boolean autoSaveProjects;
    private Project builtInProject;
    private TLModel model;

    /**
     * Default constructor.
     */
    public ProjectManager() {
        this(new TLModel(), true, null);
    }

    /**
     * Constructs a new project manager using the specified auto-save option.
     * 
     * @param autoSaveProjects
     *            flag indicating whether member projects should be auto-saved
     */
    public ProjectManager(boolean autoSaveProjects) {
        this(new TLModel(), autoSaveProjects, null);
    }

    /**
     * Constructs a new project manager using the model instance provided.
     * 
     * @param model
     *            the model instance to assigne for the new manager
     */
    public ProjectManager(TLModel model) {
        this(model, true, null);
    }

    /**
     * Constructs a new project manager using the model instance provided.
     * 
     * @param model
     *            the model instance to assigne for the new manager
     * @param autoSaveProjects
     *            flag indicating whether member projects should be auto-saved
     * @param repositoryManager
     *            the repository manager to use when interfacing with remote repositories
     */
    public ProjectManager(TLModel model, boolean autoSaveProjects, RepositoryManager repositoryManager) {
        try {
            if (model == null) {
                throw new NullPointerException("The underlying model for a project cannot be null.");
            }
            this.model = model;
            this.model.addListener(new IncludeDependencyListener());
            this.model.addListener(new ImportDependencyListener());
            this.projects.add(builtInProject = new BuiltInProject(this));
            this.repositoryManager = (repositoryManager == null) ? RepositoryManager.getDefault()
                    : repositoryManager;
            this.autoSaveProjects = autoSaveProjects;

            instanceMap.put(model, this); // If a project manager was already associated with this
                                          // model, this new instance will replace the old one.

        } catch (RepositoryException e) {
            log.error("Exception initializing local repository - only unmanaged project files are accessible.", e);
        }
    }

    /**
     * Returns the <code>TLModel</code> instance shared among all of the existing projects.
     * 
     * @return TLModel
     */
    public TLModel getModel() {
        return model;
    }

    /**
     * If the given model is associated with a <code>ProjectManager</code> instance, that instance
     * will be returned. Otherwise, this method will return null.
     * 
     * @param model
     *            the model instance for which to return the associated project manager
     */
    public static ProjectManager getProjectManager(TLModel model) {
        return instanceMap.get(model);
    }

    /**
     * Returns the repository manager used to download and edit content from a remote OTA2.0
     * repository.
     * 
     * @return RepositoryManager
     */
    public RepositoryManager getRepositoryManager() {
        return repositoryManager;
    }

    /**
     * Returns the list of all registered projects.
     * 
     * @return List<Project>
     */
    public List<Project> getAllProjects() {
        return Collections.unmodifiableList(projects);
    }

    /**
     * Returns the flag value indicating whether projects should be automatically re-saved whenever
     * a change to the project's persistent state is detected. This includes loading operations in
     * which new libraries are automatically imported and/or included. If the auto-save flag is
     * false, clients are required to manually save any such state changes to affects project(s).
     * 
     * @return boolean (default is true)
     */
    public boolean getAutoSaveProjects() {
        return autoSaveProjects;
    }

    /**
     * Assigns the flag value indicating whether projects should be automatically re-saved whenever
     * a change to the project's persistent state is detected. This includes loading operations in
     * which new libraries are automatically imported and/or included. If the auto-save flag is
     * false, clients are required to manually save any such state changes to affects project(s).
     * 
     * @param autoSaveProjects
     *            the flag value to assign (default is true)
     */
    public void setAutoSaveProjects(boolean autoSaveProjects) {
        this.autoSaveProjects = autoSaveProjects;
    }

    /**
     * Constructs a new <code>Project</code> instance using the information provided and adds it to
     * the list of projects maintained by this project manager instance.
     * 
     * @param projectFile
     *            the file location where the project will be saved
     * @param projectId
     *            the ID of the project to create
     * @param projectName
     *            the user-displayable name of the project to create
     * @param description
     *            a description for the project to be created
     * @return Project
     * @throws LibrarySaveException
     *             thrown if the new project instance cannot be saved
     * @throws IllegalArgumentException
     *             thrown if the file and/or ID of the project are already in use
     */
    public Project newProject(File projectFile, String projectId, String projectName, String description)
            throws LibrarySaveException {
        Project project = new Project(this);

        project.setProjectFile(projectFile);
        project.setProjectId(projectId);
        project.setName(projectName);
        project.setDescription(description);

        validateProjectID(projectId, null);
        validateProjectFile(projectFile, null);

        ProjectFileUtils.saveProjectFile(project);
        projects.add(project);

        return project;
    }

    /**
     * Loads the specified project file and incorporates its content into the shared model instance.
     * 
     * @param projectFile
     *            the file location of the project to be loaded
     * @return Project
     * @throws LibraryLoaderException
     *             thrown if the contents of the project cannot be loaded
     * @throws IllegalArgumentException
     *             thrown if the file and/or ID of the project are already in use
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    public Project loadProject(File projectFile) throws LibraryLoaderException, RepositoryException {
        return loadProject(projectFile, null);
    }

    /**
     * Loads the specified project file and incorporates its content into the shared model instance.
     * 
     * @param projectFile
     *            the file location of the project to be loaded
     * @param findings
     *            validation findings where errors/warnings from the loading operation will be
     *            reported
     * @return Project
     * @throws LibraryLoaderException
     *             thrown if the contents of the project cannot be loaded
     * @throws IllegalArgumentException
     *             thrown if the file and/or ID of the project are already in use
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    public Project loadProject(File projectFile, ValidationFindings findings)
            throws LibraryLoaderException, RepositoryException {
        ProjectType jaxbProject = ProjectFileUtils.loadJaxbProjectFile(projectFile, findings);
        Project project = null;

        if (jaxbProject != null) {
            ValidationFindings loaderFindings = new ValidationFindings();

            // Attempt to register any new repositories that are defined in this project file
            registerUnknownRepositories(jaxbProject, projectFile, loaderFindings);

            // Construct the new project instance and add it to the current list of projects
            project = new Project(this);
            project.setProjectId(jaxbProject.getProjectId());
            project.setProjectFile(projectFile);
            project.setName(jaxbProject.getName());
            project.setDescription(jaxbProject.getDescription());
            project.setDefaultContextId(jaxbProject.getDefaultContextId());

            validateProjectID(project.getProjectId(), null);
            validateProjectFile(projectFile, null);
            projects.add(project);

            // Load the contents of the model using the library reference from each of the project
            // items
            // defined in the file.
            List<RepositoryItem> managedItems = new ArrayList<RepositoryItem>();
            List<File> unmanagedItemFiles = new ArrayList<File>();
            RepositoryItem defaultItem = null;
            URL defaultItemUrl = null;

            for (JAXBElement<? extends ProjectItemType> jaxbItem : jaxbProject.getProjectItemBase()) {
                URL projectFolderUrl = URLUtils.toURL(projectFile.getParentFile());

                if (jaxbItem.getValue() instanceof UnmanagedProjectItemType) {
                    UnmanagedProjectItemType projectItem = (UnmanagedProjectItemType) jaxbItem.getValue();
                    try {
                        URL libraryUrl = URLUtils.getResolvedURL(projectItem.getFileLocation(), projectFolderUrl);
                        unmanagedItemFiles.add(URLUtils.toFile(libraryUrl));

                        if ((projectItem.isDefaultItem() != null) && projectItem.isDefaultItem()) {
                            defaultItemUrl = libraryUrl;
                        }

                    } catch (MalformedURLException e) {
                        loaderFindings.addFinding(FindingType.ERROR, new FileValidationSource(projectFile),
                                LoaderValidationMessageKeys.ERROR_INVALID_PROJECT_ITEM_LOCATION,
                                projectItem.getFileLocation());
                    }

                } else if (jaxbItem.getValue() instanceof ManagedProjectItemType) {
                    ManagedProjectItemType projectItem = (ManagedProjectItemType) jaxbItem.getValue();
                    try {
                        String repositoryId = projectItem.getRepository();
                        Repository repository;

                        if ((repositoryId != null) && (repositoryId.length() > 0)) {
                            repository = repositoryManager.getRepository(repositoryId);

                            if (repository == null) {
                                throw new RepositoryException("Unknown repository specified: " + repositoryId);
                            }
                        } else {
                            // If the item's repository ID was not explicitly specified, invoking
                            // the search on the
                            // repository manager will call will force a searchin all known
                            // repositories.
                            repository = repositoryManager;
                        }
                        RepositoryItem repositoryItem = repository.getRepositoryItem(projectItem.getBaseNamespace(),
                                projectItem.getFilename(), projectItem.getVersion());

                        if (repositoryItem.getRepository() == null) {
                            loaderFindings.addFinding(FindingType.ERROR, new FileValidationSource(projectFile),
                                    LoaderValidationMessageKeys.ERROR_MISSING_REPOSITORY,
                                    projectItem.getFilename());
                        }
                        if ((projectItem.isDefaultItem() != null) && projectItem.isDefaultItem()) {
                            defaultItem = repositoryItem;
                        }
                        managedItems.add(repositoryItem);

                    } catch (RepositoryUnavailableException e) {
                        loaderFindings.addFinding(FindingType.ERROR, new FileValidationSource(projectFile),
                                LoaderValidationMessageKeys.ERROR_REPOSITORY_UNAVAILABLE, projectItem.getFilename(),
                                projectItem.getRepository());

                    } catch (RepositoryException e) {
                        loaderFindings.addFinding(FindingType.ERROR, new FileValidationSource(projectFile),
                                LoaderValidationMessageKeys.ERROR_LOADING_FROM_REPOSITORY,
                                projectItem.getFilename(), e.getClass().getSimpleName(), e.getMessage());
                    }
                }
            }

            // Attempt to load the project and its items into the current project manager session
            boolean success = false;
            try {
                loadAllProjectItems(unmanagedItemFiles, managedItems, project, loaderFindings);
                success = true;

            } finally {
                // If the load threw an exception, we need to discard the project and any items that
                // might have been loaded prior to the error
                if (!success) {
                    projects.remove(project);
                    purgeOrphanedProjectItems();
                }
            }

            // Assign the default project item
            if (defaultItemUrl != null) {
                AbstractLibrary defaultLib = model.getLibrary(defaultItemUrl);

                if (defaultLib != null) {
                    project.setDefaultItem(getProjectItem(defaultLib));
                }

            } else if (defaultItem != null) {
                for (ProjectItem item : projectItems) {
                    if ((item != null)
                            && ((item.getBaseNamespace() != null)
                                    && item.getBaseNamespace().equals(defaultItem.getBaseNamespace()))
                            && ((item.getFilename() != null)
                                    && item.getFilename().equals(defaultItem.getFilename()))
                            && ((item.getVersion() != null)
                                    && item.getVersion().equals(defaultItem.getVersion()))) {
                        project.setDefaultItem(item);
                        break;
                    }
                }
            }

            // Validate for errors/warnings if requested by the caller
            if (findings != null) {
                findings.addAll(TLModelValidator.validateModel(model, ValidatorFactory.COMPILE_RULE_SET_ID));
                findings.addAll(loaderFindings);
            }

            // Ensure that all of the project items were loaded, and place any that did
            // not in the 'failedProjectItems' list.
            for (JAXBElement<? extends ProjectItemType> jaxbItem : jaxbProject.getProjectItemBase()) {
                ProjectItemType item = jaxbItem.getValue();

                if (!isProjectItemLoaded(item, project)) {
                    project.getFailedProjectItems().add(item);
                }
            }

        } else {
            throw new LibraryLoaderException("Unable to load project: " + projectFile.getName());
        }
        return project;
    }

    /**
     * Scans the contents of the given JAXB project object and attempts to register any repositories
     * that are not yet known to the local environment. If the local repository's meta-data already
     * contains a reference to a repository, it is left unchanged by this method. If a reference to
     * an as-yet unknown repository is called out, however, this method will attempt to register it
     * using the URL provided in the project file.
     * 
     * @param jaxbProject
     *            the raw JAXB content of the OTP project file
     * @param projectFile
     *            the file from which the JAXB project content was loaded
     * @param loaderFindings
     *            findings that will receive the warning if an invalid repository URL is encountered
     */
    private void registerUnknownRepositories(ProjectType jaxbProject, File projectFile,
            ValidationFindings loaderFindings) {
        Map<String, String> repositoryUrls = new HashMap<String, String>();
        Set<String> repositoryIds = new HashSet<String>();

        // Collect the IDs of the repositories that were explicitly referenced
        for (JAXBElement<? extends ProjectItemType> jaxbItem : jaxbProject.getProjectItemBase()) {
            if (jaxbItem.getValue() instanceof ManagedProjectItemType) {
                ManagedProjectItemType projectItem = (ManagedProjectItemType) jaxbItem.getValue();
                String repositoryId = projectItem.getRepository();

                if ((repositoryId != null) && (repositoryId.length() > 0)) {
                    repositoryIds.add(repositoryId);
                }
            }
        }

        // Build a map of known repository IDs and their associated URLs
        if (jaxbProject.getRepositoryReferences() != null) {
            for (RepositoryRefType repositoryRef : jaxbProject.getRepositoryReferences().getRepositoryRef()) {
                repositoryUrls.put(repositoryRef.getRepositoryId(), repositoryRef.getValue());
            }
        }

        // Check each repository ID to determine if it is new; attempt to register new remote
        // repositories using the associated URLs.
        for (String repositoryId : repositoryIds) {
            Repository r = repositoryManager.getRepository(repositoryId);
            String repositoryUrl = repositoryUrls.get(repositoryId);

            if ((r == null) && (repositoryUrl != null)) {
                boolean success = false;
                try {
                    repositoryManager.addRemoteRepository(repositoryUrl);
                    success = true;

                } catch (Throwable t) {
                    // No error; post a warning and continue

                } finally {
                    if (!success) {
                        loaderFindings.addFinding(FindingType.WARNING, new FileValidationSource(projectFile),
                                LoaderValidationMessageKeys.WARNING_INVALID_REPOSITORY_URL, repositoryUrl);
                    }
                }
            }
        }
    }

    /**
     * Saves the contents of the specified project to the local file system.
     * 
     * @param project
     *            the project instance to save
     * @throws LibrarySaveException
     */
    public void saveProject(Project project) throws LibrarySaveException {
        saveProject(project, null);
    }

    /**
     * Saves the contents of the specified project to the local file system. In addition to saving
     * the project itself, the contents of all <code>TLLibrary</code> project items are also saved.
     * 
     * @param project
     *            the project instance to save
     * @param findings
     *            validation findings where errors/warnings from the save operation will be reported
     * @throws LibrarySaveException
     */
    public void saveProject(Project project, ValidationFindings findings) throws LibrarySaveException {
        saveProject(project, true, findings);
    }

    /**
     * Saves the contents of the specified project to the local file system. In addition to saving
     * the project itself, the contents of all <code>TLLibrary</code> project items are also saved.
     * 
     * @param project
     *            the project instance to save
     * @param saveEditableLibraries
     *            flag indicating whether the unmanaged/WIP library items are to be saved as well
     * @param findings
     *            validation findings where errors/warnings from the save operation will be reported
     * @throws LibrarySaveException
     */
    public void saveProject(Project project, boolean saveUnmanagedLibraries, ValidationFindings findings)
            throws LibrarySaveException {
        if (project.getProjectManager() != this) {
            throw new IllegalArgumentException("The project is owned by another project manager instance.");
        }
        if (project == builtInProject) {
            throw new IllegalArgumentException("The built-in project cannot be saved.");
        }
        if (findings == null)
            findings = new ValidationFindings();

        // Before saving, scan the list of failed project items to make sure that none
        // of them were loaded after the initial attempt.
        checkFailedProjectItems(project);

        // In addition to the project itself, save any of the unmanaged libraries if requested
        // by the caller.
        if (saveUnmanagedLibraries) {
            List<TLLibrary> libraryList = new ArrayList<TLLibrary>();

            for (ProjectItem item : project.getProjectItems()) {
                RepositoryItemState itemState = item.getState();

                if ((itemState == RepositoryItemState.UNMANAGED)
                        || (itemState == RepositoryItemState.MANAGED_WIP)) {
                    if ((item.getContent() instanceof TLLibrary) && !((TLLibrary) item.getContent()).isReadOnly()) {
                        libraryList.add((TLLibrary) item.getContent());
                    }
                }
            }
            findings.addAll(new LibraryModelSaver().saveLibraries(libraryList));
        }
        ProjectFileUtils.saveProjectFile(project);
    }

    /**
     * Closes the specified project and removes it from this <code>ProjectManager</code> workspace.
     * 
     * <p>
     * NOTE: The contents of the project are not saved by this method. It is the caller's
     * responibility to save the project (if required) before calling this method to close it.
     * 
     * @param project
     *            the project to close
     */
    public void closeProject(Project project) {
        if (project.getProjectManager() != this) {
            throw new IllegalArgumentException("The project is owned by another project manager instance.");
        }
        if (project == builtInProject) {
            throw new IllegalArgumentException("The built-in project cannot be closed.");
        }
        projects.remove(project);
        purgeOrphanedProjectItems();
    }

    /**
     * Closes all projects and project items and re-initializes the contents of the model. All
     * references to existing project-items, projects, and libraries (including the built-ins)
     * should be considered invalid after calling this method.
     */
    public void closeAll() {
        projects.clear();
        projectItems.clear();
        model.clearModel();
        projects.add(builtInProject = new BuiltInProject(this));
    }

    /**
     * Refreshes the contents of all managed project items that are not locked for editing
     * by the current user.  The list returned by this method contains all project items that
     * were updated during the refresh.
     * 
     * @return List<ProjectItem>
     * @throws LibraryLoaderException  thrown if the contents of a library cannot be loaded
     * @throws RepositoryException  thrown if one or more managed repository items cannot be accessed
     */
    public List<ProjectItem> refreshManagedProjectItems() throws LibraryLoaderException, RepositoryException {
        return refreshManagedProjectItems(null);
    }

    /**
     * Refreshes the contents of all managed project items that are not locked for editing
     * by the current user.  The list returned by this method contains all project items that
     * were updated during the refresh.
     * 
     * @param findings  validation findings where errors/warnings from the refresh operation will be reported
     * @return List<ProjectItem>
     * @throws LibraryLoaderException  thrown if the contents of a library cannot be loaded
     * @throws RepositoryException  thrown if one or more managed repository items cannot be accessed
     */
    @SuppressWarnings("unchecked")
    public List<ProjectItem> refreshManagedProjectItems(ValidationFindings findings)
            throws LibraryLoaderException, RepositoryException {
        LibraryModelLoader<InputStream> modelLoader = new LibraryModelLoader<InputStream>(model);
        LibraryRemovedIntegrityChecker removeProcessor = new LibraryRemovedIntegrityChecker();
        ValidationFindings loaderFindings = new ValidationFindings();
        Map<String, ProjectItem> refreshedItemMap = new HashMap<>();
        List<URL> refreshedLibraryUrls = new ArrayList<>();
        List<ProjectItem> refreshedItems = new ArrayList<>();

        modelLoader.setResolveModelReferences(false);

        // Scan for libraries (project items) that need to be refreshed
        for (ProjectItem item : projectItems) {
            try {
                RepositoryItemState itemState = item.getState();

                // Skip items that are locked for local edits; this includes unmanaged items
                // and managed items that are WIP
                if ((itemState == RepositoryItemState.UNMANAGED)
                        || (itemState == RepositoryItemState.MANAGED_WIP)) {
                    continue;
                }

                // Check the last-updated date on the remote repository item against the local copy
                if (repositoryManager.refreshLocalCopy(item)) {
                    // NOTE: This has only refreshed the library content on the local file system; we
                    //       still need to update the in-memory model
                    URL libraryUrl = item.getContent().getLibraryUrl();

                    refreshedLibraryUrls.add(libraryUrl);
                    refreshedItemMap.put(libraryUrl.toExternalForm(), item);
                }

            } catch (Throwable t) {
                loaderFindings.addFinding(FindingType.ERROR, new LibraryValidationSource(item.getContent()),
                        LoaderValidationMessageKeys.ERROR_UNKNOWN_EXCEPTION_DURING_MODULE_LOAD,
                        URLUtils.getShortRepresentation(item.getContent().getLibraryUrl()),
                        ExceptionUtils.getExceptionClass(t).getSimpleName(), ExceptionUtils.getExceptionMessage(t));
            }
        }

        // Remove each refreshed library from the model
        for (ProjectItem item : refreshedItemMap.values()) {
            model.removeLibrary(item.getContent());
            removeProcessor.processModelEvent(
                    (OwnershipEvent<TLModel, AbstractLibrary>) new ModelEventBuilder(ModelEventType.LIBRARY_REMOVED,
                            model).setAffectedItem(item.getContent()).buildEvent());
        }

        // Reload each affected library from the local file system and add it back into the model
        for (URL libraryUrl : refreshedLibraryUrls) {
            ProjectItemImpl refreshedItem = (ProjectItemImpl) refreshedItemMap.get(libraryUrl.toExternalForm());
            List<Project> assignedProjects = refreshedItem.memberOfProjects();
            try {
                // Re-load the library into memory and replace the original content of the project item
                modelLoader.loadLibraryModel(new LibraryStreamInputSource(libraryUrl));
                TLLibrary refreshedLibrary = (TLLibrary) model.getLibrary(libraryUrl);

                if (refreshedLibrary != null) {
                    refreshedItem.setContent(refreshedLibrary);
                }
                refreshedItems.add(refreshedItem);

                // Look for new libraries added to the model as dependencies after the refresh
                for (AbstractLibrary library : model.getAllLibraries()) {
                    if (library instanceof BuiltInLibrary)
                        continue;

                    if (getProjectItem(library) == null) {
                        ProjectItem newItem = findOrCreateProjectItem(library);

                        for (Project project : assignedProjects) {
                            project.add(newItem);
                        }
                        addProjectItem(newItem);
                        refreshedItems.add(newItem);
                    }
                }

            } catch (Throwable t) {
                loaderFindings.addFinding(FindingType.ERROR,
                        new LibraryValidationSource(refreshedItem.getContent()),
                        LoaderValidationMessageKeys.ERROR_UNKNOWN_EXCEPTION_DURING_MODULE_LOAD,
                        URLUtils.getShortRepresentation(libraryUrl),
                        ExceptionUtils.getExceptionClass(t).getSimpleName(), ExceptionUtils.getExceptionMessage(t));
            }
        }
        modelLoader.resolveModelEntityReferences();

        // Run a final validation check (if necessary)
        if (findings != null) {
            findings.addAll(TLModelValidator.validateModel(model, ValidatorFactory.COMPILE_RULE_SET_ID));
            findings.addAll(loaderFindings);
        }
        return refreshedItems;
    }

    /**
     * Returns the list of all items maintained by this project manager.
     * 
     * @return List<ProjectItem>
     */
    public List<ProjectItem> getAllProjectItems() {
        return Collections.unmodifiableList(projectItems);
    }

    /**
     * Returns the <code>ProjectItem</code> that represents the local instance of the given library.
     * 
     * @param library
     *            the library for which to return the associated project item
     * @return ProjectItem
     */
    public ProjectItem getProjectItem(AbstractLibrary library) {
        ProjectItem result = null;

        if (library != null) {
            for (ProjectItem item : projectItems) {
                if (item.getContent() == library) {
                    result = item;
                    break;
                }
            }
        }
        return result;
    }

    /**
     * Returns the <code>ProjectItem</code> that represents the local instance of the given library
     * URL.
     * 
     * @param libraryUrl
     *            the library URL for which to return the associated project item
     * @return ProjectItem
     */
    public ProjectItem getProjectItem(URL libraryUrl) {
        return (libraryUrl == null) ? null : getProjectItem(model.getLibrary(libraryUrl));
    }

    /**
     * Returns the version chain for the given project item. The resulting list includes all
     * dependent versions starting with the one provided and ending with the major version that
     * started the chain. If the given project item does not represent a <code>TLLibrary</code>,
     * only the given item will be returned in the resulting list.
     * 
     * @param item
     *            the project item for which to return the version chain
     * @return List<ProjectItem>
     * @throws VersionSchemeException
     *             thrown if the project item's version scheme is not recognized
     */
    public List<ProjectItem> getVersionChain(ProjectItem item) throws VersionSchemeException {
        List<ProjectItem> versionChain = new ArrayList<ProjectItem>();

        if (item != null) {
            if (item.getContent() instanceof TLLibrary) {
                List<TLLibrary> libraryChain = new MajorVersionHelper()
                        .getVersionChain((TLLibrary) item.getContent());

                for (TLLibrary library : libraryChain) {
                    ProjectItem chainItem = getProjectItem(library);

                    if (chainItem != null) {
                        versionChain.add(chainItem);
                    }
                }
            } else {
                versionChain.add(item);
            }
        }
        return versionChain;
    }

    /**
     * Returns the collection of projects to which the given project item is assigned.
     * 
     * @param item
     *            the project item to analyze
     * @return Collection<Project>
     */
    public Collection<Project> getAssignedProjects(ProjectItem item) {
        Collection<Project> projectList = new ArrayList<Project>();

        for (Project project : projects) {
            if (project.getProjectItems().contains(item)) {
                projectList.add(project);
            }
        }
        return projectList;
    }

    /**
     * Scans the list of failed project items for the given project and removes any
     * items that have been loaded successfully since the last check.
     * 
     * @param project  the project to be checked
     */
    public void checkFailedProjectItems(Project project) {
        Iterator<ProjectItemType> itemIterator = project.getFailedProjectItems().iterator();

        while (itemIterator.hasNext()) {
            ProjectItemType failedItem = itemIterator.next();

            if (isProjectItemLoaded(failedItem, project)) {
                itemIterator.remove();
            }
        }
    }

    /**
     * Adds the managed repository item as a <code>ProjectItem</code> and assigns it to the
     * specified model project.
     * 
     * @param item
     *            the repository item to add
     * @param project
     *            the project to which the repository item should be added
     * @return ProjectItem
     * @throws LibraryLoaderException
     *             thrown if a problem occurs during the library loading process
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    public ProjectItem addManagedProjectItem(RepositoryItem item, Project project)
            throws LibraryLoaderException, RepositoryException {
        List<ProjectItem> itemList = addManagedProjectItems(Arrays.asList(new RepositoryItem[] { item }), project);
        return (itemList.size() == 0) ? null : itemList.get(0);
    }

    /**
     * Adds the managed repository items as <code>ProjectItems</code> and assigns each of them to
     * the specified model project.
     * 
     * @param item
     *            the managed repository items to add
     * @param project
     *            the project to which the repository item should be added
     * @return List<ProjectItem>
     * @throws LibraryLoaderException
     *             thrown if a problem occurs during the library loading process
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    public List<ProjectItem> addManagedProjectItems(List<RepositoryItem> items, Project project)
            throws LibraryLoaderException, RepositoryException {
        return addManagedProjectItems(items, project, null);
    }

    /**
     * Adds the managed repository items as <code>ProjectItems</code> and assigns each of them to
     * the specified model project.
     * 
     * @param item
     *            the managed repository items to add
     * @param project
     *            the project to which the repository item should be added
     * @param findings
     *            validation findings where errors/warnings from the loading operation will be
     *            reported
     * @return List<ProjectItem>
     * @throws LibraryLoaderException
     *             thrown if a problem occurs during the library loading process
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    public List<ProjectItem> addManagedProjectItems(List<RepositoryItem> items, Project project,
            ValidationFindings findings) throws LibraryLoaderException, RepositoryException {
        ValidationFindings loaderFindings = new ValidationFindings();
        List<ProjectItem> projectItems = loadAllProjectItems(new ArrayList<File>(), items, project, loaderFindings);

        // Validate for errors/warnings if requested by the caller
        if (findings != null) {
            findings.addAll(loaderFindings);
            findings.addAll(TLModelValidator.validateModel(model, ValidatorFactory.COMPILE_RULE_SET_ID));
        }
        return projectItems;
    }

    /**
     * Creates a new <code>ProjectItem</code> to represent the given library and adds it to the
     * contents of the project as an <code>UNMANAGED</code> repository item. This method assumes
     * that the library is an uncontrolled artifact (not currently under repository control), and
     * has already been incorporated into the existing model that is maintained by this
     * <code>ProjectManager</code>.
     * 
     * @param libraryFile
     *            the file content for the library (or legacy schema) to add
     * @param project
     *            the project to which the unmanaged project item will be added
     * @return ProjectItem
     * @throws RepositoryException
     *             thrown if the given library is currently under the control of a repository
     */
    public ProjectItem addUnmanagedProjectItem(AbstractLibrary library, Project project)
            throws RepositoryException {
        if (library.getLibraryUrl() == null) {
            throw new RepositoryException(
                    "Unable to add the given library as an unmanaged item because it has not yet been saved.");
        }
        if (isRepositoryUrl(library.getLibraryUrl())) {
            throw new RepositoryException(
                    "Unable to add the given library as an unmanaged item since it is already under repository control.");
        }
        ProjectItem projectItem = null;

        // Check to see if a project item for this library already exists
        for (ProjectItem item : projectItems) {
            if (library == item.getContent()) {
                projectItem = item;
                break;
            }
        }

        // If no project item exists yet, create one automatically
        if (projectItem == null) {
            projectItem = ProjectItemImpl.newUnmanagedItem(URLUtils.toFile(library.getLibraryUrl()), library, this);

            if (model.getLibrary(library.getLibraryUrl()) == null) {
                try {
                    model.addLibrary(library);
                    new LibraryModelLoader<InputStream>(model).resolveModelEntityReferences();

                } catch (LibraryLoaderException e) {
                    // Should never happen, but just in case...
                    throw new RepositoryException("Error resolving references during library add.", e);
                }
            }
        }

        // If the new (or existing) project item is not yet assigned to the project, assign it now
        if (!project.isMemberOf(projectItem)) {
            addProjectItem(projectItem);
            project.add(projectItem);
        }

        // If required, save the project we just modified
        if (autoSaveProjects) {
            try {
                saveProject(project, false, null);

            } catch (LibrarySaveException e) {
                log.error("Error saving updates to project: " + project.getProjectId(), e);
            }
        }
        return projectItem;
    }

    /**
     * Creates a new <code>ProjectItem</code> to represent the given library file (.otm or .xsd),
     * and adds it to the contents of the project as an <code>UNMANAGED</code> repository item.
     * 
     * @param libraryFile
     *            the file content for the library (or legacy schema) to add
     * @param project
     *            the project to which the unmanaged project item will be added
     * @return ProjectItem
     * @throws LibraryLoaderException
     *             thrown if a problem occurs during the library loading process
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    public ProjectItem addUnmanagedProjectItem(File libraryFile, Project project)
            throws LibraryLoaderException, RepositoryException {
        List<ProjectItem> itemList = addUnmanagedProjectItems(Arrays.asList(new File[] { libraryFile }), project);
        return (itemList.size() == 0) ? null : itemList.get(0);
    }

    /**
     * Creates a new <code>ProjectItem</code> to represent the given library file (.otm or .xsd),
     * and adds it to the contents of the project as an <code>UNMANAGED</code> repository item.
     * 
     * @param libraryFile
     *            the file content for the library (or legacy schema) to add
     * @param project
     *            the project to which the unmanaged project item will be added
     * @return List<ProjectItem>
     * @throws LibraryLoaderException
     *             thrown if a problem occurs during the library loading process
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    public List<ProjectItem> addUnmanagedProjectItems(List<File> libraryFiles, Project project)
            throws LibraryLoaderException, RepositoryException {
        return addUnmanagedProjectItems(libraryFiles, project, null);
    }

    /**
     * Creates new <code>ProjectItems</code> to represent each of the given library files (.otm or
     * .xsd), and adds them to the contents of the project as <code>UNMANAGED</code> repository
     * items.
     * 
     * @param libraryFiles
     *            the file content for the unmanaged libraries (and/or legacy schemas) to add
     * @param project
     *            the project to which the unmanaged project item will be added
     * @param findings
     *            validation findings where errors/warnings from the loading operation will be
     *            reported
     * @return List<ProjectItem>
     * @throws LibraryLoaderException
     *             thrown if a problem occurs during the library loading process
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    public List<ProjectItem> addUnmanagedProjectItems(List<File> libraryFiles, Project project,
            ValidationFindings findings) throws LibraryLoaderException, RepositoryException {
        ValidationFindings loaderFindings = new ValidationFindings();
        List<ProjectItem> projectItems = loadAllProjectItems(libraryFiles, new ArrayList<RepositoryItem>(), project,
                loaderFindings);

        // Validate for errors/warnings if requested by the caller
        if (findings != null) {
            findings.addAll(loaderFindings);
            findings.addAll(TLModelValidator.validateModel(model, ValidatorFactory.COMPILE_RULE_SET_ID));
        }
        return projectItems;
    }

    /**
     * Creates new <code>ProjectItems</code> to represent each of the managed and unmanaged library
     * resources (.otm or .xsd), and adds them to the contents of the project.
     * 
     * @param libraryFiles
     *            the file content for the unmanaged libraries (and/or legacy schemas) to add
     * @param managedItems
     *            the managed repository items to be included in the project
     * @param project
     *            the project to which the project item will be added
     * @param loaderFindings
     *            validation findings where errors/warnings from the loading operation will be
     *            reported
     * @return List<ProjectItem>
     * @throws LibraryLoaderException
     *             thrown if a problem occurs during the library loading process
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    private List<ProjectItem> loadAllProjectItems(List<File> libraryFiles, List<RepositoryItem> managedItems,
            Project project, ValidationFindings loaderFindings) throws LibraryLoaderException, RepositoryException {
        LibraryModelLoader<InputStream> modelLoader = new LibraryModelLoader<InputStream>(model);
        List<ProjectItem> newItems = new ArrayList<ProjectItem>();

        // Initialize the loader and validation findings
        modelLoader.setResolveModelReferences(false);

        // Optimization that will pre-process the repository URL's for each library.  This will
        // allow the loader to bypass multiple downloads of the libraries from the remote repository.
        if ((managedItems != null) && !managedItems.isEmpty()) {
            LibraryNamespaceResolver nsResolver = new DefaultLibraryNamespaceResolver();

            modelLoader.setNamespaceResolver(nsResolver);

            for (RepositoryItem managedItem : managedItems) {
                String repositoryUri = "otm://" + managedItem.getRepository().getId() + "/"
                        + managedItem.getFilename();
                String libraryUrl = repositoryManager.getContentLocation(managedItem).toExternalForm();

                nsResolver.setRepositoryLocation(repositoryUri, libraryUrl);
            }
        }

        // Load all of the managed repository items
        for (RepositoryItem managedItem : managedItems) {
            URL libraryUrl = repositoryManager.getContentLocation(managedItem);
            ProjectItem newItem = null;

            // Load the library from the local file system if it does not already exist in the model
            if (!model.hasLibrary(libraryUrl)) {
                try {
                    modelLoader.loadLibraryModel(new LibraryStreamInputSource(libraryUrl));

                    AbstractLibrary managedLibrary = model.getLibrary(libraryUrl);

                    if (managedLibrary != null) {
                        // Check the namespace of the library we just loaded and make sure it matches the
                        // repository item; if not, reassign the libary's namespace and issue a loader warning.
                        if (!managedItem.getNamespace().equals(managedLibrary.getNamespace())) {
                            try {
                                managedLibrary.setNamespace(managedItem.getNamespace());
                                loaderFindings.addFinding(FindingType.WARNING,
                                        new LibraryValidationSource(managedLibrary),
                                        LoaderValidationMessageKeys.WARNING_MANAGED_LIBRARY_NAMESPACE_MISMATCH,
                                        managedItem.getFilename());

                            } catch (IllegalArgumentException e) {
                                // If we cannot reassign the namespace because a duplicate exists,
                                // remove the library and issue a loader error.
                                loaderFindings.addFinding(FindingType.ERROR,
                                        new LibraryValidationSource(managedLibrary),
                                        LoaderValidationMessageKeys.ERROR_MANAGED_LIBRARY_NAMESPACE_ERROR,
                                        managedItem.getFilename());
                            }
                        }
                        newItem = newManagedProjectItem(managedItem, managedLibrary);
                    }

                } catch (Throwable t) {
                    if (loaderFindings != null) {
                        loaderFindings.addFinding(FindingType.ERROR,
                                new FileValidationSource(project.getProjectFile()),
                                LoaderValidationMessageKeys.ERROR_UNKNOWN_EXCEPTION_DURING_PROJECT_LOAD,
                                URLUtils.getShortRepresentation(libraryUrl),
                                ExceptionUtils.getExceptionClass(t).getSimpleName(),
                                ExceptionUtils.getExceptionMessage(t));
                    }
                }

            } else { // Otherwise, locate the existing project item
                newItem = findOrCreateProjectItem(model.getLibrary(libraryUrl));
            }

            if (newItem != null) {
                addProjectItem(newItem);
                project.add(newItem);
                newItems.add(newItem);
            }
        }

        // Load all of the unmanaged library files
        for (File libraryFile : libraryFiles) {
            URL libraryUrl = URLUtils.toURL(libraryFile);
            ProjectItem newItem = null;

            if (!libraryFile.exists()) {
                loaderFindings.addFinding(FindingType.ERROR, new FileValidationSource(libraryFile),
                        LoaderValidationMessageKeys.ERROR_INVALID_PROJECT_ITEM_LOCATION,
                        libraryFile.getAbsolutePath());

            } else if (!model.hasLibrary(libraryUrl)) { // Load the library from the local file
                                                        // system if it does not already exist in
                                                        // the model
                try {
                    modelLoader.loadLibraryModel(new LibraryStreamInputSource(libraryUrl));

                    AbstractLibrary unmanagedLibrary = model.getLibrary(libraryUrl);

                    if (unmanagedLibrary != null) {
                        newItem = ProjectItemImpl.newUnmanagedItem(URLUtils.toFile(libraryUrl), unmanagedLibrary,
                                this);
                    }

                } catch (Throwable t) {
                    if (loaderFindings != null) {
                        loaderFindings.addFinding(FindingType.ERROR,
                                new FileValidationSource(project.getProjectFile()),
                                LoaderValidationMessageKeys.ERROR_UNKNOWN_EXCEPTION_DURING_PROJECT_LOAD,
                                URLUtils.getShortRepresentation(libraryUrl),
                                ExceptionUtils.getExceptionClass(t).getSimpleName(),
                                ExceptionUtils.getExceptionMessage(t));
                    }
                }
            } else { // Otherwise, locate the existing project item
                newItem = findOrCreateProjectItem(model.getLibrary(libraryUrl));
            }

            if (newItem != null) {
                addProjectItem(newItem);
                project.add(newItem);
                newItems.add(newItem);
            }
        }

        // Searching for any newly-discovered dependencies that need to be added to the project
        if (loaderFindings != null) {
            loaderFindings.addAll(modelLoader.getLoaderFindings());
        }
        modelLoader.resolveModelEntityReferences();
        updateProjectDependencies(project, newItems);

        // Update all import/include references in the project, just in case any have changed since
        // the last time this/these libraries were loaded.
        for (ProjectItem item : projectItems) {
            if (item.getContent() instanceof TLLibrary) {
                ImportManagementIntegrityChecker.verifyReferencedLibraries((TLLibrary) item.getContent());
            }
        }

        // In some cases, libraries may have been referenced by includes/imports, but no real dependencies
        // to their content exist. In these cases, libraries (project-items) that are not explicitly assigned
        // to a project need to be purged.
        purgeOrphanedProjectItems();

        if (autoSaveProjects) {
            saveAffectedProjects(newItems);
        }
        return newItems;
    }

    /**
     * Publishes the given <code>UNMANAGED</code> item to the specified remote repository. Once
     * added, the item will be replaced by a reference to the newly-created
     * <code>MANAGED_LOCKED</code> item in the repository.
     * 
     * @param item
     *            the item to be added as a managed asset of the repository
     * @param repository
     *            the repository to which the given item will be published
     * @throws IllegalStateException
     *             thrown if the project item's state is not <code>UNMANAGED</code>
     * @throws RepositoryException
     *             thrown if the remote repository cannot be accessed or an error occurs during
     *             publication
     * @throws PublishWithLocalDependenciesException
     *             thrown if the project item to be published contains a local file reference
     */
    public void publish(ProjectItem item, Repository repository)
            throws PublishWithLocalDependenciesException, RepositoryException {
        publish(Arrays.asList(new ProjectItem[] { item }), repository);
    }

    /**
     * Publishes the given <code>UNMANAGED</code> item to the specified remote repository. Once
     * added, the item will be replaced by a reference to the newly-created
     * <code>MANAGED_LOCKED</code> item in the repository.
     * 
     * @param items
     *            the collection of items to be added as managed assets of the repository
     * @param repository
     *            the repository to which the given items will be published
     * @throws IllegalStateException
     *             thrown if a project item's state is not <code>UNMANAGED</code>
     * @throws RepositoryException
     *             thrown if the remote repository cannot be accessed or an error occurs during
     *             publication
     * @throws PublishWithLocalDependenciesException
     *             thrown if one or more of the project items to be published contains a local file
     *             reference
     */
    public void publish(Collection<ProjectItem> items, Repository repository)
            throws PublishWithLocalDependenciesException, RepositoryException {
        // Check repository state of each item to make sure it can be published; also make sure that
        // the
        // user has write access to each item's namespace in the target repository
        Set<String> authorizedNamespaces = new HashSet<String>();

        for (ProjectItem item : items) {
            if (item.getState() != RepositoryItemState.UNMANAGED) {
                throw new RepositoryException("Only unmanaged items can be published to a repository.");
            }
            if (!authorizedNamespaces.contains(item.getBaseNamespace())) {
                if (repository.getUserAuthorization(item.getBaseNamespace()) == RepositoryPermission.WRITE) {
                    authorizedNamespaces.add(item.getBaseNamespace());

                } else {
                    throw new RepositoryException("The user does not have write permission for namespace '"
                            + item.getBaseNamespace() + "'.");
                }
            }
        }

        // Also be sure that no dependencies on unmanaged files exist before proceeding
        Collection<ProjectItem> unmanagedReferences = new ArrayList<ProjectItem>();

        findUnmanagedReferences(items, unmanagedReferences);

        if (!unmanagedReferences.isEmpty()) {
            for (ProjectItem item : items) {
                if (!unmanagedReferences.contains(item)) {
                    unmanagedReferences.add(item);
                }
            }
            throw new PublishWithLocalDependenciesException(
                    "One or more libraries contains references to unmanaged content.  "
                            + "Confirm publication of dependent libraries and resubmit.",
                    items, unmanagedReferences);
        }

        // Perform the final preparation of all items for publication
        prepareForPublication(items, repository);

        // Publish each item's content to the repository and download a copy to the local cache
        Collection<ProjectItem> successfullyPublishedItems = new ArrayList<ProjectItem>();
        Map<AbstractLibrary, URL> originalLibraryUrls = new HashMap<AbstractLibrary, URL>();
        boolean allItemsSuccessful = false;

        try {
            for (ProjectItem item : items) {
                File contentFile = URLUtils.toFile(item.getContent().getLibraryUrl());
                File tempFile = null;
                boolean currentItemSuccessful = false;

                // Store the original URL of each library, just in case we need to roll back later
                originalLibraryUrls.put(item.getContent(), item.getContent().getLibraryUrl());

                try {
                    // Start by saving the current content of the library to a temporary location
                    // (TLLibrary only)
                    // NOTE: Saving to a temp file avoids the problem of stomping the original
                    // backup file we created
                    // during the preparation step above.
                    File libraryFile;

                    if (item.getContent() instanceof TLLibrary) {
                        try {
                            libraryFile = tempFile = File.createTempFile("publishTemp", ".otm");

                            ((TLLibrary) item.getContent()).setLibraryUrl(URLUtils.toURL(tempFile));
                            new LibraryModelSaver().saveLibrary((TLLibrary) item.getContent());

                        } catch (LibrarySaveException e) {
                            throw new RepositoryException(
                                    "Error saving current state of library: " + item.getFilename());
                        }

                    } else {
                        libraryFile = contentFile;
                    }

                    // Publish the library to the repository
                    InputStream contentStream = new FileInputStream(libraryFile);
                    TLLibraryStatus initialStatus = TLLibraryStatus.DRAFT;
                    String versionScheme = null;

                    if (item.getContent() instanceof TLLibrary) {
                        TLLibrary library = (TLLibrary) item.getContent();

                        initialStatus = library.getStatus();
                        versionScheme = library.getVersionScheme();
                    }
                    RepositoryItem repositoryItem = repository.publish(contentStream,
                            getPublicationFilename(item.getContent()), item.getLibraryName(), item.getNamespace(),
                            item.getVersion(), versionScheme, initialStatus);

                    // Change the library URL to be the copy we just moved into the local repository
                    URL managedLibraryUrl = repositoryManager.getContentLocation(repositoryItem);
                    ProjectItemImpl managedItem = (ProjectItemImpl) item;

                    managedItem.getContent().setLibraryUrl(managedLibraryUrl);
                    managedItem.setState(RepositoryItemState.MANAGED_UNLOCKED);
                    managedItem.setRepository(repository);

                    // Save the changes to the project state
                    if (autoSaveProjects && (item != null)) {
                        saveAffectedProjects(item);
                    }

                    // Rename our original file to ".bak" since the 'real' content is now managed
                    // by the repository.
                    File backupFile = getBackupFile(contentFile);

                    if (backupFile.exists()) { // delete the old backup, if one exists
                        backupFile.delete();
                    }
                    contentFile.renameTo(backupFile);

                    currentItemSuccessful = true;

                } catch (IOException e) {
                    throw new RepositoryException(
                            "Unable to publish unmanaged project content: " + item.getFilename(), e);

                } finally {
                    // Delete the temp file we created prior to publication
                    if (tempFile != null)
                        tempFile.delete();

                    // If unsuccessful, we need to change the library's URL back to its original
                    // value
                    if (!currentItemSuccessful && (item.getContent() instanceof TLLibrary)) {
                        ((TLLibrary) item.getContent()).setLibraryUrl(URLUtils.toURL(contentFile));
                    }
                }
            }

        } finally {
            if (allItemsSuccessful) {
                // Since each of the files are now managed, we need to update the other models in
                // the
                // project to reference them using a repository URI for the import/include file
                // hints
                Collection<Project> affectedProjects = new HashSet<Project>();
                Set<ProjectItem> affectedProjectItems = new HashSet<ProjectItem>();

                for (ProjectItem item : items) {
                    affectedProjects.addAll(getAssignedProjects(item));
                }
                for (Project project : affectedProjects) {
                    for (ProjectItem affectedItem : project.getProjectItems()) {
                        if (!items.contains(affectedItem)) {
                            affectedProjectItems.add(affectedItem);
                        }
                    }
                }
                for (ProjectItem affectedItem : affectedProjectItems) {
                    if (affectedItem.getContent() instanceof TLLibrary) {
                        TLLibrary affectedLibrary = (TLLibrary) affectedItem.getContent();

                        if (!affectedLibrary.isReadOnly()) {
                            ImportManagementIntegrityChecker.verifyReferencedLibraries(affectedLibrary);
                        }
                    }
                }

            } else {
                // Rollback the changes that were made to each of the libraries, and delete any
                // items
                // that were successfully published from the repository
                for (ProjectItem rollbackItem : successfullyPublishedItems) {
                    ProjectItemImpl unmanagedItem = (ProjectItemImpl) rollbackItem;

                    // Delete the item from the repository
                    try {
                        repository.delete(rollbackItem);

                    } catch (Throwable t) {
                        log.warn("Error during publicaton rollback - unable to delete item from the repository: "
                                + rollbackItem.getFilename(), t);
                    }

                    // Reset the properties of the local project item to their original state
                    unmanagedItem.getContent().setLibraryUrl(originalLibraryUrls.get(unmanagedItem.getContent()));
                    unmanagedItem.setState(RepositoryItemState.UNMANAGED);
                    unmanagedItem.setRepository(null);

                }
            }
        }
    }

    /**
     * Searches the model for dependencies of the given project item and adds them to the collection
     * of unmanaged references.
     * 
     * @param item
     *            the item to check for dependencies
     * @param pendingPublicationItems
     *            the list of all items to be published that are currently unmanaged
     * @param unmanagedReferences
     *            the collection of unmanaged references
     */
    private void findUnmanagedReferences(Collection<ProjectItem> pendingPublicationItems,
            Collection<ProjectItem> unmanagedReferences) {
        for (ProjectItem item : pendingPublicationItems) {
            Collection<ProjectItem> itemList = new HashSet<ProjectItem>();
            int unmanagedReferenceCount = -1;

            itemList.add(item); // 1st loop - just analyze the new item

            while (unmanagedReferences.size() != unmanagedReferenceCount) { // continue until our
                                                                            // count does not change
                Collection<ProjectItem> newUnmanagedReferences = new HashSet<ProjectItem>();

                unmanagedReferenceCount = unmanagedReferences.size();

                for (ProjectItem currentItem : itemList) {
                    if (currentItem.getContent() instanceof TLLibrary) {
                        List<AbstractLibrary> referencedLibraries = ImportManagementIntegrityChecker
                                .getReferencedLibraries((TLLibrary) currentItem.getContent());

                        for (AbstractLibrary referencedLib : referencedLibraries) {
                            if (referencedLib instanceof BuiltInLibrary)
                                continue;
                            ProjectItem referencedItem = getProjectItem(referencedLib);

                            if (referencedItem == null) {
                                throw new IllegalStateException(
                                        "A dependent library is referenced that is not under the control of the local Project Manager: "
                                                + referencedLib.getName());

                            } else if (!pendingPublicationItems.contains(referencedItem)
                                    && (referencedItem.getState() == RepositoryItemState.UNMANAGED)
                                    && !newUnmanagedReferences.contains(referencedItem)) {
                                newUnmanagedReferences.add(referencedItem);
                            }
                        }
                    }
                }

                // Add the newly-found unmanaged dependencies to the list and continue looping until
                // we do not find any new dependencies.
                for (ProjectItem urItem : newUnmanagedReferences) {
                    if (!unmanagedReferences.contains(urItem)) {
                        unmanagedReferences.add(urItem);
                    }
                }
                itemList.clear();
                itemList.addAll(newUnmanagedReferences); // subsequent loops - look for dependencies
                                                         // of the items we just found
            }
        }
    }

    /**
     * Prepares each of the items that are pending publication by updating their includes and
     * imports to reflect repository URI locations. This method also saves the original state of
     * each library prior to adjusting its imports and includes.
     * 
     * @param pendingPublicationItems
     *            the list of all items to be published that are currently unmanaged
     * @param repository
     *            the repository to which the pending items are to be published
     * @throws RepositoryException
     *             thrown if the namespace URI for any of the pending items is invalid
     */
    private void prepareForPublication(Collection<ProjectItem> pendingPublicationItems, Repository repository)
            throws RepositoryException {
        Collection<TLLibrary> preparedLibraries = new ArrayList<TLLibrary>();
        boolean success = false;
        try {
            Map<AbstractLibrary, URL> libraryUrlOverrides = new HashMap<AbstractLibrary, URL>();

            for (ProjectItem item : pendingPublicationItems) {
                // Calculate the URL for the local repository location of the item (needed for the
                // next step).
                File localRepositoryLocation = repositoryManager.getFileManager()
                        .getLibraryContentLocation(item.getBaseNamespace(), item.getFilename(), item.getVersion());

                libraryUrlOverrides.put(item.getContent(), URLUtils.toURL(localRepositoryLocation));

                // A bit of a hack - we need to trick the import/include updater into thinking that
                // the
                // items to be published have already been processed
                if (item.getContent() instanceof TLLibrary) {
                    ((ProjectItemImpl) item).setRepository(repository);
                    ((ProjectItemImpl) item).setState(RepositoryItemState.MANAGED_UNLOCKED);
                }
            }

            // Automatically save each library and update the includes/imports to reflect repository
            // URI
            // references where necessary
            for (ProjectItem item : pendingPublicationItems) {
                if (item.getContent() instanceof TLLibrary) {
                    TLLibrary itemLibrary = (TLLibrary) item.getContent();

                    try {
                        new LibraryModelSaver().saveLibrary(itemLibrary);

                    } catch (LibrarySaveException e) {
                        throw new RepositoryException(
                                "Error saving current state of library: " + item.getFilename());
                    }
                    ImportManagementIntegrityChecker.verifyReferencedLibraries(itemLibrary, libraryUrlOverrides);
                    preparedLibraries.add(itemLibrary);
                }
            }
            success = true;

        } finally {
            // Regardless of the outcome, we need to undo the hack that we used to trick the
            // import/include updater
            for (ProjectItem item : pendingPublicationItems) {
                if (item instanceof ProjectItemImpl) {
                    ((ProjectItemImpl) item).setRepository(null);
                    ((ProjectItemImpl) item).setState(RepositoryItemState.UNMANAGED);
                }
            }

            // If we were not successful, revert the changes we made to the import/includes of each
            // library
            if (!success) {
                for (TLLibrary library : preparedLibraries) {
                    ImportManagementIntegrityChecker.verifyReferencedLibraries(library);
                }
            }
        }
    }

    /**
     * Locks the given <code>ProjectItem</code> for editing by the local user. When an item is
     * locked, the reference to the remote repository item is replaced with a work-in-process (WIP)
     * copy of the library file in the local workspace.
     * 
     * @param item
     *            the repository item to lock
     * @param wipFolder
     *            the folder location where the work-in-process file is to be created
     * @throws IllegalStateException
     *             thrown if the project item's state is not <code>MANAGED_UNLOCKED</code>
     * @throws RepositoryException
     */
    public void lock(ProjectItem item, File wipFolder) throws RepositoryException {
        boolean success = false;
        File backupFile = null;
        File wipFile = null;
        try {
            // Perform validation checks
            switch (item.getState()) {
            case MANAGED_LOCKED:
                throw new RepositoryException("Unable to lock - the item is already locked by another user.");

            case MANAGED_WIP:
                throw new RepositoryException("Unable to lock - the item is already locked by the local user.");

            case UNMANAGED:
                throw new RepositoryException("Unable to lock - the item is not a managed artifact.");

            default:
                break;
            }

            // Copy the repository file to the WIP location
            File repositoryFile = URLUtils.toFile(item.getContent().getLibraryUrl());

            wipFile = repositoryManager.getFileManager().getLibraryWIPContentLocation(item.getBaseNamespace(),
                    item.getFilename());
            backupFile = ProjectFileUtils.createBackupFile(wipFile);
            ProjectFileUtils.copyFile(repositoryFile, wipFile);

            // Call the remote web service to obtain the lock
            ProjectItemImpl managedItem = (ProjectItemImpl) item;

            if (managedItem.getRepository() == repositoryManager) {
                managedItem.setLockedByUser(System.getProperty("user.name"));
            }
            repositoryManager.lock(managedItem);

            // Update the project item and its library to reference the WIP file instead of the
            // managed repository item.
            managedItem.getContent().setLibraryUrl(URLUtils.toURL(wipFile));

            // Save the changes to the project state
            if (autoSaveProjects) {
                saveAffectedProjects(item);
            }
            success = true;

        } catch (IOException e) {
            throw new RepositoryException(
                    "Unable to obtain a lock for the managed project content: " + item.getFilename(), e);

        } finally {
            try {
                if (!success) {
                    // Roll back workspace file changes if we encountered an error
                    if ((wipFile != null) && wipFile.exists())
                        wipFile.delete();
                    if (backupFile != null)
                        ProjectFileUtils.restoreBackupFile(backupFile, wipFile.getName());

                } else {
                    // Purge the backup file if the operation was successful
                    if (backupFile != null)
                        ProjectFileUtils.removeBackupFile(backupFile);
                }
            } catch (Throwable t) {
            }
        }
    }

    /**
     * Unlocks the given work-in-process (WIP) <code>ProjectItem</code>. If the 'commitWIP' flag is
     * true, the conent of the WIP file will be committed to the remote repository before the
     * existing lock is released. If false, any changes in the the WIP content will be discarded.
     * 
     * @param item
     *            the repository item to unlock
     * @param commitWIP
     *            flag indicating whether to commit the existing work-in-process content for the
     *            item
     * @throws IllegalStateException
     *             thrown if the project item's state is not <code>MANAGED_WIP</code>
     * @throws RepositoryException
     */
    public void unlock(ProjectItem item, boolean commitWIP) throws RepositoryException {
        // Refresh the content from the remote repository to make sure we are working with the most
        // current data
        if (item.getRepository() instanceof RemoteRepository) {
            ((RemoteRepository) item.getRepository()).downloadContent(item, true);
        }

        // Perform validation checks
        if (item.getState() != RepositoryItemState.MANAGED_WIP) {
            throw new RepositoryException(
                    "Unable to release lock - the item is not currently locked by the local user.");
        }

        // Call the remote web service to obtain the lock
        ProjectItemImpl managedItem = (ProjectItemImpl) item;

        repositoryManager.unlock(managedItem, commitWIP);

        // Update the project item and its library to reference the managed repository item instead
        // of the WIP file.
        URL managedItemUrl = repositoryManager.getContentLocation(managedItem);

        managedItem.getContent().setLibraryUrl(managedItemUrl);

        // Save the changes to the project state
        if (autoSaveProjects) {
            saveAffectedProjects(item);
        }
    }

    /**
     * Commits the content of the given work-in-process (WIP) <code>ProjectItem</code> to the remote
     * repository, but retains the local user's lock.
     * 
     * @param item
     *            the repository item whose content is to be committed
     * @throws IllegalStateException
     *             thrown if the project item's state is not <code>MANAGED_WIP</code>
     * @throws RepositoryException
     */
    public void commit(ProjectItem item) throws RepositoryException {
        if (item.getState() != RepositoryItemState.MANAGED_WIP) {
            throw new RepositoryException("Unable to commit - the item is not a work-in-process copy.");
        }

        // Automatically save the library to make sure its file system representation is in-synch
        // with the copy we have in memory
        if (autoSaveProjects) {
            try {
                if (item.getContent() instanceof TLLibrary) {
                    new LibraryModelSaver().saveLibrary((TLLibrary) item.getContent());
                }
            } catch (LibrarySaveException e) {
                throw new RepositoryException("Error saving library before committing content to the repository.",
                        e);
            }
        }
        repositoryManager.commit(item);
    }

    /**
     * Reverts the contents of the given work-in-process (WIP) <code>ProjectItem</code> to the
     * content currently published in the remote repository. The local user's lock on the file is
     * retained (leaving the item in the <code>MANAGED_WIP</code> state), but all local changes are
     * permanently discarded.
     * 
     * @param item
     *            the repository item whose content is to be committed
     * @throws IllegalStateException
     *             thrown if the project item's state is not <code>MANAGED_WIP</code>
     * @throws RepositoryException
     */
    public void revert(ProjectItem item) throws RepositoryException {
        if (item.getState() != RepositoryItemState.MANAGED_WIP) {
            throw new RepositoryException("Unable to revert - the item is not a work-in-process copy.");
        }
        repositoryManager.revert(item);
    }

    /**
     * Returns a backup file with the same name and location, but with an extension of ".bak".
     * 
     * @param originalFile
     *            the original file for which to return a backup
     * @return File
     */
    private File getBackupFile(File originalFile) {
        String backupFilename = originalFile.getName();
        int dotIdx = backupFilename.lastIndexOf('.');

        if (dotIdx >= 0) {
            backupFilename = backupFilename.substring(0, dotIdx);
        }
        backupFilename += ".bak";
        return new File(originalFile.getParentFile(), backupFilename);
    }

    /**
     * Promotes a managed <code>ProjectItem</code> from <code>DRAFT</code> status to
     * <code>FINAL</code>. Items must be in the <code>MANAGED_UNLOCKED</code> state in order to be
     * promoted.
     * 
     * @param item
     *            the managed project item to promote
     * @throws RepositoryException
     */
    public void promote(ProjectItem item) throws RepositoryException {
        TLLibraryStatus currentStatus = TLLibraryStatus.FINAL;

        if (item.getContent() instanceof TLLibrary) {
            currentStatus = ((TLLibrary) item.getContent()).getStatus();
        }
        if (item.getState() != RepositoryItemState.MANAGED_UNLOCKED) {
            throw new RepositoryException(
                    "Unable to promote - the item must be a managed resource that not locked for editing.");
        }
        if (currentStatus != TLLibraryStatus.DRAFT) {
            throw new RepositoryException(
                    "Unable to promote - only user-defined libraries in DRAFT status can be promoted.");
        }
        repositoryManager.promote(item);

        if (item.getContent() instanceof TLLibrary) {
            ((TLLibrary) item.getContent()).setStatus(TLLibraryStatus.FINAL);
        }
    }

    /**
     * Promotes a managed <code>ProjectItem</code> from <code>DRAFT</code> status to
     * <code>FINAL</code>. This operation can only be performed if the local user has administrative
     * permissions to modify the requested item, and the item is in the
     * <code>MANAGED_UNLOCKED</code> state.
     * 
     * @param item
     *            the managed project item to demote
     * @throws RepositoryException
     */
    public void demote(ProjectItem item) throws RepositoryException {
        TLLibraryStatus currentStatus = TLLibraryStatus.FINAL;

        if (item.getContent() instanceof TLLibrary) {
            currentStatus = ((TLLibrary) item.getContent()).getStatus();
        }
        if (item.getState() != RepositoryItemState.MANAGED_UNLOCKED) {
            throw new RepositoryException(
                    "Unable to demote - the item must be a managed resource that is not locked for editing.");
        }
        if (currentStatus != TLLibraryStatus.FINAL) {
            throw new RepositoryException(
                    "Unable to demote - only user-defined libraries in FINAL status can be demoted.");
        }
        repositoryManager.demote(item);

        if (item.getContent() instanceof TLLibrary) {
            ((TLLibrary) item.getContent()).setStatus(TLLibraryStatus.DRAFT);
        }
    }

    /**
     * Returns true if the given URL references a location in the user's local repository -- either
     * as a locally-managed item or a local copy of a remotely-managed library.
     * 
     * @param libraryUrl
     *            the library URL to analyze
     * @return boolean
     */
    public boolean isRepositoryUrl(URL libraryUrl) {
        boolean result = false;

        if (URLUtils.isFileURL(libraryUrl)) {
            File repositoryLocation = repositoryManager.getRepositoryLocation();
            File libraryFolder = URLUtils.toFile(libraryUrl).getParentFile();

            while (!result && (libraryFolder != null)) {
                result = libraryFolder.equals(repositoryLocation);
                libraryFolder = libraryFolder.getParentFile();
            }
        }
        return result;
    }

    /**
     * If the given URL resolves to a repository item, this method will return the
     * ID of the repository from which the library is managed.  If the library is not
     * a repository-managed item, this method will return null.  NOTE: This method will
     * only return a result if a local copy of the repository has been loaded from
     * the remote server.
     * 
     * @param libraryUrl  the URL of the library for which to return a repository ID
     * @return String
     */
    public String getRepositoryId(URL libraryUrl) {
        String repositoryId = null;

        if (isRepositoryUrl(libraryUrl)) {
            try {
                File libraryFile = URLUtils.toFile(libraryUrl);
                String metadataFilename = repositoryManager.getFileManager()
                        .getLibraryMetadataFilename(libraryFile.getName());
                File metadataFile = new File(libraryFile.getParentFile(), metadataFilename);

                if (metadataFile.exists()) {
                    LibraryInfoType libraryMetadata = repositoryManager.getFileManager()
                            .loadLibraryMetadata(metadataFile);
                    repositoryId = libraryMetadata.getOwningRepository();
                }

            } catch (RepositoryException e) {
                // Ignore error and return null
            }
        }
        return repositoryId;
    }

    /**
     * Verifies that the specified project ID is not among the list of currently-open projects.
     * 
     * @param projectFile
     *            the project file to analyze
     * @param existingProject
     *            indicates that this check is for an existing project that should be ignored when
     *            performing the search
     * @throws IllegalArgumentException
     *             thrown if the file and/or ID provided are already in use
     */
    protected void validateProjectID(String projectId, Project existingProject) {
        if ((projectId == null) || (projectId.length() == 0)) {
            throw new IllegalArgumentException("The project ID cannot be null or blank.");
        }
        for (Project project : projects) {
            if (project == existingProject) {
                continue;
            }
            if (projectId.equals(project.getProjectId())) {
                throw new IllegalArgumentException("The specified project ID is already in use: " + projectId);
            }
        }
    }

    /**
     * Verifies that the specified project file is not among the list of currently-open projects.
     * 
     * @param projectFile
     *            the project file to analyze
     * @param existingProject
     *            indicates that this check is for an existing project that should be ignored when
     *            performing the search
     * @throws IllegalArgumentException
     *             thrown if the file and/or ID provided are already in use
     */
    protected void validateProjectFile(File projectFile, Project existingProject) {
        if (projectFile == null) {
            throw new IllegalArgumentException("The project file location cannot be null.");
        }
        for (Project project : projects) {
            if ((project == existingProject) || BuiltInProject.BUILTIN_PROJECT_ID.equals(project.getProjectId())) {
                continue;
            }
            if (projectFile.getAbsolutePath().equals(project.getProjectFile().getAbsolutePath())) {
                throw new IllegalArgumentException(
                        "The specified project file is already open: " + projectFile.getAbsolutePath());
            }
        }
    }

    /**
     * Adds any new project items that are required based on the current set of dependencies of the
     * items that currently exist in the project.
     * 
     * @param project
     *            the project to be analyzed and updated
     * @param addedProjectItems
     *            collection where any newly discovered project items will be added (may be null)
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    private void updateProjectDependencies(Project project, Collection<ProjectItem> addedProjectItems)
            throws RepositoryException {
        Map<String, ProjectItem> globalItemMap = new HashMap<String, ProjectItem>();
        Set<String> existingProjectLibraries = new HashSet<String>();

        // Prepare the set of existing project libraries and a global map of all know project items
        refreshProjectItems();

        for (ProjectItem item : project.getProjectItems()) {
            existingProjectLibraries.add(item.getContent().getLibraryUrl().toExternalForm());
        }
        for (ProjectItem item : projectItems) {
            globalItemMap.put(item.getContent().getLibraryUrl().toExternalForm(), item);
        }

        // Scan for dependencies of the project's existing libraries
        DependencyFilterBuilder builder = new DependencyFilterBuilder()
                .setNavigator(new ProjectItemDependencyNavigator()).setIncludeExtendedLegacySchemas(true)
                .setIncludeEntityExtensions(true);

        for (ProjectItem item : project.getProjectItems()) {
            if (item.getContent() != null) {
                builder.addLibrary(item.getContent());
            }
        }
        CodeGenerationFilter filter = builder.buildFilter();

        // Check to see if new dependencies need to be added to the project
        for (AbstractLibrary library : model.getAllLibraries()) {
            if (library instanceof BuiltInLibrary) {
                continue;
            }
            if (!existingProjectLibraries.contains(library.getLibraryUrl().toExternalForm())
                    && filter.processLibrary(library)) {
                ProjectItem itemToAdd = globalItemMap.get(library.getLibraryUrl().toExternalForm());

                if (itemToAdd != null) {
                    if (addedProjectItems != null) {
                        addedProjectItems.add(itemToAdd);
                    }
                    project.add(itemToAdd);
                }
            }
        }
    }

    /**
     * Searches the model for new project items that have not yet been added to the global list, and
     * removes items whose libraries no longer exist in the project.
     * 
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    private void refreshProjectItems() throws RepositoryException {
        Set<String> existingItemUrls = new HashSet<String>();

        for (ProjectItem item : projectItems) {
            existingItemUrls.add(item.getContent().getLibraryUrl().toExternalForm());
        }
        for (AbstractLibrary library : model.getAllLibraries()) {
            if (library instanceof BuiltInLibrary) {
                continue;
            }
            if (!existingItemUrls.contains(library.getLibraryUrl().toExternalForm())) {
                ProjectItem newItem;

                if (isRepositoryUrl(library.getLibraryUrl())) { // New managed project item
                    newItem = newManagedProjectItem(null, library);

                } else { // New unmanaged project item
                    newItem = ProjectItemImpl.newUnmanagedItem(URLUtils.toFile(library.getLibraryUrl()), library,
                            this);
                }
                addProjectItem(newItem);
            }
        }
    }

    /**
     * Saves all of the affected projects when the given project item is changed in some way that
     * affects its persistent state.
     * 
     * @param item
     *            the project item that was modified
     */
    private void saveAffectedProjects(ProjectItem item) {
        saveAffectedProjects(Arrays.asList(new ProjectItem[] { item }));
    }

    /**
     * Saves all of the affected projects when the given project item is changed in some way that
     * affects its persistent state.
     * 
     * @param item
     *            the project item that was modified
     */
    private void saveAffectedProjects(List<ProjectItem> itemList) {
        Set<Project> affectedProjects = new HashSet<Project>();

        for (ProjectItem item : itemList) {
            for (Project project : item.memberOfProjects()) {
                affectedProjects.add(project);
            }
        }
        for (Project project : affectedProjects) {
            try {
                saveProject(project, false, null);

            } catch (LibrarySaveException e) {
                log.error("Error saving updates to project: " + project.getProjectId(), e);
            }
        }
    }

    /**
     * Searches the global list of project items and removes the ones that are no longer associated
     * with a project. When a project item is removed, the associated library is also removed from
     * the underlying <code>TLModel</code>.
     */
    protected void purgeOrphanedProjectItems() {
        List<ProjectItem> orphanedItems = new ArrayList<ProjectItem>();

        for (ProjectItem item : projectItems) {
            if (item.memberOfProjects().isEmpty()) {
                orphanedItems.add(item);
            }
        }
        for (ProjectItem orphan : orphanedItems) {
            projectItems.remove(orphan);
            model.removeLibrary(orphan.getContent());
        }
    }

    /**
     * Returns the existing <code>ProjectItem</code> that is associated with the specified library,
     * or creates one automatically if no such project item exists yet.
     * 
     * @param library
     *            the library for which to return a project item
     * @return ProjectItem
     * @throws RepositoryException
     *             thrown if one or more managed repository items cannot be accessed
     */
    private ProjectItem findOrCreateProjectItem(AbstractLibrary library) throws RepositoryException {
        ProjectItem projectItem = null;

        for (ProjectItem item : projectItems) {
            if (library == item.getContent()) {
                projectItem = item;
                break;
            }
        }
        if (projectItem == null) {
            if (isRepositoryUrl(library.getLibraryUrl())) { // New managed project item
                projectItem = newManagedProjectItem(null, library);

            } else { // New unmanaged project item
                projectItem = ProjectItemImpl.newUnmanagedItem(URLUtils.toFile(library.getLibraryUrl()), library,
                        this);
            }
            addProjectItem(projectItem);
        }
        return projectItem;
    }

    /**
     * Adds a project item to the current list of all items if it is not already a member.
     * 
     * @param item  the project item to add
     */
    private void addProjectItem(ProjectItem item) {
        boolean newItem = true;

        for (ProjectItem currentItem : projectItems) {
            if (currentItem == item) {
                newItem = false;
                break;
            }
        }
        if (newItem) {
            projectItems.add(item);
        }
    }

    /**
     * Returns true if the given JAXB representation of the project item exists
     * in the model (false otherwise).
     * 
     * @param jaxbItem  the JAXB representation of the project item to check
     * @param project  the project from which the item was loaded
     * @return boolean
     */
    private boolean isProjectItemLoaded(ProjectItemType jaxbItem, Project project) {
        boolean result;

        try {
            URL libraryUrl;

            if (jaxbItem instanceof UnmanagedProjectItemType) {
                UnmanagedProjectItemType _jaxbItem = (UnmanagedProjectItemType) jaxbItem;
                File projectFolder = project.getProjectFile().getParentFile();

                libraryUrl = URLUtils.getResolvedURL(_jaxbItem.getFileLocation(), URLUtils.toURL(projectFolder));

            } else { // must be a ManagedProjectItemType
                ManagedProjectItemType _jaxbItem = (ManagedProjectItemType) jaxbItem;
                RepositoryItem repositoryItem = repositoryManager.getRepositoryItem(_jaxbItem.getBaseNamespace(),
                        _jaxbItem.getFilename(), _jaxbItem.getVersion());

                libraryUrl = repositoryManager.getContentLocation(repositoryItem);
            }
            result = (model.getLibrary(libraryUrl) != null);

        } catch (MalformedURLException | RepositoryException e) {
            result = false; // Ignore error and return false
        }
        return result;
    }

    /**
     * Constructs a new <code>ProjectItem</code> instance using information provided by the given
     * library.
     * 
     * @param repositoryItem
     *            the repository item from which to create the new managed project item
     * @param library
     *            the library from which to create the new managed project item
     * @return ProjectItem
     * @throws RepositoryException
     *             thrown if the library's meta-data cannot be located in any accessible repository
     */
    private ProjectItem newManagedProjectItem(RepositoryItem repositoryItem, AbstractLibrary library)
            throws RepositoryException {
        try {
            if (repositoryItem == null) {
                String baseNamespace = library.getNamespace();
                String versionIdentifier = null;

                if (library instanceof TLLibrary) {
                    TLLibrary userLibrary = (TLLibrary) library;
                    VersionScheme vScheme = VersionSchemeFactory.getInstance()
                            .getVersionScheme(userLibrary.getVersionScheme());

                    baseNamespace = vScheme.getBaseNamespace(library.getNamespace());
                    versionIdentifier = userLibrary.getVersion();
                }
                repositoryItem = repositoryManager.getRepositoryItem(baseNamespace, getLibraryUrlFilename(library),
                        versionIdentifier);
            }
            return ProjectItemImpl.newManagedItem(repositoryItem, library, this);

        } catch (VersionSchemeException e) {
            throw new RepositoryException(e.getMessage(), e);
        }
    }

    /**
     * For <code>TLLibrary</code> instances, this method will return the name of the default
     * filename for the library (per the library's version scheme). For any non-OTM libraries, this
     * method simply returns the name of the library file without any path-specific information.
     * 
     * @param library
     *            the library whose filename is to be returned
     * @return String
     */
    private String getPublicationFilename(AbstractLibrary library) {
        String filename = null;

        if (library instanceof TLLibrary) {
            try {
                TLLibrary otmLibrary = (TLLibrary) library;
                VersionScheme vScheme = VersionSchemeFactory.getInstance()
                        .getVersionScheme(otmLibrary.getVersionScheme());

                filename = vScheme.getDefaultFileHint(otmLibrary.getNamespace(), otmLibrary.getName());

            } catch (VersionSchemeException e) {
                // No action - Return the filename from the library's URL
            }
        }
        if ((filename == null) && (library != null)) {
            filename = getLibraryUrlFilename(library);
        }
        return filename;
    }

    /**
     * Returns the name of the file content for the given library without any path-specific
     * information.
     * 
     * @param library
     *            the library whose filename is to be returned
     * @return String
     */
    private String getLibraryUrlFilename(AbstractLibrary library) {
        String filepath = library.getLibraryUrl().getFile();
        int lastPathBreak = filepath.lastIndexOf('/');
        String filename;

        if (lastPathBreak < 0) {
            filename = filepath;

        } else if (lastPathBreak < filepath.length()) {
            filename = filepath.substring(lastPathBreak + 1);

        } else {
            filename = null; // No filename if the path ends with a '/'
        }
        return filename;
    }

    /**
     * Listener that responds to the addition of <code>TLInclude</code> elements into a library,
     * adding the required project-item dependencies where they are needed.
     * 
     * @author S. Livezey
     */
    private class IncludeDependencyListener
            implements ModelEventListener<OwnershipEvent<TLLibrary, TLInclude>, TLLibrary> {

        /**
         * @see org.opentravel.schemacompiler.event.ModelEventListener#processModelEvent(org.opentravel.schemacompiler.event.ModelEvent)
         */
        @Override
        public void processModelEvent(OwnershipEvent<TLLibrary, TLInclude> event) {
            if (event.getType() == ModelEventType.INCLUDE_ADDED) {
                ProjectItem item = getProjectItem(event.getSource());

                if (item != null) {
                    for (Project project : getAssignedProjects(item)) {
                        try {
                            updateProjectDependencies(project, null);

                        } catch (RepositoryException e) {
                            // Ignore and keep going
                        }
                    }
                }
            }
        }

        /**
         * @see org.opentravel.schemacompiler.event.ModelEventListener#getEventClass()
         */
        @Override
        public Class<?> getEventClass() {
            return OwnershipEvent.class;
        }

        /**
         * @see org.opentravel.schemacompiler.event.ModelEventListener#getSourceObjectClass()
         */
        @Override
        public Class<TLLibrary> getSourceObjectClass() {
            return TLLibrary.class;
        }

    }

    /**
     * Listener that responds to the addition of <code>TLNamespaceImport</code> elements into a
     * library, adding the required project-item dependencies where they are needed.
     * 
     * @author S. Livezey
     */
    private class ImportDependencyListener
            implements ModelEventListener<OwnershipEvent<TLLibrary, TLNamespaceImport>, TLLibrary> {

        /**
         * @see org.opentravel.schemacompiler.event.ModelEventListener#processModelEvent(org.opentravel.schemacompiler.event.ModelEvent)
         */
        @Override
        public void processModelEvent(OwnershipEvent<TLLibrary, TLNamespaceImport> event) {
            if (event.getType() == ModelEventType.IMPORT_ADDED) {
                ProjectItem item = getProjectItem(event.getSource());

                if (item != null) {
                    for (Project project : getAssignedProjects(item)) {
                        try {
                            updateProjectDependencies(project, null);

                        } catch (RepositoryException e) {
                            // Ignore and keep going
                        }
                    }
                }
            }
        }

        /**
         * @see org.opentravel.schemacompiler.event.ModelEventListener#getEventClass()
         */
        @Override
        public Class<?> getEventClass() {
            return OwnershipEvent.class;
        }

        /**
         * @see org.opentravel.schemacompiler.event.ModelEventListener#getSourceObjectClass()
         */
        @Override
        public Class<TLLibrary> getSourceObjectClass() {
            return TLLibrary.class;
        }

    }

}