1. Project Clover database Fri Dec 7 2018 16:32:04 CET
  2. Package org.xwiki.extension.repository.internal.installed

File DefaultInstalledExtensionRepository.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart9.png
28% of files have more coverage

Code metrics

124
235
30
3
834
533
111
0.47
7.83
10
3.7

Classes

Class Line # Actions
DefaultInstalledExtensionRepository 65 232 0% 109 40
0.895833389.6%
DefaultInstalledExtensionRepository.InstalledRootFeature 68 1 0% 1 0
1.0100%
DefaultInstalledExtensionRepository.InstalledFeature 84 2 0% 1 0
1.0100%
 

Contributing tests

This file is covered by 82 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.installed;
21   
22    import java.util.ArrayList;
23    import java.util.Collection;
24    import java.util.Collections;
25    import java.util.Date;
26    import java.util.HashMap;
27    import java.util.HashSet;
28    import java.util.Map;
29    import java.util.Set;
30    import java.util.concurrent.ConcurrentHashMap;
31   
32    import javax.inject.Inject;
33    import javax.inject.Singleton;
34   
35    import org.apache.commons.lang3.exception.ExceptionUtils;
36    import org.slf4j.Logger;
37    import org.xwiki.component.annotation.Component;
38    import org.xwiki.component.phase.Initializable;
39    import org.xwiki.component.phase.InitializationException;
40    import org.xwiki.extension.CoreExtension;
41    import org.xwiki.extension.ExtensionDependency;
42    import org.xwiki.extension.ExtensionId;
43    import org.xwiki.extension.InstallException;
44    import org.xwiki.extension.InstalledExtension;
45    import org.xwiki.extension.InvalidExtensionException;
46    import org.xwiki.extension.LocalExtension;
47    import org.xwiki.extension.ResolveException;
48    import org.xwiki.extension.UninstallException;
49    import org.xwiki.extension.internal.ExtensionUtils;
50    import org.xwiki.extension.repository.CoreExtensionRepository;
51    import org.xwiki.extension.repository.DefaultExtensionRepositoryDescriptor;
52    import org.xwiki.extension.repository.InstalledExtensionRepository;
53    import org.xwiki.extension.repository.LocalExtensionRepository;
54    import org.xwiki.extension.version.Version;
55    import org.xwiki.extension.version.VersionConstraint;
56   
57    /**
58    * Default implementation of {@link InstalledExtensionRepository}.
59    *
60    * @version $Id: 5e93b921ffa0a1280a2160eb71f76baeca3ebc93 $
61    * @since 4.0M2
62    */
63    @Component
64    @Singleton
 
