1. Project Clover database Sat Feb 2 2019 06:45:20 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

124
235
30
3
834
533
111
0.47
7.83
10
3.7

Classes

Class Line # Actions
DefaultInstalledExtensionRepository 65 232 0% 109 22
0.942708394.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 118 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  3414 toggle public InstalledRootFeature(String namespace)
79    {
80  3414 this.namespace = namespace;
81    }
82    }
83   
 
84    private static class InstalledFeature
85    {
86    public InstalledRootFeature root;
87   
88    public ExtensionId feature;
89   
 
90  4356 toggle public InstalledFeature(InstalledRootFeature root, ExtensionId feature)
91    {
92  4356 this.root = root;
93  4356 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  257 toggle @Override
133    public void initialize() throws InitializationException
134    {
135  257 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  257 this.updateBackwardDependencies = false;
141   
142    // Get installed extensions from local repository
143  257 this.localInstalledExtensionsCache = new HashMap<>();
144  257 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
145  2503 if (DefaultInstalledExtension.isInstalled(localExtension)) {
146  2377 getInstalledLocalExtension(localExtension);
147    }
148    }
149   
150    // Validate installed extensions
151  257 validate();
152   
153    // Update backward dependencies
154  257 updateMissingBackwardDependencies();
155   
156    // Put back backdependencies update for each extension add
157  257 this.updateBackwardDependencies = true;
158   
159    // Reset temporary cache
160  257 this.localInstalledExtensionsCache = null;
161    }
162   
 
163  257 toggle private void validate()
164    {
165    // Validate top level installed extensions
166  257 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
167  2503 if (DefaultInstalledExtension.isInstalled(localExtension)) {
168  2377 validateExtension(localExtension, false);
169    }
170    }
171   
172    // Validate dependencies (just in case some dependencies don't have any backward dependencies)
173  257 for (LocalExtension localExtension : this.localRepository.getLocalExtensions()) {
174  2503 if (DefaultInstalledExtension.isInstalled(localExtension)) {
175  2377 validateExtension(localExtension, true);
176    }
177    }
178    }
179   
 
180  2377 toggle private void getInstalledLocalExtension(LocalExtension localExtension)
181    {
182  2377 getInstalledLocalExtension(localExtension.getId().getId(), localExtension);
183   
184  2377 for (ExtensionId feature : localExtension.getExtensionFeatures()) {
185  778 getInstalledLocalExtension(feature.getId(), localExtension);
186    }
187    }
188   
 
189  3155 toggle private void getInstalledLocalExtension(String feature, LocalExtension localExtension)
190    {
191  3155 Collection<String> namespaces = DefaultInstalledExtension.getNamespaces(localExtension);
192   
193  3155 if (namespaces == null) {
194  1207 getInstalledLocalExtension(feature, null, localExtension);
195    } else {
196  1948 for (String namespace : namespaces) {
197  2122 getInstalledLocalExtension(feature, namespace, localExtension);
198    }
199    }
200    }
201   
 
