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

File RepositoryManager.java

 

Coverage histogram

../../../../img/srcFileCovDistChart8.png
54% of files have more coverage

Code metrics

182
378
32
1
1,107
785
140
0.37
11.81
32
4.38

Classes

Class Line # Actions
RepositoryManager 103 378 0% 140 125
0.788851478.9%
 

Contributing tests

No tests hitting this source file were found.

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.repository.internal;
21   
22    import java.util.ArrayList;
23    import java.util.Arrays;
24    import java.util.Collection;
25    import java.util.Collections;
26    import java.util.HashSet;
27    import java.util.List;
28    import java.util.Map;
29    import java.util.Set;
30    import java.util.TreeMap;
31    import java.util.regex.Matcher;
32    import java.util.regex.Pattern;
33   
34    import javax.inject.Inject;
35    import javax.inject.Named;
36    import javax.inject.Provider;
37    import javax.inject.Singleton;
38   
39    import org.apache.commons.lang3.ObjectUtils;
40    import org.apache.commons.lang3.StringUtils;
41    import org.slf4j.Logger;
42    import org.xwiki.cache.Cache;
43    import org.xwiki.cache.CacheException;
44    import org.xwiki.cache.CacheManager;
45    import org.xwiki.cache.config.CacheConfiguration;
46    import org.xwiki.cache.eviction.LRUEvictionConfiguration;
47    import org.xwiki.component.annotation.Component;
48    import org.xwiki.component.manager.ComponentLifecycleException;
49    import org.xwiki.component.phase.Disposable;
50    import org.xwiki.component.phase.Initializable;
51    import org.xwiki.component.phase.InitializationException;
52    import org.xwiki.extension.DefaultExtensionDependency;
53    import org.xwiki.extension.Extension;
54    import org.xwiki.extension.ExtensionAuthor;
55    import org.xwiki.extension.ExtensionDependency;
56    import org.xwiki.extension.ExtensionId;
57    import org.xwiki.extension.ExtensionIssueManagement;
58    import org.xwiki.extension.ExtensionNotFoundException;
59    import org.xwiki.extension.ExtensionScm;
60    import org.xwiki.extension.ResolveException;
61    import org.xwiki.extension.internal.ExtensionFactory;
62    import org.xwiki.extension.internal.converter.ExtensionIdConverter;
63    import org.xwiki.extension.repository.ExtensionRepository;
64    import org.xwiki.extension.repository.result.IterableResult;
65    import org.xwiki.extension.version.Version;
66    import org.xwiki.extension.version.Version.Type;
67    import org.xwiki.extension.version.internal.DefaultVersion;
68    import org.xwiki.extension.version.internal.DefaultVersionConstraint;
69    import org.xwiki.model.EntityType;
70    import org.xwiki.model.reference.AttachmentReference;
71    import org.xwiki.model.reference.AttachmentReferenceResolver;
72    import org.xwiki.model.reference.DocumentReference;
73    import org.xwiki.model.reference.DocumentReferenceResolver;
74    import org.xwiki.model.reference.EntityReference;
75    import org.xwiki.model.reference.EntityReferenceSerializer;
76    import org.xwiki.model.reference.RegexEntityReference;
77    import org.xwiki.model.reference.WikiReference;
78    import org.xwiki.observation.EventListener;
79    import org.xwiki.observation.ObservationManager;
80    import org.xwiki.observation.event.Event;
81    import org.xwiki.query.Query;
82    import org.xwiki.query.QueryException;
83    import org.xwiki.query.QueryManager;
84    import org.xwiki.rendering.listener.reference.AttachmentResourceReference;
85    import org.xwiki.rendering.listener.reference.ResourceReference;
86    import org.xwiki.rendering.listener.reference.ResourceType;
87    import org.xwiki.rendering.parser.ResourceReferenceParser;
88    import org.xwiki.rendering.renderer.reference.ResourceReferenceTypeSerializer;
89    import org.xwiki.repository.internal.reference.ExtensionResourceReference;
90   
91    import com.xpn.xwiki.XWikiContext;
92    import com.xpn.xwiki.XWikiException;
93    import com.xpn.xwiki.doc.XWikiAttachment;
94    import com.xpn.xwiki.doc.XWikiDocument;
95    import com.xpn.xwiki.internal.event.XObjectPropertyAddedEvent;
96    import com.xpn.xwiki.internal.event.XObjectPropertyDeletedEvent;
97    import com.xpn.xwiki.internal.event.XObjectPropertyUpdatedEvent;
98    import com.xpn.xwiki.objects.BaseObject;
99    import com.xpn.xwiki.objects.BaseProperty;
100   
101    @Component(roles = RepositoryManager.class)
102    @Singleton
 
