1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.extension.job.internal

File AbstractInstallPlanJob.java

 

Coverage histogram

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

Code metrics

176
396
45
2
1,279
840
152
0.38
8.8
22.5
3.38

Classes

Class Line # Actions
AbstractInstallPlanJob 70 386 0% 144 31
0.948160594.8%
AbstractInstallPlanJob.ModifableExtensionPlanNode 72 10 0% 8 0
1.0100%
 

Contributing tests

This file is covered by 99 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.job.internal;
21   
22    import java.util.ArrayList;
23    import java.util.Collection;
24    import java.util.Collections;
25    import java.util.HashMap;
26    import java.util.HashSet;
27    import java.util.LinkedHashMap;
28    import java.util.LinkedHashSet;
29    import java.util.List;
30    import java.util.Map;
31    import java.util.Set;
32   
33    import javax.inject.Inject;
34   
35    import org.apache.commons.lang3.exception.ExceptionUtils;
36    import org.xwiki.component.manager.ComponentLookupException;
37    import org.xwiki.component.namespace.NamespaceNotAllowedException;
38    import org.xwiki.component.namespace.NamespaceValidator;
39    import org.xwiki.extension.CoreExtension;
40    import org.xwiki.extension.DefaultExtensionDependency;
41    import org.xwiki.extension.Extension;
42    import org.xwiki.extension.ExtensionDependency;
43    import org.xwiki.extension.ExtensionId;
44    import org.xwiki.extension.ExtensionManagerConfiguration;
45    import org.xwiki.extension.InstallException;
46    import org.xwiki.extension.InstalledExtension;
47    import org.xwiki.extension.ResolveException;
48    import org.xwiki.extension.UninstallException;
49    import org.xwiki.extension.handler.ExtensionHandler;
50    import org.xwiki.extension.internal.ExtensionFactory;
51    import org.xwiki.extension.internal.ExtensionUtils;
52    import org.xwiki.extension.job.ExtensionRequest;
53    import org.xwiki.extension.job.plan.ExtensionPlanAction;
54    import org.xwiki.extension.job.plan.ExtensionPlanAction.Action;
55    import org.xwiki.extension.job.plan.ExtensionPlanNode;
56    import org.xwiki.extension.job.plan.internal.DefaultExtensionPlanAction;
57    import org.xwiki.extension.job.plan.internal.DefaultExtensionPlanNode;
58    import org.xwiki.extension.job.plan.internal.DefaultExtensionPlanTree;
59    import org.xwiki.extension.repository.CoreExtensionRepository;
60    import org.xwiki.extension.repository.ExtensionRepositoryManager;
61    import org.xwiki.extension.version.IncompatibleVersionConstraintException;
62    import org.xwiki.extension.version.VersionConstraint;
63   
64    /**
65    * Create an Extension plan.
66    *
67    * @version $Id: f7696e7cf4fd034848ef80a194ee74ac1ce6280c $
68    * @since 4.1M1
69    */
 
