/**
* 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.lang.StringUtils;
import org.commonjava.cartographer.CartoDataException;
import org.commonjava.cartographer.CartoRequestException;
import org.commonjava.cartographer.graph.GraphResolver;
import org.commonjava.cartographer.graph.fn.GraphFunction;
import org.commonjava.cartographer.graph.fn.MatchingProjectFunction;
import org.commonjava.cartographer.graph.fn.MultiGraphFunction;
import org.commonjava.cartographer.graph.fn.ProjectCollector;
import org.commonjava.cartographer.graph.fn.ProjectProjector;
import org.commonjava.cartographer.graph.fn.ProjectSelector;
import org.commonjava.cartographer.graph.fn.ValueHolder;
import org.commonjava.cartographer.graph.util.CartoGraphUtils;
import org.commonjava.cartographer.ops.GraphOps;
import org.commonjava.cartographer.request.GraphDescription;
import org.commonjava.cartographer.request.PathsRequest;
import org.commonjava.cartographer.request.ProjectGraphRelationshipsRequest;
import org.commonjava.cartographer.request.ProjectGraphRequest;
import org.commonjava.cartographer.request.SingleGraphRequest;
import org.commonjava.cartographer.result.GraphExport;
import org.commonjava.cartographer.result.MappedProjectRelationships;
import org.commonjava.cartographer.result.MappedProjectRelationshipsResult;
import org.commonjava.cartographer.result.MappedProjectResult;
import org.commonjava.cartographer.result.MappedProjects;
import org.commonjava.cartographer.result.MappedProjectsResult;
import org.commonjava.cartographer.result.ProjectError;
import org.commonjava.cartographer.result.ProjectErrors;
import org.commonjava.cartographer.result.ProjectListResult;
import org.commonjava.cartographer.result.ProjectPath;
import org.commonjava.cartographer.result.ProjectPathsResult;
import org.commonjava.cartographer.graph.RelationshipGraph;
import org.commonjava.cartographer.graph.RelationshipGraphException;
import org.commonjava.cartographer.graph.filter.AnyFilter;
import org.commonjava.cartographer.graph.filter.ParentFilter;
import org.commonjava.cartographer.graph.filter.ProjectRelationshipFilter;
import org.commonjava.maven.atlas.graph.model.EProjectCycle;
import org.commonjava.maven.atlas.graph.rel.ParentRelationship;
import org.commonjava.maven.atlas.graph.rel.ProjectRelationship;
import org.commonjava.cartographer.graph.spi.RelationshipGraphConnectionException;
import org.commonjava.cartographer.graph.spi.neo4j.io.Conversions;
import org.commonjava.cartographer.graph.traverse.BuildOrderTraversal;
import org.commonjava.cartographer.graph.traverse.PathsTraversal;
import org.commonjava.cartographer.graph.traverse.TraversalType;
import org.commonjava.cartographer.graph.traverse.model.BuildOrder;
import org.commonjava.maven.atlas.ident.ref.ProjectVersionRef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ApplicationScoped
public class GraphOpsImpl
implements GraphOps
{
private final Logger logger = LoggerFactory.getLogger( getClass() );
@Inject
private GraphResolver resolver;
protected GraphOpsImpl()
{
}
public GraphOpsImpl( final GraphResolver resolver )
{
this.resolver = resolver;
}
@Override
public ProjectListResult listProjects( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
final ProjectListResult result = new ProjectListResult();
final ProjectProjector<ProjectVersionRef> extractor = ( ref, graph ) -> graph.containsGraph( ref ) ? ref : null;
final ProjectCollector<ProjectVersionRef> consumer = ( unused, ref ) -> {
if ( ref != null )
{
result.addProject( ref );
}
};
resolver.resolveAndExtractSingleGraph( AnyFilter.INSTANCE, recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer ) );
return result;
}
@Override
public ProjectPathsResult getPaths( final PathsRequest recipe )
throws CartoDataException, CartoRequestException
{
// Collections.sort( paths, RelationshipPathComparator.INSTANCE );
ProjectPathsResult result = new ProjectPathsResult();
final MultiGraphFunction<Set<ProjectRelationship<?, ?>>> extractor = ( allRels, graphMap ) -> {
for ( final GraphDescription desc : graphMap.keySet() )
{
final RelationshipGraph graph = graphMap.get( desc );
final ProjectRelationshipFilter filter = desc.filter();
final PathsTraversal paths = new PathsTraversal( recipe.buildFilter( filter ), recipe.getTargets() );
try
{
graph.traverse( paths, TraversalType.depth_first );
}
catch ( final RelationshipGraphException ex )
{
throw new CartoDataException(
"Failed to open / traverse the graph (for paths operation): " + ex.getMessage(),
ex );
}
final Set<List<ProjectRelationship<?, ?>>> discoveredPaths = paths.getDiscoveredPaths();
for ( final List<ProjectRelationship<?, ?>> path : discoveredPaths )
{
if ( path == null || path.isEmpty() )
{
continue;
}
for ( final ProjectRelationship<?, ?> rel : path )
{
if ( !allRels.contains( rel ) )
{
// continue to the next path...
break;
}
}
List<ProjectRelationship<?, ?>> detachedPath = Conversions.convertToDetachedRelationships( path );
final ProjectVersionRef ref = detachedPath.get( path.size() - 1 ).getTarget();
result.addPath( ref, new ProjectPath( detachedPath ) );
}
}
};
resolver.resolveAndExtractMultiGraph( AnyFilter.INSTANCE, recipe,
( allProjects, allRels, roots ) -> allRels.get(), extractor );
return result;
}
@Override
public ProjectErrors getProjectErrors( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
return getAllProjectErrors( recipe );
}
private ProjectErrors getAllProjectErrors( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
final ProjectErrors result = new ProjectErrors();
final ProjectProjector<String> extractor = ( ref, graph ) -> {
final String error = graph.getProjectError( ref );
if ( StringUtils.isEmpty( error ) )
{
return null;
}
return error;
};
final ProjectCollector<String> consumer = ( ref, error ) -> {
if ( error != null )
{
result.addProject( new ProjectError( ref, error ) );
}
};
resolver.resolveAndExtractSingleGraph( AnyFilter.INSTANCE, recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer ) );
return result;
}
@Override
public MappedProjectResult getProjectParent( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
MappedProjectResult result = new MappedProjectResult();
final ProjectProjector<ProjectVersionRef> extractor = ( ref, graph ) -> {
final Set<ProjectRelationship<?, ?>> rels = graph.getDirectRelationships( ref );
for ( final ProjectRelationship<?, ?> rel : rels )
{
if ( rel instanceof ParentRelationship )
{
return rel.getTarget();
}
}
return null;
};
final ProjectCollector<ProjectVersionRef> consumer = result::addProject;
resolver.resolveAndExtractSingleGraph( ParentFilter.EXCLUDE_TERMINAL_PARENTS, recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer ) );
return result;
}
@Override
public MappedProjectRelationshipsResult getDirectRelationshipsFrom( final ProjectGraphRelationshipsRequest recipe )
throws CartoDataException, CartoRequestException
{
MappedProjectRelationshipsResult result = new MappedProjectRelationshipsResult();
final ProjectProjector<Set<ProjectRelationship<?, ?>>> extractor = ( ref, graph ) -> {
final Set<ProjectRelationship<?, ?>> rels = graph.findDirectRelationshipsFrom( ref, recipe.isManagedIncluded(),
recipe.isConcreteIncluded(),
recipe.toTypeArray() );
return rels == null || rels.isEmpty() ? null : new HashSet<>( rels );
};
final ProjectCollector<Set<ProjectRelationship<?, ?>>> consumer = ( ref, rels ) -> {
if ( rels != null )
{
result.addProject( new MappedProjectRelationships( ref, rels ) );
}
};
resolver.resolveAndExtractSingleGraph( recipe.getTypeFilter(), recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer ) );
return result;
}
@Override
public MappedProjectRelationshipsResult getDirectRelationshipsTo( final ProjectGraphRelationshipsRequest recipe )
throws CartoDataException, CartoRequestException
{
MappedProjectRelationshipsResult result = new MappedProjectRelationshipsResult();
final ProjectProjector<Set<ProjectRelationship<?, ?>>> extractor = ( ref, graph ) -> {
final Set<ProjectRelationship<?, ?>> rels = graph.findDirectRelationshipsTo( ref, recipe.isManagedIncluded(),
recipe.isConcreteIncluded(),
recipe.toTypeArray() );
return rels == null || rels.isEmpty() ? null : new HashSet<>( rels );
};
final ProjectCollector<Set<ProjectRelationship<?, ?>>> consumer = ( ref, rels ) -> {
if ( rels != null )
{
result.addProject( new MappedProjectRelationships( ref, rels ) );
}
};
resolver.resolveAndExtractSingleGraph( recipe.getTypeFilter(), recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer ) );
return result;
}
@Override
public ProjectListResult reindex( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
return doReindex( recipe );
}
private ProjectListResult doReindex( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
recipe.setResolve( false );
if ( recipe.getGraph().filter() == null )
{
recipe.getGraph().setFilter( AnyFilter.INSTANCE );
}
final ProjectListResult result = new ProjectListResult();
final ProjectProjector<ProjectVersionRef> extractor = ( ref, graph ) -> {
try
{
graph.reindex( ref );
return ref;
}
catch ( final RelationshipGraphConnectionException e )
{
logger.error( String.format( "Failed to re-index %s in: %s", ref, recipe.getWorkspaceId() ), e );
}
return null;
};
final ProjectCollector<ProjectVersionRef> consumer = ( unused, ref ) -> {
if ( ref != null )
{
result.addProject( ref );
}
};
resolver.resolveAndExtractSingleGraph( AnyFilter.INSTANCE, recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer ) );
return result;
}
@Override
public ProjectListResult getIncomplete( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
final ProjectListResult result = new ProjectListResult();
final ProjectProjector<ProjectVersionRef> extractor = ( ref, graph ) -> ref;
final ProjectCollector<ProjectVersionRef> consumer = ( unused, ref ) -> result.addProject( ref );
final ProjectSelector supplier = RelationshipGraph::getIncompleteSubgraphs;
resolver.resolveAndExtractSingleGraph( AnyFilter.INSTANCE, recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer,
supplier ) );
return result;
}
@Override
public ProjectListResult getVariable( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
final ProjectListResult result = new ProjectListResult();
final ProjectProjector<ProjectVersionRef> extractor = ( ref, graph ) -> ref;
final ProjectCollector<ProjectVersionRef> consumer = ( unused, ref ) -> result.addProject( ref );
final ProjectSelector supplier = RelationshipGraph::getVariableSubgraphs;
resolver.resolveAndExtractSingleGraph( AnyFilter.INSTANCE, recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer,
supplier ) );
return result;
}
@Override
public MappedProjectsResult getAncestry( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
final MappedProjectsResult result = new MappedProjectsResult();
final ProjectProjector<List<ProjectVersionRef>> extractor = ( ref, graph ) -> {
try
{
return CartoGraphUtils.getAncestry( ref, graph );
}
catch ( final RelationshipGraphException e )
{
logger.error( String.format( "Failed to retrieve ancestry of: %s in: %s", ref,
recipe.getWorkspaceId() ), e );
}
return Collections.emptyList();
};
final ProjectCollector<List<ProjectVersionRef>> consumer =
( ref, mapped ) -> result.addProject( new MappedProjects( ref, mapped ) );
resolver.resolveAndExtractSingleGraph( AnyFilter.INSTANCE, recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer ) );
return result;
}
@Override
public BuildOrder getBuildOrder( final ProjectGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
final BuildOrderTraversal traversal = new BuildOrderTraversal();
final ProjectProjector<ProjectVersionRef> extractor = ( ref, graph ) -> {
try
{
graph.traverse( ref, traversal, TraversalType.breadth_first );
return ref;
}
catch ( final RelationshipGraphException e )
{
logger.error( String.format( "Failed to traverse graph: %s to discover build order for: %s",
recipe.getWorkspaceId(), ref ), e );
}
return null;
};
final ProjectCollector<ProjectVersionRef> consumer = ( ref, ref2 ) -> {
};
final ProjectSelector supplier = RelationshipGraph::getRoots;
resolver.resolveAndExtractSingleGraph( AnyFilter.INSTANCE, recipe,
new MatchingProjectFunction<>( recipe, extractor, consumer,
supplier ) );
return traversal.getBuildOrder();
}
@Override
public GraphExport exportGraph( final SingleGraphRequest recipe )
throws CartoDataException, CartoRequestException
{
final ValueHolder<GraphExport> holder = new ValueHolder<>();
final GraphFunction extractor = ( graph ) -> {
final Set<ProjectRelationship<?, ?>> rels = graph.getAllRelationships();
final Set<ProjectVersionRef> missing = graph.getAllIncompleteSubgraphs();
if ( missing != null && missing.containsAll( recipe.getGraph().getRoots() ) )
{
holder.consumer().accept( null );
}
final Set<ProjectVersionRef> variable = graph.getAllVariableSubgraphs();
final Set<EProjectCycle> cycles = graph.getCycles();
final Map<ProjectVersionRef, String> errorMap = graph.getAllProjectErrors();
ProjectErrors errors = new ProjectErrors();
for ( ProjectVersionRef key : errorMap.keySet() )
{
errors.addProject( new ProjectError( key, errorMap.get( key ) ) );
}
holder.consumer().accept( new GraphExport( rels, missing, variable, errors, cycles ) );
};
resolver.resolveAndExtractSingleGraph( AnyFilter.INSTANCE, recipe, extractor );
return holder.get();
}
}