103    public class RepositoryManager implements Initializable, Disposable
104    {
105    /**
106    * The reference to match property {@link XWikiRepositoryModel#PROP_EXTENSION_ID} of class
107    * {@link XWikiRepositoryModel#EXTENSION_CLASSNAME} on whatever wiki.
108    */
109    private static final RegexEntityReference XWIKIPREFERENCE_PROPERTY_REFERENCE =
110    new RegexEntityReference(Pattern.compile(XWikiRepositoryModel.PROP_DEPENDENCY_ID), EntityType.OBJECT_PROPERTY,
111    new RegexEntityReference(Pattern.compile(".*:" + XWikiRepositoryModel.EXTENSION_CLASSNAME + "\\[\\d*\\]"),
112    EntityType.OBJECT));
113   
114    private static final List<Event> EVENTS =
115    Arrays.<Event>asList(new XObjectPropertyAddedEvent(XWIKIPREFERENCE_PROPERTY_REFERENCE),
116    new XObjectPropertyDeletedEvent(XWIKIPREFERENCE_PROPERTY_REFERENCE),
117    new XObjectPropertyUpdatedEvent(XWIKIPREFERENCE_PROPERTY_REFERENCE));
118   
119    private static final Pattern PATTERN_NEWLINE = Pattern.compile("[\n\r]");
120   
121    /**
122    * Get the reference of the class in the current wiki.
123    */
124    @Inject
125    @Named("default")
126    private DocumentReferenceResolver<EntityReference> referenceResolver;
127   
128    @Inject
129    @Named("current")
130    private DocumentReferenceResolver<EntityReference> currentResolver;
131   
132    @Inject
133    @Named("current")
134    private DocumentReferenceResolver<String> currentStringResolver;
135   
136    /**
137    * Used to validate download reference.
138    */
139    @Inject
140    @Named("link")
141    private ResourceReferenceParser resourceReferenceParser;
142   
143    @Inject
144    private ResourceReferenceTypeSerializer resourceReferenceSerializer;
145   
146    @Inject
147    private EntityReferenceSerializer<String> entityReferenceSerializer;
148   
149    /**
150    * Used to validate download reference.
151    */
152    @Inject
153    private AttachmentReferenceResolver<String> attachmentResolver;
154   
155    @Inject
156    private QueryManager queryManager;
157   
158    @Inject
159    private Provider<XWikiContext> xcontextProvider;
160   
161    @Inject
162    private RepositoryConfiguration configuration;
163   
164    @Inject
165    private CacheManager cacheManager;
166   
167    @Inject
168    private ObservationManager observation;
169   
170    @Inject
171    private ExtensionFactory extensionFactory;
172   
173    @Inject
174    private Logger logger;
175   
176    /**
177    * Link extension id to document reference. The tabe contains null if the id link to no extension.
178    */
179    private Cache<DocumentReference[]> documentReferenceCache;
180   
181    private EventListener listener = new EventListener()
182    {
 
183  31 toggle @Override
184    public void onEvent(Event event, Object source, Object data)
185    {
186    // TODO: Improve a bit by removing only what's changed
187  31 documentReferenceCache.removeAll();
188    }
189   
 
190  18 toggle @Override
191    public String getName()
192    {
193  18 return "repository.DefaultRepositoryManager";
194    }
195   
 
196  2 toggle @Override
197    public List<Event> getEvents()
198    {
199  2 return EVENTS;
200    }
201    };
202   
 
203  2 toggle @Override
204    public void initialize() throws InitializationException
205    {
206    // Init cache
207  2 CacheConfiguration cacheConfiguration = new CacheConfiguration();
208  2 cacheConfiguration.setConfigurationId("repository.extensionid.documentreference");
209  2 LRUEvictionConfiguration lru = new LRUEvictionConfiguration();
210  2 lru.setMaxEntries(10000);
211  2 cacheConfiguration.put(LRUEvictionConfiguration.CONFIGURATIONID, lru);
212   
213  2 try {
214  2 this.documentReferenceCache = this.cacheManager.createNewCache(cacheConfiguration);
215    } catch (CacheException e) {
216  0 throw new InitializationException("Failed to initialize cache", e);
217    }
218   
219    // Listen to modifications
220  2 this.observation.addListener(listener);
221    }
222   
 
223  2 toggle @Override
224    public void dispose() throws ComponentLifecycleException
225    {
226  2 this.observation.removeListener(listener.getName());
227    }
228   
 
229  0 toggle public <T> XWikiDocument getDocument(T[] data) throws XWikiException
230    {
231  0 return getDocument((String) data[0]);
232    }
233   
 
234  0 toggle public XWikiDocument getDocument(String fullName) throws XWikiException
235    {
236  0 XWikiContext xcontext = this.xcontextProvider.get();
237   
238  0 return xcontext.getWiki().getDocument(this.currentStringResolver.resolve(fullName), xcontext);
239    }
240   
 
241  92 toggle public XWikiDocument getExistingExtensionDocumentById(String extensionId) throws QueryException, XWikiException
242    {
243  92 XWikiContext xcontext = this.xcontextProvider.get();
244   
245  92 DocumentReference[] cachedDocumentReference = this.documentReferenceCache.get(extensionId);
246   
247  92 if (cachedDocumentReference == null) {
248  23 Query query = this.queryManager.createQuery(
249    "select doc.fullName from Document doc, doc.object(" + XWikiRepositoryModel.EXTENSION_CLASSNAME
250    + ") as extension where extension." + XWikiRepositoryModel.PROP_EXTENSION_ID + " = :extensionId",
251    Query.XWQL);
252   
253  23 query.bindValue("extensionId", extensionId);
254   
255  23 List<String> documentNames = query.execute();
256   
257  23 if (!documentNames.isEmpty()) {
258  15 cachedDocumentReference =
259    new DocumentReference[] { this.currentStringResolver.resolve(documentNames.get(0)) };
260    } else {
261  8 cachedDocumentReference = new DocumentReference[1];
262    }
263   
264  23 this.documentReferenceCache.set(extensionId, cachedDocumentReference);
265    }
266   
267  92 return cachedDocumentReference[0] != null ? xcontext.getWiki().getDocument(cachedDocumentReference[0], xcontext)
268    : null;
269    }
270   
 
271  6 toggle public BaseObject getExtensionVersion(XWikiDocument document, Version version)
272    {
273  6 List<BaseObject> objects = document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE);
274  6 if (objects != null) {
275  5 for (BaseObject versionObject : objects) {
276  9 if (versionObject != null) {
277  9 String versionString =
278    getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_VERSION, (String) null);
279   
280  9 if (StringUtils.isNotEmpty(versionString) && version.equals(new DefaultVersion(versionString))) {
281  3 return versionObject;
282    }
283    }
284    }
285    }
286   
287  3 return null;
288    }
289   
 
