1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.extension.repository.internal

File DefaultExtensionRepositoryManager.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart7.png
64% of files have more coverage

Code metrics

46
137
23
2
526
372
63
0.46
5.96
11.5
2.74

Classes

Class Line # Actions
DefaultExtensionRepositoryManager 76 132 0% 59 60
0.695431569.5%
DefaultExtensionRepositoryManager.ExtensionRepositoryEntry 107 5 0% 4 2
0.777777877.8%
 

Contributing tests

This file is covered by 124 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.extension.repository.internal;
21   
22    import java.text.MessageFormat;
23    import java.util.Collection;
24    import java.util.Collections;
25    import java.util.HashSet;
26    import java.util.LinkedHashMap;
27    import java.util.List;
28    import java.util.Map;
29    import java.util.Set;
30    import java.util.SortedSet;
31    import java.util.TreeSet;
32    import java.util.stream.Collectors;
33    import java.util.stream.Stream;
34   
35    import javax.inject.Inject;
36    import javax.inject.Singleton;
37   
38    import org.apache.commons.collections4.map.LRUMap;
39    import org.apache.commons.lang3.exception.ExceptionUtils;
40    import org.slf4j.Logger;
41    import org.xwiki.component.annotation.Component;
42    import org.xwiki.component.manager.ComponentLookupException;
43    import org.xwiki.component.manager.ComponentManager;
44    import org.xwiki.component.phase.Initializable;
45    import org.xwiki.component.phase.InitializationException;
46    import org.xwiki.extension.Extension;
47    import org.xwiki.extension.ExtensionDependency;
48    import org.xwiki.extension.ExtensionId;
49    import org.xwiki.extension.ExtensionNotFoundException;
50    import org.xwiki.extension.ResolveException;
51    import org.xwiki.extension.repository.AbstractAdvancedSearchableExtensionRepository;
52    import org.xwiki.extension.repository.DefaultExtensionRepositoryDescriptor;
53    import org.xwiki.extension.repository.ExtensionRepository;
54    import org.xwiki.extension.repository.ExtensionRepositoryDescriptor;
55    import org.xwiki.extension.repository.ExtensionRepositoryException;
56    import org.xwiki.extension.repository.ExtensionRepositoryFactory;
57    import org.xwiki.extension.repository.ExtensionRepositoryId;
58    import org.xwiki.extension.repository.ExtensionRepositoryManager;
59    import org.xwiki.extension.repository.ExtensionRepositorySource;
60    import org.xwiki.extension.repository.result.CollectionIterableResult;
61    import org.xwiki.extension.repository.result.IterableResult;
62    import org.xwiki.extension.repository.search.AdvancedSearchable;
63    import org.xwiki.extension.repository.search.ExtensionQuery;
64    import org.xwiki.extension.repository.search.SearchException;
65    import org.xwiki.extension.repository.search.Searchable;
66    import org.xwiki.extension.version.Version;
67   
68    /**
69    * Default implementation of {@link ExtensionRepositoryManager}.
70    *
71    * @version $Id: 7214de4290d75ec854a3cd6ece9644f776874545 $
72    * @since 4.0M1
73    */
74    @Component
75    @Singleton
 