65    public class DefaultInstalledExtensionRepository extends AbstractInstalledExtensionRepository<DefaultInstalledExtension>
66    implements InstalledExtensionRepository, Initializable
67    {
 
68    private static class InstalledRootFeature
69    {
70    public DefaultInstalledExtension extension;
71   
72    public Set<DefaultInstalledExtension> invalidExtensions = new HashSet<DefaultInstalledExtension>();
73   
74    public String namespace;
75   
76    public Set<DefaultInstalledExtension> backwardDependencies = new HashSet<DefaultInstalledExtension>();
77   
 
78  1882 toggle public InstalledRootFeature(String namespace)
79    {
80  1882 this.namespace = namespace;
81    }
82    }
83   
 
84    private static class InstalledFeature
85    {
86    public InstalledRootFeature root;
87   
88    public ExtensionId feature;
89   
 
90  2310 toggle public InstalledFeature(InstalledRootFeature root, ExtensionId feature)
91    {
92  2310 this.root = root;
93  2310 this.feature = feature;
94    }
95    }
96   
97    /**
98    * Used to access all local extensions.
99    */
100    @Inject
101    private transient LocalExtensionRepository localRepository;
102   
103    /**
104    * Used to check for existing core extensions.
105    */
106    @Inject
107    private transient CoreExtensionRepository coreExtensionRepository;
108   
109    /**
110    * The logger to log.
111    */
112    @Inject
113    private transient Logger logger;
114   
115    /**
116    * The installed extensions sorted by provided feature and namespace.
117    * <p>
118    * <feature, <namespace, extension>>
119    */
120    private Map<String, Map<String, InstalledFeature>> extensionNamespaceByFeature =
121    new ConcurrentHashMap<String, Map<String, InstalledFeature>>();
122   
123    /**
124    * Temporary map used only during init.
125    * <p>
126    * <feature, <namespace, extensions>>
127    */
128    private Map<String, Map<String, Set<LocalExtension>>> localInstalledExtensionsCache;
129   
130    private boolean updateBackwardDependencies;
131   
 
132  131 toggle @Override
133    public void initialize() throws InitializationException
134    {
135  131 setDescriptor(new DefaultExtensionRepositoryDescriptor("installed", "installed",
136    this.localRepository.getDescriptor().getURI()));
137   
138    // Wait for all installed extension to be registered
139    // before calculating backward dependencies
140  131 this.updateBackwardDependencies = false;
141   
142    // Get installed extensions from local repository
143  131 this.localInstalledExtensionsCache = new HashMap<>();
144  131 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
145  1397 if (DefaultInstalledExtension.isInstalled(localExtension)) {
146  1397 getInstalledLocalExtension(localExtension);
147    }
148    }
149   
150    // Validate installed extensions
151  131 validate();
152   
153    // Update backward dependencies
154  131 updateMissingBackwardDependencies();
155   
156    // Put back backdependencies update for each extension add
157  131 this.updateBackwardDependencies = true;
158   
159    // Reset temporary cache
160  131 this.localInstalledExtensionsCache = null;
161    }
162   
 
163  131 toggle private void validate()
164    {
165    // Validate top level installed extensions
166  131 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
167  1397 if (DefaultInstalledExtension.isInstalled(localExtension)) {
168  1397 validateExtension(localExtension, false);
169    }
170    }
171   
172    // Validate dependencies (just in case some dependencies don't have any backward dependencies)
173  131 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
174  1397 if (DefaultInstalledExtension.isInstalled(localExtension)) {
175  1397 validateExtension(localExtension, true);
176    }
177    }
178    }
179   
 
180  1397 toggle private void getInstalledLocalExtension(LocalExtension localExtension)
181    {
182  1397 getInstalledLocalExtension(localExtension.getId().getId(), localExtension);
183   
184  1397 for (ExtensionId feature : localExtension.getExtensionFeatures()) {
185  348 getInstalledLocalExtension(feature.getId(), localExtension);
186    }
187    }
188   
 
189  1745 toggle private void getInstalledLocalExtension(String feature, LocalExtension localExtension)
190    {
191  1745 Collection<String> namespaces = DefaultInstalledExtension.getNamespaces(localExtension);
192   
193  1745 if (namespaces == null) {
194  1090 getInstalledLocalExtension(feature, null, localExtension);
195    } else {
196  655 for (String namespace : namespaces) {
197  829 getInstalledLocalExtension(feature, namespace, localExtension);
198    }
199    }
200    }
201   
 
202  1919 toggle private void getInstalledLocalExtension(String feature, String namespace, LocalExtension localExtension)
203    {
204  1919 Map<String, Set<LocalExtension>> localInstallExtensionFeature = this.localInstalledExtensionsCache.get(feature);
205  1919 if (localInstallExtensionFeature == null) {
206  1658 localInstallExtensionFeature = new HashMap<>();
207  1658 this.localInstalledExtensionsCache.put(feature, localInstallExtensionFeature);
208    }
209   
210  1919 Set<LocalExtension> localInstallExtensionNamespace = localInstallExtensionFeature.get(namespace);
211  1919 if (localInstallExtensionNamespace == null) {
212  1832 localInstallExtensionNamespace = new HashSet<LocalExtension>();
213  1832 localInstallExtensionFeature.put(namespace, localInstallExtensionNamespace);
214    }
215   
216  1919 localInstallExtensionNamespace.add(localExtension);
217    }
218   
219    // Validation
220   
221    /**
222    * Check extension validity and set it as not installed if not.
223    *
224    * @param localExtension the extension to validate
225    * @param dependencies true if dependencies should be validated
226    * @throws InvalidExtensionException when the passed extension is fond invalid
227    */
 