290  45 toggle public void validateExtension(XWikiDocument document, boolean save) throws XWikiException
291    {
292  45 BaseObject extensionObject = document.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE);
293   
294  45 if (extensionObject == null) {
295    // Not an extension
296  0 return;
297    }
298   
299  45 boolean needSave = false;
300   
301  45 XWikiContext xcontext = this.xcontextProvider.get();
302   
303    // Update last version field
304  45 String lastVersion = findLastVersion(document);
305   
306  45 if (lastVersion != null && !StringUtils.equals(lastVersion,
307    getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_LASTVERSION, (String) null))) {
308  21 BaseObject extensionObjectToSave = document.getXObject(extensionObject.getReference());
309  21 extensionObjectToSave.set(XWikiRepositoryModel.PROP_EXTENSION_LASTVERSION, lastVersion, xcontext);
310   
311  21 needSave = true;
312    }
313   
314    // Update valid extension field
315   
316  45 boolean valid = isValid(document, extensionObject, xcontext);
317   
318  45 if (valid) {
319  24 this.logger.debug("The extension in the document [{}] is not valid", document.getDocumentReference());
320    } else {
321  21 this.logger.debug("The extension in the document [{}] is valid", document.getDocumentReference());
322    }
323   
324  45 int currentValue = getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_VALIDEXTENSION, 0);
325   
326  45 if ((currentValue == 1) != valid) {
327  17 BaseObject extensionObjectToSave = document.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE);
328  17 extensionObjectToSave.set(XWikiRepositoryModel.PROP_EXTENSION_VALIDEXTENSION, valid ? "1" : "0", xcontext);
329   
330  17 needSave = true;
331    }
332   
333    // Save document
334   
335  45 if (save && needSave) {
336  0 xcontext.getWiki().saveDocument(document, "Validated extension", true, xcontext);
337    }
338    }
339   
340    /**
341    * Compare all version located in a document to find the last one.
342    *
343    * @param document the extension document
344    * @return the last version
345    */
 
346  45 toggle private String findLastVersion(XWikiDocument document)
347    {
348  45 DocumentReference versionClassReference =
349    getClassReference(document, XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE);
350   
351  45 List<BaseObject> versionObjects = document.getXObjects(versionClassReference);
352   
353  45 DefaultVersion lastVersion = null;
354  45 if (versionObjects != null) {
355  44 for (BaseObject versionObject : versionObjects) {
356  60 if (versionObject != null) {
357  60 String versionString = getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_VERSION);
358  60 if (versionString != null) {
359  60 DefaultVersion version = new DefaultVersion(versionString);
360  60 if (lastVersion == null || version.compareTo(lastVersion) > 0) {
361  55 lastVersion = version;
362    }
363    }
364    }
365    }
366    }
367   
368  45 return lastVersion != null ? lastVersion.getValue() : null;
369    }
370   
 
371  45 toggle private DocumentReference getClassReference(XWikiDocument document, EntityReference localReference)
372    {
373  45 return this.referenceResolver.resolve(localReference, document.getDocumentReference().getWikiReference());
374    }
375   
376    /**
377    * @param document the extension document
378    * @param extensionObject the extension object
379    * @param context the XWiki context
380    * @return true if the extension is valid from Extension Manager point of view
381    * @throws XWikiException unknown issue when manipulating the model
382    */
 
383  45 toggle private boolean isValid(XWikiDocument document, BaseObject extensionObject, XWikiContext context)
384    throws XWikiException
385    {
386  45 String extensionId = getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ID);
387  45 boolean valid = !StringUtils.isBlank(extensionId);
388  45 if (valid) {
389    // Type
390  45 String type = getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE);
391   
392  45 valid = this.configuration.isValidType(type);
393   
394  45 if (valid) {
395    // Versions
396  45 valid = false;
397  45 List<BaseObject> extensionVersions =
398    document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE);
399  45 if (extensionVersions != null) {
400  44 for (BaseObject extensionVersionObject : extensionVersions) {
401  53 if (extensionVersionObject != null) {
402  53 valid = isVersionValid(document, extensionVersionObject, context);
403   
404  53 if (!valid) {
405  20 return false;
406    }
407    }
408    }
409    }
410    }
411    }
412   
413  25 return valid;
414    }
415   
 
