org.opentravel.schemacompiler.repository.impl.RemoteRepositoryClient.java Source code

Java tutorial

Introduction

Here is the source code for org.opentravel.schemacompiler.repository.impl.RemoteRepositoryClient.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.impl;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.HttpClientBuilder;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.LibraryInfoListType;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.LibraryInfoType;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.LibraryStatus;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.ListItemsRQType;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.NamespaceListType;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.ObjectFactory;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.RefreshPolicy;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.RepositoryInfoType;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.RepositoryItemIdentityType;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.RepositoryPermission;
import org.opentravel.ns.ota2.repositoryinfo_v01_00.RepositoryPermissionType;
import org.opentravel.schemacompiler.model.TLLibraryStatus;
import org.opentravel.schemacompiler.repository.RemoteRepository;
import org.opentravel.schemacompiler.repository.RepositoryException;
import org.opentravel.schemacompiler.repository.RepositoryFileManager;
import org.opentravel.schemacompiler.repository.RepositoryItem;
import org.opentravel.schemacompiler.repository.RepositoryItemState;
import org.opentravel.schemacompiler.repository.RepositoryManager;
import org.opentravel.schemacompiler.repository.RepositoryNamespaceUtils;
import org.opentravel.schemacompiler.repository.RepositoryOutOfSyncException;
import org.opentravel.schemacompiler.repository.RepositoryUnavailableException;
import org.opentravel.schemacompiler.security.PasswordHelper;
import org.opentravel.schemacompiler.version.VersionScheme;
import org.opentravel.schemacompiler.version.VersionSchemeException;
import org.opentravel.schemacompiler.version.VersionSchemeFactory;
import org.opentravel.schemacompiler.xml.XMLGregorianCalendarConverter;

/**
 * Represents the remote repositories that are accessible from the local environment.
 * 
 * @author S. Livezey
 */
public class RemoteRepositoryClient implements RemoteRepository {

    private static final String SERVICE_CONTEXT = "/service";
    private static final String REPOSITORY_METADATA_ENDPOINT = SERVICE_CONTEXT + "/repository-metadata";
    private static final String ALL_NAMSPACES_ENDPOINT = SERVICE_CONTEXT + "/all-namespaces";
    private static final String BASE_NAMSPACES_ENDPOINT = SERVICE_CONTEXT + "/base-namespaces";
    private static final String NAMESPACE_CHILDREN_ENDPOINT = SERVICE_CONTEXT + "/namespace-children";
    private static final String LIST_ITEMS_ENDPOINT = SERVICE_CONTEXT + "/list-items";
    private static final String VERSION_HISTORY_ENDPOINT = SERVICE_CONTEXT + "/version-history";
    private static final String SEARCH_ENDPOINT = SERVICE_CONTEXT + "/search";
    private static final String CREATE_ROOT_NAMESPACE_ENDPOINT = SERVICE_CONTEXT + "/create-root-namespace";
    private static final String DELETE_ROOT_NAMESPACE_ENDPOINT = SERVICE_CONTEXT + "/delete-root-namespace";
    private static final String CREATE_NAMESPACE_ENDPOINT = SERVICE_CONTEXT + "/create-namespace";
    private static final String DELETE_NAMESPACE_ENDPOINT = SERVICE_CONTEXT + "/delete-namespace";
    private static final String PUBLISH_ENDPOINT = SERVICE_CONTEXT + "/publish";
    private static final String LOCK_ENDPOINT = SERVICE_CONTEXT + "/lock";
    private static final String UNLOCK_ENDPOINT = SERVICE_CONTEXT + "/unlock";
    private static final String COMMIT_ENDPOINT = SERVICE_CONTEXT + "/commit";
    private static final String PROMOTE_ENDPOINT = SERVICE_CONTEXT + "/promote";
    private static final String DEMOTE_ENDPOINT = SERVICE_CONTEXT + "/demote";
    private static final String RECALCULATE_CRC_ENDPOINT = SERVICE_CONTEXT + "/recalculate-crc";
    private static final String DELETE_ENDPOINT = SERVICE_CONTEXT + "/delete";
    private static final String REPOSITORY_ITEM_METADATA_ENDPOINT = SERVICE_CONTEXT + "/metadata";
    private static final String REPOSITORY_ITEM_CONTENT_ENDPOINT = SERVICE_CONTEXT + "/content";
    private static final String USER_AUTHORIZATION_ENDPOINT = SERVICE_CONTEXT + "/user-authorization";

    private static final int HTTP_RESPONSE_STATUS_OK = 200;

    private static final DateFormat dateOnlyFormat = new SimpleDateFormat("dd-MM-yyyy");

    private static Log log = LogFactory.getLog(RemoteRepositoryClient.class);
    protected static ObjectFactory objectFactory = new ObjectFactory();

    private RepositoryManager manager;
    private String id;
    private String displayName;
    private String endpointUrl;
    private List<String> rootNamespaces = new ArrayList<String>();
    private RefreshPolicy refreshPolicy;
    private String userId;
    private String encryptedPassword;

    /**
     * Initializes this instance with a handle to the <code>RepositoryManager</code> that controls
     * access to this <code>RemoteRepository</code>.
     * 
     * @param manager
     *            the owning repository manager instance
     */
    public RemoteRepositoryClient(RepositoryManager manager) {
        this.manager = manager;
    }

    private static HttpClient createHttpClient() {
        return HttpClientBuilder.create().useSystemProperties()
                .setDefaultCredentialsProvider(new NTLMSystemCredentialsProvider()).build();
    }

    private HttpResponse executeWithAuthentication(HttpUriRequest request)
            throws ClientProtocolException, IOException {
        return createHttpClient().execute(request, createHttpContext());
    }

