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

File DefaultInstalledExtensionRepository.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

122
234
30
3
832
531
109
0.47
7.8
10
3.63

Classes

Class Line # Actions
DefaultInstalledExtensionRepository 65 231 0% 107 33
0.913385891.3%
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 111 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: be4334fc03fd251e87cc0b365d201df1321472b1 $
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  1873 toggle public InstalledRootFeature(String namespace)
79    {
80  1873 this.namespace = namespace;
81    }
82    }
83   
 
84    private static class InstalledFeature
85    {
86    public InstalledRootFeature root;
87   
88    public ExtensionId feature;
89   
 
90  2197 toggle public InstalledFeature(InstalledRootFeature root, ExtensionId feature)
91    {
92  2197 this.root = root;
93  2197 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  231 toggle @Override
133    public void initialize() throws InitializationException
134    {
135  231 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  231 this.updateBackwardDependencies = false;
141   
142    // Get installed extensions from local repository
143  231 this.localInstalledExtensionsCache = new HashMap<>();
144  231 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
145  1338 if (DefaultInstalledExtension.isInstalled(localExtension)) {
146  1337 getInstalledLocalExtension(localExtension);
147    }
148    }
149   
150    // Validate installed extensions
151  231 validate();
152   
153    // Update backward dependencies
154  231 updateMissingBackwardDependencies();
155   
156    // Put back backdependencies update for each extension add
157  231 this.updateBackwardDependencies = true;
158   
159    // Reset temporary cache
160  231 this.localInstalledExtensionsCache = null;
161    }
162   
 
163  231 toggle private void validate()
164    {
165    // Validate top level installed extensions
166  231 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
167  1338 if (DefaultInstalledExtension.isInstalled(localExtension)) {
168  1337 validateExtension(localExtension, false);
169    }
170    }
171   
172    // Validate dependencies (just in case some dependencies don't have any backward dependencies)
173  231 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
174  1338 if (DefaultInstalledExtension.isInstalled(localExtension)) {
175  1337 validateExtension(localExtension, true);
176    }
177    }
178    }
179   
 
180  1337 toggle private void getInstalledLocalExtension(LocalExtension localExtension)
181    {
182  1337 getInstalledLocalExtension(localExtension.getId().getId(), localExtension);
183   
184  1337 for (ExtensionId feature : localExtension.getExtensionFeatures()) {
185  320 getInstalledLocalExtension(feature.getId(), localExtension);
186    }
187    }
188   
 
189  1657 toggle private void getInstalledLocalExtension(String feature, LocalExtension localExtension)
190    {
191  1657 Collection<String> namespaces = DefaultInstalledExtension.getNamespaces(localExtension);
192   
193  1657 if (namespaces == null) {
194  1041 getInstalledLocalExtension(feature, null, localExtension);
195    } else {
196  616 for (String namespace : namespaces) {
197  776 getInstalledLocalExtension(feature, namespace, localExtension);
198    }
199    }
200    }
201   
 