416  53 toggle private boolean isVersionValid(XWikiDocument document, BaseObject extensionVersionObject, XWikiContext context)
417    {
418    // Has a version
419  53 String extensionVersion = getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_VERSION);
420  53 if (StringUtils.isBlank(extensionVersion)) {
421  0 this.logger.debug("No actual version provided for object [{}({})]",
422    XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, extensionVersionObject.getNumber());
423   
424  0 return false;
425    }
426   
427  53 boolean valid;
428   
429  53 ResourceReference resourceReference = getDownloadReference(document, extensionVersionObject);
430   
431  53 if (resourceReference != null) {
432  33 if (ResourceType.ATTACHMENT.equals(resourceReference.getType())) {
433  28 AttachmentReference attachmentReference =
434    this.attachmentResolver.resolve(resourceReference.getReference(), document.getDocumentReference());
435   
436  28 XWikiDocument attachmentDocument;
437  28 try {
438  28 attachmentDocument =
439    context.getWiki().getDocument(attachmentReference.getDocumentReference(), context);
440   
441  28 valid = attachmentDocument.getAttachment(attachmentReference.getName()) != null;
442    } catch (XWikiException e) {
443  0 this.logger.error("Failed to get document [{}]", attachmentReference.getDocumentReference(), e);
444   
445  0 valid = false;
446    }
447   
448  28 if (!valid) {
449  0 this.logger.debug("Attachment [{}] does not exists", attachmentReference);
450    }
451  5 } else if (ResourceType.URL.equals(resourceReference.getType())
452    || ExtensionResourceReference.TYPE.equals(resourceReference.getType())) {
453  5 valid = true;
454    } else {
455  0 valid = false;
456   
457  0 this.logger.debug("Unknown resource type [{}]", resourceReference.getType());
458    }
459    } else {
460  20 valid = false;
461   
462  20 this.logger.debug("No actual download provided for object [{}({})]",
463    XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, extensionVersionObject.getNumber());
464    }
465   
466  53 return valid;
467    }
468   
 
469  0 toggle public void validateExtensions() throws QueryException, XWikiException
470    {
471  0 Query query = this.queryManager.createQuery("select doc.fullName from Document doc, doc.object("
472    + XWikiRepositoryModel.EXTENSION_CLASSNAME + ") as extension", Query.XWQL);
473   
474  0 for (Object[] documentName : query.<Object[]>execute()) {
475  0 validateExtension(getDocument(documentName), true);
476    }
477    }
478   
 
479  73 toggle public ResourceReference getDownloadReference(XWikiDocument document, BaseObject extensionVersionObject)
480    {
481    // Has a version
482  73 String extensionVersion = getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_VERSION);
483   
484    // The download reference seems ok
485  73 String download = getValue(extensionVersionObject, XWikiRepositoryModel.PROP_VERSION_DOWNLOAD);
486   
487  73 ResourceReference resourceReference = null;
488   
489  73 if (StringUtils.isNotEmpty(download)) {
490  17 resourceReference = this.resourceReferenceParser.parse(download);
491    } else {
492  56 BaseObject extensionObject = document.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE);
493  56 String extensionId = getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ID);
494   
495  56 String fileName = extensionId + '-' + extensionVersion + '.'
496    + getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE);
497   
498  56 XWikiAttachment attachment = document.getAttachment(fileName);
499  56 if (attachment == null) {
500    // Try without the prefix
501  20 int index = fileName.indexOf(':');
502  20 if (index != -1 && index < extensionId.length()) {
503  0 fileName = fileName.substring(index + 1);
504   
505  0 attachment = document.getAttachment(fileName);
506    }
507    }
508   
509  56 if (attachment != null) {
510  36 resourceReference = new AttachmentResourceReference(
511    this.entityReferenceSerializer.serialize(attachment.getReference()));
512    }
513    }
514   
515  73 return resourceReference;
516    }
517   
 
518  4 toggle private Version getVersions(String extensionId, ExtensionRepository repository, Type type,
519    Map<Version, String> versions) throws ResolveException
520    {
521  4 Version lastVersion = null;
522   
523  4 IterableResult<Version> versionsIterable = repository.resolveVersions(extensionId, 0, -1);
524   
525  4 for (Version version : versionsIterable) {
526  6 if (type == null || version.getType() == type) {
527  6 if (!versions.containsKey(version)) {
528  6 versions.put(version, extensionId);
529    }
530    }
531   
532  6 lastVersion = version;
533    }
534   
535  4 return lastVersion;
536    }
537   
 
