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

Java tutorial

Introduction

Here is the source code for org.xwiki.extension.repository.internal.DefaultExtensionRepositoryManager.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.internal;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.apache.commons.collections4.map.LRUMap;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.component.phase.Initializable;
import org.xwiki.component.phase.InitializationException;
import org.xwiki.extension.Extension;
import org.xwiki.extension.ExtensionDependency;
import org.xwiki.extension.ExtensionId;
import org.xwiki.extension.ResolveException;
import org.xwiki.extension.repository.ExtensionRepository;
import org.xwiki.extension.repository.ExtensionRepositoryDescriptor;
import org.xwiki.extension.repository.ExtensionRepositoryException;
import org.xwiki.extension.repository.ExtensionRepositoryFactory;
import org.xwiki.extension.repository.ExtensionRepositoryId;
import org.xwiki.extension.repository.ExtensionRepositoryManager;
import org.xwiki.extension.repository.ExtensionRepositorySource;
import org.xwiki.extension.repository.result.AggregatedIterableResult;
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.ExtensionQuery;
import org.xwiki.extension.repository.search.SearchException;
import org.xwiki.extension.repository.search.Searchable;
import org.xwiki.extension.version.Version;

/**
 * Default implementation of {@link ExtensionRepositoryManager}.
 *
 * @version $Id: 8872cf8f055bc202c67d61a1eb15779a5a10ed51 $
 * @since 4.0M1
 */
@Component
@Singleton
public class DefaultExtensionRepositoryManager implements ExtensionRepositoryManager, Initializable {
    /**
     * Used to lookup {@link ExtensionRepositoryFactory}s.
     */
    @Inject
    private ComponentManager componentManager;

    /**
     * The logger to log.
     */
    @Inject
    private Logger logger;

    /**
     * Used to initialize {@link #repositoryManager}.
     */
    @Inject
    private List<ExtensionRepositorySource> repositoriesSources;

    /**
     * The registered repositories.
     */
    private final Map<String, ExtensionRepository> repositoryMap = Collections
            .synchronizedMap(new LinkedHashMap<String, ExtensionRepository>());

    private Collection<ExtensionRepository> repositories = Collections.emptyList();

    private LRUMap<ExtensionRepositoryDescriptor, ExtensionRepository> repositoriesCache = new LRUMap<>(100);

    @Override
    public void initialize() throws InitializationException {
        // Load extension repositories
        for (ExtensionRepositorySource repositoriesSource : this.repositoriesSources) {
            for (ExtensionRepositoryDescriptor repositoryDescriptor : repositoriesSource
                    .getExtensionRepositoryDescriptors()) {
                try {
                    addRepository(repositoryDescriptor);
                } catch (ExtensionRepositoryException e) {
                    this.logger.error("Failed to add repository [" + repositoryDescriptor + "]", e);
                }
            }
        }
    }

    @Override
    @Deprecated
    public ExtensionRepository addRepository(ExtensionRepositoryId repositoryId)
            throws ExtensionRepositoryException {
        return addRepository((ExtensionRepositoryDescriptor) repositoryId);
    }

    @Override
    public ExtensionRepository addRepository(ExtensionRepositoryDescriptor repositoryDescriptor)
            throws ExtensionRepositoryException {
        ExtensionRepository repository;

        try {
            ExtensionRepositoryFactory repositoryFactory = this.componentManager
                    .getInstance(ExtensionRepositoryFactory.class, repositoryDescriptor.getType());

            repository = repositoryFactory.createRepository(repositoryDescriptor);

            addRepository(repository);
        } catch (ComponentLookupException e) {
            throw new ExtensionRepositoryException(
                    "Unsupported repository type [" + repositoryDescriptor.getType() + "]", e);
        }

        return repository;
    }

    @Override
    public void addRepository(ExtensionRepository repository) {
        this.repositoryMap.put(repository.getDescriptor().getId(), repository);
        this.repositories = new ArrayList<>(this.repositoryMap.values());
    }

