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

File XWikiExtensionRepository.java

 

Coverage histogram

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

Code metrics

26
159
21
1
520
395
52
0.33
7.57
21
2.48

Classes

Class Line # Actions
XWikiExtensionRepository 85 159 0% 52 69
0.6650485466.5%
 

Contributing tests

This file is covered by 2 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.xwiki.internal;
21   
22    import java.io.IOException;
23    import java.io.InputStream;
24    import java.io.StringWriter;
25    import java.net.URI;
26    import java.util.ArrayList;
27    import java.util.List;
28   
29    import javax.xml.bind.JAXBException;
30   
31    import org.apache.commons.lang3.StringUtils;
32    import org.apache.http.Consts;
33    import org.apache.http.HttpHost;
34    import org.apache.http.HttpStatus;
35    import org.apache.http.client.AuthCache;
36    import org.apache.http.client.methods.CloseableHttpResponse;
37    import org.apache.http.client.methods.HttpGet;
38    import org.apache.http.client.methods.HttpPost;
39    import org.apache.http.client.protocol.HttpClientContext;
40    import org.apache.http.entity.ContentType;
41    import org.apache.http.entity.StringEntity;
42    import org.apache.http.impl.auth.BasicScheme;
43    import org.apache.http.impl.client.BasicAuthCache;
44    import org.apache.http.impl.client.CloseableHttpClient;
45    import org.slf4j.Logger;
46    import org.slf4j.LoggerFactory;
47    import org.xwiki.extension.Extension;
48    import org.xwiki.extension.ExtensionDependency;
49    import org.xwiki.extension.ExtensionId;
50    import org.xwiki.extension.ExtensionLicenseManager;
51    import org.xwiki.extension.ExtensionNotFoundException;
52    import org.xwiki.extension.ResolveException;
53    import org.xwiki.extension.internal.ExtensionFactory;
54    import org.xwiki.extension.rating.ExtensionRating;
55    import org.xwiki.extension.repository.AbstractExtensionRepository;
56    import org.xwiki.extension.repository.DefaultExtensionRepositoryDescriptor;
57    import org.xwiki.extension.repository.ExtensionRepositoryDescriptor;
58    import org.xwiki.extension.repository.http.internal.HttpClientFactory;
59    import org.xwiki.extension.repository.rating.RatableExtensionRepository;
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.SearchException;
64    import org.xwiki.extension.repository.xwiki.model.jaxb.COMPARISON;
65    import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionQuery;
66    import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersion;
67    import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersionSummary;
68    import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionVersions;
69    import org.xwiki.extension.repository.xwiki.model.jaxb.ExtensionsSearchResult;
70    import org.xwiki.extension.repository.xwiki.model.jaxb.Filter;
71    import org.xwiki.extension.repository.xwiki.model.jaxb.ORDER;
72    import org.xwiki.extension.repository.xwiki.model.jaxb.ObjectFactory;
73    import org.xwiki.extension.repository.xwiki.model.jaxb.Repository;
74    import org.xwiki.extension.repository.xwiki.model.jaxb.SortClause;
75    import org.xwiki.extension.version.Version;
76    import org.xwiki.extension.version.VersionConstraint;
77    import org.xwiki.extension.version.internal.DefaultVersion;
78    import org.xwiki.repository.Resources;
79    import org.xwiki.repository.UriBuilder;
80   
81    /**
82    * @version $Id: 13024f96e14b21972e9163c4eccb3b070ccc32f6 $
83    * @since 4.0M1
84    */
 
85    public class XWikiExtensionRepository extends AbstractExtensionRepository
86    implements AdvancedSearchable, RatableExtensionRepository
87    {
88    public static final Version VERSION10 = new DefaultVersion(Resources.VERSION10);
89   
90    public static final Version VERSION11 = new DefaultVersion(Resources.VERSION11);
91   
92    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiExtensionRepository.class);
93   
94    private static final ObjectFactory EXTENSION_OBJECT_FACTORY = new ObjectFactory();
95   
96    private final transient XWikiExtensionRepositoryFactory repositoryFactory;
97   
98    private final transient ExtensionLicenseManager licenseManager;
99   
100    private final transient HttpClientFactory httpClientFactory;
101   
102    private final transient ExtensionFactory factory;
103   
104    private final transient UriBuilder rootUriBuider;
105   
106    private final transient UriBuilder extensionVersionUriBuider;
107   
108    private final transient UriBuilder extensionVersionFileUriBuider;
109   
110    private final transient UriBuilder extensionVersionsUriBuider;
111   
112    private final transient UriBuilder searchUriBuider;
113   
114    private HttpClientContext localContext;
115   
116    private Version repositoryVersion;
117   
118    private boolean filterable;
119   
120    private boolean sortable;
121   
 