538  2 toggle public DocumentReference importExtension(String extensionId, ExtensionRepository repository, Type type)
539    throws QueryException, XWikiException, ResolveException
540    {
541  2 TreeMap<Version, String> versions = new TreeMap<Version, String>();
542   
543  2 Version lastVersion = getVersions(extensionId, repository, type, versions);
544   
545  2 if (lastVersion == null) {
546  0 throw new ExtensionNotFoundException(
547    "Can't find any version for the extension [" + extensionId + "] on repository [" + repository + "]");
548  2 } else if (versions.isEmpty()) {
549    // If no valid version import the last version
550  0 versions.put(lastVersion, extensionId);
551    } else {
552    // Select the last valid version
553  2 lastVersion = versions.lastKey();
554    }
555   
556  2 Extension extension = repository.resolve(new ExtensionId(extensionId, lastVersion));
557   
558    // Get former ids versions
559  2 Collection<ExtensionId> features = extension.getExtensionFeatures();
560   
561  2 for (ExtensionId feature : features) {
562  2 try {
563  2 getVersions(feature.getId(), repository, type, versions);
564    } catch (ResolveException e) {
565    // Ignore
566    }
567    }
568   
569  2 XWikiContext xcontext = this.xcontextProvider.get();
570   
571  2 boolean needSave = false;
572   
573  2 XWikiDocument document = getExistingExtensionDocumentById(extensionId);
574   
575  2 if (document == null) {
576    // Create document
577  1 document = xcontext.getWiki().getDocument(
578    new DocumentReference(xcontext.getWikiId(), Arrays.asList("Extension", extension.getName()), "WebHome"),
579    xcontext);
580   
581  1 for (int i = 1; !document.isNew(); ++i) {
582  0 document = xcontext.getWiki().getDocument(new DocumentReference(xcontext.getWikiId(),
583    Arrays.asList("Extension", extension.getName() + ' ' + i), "WebHome"), xcontext);
584    }
585   
586  1 document.readFromTemplate(this.currentResolver.resolve(XWikiRepositoryModel.EXTENSION_TEMPLATEREFERENCE),
587    xcontext);
588   
589  1 needSave = true;
590    }
591   
592    // Update document
593   
594  2 BaseObject extensionObject = document.getXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE);
595  2 if (extensionObject == null) {
596  0 extensionObject = document.newXObject(XWikiRepositoryModel.EXTENSION_CLASSREFERENCE, xcontext);
597  0 needSave = true;
598    }
599   
600  2 if (!StringUtils.equals(extensionId,
601    getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ID, (String) null))) {
602  1 extensionObject.set(XWikiRepositoryModel.PROP_EXTENSION_ID, extensionId, xcontext);
603  1 needSave = true;
604    }
605   
606    // Update extension informations
607   
608  2 needSave |= updateExtension(extension, extensionObject, xcontext);
609   
610    // Remove unexisting versions
611   
612  2 Set<String> validVersions = new HashSet<String>();
613   
614  2 List<BaseObject> versionObjects = document.getXObjects(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE);
615  2 if (versionObjects != null) {
616  1 for (BaseObject versionObject : versionObjects) {
617  3 if (versionObject != null) {
618  3 String version = getValue(versionObject, XWikiRepositoryModel.PROP_VERSION_VERSION);
619   
620  3 if (StringUtils.isBlank(version)) {
621    // Empty version, it's invalid
622  0 document.removeXObject(versionObject);
623  0 needSave = true;
624    } else {
625  3 if (!versions.containsKey(new DefaultVersion(version))) {
626    // The version does not exist on remote repository
627  0 if (!isVersionValid(document, versionObject, xcontext)) {
628    // The version is invalid, removing it to not make the whole extension invalid
629  0 document.removeXObject(versionObject);
630  0 needSave = true;
631    } else {
632    // The version is valid, lets keep it
633  0 validVersions.add(version);
634    }
635    } else {
636    // This version exist on remote repository
637  3 validVersions.add(version);
638    }
639    }
640    }
641    }
642    }
643  2 List<BaseObject> dependencyObjects =
644    document.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE);
645  2 if (dependencyObjects != null) {
646  1 for (BaseObject dependencyObject : dependencyObjects) {
647  3 if (dependencyObject != null) {
648  3 String version = getValue(dependencyObject, XWikiRepositoryModel.PROP_DEPENDENCY_EXTENSIONVERSION);
649   
650  3 if (!validVersions.contains(version)) {
651    // The version is invalid, removing it to not make the whole extension invalid
652  0 document.removeXObject(dependencyObject);
653  0 needSave = true;
654    }
655    }
656    }
657    }
658   
659    // Update versions
660   
661  2 for (Map.Entry<Version, String> entry : versions.entrySet()) {
662  6 Version version = entry.getKey();
663  6 String id = entry.getValue();
664   
665  6 try {
666  6 Extension versionExtension;
667  6 if (version.equals(extension.getId().getVersion())) {
668  2 versionExtension = extension;
669    } else {
670  4 versionExtension = repository.resolve(new ExtensionId(id, version));
671    }
672   
673    // Update version related informations
674  6 needSave |= updateExtensionVersion(document, versionExtension);
675    } catch (Exception e) {
676  0 this.logger.error("Failed to resolve extension with id [" + id + "] and version [" + version
677    + "] on repository [" + repository + "]", e);
678    }
679    }
680   
681    // Proxy marker
682   
683  2 BaseObject extensionProxyObject = document.getXObject(XWikiRepositoryModel.EXTENSIONPROXY_CLASSREFERENCE);
684  2 if (extensionProxyObject == null) {
685  1 extensionProxyObject = document.newXObject(XWikiRepositoryModel.EXTENSIONPROXY_CLASSREFERENCE, xcontext);
686  1 extensionProxyObject.setIntValue(XWikiRepositoryModel.PROP_PROXY_AUTOUPDATE, 1);
687  1 needSave = true;
688    }
689   
690  2 needSave |= update(extensionProxyObject, XWikiRepositoryModel.PROP_PROXY_REPOSITORYID,
691    repository.getDescriptor().getId());
692  2 needSave |= update(extensionProxyObject, XWikiRepositoryModel.PROP_PROXY_REPOSITORYTYPE,
693    repository.getDescriptor().getType());
694  2 needSave |= update(extensionProxyObject, XWikiRepositoryModel.PROP_PROXY_REPOSITORYURI,
695    repository.getDescriptor().getURI().toString());
696   
697  2 if (needSave) {
698  1 document.setAuthorReference(xcontext.getUserReference());
699  1 if (document.isNew()) {
700  1 document.setContentAuthorReference(xcontext.getUserReference());
701  1 document.setCreatorReference(xcontext.getUserReference());
702    }
703   
704  1 xcontext.getWiki().saveDocument(document,
705    "Imported extension [" + extensionId + "] from repository [" + repository.getDescriptor() + "]", true,
706    xcontext);
707    }
708   
709  2 return document.getDocumentReference();
710    }
711   
 