    private static HttpResponse execute(HttpUriRequest request) throws ClientProtocolException, IOException {
        return createHttpClient().execute(request);
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#getManager()
     */
    @Override
    public RepositoryManager getManager() {
        return manager;
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#getId()
     */
    @Override
    public String getId() {
        return id;
    }

    /**
     * Assigns the ID of the remote repository.
     * 
     * @param id
     *            the repository ID to assign
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#getDisplayName()
     */
    @Override
    public String getDisplayName() {
        return displayName;
    }

    /**
     * Assigns the display name of the remote repository.
     * 
     * @param displayName
     *            the display name value to assign
     */
    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }

    /**
     * @see org.opentravel.schemacompiler.repository.RemoteRepository#getRefreshPolicy()
     */
    @Override
    public RefreshPolicy getRefreshPolicy() {
        return refreshPolicy;
    }

    /**
     * Assigns the value of the 'refreshPolicy' field.
     * 
     * @param refreshPolicy
     *            the field value to assign
     */
    public void setRefreshPolicy(RefreshPolicy refreshPolicy) {
        this.refreshPolicy = refreshPolicy;
    }

    /**
     * @see org.opentravel.schemacompiler.repository.RemoteRepository#getEndpointUrl()
     */
    public String getEndpointUrl() {
        return endpointUrl;
    }

    /**
     * Assigns the web service endpoint URL of the remote repository.
     * 
     * @param endpointUrl
     *            the endpoint URL to assign
     */
    public void setEndpointUrl(String endpointUrl) {
        this.endpointUrl = endpointUrl;
    }

    /**
     * Returns the user ID credential for the remote repository's web service.
     * 
     * @return String
     */
    public String getUserId() {
        return userId;
    }

    /**
     * Assigns the user ID credential for the remote repository's web service.
     * 
     * @param userId
     *            the user ID value to assign
     */
    public void setUserId(String userId) {
        this.userId = userId;
    }

    /**
     * Returns the encryptedPassword credential for the remote repository's web service.
     * 
     * @return String
     */
    public String getEncryptedPassword() {
        return encryptedPassword;
    }

    /**
     * Assigns the encryptedPassword credential for the remote repository's web service.
     * 
     * @param encryptedPassword
     *            the encryptedPassword value to assign
     */
    public void setEncryptedPassword(String password) {
        this.encryptedPassword = password;
    }

    /**
     * @see org.opentravel.schemacompiler.repository.RemoteRepository#refreshRepositoryMetadata()
     */
    @Override
    public void refreshRepositoryMetadata() throws RepositoryException {
        RepositoryInfoType updatedMetadata = getRepositoryMetadata(endpointUrl);

        if (updatedMetadata != null) {
            this.id = updatedMetadata.getID();
            this.displayName = updatedMetadata.getDisplayName();
            setRootNamespaces(updatedMetadata.getRootNamespace());
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#listRootNamespaces()
     */
    @Override
    public List<String> listRootNamespaces() throws RepositoryException {
        return rootNamespaces;
    }

    /**
     * Assigns the list of root namespaces that are managed by the remote repository.
     * 
     * @param rootNamespaces
     */
    public synchronized void setRootNamespaces(List<String> rootNamespaces) {
        this.rootNamespaces.clear();

        if (rootNamespaces != null) {
            this.rootNamespaces.addAll(rootNamespaces);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#listNamespaceChildren(java.lang.String)
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<String> listNamespaceChildren(String baseNamespace) throws RepositoryException {
        try {
            String baseNS = RepositoryNamespaceUtils.normalizeUri(baseNamespace);
            HttpGet request = newGetRequest(NAMESPACE_CHILDREN_ENDPOINT, new HttpGetParam("baseNamespace", baseNS));
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<NamespaceListType> jaxbElement = (JAXBElement<NamespaceListType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());
            List<String> nsList = new ArrayList<String>();

            nsList.addAll(jaxbElement.getValue().getNamespace());
            return nsList;

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#listBaseNamespaces()
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<String> listBaseNamespaces() throws RepositoryException {
        try {
            HttpGet request = newGetRequest(BASE_NAMSPACES_ENDPOINT);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<NamespaceListType> jaxbElement = (JAXBElement<NamespaceListType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());
            List<String> nsList = new ArrayList<String>();

            nsList.addAll(jaxbElement.getValue().getNamespace());
            return nsList;

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the service response is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#listAllNamespaces()
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<String> listAllNamespaces() throws RepositoryException {
        try {
            HttpGet request = newGetRequest(ALL_NAMSPACES_ENDPOINT);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<NamespaceListType> jaxbElement = (JAXBElement<NamespaceListType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());
            List<String> nsList = new ArrayList<String>();

            nsList.addAll(jaxbElement.getValue().getNamespace());
            return nsList;

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the service response is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#listItems(java.lang.String, boolean,
     *      boolean)
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<RepositoryItem> listItems(String baseNamespace, boolean latestVersionsOnly,
            boolean includeDraftVersions) throws RepositoryException {
        try {
            String baseNS = RepositoryNamespaceUtils.normalizeUri(baseNamespace);
            HttpPost request = newPostRequest(LIST_ITEMS_ENDPOINT);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            ListItemsRQType listItemsRQ = new ListItemsRQType();
            StringWriter xmlWriter = new StringWriter();

            listItemsRQ.setNamespace(baseNS);
            listItemsRQ.setLatestVersionOnly(latestVersionsOnly);
            listItemsRQ.setIncludeDraft(includeDraftVersions);
            marshaller.marshal(objectFactory.createListItemsRQ(listItemsRQ), xmlWriter);
            request.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<LibraryInfoListType> jaxbElement = (JAXBElement<LibraryInfoListType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());
            List<RepositoryItem> itemList = new ArrayList<RepositoryItem>();

            for (LibraryInfoType itemMetadata : jaxbElement.getValue().getLibraryInfo()) {
                RepositoryItemImpl item = RepositoryUtils.createRepositoryItem(manager, itemMetadata);

                RepositoryUtils.checkItemState(item, manager);
                itemList.add(item);
            }
            return itemList;

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the service response is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#search(java.lang.String, boolean,
     *      boolean)
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<RepositoryItem> search(String freeTextQuery, boolean latestVersionsOnly,
            boolean includeDraftVersions) throws RepositoryException {
        try {
            HttpGet request = newGetRequest(SEARCH_ENDPOINT, new HttpGetParam("query", freeTextQuery),
                    new HttpGetParam("latestVersion", latestVersionsOnly + ""),
                    new HttpGetParam("includeDraft", includeDraftVersions + ""));
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<LibraryInfoListType> jaxbElement = (JAXBElement<LibraryInfoListType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());
            List<RepositoryItem> itemList = new ArrayList<RepositoryItem>();

            for (LibraryInfoType itemMetadata : jaxbElement.getValue().getLibraryInfo()) {
                RepositoryItemImpl item = RepositoryUtils.createRepositoryItem(manager, itemMetadata);

                RepositoryUtils.checkItemState(item, manager);
                itemList.add(item);
            }
            return itemList;

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#getVersionHistory(org.opentravel.schemacompiler.repository.RepositoryItem)
     */
    @SuppressWarnings("unchecked")
    @Override
    public List<RepositoryItem> getVersionHistory(RepositoryItem item) throws RepositoryException {
        try {
            validateRepositoryItem(item);

            // Build the HTTP request for the remote service
            RepositoryItemIdentityType itemIdentity = createItemIdentity(item);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            HttpPost request = newPostRequest(VERSION_HISTORY_ENDPOINT);
            StringWriter xmlWriter = new StringWriter();

            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            request.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

            // Send the web service request and unmarshall the updated meta-data from the response
            log.info("Sending version history request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Version history response received - Status OK");

            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<LibraryInfoListType> jaxbElement = (JAXBElement<LibraryInfoListType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());
            List<RepositoryItem> itemList = new ArrayList<RepositoryItem>();

            for (LibraryInfoType itemMetadata : jaxbElement.getValue().getLibraryInfo()) {
                RepositoryItemImpl itemVersion = RepositoryUtils.createRepositoryItem(manager, itemMetadata);

                RepositoryUtils.checkItemState(itemVersion, manager);
                itemList.add(itemVersion);
            }
            return itemList;

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#getRepositoryItem(java.lang.String,
     *      java.lang.String, java.lang.String)
     */
    @Override
    public RepositoryItem getRepositoryItem(String baseNamespace, String filename, String versionIdentifier)
            throws RepositoryException {
        String baseNS = RepositoryNamespaceUtils.normalizeUri(baseNamespace);

        downloadContent(baseNS, filename, versionIdentifier, false);

        LibraryInfoType libraryInfo = manager.getFileManager().loadLibraryMetadata(baseNS, filename,
                versionIdentifier);
        return newRepositoryItem(libraryInfo);
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#getRepositoryItem(java.lang.String)
     */
    @Override
    public RepositoryItem getRepositoryItem(String itemUri) throws RepositoryException, URISyntaxException {
        return getRepositoryItem(itemUri, null);
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#getRepositoryItem(java.lang.String,
     *      java.lang.String)
     */
    @Override
    public RepositoryItem getRepositoryItem(String itemUri, String itemNamespace)
            throws RepositoryException, URISyntaxException {
        URI uri = RepositoryUtils.toRepositoryItemUri(itemUri);
        RepositoryItem item;

        if ((uri.getAuthority() == null) || !uri.getAuthority().equals(id)) {
            throw new RepositoryException(
                    "Unable to retrieve the requested item becuase it does not belong to this repository.");
        }
        VersionSchemeFactory vsFactory = VersionSchemeFactory.getInstance();
        String[] uriParts = RepositoryUtils.parseRepositoryItemUri(uri);
        String versionScheme = (uriParts[3] == null) ? vsFactory.getDefaultVersionScheme() : uriParts[3];

        if (itemNamespace == null) {
            itemNamespace = uriParts[1];
        }
        if (itemNamespace == null) {
            throw new RepositoryException(
                    "Unable to identify the repository item because its namespace was not specified.");
        }
        try {
            VersionScheme vScheme = vsFactory.getVersionScheme(versionScheme);
            String baseNS = vScheme.getBaseNamespace(itemNamespace);
            String versionIdentifier = vScheme.getVersionIdentifier(itemNamespace);

            downloadContent(baseNS, uriParts[2], versionIdentifier, false);

            LibraryInfoType libraryMetadata = manager.getFileManager().loadLibraryMetadata(baseNS, uriParts[2],
                    versionIdentifier);

            if ((libraryMetadata == null) || !libraryMetadata.getOwningRepository().equals(uriParts[0])) {
                throw new RepositoryException("Resource not found in repository: " + uriParts[0]);
            }
            item = newRepositoryItem(libraryMetadata);

        } catch (VersionSchemeException e) {
            throw new RepositoryException("Unknown version scheme specified by URI: " + versionScheme);
        }
        return item;
    }

    /**
     * @see org.opentravel.schemacompiler.repository.RemoteRepository#getUserAuthorization(java.lang.String)
     */
    @SuppressWarnings("unchecked")
    @Override
    public RepositoryPermission getUserAuthorization(String baseNamespace) throws RepositoryException {
        try {
            String baseNS = RepositoryNamespaceUtils.normalizeUri(baseNamespace);
            HttpGet request = newGetRequest(USER_AUTHORIZATION_ENDPOINT, new HttpGetParam("baseNamespace", baseNS));

            // Send the web service request and check the response
            log.info("Sending user-authorization request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<RepositoryPermissionType> jaxbElement = (JAXBElement<RepositoryPermissionType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());

            log.info("User-authorization response received - Status OK");
            return jaxbElement.getValue().getRepositoryPermission();

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the service response is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#createRootNamespace(java.lang.String)
     */
    @Override
    public void createRootNamespace(String rootNamespace) throws RepositoryException {
        try {
            String rootNS = RepositoryNamespaceUtils.normalizeUri(rootNamespace);
            HttpGet request = newGetRequest(CREATE_ROOT_NAMESPACE_ENDPOINT,
                    new HttpGetParam("rootNamespace", rootNS));

            // Send the web service request and check the response
            log.info("Sending create-root-namespace request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            refreshRepositoryMetadata();
            log.info("Create-root-namespace response received - Status OK");

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#deleteRootNamespace(java.lang.String)
     */
    @Override
    public void deleteRootNamespace(String rootNamespace) throws RepositoryException {
        try {
            String rootNS = RepositoryNamespaceUtils.normalizeUri(rootNamespace);
            HttpGet request = newGetRequest(DELETE_ROOT_NAMESPACE_ENDPOINT,
                    new HttpGetParam("rootNamespace", rootNS));

            // Send the web service request and check the response
            log.info("Sending delete-root-namespace request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            refreshRepositoryMetadata();
            log.info("Delete-root-namespace response received - Status OK");

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#createNamespace(java.lang.String)
     */
    @Override
    public void createNamespace(String baseNamespace) throws RepositoryException {
        try {
            String baseNS = RepositoryNamespaceUtils.normalizeUri(baseNamespace);
            HttpGet request = newGetRequest(CREATE_NAMESPACE_ENDPOINT, new HttpGetParam("baseNamespace", baseNS));

            // Send the web service request and check the response
            log.info("Sending create-namespace request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Create-namespace response received - Status OK");

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#deleteNamespace(java.lang.String)
     */
    @Override
    public void deleteNamespace(String baseNamespace) throws RepositoryException {
        try {
            String baseNS = RepositoryNamespaceUtils.normalizeUri(baseNamespace);
            HttpGet request = newGetRequest(DELETE_NAMESPACE_ENDPOINT, new HttpGetParam("baseNamespace", baseNS));

            // Send the web service request and check the response
            log.info("Sending delete-namespace request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Delete-namespace response received - Status OK");

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#publish(java.io.InputStream,
     *      java.lang.String, java.lang.String, java.lang.String, java.lang.String,
     *      java.lang.String, org.opentravel.schemacompiler.model.TLLibraryStatus)
     */
    @Override
    public RepositoryItem publish(InputStream unmanagedContent, String filename, String libraryName,
            String namespace, String versionIdentifier, String versionScheme, TLLibraryStatus initialStatus)
            throws RepositoryException {
        try {
            // Build a repository item to represent the content that we are attempting to publish
            String targetNS = RepositoryNamespaceUtils.normalizeUri(namespace);
            RepositoryItemImpl item = new RepositoryItemImpl();
            String baseNamespace = targetNS;

            if (versionScheme != null) {
                VersionScheme vScheme = VersionSchemeFactory.getInstance().getVersionScheme(versionScheme);
                baseNamespace = vScheme.getBaseNamespace(targetNS);
            }

            item.setRepository(this);
            item.setNamespace(targetNS);
            item.setBaseNamespace(baseNamespace);
            item.setFilename(filename);
            item.setVersion(versionIdentifier);
            item.setStatus(initialStatus);
            item.setState(RepositoryItemState.MANAGED_UNLOCKED);

            // Invoke the remote web service call to perform the publication
            HttpPost postRequest = newPostRequest(PUBLISH_ENDPOINT);
            MultipartEntityBuilder mpEntity = MultipartEntityBuilder.create();

            if (versionScheme != null) {
                mpEntity.addTextBody("versionScheme", versionScheme);
            }
            mpEntity.addBinaryBody("fileContent", toByteArray(unmanagedContent), ContentType.DEFAULT_BINARY,
                    filename);
            mpEntity.addTextBody("namespace", targetNS);
            mpEntity.addTextBody("libraryName", libraryName);
            mpEntity.addTextBody("version", versionIdentifier);
            mpEntity.addTextBody("status", initialStatus.toRepositoryStatus().toString());
            postRequest.setEntity(mpEntity.build());

            log.info("Sending publish request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(postRequest);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Publish response received - Status OK");
            return item;

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

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);

        } finally {
            try {
                if (unmanagedContent != null)
                    unmanagedContent.close();
            } catch (Throwable t) {
            }
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#commit(org.opentravel.schemacompiler.repository.RepositoryItem)
     */
    @Override
    public void commit(RepositoryItem item) throws RepositoryException {
        InputStream wipContent = null;
        try {
            validateRepositoryItem(item);

            // Obtain a file stream for the item's WIP content
            File wipFile = manager.getFileManager().getLibraryWIPContentLocation(item.getBaseNamespace(),
                    item.getFilename());

            if (!wipFile.exists()) {
                throw new RepositoryException("The work-in-process file does not exist: " + item.getFilename());
            }
            wipContent = new FileInputStream(wipFile);

            // Build the HTTP request for the remote service
            RepositoryItemIdentityType itemIdentity = createItemIdentity(item);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            HttpPost request = newPostRequest(COMMIT_ENDPOINT);
            MultipartEntityBuilder mpEntity = MultipartEntityBuilder.create();
            StringWriter xmlWriter = new StringWriter();

            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            mpEntity.addTextBody("item", xmlWriter.toString(), ContentType.TEXT_XML);
            mpEntity.addBinaryBody("fileContent", toByteArray(wipContent), ContentType.DEFAULT_BINARY,
                    item.getFilename());

            // mpEntity.addPart( "fileContent", new InputStreamBody(wipContent, item.getFilename())
            // );

            request.setEntity(mpEntity.build());

            // Send the web service request and check the response
            log.info("Sending commit request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Commit response received - Status OK");

            // Update the local cache with the content we just sent to the remote web service
            downloadContent(item, true);

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);

        } finally {
            try {
                if (wipContent != null)
                    wipContent.close();
            } catch (Throwable t) {
            }
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#lock(org.opentravel.schemacompiler.repository.RepositoryItem)
     */
    @SuppressWarnings("unchecked")
    @Override
    public void lock(RepositoryItem item) throws RepositoryException {
        boolean success = false;
        try {
            validateRepositoryItem(item);
            manager.getFileManager().startChangeSet();

            // Before performing the lock, check to see if we need to refresh the
            // library from the remote repository
            if (isLocalContentStale(item)) {
                throw new RepositoryOutOfSyncException("Unable to lock '" + item.getFilename()
                        + "' because the local copy is out of date (refresh required).");
            }

            // Build the HTTP request for the remote service
            RepositoryItemIdentityType itemIdentity = createItemIdentity(item);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            HttpPost request = newPostRequest(LOCK_ENDPOINT);
            StringWriter xmlWriter = new StringWriter();

            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            request.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

            // Send the web service request and unmarshall the updated meta-data from the response
            log.info("Sending lock request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Lock response received - Status OK");

            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<LibraryInfoType> jaxbElement = (JAXBElement<LibraryInfoType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());

            // Update the local cache with the content we just received from the remote web service
            manager.getFileManager().saveLibraryMetadata(jaxbElement.getValue());

            // Update the local repository item with the latest state information
            ((RepositoryItemImpl) item).setState(RepositoryItemState.MANAGED_WIP);
            ((RepositoryItemImpl) item).setLockedByUser(userId);
            success = true;

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);

        } finally {
            // Commit or roll back the changes based on the result of the operation
            if (success) {
                manager.getFileManager().commitChangeSet();
            } else {
                try {
                    manager.getFileManager().rollbackChangeSet();
                } catch (Throwable t) {
                    log.error("Error rolling back the current change set.", t);
                }
            }
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#unlock(org.opentravel.schemacompiler.repository.RepositoryItem,
     *      boolean)
     */
    @SuppressWarnings("unchecked")
    @Override
    public void unlock(RepositoryItem item, boolean commitWIP) throws RepositoryException {
        InputStream wipContent = null;
        boolean success = false;
        try {
            validateRepositoryItem(item);
            manager.getFileManager().startChangeSet();

            // Build the HTTP request for the remote service
            RepositoryItemIdentityType itemIdentity = createItemIdentity(item);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            HttpPost request = newPostRequest(UNLOCK_ENDPOINT);

            MultipartEntityBuilder mpEntity = MultipartEntityBuilder.create();
            StringWriter xmlWriter = new StringWriter();

            if (commitWIP) {
                File wipFile = manager.getFileManager().getLibraryWIPContentLocation(item.getBaseNamespace(),
                        item.getFilename());

                if (!wipFile.exists()) {
                    throw new RepositoryException("The work-in-process file does not exist: " + item.getFilename());
                }
                wipContent = new FileInputStream(wipFile);
                mpEntity.addBinaryBody("fileContent", toByteArray(wipContent), ContentType.DEFAULT_BINARY,
                        item.getFilename());
                // mpEntity.addPart( "fileContent", new InputStreamBody(wipContent,
                // item.getFilename()) );
            }
            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            mpEntity.addTextBody("item", xmlWriter.toString(), ContentType.TEXT_XML);
            request.setEntity(mpEntity.build());

            // Send the web service request and unmarshall the updated meta-data from the response
            log.info("Sending lock request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Lock response received - Status OK");

            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<LibraryInfoType> jaxbElement = (JAXBElement<LibraryInfoType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());

            // Update the local cache with the content we just received from the remote web service
            manager.getFileManager().saveLibraryMetadata(jaxbElement.getValue());

            // Update the local repository item with the latest state information
            ((RepositoryItemImpl) item).setState(RepositoryItemState.MANAGED_UNLOCKED);
            ((RepositoryItemImpl) item).setLockedByUser(null);

            // Force a re-download of the updated content to make sure the local copy is
            // synchronized
            // with the remote repository.
            downloadContent(item, true);

            success = true;

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);

        } finally {
            // Close the WIP content input stream
            try {
                if (wipContent != null)
                    wipContent.close();
            } catch (Throwable t) {
            }

            // Commit or roll back the changes based on the result of the operation
            if (success) {
                manager.getFileManager().commitChangeSet();
            } else {
                try {
                    manager.getFileManager().rollbackChangeSet();
                } catch (Throwable t) {
                    log.error("Error rolling back the current change set.", t);
                }
            }
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#promote(org.opentravel.schemacompiler.repository.RepositoryItem)
     */
    @Override
    public void promote(RepositoryItem item) throws RepositoryException {
        try {
            validateRepositoryItem(item);

            // Build the HTTP request for the remote service
            RepositoryItemIdentityType itemIdentity = createItemIdentity(item);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            HttpPost request = newPostRequest(PROMOTE_ENDPOINT);
            StringWriter xmlWriter = new StringWriter();

            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            request.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

            // Send the web service request and check the response
            log.info("Sending promote request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Promote response received - Status OK");

            // Update the local cache with the content that was just modified in the remote
            // repository
            downloadContent(item, true);

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#demote(org.opentravel.schemacompiler.repository.RepositoryItem)
     */
    @Override
    public void demote(RepositoryItem item) throws RepositoryException {
        try {
            validateRepositoryItem(item);

            // Build the HTTP request for the remote service
            RepositoryItemIdentityType itemIdentity = createItemIdentity(item);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            HttpPost request = newPostRequest(DEMOTE_ENDPOINT);
            StringWriter xmlWriter = new StringWriter();

            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            request.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

            // Send the web service request and check the response
            log.info("Sending promote request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Promote response received - Status OK");

            // Update the local cache by deleting the local copy of the item
            downloadContent(item, true);

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#recalculateCrc(org.opentravel.schemacompiler.repository.RepositoryItem)
     */
    @Override
    public void recalculateCrc(RepositoryItem item) throws RepositoryException {
        try {
            validateRepositoryItem(item);

            // Build the HTTP request for the remote service
            RepositoryItemIdentityType itemIdentity = createItemIdentity(item);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            HttpPost request = newPostRequest(RECALCULATE_CRC_ENDPOINT);
            StringWriter xmlWriter = new StringWriter();

            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            request.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

            // Send the web service request and check the response
            log.info("Sending recalculate-crc request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Recalculate-crc response received - Status OK");

            // Update the local cache by deleting the local copy of the item
            downloadContent(item, true);

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.Repository#delete(org.opentravel.schemacompiler.repository.RepositoryItem)
     */
    @Override
    public void delete(RepositoryItem item) throws RepositoryException {
        try {
            validateRepositoryItem(item);

            // Build the HTTP request for the remote service
            RepositoryItemIdentityType itemIdentity = createItemIdentity(item);
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            HttpPost request = newPostRequest(DELETE_ENDPOINT);
            StringWriter xmlWriter = new StringWriter();

            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            request.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

            // Send the web service request and check the response
            log.info("Sending delete request to HTTP endpoint: " + endpointUrl);
            HttpResponse response = executeWithAuthentication(request);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            log.info("Delete response received - Status OK");

            // Update the local cache with the content that was just modified in the remote
            // repository
            File itemMetadata = manager.getFileManager().getLibraryMetadataLocation(item.getBaseNamespace(),
                    item.getFilename(), item.getVersion());
            File itemContent = manager.getFileManager().getLibraryContentLocation(item.getBaseNamespace(),
                    item.getFilename(), item.getVersion());

            itemMetadata.delete();
            itemContent.delete();

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * @see org.opentravel.schemacompiler.repository.RemoteRepository#downloadContent(org.opentravel.schemacompiler.repository.RepositoryItem,
     *      boolean)
     */
    @Override
    public boolean downloadContent(RepositoryItem item, boolean forceUpdate) throws RepositoryException {
        String baseNS = RepositoryNamespaceUtils.normalizeUri(item.getBaseNamespace());

        return downloadContent(baseNS, item.getFilename(), item.getVersion(), forceUpdate);
    }

    /**
     * Downloads the specified content (and its associated meta-data) from the remote repository
     * into the local instance. If the refresh policy for this repository does not require an update
     * (or the remote repository is not accessible), the locally cached copy of the content will be
     * used.
     * 
     * <p>This method will return true if the local copy was replaced by newer content from the remote
     * repository.  False will be returned if the local copy was up-to-date, even if a refresh was
     * forced by the caller or the update policy.
     * 
     * @param baseNamespace
     *            the namespace of the repository item to download
     * @param filename
     *            the filename of the repository item to download
     * @param versionIdentifier
     *            the version identifier of the repository item to download
     * @param forceUpdate
     *            disregards the repository's update policy and forces the remote content to be
     *            downloaded
     * @return boolean
     * @throws RepositoryException
     *             thrown if the remote repository cannot be accessed
     */
    @SuppressWarnings("unchecked")
    public boolean downloadContent(String baseNamespace, String filename, String versionIdentifier,
            boolean forceUpdate) throws RepositoryException {
        String baseNS = RepositoryNamespaceUtils.normalizeUri(baseNamespace);
        LibraryInfoType contentMetadata = null;
        boolean refreshRequired, isStaleContent = false;
        Date localLastUpdated;

        try {
            contentMetadata = manager.getFileManager().loadLibraryMetadata(baseNS, filename, versionIdentifier);
            localLastUpdated = XMLGregorianCalendarConverter.toJavaDate(contentMetadata.getLastUpdated());
            refreshRequired = forceUpdate || (contentMetadata.getStatus() == LibraryStatus.DRAFT) || // always update draft items
                    (refreshPolicy == RefreshPolicy.ALWAYS);

            if (!refreshRequired && (refreshPolicy == RefreshPolicy.DAILY)) {
                refreshRequired = !dateOnlyFormat.format(localLastUpdated)
                        .equals(dateOnlyFormat.format(new Date()));
            }

        } catch (RepositoryException e) {
            localLastUpdated = new Date(0L); // make sure the local date is earlier than anything we will get from the repository
            refreshRequired = true;
        }

        // If the item was previously downloaded, make sure it originated from this remote
        // repository
        if ((contentMetadata != null) && !contentMetadata.getOwningRepository().equals(id)) {
            throw new RepositoryException("The requested content is managed by a different remote repository.");
        }

        // If a refresh is required, download the item's metadata and content from the remote web
        // service
        if (refreshRequired) {
            File repositoryContentFile = manager.getFileManager().getLibraryContentLocation(baseNS, filename,
                    versionIdentifier);
            boolean success = false;

            try {
                log.info("Downloading content from repository '" + id + "' - " + baseNS + "; " + filename + "; "
                        + versionIdentifier);
                manager.getFileManager().startChangeSet();

                // Marshal the JAXB content to a string and construct the HTTP requests
                HttpPost metadataRequest = newPostRequest(REPOSITORY_ITEM_METADATA_ENDPOINT);
                HttpPost contentRequest = newPostRequest(REPOSITORY_ITEM_CONTENT_ENDPOINT);
                RepositoryItemIdentityType itemIdentity = new RepositoryItemIdentityType();
                Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
                StringWriter xmlWriter = new StringWriter();

                itemIdentity.setBaseNamespace(baseNS);
                itemIdentity.setFilename(filename);
                itemIdentity.setVersion(versionIdentifier);

                marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
                metadataRequest.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));
                contentRequest.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

                // Send the requests for meta-data and content to the remote web service
                HttpResponse metadataResponse = executeWithAuthentication(metadataRequest);
                HttpResponse contentResponse = executeWithAuthentication(contentRequest);

                if (metadataResponse.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                    throw new RepositoryException(getResponseErrorMessage(metadataResponse));
                }
                if (contentResponse.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                    throw new RepositoryException(getResponseErrorMessage(contentResponse));
                }

                // Update the local cache with the content we just received from the remote web
                // service
                Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
                JAXBElement<LibraryInfoType> jaxbElement = (JAXBElement<LibraryInfoType>) unmarshaller
                        .unmarshal(metadataResponse.getEntity().getContent());
                LibraryInfoType libraryMetadata = jaxbElement.getValue();

                manager.getFileManager().createNamespaceIdFiles(jaxbElement.getValue().getBaseNamespace());
                manager.getFileManager().saveLibraryMetadata(libraryMetadata);
                manager.getFileManager().saveFile(repositoryContentFile, contentResponse.getEntity().getContent());
                success = true;

                // Compare the last-updated with our previous local value and return true if the
                // local content was modified.
                Date remoteLastUpdated = XMLGregorianCalendarConverter.toJavaDate(libraryMetadata.getLastUpdated());
                isStaleContent = remoteLastUpdated.after(localLastUpdated);

            } catch (UnknownHostException e) {
                // If the remote repository is inaccessible, it is only an error if we are
                // downloading the
                // files for the first time.
                File repositoryMetadataFile = manager.getFileManager().getLibraryMetadataLocation(baseNS, filename,
                        versionIdentifier);

                if (repositoryMetadataFile.exists() && repositoryContentFile.exists()) {
                    log.warn("Remote repository is unavailable - using cached copy of file '" + filename + "'.");

                } else {
                    throw new RepositoryUnavailableException("The remote repository is unavailable.", e);
                }

            } catch (JAXBException e) {
                throw new RepositoryException("The format of the library meta-data is unreadable.", e);

            } catch (IOException e) {
                throw new RepositoryException("The remote repository is unavailable.", e);

            } finally {
                // Commit or roll back the changes based on the result of the operation
                if (success) {
                    manager.getFileManager().commitChangeSet();
                } else {
                    try {
                        manager.getFileManager().rollbackChangeSet();
                    } catch (Throwable t) {
                        log.error("Error rolling back the current change set.", t);
                    }
                }
            }
        }
        return isStaleContent;
    }

    /**
     * Returns true if the copy of the item in the remote repository has been updated
     * since the local copy was last downloaded.
     * 
     * @param item  the repository item to check for staleness
     * @return boolean
     * @throws RepositoryException  thrown if the remote web service is not available
     */
    @SuppressWarnings("unchecked")
    private boolean isLocalContentStale(RepositoryItem item) throws RepositoryException {
        try {
            HttpPost metadataRequest = newPostRequest(REPOSITORY_ITEM_METADATA_ENDPOINT);
            RepositoryItemIdentityType itemIdentity = new RepositoryItemIdentityType();
            Marshaller marshaller = RepositoryFileManager.getSharedJaxbContext().createMarshaller();
            StringWriter xmlWriter = new StringWriter();

            itemIdentity.setBaseNamespace(item.getBaseNamespace());
            itemIdentity.setFilename(item.getFilename());
            itemIdentity.setVersion(item.getVersion());

            marshaller.marshal(objectFactory.createRepositoryItemIdentity(itemIdentity), xmlWriter);
            metadataRequest.setEntity(new StringEntity(xmlWriter.toString(), ContentType.TEXT_XML));

            // Send the request for meta-data to the remote web service
            HttpResponse metadataResponse = executeWithAuthentication(metadataRequest);

            if (metadataResponse.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(metadataResponse));
            }
            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<LibraryInfoType> jaxbElement = (JAXBElement<LibraryInfoType>) unmarshaller
                    .unmarshal(metadataResponse.getEntity().getContent());
            LibraryInfoType remoteMetadata = jaxbElement.getValue();

            // Get the local meta-data for the item and compare the last-updated timestamps
            LibraryInfoType localMetadata = manager.getFileManager().loadLibraryMetadata(item.getBaseNamespace(),
                    item.getFilename(), item.getVersion());
            Date localLastUpdated = XMLGregorianCalendarConverter.toJavaDate(localMetadata.getLastUpdated());
            Date remoteLastUpdated = XMLGregorianCalendarConverter.toJavaDate(remoteMetadata.getLastUpdated());

            return localLastUpdated.before(remoteLastUpdated);

        } catch (IOException e) {
            throw new RepositoryUnavailableException("The remote repository is unavailable.", e);

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the library meta-data is unreadable.", e);
        }
    }

    /**
     * Contacts the repository web service at the specified endpoint URL, and returns the repository
     * meta-data information.
     * 
     * @param endpointUrl
     *            the URL endpoint of the OTA2.0 repository web service
     * @return RepositoryInfoType
     * @throws RepositoryException
     *             thrown if the remote web service is not available
     */
    @SuppressWarnings("unchecked")
    public static RepositoryInfoType getRepositoryMetadata(String endpointUrl) throws RepositoryException {
        try {
            HttpGet getRequest = new HttpGet(endpointUrl + REPOSITORY_METADATA_ENDPOINT);
            HttpResponse response = execute(getRequest);

            if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
                throw new RepositoryException(getResponseErrorMessage(response));
            }
            Unmarshaller unmarshaller = RepositoryFileManager.getSharedJaxbContext().createUnmarshaller();
            JAXBElement<RepositoryInfoType> jaxbElement = (JAXBElement<RepositoryInfoType>) unmarshaller
                    .unmarshal(response.getEntity().getContent());

            return jaxbElement.getValue();

        } catch (JAXBException e) {
            throw new RepositoryException("The format of the repository meta-data is unreadable.", e);

        } catch (IOException e) {
            throw new RepositoryException("The remote repository is unavailable.", e);
        }
    }

    /**
     * Constructs a new repository item instance using values from the given meta-data record.
     * 
     * @param libraryInfo
     *            the library meta-data record for the repository item
     * @return RepositoryItem
     * @throws RepositoryException
     *             thrown if the namespace URI from the item's meta-data is not valid
     */
    private RepositoryItem newRepositoryItem(LibraryInfoType libraryInfo) throws RepositoryException {
        RepositoryItemImpl item = new RepositoryItemImpl();

        item.setRepository(this);
        item.setNamespace(libraryInfo.getNamespace());
        item.setBaseNamespace(libraryInfo.getBaseNamespace());
        item.setFilename(libraryInfo.getFilename());
        item.setVersion(libraryInfo.getVersion());
        item.setStatus(TLLibraryStatus.valueOf(libraryInfo.getStatus().toString().toUpperCase()));
        item.setState(RepositoryItemState.valueOf(libraryInfo.getState().toString()));
        item.setLockedByUser(libraryInfo.getLockedBy());
        RepositoryUtils.checkItemState(item, manager);
        return item;
    }

    /**
     * Verifies that the given repository item is owned by this repository.
     * 
     * @param item
     *            the repository item to validate
     * @throws IllegalArgumentException
     *             thrown if the item is owned by another repository
     */
    private void validateRepositoryItem(RepositoryItem item) {
        if (item.getRepository() != this) {
            throw new IllegalArgumentException("The repository item is not a member of this repository.");
        }
    }

    /**
     * Constructs a new <code>RepositoryItemIdentityType</code> that is used by the remote web
     * service to uniquely identify a repository item.
     * 
     * @param item
     *            the repository item to be identified
     * @return RepositoryItemIdentityType
     */
    protected RepositoryItemIdentityType createItemIdentity(RepositoryItem item) {
        String baseNS = RepositoryNamespaceUtils.normalizeUri(item.getBaseNamespace());
        RepositoryItemIdentityType itemIdentity = new RepositoryItemIdentityType();

        itemIdentity.setBaseNamespace(baseNS);
        itemIdentity.setFilename(item.getFilename());
        itemIdentity.setVersion(item.getVersion());
        return itemIdentity;
    }

    /**
     * Returns a new HTTP-GET request for the remote web service.
     * 
     * @param path
     *            the base URL path for the request
     * @urlParams the parameters to include on the request URL
     * @return HttpGet
     */
    protected HttpGet newGetRequest(String path, HttpGetParam... urlParams) {
        StringBuilder requestUrl = new StringBuilder(endpointUrl).append(path);
        boolean firstParam = true;

        for (HttpGetParam urlParam : urlParams) {
            requestUrl.append(firstParam ? '?' : '&');
            requestUrl.append(urlParam.paramName).append('=');

            if (urlParam.paramValue != null) {
                try {
                    requestUrl.append(URLEncoder.encode(urlParam.paramValue, "UTF-8"));

                } catch (UnsupportedEncodingException e) {
                    // No error - UTF-8 is always supported
                }
            }
            firstParam = false;
        }

        HttpGet request = new HttpGet(requestUrl.toString());

        return request;
    }

    /**
     * Returns a new HTTP-POST request for the remote web service.
     * 
     * @param path
     *            the base URL path for the request
     * @return HttpPost
     */
    protected HttpPost newPostRequest(String path) {
        HttpPost request = new HttpPost(endpointUrl + path);

        return request;
    }

    /**
     * Configures the 'Authorization' header on the given HTTP request using the current user ID and
     * encrypted password that is configured for this repository.
     */
    private HttpClientContext createHttpContext() {
        HttpClientContext context = HttpClientContext.create();

        if ((userId != null) && (encryptedPassword != null)) {
            AuthState target = new AuthState();
            target.update(new BasicScheme(),
                    new UsernamePasswordCredentials(userId, PasswordHelper.decrypt(encryptedPassword)));
            context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, target);
        }
        return context;
    }

    /**
     * If the response status indicates an error condition and a message is provided, the text of
     * that message is returned.
     * 
     * @param response
     *            the HTTP response to process
     * @return String
     */
    private static String getResponseErrorMessage(HttpResponse response) {
        String errorMessage = null;

        if (response.getStatusLine().getStatusCode() != HTTP_RESPONSE_STATUS_OK) {
            try {
                InputStream responseStream = response.getEntity().getContent();
                ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
                byte[] buffer = new byte[256];
                int bytesRead;

                while ((bytesRead = responseStream.read(buffer)) >= 0) {
                    byteStream.write(buffer, 0, bytesRead);
                }
                responseStream.close();
                errorMessage = new String(byteStream.toByteArray(), "UTF-8");

            } catch (IOException e) {
                errorMessage = "Unknown repository error on the remote host.";
            }
        }
        return errorMessage;
    }

    /**
     * Encapsulates a single name/value pair that should be included as a URL parameter on an HTTP
     * GET request.
     */
    private class HttpGetParam {

        public String paramName;
        public String paramValue;

        public HttpGetParam(String paramName, String paramValue) {
            this.paramName = paramName;
            this.paramValue = paramValue;
        }

    }

    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

    private byte[] toByteArray(InputStream is) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int nRead;
        byte[] data = new byte[DEFAULT_BUFFER_SIZE];
        while ((nRead = is.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
        buffer.flush();
        return buffer.toByteArray();
    }

}