122  3 toggle public XWikiExtensionRepository(ExtensionRepositoryDescriptor repositoryDescriptor,
123    XWikiExtensionRepositoryFactory repositoryFactory, ExtensionLicenseManager licenseManager,
124    HttpClientFactory httpClientFactory, ExtensionFactory factory) throws Exception
125    {
126  3 super(repositoryDescriptor.getURI().getPath().endsWith("/")
127    ? new DefaultExtensionRepositoryDescriptor(repositoryDescriptor.getId(), repositoryDescriptor.getType(),
128    new URI(StringUtils.chop(repositoryDescriptor.getURI().toString())))
129    : repositoryDescriptor);
130   
131  3 this.repositoryFactory = repositoryFactory;
132  3 this.licenseManager = licenseManager;
133  3 this.httpClientFactory = httpClientFactory;
134  3 this.factory = factory;
135   
136    // Uri builders
137  3 this.rootUriBuider = createUriBuilder(Resources.ENTRYPOINT);
138  3 this.extensionVersionUriBuider = createUriBuilder(Resources.EXTENSION_VERSION);
139  3 this.extensionVersionFileUriBuider = createUriBuilder(Resources.EXTENSION_VERSION_FILE);
140  3 this.extensionVersionsUriBuider = createUriBuilder(Resources.EXTENSION_VERSIONS);
141  3 this.searchUriBuider = createUriBuilder(Resources.SEARCH);
142   
143    // Setup preemptive authentication
144  3 if (getDescriptor().getProperty("auth.user") != null) {
145    // Create AuthCache instance
146  0 AuthCache authCache = new BasicAuthCache();
147    // Generate BASIC scheme object and add it to the local
148    // auth cache
149  0 BasicScheme basicAuth = new BasicScheme();
150  0 authCache.put(new HttpHost(getDescriptor().getURI().getHost(), getDescriptor().getURI().getPort(),
151    getDescriptor().getURI().getScheme()), basicAuth);
152   
153    // Add AuthCache to the execution context
154  0 this.localContext = HttpClientContext.create();
155  0 this.localContext.setAuthCache(authCache);
156    }
157    }
158   
159    /**
160    * Check what is supported by the remote repository.
161    */
 
162  28 toggle private void initRepositoryFeatures()
163    {
164  28 if (this.repositoryVersion == null) {
165    // Default features
166   
167  1 this.repositoryVersion = new DefaultVersion(Resources.VERSION10);
168  1 this.filterable = false;
169  1 this.sortable = false;
170   
171    // Get remote features
172   
173  1 CloseableHttpResponse response;
174  1 try {
175  1 response = getRESTResource(this.rootUriBuider);
176    } catch (IOException e) {
177    // Assume it's a 1.0 repository
178  0 return;
179    }
180   
181  1 try {
182  1 Repository repository = getRESTObject(response);
183   
184  1 this.repositoryVersion = new DefaultVersion(repository.getVersion());
185  1 this.filterable = repository.isFilterable() == Boolean.TRUE;
186  1 this.sortable = repository.isSortable() == Boolean.TRUE;
187    } catch (Exception e) {
188  0 LOGGER.error("Failed to get repository features", e);
189    }
190    }
191    }
192   
193    /**
194    * @return the version of the protocol supported by the repository
195    */
 
196  28 toggle public Version getRepositoryVersion()
197    {
198  28 initRepositoryFeatures();
199   
200  28 return this.repositoryVersion;
201    }
202   
 
203  0 toggle @Override
204    public boolean isFilterable()
205    {
206  0 initRepositoryFeatures();
207   
208  0 return this.filterable;
209    }
210   
 
211  0 toggle @Override
212    public boolean isSortable()
213    {
214  0 initRepositoryFeatures();
215   
216  0 return this.sortable;
217    }
218   
 
219  14 toggle protected UriBuilder getExtensionFileUriBuider()
220    {
221  14 return this.extensionVersionFileUriBuider;
222    }
223   
 
