| /** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.karaf.tooling.features; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; |
| import org.apache.maven.artifact.metadata.ArtifactMetadataSource; |
| import org.apache.maven.artifact.metadata.ResolutionGroup; |
| import org.apache.maven.artifact.repository.ArtifactRepository; |
| import org.apache.maven.artifact.resolver.ArtifactCollector; |
| import org.apache.maven.artifact.resolver.ArtifactResolutionException; |
| import org.apache.maven.artifact.resolver.ArtifactResolutionResult; |
| import org.apache.maven.artifact.resolver.CyclicDependencyException; |
| import org.apache.maven.artifact.resolver.ResolutionListener; |
| import org.apache.maven.artifact.resolver.ResolutionNode; |
| import org.apache.maven.artifact.resolver.filter.ArtifactFilter; |
| import org.apache.maven.artifact.versioning.ArtifactVersion; |
| import org.apache.maven.artifact.versioning.OverConstrainedVersionException; |
| import org.apache.maven.artifact.versioning.VersionRange; |
| |
| /** |
| * @version $Revision: 1.1 $ |
| */ |
| public class GraphArtifactCollector implements ArtifactCollector { |
| public ArtifactResolutionResult collect( |
| Set artifacts, |
| Artifact originatingArtifact, |
| ArtifactRepository localRepository, |
| List remoteRepositories, |
| ArtifactMetadataSource source, |
| ArtifactFilter filter, |
| List listeners) throws ArtifactResolutionException { |
| return collect(artifacts, originatingArtifact, Collections.EMPTY_MAP, |
| localRepository, remoteRepositories, source, filter, listeners); |
| } |
| |
| public ArtifactResolutionResult collect( |
| Set artifacts, |
| Artifact originatingArtifact, |
| Map managedVersions, |
| ArtifactRepository localRepository, |
| List remoteRepositories, |
| ArtifactMetadataSource source, |
| ArtifactFilter filter, |
| List listeners) throws ArtifactResolutionException { |
| Map resolvedArtifacts = new HashMap(); |
| |
| ResolutionNode root = new ResolutionNode(originatingArtifact, remoteRepositories); |
| root.addDependencies(artifacts, remoteRepositories, filter); |
| recurse(root, resolvedArtifacts, managedVersions, localRepository, |
| remoteRepositories, source, filter, listeners); |
| |
| Set set = new HashSet(); |
| for (Iterator i = resolvedArtifacts.values().iterator(); i.hasNext();) { |
| List nodes = (List) i.next(); |
| for (Iterator j = nodes.iterator(); j.hasNext();) { |
| ResolutionNode node = (ResolutionNode) j.next(); |
| Artifact artifact = node.getArtifact(); |
| if (!node.equals(root) && node.isActive() && node.filterTrail(filter) |
| // If it was optional and not a direct dependency, |
| // we don't add it or its children, just allow the |
| // update of the version and scope |
| && (node.isChildOfRootNode() || !artifact.isOptional())) { |
| artifact.setDependencyTrail(node.getDependencyTrail()); |
| set.add(node); |
| } |
| } |
| } |
| |
| ArtifactResolutionResult result = new ArtifactResolutionResult(); |
| result.setArtifactResolutionNodes(set); |
| return result; |
| } |
| |
| private void recurse( |
| ResolutionNode node, |
| Map resolvedArtifacts, |
| Map managedVersions, |
| ArtifactRepository localRepository, |
| List remoteRepositories, |
| ArtifactMetadataSource source, |
| ArtifactFilter filter, |
| List listeners) throws CyclicDependencyException, ArtifactResolutionException, |
| OverConstrainedVersionException { |
| fireEvent(ResolutionListener.TEST_ARTIFACT, listeners, node); |
| |
| // TODO: use as a conflict resolver |
| Object key = node.getKey(); |
| if (managedVersions.containsKey(key)) { |
| Artifact artifact = (Artifact) managedVersions.get(key); |
| fireEvent(ResolutionListener.MANAGE_ARTIFACT, listeners, node, artifact); |
| if (artifact.getVersion() != null) { |
| node.getArtifact().setVersion(artifact.getVersion()); |
| } |
| if (artifact.getScope() != null) { |
| node.getArtifact().setScope(artifact.getScope()); |
| } |
| } |
| |
| List previousNodes = (List) resolvedArtifacts.get(key); |
| if (previousNodes != null) { |
| node = checkPreviousNodes(node, listeners, previousNodes); |
| } |
| else { |
| previousNodes = new ArrayList(); |
| resolvedArtifacts.put(key, previousNodes); |
| } |
| previousNodes.add(node); |
| |
| if (node.isActive()) { |
| fireEvent(ResolutionListener.INCLUDE_ARTIFACT, listeners, node); |
| } |
| |
| // don't pull in the transitive deps of a system-scoped dependency. |
| if (node.isActive() && !Artifact.SCOPE_SYSTEM.equals(node.getArtifact().getScope())) { |
| fireEvent(ResolutionListener.PROCESS_CHILDREN, listeners, node); |
| for (Iterator i = node.getChildrenIterator(); i.hasNext();) { |
| ResolutionNode child = (ResolutionNode) i.next(); |
| // We leave in optional ones, but don't pick up its dependencies |
| if (!child.isResolved() |
| && (!child.getArtifact().isOptional() || child.isChildOfRootNode())) { |
| Artifact artifact = child.getArtifact(); |
| try { |
| if (artifact.getVersion() == null) { |
| // set the recommended version |
| // TODO: maybe its better to just pass the range |
| // through to retrieval and use a transformation? |
| ArtifactVersion version; |
| version = getArtifactVersion(localRepository, remoteRepositories, source, artifact); |
| |
| artifact.selectVersion(version.toString()); |
| fireEvent(ResolutionListener.SELECT_VERSION_FROM_RANGE, |
| listeners, child); |
| } |
| |
| ResolutionGroup rGroup = source.retrieve(artifact, |
| localRepository, remoteRepositories); |
| |
| // TODO might be better to have source.retreive() throw |
| // a specific exception for this situation |
| // and catch here rather than have it return null |
| if (rGroup == null) { |
| // relocated dependency artifact is declared |
| // excluded, no need to add and recurse further |
| continue; |
| } |
| |
| child.addDependencies(rGroup.getArtifacts(), |
| rGroup.getResolutionRepositories(), filter); |
| } |
| catch (CyclicDependencyException e) { |
| // would like to throw this, but we have crappy stuff in |
| // the repo |
| |
| fireEvent(ResolutionListener.OMIT_FOR_CYCLE, listeners, |
| new ResolutionNode(e.getArtifact(), remoteRepositories, child)); |
| } |
| catch (ArtifactMetadataRetrievalException e) { |
| artifact.setDependencyTrail(node.getDependencyTrail()); |
| throw new ArtifactResolutionException( |
| "Unable to get dependency information: " |
| + e.getMessage(), artifact, e); |
| } |
| |
| recurse(child, resolvedArtifacts, managedVersions, |
| localRepository, remoteRepositories, source, |
| filter, listeners); |
| } |
| } |
| fireEvent(ResolutionListener.FINISH_PROCESSING_CHILDREN, listeners, |
| node); |
| } |
| } |
| |
| private ArtifactVersion getArtifactVersion( |
| ArtifactRepository localRepository, |
| List remoteRepositories, |
| ArtifactMetadataSource source, |
| Artifact artifact) throws OverConstrainedVersionException, |
| ArtifactMetadataRetrievalException { |
| ArtifactVersion version; |
| if (!artifact.isSelectedVersionKnown()) { |
| List versions = artifact.getAvailableVersions(); |
| if (versions == null) { |
| versions = source.retrieveAvailableVersions( |
| artifact, localRepository, |
| remoteRepositories); |
| artifact.setAvailableVersions(versions); |
| } |
| |
| VersionRange versionRange = artifact.getVersionRange(); |
| |
| version = versionRange.matchVersion(versions); |
| |
| if (version == null) { |
| if (versions.isEmpty()) { |
| throw new OverConstrainedVersionException( |
| "No versions are present in the repository for the artifact with a range " |
| + versionRange, artifact, remoteRepositories); |
| } |
| else { |
| throw new OverConstrainedVersionException( |
| "Couldn't find a version in " |
| + versions |
| + " to match range " |
| + versionRange, |
| artifact, remoteRepositories); |
| } |
| } |
| } |
| else { |
| version = artifact.getSelectedVersion(); |
| } |
| return version; |
| } |
| |
| private ResolutionNode checkPreviousNodes( |
| ResolutionNode node, |
| List listeners, |
| List previousNodes) throws OverConstrainedVersionException { |
| for (Iterator i = previousNodes.iterator(); i.hasNext();) { |
| ResolutionNode previous = (ResolutionNode) i.next(); |
| if (previous.isActive()) { |
| // Version mediation |
| VersionRange previousRange = previous.getArtifact().getVersionRange(); |
| VersionRange currentRange = node.getArtifact().getVersionRange(); |
| // TODO: why do we force the version on it? what if they |
| // don't match? |
| if (previousRange == null) { |
| // version was already resolved |
| node.getArtifact().setVersion(previous.getArtifact().getVersion()); |
| } |
| else if (currentRange == null) { |
| // version was already resolved |
| previous.getArtifact().setVersion(node.getArtifact().getVersion()); |
| } |
| else { |
| // TODO: shouldn't need to double up on this work, only |
| // done for simplicity of handling recommended |
| // version but the restriction is identical |
| VersionRange newRange = previousRange.restrict(currentRange); |
| // TODO: ick. this forces the OCE that should have come |
| // from the previous call. It is still correct |
| if (newRange.isSelectedVersionKnown(previous.getArtifact())) { |
| fireEvent(ResolutionListener.RESTRICT_RANGE, |
| listeners, node, previous.getArtifact(), |
| newRange); |
| } |
| previous.getArtifact().setVersionRange(newRange); |
| node.getArtifact().setVersionRange( |
| currentRange.restrict(previousRange)); |
| |
| // Select an appropriate available version from the (now |
| // restricted) range |
| // Note this version was selected before to get the |
| // appropriate POM |
| // But it was reset by the call to setVersionRange on |
| // restricting the version |
| ResolutionNode[] resetNodes = {previous, node}; |
| for (int j = 0; j < 2; j++) { |
| Artifact resetArtifact = resetNodes[j] |
| .getArtifact(); |
| if (resetArtifact.getVersion() == null |
| && resetArtifact.getVersionRange() != null |
| && resetArtifact.getAvailableVersions() != null) { |
| |
| resetArtifact |
| .selectVersion(resetArtifact |
| .getVersionRange() |
| .matchVersion( |
| resetArtifact |
| .getAvailableVersions()) |
| .toString()); |
| fireEvent(ResolutionListener.SELECT_VERSION_FROM_RANGE, |
| listeners, resetNodes[j]); |
| } |
| } |
| } |
| |
| // Conflict Resolution |
| // TODO: use as conflict resolver(s), chain |
| |
| // TODO: should this be part of mediation? |
| // previous one is more dominant |
| if (previous.getDepth() <= node.getDepth()) { |
| checkScopeUpdate(node, previous, listeners); |
| } |
| else { |
| checkScopeUpdate(previous, node, listeners); |
| } |
| |
| if (previous.getDepth() <= node.getDepth()) { |
| // previous was nearer |
| fireEvent(ResolutionListener.OMIT_FOR_NEARER, |
| listeners, node, previous.getArtifact()); |
| node.disable(); |
| node = previous; |
| } |
| else { |
| fireEvent(ResolutionListener.OMIT_FOR_NEARER, |
| listeners, previous, node.getArtifact()); |
| previous.disable(); |
| } |
| } |
| } |
| return node; |
| } |
| |
| private void checkScopeUpdate(ResolutionNode farthest, |
| ResolutionNode nearest, List listeners) { |
| boolean updateScope = false; |
| Artifact farthestArtifact = farthest.getArtifact(); |
| Artifact nearestArtifact = nearest.getArtifact(); |
| |
| if (Artifact.SCOPE_RUNTIME.equals(farthestArtifact.getScope()) |
| && (Artifact.SCOPE_TEST.equals(nearestArtifact.getScope()) || Artifact.SCOPE_PROVIDED |
| .equals(nearestArtifact.getScope()))) { |
| updateScope = true; |
| } |
| |
| if (Artifact.SCOPE_COMPILE.equals(farthestArtifact.getScope()) |
| && !Artifact.SCOPE_COMPILE.equals(nearestArtifact.getScope())) { |
| updateScope = true; |
| } |
| |
| // current POM rules all |
| if (nearest.getDepth() < 2 && updateScope) { |
| updateScope = false; |
| |
| fireEvent(ResolutionListener.UPDATE_SCOPE_CURRENT_POM, listeners, |
| nearest, farthestArtifact); |
| } |
| |
| if (updateScope) { |
| fireEvent(ResolutionListener.UPDATE_SCOPE, listeners, nearest, |
| farthestArtifact); |
| |
| // previously we cloned the artifact, but it is more effecient to |
| // just update the scope |
| // if problems are later discovered that the original object needs |
| // its original scope value, cloning may |
| // again be appropriate |
| nearestArtifact.setScope(farthestArtifact.getScope()); |
| } |
| } |
| |
| private void fireEvent(int event, List listeners, ResolutionNode node) { |
| fireEvent(event, listeners, node, null); |
| } |
| |
| private void fireEvent(int event, List listeners, ResolutionNode node, |
| Artifact replacement) { |
| fireEvent(event, listeners, node, replacement, null); |
| } |
| |
| private void fireEvent(int event, List listeners, ResolutionNode node, |
| Artifact replacement, VersionRange newRange) { |
| for (Iterator i = listeners.iterator(); i.hasNext();) { |
| ResolutionListener listener = (ResolutionListener) i.next(); |
| |
| switch (event) { |
| case ResolutionListener.TEST_ARTIFACT: |
| listener.testArtifact(node.getArtifact()); |
| break; |
| case ResolutionListener.PROCESS_CHILDREN: |
| listener.startProcessChildren(node.getArtifact()); |
| break; |
| case ResolutionListener.FINISH_PROCESSING_CHILDREN: |
| listener.endProcessChildren(node.getArtifact()); |
| break; |
| case ResolutionListener.INCLUDE_ARTIFACT: |
| listener.includeArtifact(node.getArtifact()); |
| break; |
| case ResolutionListener.OMIT_FOR_NEARER: |
| String version = node.getArtifact().getVersion(); |
| String replacementVersion = replacement.getVersion(); |
| if (version != null ? !version.equals(replacementVersion) |
| : replacementVersion != null) { |
| listener.omitForNearer(node.getArtifact(), replacement); |
| } |
| break; |
| case ResolutionListener.OMIT_FOR_CYCLE: |
| listener.omitForCycle(node.getArtifact()); |
| break; |
| case ResolutionListener.UPDATE_SCOPE: |
| listener |
| .updateScope(node.getArtifact(), replacement.getScope()); |
| break; |
| case ResolutionListener.UPDATE_SCOPE_CURRENT_POM: |
| listener.updateScopeCurrentPom(node.getArtifact(), replacement |
| .getScope()); |
| break; |
| case ResolutionListener.MANAGE_ARTIFACT: |
| listener.manageArtifact(node.getArtifact(), replacement); |
| break; |
| case ResolutionListener.SELECT_VERSION_FROM_RANGE: |
| listener.selectVersionFromRange(node.getArtifact()); |
| break; |
| case ResolutionListener.RESTRICT_RANGE: |
| if (node.getArtifact().getVersionRange().hasRestrictions() |
| || replacement.getVersionRange().hasRestrictions()) { |
| listener.restrictRange(node.getArtifact(), replacement, |
| newRange); |
| } |
| break; |
| default: |
| throw new IllegalStateException("Unknown event: " + event); |
| } |
| } |
| } |
| } |