228  2794 toggle private void validateExtension(LocalExtension localExtension, boolean dependencies)
229    {
230  2794 Collection<String> namespaces = DefaultInstalledExtension.getNamespaces(localExtension);
231   
232  2794 if (namespaces == null) {
233  1658 if (dependencies || !DefaultInstalledExtension.isDependency(localExtension, null)) {
234  1397 try {
235  1397 validateExtension(localExtension, null, Collections.emptyMap());
236    } catch (InvalidExtensionException e) {
237  197 if (this.logger.isDebugEnabled()) {
238  0 this.logger.warn("Invalid extension [{}]", localExtension.getId(), e);
239    } else {
240  197 this.logger.warn("Invalid extension [{}] ({})", localExtension.getId(),
241    ExceptionUtils.getRootCauseMessage(e));
242    }
243   
244  197 addInstalledExtension(localExtension, null, false);
245    }
246    }
247    } else {
248  1136 for (String namespace : namespaces) {
249  1484 if (dependencies || !DefaultInstalledExtension.isDependency(localExtension, namespace)) {
250  1484 try {
251  1484 validateExtension(localExtension, namespace, Collections.emptyMap());
252    } catch (InvalidExtensionException e) {
253  23 if (this.logger.isDebugEnabled()) {
254  0 this.logger.warn("Invalid extension [{}] on namespace [{}]", localExtension.getId(),
255    namespace, e);
256    } else {
257  23 this.logger.warn("Invalid extension [{}] on namespace [{}] ({})", localExtension.getId(),
258    namespace, ExceptionUtils.getRootCauseMessage(e));
259    }
260   
261  23 addInstalledExtension(localExtension, namespace, false);
262    }
263    }
264    }
265    }
266    }
267   
 
268  1113 toggle private LocalExtension getInstalledLocalExtension(ExtensionDependency dependency, String namespace)
269    {
270  1113 Map<String, Set<LocalExtension>> localInstallExtensionFeature =
271    this.localInstalledExtensionsCache.get(dependency.getId());
272   
273  1113 if (localInstallExtensionFeature != null) {
274  870 Set<LocalExtension> localInstallExtensionNamespace = localInstallExtensionFeature.get(namespace);
275   
276  870 if (localInstallExtensionNamespace != null) {
277  696 for (LocalExtension dependencyVersion : localInstallExtensionNamespace) {
278  783 if (isCompatible(dependencyVersion.getId().getVersion(), dependency.getVersionConstraint())) {
279  696 return dependencyVersion;
280    }
281    }
282    }
283    }
284   
285    // Try on root namespace
286  417 if (namespace != null) {
287  197 return getInstalledLocalExtension(dependency, null);
288    }
289   
290  220 return null;
291    }
292   
 
293  1049 toggle private void validateDependency(ExtensionDependency dependency, String namespace,
294    Map<String, ExtensionDependency> managedDependencies) throws InvalidExtensionException
295    {
296  1049 CoreExtension coreExtension = this.coreExtensionRepository.getCoreExtension(dependency.getId());
297   
298  1049 if (coreExtension != null) {
299  97 if (!isCompatible(coreExtension.getId().getVersion(), dependency.getVersionConstraint())) {
300  0 throw new InvalidExtensionException(String
301    .format("Dependency [%s] is incompatible with the core extension [%s]", dependency, coreExtension));
302    }
303    } else {
304  952 LocalExtension dependencyExtension =
305  952 this.localInstalledExtensionsCache != null ? getInstalledLocalExtension(dependency, namespace)
306    : getInstalledExtension(dependency.getId(), namespace);
307   
308  952 if (dependencyExtension == null) {
309  220 throw new InvalidExtensionException(
310    String.format("No compatible extension is installed for dependency [%s]", dependency));
311    } else {
312  732 try {
313  732 DefaultInstalledExtension installedExtension =
314    validateExtension(dependencyExtension, namespace, managedDependencies);
315   
316  732 if (!installedExtension.isValid(namespace)) {
317  0 throw new InvalidExtensionException(
318    String.format("Extension dependency [%s] is invalid", installedExtension.getId()));
319    }
320    } catch (InvalidExtensionException e) {
321  0 if (this.localInstalledExtensionsCache != null) {
322  0 addInstalledExtension(dependencyExtension, namespace, false);
323    }
324   
325  0 throw e;
326    }
327    }
328    }
329    }
330   
331    /**
332    * Check extension validity against a specific namespace.
333    *
334    * @param localExtension the extension to validate
335    * @param namespace the namespace
336    * @param managedDependencies the managed dependencies
337    * @return the corresponding {@link DefaultInstalledExtension}
338    * @throws InvalidExtensionException when the passed extension is fond invalid
339    */
 