224  82 toggle protected CloseableHttpResponse getRESTResource(UriBuilder builder, Object... values) throws IOException
225    {
226  82 String url;
227  82 try {
228  82 url = builder.build(values).toString();
229    } catch (Exception e) {
230  0 throw new IOException("Failed to build REST URL", e);
231    }
232   
233  82 CloseableHttpClient httpClient = this.httpClientFactory.createClient(getDescriptor().getProperty("auth.user"),
234    getDescriptor().getProperty("auth.password"));
235   
236  82 HttpGet getMethod = new HttpGet(url);
237  82 getMethod.addHeader("Accept", "application/xml");
238  82 CloseableHttpResponse response;
239  82 try {
240  82 if (this.localContext != null) {
241  0 response = httpClient.execute(getMethod, this.localContext);
242    } else {
243  82 response = httpClient.execute(getMethod);
244    }
245    } catch (Exception e) {
246  1 throw new IOException(String.format("Failed to request [%s]", getMethod.getURI()), e);
247    }
248   
249  81 if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
250  9 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
251  9 throw new ResourceNotFoundException(
252    String.format("Resource with URI [%s] does not exist", getMethod.getURI()));
253    } else {
254  0 throw new IOException(String.format("Invalid answer [%s] from the server when requesting [%s]",
255    response.getStatusLine().getStatusCode(), getMethod.getURI()));
256    }
257    }
258   
259  72 return response;
260    }
261   
 
262  28 toggle protected CloseableHttpResponse postRESTResource(UriBuilder builder, String content, Object... values)
263    throws IOException
264    {
265  28 String url;
266  28 try {
267  28 url = builder.build(values).toString();
268    } catch (Exception e) {
269  0 throw new IOException("Failed to build REST URL", e);
270    }
271   
272  28 CloseableHttpClient httpClient = this.httpClientFactory.createClient(getDescriptor().getProperty("auth.user"),
273    getDescriptor().getProperty("auth.password"));
274   
275  28 HttpPost postMethod = new HttpPost(url);
276  28 postMethod.addHeader("Accept", "application/xml");
277   
278  28 StringEntity entity =
279    new StringEntity(content, ContentType.create(ContentType.APPLICATION_XML.getMimeType(), Consts.UTF_8));
280  28 postMethod.setEntity(entity);
281   
282  28 CloseableHttpResponse response;
283  28 try {
284  28 if (this.localContext != null) {
285  0 response = httpClient.execute(postMethod, this.localContext);
286    } else {
287  28 response = httpClient.execute(postMethod);
288    }
289    } catch (Exception e) {
290  0 throw new IOException(String.format("Failed to request [%s]", postMethod.getURI()), e);
291    }
292   
293  28 if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
294  0 throw new IOException(String.format("Invalid answer [%s] from the server when requesting [%s]",
295    response.getStatusLine().getStatusCode(), postMethod.getURI()));
296    }
297   
298  28 return response;
299    }
300   
 
301  67 toggle protected Object getRESTObject(UriBuilder builder, Object... values)
302    throws IllegalStateException, IOException, JAXBException
303    {
304  67 return getRESTObject(getRESTResource(builder, values));
305    }
306   
 
307  28 toggle protected Object postRESTObject(UriBuilder builder, Object restObject, Object... values)
308    throws IllegalStateException, IOException, JAXBException
309    {
310  28 StringWriter writer = new StringWriter();
311  28 this.repositoryFactory.createMarshaller().marshal(restObject, writer);
312   
313  28 return getRESTObject(postRESTResource(builder, writer.toString(), values));
314    }
315   
 
316  86 toggle protected <T> T getRESTObject(CloseableHttpResponse response)
317    throws IllegalStateException, IOException, JAXBException
318    {
319  86 try {
320  86 try (InputStream inputStream = response.getEntity().getContent()) {
321  86 return (T) this.repositoryFactory.createUnmarshaller().unmarshal(inputStream);
322    }
323    } finally {
324  86 response.close();
325    }
326    }
327   
 
328  15 toggle private UriBuilder createUriBuilder(String path)
329    {
330  15 return new UriBuilder(getDescriptor().getURI(), path);
331    }
332   
333    // ExtensionRepository
334   
 