202  1817 toggle private void getInstalledLocalExtension(String feature, String namespace, LocalExtension localExtension)
203    {
204  1817 Map<String, Set<LocalExtension>> localInstallExtensionFeature = this.localInstalledExtensionsCache.get(feature);
205  1817 if (localInstallExtensionFeature == null) {
206  1577 localInstallExtensionFeature = new HashMap<>();
207  1577 this.localInstalledExtensionsCache.put(feature, localInstallExtensionFeature);
208    }
209   
210  1817 Set<LocalExtension> localInstallExtensionNamespace = localInstallExtensionFeature.get(namespace);
211  1817 if (localInstallExtensionNamespace == null) {
212  1737 localInstallExtensionNamespace = new HashSet<LocalExtension>();
213  1737 localInstallExtensionFeature.put(namespace, localInstallExtensionNamespace);
214    }
215   
216  1817 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  2674 toggle private void validateExtension(LocalExtension localExtension, boolean dependencies)
229    {
230  2674 Collection<String> namespaces = DefaultInstalledExtension.getNamespaces(localExtension);
231   
232  2674 if (namespaces == null) {
233  1602 if (dependencies || !DefaultInstalledExtension.isDependency(localExtension, null)) {
234  1362 try {
235  1362 validateExtension(localExtension, null, Collections.emptyMap());
236    } catch (InvalidExtensionException e) {
237  183 if (this.logger.isDebugEnabled()) {
238  0 this.logger.warn("Invalid extension [{}]", localExtension.getId(), e);
239    } else {
240  183 this.logger.warn("Invalid extension [{}] ({})", localExtension.getId(),
241    ExceptionUtils.getRootCauseMessage(e));
242    }
243   
244  183 addInstalledExtension(localExtension, null, false);
245    }
246    }
247    } else {
248  1072 for (String namespace : namespaces) {
249  1392 if (dependencies || !DefaultInstalledExtension.isDependency(localExtension, namespace)) {
250  1392 try {
251  1392 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  1029 toggle private LocalExtension getInstalledLocalExtension(ExtensionDependency dependency, String namespace)
269    {
270  1029 Map<String, Set<LocalExtension>> localInstallExtensionFeature =
271    this.localInstalledExtensionsCache.get(dependency.getId());
272   
273  1029 if (localInstallExtensionFeature != null) {
274  800 Set<LocalExtension> localInstallExtensionNamespace = localInstallExtensionFeature.get(namespace);
275   
276  800 if (localInstallExtensionNamespace != null) {
277  640 for (LocalExtension dependencyVersion : localInstallExtensionNamespace) {
278  640 if (isCompatible(dependencyVersion.getId().getVersion(), dependency.getVersionConstraint())) {
279  640 return dependencyVersion;
280    }
281    }
282    }
283    }
284   
285    // Try on root namespace
286  389 if (namespace != null) {
287  183 return getInstalledLocalExtension(dependency, null);
288    }
289   
290  206 return null;
291    }
292   
 
293  1001 toggle private void validateDependency(ExtensionDependency dependency, String namespace,
294    Map<String, ExtensionDependency> managedDependencies) throws InvalidExtensionException
295    {
296  1001 CoreExtension coreExtension = this.coreExtensionRepository.getCoreExtension(dependency.getId());
297   
298  1001 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  904 LocalExtension dependencyExtension =
305  904 this.localInstalledExtensionsCache != null ? getInstalledLocalExtension(dependency, namespace)
306    : getInstalledExtension(dependency.getId(), namespace);
307   
308  904 if (dependencyExtension == null) {
309  207 throw new InvalidExtensionException(
310    String.format("No compatible extension is installed for dependency [%s]", dependency));
311    } else {
312  697 try {
313  697 DefaultInstalledExtension installedExtension =
314    validateExtension(dependencyExtension, namespace, managedDependencies);
315   
316  697 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  3824 toggle private DefaultInstalledExtension validateExtension(LocalExtension localExtension, String namespace,
341    Map<String, ExtensionDependency> managedDependencies) throws InvalidExtensionException
342    {
343  3824 DefaultInstalledExtension installedExtension = this.extensions.get(localExtension.getId());
344  3824 if (installedExtension != null && installedExtension.isValidated(namespace)) {
345    // Already validated
346  1979 return installedExtension;
347    }
348   
349    // Actually validate
350   
351  1845 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  168 return validateExtension(localExtension, null, managedDependencies);
355    }
356   
357  1677 if (!DefaultInstalledExtension.isInstalled(localExtension, namespace)) {
358  0 throw new InvalidExtensionException(String.format("Extension [%s] is not installed", localExtension));
359    }
360   
361  1677 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  1677 InvalidExtensionException dependencyException = null;
368  1677 for (ExtensionDependency dependency : localExtension.getDependencies()) {
369  1001 try {
370    // Replace with managed dependency if any
371  1001 dependency = ExtensionUtils.getDependency(dependency, managedDependencies, localExtension);
372   
373  1001 validateDependency(dependency, namespace, ExtensionUtils.append(managedDependencies, localExtension));
374    } catch (InvalidExtensionException e) {
375    // Continue to make sure all extension are validated in the right order
376  207 if (dependencyException == null) {
377  207 dependencyException = e;
378    }
379    }
380    }
381   
382    // Throw exception if any issue has been found with dependencies
383  1677 if (dependencyException != null) {
384  207 throw dependencyException;
385    }
386   
387    // Complete local extension installation
388  1470 return localExtension instanceof DefaultInstalledExtension ? (DefaultInstalledExtension) localExtension
389    : addInstalledExtension(localExtension, namespace, true);
390    }
391   
 
392  205 toggle private boolean isValid(DefaultInstalledExtension installedExtension, String namespace,
393    Map<String, ExtensionDependency> managedDependencies)
394    {
395  205 try {
396  205 validateExtension(installedExtension, namespace, managedDependencies);
397   
398  204 return true;
399    } catch (InvalidExtensionException e) {
400  1 this.logger.debug("Invalid extension [{}] on namespace [{}]", installedExtension.getId(), namespace, e);
401    }
402   
403  1 return false;
404    }
405   
 
406  737 toggle private boolean isCompatible(Version existingVersion, VersionConstraint versionConstraint)
407    {
408  737 boolean compatible = true;
409   
410  737 if (versionConstraint.getVersion() == null) {
411  2 compatible = versionConstraint.containsVersion(existingVersion);
412    } else {
413  735 compatible = existingVersion.compareTo(versionConstraint.getVersion()) >= 0;
414    }
415   
416  737 return compatible;
417    }
418   
419    // Install/Uninstall
420   
421    /**
422    * Uninstall provided extension.
423    *
424    * @param installedExtension the extension to uninstall
425    * @param namespace the namespace
426    * @see #uninstallExtension(LocalExtension, String)
427    */
 
428  120 toggle private void removeInstalledExtension(DefaultInstalledExtension installedExtension, String namespace)
429    {
430  120 removeInstalledFeature(installedExtension.getId().getId(), namespace);
431   
432  120 for (ExtensionId feature : installedExtension.getExtensionFeatures()) {
433  61 removeInstalledFeature(feature.getId(), namespace);
434    }
435   
436  120 removeFromBackwardDependencies(installedExtension, namespace);
437   
438  120 if (!installedExtension.isInstalled()) {
439  104 removeCachedExtension(installedExtension);
440    }
441    }
442   
443    /**
444    * Uninstall provided extension.
445    *
446    * @param feature the feature to uninstall
447    * @param namespace the namespace
448    * @see #uninstallExtension(LocalExtension, String)
449    */
 
450  181 toggle private void removeInstalledFeature(String feature, String namespace)
451    {
452    // Extensions namespaces by feature
453   
454  181 if (namespace == null) {
455  66 this.extensionNamespaceByFeature.remove(feature);
456    } else {
457  115 Map<String, InstalledFeature> namespaceInstalledExtension = this.extensionNamespaceByFeature.get(feature);
458   
459  115 namespaceInstalledExtension.remove(namespace);
460    }
461    }
462   
463    /**
464    * Install provided extension.
465    *
466    * @param localExtension the extension to install
467    * @param namespace the namespace
468    * @param dependency indicate if the extension is stored as a dependency of another one
469    * @param properties the custom properties to set on the installed extension for the specified namespace
470    * @param managedDependencies the managed dependencies
471    * @throws InstallException error when trying to uninstall extension
472    * @see #installExtension(LocalExtension, String)
473    */
 
474  205 toggle private void applyInstallExtension(DefaultInstalledExtension installedExtension, String namespace,
475    boolean dependency, Map<String, Object> properties, Map<String, ExtensionDependency> managedDependencies)
476    throws InstallException
477    {
478    // INSTALLED
479  205 installedExtension.setInstalled(true, namespace);
480  205 installedExtension.setInstallDate(new Date(), namespace);
481   
482    // DEPENDENCY
483  205 installedExtension.setDependency(dependency, namespace);
484   
485    // Add custom install properties for the specified namespace. The map holding the namespace properties should
486    // not be null because it is initialized by the InstalledExtension#setInstalled(true, namespace) call above.
487  205 installedExtension.getNamespaceProperties(namespace).putAll(properties);
488   
489    // Save properties
490  205 try {
491  205 this.localRepository.setProperties(installedExtension.getLocalExtension(),
492    installedExtension.getProperties());
493    } catch (Exception e) {
494  0 throw new InstallException("Failed to modify extension descriptor", e);
495    }
496   
497    // VALID
498  205 installedExtension.setValid(namespace, isValid(installedExtension, namespace, managedDependencies));
499   
500    // Update caches
501   
502  205 addInstalledExtension(installedExtension, namespace);
503    }
504   
 
505  120 toggle private void removeFromBackwardDependencies(DefaultInstalledExtension installedExtension, String namespace)
506    {
507    // Clean provided extension dependencies backward dependencies
508  120 for (ExtensionDependency dependency : installedExtension.getDependencies()) {
509  94 if (this.coreExtensionRepository.getCoreExtension(dependency.getId()) == null) {
510  62 InstalledFeature installedFeature = getInstalledFeatureFromCache(dependency.getId(), namespace);
511   
512  62 if (installedFeature != null) {
513  57 installedFeature.root.backwardDependencies.remove(installedExtension);
514    }
515    }
516    }
517    }
518   
519    /**
520    * Register a newly installed extension in backward dependencies map.
521    *
522    * @param localExtension the local extension to register
523    * @param namespace the namespace
524    * @param valid is the extension valid
525    * @return the new {@link DefaultInstalledExtension}
526    */
 
527  1497 toggle private DefaultInstalledExtension addInstalledExtension(LocalExtension localExtension, String namespace,
528    boolean valid)
529    {
530  1497 DefaultInstalledExtension installedExtension = this.extensions.get(localExtension.getId());
531  1497 if (installedExtension == null) {
532  1337 installedExtension = new DefaultInstalledExtension(localExtension, this);
533    }
534   
535  1497 installedExtension.setInstalled(true, namespace);
536  1497 installedExtension.setValid(namespace, valid);
537   
538  1497 addInstalledExtension(installedExtension, namespace);
539   
540  1497 return installedExtension;
541    }
542   
543    /**
544    * Register a newly installed extension in backward dependencies map.
545    *
546    * @param installedExtension the installed extension to register
547    * @param namespace the namespace
548    */
 
549  1702 toggle private void addInstalledExtension(DefaultInstalledExtension installedExtension, String namespace)
550    {
551  1702 addCachedExtension(installedExtension);
552   
553  1702 boolean isValid = installedExtension.isValid(namespace);
554   
555    // Register the extension in the installed extensions for the provided namespace
556  1702 addInstalledFeatureToCache(installedExtension.getId(), namespace, installedExtension, isValid);
557   
558    // Add virtual extensions
559  1702 for (ExtensionId feature : installedExtension.getExtensionFeatures()) {
560  404 addInstalledFeatureToCache(feature, namespace, installedExtension, isValid);
561    }
562   
563  1702 if (this.updateBackwardDependencies) {
564    // Recalculate backward dependencies index
565  205 updateMissingBackwardDependencies();
566    }
567    }
568   
 
569  436 toggle private void updateMissingBackwardDependencies()
570    {
571  436 for (DefaultInstalledExtension installedExtension : this.extensions.values()) {
572  2925 updateMissingBackwardDependencies(installedExtension);
573    }
574    }
575   
 
576  2925 toggle private void updateMissingBackwardDependencies(DefaultInstalledExtension installedExtension)
577    {
578  2925 Collection<String> namespaces = installedExtension.getNamespaces();
579   
580  2925 if (namespaces == null) {
581  1660 if (installedExtension.isValid(null)) {
582  1280 updateMissingBackwardDependencies(installedExtension, null);
583    }
584    } else {
585  1265 for (String namespace : namespaces) {
586  1604 if (installedExtension.isValid(namespace)) {
587  1527 updateMissingBackwardDependencies(installedExtension, namespace);
588    }
589    }
590    }
591    }
592   
 
593  2807 toggle private void updateMissingBackwardDependencies(DefaultInstalledExtension installedExtension, String namespace)
594    {
595    // Add the extension as backward dependency
596  2807 for (ExtensionDependency dependency : installedExtension.getDependencies()) {
597  1475 if (!this.coreExtensionRepository.exists(dependency.getId())) {
598    // Get the extension for the dependency feature for the provided namespace
599  1274 DefaultInstalledExtension dependencyLocalExtension =
600    (DefaultInstalledExtension) getInstalledExtension(dependency.getId(), namespace);
601   
602  1274 if (dependencyLocalExtension != null) {
603  1264 ExtensionId feature = dependencyLocalExtension.getExtensionFeature(dependency.getId());
604   
605    // Make sure to register backward dependency on the right namespace
606  1264 InstalledFeature dependencyInstalledExtension =
607    addInstalledFeatureToCache(feature, namespace, dependencyLocalExtension, false);
608   
609  1264 dependencyInstalledExtension.root.backwardDependencies.add(installedExtension);
610    }
611    }
612    }
613    }
614   
615    /**
616    * Get extension registered as installed for the provided feature and namespace or can register it if provided.
617    * <p>
618    * Only look at provide namespace and does take into account inheritance.
619    *
620    * @param feature the feature provided by the extension
621    * @param namespace the namespace where the extension is installed
622    * @param installedExtension the extension
623    * @return the installed extension informations
624    */
 
625  3370 toggle private InstalledFeature addInstalledFeatureToCache(ExtensionId feature, String namespace,
626    DefaultInstalledExtension installedExtension, boolean forceCreate)
627    {
628  3370 Map<String, InstalledFeature> installedExtensionsForFeature =
629    this.extensionNamespaceByFeature.get(feature.getId());
630   
631  3370 if (installedExtensionsForFeature == null) {
632  1782 installedExtensionsForFeature = new HashMap<String, InstalledFeature>();
633  1782 this.extensionNamespaceByFeature.put(feature.getId(), installedExtensionsForFeature);
634    }
635   
636  3370 InstalledFeature installedFeature = installedExtensionsForFeature.get(namespace);
637  3370 if (forceCreate || installedFeature == null) {
638    // Find or create root feature
639  2197 InstalledRootFeature rootInstalledFeature;
640  2197 if (installedExtension.getId().getId().equals(feature.getId())) {
641  1873 rootInstalledFeature = new InstalledRootFeature(namespace);
642    } else {
643  324 rootInstalledFeature = getInstalledFeatureFromCache(installedExtension.getId().getId(), namespace).root;
644    }
645   
646    // Create new feature
647  2197 installedFeature = new InstalledFeature(rootInstalledFeature, feature);
648   
649    // Add new feature
650  2197 installedExtensionsForFeature.put(namespace, installedFeature);
651    }
652   
653  3370 if (installedExtension.isValid(namespace)) {
654  3083 installedFeature.root.extension = installedExtension;
655    } else {
656  287 installedFeature.root.invalidExtensions.add(installedExtension);
657    }
658   
659  3370 return installedFeature;
660    }
661   
662    /**
663    * Get extension registered as installed for the provided feature and namespace (including on root namespace).
664    *
665    * @param feature the feature provided by the extension
666    * @param namespace the namespace where the extension is installed
667    * @return the installed extension informations
668    */
 
669  5356 toggle private InstalledFeature getInstalledFeatureFromCache(String feature, String namespace)
670    {
671  5356 if (feature == null) {
672  0 return null;
673    }
674   
675  5356 Map<String, InstalledFeature> installedExtensionsForFeature = this.extensionNamespaceByFeature.get(feature);
676   
677  5356 if (installedExtensionsForFeature == null) {
678  1338 return null;
679    }
680   
681  4018 InstalledFeature installedExtension = installedExtensionsForFeature.get(namespace);
682   
683    // Fallback on root namespace if the feature could not be found
684  4018 if (installedExtension == null && namespace != null) {
685  471 installedExtension = getInstalledFeatureFromCache(feature, null);
686    }
687   
688  4018 return installedExtension;
689    }
690   
691    // InstalledExtensionRepository
692   
 
693  4499 toggle @Override
694    public InstalledExtension getInstalledExtension(String feature, String namespace)
695    {
696  4499 InstalledFeature installedFeature = getInstalledFeatureFromCache(feature, namespace);
697   
698  4499 if (installedFeature != null) {
699  2604 if (installedFeature.root.extension != null) {
700  2590 return installedFeature.root.extension;
701    }
702   
703  14 return installedFeature.root.invalidExtensions.isEmpty() ? null
704    : installedFeature.root.invalidExtensions.iterator().next();
705    }
706   
707  1895 return null;
708    }
709   
 
710  207 toggle @Override
711    public InstalledExtension installExtension(LocalExtension extension, String namespace, boolean dependency,
712    Map<String, Object> properties) throws InstallException
713    {
714  207 DefaultInstalledExtension installedExtension = this.extensions.get(extension.getId());
715   
716  207 if (installedExtension != null && installedExtension.isInstalled(namespace)
717    && installedExtension.isValid(namespace)) {
718  2 if (installedExtension.isDependency(namespace) == dependency) {
719  1 throw new InstallException(String.format("The extension [%s] is already installed on namespace [%s]",
720    installedExtension, namespace));
721    }
722   
723  1 installedExtension.setDependency(dependency, namespace);
724   
725  1 try {
726  1 this.localRepository.setProperties(installedExtension.getLocalExtension(),
727    installedExtension.getProperties());
728    } catch (Exception e) {
729  0 throw new InstallException("Failed to modify extension descriptor", e);
730    }
731    } else {
732  205 LocalExtension localExtension = this.localRepository.getLocalExtension(extension.getId());
733   
734  205 if (localExtension == null) {
735    // Should be a very rare use case since we explicitly ask for a LocalExtension
736  0 throw new InstallException(String.format("The extension [%s] need to be stored first", extension));
737    }
738   
739  205 if (installedExtension == null) {
740  180 installedExtension = new DefaultInstalledExtension(localExtension, this);
741    }
742   
743  205 applyInstallExtension(installedExtension, namespace, dependency, properties, Collections.emptyMap());
744    }
745   
746  206 return installedExtension;
747    }
748   
 
749  121 toggle @Override
750    public void uninstallExtension(InstalledExtension extension, String namespace) throws UninstallException
751    {
752  121 DefaultInstalledExtension installedExtension =
753    (DefaultInstalledExtension) getInstalledExtension(extension.getId().getId(), namespace);
754   
755  121 if (installedExtension != null) {
756  120 applyUninstallExtension(installedExtension, namespace);
757    }
758    }
759   
 
760  120 toggle private void applyUninstallExtension(DefaultInstalledExtension installedExtension, String namespace)
761    throws UninstallException
762    {
763  120 installedExtension.setInstalled(false, namespace);
764   
765  120 try {
766  120 this.localRepository.setProperties(installedExtension.getLocalExtension(),
767    installedExtension.getProperties());
768    } catch (Exception e) {
769  0 throw new UninstallException("Failed to modify extension descriptor", e);
770    }
771   
772    // Clean caches
773   
774  120 removeInstalledExtension(installedExtension, namespace);
775    }
776   
 
777  148 toggle @Override
778    public Collection<InstalledExtension> getBackwardDependencies(String feature, String namespace)
779    throws ResolveException
780    {
781  148 if (getInstalledExtension(feature, namespace) == null) {
782  0 throw new ResolveException(
783    String.format("Extension [%s] is not installed on namespace [%s]", feature, namespace));
784    }
785   
786  148 Map<String, InstalledFeature> installedExtensionsByFeature = this.extensionNamespaceByFeature.get(feature);
787  148 if (installedExtensionsByFeature != null) {
788  148 InstalledFeature installedExtension = installedExtensionsByFeature.get(namespace);
789   
790  148 if (installedExtension != null) {
791  147 Set<DefaultInstalledExtension> backwardDependencies = installedExtension.root.backwardDependencies;
792   
793    // copy the list to allow use cases like uninstalling all backward dependencies without getting a
794    // concurrent issue on the list
795  147 return backwardDependencies.isEmpty() ? Collections.<InstalledExtension>emptyList()
796    : new ArrayList<InstalledExtension>(backwardDependencies);
797    }
798    }
799   
800  1 return Collections.emptyList();
801    }
802   
 
803  119 toggle @Override
804    public Map<String, Collection<InstalledExtension>> getBackwardDependencies(ExtensionId extensionId)
805    throws ResolveException
806    {
807  119 Map<String, Collection<InstalledExtension>> result;
808   
809  119 DefaultInstalledExtension installedExtension = resolve(extensionId);
810   
811  119 Collection<String> namespaces = installedExtension.getNamespaces();
812   
813  119 Map<String, InstalledFeature> featureExtensions =
814    this.extensionNamespaceByFeature.get(installedExtension.getId().getId());
815  119 if (featureExtensions != null) {
816  119 result = new HashMap<String, Collection<InstalledExtension>>();
817  119 for (InstalledFeature festureExtension : featureExtensions.values()) {
818  138 if ((namespaces == null || namespaces.contains(festureExtension.root.namespace))
819    && !festureExtension.root.backwardDependencies.isEmpty()) {
820    // copy the list to allow use cases like uninstalling all backward dependencies without getting a
821    // concurrent issue on the list
822  55 result.put(festureExtension.root.namespace,
823    new ArrayList<InstalledExtension>(festureExtension.root.backwardDependencies));
824    }
825    }
826    } else {
827  0 result = Collections.emptyMap();
828    }
829   
830  119 return result;
831    }
832    }