| /** |
| * 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.maven.mercury.metadata; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.maven.mercury.artifact.ArtifactBasicMetadata; |
| import org.apache.maven.mercury.artifact.ArtifactExclusionList; |
| import org.apache.maven.mercury.artifact.ArtifactInclusionList; |
| import org.apache.maven.mercury.artifact.ArtifactMetadata; |
| import org.apache.maven.mercury.artifact.ArtifactQueryList; |
| import org.apache.maven.mercury.artifact.ArtifactScopeEnum; |
| import org.apache.maven.mercury.artifact.api.ArtifactListProcessor; |
| import org.apache.maven.mercury.artifact.version.VersionException; |
| import org.apache.maven.mercury.event.EventGenerator; |
| import org.apache.maven.mercury.event.EventManager; |
| import org.apache.maven.mercury.event.EventTypeEnum; |
| import org.apache.maven.mercury.event.GenericEvent; |
| import org.apache.maven.mercury.event.MercuryEventListener; |
| import org.apache.maven.mercury.logging.IMercuryLogger; |
| import org.apache.maven.mercury.logging.MercuryLoggerManager; |
| import org.apache.maven.mercury.metadata.sat.DefaultSatSolver; |
| import org.apache.maven.mercury.metadata.sat.SatException; |
| import org.apache.maven.mercury.repository.api.ArtifactBasicResults; |
| import org.apache.maven.mercury.repository.api.Repository; |
| import org.apache.maven.mercury.repository.api.RepositoryException; |
| import org.apache.maven.mercury.repository.virtual.VirtualRepositoryReader; |
| import org.apache.maven.mercury.util.Util; |
| import org.codehaus.plexus.lang.DefaultLanguage; |
| import org.codehaus.plexus.lang.Language; |
| |
| /** |
| * This is the new entry point into Artifact resolution process. |
| * |
| * @author Oleg Gusakov |
| * @version $Id$ |
| */ |
| class DependencyTreeBuilder |
| implements DependencyBuilder, EventGenerator |
| { |
| public static final ArtifactMetadata DUMMY_ROOT = new ArtifactMetadata( "__fake:__fake:1.0" ); |
| |
| private static final Language _lang = new DefaultLanguage( DependencyTreeBuilder.class ); |
| |
| private static final IMercuryLogger _log = MercuryLoggerManager.getLogger( DependencyTreeBuilder.class ); |
| |
| private Collection<MetadataTreeArtifactFilter> _filters; |
| |
| private List<Comparator<MetadataTreeNode>> _comparators; |
| |
| private Map<String, ArtifactListProcessor> _processors; |
| |
| private VirtualRepositoryReader _reader; |
| |
| private Map<String, MetadataTreeNode> _existingNodes; |
| |
| private EventManager _eventManager; |
| |
| /** |
| * creates an instance of MetadataTree. Use this instance to |
| * <ul> |
| * <li>buildTree - process all the dependencies</li> |
| * <li>resolveConflicts</li> |
| * <ul> |
| * |
| * @param filters - can veto any artifact before it's added to the tree |
| * @param comparators - used to define selection policies. If null is passed, classic comparators - nearest/newest |
| * first - will be used. |
| * @param repositories - order is <b>very</b> important. Ordering allows m2eclipse, for instance, insert a workspace |
| * repository |
| * @throws RepositoryException |
| */ |
| protected DependencyTreeBuilder( Collection<Repository> repositories, |
| Collection<MetadataTreeArtifactFilter> filters, |
| List<Comparator<MetadataTreeNode>> comparators, |
| Map<String, ArtifactListProcessor> processors ) |
| throws RepositoryException |
| { |
| this._filters = filters; |
| this._comparators = comparators; |
| |
| // if used does not want to bother. |
| // if it's an empty list - user does not want any comparators - so be it |
| if ( _comparators == null ) |
| { |
| _comparators = new ArrayList<Comparator<MetadataTreeNode>>( 2 ); |
| _comparators.add( new ClassicDepthComparator() ); |
| _comparators.add( new ClassicVersionComparator() ); |
| } |
| |
| if ( processors != null ) |
| _processors = processors; |
| |
| this._reader = new VirtualRepositoryReader( repositories ); |
| } |
| |
| // ------------------------------------------------------------------------ |
| public MetadataTreeNode buildTree( ArtifactBasicMetadata startMD, ArtifactScopeEnum treeScope ) |
| throws MetadataTreeException |
| { |
| if ( startMD == null ) |
| throw new MetadataTreeException( "null start point" ); |
| |
| try |
| { |
| _reader.setEventManager( _eventManager ); |
| _reader.setProcessors( _processors ); |
| _reader.init(); |
| } |
| catch ( RepositoryException e ) |
| { |
| throw new MetadataTreeException( e ); |
| } |
| |
| _existingNodes = new HashMap<String, MetadataTreeNode>( 256 ); |
| |
| GenericEvent treeBuildEvent = null; |
| if ( _eventManager != null ) |
| treeBuildEvent = new GenericEvent( EventTypeEnum.dependencyBuilder, TREE_BUILD_EVENT, startMD.getGAV() ); |
| |
| MetadataTreeNode root = createNode( startMD, null, startMD, treeScope ); |
| |
| if ( _eventManager != null ) |
| treeBuildEvent.stop(); |
| |
| if ( _eventManager != null ) |
| _eventManager.fireEvent( treeBuildEvent ); |
| |
| MetadataTreeNode.reNumber( root, 1 ); |
| |
| return root; |
| } |
| |
| // ------------------------------------------------------------------------ |
| public List<ArtifactMetadata> resolveConflicts( |
| ArtifactScopeEnum scope |
| , ArtifactQueryList artifacts |
| , ArtifactInclusionList inclusions |
| , ArtifactExclusionList exclusions |
| ) |
| |
| throws MetadataTreeException |
| { |
| if ( artifacts == null ) |
| throw new MetadataTreeException( _lang.getMessage( "empty.md.collection" ) ); |
| |
| List<ArtifactBasicMetadata> startMDs = artifacts.getMetadataList(); |
| |
| if ( Util.isEmpty( startMDs ) ) |
| throw new MetadataTreeException( _lang.getMessage( "empty.md.collection" ) ); |
| |
| int nodeCount = startMDs.size(); |
| |
| if ( nodeCount == 1 && inclusions == null && exclusions == null ) |
| { |
| ArtifactBasicMetadata bmd = startMDs.get( 0 ); |
| MetadataTreeNode rooty = buildTree( bmd, scope ); |
| List<ArtifactMetadata> res = resolveConflicts( rooty ); |
| return res; |
| } |
| |
| List<MetadataTreeNode> deps = new ArrayList<MetadataTreeNode>( nodeCount ); |
| |
| // build all trees |
| for ( ArtifactBasicMetadata bmd : startMDs ) |
| { |
| if( inclusions != null ) |
| { |
| List<ArtifactBasicMetadata> inc = inclusions.getMetadataList(); |
| |
| if( ! inc.contains( bmd ) ) |
| continue; |
| |
| if( bmd.hasInclusions() ) |
| bmd.getInclusions().addAll( inc ); |
| else |
| bmd.setInclusions( inc ); |
| } |
| |
| if( exclusions != null ) |
| { |
| List<ArtifactBasicMetadata> excl = exclusions.getMetadataList(); |
| |
| if( excl.contains( bmd ) ) |
| continue; |
| |
| if( bmd.hasExclusions() ) |
| bmd.getExclusions().addAll( excl ); |
| else |
| bmd.setExclusions( excl ); |
| } |
| |
| MetadataTreeNode rooty = buildTree( bmd, scope ); |
| |
| deps.add( rooty ); |
| } |
| |
| DUMMY_ROOT.setDependencies( startMDs ); |
| DUMMY_ROOT.setInclusions( inclusions == null ? null : inclusions.getMetadataList() ); |
| DUMMY_ROOT.setExclusions( exclusions == null ? null : exclusions.getMetadataList() ); |
| |
| // combine into one tree |
| MetadataTreeNode root = new MetadataTreeNode( DUMMY_ROOT, null, null ); |
| |
| for ( MetadataTreeNode kid : deps ) |
| root.addChild( kid ); |
| |
| List<ArtifactMetadata> res = resolveConflicts( root ); |
| |
| res.remove( DUMMY_ROOT ); |
| |
| return res; |
| } |
| // ----------------------------------------------------- |
| private MetadataTreeNode createNode( ArtifactBasicMetadata nodeMD, MetadataTreeNode parent |
| , ArtifactBasicMetadata nodeQuery, ArtifactScopeEnum globalScope |
| ) |
| throws MetadataTreeException |
| { |
| GenericEvent nodeBuildEvent = null; |
| |
| if ( _eventManager != null ) |
| nodeBuildEvent = new GenericEvent( EventTypeEnum.dependencyBuilder, TREE_NODE_BUILD_EVENT, nodeMD.getGAV() ); |
| |
| try |
| { |
| checkForCircularDependency( nodeMD, parent ); |
| |
| ArtifactMetadata mr; |
| |
| MetadataTreeNode existingNode = _existingNodes.get( nodeQuery.toString() ); |
| |
| if ( existingNode != null ) |
| return MetadataTreeNode.deepCopy( existingNode ); |
| |
| mr = _reader.readDependencies( nodeMD ); |
| |
| if ( mr == null ) |
| throw new MetadataTreeException( _lang.getMessage( "artifact.md.not.found", nodeMD.toString() ) ); |
| |
| MetadataTreeNode node = new MetadataTreeNode( mr, parent, nodeQuery ); |
| |
| List<ArtifactBasicMetadata> allDependencies = mr.getDependencies(); |
| |
| if ( allDependencies == null || allDependencies.size() < 1 ) |
| return node; |
| |
| List<ArtifactBasicMetadata> dependencies = new ArrayList<ArtifactBasicMetadata>( allDependencies.size() ); |
| if ( globalScope != null ) |
| for ( ArtifactBasicMetadata md : allDependencies ) |
| { |
| ArtifactScopeEnum mdScope = md.getArtifactScope(); |
| if ( globalScope.encloses( mdScope ) ) |
| dependencies.add( md ); |
| } |
| else |
| dependencies.addAll( allDependencies ); |
| |
| if ( Util.isEmpty( dependencies ) ) |
| return node; |
| |
| ArtifactBasicResults res = _reader.readVersions( dependencies ); |
| |
| Map<ArtifactBasicMetadata, List<ArtifactBasicMetadata>> expandedDeps = res.getResults(); |
| |
| for ( ArtifactBasicMetadata md : dependencies ) |
| { |
| |
| if ( _log.isDebugEnabled() ) |
| _log.debug( "node " + nodeQuery + ", dep " + md ); |
| |
| List<ArtifactBasicMetadata> versions = expandedDeps.get( md ); |
| if ( versions == null || versions.size() < 1 ) |
| { |
| if ( md.isOptional() ) |
| continue; |
| |
| throw new MetadataTreeException( "did not find non-optional artifact for " + md + " <== " |
| + showPath( node ) ); |
| } |
| |
| boolean noGoodVersions = true; |
| boolean noVersions = true; |
| for ( ArtifactBasicMetadata ver : versions ) |
| { |
| if ( veto( ver, _filters ) || vetoInclusionsExclusions( node, ver ) ) |
| { |
| // there were good versions, but this one is filtered out |
| noGoodVersions = false; |
| continue; |
| } |
| |
| MetadataTreeNode kid = createNode( ver, node, md, globalScope ); |
| node.addChild( kid ); |
| |
| noVersions = false; |
| |
| noGoodVersions = false; |
| } |
| |
| if ( noVersions && !noGoodVersions ) |
| { |
| // there were good versions, but they were all filtered out |
| continue; |
| } |
| else if ( noGoodVersions ) |
| { |
| if ( md.isOptional() ) |
| continue; |
| throw new MetadataTreeException( "did not find non-optional artifact for " + md ); |
| } |
| else |
| node.addQuery( md ); |
| } |
| |
| _existingNodes.put( nodeQuery.toString(), node ); |
| |
| return node; |
| } |
| catch ( RepositoryException e ) |
| { |
| if ( _eventManager != null ) |
| nodeBuildEvent.setResult( e.getMessage() ); |
| |
| throw new MetadataTreeException( e ); |
| } |
| catch ( VersionException e ) |
| { |
| if ( _eventManager != null ) |
| nodeBuildEvent.setResult( e.getMessage() ); |
| |
| throw new MetadataTreeException( e ); |
| } |
| catch ( MetadataTreeException e ) |
| { |
| if ( _eventManager != null ) |
| nodeBuildEvent.setResult( e.getMessage() ); |
| throw e; |
| } |
| finally |
| { |
| if ( _eventManager != null ) |
| { |
| nodeBuildEvent.stop(); |
| _eventManager.fireEvent( nodeBuildEvent ); |
| } |
| } |
| } |
| |
| // ----------------------------------------------------- |
| private void checkForCircularDependency( ArtifactBasicMetadata md, MetadataTreeNode parent ) |
| throws MetadataTreeCircularDependencyException |
| { |
| MetadataTreeNode p = parent; |
| int count = 0; |
| while ( p != null ) |
| { |
| count++; |
| // System.out.println("circ "+md+" vs "+p.md); |
| if ( md.sameGA( p.md ) ) |
| { |
| p = parent; |
| StringBuilder sb = new StringBuilder( 128 ); |
| sb.append( md.toString() ); |
| while ( p != null ) |
| { |
| sb.append( " <- " + p.md.toString() ); |
| |
| if ( md.sameGA( p.md ) ) |
| { |
| throw new MetadataTreeCircularDependencyException( "circular dependency " + count |
| + " levels up. " + sb.toString() + " <= " + ( p.parent == null ? "no parent" : p.parent.md ) ); |
| } |
| p = p.parent; |
| } |
| } |
| p = p.parent; |
| } |
| } |
| |
| // ----------------------------------------------------- |
| private boolean veto( ArtifactBasicMetadata md, Collection<MetadataTreeArtifactFilter> filters ) |
| { |
| if ( filters != null && filters.size() > 1 ) |
| for ( MetadataTreeArtifactFilter filter : filters ) |
| if ( filter.veto( md ) ) |
| return true; |
| return false; |
| } |
| |
| // ----------------------------------------------------- |
| private boolean vetoInclusionsExclusions( MetadataTreeNode node, ArtifactBasicMetadata ver ) |
| throws VersionException |
| { |
| for ( MetadataTreeNode n = node; n != null; n = n.getParent() ) |
| { |
| ArtifactBasicMetadata md = n.getQuery(); |
| |
| if ( !md.allowDependency( ver ) ) // veto it |
| return true; |
| } |
| return false; // allow because all parents are OK with it |
| } |
| |
| // ----------------------------------------------------- |
| public List<ArtifactMetadata> resolveConflicts( MetadataTreeNode root ) |
| throws MetadataTreeException |
| { |
| if ( root == null ) |
| throw new MetadataTreeException( _lang.getMessage( "empty.tree" ) ); |
| |
| try |
| { |
| DefaultSatSolver solver = new DefaultSatSolver( root, _eventManager ); |
| |
| solver.applyPolicies( getComparators() ); |
| |
| List<ArtifactMetadata> res = solver.solve(); |
| |
| return res; |
| } |
| catch ( SatException e ) |
| { |
| throw new MetadataTreeException( e ); |
| } |
| |
| } |
| |
| // ----------------------------------------------------- |
| public MetadataTreeNode resolveConflictsAsTree( MetadataTreeNode root ) |
| throws MetadataTreeException |
| { |
| if ( root == null ) |
| throw new MetadataTreeException( _lang.getMessage( "empty.tree" ) ); |
| |
| try |
| { |
| DefaultSatSolver solver = new DefaultSatSolver( root, _eventManager ); |
| |
| solver.applyPolicies( getComparators() ); |
| |
| MetadataTreeNode res = solver.solveAsTree(); |
| |
| return res; |
| } |
| catch ( SatException e ) |
| { |
| throw new MetadataTreeException( e ); |
| } |
| |
| } |
| |
| // ----------------------------------------------------- |
| private List<Comparator<MetadataTreeNode>> getComparators() |
| { |
| if ( Util.isEmpty( _comparators ) ) |
| _comparators = new ArrayList<Comparator<MetadataTreeNode>>( 2 ); |
| |
| if ( _comparators.size() < 1 ) |
| { |
| _comparators.add( new ClassicDepthComparator() ); |
| _comparators.add( new ClassicVersionComparator() ); |
| } |
| |
| return _comparators; |
| } |
| |
| // ----------------------------------------------------- |
| private String showPath( MetadataTreeNode node ) |
| throws MetadataTreeCircularDependencyException |
| { |
| StringBuilder sb = new StringBuilder( 256 ); |
| |
| String comma = ""; |
| |
| MetadataTreeNode p = node; |
| |
| while ( p != null ) |
| { |
| sb.append( comma + p.getMd().toString() ); |
| |
| comma = " <== "; |
| |
| p = p.parent; |
| } |
| |
| return sb.toString(); |
| } |
| |
| public void register( MercuryEventListener listener ) |
| { |
| if ( _eventManager == null ) |
| _eventManager = new EventManager(); |
| |
| _eventManager.register( listener ); |
| } |
| |
| public void unRegister( MercuryEventListener listener ) |
| { |
| if ( _eventManager != null ) |
| _eventManager.unRegister( listener ); |
| } |
| |
| public void setEventManager( EventManager eventManager ) |
| { |
| if ( _eventManager == null ) |
| _eventManager = eventManager; |
| else |
| _eventManager.getListeners().addAll( eventManager.getListeners() ); |
| |
| } |
| } |