340  3921 toggle private DefaultInstalledExtension validateExtension(LocalExtension localExtension, String namespace,
341    Map<String, ExtensionDependency> managedDependencies) throws InvalidExtensionException
342    {
343  3921 DefaultInstalledExtension installedExtension = this.extensions.get(localExtension.getId());
344  3921 if (installedExtension != null && installedExtension.isValidated(namespace)) {
345    // Already validated
346  2061 return installedExtension;
347    }
348   
349    // Actually validate
350   
351  1860 if (namespace != null && DefaultInstalledExtension.getNamespaces(localExtension) == null) {
352    // This extension is supposed to be installed on root namespace only so redirecting to null namespace
353    // initialization
354  182 return validateExtension(localExtension, null, managedDependencies);
355    }
356   
357  1678 if (!DefaultInstalledExtension.isInstalled(localExtension, namespace)) {
358  0 throw new InvalidExtensionException(String.format("Extension [%s] is not installed", localExtension));
359    }
360   
361  1678 if (this.coreExtensionRepository.exists(localExtension.getId().getId())) {
362  0 throw new InvalidExtensionException(
363    String.format("Extension [%s] already exists as a core extension", localExtension));
364    }
365   
366    // Validate dependencies
367  1678 InvalidExtensionException dependencyException = null;
368  1678 for (ExtensionDependency dependency : localExtension.getDependencies()) {
369  1049 try {
370    // Replace with managed dependency if any
371  1049 dependency = ExtensionUtils.getDependency(dependency, managedDependencies, localExtension);
372   
373  1049 validateDependency(dependency, namespace, ExtensionUtils.append(managedDependencies, localExtension));
374    } catch (InvalidExtensionException e) {
375  220 if (!dependency.isOptional()) {
376    // Continue to make sure all extensions are validated in the right order
377  220 if (dependencyException == null) {
378  220 dependencyException = e;
379    }
380    }
381    }
382    }
383   
384    // Throw exception if any issue has been found with dependencies
385  1678 if (dependencyException != null) {
386  220 throw dependencyException;
387    }
388   
389    // Complete local extension installation
390  1458 return localExtension instanceof DefaultInstalledExtension ? (DefaultInstalledExtension) localExtension
391    : addInstalledExtension(localExtension, namespace, true);
392    }
393   
 
394  126 toggle private boolean isValid(DefaultInstalledExtension installedExtension, String namespace,
395    Map<String, ExtensionDependency> managedDependencies)
396    {
397  126 try {
398  126 validateExtension(installedExtension, namespace, managedDependencies);
399   
400  126 return true;
401    } catch (InvalidExtensionException e) {
402  0 this.logger.debug("Invalid extension [{}] on namespace [{}]", installedExtension.getId(), namespace, e);
403    }
404   
405  0 return false;
406    }
407   
 
408  880 toggle private boolean isCompatible(Version existingVersion, VersionConstraint versionConstraint)
409    {
410  880 boolean compatible = true;
411   
412  880 if (versionConstraint.getVersion() == null) {
413  0 compatible = versionConstraint.containsVersion(existingVersion);
414    } else {
415  880 compatible = existingVersion.compareTo(versionConstraint.getVersion()) >= 0;
416    }
417   
418  880 return compatible;
419    }
420   
421    // Install/Uninstall
422   
423    /**
424    * Uninstall provided extension.
425    *
426    * @param installedExtension the extension to uninstall
427    * @param namespace the namespace
428    * @see #uninstallExtension(LocalExtension, String)
429    */
 
