org.xwiki.extension.repository.xwiki.internal.XWikiExtensionRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.extension.repository.xwiki.internal.XWikiExtensionRepository.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.extension.repository.xwiki.internal;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBException;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.client.AuthCache;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.extension.Extension;
import org.xwiki.extension.ExtensionDependency;
import org.xwiki.extension.ExtensionId;
import org.xwiki.extension.ExtensionLicenseManager;
import org.xwiki.extension.ResolveException;
import org.xwiki.extension.rating.ExtensionRating;
import org.xwiki.extension.repository.AbstractExtensionRepository;
import org.xwiki.extension.repository.DefaultExtensionRepositoryDescriptor;
import org.xwiki.extension.repository.ExtensionRepositoryDescriptor;
import org.xwiki.extension.repository.http.internal.HttpClientFactory;
import org.xwiki.extension.repository.rating.RatableExtensionRepository;
import org.xwiki.extension.repository.result.CollectionIterableResult;
import org.xwiki.extension.repository.result.IterableResult;
import org.xwiki.extension.repository.search.AdvancedSearchable;
import org.xwiki.extension.repository.search.SearchException;
import org.xwiki.extension.repository.xwiki.model.jaxb.COMPARISON;
import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionQuery;
import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersion;
import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersionSummary;
import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersions;
import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionsSearchResult;
import org.xwiki.extension.repository.xwiki.model.jaxb.Filter;
import org.xwiki.extension.repository.xwiki.model.jaxb.ORDER;
import org.xwiki.extension.repository.xwiki.model.jaxb.ObjectFactory;
import org.xwiki.extension.repository.xwiki.model.jaxb.Repository;
import org.xwiki.extension.repository.xwiki.model.jaxb.SortClause;
import org.xwiki.extension.version.Version;
import org.xwiki.extension.version.VersionConstraint;
import org.xwiki.extension.version.internal.DefaultVersion;
import org.xwiki.repository.Resources;
import org.xwiki.repository.UriBuilder;

/**
 * @version $Id: 991f748d29d3fb787948c14c524de296035d5926 $
 * @since 4.0M1
 */