712  2 toggle private boolean updateExtension(Extension extension, BaseObject extensionObject, XWikiContext xcontext)
713    {
714  2 boolean needSave = false;
715   
716    // Update properties
717   
718    // Type
719  2 needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_TYPE, extension.getType());
720   
721    // Name
722  2 needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_NAME, extension.getName());
723   
724    // Summary
725  2 needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_SUMMARY, getSummary(extension));
726   
727    // Category
728  2 if (extension.getCategory() != null) {
729  2 needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_CATEGORY, extension.getCategory());
730    }
731   
732    // Website
733    /*
734    * Don't import website since most of the time we want the new page to be the extension entry point needSave |=
735    * update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_WEBSITE, extension.getWebSite());
736    */
737   
738    // Description
739  2 if (StringUtils
740    .isEmpty(getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION, (String) null))) {
741  1 extensionObject.set(XWikiRepositoryModel.PROP_EXTENSION_DESCRIPTION, getDescription(extension), xcontext);
742  1 needSave = true;
743    }
744   
745    // License
746  2 if (!extension.getLicenses().isEmpty()
747    && !StringUtils.equals(extension.getLicenses().iterator().next().getName(),
748    getValue(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_LICENSENAME, (String) null))) {
749  0 extensionObject.set(XWikiRepositoryModel.PROP_EXTENSION_LICENSENAME,
750    extension.getLicenses().iterator().next().getName(), xcontext);
751  0 needSave = true;
752    }
753   
754    // SCM
755  2 ExtensionScm scm = extension.getScm();
756  2 if (scm != null) {
757  0 if (scm.getUrl() != null) {
758  0 needSave |=
759    update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMURL, scm.getUrl().toString());
760    }
761  0 if (scm.getConnection() != null) {
762  0 needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMCONNECTION,
763    scm.getConnection().toString());
764    }
765  0 if (scm.getDeveloperConnection() != null) {
766  0 needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_SCMDEVCONNECTION,
767    scm.getDeveloperConnection().toString());
768    }
769    }
770   
771    // Issue Management
772  2 ExtensionIssueManagement issueManagement = extension.getIssueManagement();
773  2 if (issueManagement != null) {
774  0 if (issueManagement.getSystem() != null) {
775  0 needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_SYSTEM,
776    issueManagement.getSystem());
777    }
778  0 if (issueManagement.getURL() != null) {
779  0 needSave |= update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ISSUEMANAGEMENT_URL,
780    issueManagement.getURL());
781    }
782    }
783   
784    // Authors
785  2 needSave |= updateAuthors(extensionObject, extension.getAuthors());
786   
787    // Features
788  2 needSave |= updateFeatures(XWikiRepositoryModel.PROP_EXTENSION_FEATURES, extensionObject,
789    extension.getExtensionFeatures());
790   
791    // Allowed namespaces
792  2 needSave |= updateCollection(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_ALLOWEDNAMESPACES,
793    extension.getAllowedNamespaces(), xcontext);
794   
795    // Properties
796  2 needSave |= updateProperties(extensionObject, extension.getProperties());
797   
798  2 return needSave;
799    }
800   
 
801  2 toggle private String getSummary(Extension extension)
802    {
803  2 String summary = extension.getSummary();
804  2 if (summary != null) {
805    // Extract first not blank line
806  2 Matcher matcher = PATTERN_NEWLINE.matcher(summary);
807  2 int previousIndex = 0;
808  2 while (matcher.find()) {
809  2 int index = matcher.start();
810  2 String str = summary.substring(previousIndex, index);
811  2 if (StringUtils.isNotBlank(str)) {
812  2 summary = str.trim();
813  2 break;
814    }
815    }
816    // truncated to 255 in case it's too long, TODO: should probably be handled at a lower level)
817  2 if (summary.length() > 255) {
818  0 summary = summary.substring(0, 255);
819    }
820    } else {
821  0 summary = "";
822    }
823   
824  2 return summary;
825    }
826   
 
827  1 toggle private String getDescription(Extension extension)
828    {
829  1 String description;
830   
831  1 if (extension.getDescription() != null) {
832  0 description = extension.getDescription();
833  1 } else if (extension.getSummary() != null) {
834  1 description = extension.getSummary();
835    } else {
836  0 description = "";
837    }
838   
839  1 return description;
840    }
841   
 
842  2 toggle private boolean updateAuthors(BaseObject extensionObject, Collection<ExtensionAuthor> authors)
843    {
844  2 List<String> authorIds = new ArrayList<String>(authors.size());
845   
846  2 for (ExtensionAuthor author : authors) {
847  2 authorIds.add(resolveAuthorId(author.getName()));
848    }
849   
850  2 return update(extensionObject, XWikiRepositoryModel.PROP_EXTENSION_AUTHORS, authorIds);
851    }
852   
 
853  8 toggle private boolean updateFeatures(String fieldName, BaseObject extensionObject, Collection<ExtensionId> features)
854    {
855  8 List<String> featureStrings = new ArrayList<String>(features.size());
856   
857  8 for (ExtensionId feature : features) {
858  6 featureStrings.add(ExtensionIdConverter.toString(feature));
859    }
860   
861  8 return update(extensionObject, fieldName, featureStrings);
862    }
863   
 