    @Override
    public void removeRepository(String repositoryId) {
        this.repositoryMap.remove(repositoryId);
        this.repositories = new ArrayList<>(this.repositoryMap.values());
    }

    @Override
    public ExtensionRepository getRepository(String repositoryId) {
        return this.repositoryMap.get(repositoryId);
    }

    private ExtensionRepository getRepository(ExtensionRepositoryDescriptor repositoryDescriptor)
            throws ExtensionRepositoryException {
        // Try in the cache
        ExtensionRepository repository = this.repositoriesCache.get(repositoryDescriptor);

        if (repository == null) {
            // Try in the registered repositories
            if (repositoryDescriptor.getId() != null) {
                repository = getRepository(repositoryDescriptor.getId());
            }

            if (repository == null || !repository.getDescriptor().equals(repositoryDescriptor)) {
                // Create one
                ExtensionRepositoryFactory repositoryFactory;
                try {
                    repositoryFactory = this.componentManager.getInstance(ExtensionRepositoryFactory.class,
                            repositoryDescriptor.getType());
                } catch (ComponentLookupException e) {
                    throw new ExtensionRepositoryException(
                            "Unsupported extension repository type [{" + repositoryDescriptor.getType() + "}]", e);
                }

                repository = repositoryFactory.createRepository(repositoryDescriptor);
            }

            this.repositoriesCache.put(repositoryDescriptor, repository);
        }

        return repository;
    }

    @Override
    public Collection<ExtensionRepository> getRepositories() {
        return Collections.unmodifiableCollection(this.repositories);
    }

    @Override
    public Extension resolve(ExtensionId extensionId) throws ResolveException {
        ResolveException lastExtension = null;

        for (ExtensionRepository repository : this.repositories) {
            try {
                return repository.resolve(extensionId);
            } catch (ResolveException e) {
                this.logger.debug("Could not find extension [{}] in repository [{}]", extensionId,
                        repository.getDescriptor(), e);

                lastExtension = e;
            }
        }

        throw new ResolveException(MessageFormat.format("Could not find extension [{0}]", extensionId),
                lastExtension);
    }

    @Override
    public Extension resolve(ExtensionDependency extensionDependency) throws ResolveException {
        Set<ExtensionRepositoryDescriptor> checkedRepositories = new HashSet<>();

        Exception lastExtension = null;

        for (ExtensionRepositoryDescriptor repositoryDescriptor : extensionDependency.getRepositories()) {
            if (checkedRepositories.contains(repositoryDescriptor)) {
                continue;
            }

            // Remember we tried that repository
            checkedRepositories.add(repositoryDescriptor);

            ExtensionRepository repository;
            try {
                repository = getRepository(repositoryDescriptor);
            } catch (ExtensionRepositoryException e) {
                this.logger.warn("Invalid repository [{}] in extension dependency",
                        extensionDependency.getRepositories(), extensionDependency,
                        ExceptionUtils.getRootCauseMessage(e));

                continue;
            }

            try {
                return repository.resolve(extensionDependency);
            } catch (ResolveException e) {
                this.logger.debug("Could not find extension dependency [{}] in repository [{}]",
                        extensionDependency, repository.getDescriptor(), e);

                lastExtension = e;
            }
        }

        for (ExtensionRepository repository : this.repositories) {
            if (checkedRepositories.contains(repository.getDescriptor())) {
                continue;
            }

            // Remember we tried that repository
            checkedRepositories.add(repository.getDescriptor());

            try {
                return repository.resolve(extensionDependency);
            } catch (ResolveException e) {
                this.logger.debug("Could not find extension dependency [{}] in repository [{}]",
                        extensionDependency, repository.getDescriptor(), e);

                lastExtension = e;
            }
        }

        throw new ResolveException(
                MessageFormat.format("Could not find extension dependency [{0}]", extensionDependency),
                lastExtension);
    }