335  52 toggle @Override
336    public Extension resolve(ExtensionId extensionId) throws ResolveException
337    {
338  52 try {
339  52 return new XWikiExtension(this, (ExtensionVersion) getRESTObject(this.extensionVersionUriBuider,
340    extensionId.getId(), extensionId.getVersion().getValue()), this.licenseManager, this.factory);
341    } catch (ResourceNotFoundException e) {
342  2 throw new ExtensionNotFoundException("Could not find extension [" + extensionId + "]", e);
343    } catch (Exception e) {
344  0 throw new ResolveException("Failed to create extension object for extension [" + extensionId + "]", e);
345    }
346    }
347   
 
348  12 toggle @Override
349    public Extension resolve(ExtensionDependency extensionDependency) throws ResolveException
350    {
351  12 VersionConstraint constraint = extensionDependency.getVersionConstraint();
352   
353  12 try {
354  12 Version version;
355  12 if (!constraint.getRanges().isEmpty()) {
356  0 ExtensionVersions versions =
357    resolveExtensionVersions(extensionDependency.getId(), constraint, 0, -1, false);
358  0 if (versions.getExtensionVersionSummaries().isEmpty()) {
359  0 throw new ExtensionNotFoundException(
360    "Can't find any version with id [" + extensionDependency.getId()
361    + "] matching version constraint [" + extensionDependency.getVersionConstraint() + "]");
362    }
363   
364  0 version = new DefaultVersion(versions.getExtensionVersionSummaries()
365    .get(versions.getExtensionVersionSummaries().size() - 1).getVersion());
366    } else {
367  12 version = constraint.getVersion();
368    }
369   
370  12 return new XWikiExtension(this,
371    (ExtensionVersion) getRESTObject(this.extensionVersionUriBuider, extensionDependency.getId(), version),
372    this.licenseManager, this.factory);
373    } catch (ResourceNotFoundException e) {
374  6 throw new ExtensionNotFoundException(
375    "Could not find any extension to match dependency [" + extensionDependency + "]", e);
376    } catch (Exception e) {
377  0 throw new ResolveException(
378    "Failed to create extension object for extension dependency [" + extensionDependency + "]", e);
379    }
380    }
381   
 
382  3 toggle private ExtensionVersions resolveExtensionVersions(String id, VersionConstraint constraint, int offset, int nb,
383    boolean requireTotalHits) throws ResolveException
384    {
385  3 UriBuilder builder = this.extensionVersionsUriBuider.clone();
386   
387  3 builder.queryParam(Resources.QPARAM_LIST_REQUIRETOTALHITS, requireTotalHits);
388  3 builder.queryParam(Resources.QPARAM_LIST_START, offset);
389  3 builder.queryParam(Resources.QPARAM_LIST_NUMBER, nb);
390  3 if (constraint != null) {
391  0 builder.queryParam(Resources.QPARAM_VERSIONS_RANGES, constraint.getValue());
392    }
393   
394  3 try {
395  3 return (ExtensionVersions) getRESTObject(builder, id);
396    } catch (ResourceNotFoundException e) {
397  1 throw new ExtensionNotFoundException("Could not find extension with id [" + id + "]", e);
398    } catch (Exception e) {
399  1 throw new ResolveException("Failed to find version for extension id [" + id + "]", e);
400    }
401    }
402   
 
403  3 toggle @Override
404    public IterableResult<Version> resolveVersions(String id, int offset, int nb) throws ResolveException
405    {
406  3 ExtensionVersions restExtensions = resolveExtensionVersions(id, null, offset, nb, true);
407   
408  1 List<Version> versions = new ArrayList<Version>(restExtensions.getExtensionVersionSummaries().size());
409  1 for (ExtensionVersionSummary restExtension : restExtensions.getExtensionVersionSummaries()) {
410  2 versions.add(new DefaultVersion(restExtension.getVersion()));
411    }
412   
413  1 return new CollectionIterableResult<Version>(restExtensions.getTotalHits(), restExtensions.getOffset(),
414    versions);
415    }
416   
417    // Searchable
418   
 