430  87 toggle private void removeInstalledExtension(DefaultInstalledExtension installedExtension, String namespace)
431    {
432  87 removeInstalledFeature(installedExtension.getId().getId(), namespace);
433   
434  87 for (ExtensionId feature : installedExtension.getExtensionFeatures()) {
435  57 removeInstalledFeature(feature.getId(), namespace);
436    }
437   
438  87 removeFromBackwardDependencies(installedExtension, namespace);
439   
440  87 if (!installedExtension.isInstalled()) {
441  75 removeCachedExtension(installedExtension);
442    }
443    }
444   
445    /**
446    * Uninstall provided extension.
447    *
448    * @param feature the feature to uninstall
449    * @param namespace the namespace
450    * @see #uninstallExtension(LocalExtension, String)
451    */
 
452  144 toggle private void removeInstalledFeature(String feature, String namespace)
453    {
454    // Extensions namespaces by feature
455   
456  144 if (namespace == null) {
457  63 this.extensionNamespaceByFeature.remove(feature);
458    } else {
459  81 Map<String, InstalledFeature> namespaceInstalledExtension = this.extensionNamespaceByFeature.get(feature);
460   
461  81 namespaceInstalledExtension.remove(namespace);
462    }
463    }
464   
465    /**
466    * Install provided extension.
467    *
468    * @param localExtension the extension to install
469    * @param namespace the namespace
470    * @param dependency indicate if the extension is stored as a dependency of another one
471    * @param properties the custom properties to set on the installed extension for the specified namespace
472    * @param managedDependencies the managed dependencies
473    * @throws InstallException error when trying to uninstall extension
474    * @see #installExtension(LocalExtension, String)
475    */
 
476  126 toggle private void applyInstallExtension(DefaultInstalledExtension installedExtension, String namespace,
477    boolean dependency, Map<String, Object> properties, Map<String, ExtensionDependency> managedDependencies)
478    throws InstallException
479    {
480    // INSTALLED
481  126 installedExtension.setInstalled(true, namespace);
482  126 installedExtension.setInstallDate(new Date(), namespace);
483   
484    // DEPENDENCY
485  126 installedExtension.setDependency(dependency, namespace);
486   
487    // Add custom install properties for the specified namespace. The map holding the namespace properties should
488    // not be null because it is initialized by the InstalledExtension#setInstalled(true, namespace) call above.
489  126 installedExtension.getNamespaceProperties(namespace).putAll(properties);
490   
491    // Save properties
492  126 try {
493  126 this.localRepository.setProperties(installedExtension.getLocalExtension(),
494    installedExtension.getProperties());
495    } catch (Exception e) {
496  0 throw new InstallException("Failed to modify extension descriptor", e);
497    }
498   
499    // VALID
500  126 installedExtension.setValid(namespace, isValid(installedExtension, namespace, managedDependencies));
501   
502    // Update caches
503   
504  126 addInstalledExtension(installedExtension, namespace);
505    }
506   
 
507  87 toggle private void removeFromBackwardDependencies(DefaultInstalledExtension installedExtension, String namespace)
508    {
509    // Clean provided extension dependencies backward dependencies
510  87 for (ExtensionDependency dependency : installedExtension.getDependencies()) {
511  65 if (this.coreExtensionRepository.getCoreExtension(dependency.getId()) == null) {
512  49 InstalledFeature installedFeature = getInstalledFeatureFromCache(dependency.getId(), namespace);
513   
514  49 if (installedFeature != null) {
515  47 installedFeature.root.backwardDependencies.remove(installedExtension);
516    }
517    }
518    }
519    }
520   
521    /**
522    * Register a newly installed extension in backward dependencies map.
523    *
524    * @param localExtension the local extension to register
525    * @param namespace the namespace
526    * @param valid is the extension valid
527    * @return the new {@link DefaultInstalledExtension}
528    */
 
