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

File AbstractInstallPlanJob.java

 

Coverage histogram

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

Code metrics

166
369
41
2
1,165
779
143
0.39
9
20.5
3.49

Classes

Class Line # Actions
AbstractInstallPlanJob 67 359 0% 135 63
0.8868940588.7%
AbstractInstallPlanJob.ModifableExtensionPlanNode 69 10 0% 8 6
0.6842105468.4%
 

Contributing tests

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