public class XWikiExtensionRepository extends AbstractExtensionRepository
        implements AdvancedSearchable, RatableExtensionRepository {
    public static final Version VERSION10 = new DefaultVersion(Resources.VERSION10);

    public static final Version VERSION11 = new DefaultVersion(Resources.VERSION11);

    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiExtensionRepository.class);

    private static final ObjectFactory EXTENSION_OBJECT_FACTORY = new ObjectFactory();

    private final transient XWikiExtensionRepositoryFactory repositoryFactory;

    private final transient ExtensionLicenseManager licenseManager;

    private final transient HttpClientFactory httpClientFactory;

    private final transient UriBuilder rootUriBuider;

    private final transient UriBuilder extensionVersionUriBuider;

    private final transient UriBuilder extensionVersionFileUriBuider;

    private final transient UriBuilder extensionVersionsUriBuider;

    private final transient UriBuilder searchUriBuider;

    private HttpClientContext localContext;

    private Version repositoryVersion;

    private boolean filterable;

    private boolean sortable;

    public XWikiExtensionRepository(ExtensionRepositoryDescriptor repositoryDescriptor,
            XWikiExtensionRepositoryFactory repositoryFactory, ExtensionLicenseManager licenseManager,
            HttpClientFactory httpClientFactory) throws Exception {
        super(repositoryDescriptor.getURI().getPath().endsWith("/")
                ? new DefaultExtensionRepositoryDescriptor(repositoryDescriptor.getId(),
                        repositoryDescriptor.getType(),
                        new URI(StringUtils.chop(repositoryDescriptor.getURI().toString())))
                : repositoryDescriptor);

        this.repositoryFactory = repositoryFactory;
        this.licenseManager = licenseManager;
        this.httpClientFactory = httpClientFactory;

        // Uri builders
        this.rootUriBuider = createUriBuilder(Resources.ENTRYPOINT);
        this.extensionVersionUriBuider = createUriBuilder(Resources.EXTENSION_VERSION);
        this.extensionVersionFileUriBuider = createUriBuilder(Resources.EXTENSION_VERSION_FILE);
        this.extensionVersionsUriBuider = createUriBuilder(Resources.EXTENSION_VERSIONS);
        this.searchUriBuider = createUriBuilder(Resources.SEARCH);

        // Setup preemptive authentication
        if (getDescriptor().getProperty("auth.user") != null) {
            // Create AuthCache instance
            AuthCache authCache = new BasicAuthCache();
            // Generate BASIC scheme object and add it to the local
            // auth cache
            BasicScheme basicAuth = new BasicScheme();
            authCache.put(new HttpHost(getDescriptor().getURI().getHost(), getDescriptor().getURI().getPort(),
                    getDescriptor().getURI().getScheme()), basicAuth);

            // Add AuthCache to the execution context
            this.localContext = HttpClientContext.create();
            this.localContext.setAuthCache(authCache);
        }
    }

    /**
     * Check what is supported by the remote repository.
     */
    private void initRepositoryFeatures() {
        if (this.repositoryVersion == null) {
            // Default features

            this.repositoryVersion = new DefaultVersion(Resources.VERSION10);
            this.filterable = false;
            this.sortable = false;

            // Get remote features

            CloseableHttpResponse response;
            try {
                response = getRESTResource(this.rootUriBuider);
            } catch (IOException e) {
                // Assume it's a 1.0 repository
                return;
            }

            try {
                Repository repository = getRESTObject(response);

                this.repositoryVersion = new DefaultVersion(repository.getVersion());
                this.filterable = repository.isFilterable() == Boolean.TRUE;
                this.sortable = repository.isSortable() == Boolean.TRUE;
            } catch (Exception e) {
                LOGGER.error("Failed to get repository features", e);
            }
        }
    }

    /**
     * @return the version of the protocol supported by the repository
     */
    public Version getRepositoryVersion() {
        initRepositoryFeatures();

        return this.repositoryVersion;
    }

    @Override
    public boolean isFilterable() {
        initRepositoryFeatures();

        return this.filterable;
    }

    @Override
    public boolean isSortable() {
        initRepositoryFeatures();

        return this.sortable;
    }

    protected UriBuilder getExtensionFileUriBuider() {
        return this.extensionVersionFileUriBuider;
    }

    protected CloseableHttpResponse getRESTResource(UriBuilder builder, Object... values) throws IOException {
        String url;
        try {
            url = builder.build(values).toString();
        } catch (Exception e) {
            throw new IOException("Failed to build REST URL", e);
        }

        CloseableHttpClient httpClient = this.httpClientFactory.createClient(
                getDescriptor().getProperty("auth.user"), getDescriptor().getProperty("auth.password"));

        HttpGet getMethod = new HttpGet(url);
        getMethod.addHeader("Accept", "application/xml");
        CloseableHttpResponse response;
        try {
            if (this.localContext != null) {
                response = httpClient.execute(getMethod, this.localContext);
            } else {
                response = httpClient.execute(getMethod);
            }
        } catch (Exception e) {
            throw new IOException(String.format("Failed to request [%s]", getMethod.getURI()), e);
        }

        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            throw new IOException(String.format("Invalid answer [%s] from the server when requesting [%s]",
                    response.getStatusLine().getStatusCode(), getMethod.getURI()));
        }

        return response;
    }

    protected CloseableHttpResponse postRESTResource(UriBuilder builder, String content, Object... values)
            throws IOException {
        String url;
        try {
            url = builder.build(values).toString();
        } catch (Exception e) {
            throw new IOException("Failed to build REST URL", e);
        }

        CloseableHttpClient httpClient = this.httpClientFactory.createClient(
                getDescriptor().getProperty("auth.user"), getDescriptor().getProperty("auth.password"));

        HttpPost postMethod = new HttpPost(url);
        postMethod.addHeader("Accept", "application/xml");

        StringEntity entity = new StringEntity(content,
                ContentType.create(ContentType.APPLICATION_XML.getMimeType(), Consts.UTF_8));
        postMethod.setEntity(entity);

        CloseableHttpResponse response;
        try {
            if (this.localContext != null) {
                response = httpClient.execute(postMethod, this.localContext);
            } else {
                response = httpClient.execute(postMethod);
            }
        } catch (Exception e) {
            throw new IOException(String.format("Failed to request [%s]", postMethod.getURI()), e);
        }

        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            throw new IOException(String.format("Invalid answer [%s] from the server when requesting [%s]",
                    response.getStatusLine().getStatusCode(), postMethod.getURI()));
        }

        return response;
    }

    protected Object getRESTObject(UriBuilder builder, Object... values)
            throws IllegalStateException, IOException, JAXBException {
        return getRESTObject(getRESTResource(builder, values));
    }

    protected Object postRESTObject(UriBuilder builder, Object restObject, Object... values)
            throws IllegalStateException, IOException, JAXBException {
        StringWriter writer = new StringWriter();
        this.repositoryFactory.getMarshaller().marshal(restObject, writer);

        return getRESTObject(postRESTResource(builder, writer.toString(), values));
    }

    protected <T> T getRESTObject(CloseableHttpResponse response)
            throws IllegalStateException, IOException, JAXBException {
        try {
            try (InputStream inputStream = response.getEntity().getContent()) {
                return (T) this.repositoryFactory.getUnmarshaller().unmarshal(inputStream);
            }
        } finally {
            response.close();
        }
    }

    private UriBuilder createUriBuilder(String path) {
        return new UriBuilder(getDescriptor().getURI(), path);
    }

    // ExtensionRepository

    @Override
    public Extension resolve(ExtensionId extensionId) throws ResolveException {
        try {
            return new XWikiExtension(this, (ExtensionVersion) getRESTObject(this.extensionVersionUriBuider,
                    extensionId.getId(), extensionId.getVersion().getValue()), this.licenseManager);
        } catch (Exception e) {
            throw new ResolveException("Failed to create extension object for extension [" + extensionId + "]", e);
        }
    }

    @Override
    public Extension resolve(ExtensionDependency extensionDependency) throws ResolveException {
        VersionConstraint constraint = extensionDependency.getVersionConstraint();

        try {
            Version version;
            if (!constraint.getRanges().isEmpty()) {
                ExtensionVersions versions = resolveExtensionVersions(extensionDependency.getId(), constraint, 0,
                        -1, false);
                if (versions.getExtensionVersionSummaries().isEmpty()) {
                    throw new ResolveException("Can't find any version with id [" + extensionDependency.getId()
                            + "] matching version constraint [" + extensionDependency.getVersionConstraint() + "]");
                }

                version = new DefaultVersion(versions.getExtensionVersionSummaries()
                        .get(versions.getExtensionVersionSummaries().size() - 1).getVersion());
            } else {
                version = constraint.getVersion();
            }

            return new XWikiExtension(this, (ExtensionVersion) getRESTObject(this.extensionVersionUriBuider,
                    extensionDependency.getId(), version), this.licenseManager);
        } catch (Exception e) {
            throw new ResolveException(
                    "Failed to create extension object for extension dependency [" + extensionDependency + "]", e);
        }
    }

    private ExtensionVersions resolveExtensionVersions(String id, VersionConstraint constraint, int offset, int nb,
            boolean requireTotalHits) throws ResolveException {
        UriBuilder builder = this.extensionVersionsUriBuider.clone();

        builder.queryParam(Resources.QPARAM_LIST_REQUIRETOTALHITS, requireTotalHits);
        builder.queryParam(Resources.QPARAM_LIST_START, offset);
        builder.queryParam(Resources.QPARAM_LIST_NUMBER, nb);
        if (constraint != null) {
            builder.queryParam(Resources.QPARAM_VERSIONS_RANGES, constraint.getValue());
        }

        try {
            return (ExtensionVersions) getRESTObject(builder, id);
        } catch (Exception e) {
            throw new ResolveException("Failed to find version for extension id [" + id + "]", e);
        }
    }

    @Override
    public IterableResult<Version> resolveVersions(String id, int offset, int nb) throws ResolveException {
        ExtensionVersions restExtensions = resolveExtensionVersions(id, null, offset, nb, true);

        List<Version> versions = new ArrayList<Version>(restExtensions.getExtensionVersionSummaries().size());
        for (ExtensionVersionSummary restExtension : restExtensions.getExtensionVersionSummaries()) {
            versions.add(new DefaultVersion(restExtension.getVersion()));
        }

        return new CollectionIterableResult<Version>(restExtensions.getTotalHits(), restExtensions.getOffset(),
                versions);
    }

    // Searchable

    @Override
    public IterableResult<Extension> search(String pattern, int offset, int nb) throws SearchException {
        UriBuilder builder = this.searchUriBuider.clone();

        builder.queryParam(Resources.QPARAM_LIST_START, offset);
        builder.queryParam(Resources.QPARAM_LIST_NUMBER, nb);
        if (pattern != null) {
            builder.queryParam(Resources.QPARAM_SEARCH_QUERY, pattern);
        }

        ExtensionsSearchResult restExtensions;
        try {
            restExtensions = (ExtensionsSearchResult) getRESTObject(builder);
        } catch (Exception e) {
            throw new SearchException("Failed to search extensions based on pattern [" + pattern + "]", e);
        }

        List<Extension> extensions = new ArrayList<Extension>(restExtensions.getExtensions().size());
        for (ExtensionVersion restExtension : restExtensions.getExtensions()) {
            extensions.add(new XWikiExtension(this, restExtension, this.licenseManager));
        }

        return new CollectionIterableResult<Extension>(restExtensions.getTotalHits(), restExtensions.getOffset(),
                extensions);
    }

    @Override
    public IterableResult<Extension> search(org.xwiki.extension.repository.search.ExtensionQuery query)
            throws SearchException {
        if (getRepositoryVersion().equals(VERSION10)) {
            return search(query.getQuery(), query.getOffset(), query.getLimit());
        }

        UriBuilder builder = this.searchUriBuider.clone();

        ExtensionQuery restQuery = EXTENSION_OBJECT_FACTORY.createExtensionQuery();

        restQuery.setQuery(query.getQuery());
        restQuery.setOffset(query.getOffset());
        restQuery.setLimit(query.getLimit());
        for (org.xwiki.extension.repository.search.ExtensionQuery.Filter filter : query.getFilters()) {
            Filter restFilter = EXTENSION_OBJECT_FACTORY.createFilter();
            restFilter.setField(filter.getField());
            restFilter.setValueString(filter.getValue().toString());
            restFilter.setComparison(COMPARISON.fromValue(filter.getComparison().name()));
            restQuery.getFilters().add(restFilter);
        }
        for (org.xwiki.extension.repository.search.ExtensionQuery.SortClause sortClause : query.getSortClauses()) {
            SortClause restSortClause = EXTENSION_OBJECT_FACTORY.createSortClause();
            restSortClause.setField(sortClause.getField());
            restSortClause.setOrder(ORDER.fromValue(sortClause.getOrder().name()));
            restQuery.getSortClauses().add(restSortClause);
        }

        ExtensionsSearchResult restExtensions;
        try {
            restExtensions = (ExtensionsSearchResult) postRESTObject(builder, restQuery);
        } catch (Exception e) {
            throw new SearchException("Failed to search extensions based on pattern [" + query.getQuery() + "]", e);
        }

        List<Extension> extensions = new ArrayList<Extension>(restExtensions.getExtensions().size());
        for (ExtensionVersion restExtension : restExtensions.getExtensions()) {
            extensions.add(new XWikiExtension(this, restExtension, this.licenseManager));
        }

        return new CollectionIterableResult<Extension>(restExtensions.getTotalHits(), restExtensions.getOffset(),
                extensions);
    }

    // Ratable

    @Override
    public ExtensionRating getRating(ExtensionId extensionId) throws ResolveException {
        return getRating(extensionId.getId(), extensionId.getVersion());
    }

    @Override
    public ExtensionRating getRating(String extensionId, Version extensionVersion) throws ResolveException {
        return getRating(extensionId, extensionVersion.getValue());
    }

    @Override
    public ExtensionRating getRating(String extensionId, String extensionVersion) throws ResolveException {
        try {
            return new XWikiExtension(this,
                    (ExtensionVersion) getRESTObject(this.extensionVersionUriBuider, extensionId, extensionVersion),
                    this.licenseManager).getRating();
        } catch (Exception e) {
            throw new ResolveException("Failed to create extension object for extension [" + extensionId + ":"
                    + extensionVersion + "]", e);
        }
    }
}