529  1571 toggle private DefaultInstalledExtension addInstalledExtension(LocalExtension localExtension, String namespace,
530    boolean valid)
531    {
532  1571 DefaultInstalledExtension installedExtension = this.extensions.get(localExtension.getId());
533  1571 if (installedExtension == null) {
534  1397 installedExtension = new DefaultInstalledExtension(localExtension, this);
535    }
536   
537  1571 installedExtension.setInstalled(true, namespace);
538  1571 installedExtension.setValid(namespace, valid);
539   
540  1571 addInstalledExtension(installedExtension, namespace);
541   
542  1571 return installedExtension;
543    }
544   
545    /**
546    * Register a newly installed extension in backward dependencies map.
547    *
548    * @param installedExtension the installed extension to register
549    * @param namespace the namespace
550    */
 
551  1697 toggle private void addInstalledExtension(DefaultInstalledExtension installedExtension, String namespace)
552    {
553  1697 addCachedExtension(installedExtension);
554   
555  1697 boolean isValid = installedExtension.isValid(namespace);
556   
557    // Register the extension in the installed extensions for the provided namespace
558  1697 addInstalledFeatureToCache(installedExtension.getId(), namespace, installedExtension, isValid);
559   
560    // Add virtual extensions
561  1697 for (ExtensionId feature : installedExtension.getExtensionFeatures()) {
562  428 addInstalledFeatureToCache(feature, namespace, installedExtension, isValid);
563    }
564   
565  1697 if (this.updateBackwardDependencies) {
566    // Recalculate backward dependencies index
567  126 updateMissingBackwardDependencies();
568    }
569    }
570   
 
571  257 toggle private void updateMissingBackwardDependencies()
572    {
573  257 for (DefaultInstalledExtension installedExtension : this.extensions.values()) {
574  2815 updateMissingBackwardDependencies(installedExtension);
575    }
576    }
577   
 
578  2815 toggle private void updateMissingBackwardDependencies(DefaultInstalledExtension installedExtension)
579    {
580  2815 Collection<String> namespaces = installedExtension.getNamespaces();
581   
582  2815 if (namespaces == null) {
583  1605 if (installedExtension.isValid(null)) {
584  1212 updateMissingBackwardDependencies(installedExtension, null);
585    }
586    } else {
587  1210 for (String namespace : namespaces) {
588  1554 if (installedExtension.isValid(namespace)) {
589  1477 updateMissingBackwardDependencies(installedExtension, namespace);
590    }
591    }
592    }
593    }
594   
 
595  2689 toggle private void updateMissingBackwardDependencies(DefaultInstalledExtension installedExtension, String namespace)
596    {
597    // Add the extension as backward dependency
598  2689 for (ExtensionDependency dependency : installedExtension.getDependencies()) {
599  1462 if (!dependency.isOptional() && !this.coreExtensionRepository.exists(dependency.getId())) {
600    // Get the extension for the dependency feature for the provided namespace
601  1300 DefaultInstalledExtension dependencyLocalExtension =
602    (DefaultInstalledExtension) getInstalledExtension(dependency.getId(), namespace);
603   
604  1300 if (dependencyLocalExtension != null) {
605  1290 ExtensionId feature = dependencyLocalExtension.getExtensionFeature(dependency.getId());
606   
607    // Make sure to register backward dependency on the right namespace
608  1290 InstalledFeature dependencyInstalledExtension =
609    addInstalledFeatureToCache(feature, namespace, dependencyLocalExtension, false);
610   
611  1290 dependencyInstalledExtension.root.backwardDependencies.add(installedExtension);
612    }
613    }
614    }
615    }
616   
617    /**
618    * Get extension registered as installed for the provided feature and namespace or can register it if provided.
619    * <p>
620    * Only look at provide namespace and does take into account inheritance.
621    *
622    * @param feature the feature provided by the extension
623    * @param namespace the namespace where the extension is installed
624    * @param installedExtension the extension
625    * @return the installed extension informations
626    */
 