864  2 toggle private String resolveAuthorId(String authorName)
865    {
866  2 String[] authorElements = StringUtils.split(authorName, ' ');
867   
868  2 XWikiContext xcontext = this.xcontextProvider.get();
869   
870  2 String authorId = resolveAuthorIdOnWiki(xcontext.getWikiId(), authorName, authorElements, xcontext);
871   
872  2 if (authorId == null && !xcontext.isMainWiki()) {
873  0 authorId = resolveAuthorIdOnWiki(xcontext.getMainXWiki(), authorName, authorElements, xcontext);
874   
875  0 if (authorId != null) {
876  0 authorId = xcontext.getMainXWiki() + ':' + authorId;
877    }
878    }
879   
880  2 return authorId != null ? authorId : authorName;
881    }
882   
 
883  2 toggle private String resolveAuthorIdOnWiki(String wiki, String authorName, String[] authorElements, XWikiContext xcontext)
884    {
885  2 Query query;
886  2 try {
887  2 query = this.queryManager.createQuery("from doc.object(XWiki.XWikiUsers) as user"
888    + " where user.first_name like :userfirstname OR user.last_name like :userlastname", Query.XWQL);
889   
890  2 query.bindValue("userfirstname", '%' + authorElements[0] + '%');
891  2 query.bindValue("userlastname", '%' + authorElements[authorElements.length - 1] + '%');
892   
893  2 query.setWiki(wiki);
894   
895  2 List<String> documentNames = query.execute();
896   
897  2 if (!documentNames.isEmpty()) {
898  2 WikiReference wikiReference = new WikiReference(wiki);
899  2 for (String documentName : documentNames) {
900  2 DocumentReference documentReference =
901    this.currentStringResolver.resolve(documentName, wikiReference);
902   
903  2 String userDisplayName = xcontext.getWiki().getPlainUserName(documentReference, xcontext);
904   
905  2 if (userDisplayName.equals(authorName)) {
906  2 return documentName;
907    }
908    }
909    }
910    } catch (QueryException e) {
911  0 this.logger.error("Failed to resolve extension author [{}]", authorName, e);
912    }
913   
914  0 return null;
915    }
916   
 
917  6 toggle private boolean updateExtensionVersionDependencies(XWikiDocument document, Extension extension)
918    throws XWikiException
919    {
920  6 boolean needSave = false;
921   
922  6 List<ExtensionDependency> dependencies = new ArrayList<>(extension.getDependencies());
923  6 int dependencyIndex = 0;
924   
925    // Clean misplaced or bad existing dependencies associated to this extension version
926  6 List<BaseObject> xobjects = document.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE);
927  6 if (xobjects != null) {
928  5 boolean deleteExistingObjects = false;
929   
930    // Clone since we are going to modify and parse it at the same time
931  5 xobjects = new ArrayList<>(document.getXObjects(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE));
932   
933  17 for (int i = 0; i < xobjects.size(); ++i) {
934  12 BaseObject dependencyObject = xobjects.get(i);
935   
936  12 if (dependencyObject != null) {
937  12 String extensionVersion = getValue(dependencyObject,
938    XWikiRepositoryModel.PROP_DEPENDENCY_EXTENSIONVERSION, (String) null);
939   
940  12 if (StringUtils.isNotEmpty(extensionVersion)
941    && extension.getId().getVersion().equals(new DefaultVersion(extensionVersion))) {
942  3 if (deleteExistingObjects) {
943  0 document.removeXObject(dependencyObject);
944  0 needSave = true;
945    } else {
946  3 String xobjectId = getValue(dependencyObject, XWikiRepositoryModel.PROP_DEPENDENCY_ID);
947  3 String xobjectConstraint =
948    getValue(dependencyObject, XWikiRepositoryModel.PROP_DEPENDENCY_CONSTRAINT);
949  3 List<String> xobjectRepositories = (List<String>) getValue(dependencyObject,
950    XWikiRepositoryModel.PROP_DEPENDENCY_REPOSITORIES);
951   
952  3 DefaultExtensionDependency xobjectDependency = new DefaultExtensionDependency(xobjectId,
953    new DefaultVersionConstraint(xobjectConstraint));
954  3 xobjectDependency.setRepositories(
955    XWikiRepositoryModel.toRepositoryDescriptors(xobjectRepositories, this.extensionFactory));
956   
957  3 if (dependencies.size() > dependencyIndex) {
958  3 ExtensionDependency dependency = dependencies.get(dependencyIndex);
959   
960  3 if (dependency.equals(xobjectDependency)) {
961  3 ++dependencyIndex;
962   
963  3 continue;
964    }
965    }
966   
967  0 deleteExistingObjects = true;
968   
969  0 document.removeXObject(dependencyObject);
970  0 needSave = true;
971    }
972    }
973    }
974    }
975    }
976   
977    // Add missing dependencies
978  6 if (dependencyIndex < dependencies.size()) {
979  3 XWikiContext xcontext = this.xcontextProvider.get();
980  6 for (; dependencyIndex < dependencies.size(); ++dependencyIndex) {
981  3 ExtensionDependency dependency = dependencies.get(dependencyIndex);
982   
983  3 BaseObject dependencyObject =
984    document.newXObject(XWikiRepositoryModel.EXTENSIONDEPENDENCY_CLASSREFERENCE, xcontext);
985   
986  3 dependencyObject.set(XWikiRepositoryModel.PROP_DEPENDENCY_EXTENSIONVERSION,
987    extension.getId().getVersion().getValue(), xcontext);
988  3 dependencyObject.set(XWikiRepositoryModel.PROP_DEPENDENCY_ID, dependency.getId(), xcontext);
989  3 dependencyObject.set(XWikiRepositoryModel.PROP_DEPENDENCY_CONSTRAINT,
990    dependency.getVersionConstraint().getValue(), xcontext);
991  3 dependencyObject.set(XWikiRepositoryModel.PROP_DEPENDENCY_REPOSITORIES,
992    XWikiRepositoryModel.toStringList(dependency.getRepositories()), xcontext);
993   
994  3 needSave = true;
995    }
996    }
997   
998  6 return needSave;
999    }
1000   
 