419  0 toggle @Override
420    public IterableResult<Extension> search(String pattern, int offset, int nb) throws SearchException
421    {
422  0 UriBuilder builder = this.searchUriBuider.clone();
423   
424  0 builder.queryParam(Resources.QPARAM_LIST_START, offset);
425  0 builder.queryParam(Resources.QPARAM_LIST_NUMBER, nb);
426  0 if (pattern != null) {
427  0 builder.queryParam(Resources.QPARAM_SEARCH_QUERY, pattern);
428    }
429   
430  0 ExtensionsSearchResult restExtensions;
431  0 try {
432  0 restExtensions = (ExtensionsSearchResult) getRESTObject(builder);
433    } catch (Exception e) {
434  0 throw new SearchException("Failed to search extensions based on pattern [" + pattern + "]", e);
435    }
436   
437  0 List<Extension> extensions = new ArrayList<Extension>(restExtensions.getExtensions().size());
438  0 for (ExtensionVersion restExtension : restExtensions.getExtensions()) {
439  0 extensions.add(new XWikiExtension(this, restExtension, this.licenseManager, this.factory));
440    }
441   
442  0 return new CollectionIterableResult<Extension>(restExtensions.getTotalHits(), restExtensions.getOffset(),
443    extensions);
444    }
445   
 
446  28 toggle @Override
447    public IterableResult<Extension> search(org.xwiki.extension.repository.search.ExtensionQuery query)
448    throws SearchException
449    {
450  28 if (getRepositoryVersion().equals(VERSION10)) {
451  0 return search(query.getQuery(), query.getOffset(), query.getLimit());
452    }
453   
454  28 UriBuilder builder = this.searchUriBuider.clone();
455   
456  28 ExtensionQuery restQuery = EXTENSION_OBJECT_FACTORY.createExtensionQuery();
457   
458  28 restQuery.setQuery(query.getQuery());
459  28 restQuery.setOffset(query.getOffset());
460  28 restQuery.setLimit(query.getLimit());
461  28 for (org.xwiki.extension.repository.search.ExtensionQuery.Filter filter : query.getFilters()) {
462  13 Filter restFilter = EXTENSION_OBJECT_FACTORY.createFilter();
463  13 restFilter.setField(filter.getField());
464  13 restFilter.setValueString(filter.getValue().toString());
465  13 restFilter.setComparison(COMPARISON.fromValue(filter.getComparison().name()));
466  13 restQuery.getFilters().add(restFilter);
467    }
468  28 for (org.xwiki.extension.repository.search.ExtensionQuery.SortClause sortClause : query.getSortClauses()) {
469  0 SortClause restSortClause = EXTENSION_OBJECT_FACTORY.createSortClause();
470  0 restSortClause.setField(sortClause.getField());
471  0 restSortClause.setOrder(ORDER.fromValue(sortClause.getOrder().name()));
472  0 restQuery.getSortClauses().add(restSortClause);
473    }
474   
475  28 ExtensionsSearchResult restExtensions;
476  28 try {
477  28 restExtensions = (ExtensionsSearchResult) postRESTObject(builder, restQuery);
478    } catch (Exception e) {
479  0 throw new SearchException("Failed to search extensions based on pattern [" + query.getQuery() + "]", e);
480    }
481   
482  28 List<Extension> extensions = new ArrayList<Extension>(restExtensions.getExtensions().size());
483  28 for (ExtensionVersion restExtension : restExtensions.getExtensions()) {
484  11 extensions.add(new XWikiExtension(this, restExtension, this.licenseManager, this.factory));
485    }
486   
487  28 return new CollectionIterableResult<Extension>(restExtensions.getTotalHits(), restExtensions.getOffset(),
488    extensions);
489    }
490   
491    // Ratable
492   
 
493  0 toggle @Override
494    public ExtensionRating getRating(ExtensionId extensionId) throws ResolveException
495    {
496  0 return getRating(extensionId.getId(), extensionId.getVersion());
497    }
498   
 
499  0 toggle @Override
500    public ExtensionRating getRating(String extensionId, Version extensionVersion) throws ResolveException
501    {
502  0 return getRating(extensionId, extensionVersion.getValue());
503    }
504   
 
505  0 toggle @Override
506    public ExtensionRating getRating(String extensionId, String extensionVersion) throws ResolveException
507    {
508  0 try {
509  0 return new XWikiExtension(this,
510    (ExtensionVersion) getRESTObject(this.extensionVersionUriBuider, extensionId, extensionVersion),
511    this.licenseManager, this.factory).getRating();
512    } catch (ResourceNotFoundException e) {
513  0 throw new ExtensionNotFoundException(
514    "Could not find extension with id [" + extensionId + "] and version [" + extensionVersion + "]", e);
515    } catch (Exception e) {
516  0 throw new ResolveException(
517    "Failed to create extension object for extension [" + extensionId + ":" + extensionVersion + "]", e);
518    }
519    }
520    }