70    public abstract class AbstractInstallPlanJob<R extends ExtensionRequest> extends AbstractExtensionPlanJob<R>
71    {
 
72    protected static class ModifableExtensionPlanNode extends DefaultExtensionPlanNode
73    {
74    // never change
75   
76    private final ExtensionDependency initialDependency;
77   
78    // can change
79   
80    public VersionConstraint versionConstraint;
81   
82    public final List<ModifableExtensionPlanNode> duplicates = new ArrayList<>();
83   
84    // helpers
85   
 
86  183 toggle public ModifableExtensionPlanNode()
87    {
88  183 this.initialDependency = null;
89    }
90   
 
91  1882 toggle public ModifableExtensionPlanNode(ExtensionDependency initialDependency, VersionConstraint versionConstraint)
92    {
93  1882 this.initialDependency = initialDependency;
94  1882 this.versionConstraint = versionConstraint;
95    }
96   
 
97  317 toggle public ModifableExtensionPlanNode(ExtensionDependency initialDependency, ModifableExtensionPlanNode node)
98    {
99  317 this.initialDependency = initialDependency;
100   
101  317 set(node);
102    }
103   
 
104  966 toggle public void set(ModifableExtensionPlanNode node)
105    {
106  966 this.action = node.action;
107  966 this.children = node.children;
108    }
109   
 
110  3051544 toggle @Override
111    public VersionConstraint getInitialVersionConstraint()
112    {
113  3051544 return this.initialDependency != null ? this.initialDependency.getVersionConstraint() : null;
114    }
115   
 
116  2065 toggle public void setAction(ExtensionPlanAction action)
117    {
118  2065 this.action = action;
119    }
120   
 
121  675 toggle public void setChildren(Collection<? extends ExtensionPlanNode> children)
122    {
123  675 this.children = (Collection) children;
124    }
125    }
126   
127    /**
128    * Used to resolve extensions to install.
129    */
130    @Inject
131    protected ExtensionRepositoryManager repositoryManager;
132   
133    /**
134    * Used to check if extension or its dependencies are already core extensions.
135    */
136    @Inject
137    protected CoreExtensionRepository coreExtensionRepository;
138   
139    @Inject
140    protected NamespaceValidator namespaceResolver;
141   
142    @Inject
143    protected ExtensionManagerConfiguration configuration;
144   
145    @Inject
146    protected ExtensionFactory factory;
147   
148    /**
149    * Used to make sure dependencies are compatible between each other in the whole plan.
150    * <p>
151    * <id, <namespace, node>>.
152    */
153    protected Map<String, Map<String, ModifableExtensionPlanNode>> extensionsNodeCache = new HashMap<>();
154   
 
155  9 toggle protected void setExtensionTree(DefaultExtensionPlanTree extensionTree)
156    {
157  9 this.extensionTree = extensionTree;
158  9 this.status.setTree(this.extensionTree);
159    }
160   
161    /**
162    * @param extensionsByNamespace the map to fill
163    * @param extensionId the id of the extension to install/upgrade
164    * @param namespace the namespace where to install the extension
165    */
 
166  200 toggle protected void addExtensionToProcess(Map<ExtensionId, Collection<String>> extensionsByNamespace,
167    ExtensionId extensionId, String namespace)
168    {
169  200 Collection<String> namespaces;
170   
171    // Get namespaces
172  200 if (extensionsByNamespace.containsKey(extensionId) && namespace != null) {
173  1 namespaces = extensionsByNamespace.get(extensionId);
174    } else {
175  199 if (namespace == null) {
176  81 namespaces = null;
177    } else {
178  118 namespaces = new HashSet<>();
179    }
180   
181  199 extensionsByNamespace.put(extensionId, namespaces);
182    }
183   
184    // Add namespace
185  200 if (namespaces != null) {
186  119 namespaces.add(namespace);
187    }
188    }
189   
 
190  187 toggle protected void start(Map<ExtensionId, Collection<String>> extensionsByNamespace) throws Exception
191    {
192  187 this.progressManager.pushLevelProgress(extensionsByNamespace.size(), this);
193   
194  187 try {
195  187 for (Map.Entry<ExtensionId, Collection<String>> entry : extensionsByNamespace.entrySet()) {
196  199 this.progressManager.startStep(this);
197   
198  199 ExtensionId extensionId = entry.getKey();
199  199 Collection<String> namespaces = entry.getValue();
200   
201  199 if (namespaces != null) {
202  118 this.progressManager.pushLevelProgress(namespaces.size(), this);
203   
204  118 try {
205  118 for (String namespace : namespaces) {
206  119 this.progressManager.startStep(this);
207   
208  119 installExtension(extensionId, namespace, this.extensionTree);
209   
210  111 this.progressManager.endStep(this);
211    }
212    } finally {
213  118 this.progressManager.popLevelProgress(this);
214    }
215    } else {
216  81 installExtension(extensionId, null, this.extensionTree);
217    }
218   
219  180 this.progressManager.endStep(this);
220    }
221    } finally {
222  187 this.progressManager.popLevelProgress(this);
223    }
224    }
225   
 
226  2062 toggle private ModifableExtensionPlanNode getExtensionNode(String id, String namespace)
227    {
228  2062 Map<String, ModifableExtensionPlanNode> extensionsById = this.extensionsNodeCache.get(id);
229   
230  2062 ModifableExtensionPlanNode node = null;
231  2062 if (extensionsById != null) {
232  328 node = extensionsById.get(namespace);
233   
234  328 if (node == null && namespace != null) {
235  4 node = extensionsById.get(null);
236    }
237    }
238   
239  2062 return node;
240    }
241   
 
242  1011 toggle private void addExtensionNode(ModifableExtensionPlanNode node)
243    {
244  1011 String id = node.getAction().getExtension().getId().getId();
245   
246  1011 Map<String, ModifableExtensionPlanNode> extensionsById = this.extensionsNodeCache.get(id);
247   
248  1011 if (extensionsById == null) {
249  692 extensionsById = new HashMap<>();
250  692 this.extensionsNodeCache.put(id, extensionsById);
251    }
252   
253  1011 ModifableExtensionPlanNode existingNode = extensionsById.get(node.getAction().getNamespace());
254   
255  1011 if (existingNode != null) {
256  317 existingNode.set(node);
257  317 for (ModifableExtensionPlanNode duplicate : existingNode.duplicates) {
258  332 duplicate.set(node);
259    }
260  317 existingNode.duplicates.add(node);
261    } else {
262  694 extensionsById.put(node.getAction().getNamespace(), node);
263    }
264    }
265   
266    /**
267    * Install provided extension.
268    *
269    * @param extensionId the identifier of the extension to install
270    * @param namespace the namespace where to install the extension
271    * @param parentBranch the children of the parent {@link DefaultExtensionPlanNode}
272    * @throws InstallException error when trying to install provided extension
273    */
 
274  237 toggle protected void installExtension(ExtensionId extensionId, String namespace, DefaultExtensionPlanTree parentBranch)
275    throws InstallException
276    {
277  237 installExtension(extensionId, false, namespace, parentBranch);
278    }
279   
280    /**
281    * Install provided extension.
282    *
283    * @param extensionId the identifier of the extension to install
284    * @param dependency indicate if the extension is installed as a dependency
285    * @param namespace the namespace where to install the extension
286    * @param parentBranch the children of the parent {@link DefaultExtensionPlanNode}
287    * @throws InstallException error when trying to install provided extension
288    */
 
289  237 toggle protected void installExtension(ExtensionId extensionId, boolean dependency, String namespace,
290    DefaultExtensionPlanTree parentBranch) throws InstallException
291    {
292  237 if (getRequest().isVerbose()) {
293  113 if (namespace != null) {
294  84 this.logger.info(LOG_RESOLVE_NAMESPACE, "Resolving extension [{}] on namespace [{}]", extensionId,
295    namespace);
296    } else {
297  29 this.logger.info(LOG_RESOLVE, "Resolving extension [{}] on all namespaces", extensionId);
298    }
299    }
300   
301    // Check if the feature is already in the install plan
302  237 if (checkExistingPlanNodeExtension(extensionId, true, namespace)) {
303  7 return;
304    }
305   
306    // Check if the feature is a core extension
307  230 checkCoreExtension(extensionId.getId());
308   
309  228 ModifableExtensionPlanNode node = installExtension(extensionId, dependency, namespace);
310   
311  183 addExtensionNode(node);
312  183 parentBranch.add(node);
313    }
314   
 
315  2216 toggle private boolean checkCoreDependency(ExtensionDependency extensionDependency,
316    List<ModifableExtensionPlanNode> parentBranch) throws InstallException
317    {
318  2216 CoreExtension coreExtension = this.coreExtensionRepository.getCoreExtension(extensionDependency.getId());
319   
320  2216 if (coreExtension != null) {
321  1372 ExtensionId feature = coreExtension.getExtensionFeature(extensionDependency.getId());
322  1372 if (!extensionDependency.getVersionConstraint().isCompatible(feature.getVersion())) {
323  1 throw new InstallException("Dependency [" + extensionDependency
324    + "] is not compatible with core extension feature [" + feature + "] ([" + coreExtension + "])");
325    } else {
326  1371 if (getRequest().isVerbose()) {
327  949 this.logger.debug(
328    "There is already a core extension feature [{}] ([{}]) covering extension dependency [{}]",
329    feature, coreExtension, extensionDependency);
330    }
331   
332  1371 ModifableExtensionPlanNode node =
333    new ModifableExtensionPlanNode(extensionDependency, extensionDependency.getVersionConstraint());
334  1371 node.setAction(
335    new DefaultExtensionPlanAction(coreExtension, coreExtension, null, Action.NONE, null, true));
336   
337  1371 parentBranch.add(node);
338   
339  1371 return true;
340    }
341    }
342   
343  844 return false;
344    }
345   
 
346  837 toggle private VersionConstraint checkExistingPlanNodeDependency(ExtensionDependency extensionDependency, String namespace,
347    List<ModifableExtensionPlanNode> parentBranch, VersionConstraint previousVersionConstraint)
348    throws InstallException
349    {
350  837 VersionConstraint versionConstraint = previousVersionConstraint;
351   
352  837 ModifableExtensionPlanNode existingNode = getExtensionNode(extensionDependency.getId(), namespace);
353  837 if (existingNode != null) {
354  317 if (versionConstraint.isCompatible(existingNode.getAction().getExtension().getId().getVersion())) {
355  317 ModifableExtensionPlanNode node = new ModifableExtensionPlanNode(extensionDependency, existingNode);
356  317 addExtensionNode(node);
357  317 parentBranch.add(node);
358   
359  317 return null;
360    } else {
361  0 if (existingNode.versionConstraint != null) {
362  0 try {
363  0 versionConstraint = versionConstraint.merge(existingNode.versionConstraint);
364    } catch (IncompatibleVersionConstraintException e) {
365  0 throw new InstallException("Dependency [" + extensionDependency
366    + "] is incompatible with current constaint [" + existingNode.versionConstraint + "]", e);
367    }
368    } else {
369  0 throw new InstallException("Dependency [" + extensionDependency + "] incompatible with extension ["
370    + existingNode.getAction().getExtension() + "]");
371    }
372    }
373    }
374   
375  520 return versionConstraint;
376    }
377   
 
378  697 toggle private boolean checkExistingPlanNodeExtension(Extension extension, String namespace) throws InstallException
379    {
380  697 if (checkExistingPlanNodeExtension(extension.getId(), true, namespace)) {
381  0 return true;
382    }
383   
384  697 for (ExtensionId feature : extension.getExtensionFeatures()) {
385  291 checkExistingPlanNodeExtension(feature, false, namespace);
386    }
387   
388  697 return false;
389    }
390   
 
391  1225 toggle private boolean checkExistingPlanNodeExtension(ExtensionId extensionId, boolean isId, String namespace)
392    throws InstallException
393    {
394  1225 ModifableExtensionPlanNode existingNode = getExtensionNode(extensionId.getId(), namespace);
395  1225 if (existingNode != null) {
396  7 if (isId && existingNode.getAction().getExtension().getId().equals(extensionId)) {
397    // Same extension already planned for install
398  7 return true;
399    }
400   
401  0 if (existingNode.versionConstraint != null) {
402  0 if (!existingNode.versionConstraint.isCompatible(extensionId.getVersion())) {
403  0 throw new InstallException(
404    String.format("Extension feature [%s] is incompatible with existing constraint [%s]",
405    extensionId, existingNode.versionConstraint));
406    }
407    }
408    }
409   
410  1218 return false;
411    }
412   
 
413  520 toggle private ExtensionDependency checkInstalledDependency(ExtensionDependency extensionDependency,
414    VersionConstraint versionConstraint, String namespace, List<ModifableExtensionPlanNode> parentBranch)
415    throws InstallException
416    {
417  520 InstalledExtension installedExtension =
418    this.installedExtensionRepository.getInstalledExtension(extensionDependency.getId(), namespace);
419   
420  520 ExtensionDependency targetDependency = extensionDependency;
421   
422  520 if (installedExtension != null) {
423    // Check if already installed version is compatible
424  226 if (installedExtension.isValid(namespace)) {
425  202 ExtensionId feature = installedExtension.getExtensionFeature(extensionDependency.getId());
426   
427  202 if (versionConstraint.isCompatible(feature.getVersion())) {
428  19 if (getRequest().isVerbose()) {
429  10 this.logger.debug(
430    "There is already an installed extension [{}] covering extension dependency [{}]",
431    installedExtension.getId(), extensionDependency);
432    }
433   
434  19 ModifableExtensionPlanNode node =
435    new ModifableExtensionPlanNode(extensionDependency, versionConstraint);
436  19 node.setAction(new DefaultExtensionPlanAction(installedExtension, installedExtension, null,
437    Action.NONE, namespace, installedExtension.isDependency(namespace)));
438   
439  19 addExtensionNode(node);
440  19 parentBranch.add(node);
441   
442  19 return null;
443    }
444    }
445   
446    // If incompatible root extension fail
447  207 if (namespace != null && installedExtension.isInstalled(null)) {
448  1 if (!getRequest().isRootModificationsAllowed()) {
449  1 throw new InstallException(
450    String.format("Dependency [%s] is incompatible with installed root extension [%s]",
451    extensionDependency, installedExtension.getId()));
452    }
453    }
454   
455    // If not compatible with it, try to merge dependencies constraint of all backward dependencies to find a
456    // new compatible version for this extension
457  206 VersionConstraint mergedVersionContraint;
458  206 try {
459  206 if (installedExtension.isInstalled(null)) {
460  16 Map<String, Collection<InstalledExtension>> backwardDependencies =
461    this.installedExtensionRepository.getBackwardDependencies(installedExtension.getId());
462   
463  16 mergedVersionContraint = mergeVersionConstraints(backwardDependencies.get(null),
464    extensionDependency.getId(), versionConstraint);
465  16 if (namespace != null) {
466  0 mergedVersionContraint = mergeVersionConstraints(backwardDependencies.get(namespace),
467    extensionDependency.getId(), mergedVersionContraint);
468    }
469    } else {
470  190 Collection<InstalledExtension> backwardDependencies = this.installedExtensionRepository
471    .getBackwardDependencies(installedExtension.getId().getId(), namespace);
472   
473  190 mergedVersionContraint =
474    mergeVersionConstraints(backwardDependencies, extensionDependency.getId(), versionConstraint);
475    }
476    } catch (IncompatibleVersionConstraintException e) {
477  0 throw new InstallException("Provided depency is incompatible with already installed extensions", e);
478    } catch (ResolveException e) {
479  0 throw new InstallException("Failed to resolve backward dependencies", e);
480    }
481   
482  206 if (mergedVersionContraint != versionConstraint) {
483  66 targetDependency = new DefaultExtensionDependency(extensionDependency, mergedVersionContraint);
484    }
485    }
486   
487  500 return targetDependency;
488    }
489   
490    /**
491    * Install provided extension dependency.
492    *
493    * @param extensionDependency the extension dependency to install
494    * @param namespace the namespace where to install the extension
495    * @param parentBranch the children of the parent {@link DefaultExtensionPlanNode}
496    * @param managedDependencies the managed dependencies
497    * @param parents the parents extensions (which triggered this extension install)
498    * @throws InstallException error when trying to install provided extension
499    * @throws ResolveException
500    * @throws IncompatibleVersionConstraintException
501    */
 
502  437 toggle private void installExtensionDependency(ExtensionDependency extensionDependency, String namespace,
503    List<ModifableExtensionPlanNode> parentBranch, Map<String, ExtensionDependency> managedDependencies,
504    Set<String> parents) throws InstallException, IncompatibleVersionConstraintException, ResolveException
505    {
506  437 if (extensionDependency.isOptional()) {
507  19 installOptionalExtensionDependency(extensionDependency, namespace, parentBranch, managedDependencies,
508    parents);
509    } else {
510  418 installMandatoryExtensionDependency(extensionDependency, namespace, parentBranch, managedDependencies,
511    parents);
512    }
513    }
514   
515    /**
516    * Install provided extension dependency.
517    *
518    * @param extensionDependency the extension dependency to install
519    * @param namespace the namespace where to install the extension
520    * @param parentBranch the children of the parent {@link DefaultExtensionPlanNode}
521    * @param managedDependencies the managed dependencies
522    * @param parents the parents extensions (which triggered this extension install)
523    * @throws InstallException error when trying to install provided extension
524    * @throws ResolveException
525    * @throws IncompatibleVersionConstraintException
526    */
 
527  1791 toggle private boolean installOptionalExtensionDependency(ExtensionDependency extensionDependency, String namespace,
528    List<ModifableExtensionPlanNode> parentBranch, Map<String, ExtensionDependency> managedDependencies,
529    Set<String> parents)
530    {
531    // Save current plan
532  1791 List<ModifableExtensionPlanNode> dependencyBranch = new ArrayList<>();
533   
534  1791 try {
535  1791 installMandatoryExtensionDependency(extensionDependency, namespace, dependencyBranch, managedDependencies,
536    parents);
537   
538  1786 parentBranch.addAll(dependencyBranch);
539   
540  1786 return true;
541    } catch (Throwable e) {
542  5 if (getRequest().isVerbose()) {
543  3 this.logger.warn("Failed to install optional dependency [{}] with error: {}", extensionDependency,
544    ExceptionUtils.getRootCauseMessage(e));
545    }
546    }
547   
548  5 return false;
549    }
550   
551    /**
552    * Install provided extension dependency.
553    *
554    * @param extensionDependency the extension dependency to install
555    * @param namespace the namespace where to install the extension
556    * @param parentBranch the children of the parent {@link DefaultExtensionPlanNode}
557    * @param managedDependencies the managed dependencies
558    * @param parents the parents extensions (which triggered this extension install)
559    * @throws InstallException error when trying to install provided extension
560    * @throws ResolveException
561    * @throws IncompatibleVersionConstraintException
562    */
 
563  2216 toggle protected void installMandatoryExtensionDependency(ExtensionDependency extensionDependency, String namespace,
564    List<ModifableExtensionPlanNode> parentBranch, Map<String, ExtensionDependency> managedDependencies,
565    Set<String> parents) throws InstallException, IncompatibleVersionConstraintException, ResolveException
566    {
567    // Make sure the dependency have a version constraint
568  2216 if (extensionDependency.getVersionConstraint() == null) {
569    // TODO: install the last version instead of failing ?
570  0 throw new InstallException("Dependency [" + extensionDependency + "] does not have any version constraint");
571    }
572   
573  2216 if (getRequest().isVerbose()) {
574  1504 if (namespace != null) {
575  1436 this.logger.info(LOG_RESOLVEDEPENDENCY_NAMESPACE,
576    "Resolving extension dependency [{}] on namespace [{}]", extensionDependency, namespace);
577    } else {
578  68 this.logger.info(LOG_RESOLVEDEPENDENCY, "Resolving extension dependency [{}] on all namespaces",
579    extensionDependency);
580    }
581    }
582   
583  2216 VersionConstraint versionConstraint = extensionDependency.getVersionConstraint();
584   
585    // Make sure the dependency is not already a core extension
586  2215 if (checkCoreDependency(extensionDependency, parentBranch)) {
587    // Already exists and added to the tree by #checkCoreExtension
588  1371 return;
589    }
590   
591    // If the dependency matches an incompatible root extension switch target namespace to root (to try to
592    // upgrade/downgrade/replace it)
593  844 if (namespace != null && getRequest().isRootModificationsAllowed()
594    && hasIncompatileRootDependency(extensionDependency)) {
595  7 installMandatoryExtensionDependency(extensionDependency, null, parentBranch, managedDependencies, parents);
596   
597  7 return;
598    }
599   
600    // Make sure the dependency is not already in the current plan
601  837 versionConstraint =
602    checkExistingPlanNodeDependency(extensionDependency, namespace, parentBranch, versionConstraint);
603  837 if (versionConstraint == null) {
604    // Already exists and added to the tree by #checkExistingPlan
605  317 return;
606    }
607   
608    // Check installed extensions
609  520 ExtensionDependency targetDependency =
610    checkInstalledDependency(extensionDependency, versionConstraint, namespace, parentBranch);
611  519 if (targetDependency == null) {
612    // Already exists and added to the tree by #checkInstalledExtension
613  19 return;
614    }
615   
616    // For root dependency make sure to generate a version constraint compatible with any already existing
617    // namespace extension backward dependency
618  500 if (namespace == null) {
619  106 versionConstraint = mergeBackwardDependenciesVersionConstraints(targetDependency.getId(), namespace,
620    targetDependency.getVersionConstraint());
621  106 targetDependency = new DefaultExtensionDependency(targetDependency, versionConstraint);
622    }
623   
624    // Not found locally, search it remotely
625  500 ModifableExtensionPlanNode node =
626    installExtensionDependency(targetDependency, true, namespace, managedDependencies, parents);
627   
628  492 node.versionConstraint = versionConstraint;
629   
630  492 addExtensionNode(node);
631  492 parentBranch.add(node);
632    }
633   
634    /**
635    * Install provided extension.
636    *
637    * @param targetDependency used to search the extension to install in remote repositories
638    * @param dependency indicate if the extension is installed as a dependency
639    * @param namespace the namespace where to install the extension
640    * @param managedDependencies the managed dependencies
641    * @param parents the parents extensions (which triggered this extension install)
642    * @return the install plan node for the provided extension
643    * @throws InstallException error when trying to install provided extension
644    */
 
645  500 toggle private ModifableExtensionPlanNode installExtensionDependency(ExtensionDependency targetDependency,
646    boolean dependency, String namespace, Map<String, ExtensionDependency> managedDependencies, Set<String> parents)
647    throws InstallException
648    {
649  500 this.progressManager.pushLevelProgress(2, this);
650   
651  500 try {
652  500 this.progressManager.startStep(this);
653   
654    // Check if the extension is already in local repository
655  500 Extension extension = resolveExtension(targetDependency);
656   
657    // Rewrite the extension
658  492 Extension rewrittenExtension;
659  492 if (getRequest().getRewriter() != null) {
660  304 rewrittenExtension = getRequest().getRewriter().rewrite(extension);
661    } else {
662  188 rewrittenExtension = extension;
663    }
664   
665  492 this.progressManager.endStep(this);
666   
667  492 this.progressManager.startStep(this);
668   
669  492 try {
670  492 return installExtension(extension, rewrittenExtension, dependency, namespace, targetDependency,
671    managedDependencies, parents);
672    } catch (Exception e) {
673  0 throw new InstallException(
674    String.format("Failed to create an install plan for extension dependency [%s]", targetDependency),
675    e);
676    }
677    } finally {
678  500 this.progressManager.popLevelProgress(this);
679    }
680    }
681   
682    /**
683    * @param extensions the extensions containing the dependencies for which to merge the constraints
684    * @param feature the id of the dependency
685    * @param previousMergedVersionContraint if not null it's merged with the provided extension dependencies version
686    * constraints
687    * @return the merged version constraint
688    * @throws IncompatibleVersionConstraintException the provided version constraint is compatible with the provided
689    * version constraint
690    */
 
691  356 toggle private VersionConstraint mergeVersionConstraints(Collection<? extends Extension> extensions, String feature,
692    VersionConstraint previousMergedVersionContraint) throws IncompatibleVersionConstraintException
693    {
694  356 VersionConstraint mergedVersionContraint = previousMergedVersionContraint;
695   
696  356 if (extensions != null) {
697  347 for (Extension extension : extensions) {
698  367 ExtensionDependency dependency = getDependency(extension, feature);
699   
700  367 if (dependency != null) {
701  305 if (mergedVersionContraint == null) {
702  102 mergedVersionContraint = dependency.getVersionConstraint();
703    } else {
704  203 mergedVersionContraint = mergedVersionContraint.merge(dependency.getVersionConstraint());
705    }
706    }
707    }
708    }
709   
710  356 return mergedVersionContraint;
711    }
712   
713    /**
714    * Extract extension with the provided id from the provided extension.
715    *
716    * @param extension the extension
717    * @param dependencyId the id of the dependency
718    * @return the extension dependency or null if none has been found
719    */
 
720  367 toggle private ExtensionDependency getDependency(Extension extension, String dependencyId)
721    {
722  367 for (ExtensionDependency dependency : extension.getDependencies()) {
723  1396 if (dependency.getId().equals(dependencyId)) {
724  305 return dependency;
725    }
726    }
727   
728  62 return null;
729    }
730   
731    /**
732    * Install provided extension.
733    *
734    * @param extensionId the identifier of the extension to install
735    * @param dependency indicate if the extension is installed as a dependency
736    * @param namespace the namespace where to install the extension
737    * @return the install plan node for the provided extension
738    * @throws InstallException error when trying to install provided extension
739    */
 
740  229 toggle private ModifableExtensionPlanNode installExtension(ExtensionId extensionId, boolean dependency, String namespace)
741    throws InstallException
742    {
743    // Check if the feature is a root extension
744  229 if (namespace != null) {
745    // Check if the extension already exist on root, throw exception if not allowed
746  135 if (checkRootExtension(extensionId.getId())) {
747    // Restart install on root
748  1 return installExtension(extensionId, dependency, null);
749    }
750    }
751   
752  227 this.progressManager.pushLevelProgress(2, this);
753   
754  227 try {
755  227 this.progressManager.startStep(this);
756   
757    // Find the extension in a repository (starting with local one)
758  227 Extension extension = resolveExtension(extensionId);
759   
760    // Rewrite the extension
761  226 Extension rewrittenExtension;
762  226 if (getRequest().getRewriter() != null) {
763  33 rewrittenExtension = getRequest().getRewriter().rewrite(extension);
764    } else {
765  193 rewrittenExtension = extension;
766    }
767   
768  226 this.progressManager.endStep(this);
769   
770  226 this.progressManager.startStep(this);
771   
772  226 try {
773  226 return installExtension(extension, rewrittenExtension, dependency, namespace, null,
774    Collections.emptyMap(), null);
775    } catch (Exception e) {
776  43 throw new InstallException("Failed to resolve extension", e);
777    }
778    } finally {
779  227 this.progressManager.popLevelProgress(this);
780    }
781    }
782   
783    /**
784    * @param extensionId the identifier of the extension to install
785    * @return the extension
786    * @throws InstallException error when trying to resolve extension
787    */
 
788  227 toggle private Extension resolveExtension(ExtensionId extensionId) throws InstallException
789    {
790    // Check if the extension is already in local repository
791  227 Extension extension = this.localExtensionRepository.getLocalExtension(extensionId);
792   
793  227 if (extension == null) {
794  177 this.logger.debug("Can't find extension in local repository, trying to download it.");
795   
796    // Resolve extension
797  177 try {
798  177 extension = this.repositoryManager.resolve(extensionId);
799    } catch (ResolveException e1) {
800  1 throw new InstallException(String.format("Failed to resolve extension [%s]", extensionId), e1);
801    }
802    }
803   
804  226 return extension;
805    }
806   
807    /**
808    * @param extensionDependency describe the extension to install
809    * @return the extension
810    * @throws InstallException error when trying to resolve extension
811    */
 
812  500 toggle private Extension resolveExtension(ExtensionDependency extensionDependency) throws InstallException
813    {
814    // Check is the extension is already in local repository
815  500 Extension extension;
816  500 try {
817  500 extension = this.localExtensionRepository.resolve(extensionDependency);
818    } catch (ResolveException e) {
819  245 this.logger.debug("Can't find extension dependency in local repository, trying to download it.", e);
820   
821    // Resolve extension
822  245 try {
823  245 extension = this.repositoryManager.resolve(extensionDependency);
824    } catch (ResolveException e1) {
825  8 throw new InstallException(
826    String.format("Failed to resolve extension dependency [%s]", extensionDependency), e1);
827    }
828    }
829   
830  492 return extension;
831    }
832   
 
833  684 toggle protected boolean isNamespaceAllowed(Extension extension, String namespace)
834    {
835  684 return this.namespaceResolver.isAllowed(extension.getAllowedNamespaces(), namespace);
836    }
837   
 
838  564 toggle protected void checkTypeInstall(Extension extension, String namespace) throws InstallException
839    {
840  564 ExtensionHandler extensionHandler;
841   
842    // Is type supported ?
843  564 try {
844  564 extensionHandler = this.componentManager.getInstance(ExtensionHandler.class, extension.getType());
845    } catch (ComponentLookupException e) {
846  1 throw new InstallException(String.format("Unsupported type [%s]", extension.getType()), e);
847    }
848   
849    // Is installing the extension allowed ?
850  563 extensionHandler.checkInstall(extension, namespace, getRequest());
851    }
852   
853    /**
854    * @param sourceExtension the new extension to install
855    * @param rewrittenExtension the rewritten version of the passed extension
856    * @param dependency indicate if the extension is installed as a dependency
857    * @param namespace the namespace where to install the extension
858    * @param initialDependency the initial dependency used to resolve the extension
859    * @param managedDependencies the managed dependencies
860    * @param parents the parents extensions (which triggered this extension install)
861    * @return the install plan node for the provided extension
862    * @throws InstallException error when trying to install provided extension
863    * @throws IncompatibleVersionConstraintException
864    * @throws UninstallException
865    * @throws NamespaceNotAllowedException when passed namespace is not compatible with the passed extension
866    */
 
867  749 toggle private ModifableExtensionPlanNode installExtension(Extension sourceExtension, Extension rewrittenExtension,
868    boolean dependency, String namespace, ExtensionDependency initialDependency,
869    Map<String, ExtensionDependency> managedDependencies, Set<String> parents) throws InstallException,
870    ResolveException, IncompatibleVersionConstraintException, UninstallException, NamespaceNotAllowedException
871    {
872  749 boolean allowed = isNamespaceAllowed(rewrittenExtension, namespace);
873   
874    // Check if the namespace is compatible with the Extension
875  749 if (!allowed) {
876  52 if (namespace != null) {
877  32 if (getRequest().isRootModificationsAllowed()) {
878    // Try to install it on root namespace
879  31 return installExtension(sourceExtension, rewrittenExtension, dependency, null, initialDependency,
880    managedDependencies, parents);
881    }
882    }
883   
884    // Extension is not allowed on target namespace
885  21 throw new NamespaceNotAllowedException(
886    String.format("Extension [%s] is not allowed on namespace [%s]. Allowed namespaces are: %s",
887    rewrittenExtension.getId(), namespace, rewrittenExtension.getAllowedNamespaces()));
888    }
889   
890    // Check if the extension is already in the install plan
891  697 checkExistingPlanNodeExtension(rewrittenExtension, namespace);
892   
893    // Check if the extension matches a root extension
894  697 if (namespace != null) {
895    // Check if the extension already exist on root, throw exception if not allowed
896  487 if (checkRootExtension(rewrittenExtension)) {
897    // Restart install on root
898  0 return installExtension(sourceExtension, rewrittenExtension, dependency, null, initialDependency,
899    managedDependencies, parents);
900    }
901    }
902   
903    // Check if the extension is already installed
904  697 Extension installedExtension = checkInstalledExtension(rewrittenExtension, namespace);
905  695 if (installedExtension != rewrittenExtension) {
906  15 sourceExtension = installedExtension;
907   
908    // Rewrite the extension
909  15 if (getRequest().getRewriter() != null) {
910  0 rewrittenExtension = getRequest().getRewriter().rewrite(installedExtension);
911    } else {
912  15 rewrittenExtension = installedExtension;
913    }
914    }
915   
916    // Check if the extension is a core extension
917  695 checkCoreExtension(rewrittenExtension);
918   
919    // Find out what need to be upgraded and uninstalled
920   
921  693 this.progressManager.pushLevelProgress(5, this);
922   
923  693 try {
924  693 this.progressManager.startStep(this);
925   
926    // Is installing the extension supported/allowed ?
927  693 checkTypeInstall(rewrittenExtension, namespace);
928   
929  687 this.progressManager.endStep(this);
930   
931  687 this.progressManager.startStep(this);
932   
933    // Find all existing versions of the extension
934  687 Set<InstalledExtension> previousExtensions = getReplacedInstalledExtensions(rewrittenExtension, namespace);
935   
936  681 this.progressManager.endStep(this);
937   
938  681 this.progressManager.startStep(this);
939   
940    // Mark replaced extensions as uninstalled
941  681 for (InstalledExtension previousExtension : new ArrayList<>(previousExtensions)) {
942  259 if (!previousExtension.isInstalled(namespace)
943    || !previousExtension.getId().getId().equals(rewrittenExtension.getId().getId())) {
944  29 if (namespace == null && previousExtension.getNamespaces() != null) {
945  12 for (String previousNamespace : previousExtension.getNamespaces()) {
946  12 uninstallExtension(previousExtension, previousNamespace, this.extensionTree, false);
947  12 previousExtensions.remove(previousExtension);
948    }
949    } else {
950  17 uninstallExtension(previousExtension, namespace, this.extensionTree, false);
951    }
952    }
953    }
954   
955  680 this.progressManager.endStep(this);
956   
957  680 this.progressManager.startStep(this);
958   
959    // Check dependencies
960  680 Collection<? extends ExtensionDependency> dependencies = rewrittenExtension.getDependencies();
961   
962  680 List<ModifableExtensionPlanNode> children = null;
963  680 if (!dependencies.isEmpty()) {
964  480 parents = ExtensionUtils.append(parents, rewrittenExtension.getId().getId());
965   
966  480 this.progressManager.pushLevelProgress(dependencies.size() + 1, this);
967   
968  480 try {
969  480 children = new ArrayList<>();
970  480 for (ExtensionDependency extensionDependency : dependencies) {
971  2206 this.progressManager.startStep(this);
972   
973  2206 if (parents.contains(extensionDependency.getId())) {
974    // In case of cross dependency just ignore it
975  1 continue;
976    }
977   
978    // Replace with managed dependency if any
979  2205 extensionDependency =
980    ExtensionUtils.getDependency(extensionDependency, managedDependencies, rewrittenExtension);
981   
982    // Try installing recommended version (if any)
983  2205 boolean valid = false;
984  2205 ExtensionDependency recommendedDependency = ExtensionUtils
985    .getRecommendedDependency(extensionDependency, this.configuration, this.factory);
986  2205 if (recommendedDependency != null) {
987  1772 valid = installOptionalExtensionDependency(recommendedDependency, namespace, children,
988    ExtensionUtils.append(managedDependencies, rewrittenExtension), parents);
989    }
990   
991    // If recommended version is invalid, try the one provided by the extension
992  2205 if (!valid) {
993  437 installExtensionDependency(extensionDependency, namespace, children,
994    ExtensionUtils.append(managedDependencies, rewrittenExtension), parents);
995    }
996   
997  2200 this.progressManager.endStep(this);
998    }
999    } finally {
1000  480 this.progressManager.popLevelProgress(this);
1001    }
1002    }
1003   
1004  675 this.progressManager.endStep(this);
1005   
1006  675 this.progressManager.startStep(this);
1007   
1008  675 ModifableExtensionPlanNode node = initialDependency != null
1009    ? new ModifableExtensionPlanNode(initialDependency, initialDependency.getVersionConstraint())
1010    : new ModifableExtensionPlanNode();
1011   
1012  675 node.setChildren(children);
1013   
1014  675 Action action;
1015  675 if (!previousExtensions.isEmpty()) {
1016  246 if (rewrittenExtension.compareTo(previousExtensions.iterator().next()) < 0) {
1017  7 action = Action.DOWNGRADE;
1018    } else {
1019  239 action = Action.UPGRADE;
1020    }
1021  429 } else if (rewrittenExtension instanceof InstalledExtension) {
1022  7 action = Action.REPAIR;
1023    } else {
1024  422 action = Action.INSTALL;
1025    }
1026   
1027  675 node.setAction(new DefaultExtensionPlanAction(sourceExtension, rewrittenExtension, previousExtensions,
1028    action, namespace, dependency));
1029   
1030  675 return node;
1031    } finally {
1032  693 this.progressManager.popLevelProgress(this);
1033    }
1034    }
1035   
 
1036  697 toggle private Extension checkInstalledExtension(Extension extension, String namespace) throws InstallException
1037    {
1038    // Check if the extension conflict with an extension installed on root namespace
1039  697 if (namespace != null) {
1040  487 checkRootExtension(extension);
1041    }
1042   
1043    // Check if the exact same valid extension is already installed on target namespace
1044  697 InstalledExtension installedExtension =
1045    this.installedExtensionRepository.getInstalledExtension(extension.getId());
1046  697 if (installedExtension != null && installedExtension.isInstalled(namespace)) {
1047  17 if (installedExtension.isValid(namespace)) {
1048  2 throw new InstallException(String.format("Extension [%s] is already installed on namespace [%s]",
1049    extension.getId(), namespace));
1050    }
1051   
1052    // In case the extension is already installed on the namespace but is invalid continue with it to make clear
1053    // to following code we are actually repairing it
1054  15 return installedExtension;
1055    }
1056   
1057  680 return extension;
1058    }
1059   
 
1060  974 toggle private boolean checkRootExtension(Extension extension) throws InstallException
1061    {
1062  974 boolean isRootExtension = checkRootExtension(extension.getId().getId());
1063   
1064  974 for (ExtensionId feature : extension.getExtensionFeatures()) {
1065  442 isRootExtension |= checkRootExtension(feature.getId());
1066    }
1067   
1068  974 return isRootExtension;
1069    }
1070   
 
1071  2255 toggle private boolean checkRootExtension(String feature) throws InstallException
1072    {
1073  2255 InstalledExtension rootExtension = this.installedExtensionRepository.getInstalledExtension(feature, null);
1074  2255 if (rootExtension != null) {
1075  2 if (!getRequest().isRootModificationsAllowed()) {
1076  1 throw new InstallException(
1077    String.format("An extension with feature [%s] is already installed on root namespace ([%s])",
1078    feature, rootExtension.getId()));
1079    }
1080   
1081  1 return true;
1082    }
1083   
1084  2253 return false;
1085    }
1086   
 
1087  679 toggle private boolean hasIncompatileRootDependency(ExtensionDependency extensionDependency)
1088    {
1089  679 InstalledExtension rootExtension =
1090    this.installedExtensionRepository.getInstalledExtension(extensionDependency.getId(), null);
1091  679 return rootExtension != null && !extensionDependency.isCompatible(rootExtension);
1092    }
1093   
 
1094  695 toggle private void checkCoreExtension(Extension extension) throws InstallException
1095    {
1096  695 for (ExtensionId feature : extension.getExtensionFeatures()) {
1097  289 checkCoreExtension(feature.getId());
1098    }
1099    }
1100   
 
1101  519 toggle private void checkCoreExtension(String feature) throws InstallException
1102    {
1103  519 if (this.coreExtensionRepository.exists(feature)) {
1104  4 throw new InstallException(
1105    String.format("There is already a core extension covering feature [%s]", feature));
1106    }
1107    }
1108   
1109    /**
1110    * Search and validate existing extensions that will be replaced by the extension.
1111    */
 
1112  687 toggle private Set<InstalledExtension> getReplacedInstalledExtensions(Extension extension, String namespace)
1113    throws IncompatibleVersionConstraintException, ResolveException, InstallException
1114    {
1115    // If a namespace extension already exist on root, fail the install
1116  687 if (namespace != null) {
1117  483 checkRootExtension(extension.getId().getId());
1118    }
1119   
1120  687 Set<InstalledExtension> previousExtensions = new LinkedHashSet<>();
1121   
1122  687 Set<InstalledExtension> installedExtensions = getInstalledExtensions(extension.getId().getId(), namespace);
1123   
1124  687 VersionConstraint versionConstraint =
1125    checkReplacedInstalledExtensions(installedExtensions, extension.getId(), namespace, null);
1126  686 previousExtensions.addAll(installedExtensions);
1127   
1128  686 for (ExtensionId feature : extension.getExtensionFeatures()) {
1129    // If a namespace extension already exist on root, fail the install
1130  287 if (namespace != null) {
1131  220 checkRootExtension(feature.getId());
1132    }
1133   
1134  287 installedExtensions = getInstalledExtensions(feature.getId(), namespace);
1135  287 versionConstraint =
1136    checkReplacedInstalledExtensions(installedExtensions, feature, namespace, versionConstraint);
1137  282 previousExtensions.addAll(installedExtensions);
1138    }
1139   
1140    // If the extensions is already installed (usually mean we are trying to repair an invalid extension) it's not
1141    // going to replace itself
1142  681 if (extension instanceof InstalledExtension) {
1143  10 previousExtensions.remove(extension);
1144    }
1145   
1146  681 return previousExtensions;
1147    }
1148   
 
1149  106 toggle private VersionConstraint mergeBackwardDependenciesVersionConstraints(String feature, String namespace,
1150    VersionConstraint versionConstraint) throws IncompatibleVersionConstraintException, ResolveException
1151    {
1152  106 Set<InstalledExtension> installedExtensions = getInstalledExtensions(feature, namespace);
1153   
1154  106 return mergeBackwardDependenciesVersionConstraints(installedExtensions, feature, namespace, versionConstraint);
1155    }
1156   
 
1157  106 toggle private VersionConstraint mergeBackwardDependenciesVersionConstraints(
1158    Collection<InstalledExtension> installedExtensions, String feature, String namespace,
1159    VersionConstraint versionConstraint) throws IncompatibleVersionConstraintException, ResolveException
1160    {
1161  106 if (installedExtensions.isEmpty()) {
1162  88 return versionConstraint;
1163    }
1164   
1165    // Get all backward dependencies
1166  18 Map<String, Set<InstalledExtension>> backwardDependencies =
1167    getBackwardDependencies(installedExtensions, namespace);
1168   
1169    // Merge all backward dependencies constraints
1170  18 for (Map.Entry<String, Set<InstalledExtension>> entry : backwardDependencies.entrySet()) {
1171  16 versionConstraint = mergeVersionConstraints(entry.getValue(), feature, versionConstraint);
1172    }
1173   
1174  18 return versionConstraint;
1175    }
1176   
 
1177  974 toggle private VersionConstraint checkReplacedInstalledExtensions(Collection<InstalledExtension> installedExtensions,
1178    ExtensionId feature, String namespace, VersionConstraint versionConstraint)
1179    throws IncompatibleVersionConstraintException, ResolveException
1180    {
1181  974 if (installedExtensions.isEmpty()) {
1182  595 return versionConstraint;
1183    }
1184   
1185    // Get all backward dependencies
1186  379 Map<String, Set<InstalledExtension>> backwardDependencies =
1187    getBackwardDependencies(installedExtensions, namespace);
1188   
1189    // Merge all backward dependencies constraints
1190  379 for (Map.Entry<String, Set<InstalledExtension>> entry : backwardDependencies.entrySet()) {
1191  134 versionConstraint = mergeVersionConstraints(entry.getValue(), feature.getId(), versionConstraint);
1192   
1193    // Validate version constraint
1194  134 if (versionConstraint != null) {
1195  132 if (!versionConstraint.isCompatible(feature.getVersion())) {
1196  6 throw new IncompatibleVersionConstraintException(String.format(
1197    "The extension with feature [%s] is not compatible with existing backward dependency constraint [%s]",
1198    feature, versionConstraint));
1199    }
1200    }
1201    }
1202   
1203  373 return versionConstraint;
1204    }
1205   
 
1206  397 toggle private Map<String, Set<InstalledExtension>> getBackwardDependencies(
1207    Collection<InstalledExtension> installedExtensions, String namespace) throws ResolveException
1208    {
1209  397 Map<String, Set<InstalledExtension>> backwardDependencies = new LinkedHashMap<>();
1210   
1211  397 for (InstalledExtension installedExtension : installedExtensions) {
1212  402 if (namespace == null) {
1213  88 for (Map.Entry<String, Collection<InstalledExtension>> entry : this.installedExtensionRepository
1214    .getBackwardDependencies(installedExtension.getId()).entrySet()) {
1215  53 Set<InstalledExtension> namespaceBackwardDependencies = backwardDependencies.get(entry.getKey());
1216  53 if (namespaceBackwardDependencies == null) {
1217  53 namespaceBackwardDependencies = new LinkedHashSet<>();
1218  53 backwardDependencies.put(entry.getKey(), namespaceBackwardDependencies);
1219    }
1220   
1221  53 namespaceBackwardDependencies.addAll(entry.getValue());
1222    }
1223    } else {
1224  314 for (InstalledExtension backwardDependency : this.installedExtensionRepository
1225    .getBackwardDependencies(installedExtension.getId().getId(), namespace)) {
1226  184 Set<InstalledExtension> namespaceBackwardDependencies = backwardDependencies.get(namespace);
1227  184 if (namespaceBackwardDependencies == null) {
1228  102 namespaceBackwardDependencies = new LinkedHashSet<>();
1229  102 backwardDependencies.put(namespace, namespaceBackwardDependencies);
1230    }
1231   
1232  184 namespaceBackwardDependencies.add(backwardDependency);
1233    }
1234    }
1235    }
1236   
1237  397 return backwardDependencies;
1238    }
1239   
 
1240  1080 toggle private Set<InstalledExtension> getInstalledExtensions(String feature, String namespace)
1241    {
1242  1080 Set<InstalledExtension> installedExtensions = new LinkedHashSet<>();
1243   
1244  1080 getInstalledExtensions(feature, namespace, installedExtensions);
1245   
1246  1080 return installedExtensions;
1247    }
1248   
 
1249  1080 toggle private void getInstalledExtensions(String feature, String namespace, Set<InstalledExtension> installedExtensions)
1250    {
1251  1080 if (namespace == null) {
1252  377 getInstalledExtensions(feature, installedExtensions);
1253    } else {
1254  703 InstalledExtension installedExtension =
1255    this.installedExtensionRepository.getInstalledExtension(feature, namespace);
1256  703 if (installedExtension != null) {
1257  314 installedExtensions.add(installedExtension);
1258    }
1259    }
1260    }
1261   
 
1262  377 toggle private void getInstalledExtensions(String feature, Set<InstalledExtension> installedExtensions)
1263    {
1264  377 for (InstalledExtension installedExtension : this.installedExtensionRepository.getInstalledExtensions()) {
1265    // Check id
1266  3842 if (installedExtension.getId().getId().equals(feature)) {
1267  74 installedExtensions.add(installedExtension);
1268  74 continue;
1269    }
1270   
1271    // Check features
1272  3768 for (ExtensionId installedFeature : installedExtension.getExtensionFeatures())
1273  1268 if (installedFeature.getId().equals(feature)) {
1274  14 installedExtensions.add(installedExtension);
1275  14 break;
1276    }
1277    }
1278    }
1279    }