202  3329 toggle private void getInstalledLocalExtension(String feature, String namespace, LocalExtension localExtension)
203    {
204  3329 Map<String, Set<LocalExtension>> localInstallExtensionFeature = this.localInstalledExtensionsCache.get(feature);
205  3329 if (localInstallExtensionFeature == null) {
206  3067 localInstallExtensionFeature = new HashMap<>();
207  3067 this.localInstalledExtensionsCache.put(feature, localInstallExtensionFeature);
208    }
209   
210  3329 Set<LocalExtension> localInstallExtensionNamespace = localInstallExtensionFeature.get(namespace);
211  3329 if (localInstallExtensionNamespace == null) {
212  3241 localInstallExtensionNamespace = new HashSet<LocalExtension>();
213  3241 localInstallExtensionFeature.put(namespace, localInstallExtensionNamespace);
214    }
215   
216  3329 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  4754 toggle private void validateExtension(LocalExtension localExtension, boolean dependencies)
229    {
230  4754 Collection<String> namespaces = DefaultInstalledExtension.getNamespaces(localExtension);
231   
232  4754 if (namespaces == null) {
233  1892 if (dependencies || !DefaultInstalledExtension.isDependency(localExtension, null)) {
234  1550 try {
235  1550 validateExtension(localExtension, null, Collections.emptyMap());
236    } catch (InvalidExtensionException e) {
237  206 if (this.logger.isDebugEnabled()) {
238  0 this.logger.warn("Invalid extension [{}]", localExtension.getId(), e);
239    } else {
240  206 this.logger.warn("Invalid extension [{}] ({})", localExtension.getId(),
241    ExceptionUtils.getRootCauseMessage(e));
242    }
243   
244  206 addInstalledExtension(localExtension, null, false);
245    }
246    }
247    } else {
248  2862 for (String namespace : namespaces) {
249  3210 if (dependencies || !DefaultInstalledExtension.isDependency(localExtension, namespace)) {
250  2557 try {
251  2557 validateExtension(localExtension, namespace, Collections.emptyMap());
252    } catch (InvalidExtensionException e) {
253  28 if (this.logger.isDebugEnabled()) {
254  0 this.logger.warn("Invalid extension [{}] on namespace [{}]", localExtension.getId(),
255    namespace, e);
256    } else {
257  28 this.logger.warn("Invalid extension [{}] on namespace [{}] ({})", localExtension.getId(),
258    namespace, ExceptionUtils.getRootCauseMessage(e));
259    }
260   
261  28 addInstalledExtension(localExtension, namespace, false);
262    }
263    }
264    }
265    }
266    }
267   
 
268  3846 toggle private LocalExtension getInstalledLocalExtension(ExtensionDependency dependency, String namespace)
269    {
270  3846 Map<String, Set<LocalExtension>> localInstallExtensionFeature =
271    this.localInstalledExtensionsCache.get(dependency.getId());
272   
273  3846 if (localInstallExtensionFeature != null) {
274  2382 Set<LocalExtension> localInstallExtensionNamespace = localInstallExtensionFeature.get(namespace);
275   
276  2382 if (localInstallExtensionNamespace != null) {
277  2162 for (LocalExtension dependencyVersion : localInstallExtensionNamespace) {
278  2162 if (isCompatible(dependencyVersion.getId().getVersion(), dependency.getVersionConstraint())) {
279  2162 return dependencyVersion;
280    }
281    }
282    }
283    }
284   
285    // Try on root namespace
286  1684 if (namespace != null) {
287  831 return getInstalledLocalExtension(dependency, null);
288    }
289   
290  853 return null;
291    }
292   
 
293  7892 toggle private void validateDependency(ExtensionDependency dependency, String namespace,
294    Map<String, ExtensionDependency> managedDependencies) throws InvalidExtensionException
295    {
296  7892 CoreExtension coreExtension = this.coreExtensionRepository.getCoreExtension(dependency.getId());
297   
298  7892 if (coreExtension != null) {
299  3444 if (!isCompatible(coreExtension.getId().getVersion(), dependency.getVersionConstraint())) {
300  3 throw new InvalidExtensionException(String
301    .format("Dependency [%s] is incompatible with the core extension [%s]", dependency, coreExtension));
302    }
303    } else {
304  4448 LocalExtension dependencyExtension =
305  4448 this.localInstalledExtensionsCache != null ? getInstalledLocalExtension(dependency, namespace)
306    : getInstalledExtension(dependency.getId(), namespace);
307   
308  4448 if (dependencyExtension == null) {
309  1675 throw new InvalidExtensionException(
310    String.format("No compatible extension is installed for dependency [%s]", dependency));
311    } else {
312  2773 try {
313  2773 DefaultInstalledExtension installedExtension =
314    validateExtension(dependencyExtension, namespace, managedDependencies);
315   
316  2590 if (!installedExtension.isValid(namespace)) {
317  287 throw new InvalidExtensionException(
318    String.format("Extension dependency [%s] is invalid", installedExtension.getId()));
319    }
320    } catch (InvalidExtensionException e) {
321  470 if (this.localInstalledExtensionsCache != null) {
322  334 addInstalledExtension(dependencyExtension, namespace, false);
323    }
324   
325  470 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  7745 toggle private DefaultInstalledExtension validateExtension(LocalExtension localExtension, String namespace,
341    Map<String, ExtensionDependency> managedDependencies) throws InvalidExtensionException
342    {
343  7745 DefaultInstalledExtension installedExtension = this.extensions.get(localExtension.getId());
344  7745 if (installedExtension != null && installedExtension.isValidated(namespace)) {
345    // Already validated
346  4341 return installedExtension;
347    }
348   
349    // Actually validate
350   
351  3404 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  240 return validateExtension(localExtension, null, managedDependencies);
355    }
356   
357  3164 if (!DefaultInstalledExtension.isInstalled(localExtension, namespace)) {
358  0 throw new InvalidExtensionException(String.format("Extension [%s] is not installed", localExtension));
359    }
360   
361  3164 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  3164 InvalidExtensionException dependencyException = null;
368  3164 for (ExtensionDependency dependency : localExtension.getDependencies()) {
369  7892 try {
370    // Replace with managed dependency if any
371  7892 dependency = ExtensionUtils.getDependency(dependency, managedDependencies, localExtension);
372   
373  7892 validateDependency(dependency, namespace, ExtensionUtils.append(managedDependencies, localExtension));
374    } catch (InvalidExtensionException e) {
375  2148 if (!dependency.isOptional()) {
376    // Continue to make sure all extensions are validated in the right order
377  2079 if (dependencyException == null) {
378  585 dependencyException = e;
379    }
380    }
381    }
382    }
383   
384    // Throw exception if any issue has been found with dependencies
385  3164 if (dependencyException != null) {
386  585 throw dependencyException;
387    }
388   
389    // Complete local extension installation
390  2579 return localExtension instanceof DefaultInstalledExtension ? (DefaultInstalledExtension) localExtension
391    : addInstalledExtension(localExtension, namespace, true);
392    }
393   
 
394  625 toggle private boolean isValid(DefaultInstalledExtension installedExtension, String namespace,
395    Map<String, ExtensionDependency> managedDependencies)
396    {
397  625 try {
398  625 validateExtension(installedExtension, namespace, managedDependencies);
399   
400  457 return true;
401    } catch (InvalidExtensionException e) {
402  168 this.logger.debug("Invalid extension [{}] on namespace [{}]", installedExtension.getId(), namespace, e);
403    }
404   
405  168 return false;
406    }
407   
 
408  5606 toggle private boolean isCompatible(Version existingVersion, VersionConstraint versionConstraint)
409    {
410  5606 boolean compatible = true;
411   
412  5606 if (versionConstraint.getVersion() == null) {
413  15 compatible = versionConstraint.containsVersion(existingVersion);
414    } else {
415  5591 compatible = existingVersion.compareTo(versionConstraint.getVersion()) >= 0;
416    }
417   
418  5606 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  223 toggle private void removeInstalledExtension(DefaultInstalledExtension installedExtension, String namespace)
431    {
432  223 removeInstalledFeature(installedExtension.getId().getId(), namespace);
433   
434  223 for (ExtensionId feature : installedExtension.getExtensionFeatures()) {
435  108 removeInstalledFeature(feature.getId(), namespace);
436    }
437   
438  223 removeFromBackwardDependencies(installedExtension, namespace);
439   
440  223 if (!installedExtension.isInstalled()) {
441  211 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  331 toggle private void removeInstalledFeature(String feature, String namespace)
453    {
454    // Extensions namespaces by feature
455   
456  331 if (namespace == null) {
457  74 this.extensionNamespaceByFeature.remove(feature);
458    } else {
459  257 Map<String, InstalledFeature> namespaceInstalledExtension = this.extensionNamespaceByFeature.get(feature);
460   
461  257 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  625 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  625 installedExtension.setInstalled(true, namespace);
482  625 installedExtension.setInstallDate(new Date(), namespace);
483   
484    // DEPENDENCY
485  625 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  625 installedExtension.getNamespaceProperties(namespace).putAll(properties);
490   
491    // Save properties
492  625 try {
493  625 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  625 installedExtension.setValid(namespace, isValid(installedExtension, namespace, managedDependencies));
501   
502    // Update caches
503   
504  625 addInstalledExtension(installedExtension, namespace);
505    }
506   
 
507  223 toggle private void removeFromBackwardDependencies(DefaultInstalledExtension installedExtension, String namespace)
508    {
509    // Clean provided extension dependencies backward dependencies
510  223 for (ExtensionDependency dependency : installedExtension.getDependencies()) {
511  616 if (this.coreExtensionRepository.getCoreExtension(dependency.getId()) == null) {
512  252 InstalledFeature installedFeature = getInstalledFeatureFromCache(dependency.getId(), namespace);
513   
514  252 if (installedFeature != null) {
515  244 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  2711 toggle private DefaultInstalledExtension addInstalledExtension(LocalExtension localExtension, String namespace,
530    boolean valid)
531    {
532  2711 DefaultInstalledExtension installedExtension = this.extensions.get(localExtension.getId());
533  2711 if (installedExtension == null) {
534  2377 installedExtension = new DefaultInstalledExtension(localExtension, this);
535    }
536   
537  2711 installedExtension.setInstalled(true, namespace);
538  2711 installedExtension.setValid(namespace, valid);
539   
540  2711 addInstalledExtension(installedExtension, namespace);
541   
542  2711 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  3336 toggle private void addInstalledExtension(DefaultInstalledExtension installedExtension, String namespace)
552    {
553  3336 addCachedExtension(installedExtension);
554   
555  3336 boolean isValid = installedExtension.isValid(namespace);
556   
557    // Register the extension in the installed extensions for the provided namespace
558  3336 addInstalledFeatureToCache(installedExtension.getId(), namespace, installedExtension, isValid);
559   
560    // Add virtual extensions
561  3336 for (ExtensionId feature : installedExtension.getExtensionFeatures()) {
562  1097 addInstalledFeatureToCache(feature, namespace, installedExtension, isValid);
563    }
564   
565  3336 if (this.updateBackwardDependencies) {
566    // Recalculate backward dependencies index
567  625 updateMissingBackwardDependencies();
568    }
569    }
570   
 
571  882 toggle private void updateMissingBackwardDependencies()
572    {
573  882 for (DefaultInstalledExtension installedExtension : this.extensions.values()) {
574  15600 updateMissingBackwardDependencies(installedExtension);
575    }
576    }
577   
 
578  15600 toggle private void updateMissingBackwardDependencies(DefaultInstalledExtension installedExtension)
579    {
580  15600 Collection<String> namespaces = installedExtension.getNamespaces();
581   
582  15600 if (namespaces == null) {
583  2664 if (installedExtension.isValid(null)) {
584  2255 updateMissingBackwardDependencies(installedExtension, null);
585    }
586    } else {
587  12936 for (String namespace : namespaces) {
588  13283 if (installedExtension.isValid(namespace)) {
589  11645 updateMissingBackwardDependencies(installedExtension, namespace);
590    }
591    }
592    }
593    }
594   
 
595  13900 toggle private void updateMissingBackwardDependencies(DefaultInstalledExtension installedExtension, String namespace)
596    {
597    // Add the extension as backward dependency
598  13900 for (ExtensionDependency dependency : installedExtension.getDependencies()) {
599  48801 if (!dependency.isOptional() && !this.coreExtensionRepository.exists(dependency.getId())) {
600    // Get the extension for the dependency feature for the provided namespace
601  12870 DefaultInstalledExtension dependencyLocalExtension =
602    (DefaultInstalledExtension) getInstalledExtension(dependency.getId(), namespace);
603   
604  12870 if (dependencyLocalExtension != null) {
605  12860 ExtensionId feature = dependencyLocalExtension.getExtensionFeature(dependency.getId());
606   
607    // Make sure to register backward dependency on the right namespace
608  12860 InstalledFeature dependencyInstalledExtension =
609    addInstalledFeatureToCache(feature, namespace, dependencyLocalExtension, false);
610   
611  12860 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  17293 toggle private InstalledFeature addInstalledFeatureToCache(ExtensionId feature, String namespace,
628    DefaultInstalledExtension installedExtension, boolean forceCreate)
629    {
630  17293 Map<String, InstalledFeature> installedExtensionsForFeature =
631    this.extensionNamespaceByFeature.get(feature.getId());
632   
633  17293 if (installedExtensionsForFeature == null) {
634  3718 installedExtensionsForFeature = new HashMap<String, InstalledFeature>();
635  3718 this.extensionNamespaceByFeature.put(feature.getId(), installedExtensionsForFeature);
636    }
637   
638  17293 InstalledFeature installedFeature = installedExtensionsForFeature.get(namespace);
639  17293 if (forceCreate || installedFeature == null) {
640    // Find or create root feature
641  4356 InstalledRootFeature rootInstalledFeature;
642  4356 if (installedExtension.getId().getId().equals(feature.getId())) {
643  3414 rootInstalledFeature = new InstalledRootFeature(namespace);
644    } else {
645  942 rootInstalledFeature = getInstalledFeatureFromCache(installedExtension.getId().getId(), namespace).root;
646    }
647   
648    // Create new feature
649  4356 installedFeature = new InstalledFeature(rootInstalledFeature, feature);
650   
651    // Add new feature
652  4356 installedExtensionsForFeature.put(namespace, installedFeature);
653    }
654   
655  17293 if (installedExtension.isValid(namespace)) {
656  16223 installedFeature.root.extension = installedExtension;
657    } else {
658  1070 installedFeature.root.invalidExtensions.add(installedExtension);
659    }
660   
661  17293 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  28244 toggle private InstalledFeature getInstalledFeatureFromCache(String feature, String namespace)
672    {
673  28244 if (feature == null) {
674  0 return null;
675    }
676   
677  28244 Map<String, InstalledFeature> installedExtensionsForFeature = this.extensionNamespaceByFeature.get(feature);
678   
679  28244 if (installedExtensionsForFeature == null) {
680  4676 return null;
681    }
682   
683  23568 InstalledFeature installedExtension = installedExtensionsForFeature.get(namespace);
684   
685    // Fallback on root namespace if the feature could not be found
686  23568 if (installedExtension == null && namespace != null) {
687  1339 installedExtension = getInstalledFeatureFromCache(feature, null);
688    }
689   
690  23568 return installedExtension;
691    }
692   
693    // InstalledExtensionRepository
694   
 
695  25711 toggle @Override
696    public InstalledExtension getInstalledExtension(String feature, String namespace)
697    {
698  25711 InstalledFeature installedFeature = getInstalledFeatureFromCache(feature, namespace);
699   
700  25711 if (installedFeature != null) {
701  18396 if (installedFeature.root.extension != null) {
702  17547 return installedFeature.root.extension;
703    }
704   
705  850 return installedFeature.root.invalidExtensions.isEmpty() ? null
706    : installedFeature.root.invalidExtensions.iterator().next();
707    }
708   
709  7314 return null;
710    }
711   
 
712  628 toggle @Override
713    public InstalledExtension installExtension(LocalExtension extension, String namespace, boolean dependency,
714    Map<String, Object> properties) throws InstallException
715    {
716  628 DefaultInstalledExtension installedExtension = this.extensions.get(extension.getId());
717   
718  628 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  625 LocalExtension localExtension = this.localRepository.getLocalExtension(extension.getId());
735   
736  625 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  625 if (installedExtension == null) {
742  604 installedExtension = new DefaultInstalledExtension(localExtension, this);
743    }
744   
745  625 applyInstallExtension(installedExtension, namespace, dependency, properties, Collections.emptyMap());
746    }
747   
748  627 return installedExtension;
749    }
750   
 
751  230 toggle @Override
752    public void uninstallExtension(InstalledExtension extension, String namespace) throws UninstallException
753    {
754  230 DefaultInstalledExtension installedExtension =
755    (DefaultInstalledExtension) getInstalledExtension(extension.getId().getId(), namespace);
756   
757  230 if (installedExtension != null) {
758  223 applyUninstallExtension(installedExtension, namespace);
759    }
760    }
761   
 
762  223 toggle private void applyUninstallExtension(DefaultInstalledExtension installedExtension, String namespace)
763    throws UninstallException
764    {
765  223 installedExtension.setInstalled(false, namespace);
766   
767  223 try {
768  223 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  223 removeInstalledExtension(installedExtension, namespace);
777    }
778   
 
779  894 toggle @Override
780    public Collection<InstalledExtension> getBackwardDependencies(String feature, String namespace)
781    throws ResolveException
782    {
783  894 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  894 Map<String, InstalledFeature> installedExtensionsByFeature = this.extensionNamespaceByFeature.get(feature);
789  894 if (installedExtensionsByFeature != null) {
790  894 InstalledFeature installedExtension = installedExtensionsByFeature.get(namespace);
791   
792  894 if (installedExtension != null) {
793  893 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  893 return backwardDependencies.isEmpty() ? Collections.<InstalledExtension>emptyList()
798    : new ArrayList<InstalledExtension>(backwardDependencies);
799    }
800    }
801   
802  1 return Collections.emptyList();
803    }
804   
 
805  204 toggle @Override
806    public Map<String, Collection<InstalledExtension>> getBackwardDependencies(ExtensionId extensionId)
807    throws ResolveException
808    {
809  204 Map<String, Collection<InstalledExtension>> result;
810   
811  204 DefaultInstalledExtension installedExtension = resolve(extensionId);
812   
813  204 Collection<String> namespaces = installedExtension.getNamespaces();
814   
815  204 Map<String, InstalledFeature> featureExtensions =
816    this.extensionNamespaceByFeature.get(installedExtension.getId().getId());
817  204 if (featureExtensions != null) {
818  204 result = new HashMap<String, Collection<InstalledExtension>>();
819  204 for (InstalledFeature festureExtension : featureExtensions.values()) {
820  263 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  137 result.put(festureExtension.root.namespace,
825    new ArrayList<InstalledExtension>(festureExtension.root.backwardDependencies));
826    }
827    }
828    } else {
829  0 result = Collections.emptyMap();
830    }
831   
832  204 return result;
833    }
834    }