76    public class DefaultExtensionRepositoryManager extends AbstractAdvancedSearchableExtensionRepository
77    implements ExtensionRepositoryManager, Initializable
78    {
79    /**
80    * Used to lookup {@link ExtensionRepositoryFactory}s.
81    */
82    @Inject
83    private ComponentManager componentManager;
84   
85    /**
86    * The logger to log.
87    */
88    @Inject
89    private Logger logger;
90   
91    /**
92    * Used to initialize {@link #repositoryManager}.
93    */
94    @Inject
95    private List<ExtensionRepositorySource> repositoriesSources;
96   
97    /**
98    * The registered repositories.
99    */
100    private final Map<String, ExtensionRepositoryEntry> repositoryMap =
101    Collections.synchronizedMap(new LinkedHashMap<>());
102   
103    private List<ExtensionRepository> repositories = Collections.emptyList();
104   
105    private LRUMap<ExtensionRepositoryDescriptor, ExtensionRepository> repositoriesCache = new LRUMap<>(100);
106   
 
107    private class ExtensionRepositoryEntry implements Comparable<ExtensionRepositoryEntry>
108    {
109    private ExtensionRepository repository;
110   
111    private int priority;
112   
 
113  197 toggle public ExtensionRepositoryEntry(ExtensionRepository repository, int priority)
114    {
115  197 this.repository = repository;
116  197 this.priority = priority;
117    }
118   
 
119  30 toggle @Override
120    public int compareTo(ExtensionRepositoryEntry other)
121    {
122  30 return this.priority - other.priority;
123    }
124   
 
125  235 toggle public ExtensionRepository getRepository()
126    {
127  235 return this.repository;
128    }
129   
 
130  0 toggle @Override
131    public String toString()
132    {
133  0 return getRepository().toString();
134    }
135    }
136   
137    // Initializable
138   
 
139  245 toggle @Override
140    public void initialize() throws InitializationException
141    {
142    // Set descriptor
143  245 setDescriptor(new DefaultExtensionRepositoryDescriptor("remote"));
144   
145    // Load default extension repositories
146  245 for (ExtensionRepositorySource repositoriesSource : this.repositoriesSources) {
147  260 for (ExtensionRepositoryDescriptor repositoryDescriptor : repositoriesSource
148    .getExtensionRepositoryDescriptors()) {
149  6 try {
150  6 addRepository(repositoryDescriptor, repositoriesSource.getPriority());
151    } catch (ExtensionRepositoryException e) {
152  0 this.logger.error("Failed to add repository [" + repositoryDescriptor + "]", e);
153    }
154    }
155    }
156    }
157   
158    // ExtensionRepositoryManager
159   
 
160  198 toggle private void updateRepositories()
161    {
162    // Get values
163  198 Stream<ExtensionRepositoryEntry> entryStream = this.repositoryMap.values().stream();
164   
165    // Sort
166  198 entryStream = entryStream.sorted();
167   
168    // Convert to list of ExtensionRepository
169  198 this.repositories = entryStream.map(ExtensionRepositoryEntry::getRepository).collect(Collectors.toList());
170    }
171   
 
172  0 toggle @Override
173    @Deprecated
174    public ExtensionRepository addRepository(ExtensionRepositoryId repositoryId) throws ExtensionRepositoryException
175    {
176  0 return addRepository((ExtensionRepositoryDescriptor) repositoryId);
177    }
178   
 
179  45 toggle @Override
180    public ExtensionRepository addRepository(ExtensionRepositoryDescriptor repositoryDescriptor)
181    throws ExtensionRepositoryException
182    {
183  45 return addRepository(repositoryDescriptor, DEFAULT_PRIORITY);
184    }
185   
 
186  55 toggle @Override
187    public ExtensionRepository addRepository(ExtensionRepositoryDescriptor repositoryDescriptor, int priority)
188    throws ExtensionRepositoryException
189    {
190  55 ExtensionRepository repository;
191   
192  55 try {
193  55 ExtensionRepositoryFactory repositoryFactory =
194    this.componentManager.getInstance(ExtensionRepositoryFactory.class, repositoryDescriptor.getType());
195   
196  54 repository = repositoryFactory.createRepository(repositoryDescriptor);
197   
198  54 addRepository(repository, priority);
199    } catch (ComponentLookupException e) {
200  1 throw new ExtensionRepositoryException(
201    "Unsupported repository type [" + repositoryDescriptor.getType() + "]", e);
202    }
203   
204  54 return repository;
205    }
206   
 
207  143 toggle @Override
208    public void addRepository(ExtensionRepository repository)
209    {
210  143 addRepository(repository, DEFAULT_PRIORITY);
211    }
212   
 
213  197 toggle @Override
214    public void addRepository(ExtensionRepository repository, int priority)
215    {
216    // Update the map
217  197 this.repositoryMap.put(repository.getDescriptor().getId(), new ExtensionRepositoryEntry(repository, priority));
218   
219    // Update the list
220  197 updateRepositories();
221    }
222   
 
223  1 toggle @Override
224    public void removeRepository(String repositoryId)
225    {
226    // Update the map
227  1 this.repositoryMap.remove(repositoryId);
228   
229    // Update the list
230  1 updateRepositories();
231    }
232   
 
233  11 toggle @Override
234    public ExtensionRepository getRepository(String repositoryId)
235    {
236  11 ExtensionRepositoryEntry entry = this.repositoryMap.get(repositoryId);
237   
238  11 return entry != null ? entry.getRepository() : null;
239    }
240   
 
241  5 toggle private ExtensionRepository getRepository(ExtensionRepositoryDescriptor repositoryDescriptor)
242    throws ExtensionRepositoryException
243    {
244    // Try in the cache
245  5 ExtensionRepository repository = this.repositoriesCache.get(repositoryDescriptor);
246   
247  5 if (repository == null) {
248    // Try in the registered repositories
249  3 if (repositoryDescriptor.getId() != null) {
250  3 repository = getRepository(repositoryDescriptor.getId());
251    }
252   
253  3 if (repository == null || !repository.getDescriptor().equals(repositoryDescriptor)) {
254    // Create one
255  1 ExtensionRepositoryFactory repositoryFactory;
256  1 try {
257  1 repositoryFactory = this.componentManager.getInstance(ExtensionRepositoryFactory.class,
258    repositoryDescriptor.getType());
259    } catch (ComponentLookupException e) {
260  0 throw new ExtensionRepositoryException(
261    "Unsupported extension repository type [{" + repositoryDescriptor.getType() + "}]", e);
262    }
263   
264  1 repository = repositoryFactory.createRepository(repositoryDescriptor);
265    }
266   
267  3 this.repositoriesCache.put(repositoryDescriptor, repository);
268    }
269   
270  5 return repository;
271    }
272   
 
273  285 toggle @Override
274    public Collection<ExtensionRepository> getRepositories()
275    {
276  285 return this.repositories;
277    }
278   
279    // ExtensionRepository
280   
 
281  393 toggle @Override
282    public Extension resolve(ExtensionId extensionId) throws ResolveException
283    {
284  393 ResolveException lastException = null;
285   
286  393 for (ExtensionRepository repository : this.repositories) {
287  253 try {
288  253 return repository.resolve(extensionId);
289    } catch (ExtensionNotFoundException e1) {
290  45 this.logger.debug("Could not find extension [{}] in repository [{}]", extensionId,
291    repository.getDescriptor(), e1);
292    } catch (ResolveException e2) {
293  0 this.logger.error("Unexpected error when trying to find extension [{}] in repository [{}]", extensionId,
294    repository.getDescriptor(), e2);
295   
296  0 lastException = e2;
297    }
298    }
299   
300  185 if (lastException != null) {
301  0 throw new ResolveException(MessageFormat.format("Failed to resolve extension [{0}]", extensionId),
302    lastException);
303    } else {
304  185 throw new ExtensionNotFoundException(MessageFormat.format("Could not find extension [{0}]", extensionId));
305    }
306    }
307   
 
308  72 toggle @Override
309    public Extension resolve(ExtensionDependency extensionDependency) throws ResolveException
310    {
311  72 Set<ExtensionRepositoryDescriptor> checkedRepositories = new HashSet<>();
312   
313  72 Exception lastException = null;
314   
315    // Try repositories declared in the extension dependency
316  72 for (ExtensionRepositoryDescriptor repositoryDescriptor : extensionDependency.getRepositories()) {
317  5 if (checkedRepositories.contains(repositoryDescriptor)) {
318  0 continue;
319    }
320   
321    // Remember we tried that repository
322  5 checkedRepositories.add(repositoryDescriptor);
323   
324  5 ExtensionRepository repository;
325  5 try {
326  5 repository = getRepository(repositoryDescriptor);
327    } catch (ExtensionRepositoryException e) {
328  0 this.logger.warn("Invalid repository [{}] in extension dependency",
329    extensionDependency.getRepositories(), extensionDependency, ExceptionUtils.getRootCauseMessage(e));
330   
331  0 continue;
332    }
333   
334  5 try {
335  5 return repository.resolve(extensionDependency);
336    } catch (ExtensionNotFoundException e1) {
337  0 this.logger.debug("Could not find extension dependency [{}] in repository [{}]", extensionDependency,
338    repository.getDescriptor(), e1);
339    } catch (ResolveException e2) {
340  0 this.logger.warn("Unexpected error when trying to find extension dependency [{}] in repository [{}]: ",
341    extensionDependency, repository.getDescriptor(), ExceptionUtils.getRootCauseMessage(e2));
342   
343  0 lastException = e2;
344    }
345    }
346   
347    // Try configured repositories
348  67 for (ExtensionRepository repository : this.repositories) {
349  72 if (checkedRepositories.contains(repository.getDescriptor())) {
350  0 continue;
351    }
352   
353    // Remember we tried that repository
354  72 checkedRepositories.add(repository.getDescriptor());
355   
356  72 try {
357  72 return repository.resolve(extensionDependency);
358    } catch (ExtensionNotFoundException e1) {
359  10 this.logger.debug("Could not find extension dependency [{}] in repository [{}]", extensionDependency,
360    repository.getDescriptor(), e1);
361    } catch (ResolveException e2) {
362  0 this.logger.error("Unexpected error when trying to find extension dependency [{}] in repository [{}]",
363    extensionDependency, repository.getDescriptor(), e2);
364   
365  0 lastException = e2;
366    }
367    }
368   
369  5 if (lastException != null) {
370  0 throw new ResolveException(
371    MessageFormat.format("Failed to resolve extension dependency [{0}]", extensionDependency),
372    lastException);
373    } else {
374  5 throw new ExtensionNotFoundException(
375    MessageFormat.format("Could not find extension dependency [{0}]", extensionDependency));
376    }
377    }
378   
 
379  87 toggle @Override
380    public IterableResult<Version> resolveVersions(String id, int offset, int nb) throws ResolveException
381    {
382  87 SortedSet<Version> versionSet = new TreeSet<>();
383   
384  87 for (ExtensionRepository repository : this.repositories) {
385  92 try {
386  92 IterableResult<Version> versions = repository.resolveVersions(id, 0, -1);
387   
388  12 for (Version version : versions) {
389  41 versionSet.add(version);
390    }
391    } catch (ExtensionNotFoundException e1) {
392  80 this.logger.debug("Could not find extension with id [{}] in repository [{}]", id,
393    repository.getDescriptor(), e1);
394    } catch (ResolveException e2) {
395  0 this.logger.error("Unexpected error when trying to find versions for extension with id [{}]", id, e2);
396    }
397    }
398   
399  87 if (versionSet.isEmpty()) {
400  78 throw new ExtensionNotFoundException(
401    MessageFormat.format("Could not find versions for extension with id [{0}]", id));
402    }
403   
404  9 return RepositoryUtils.getIterableResult(offset, nb, versionSet);
405    }
406   
 
407  0 toggle @Override
408    public boolean exists(ExtensionId extensionId)
409    {
410  0 for (ExtensionRepository repository : this.repositories) {
411  0 if (repository.exists(extensionId)) {
412  0 return true;
413    }
414    }
415   
416  0 return false;
417    }
418   
419    // AdvancedSearchable
420   
 
421  37 toggle @Override
422    public IterableResult<Extension> search(ExtensionQuery query) throws SearchException
423    {
424  37 IterableResult<Extension> searchResult = null;
425   
426  37 int currentOffset = query.getOffset() > 0 ? query.getOffset() : 0;
427  37 int currentNb = query.getLimit();
428   
429    // A local index would avoid things like this...
430  37 for (ExtensionRepository repository : this.repositories) {
431  74 try {
432  74 ExtensionQuery customQuery = query;
433  74 if (currentOffset != customQuery.getOffset() && currentNb != customQuery.getLimit()) {
434  0 customQuery = new ExtensionQuery(query);
435  0 customQuery.setOffset(currentOffset);
436  0 customQuery.setLimit(currentNb);
437    }
438   
439  74 searchResult = search(repository, customQuery, searchResult);
440   
441  74 if (searchResult != null) {
442  56 if (currentOffset > 0) {
443  0 currentOffset = query.getOffset() - searchResult.getTotalHits();
444  0 if (currentOffset < 0) {
445  0 currentOffset = 0;
446    }
447    }
448   
449  56 if (currentNb > 0) {
450  56 currentNb = query.getLimit() - searchResult.getSize();
451  56 if (currentNb < 0) {
452  0 currentNb = 0;
453    }
454    }
455    }
456    } catch (SearchException e) {
457  0 this.logger.error(
458    "Failed to search on repository [{}] with query [{}]. " + "Ignore and go to next repository.",
459    repository.getDescriptor().toString(), query, e);
460    }
461    }
462   
463  37 return searchResult != null ? (IterableResult) searchResult
464    : new CollectionIterableResult<Extension>(0, query.getOffset(), Collections.<Extension>emptyList());
465   
466    }
467   
468    /**
469    * Search one repository.
470    *
471    * @param repository the repository to search
472    * @param query the search query
473    * @param previousSearchResult the current search result merged from all previous repositories
474    * @return the updated maximum number of search results to return
475    * @throws SearchException error while searching on provided repository
476    */
 
477  74 toggle private IterableResult<Extension> search(ExtensionRepository repository, ExtensionQuery query,
478    IterableResult<Extension> previousSearchResult) throws SearchException
479    {
480  74 IterableResult<Extension> result;
481   
482  74 if (repository instanceof Searchable) {
483  46 if (repository instanceof AdvancedSearchable) {
484  28 AdvancedSearchable searchableRepository = (AdvancedSearchable) repository;
485   
486  28 result = searchableRepository.search(query);
487    } else {
488  18 Searchable searchableRepository = (Searchable) repository;
489   
490  18 result = searchableRepository.search(query.getQuery(), query.getOffset(), query.getLimit());
491    }
492   
493  46 if (previousSearchResult != null) {
494  0 result = RepositoryUtils.appendSearchResults(previousSearchResult, result);
495    }
496    } else {
497  28 result = previousSearchResult;
498    }
499   
500  74 return result;
501    }
502   
 
503  0 toggle @Override
504    public boolean isFilterable()
505    {
506  0 for (ExtensionRepository repository : this.repositories) {
507  0 if (repository instanceof AdvancedSearchable && ((AdvancedSearchable) repository).isFilterable()) {
508  0 return true;
509    }
510    }
511   
512  0 return false;
513    }
514   
 
515  0 toggle @Override
516    public boolean isSortable()
517    {
518  0 for (ExtensionRepository repository : this.repositories) {
519  0 if (repository instanceof AdvancedSearchable && ((AdvancedSearchable) repository).isSortable()) {
520  0 return true;
521    }
522    }
523   
524  0 return false;
525    }
526    }