627  3415 toggle private InstalledFeature addInstalledFeatureToCache(ExtensionId feature, String namespace,
628    DefaultInstalledExtension installedExtension, boolean forceCreate)
629    {
630  3415 Map<String, InstalledFeature> installedExtensionsForFeature =
631    this.extensionNamespaceByFeature.get(feature.getId());
632   
633  3415 if (installedExtensionsForFeature == null) {
634  1806 installedExtensionsForFeature = new HashMap<String, InstalledFeature>();
635  1806 this.extensionNamespaceByFeature.put(feature.getId(), installedExtensionsForFeature);
636    }
637   
638  3415 InstalledFeature installedFeature = installedExtensionsForFeature.get(namespace);
639  3415 if (forceCreate || installedFeature == null) {
640    // Find or create root feature
641  2310 InstalledRootFeature rootInstalledFeature;
642  2310 if (installedExtension.getId().getId().equals(feature.getId())) {
643  1882 rootInstalledFeature = new InstalledRootFeature(namespace);
644    } else {
645  428 rootInstalledFeature = getInstalledFeatureFromCache(installedExtension.getId().getId(), namespace).root;
646    }
647   
648    // Create new feature
649  2310 installedFeature = new InstalledFeature(rootInstalledFeature, feature);
650   
651    // Add new feature
652  2310 installedExtensionsForFeature.put(namespace, installedFeature);
653    }
654   
655  3415 if (installedExtension.isValid(namespace)) {
656  3108 installedFeature.root.extension = installedExtension;
657    } else {
658  307 installedFeature.root.invalidExtensions.add(installedExtension);
659    }
660   
661  3415 return installedFeature;
662    }
663   
664    /**
665    * Get extension registered as installed for the provided feature and namespace (including on root namespace).
666    *
667    * @param feature the feature provided by the extension
668    * @param namespace the namespace where the extension is installed
669    * @return the installed extension informations
670    */
 
671  3913 toggle private InstalledFeature getInstalledFeatureFromCache(String feature, String namespace)
672    {
673  3913 if (feature == null) {
674  0 return null;
675    }
676   
677  3913 Map<String, InstalledFeature> installedExtensionsForFeature = this.extensionNamespaceByFeature.get(feature);
678   
679  3913 if (installedExtensionsForFeature == null) {
680  524 return null;
681    }
682   
683  3389 InstalledFeature installedExtension = installedExtensionsForFeature.get(namespace);
684   
685    // Fallback on root namespace if the feature could not be found
686  3389 if (installedExtension == null && namespace != null) {
687  278 installedExtension = getInstalledFeatureFromCache(feature, null);
688    }
689   
690  3389 return installedExtension;
691    }
692   
693    // InstalledExtensionRepository
694   
 
695  3158 toggle @Override
696    public InstalledExtension getInstalledExtension(String feature, String namespace)
697    {
698  3158 InstalledFeature installedFeature = getInstalledFeatureFromCache(feature, namespace);
699   
700  3158 if (installedFeature != null) {
701  2370 if (installedFeature.root.extension != null) {
702  2356 return installedFeature.root.extension;
703    }
704   
705  14 return installedFeature.root.invalidExtensions.isEmpty() ? null
706    : installedFeature.root.invalidExtensions.iterator().next();
707    }
708   
709  788 return null;
710    }
711   
 
712  129 toggle @Override
713    public InstalledExtension installExtension(LocalExtension extension, String namespace, boolean dependency,
714    Map<String, Object> properties) throws InstallException
715    {
716  129 DefaultInstalledExtension installedExtension = this.extensions.get(extension.getId());
717   
718  129 if (installedExtension != null && installedExtension.isInstalled(namespace)
719    && installedExtension.isValid(namespace)) {
720  3 if (installedExtension.isDependency(namespace) == dependency) {
721  1 throw new InstallException(String.format("The extension [%s] is already installed on namespace [%s]",
722    installedExtension, namespace));
723    }
724   
725  2 installedExtension.setDependency(dependency, namespace);
726   
727  2 try {
728  2 this.localRepository.setProperties(installedExtension.getLocalExtension(),
729    installedExtension.getProperties());
730    } catch (Exception e) {
731  0 throw new InstallException("Failed to modify extension descriptor", e);
732    }
733    } else {
734  126 LocalExtension localExtension = this.localRepository.getLocalExtension(extension.getId());
735   
736  126 if (localExtension == null) {
737    // Should be a very rare use case since we explicitly ask for a LocalExtension
738  0 throw new InstallException(String.format("The extension [%s] need to be stored first", extension));
739    }
740   
741  126 if (installedExtension == null) {
742  107 installedExtension = new DefaultInstalledExtension(localExtension, this);
743    }
744   
745  126 applyInstallExtension(installedExtension, namespace, dependency, properties, Collections.emptyMap());
746    }
747   
748  128 return installedExtension;
749    }
750   
 
