/** * Copyright (C) 2013 Red Hat, Inc. (jdcasey@commonjava.org) * * Licensed 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.commonjava.cartographer.INTERNAL.ops; import org.apache.commons.io.IOUtils; import org.commonjava.cartographer.CartoDataException; import org.commonjava.cartographer.graph.GraphResolver; import org.commonjava.cartographer.graph.RecipeResolver; import org.commonjava.cartographer.graph.RepoContentCollector; import org.commonjava.cartographer.graph.agg.ProjectRefCollection; import org.commonjava.cartographer.spi.graph.discover.DiscoverySourceManager; import org.commonjava.cartographer.spi.graph.discover.ProjectRelationshipDiscoverer; import org.commonjava.cartographer.graph.fn.MultiGraphFunction; import org.commonjava.cartographer.ops.ResolveOps; import org.commonjava.cdi.util.weft.ExecutorConfig; import org.commonjava.cdi.util.weft.WeftManaged; import org.commonjava.cartographer.graph.RelationshipGraph; import org.commonjava.cartographer.graph.ViewParams; import org.commonjava.cartographer.graph.filter.AnyFilter; import org.commonjava.maven.atlas.graph.rel.ProjectRelationship; import org.commonjava.maven.atlas.ident.ref.ArtifactRef; import org.commonjava.maven.atlas.ident.ref.ProjectVersionRef; import org.commonjava.maven.atlas.ident.util.JoinString; import org.commonjava.cartographer.CartoRequestException; import org.commonjava.cartographer.graph.discover.DiscoveryConfig; import org.commonjava.cartographer.request.RepositoryContentRequest; import org.commonjava.maven.galley.maven.ArtifactManager; import org.commonjava.maven.galley.maven.parse.MavenPomReader; import org.commonjava.maven.galley.model.ConcreteResource; import org.commonjava.maven.galley.model.Location; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import java.net.URI; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import static org.commonjava.cartographer.INTERNAL.graph.agg.AggregationUtils.collectProjectVersionReferences; @ApplicationScoped public class ResolveOpsImpl implements ResolveOps { private final Logger logger = LoggerFactory.getLogger( getClass() ); @Inject private GraphResolver resolver; @Inject private DiscoverySourceManager sourceManager; @Inject private ProjectRelationshipDiscoverer discoverer; @Inject private ArtifactManager artifacts; @Inject protected MavenPomReader pomReader; @Inject private RecipeResolver recipeResolver; @Inject @WeftManaged @ExecutorConfig( daemon = true, named = "carto-graph-ops", priority = 9, threads = 16 ) private ExecutorService executor; protected ResolveOpsImpl() { } public ResolveOpsImpl( final DiscoverySourceManager sourceManager, final ProjectRelationshipDiscoverer discoverer, final ArtifactManager artifacts, final ExecutorService executor, final RecipeResolver dtoResolver, GraphResolver resolver ) { this.sourceManager = sourceManager; this.discoverer = discoverer; this.artifacts = artifacts; this.executor = executor; this.recipeResolver = dtoResolver; this.resolver = resolver; } @Override public Map<ProjectVersionRef, Map<ArtifactRef, ConcreteResource>> resolveRepositoryContents( final RepositoryContentRequest recipe ) throws CartoDataException, CartoRequestException { recipeResolver.resolve( recipe ); if ( recipe == null || !recipe.isValid() ) { throw new CartoDataException( "Repository content request is invalid: {}", recipe ); } final URI sourceUri = sourceManager.createSourceURI( recipe.getSourceLocation().getUri() ); if ( sourceUri == null ) { throw new CartoDataException( "Invalid source format: '{}'. Use the form: '{}' instead.", recipe.getSourceLocation(), sourceManager.getFormatHint() ); } // FIXME: lambdas... final Map<ProjectVersionRef, ProjectRefCollection> refMap = resolveReferenceMap( recipe ); final List<RepoContentCollector> collectors = collectContent( refMap, recipe ); final Map<ProjectVersionRef, Map<ArtifactRef, ConcreteResource>> itemMap = new HashMap<>(); for ( final RepoContentCollector collector : collectors ) { final Map<ArtifactRef, ConcreteResource> items = collector.getItems(); if ( items != null && !items.isEmpty() ) { logger.debug( "{} Returning for: {}\n\n {}", collector, collector.getRef(), new JoinString( "\n ", items.entrySet() ) ); Map<ArtifactRef, ConcreteResource> existingItems = itemMap.get( collector.getRef() ); if ( existingItems == null ) { itemMap.put( collector.getRef(), items ); existingItems = items; } else { existingItems.putAll( items ); } logger.debug( "{} Accumulated for: {}\n\n {}", collector, collector.getRef(), new JoinString( "\n ", existingItems.entrySet() ) ); } else { logger.warn( "{} No items returned for: {}", collector, collector.getRef() ); } } return itemMap; } private List<RepoContentCollector> collectContent( final Map<ProjectVersionRef, ProjectRefCollection> refMap, final RepositoryContentRequest recipe ) throws CartoDataException, CartoRequestException { final Location location = recipe.getSourceLocation(); final Set<Location> excluded = recipe.getExcludedSourceLocations(); if ( excluded != null && excluded.contains( location ) ) { // no sense in going through all the rest if everything is excluded... throw new CartoDataException( "RepositoryContentRequest is insane! Source location is among those excluded!" ); } int projectCounter = 1; final int projectSz = refMap.size(); final List<RepoContentCollector> collectors = new ArrayList<>( projectSz ); final DiscoveryConfig dconf = recipe.getDiscoveryConfig(); for ( final Map.Entry<ProjectVersionRef, ProjectRefCollection> entry : refMap.entrySet() ) { final ProjectVersionRef ref = entry.getKey(); final ProjectRefCollection refs = entry.getValue(); final RepoContentCollector collector = new RepoContentCollector( ref, refs, recipe, location, dconf, artifacts, discoverer, excluded, projectCounter, projectSz ); collectors.add( collector ); projectCounter++; } final CountDownLatch latch = new CountDownLatch( collectors.size() ); for ( final RepoContentCollector collector : collectors ) { collector.setLatch( latch ); executor.execute( collector ); } // TODO: timeout with loop... while ( latch.getCount() > 0 ) { logger.info( "Waiting for {} more content-collection threads to complete.", latch.getCount() ); try { latch.await( 2, TimeUnit.SECONDS ); } catch ( final InterruptedException e ) { logger.error( "Abandoning repo-content assembly for: {}", recipe ); } } return collectors; } /** * Discover the dependency graphs for the configured graph composition, and then traverse them to construct a mapping of GAV to set of references * that can be used to render various kinds of output. If the request contains injectedBOMs, then read the managed dependencies from these into * a mapping of GA -> GAV that we can pass into the {@link ViewParams} we'll eventually use to discover and traverse the graph. * <br/> * Returns null if {@link RepositoryContentRequest#setSourceLocation(Location)} hasn't * been called before this method is called. * <br/> * @throws {@link CartoDataException} if one or more of the request's injected BOMs cannot be resolved or if an * unexpected problem takes place during graph resolution or traversal. * @throws {@link CartoRequestException} if the request doesn't contain enough basic * info to be used (See: {@link RepositoryContentRequest#isValid()}) or if the source {@link Location} hasn't been set on the request */ private Map<ProjectVersionRef, ProjectRefCollection> resolveReferenceMap( final RepositoryContentRequest recipe ) throws CartoDataException, CartoRequestException { logger.info( "Building repository for: {}", recipe ); recipeResolver.resolve( recipe ); final Map<ProjectVersionRef, ProjectRefCollection> refMap = new HashMap<>(); final MultiGraphFunction<Set<ProjectRelationship<?, ?>>> extractor = ( allRels, graphMap ) -> { try { refMap.putAll( collectProjectVersionReferences( allRels ) ); for ( final RelationshipGraph graph : graphMap.values() ) { for ( final ProjectVersionRef root : graph.getRoots() ) { ProjectRefCollection refCollection = refMap.get( root ); if ( refCollection == null ) { refCollection = new ProjectRefCollection(); refCollection.addVersionRef( root ); refMap.put( root, refCollection ); } if ( root instanceof ArtifactRef ) { refCollection.addArtifactRef( (ArtifactRef) root ); } } } } finally { graphMap.values().forEach( IOUtils::closeQuietly ); } }; resolver.resolveAndExtractMultiGraph( AnyFilter.INSTANCE, recipe, ( allRefs, allRels, roots ) -> allRels.get(), extractor ); return refMap; } }