    @Override
    public IterableResult<Version> resolveVersions(String id, int offset, int nb) throws ResolveException {
        SortedSet<Version> versionSet = new TreeSet<Version>();

        for (ExtensionRepository repository : this.repositories) {
            try {
                IterableResult<Version> versions = repository.resolveVersions(id, 0, -1);

                for (Version version : versions) {
                    versionSet.add(version);
                }
            } catch (ResolveException e) {
                this.logger.debug("Could not find versions for extension with id [{}]", id, e);
            }
        }

        if (versionSet.isEmpty()) {
            throw new ResolveException(
                    MessageFormat.format("Could not find versions for extension with id [{0}]", id));
        }

        return RepositoryUtils.getIterableResult(offset, nb, versionSet);
    }

    @Override
    public IterableResult<Extension> search(String pattern, int offset, int nb) {
        ExtensionQuery query = new ExtensionQuery(pattern);

        query.setOffset(offset);
        query.setLimit(nb);

        return search(query);
    }

    @Override
    public IterableResult<Extension> search(ExtensionQuery query) {
        IterableResult<? extends Extension> searchResult = null;

        int currentOffset = query.getOffset() > 0 ? query.getOffset() : 0;
        int currentNb = query.getLimit();

        // A local index would avoid things like this...
        for (ExtensionRepository repository : this.repositories) {
            try {
                ExtensionQuery customQuery = query;
                if (currentOffset != customQuery.getOffset() && currentNb != customQuery.getLimit()) {
                    customQuery = new ExtensionQuery(query);
                    customQuery.setOffset(currentOffset);
                    customQuery.setLimit(currentNb);
                }

                searchResult = search(repository, customQuery, searchResult);

                if (searchResult != null) {
                    if (currentOffset > 0) {
                        currentOffset = query.getOffset() - searchResult.getTotalHits();
                        if (currentOffset < 0) {
                            currentOffset = 0;
                        }
                    }

                    if (currentNb > 0) {
                        currentNb = query.getLimit() - searchResult.getSize();
                        if (currentNb < 0) {
                            currentNb = 0;
                        }
                    }
                }
            } catch (SearchException e) {
                this.logger.error(
                        "Failed to search on repository [{}] with query [{}]. "
                                + "Ignore and go to next repository.",
                        repository.getDescriptor().toString(), query, e);
            }
        }

        return searchResult != null ? (IterableResult) searchResult
                : new CollectionIterableResult<Extension>(0, query.getOffset(), Collections.<Extension>emptyList());

    }

    /**
     * Search one repository.
     *
     * @param repository the repository to search
     * @param query the search query
     * @param previousSearchResult the current search result merged from all previous repositories
     * @return the updated maximum number of search results to return
     * @throws SearchException error while searching on provided repository
     */
    private IterableResult<? extends Extension> search(ExtensionRepository repository, ExtensionQuery query,
            IterableResult<? extends Extension> previousSearchResult) throws SearchException {
        IterableResult<? extends Extension> result;

        if (repository instanceof Searchable) {
            if (repository instanceof AdvancedSearchable) {
                AdvancedSearchable searchableRepository = (AdvancedSearchable) repository;

                result = searchableRepository.search(query);
            } else {
                Searchable searchableRepository = (Searchable) repository;

                result = searchableRepository.search(query.getQuery(), query.getOffset(), query.getLimit());
            }

            if (previousSearchResult != null) {
                result = appendSearchResults(previousSearchResult, result);
            }
        } else {
            result = previousSearchResult;
        }

        return result;
    }

    /**
     * @param previousSearchResult all the previous search results
     * @param result the new search result to append
     * @return the new aggregated search result
     */
    private AggregatedIterableResult<Extension> appendSearchResults(
            IterableResult<? extends Extension> previousSearchResult, IterableResult<? extends Extension> result) {
        AggregatedIterableResult<Extension> newResult;

        if (previousSearchResult instanceof AggregatedIterableResult) {
            newResult = ((AggregatedIterableResult<Extension>) previousSearchResult);
        } else {
            newResult = new AggregatedIterableResult<Extension>(previousSearchResult.getOffset());
            newResult.addSearchResult((IterableResult) previousSearchResult);
        }

        newResult.addSearchResult((IterableResult) result);

        return newResult;
    }
}