1001  6 toggle private boolean updateExtensionVersion(XWikiDocument document, Extension extension) throws XWikiException
1002    {
1003  6 boolean needSave;
1004   
1005  6 XWikiContext xcontext = this.xcontextProvider.get();
1006   
1007    // Update version object
1008  6 BaseObject versionObject = getExtensionVersion(document, extension.getId().getVersion());
1009  6 if (versionObject == null) {
1010  3 versionObject = document.newXObject(XWikiRepositoryModel.EXTENSIONVERSION_CLASSREFERENCE, xcontext);
1011   
1012  3 versionObject.set(XWikiRepositoryModel.PROP_VERSION_VERSION, extension.getId().getVersion().getValue(),
1013    xcontext);
1014   
1015  3 needSave = true;
1016    } else {
1017  3 needSave = false;
1018    }
1019   
1020    // Id
1021  6 needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_ID, extension.getId().getId());
1022   
1023    // Features
1024  6 needSave |=
1025    updateFeatures(XWikiRepositoryModel.PROP_VERSION_FEATURES, versionObject, extension.getExtensionFeatures());
1026   
1027    // Repositories
1028  6 List<String> repositories = XWikiRepositoryModel.toStringList(extension.getRepositories());
1029  6 needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_REPOSITORIES, repositories);
1030   
1031    // Update dependencies
1032  6 needSave |= updateExtensionVersionDependencies(document, extension);
1033   
1034    // Download
1035  6 ExtensionResourceReference resource = new ExtensionResourceReference(extension.getId().getId(),
1036    extension.getId().getVersion().getValue(), extension.getRepository().getDescriptor().getId());
1037  6 String download = this.resourceReferenceSerializer.serialize(resource);
1038  6 needSave |= update(versionObject, XWikiRepositoryModel.PROP_VERSION_DOWNLOAD, download);
1039   
1040  6 return needSave;
1041    }
1042   
 
1043  2 toggle protected boolean updateProperties(BaseObject object, Map<String, ?> map)
1044    {
1045  2 List<String> list = new ArrayList<>(map.size());
1046  2 for (Map.Entry<String, ?> entry : map.entrySet()) {
1047  6 String entryString = entry.getKey() + '=' + entry.getValue();
1048  6 if (entryString.length() > 255) {
1049    // Protect against properties too big
1050  0 entryString = entryString.substring(0, 255);
1051    }
1052  6 list.add(entryString);
1053    }
1054   
1055  2 if (ObjectUtils.notEqual(list, getValue(object, XWikiRepositoryModel.PROP_EXTENSION_PROPERTIES))) {
1056  1 object.set(XWikiRepositoryModel.PROP_EXTENSION_PROPERTIES, list, this.xcontextProvider.get());
1057   
1058  1 return true;
1059    }
1060   
1061  1 return false;
1062    }
1063   
 
1064  524 toggle protected <T> T getValue(BaseObject object, String field)
1065    {
1066  524 return getValue(object, field, (T) null);
1067    }
1068   
 
1069  640 toggle protected <T> T getValue(BaseObject object, String field, T def)
1070    {
1071  640 BaseProperty<?> property = (BaseProperty<?>) object.safeget(field);
1072   
1073  640 return property != null && property.getValue() != null ? (T) property.getValue() : def;
1074    }
1075   
 
1076  46 toggle protected boolean update(BaseObject object, String fieldName, Object value)
1077    {
1078    // Make sure collection are lists
1079  46 if (value instanceof Collection) {
1080  18 if (!(value instanceof List)) {
1081  0 value = new ArrayList<>((Collection) value);
1082    }
1083    }
1084   
1085  46 if (ObjectUtils.notEqual(value, getValue(object, fieldName))) {
1086  23 object.set(fieldName, value, this.xcontextProvider.get());
1087   
1088  23 return true;
1089    }
1090   
1091  23 return false;
1092    }
1093   
 
1094  2 toggle private boolean updateCollection(BaseObject extensionObject, String fieldName, Collection<String> allowedNamespaces,
1095    XWikiContext xcontext)
1096    {
1097  2 boolean needSave =
1098  2 update(extensionObject, fieldName, allowedNamespaces != null ? allowedNamespaces : Collections.emptyList());
1099   
1100  2 String fieldNameEmpty = fieldName + XWikiRepositoryModel.PROPSUFFIX_EMPTYCOLLECTION;
1101  2 if (extensionObject.getXClass(xcontext).get(fieldNameEmpty) != null) {
1102  2 update(extensionObject, fieldNameEmpty, allowedNamespaces != null && allowedNamespaces.isEmpty() ? 1 : 0);
1103    }
1104   
1105  2 return needSave;
1106    }
1107    }