751  88 toggle @Override
752    public void uninstallExtension(InstalledExtension extension, String namespace) throws UninstallException
753    {
754  88 DefaultInstalledExtension installedExtension =
755    (DefaultInstalledExtension) getInstalledExtension(extension.getId().getId(), namespace);
756   
757  88 if (installedExtension != null) {
758  87 applyUninstallExtension(installedExtension, namespace);
759    }
760    }
761   
 
762  87 toggle private void applyUninstallExtension(DefaultInstalledExtension installedExtension, String namespace)
763    throws UninstallException
764    {
765  87 installedExtension.setInstalled(false, namespace);
766   
767  87 try {
768  87 this.localRepository.setProperties(installedExtension.getLocalExtension(),
769    installedExtension.getProperties());
770    } catch (Exception e) {
771  0 throw new UninstallException("Failed to modify extension descriptor", e);
772    }
773   
774    // Clean caches
775   
776  87 removeInstalledExtension(installedExtension, namespace);
777    }
778   
 
779  92 toggle @Override
780    public Collection<InstalledExtension> getBackwardDependencies(String feature, String namespace)
781    throws ResolveException
782    {
783  92 if (getInstalledExtension(feature, namespace) == null) {
784  0 throw new ResolveException(
785    String.format("Extension [%s] is not installed on namespace [%s]", feature, namespace));
786    }
787   
788  92 Map<String, InstalledFeature> installedExtensionsByFeature = this.extensionNamespaceByFeature.get(feature);
789  92 if (installedExtensionsByFeature != null) {
790  92 InstalledFeature installedExtension = installedExtensionsByFeature.get(namespace);
791   
792  92 if (installedExtension != null) {
793  91 Set<DefaultInstalledExtension> backwardDependencies = installedExtension.root.backwardDependencies;
794   
795    // copy the list to allow use cases like uninstalling all backward dependencies without getting a
796    // concurrent issue on the list
797  91 return backwardDependencies.isEmpty() ? Collections.<InstalledExtension>emptyList()
798    : new ArrayList<InstalledExtension>(backwardDependencies);
799    }
800    }
801   
802  1 return Collections.emptyList();
803    }
804   
 
805  131 toggle @Override
806    public Map<String, Collection<InstalledExtension>> getBackwardDependencies(ExtensionId extensionId)
807    throws ResolveException
808    {
809  131 Map<String, Collection<InstalledExtension>> result;
810   
811  131 DefaultInstalledExtension installedExtension = resolve(extensionId);
812   
813  131 Collection<String> namespaces = installedExtension.getNamespaces();
814   
815  131 Map<String, InstalledFeature> featureExtensions =
816    this.extensionNamespaceByFeature.get(installedExtension.getId().getId());
817  131 if (featureExtensions != null) {
818  131 result = new HashMap<String, Collection<InstalledExtension>>();
819  131 for (InstalledFeature festureExtension : featureExtensions.values()) {
820  156 if ((namespaces == null || namespaces.contains(festureExtension.root.namespace))
821    && !festureExtension.root.backwardDependencies.isEmpty()) {
822    // copy the list to allow use cases like uninstalling all backward dependencies without getting a
823    // concurrent issue on the list
824  71 result.put(festureExtension.root.namespace,
825    new ArrayList<InstalledExtension>(festureExtension.root.backwardDependencies));
826    }
827    }
828    } else {
829  0 result = Collections.emptyMap();
830    }
831   
832  131 return result;
833    }
834    }