/**
* Copyright (C) 2012 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.graph.spi.neo4j.traverse;
import static org.commonjava.cartographer.graph.spi.neo4j.io.Conversions.toProjectRelationship;
import static org.commonjava.cartographer.graph.spi.neo4j.traverse.TraversalUtils.accepted;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import org.commonjava.cartographer.graph.ViewParams;
import org.commonjava.cartographer.graph.filter.ProjectRelationshipFilter;
import org.commonjava.cartographer.graph.model.GraphPathInfo;
import org.commonjava.maven.atlas.graph.rel.ProjectRelationship;
import org.commonjava.cartographer.graph.spi.RelationshipGraphConnection;
import org.commonjava.cartographer.graph.spi.neo4j.GraphAdmin;
import org.commonjava.cartographer.graph.spi.neo4j.GraphRelType;
import org.commonjava.cartographer.graph.spi.neo4j.model.CyclePath;
import org.commonjava.cartographer.graph.spi.neo4j.model.Neo4jGraphPath;
import org.commonjava.cartographer.graph.spi.neo4j.update.CycleCacheUpdater;
import org.commonjava.maven.atlas.ident.util.JoinString;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.PathExpander;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.traversal.BranchState;
import org.neo4j.graphdb.traversal.Evaluation;
import org.neo4j.graphdb.traversal.Evaluator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class AtlasCollector<STATE>
implements Evaluator, PathExpander<STATE>
{
private final Logger logger = LoggerFactory.getLogger( getClass() );
private Direction direction = Direction.OUTGOING;
private final Set<Node> startNodes;
private ViewParams view;
private TraverseVisitor visitor;
private GraphAdmin admin;
private boolean useSelections = true;
private Node viewNode;
private GraphRelType[] types;
private RelationshipGraphConnection connection;
public AtlasCollector( final TraverseVisitor visitor, final Node start,
final RelationshipGraphConnection connection, final ViewParams view, final Node viewNode,
final GraphAdmin admin, final GraphRelType... types )
{
this( visitor, Collections.singleton( start ), connection, view, viewNode, admin );
this.types = types;
}
public AtlasCollector( final TraverseVisitor visitor, final Set<Node> startNodes,
final RelationshipGraphConnection connection, final ViewParams view, final Node viewNode,
final GraphAdmin admin, final GraphRelType... types )
{
this.visitor = visitor;
this.connection = connection;
this.viewNode = viewNode;
this.admin = admin;
this.types = types;
visitor.configure( this );
this.startNodes = startNodes;
this.view = view;
}
public AtlasCollector( final TraverseVisitor visitor, final Set<Node> startNodes,
final RelationshipGraphConnection connection, final ViewParams view, final Node viewNode,
final GraphAdmin admin, final GraphRelType[] types, final Direction direction )
{
this( visitor, startNodes, connection, view, viewNode, admin, types );
this.direction = direction;
}
public void setUseSelections( final boolean useSelections )
{
this.useSelections = useSelections;
}
@Override
@SuppressWarnings( "rawtypes" )
public final Iterable<Relationship> expand( final Path path, final BranchState state )
{
if ( !visitor.isEnabledFor( path ) )
{
logger.debug( "Disabled, NOT expanding: {}", path );
return Collections.emptySet();
}
if ( !startNodes.isEmpty() )
{
final Node startNode = path.startNode();
if ( !startNodes.contains( startNode ) )
{
logger.debug( "Rejecting path; it does not start with one of our roots:\n\t{}", path );
return Collections.emptySet();
}
}
final Neo4jGraphPath graphPath = new Neo4jGraphPath( path );
GraphPathInfo pathInfo = new GraphPathInfo( connection, view );
// if we're here, we're pre-cleared to blindly construct this pathInfo (see child iteration below)
for ( final Long rid : graphPath )
{
final Relationship r = admin.getRelationship( rid );
pathInfo = pathInfo.getChildPathInfo( toProjectRelationship( r ) );
}
logger.debug( "For {}, using pathInfo: {}", graphPath, pathInfo );
final CyclePath cyclePath = CycleCacheUpdater.getTerminatingCycle( path );
if ( cyclePath != null )
{
final Relationship injector = path.lastRelationship();
logger.debug( "Detected cycle in progress for path: {} at relationship: {}\n Cycle path is: {}", path,
injector, cyclePath );
visitor.cycleDetected( cyclePath, injector );
}
// logger.debug( "Checking hasSeen for graphPath: {} with pathInfo: {} (actual path: {})", graphPath, pathInfo, path );
// if ( visitor.hasSeen( graphPath, pathInfo ) )
// {
// logger.debug( "Already seen: {} (path: {})", graphPath, path );
// return Collections.emptySet();
// }
// split this so we register both the seen and the cycle.
/*else*/if ( cyclePath != null )
{
return Collections.emptySet();
}
if ( returnChildren( path, graphPath, pathInfo ) )
{
// final ProjectRelationshipFilter nextFilter = pathInfo.getFilter();
// log( "Implementation says return the children of: {}\n lastRel={}\n nextFilter={}\n\n",
// path.endNode()
// .hasProperty( GAV ) ? path.endNode()
// .getProperty( GAV ) : "Unknown", path.lastRelationship(), nextFilter );
final Set<Relationship> nextRelationships = new TreeSet<Relationship>( new AtlasRelIndexComparator() );
GraphRelType[] childTypes = types;
final ProjectRelationshipFilter filter = pathInfo.getFilter();
if ( filter != null )
{
childTypes = TraversalUtils.getGraphRelTypes( filter );
}
logger.debug( "Getting relationships from node: {} with type in [{}] and direction: {} (path: {})",
path.endNode(), new JoinString( ", ", childTypes ), direction, path );
final Iterable<Relationship> relationships = path.endNode()
.getRelationships( direction, childTypes );
// logger.info( "{} Determining which of {} child relationships to expand traversal into for: {}\n{}", getClass().getName(), path.length(),
// path.endNode()
// .hasProperty( GAV ) ? path.endNode()
// .getProperty( GAV ) : "Unknown", new JoinString( "\n ", Thread.currentThread()
// .getStackTrace() ) );
for ( Relationship r : relationships )
{
logger.debug( "Analyzing child relationship for traversal potential: {}", r );
if ( useSelections )
{
final Relationship selected = admin.select( r, view, viewNode, pathInfo, graphPath );
if ( selected == null )
{
logger.debug( "selection failed for: {} at {}. Likely, this is filter rejection from: {}", r,
graphPath, pathInfo );
continue;
}
// if no selection happened and r is a selection-only relationship, skip it.
if ( selected == r && admin.isSelection( r, viewNode ) )
{
logger.debug( "{} is NOT the result of selection, yet it is marked as a selection relationship. Path: {}",
r, path );
continue;
}
if ( !accepted( selected, view ) )
{
logger.debug( "{} NOT accepted, likely due to incompatible POM location or source URI. Path: {}",
r, path );
continue;
}
if ( selected != null )
{
r = selected;
}
logger.debug( "After selection, using child relationship: {}", r );
}
final ProjectRelationship<?, ?> rel = toProjectRelationship( r );
final Neo4jGraphPath nextPath = new Neo4jGraphPath( graphPath, r );
final GraphPathInfo nextPathInfo = pathInfo.getChildPathInfo( rel );
logger.debug( "Including child: {} with next-path: {} and childPathInfo: {} from parent path: {}", r,
nextPath, nextPathInfo, path );
visitor.includingChild( r, nextPath, nextPathInfo, path );
logger.debug( "+= {}", wrap( r ) );
nextRelationships.add( r );
}
return nextRelationships;
}
logger.debug( "children not being returned for: {}", path );
return Collections.emptySet();
}
public boolean returnChildren( final Path path, final Neo4jGraphPath graphPath, final GraphPathInfo pathInfo )
{
// if there's a GraphPathInfo mapped for this path, then it was accepted during expansion.
return visitor.includeChildren( path, graphPath, pathInfo );
}
private Object wrap( final Relationship r )
{
return new Object()
{
@Override
public String toString()
{
return r + " " + String.valueOf( toProjectRelationship( r ) );
}
};
}
@Override
public final Evaluation evaluate( final Path path )
{
return Evaluation.INCLUDE_AND_CONTINUE;
}
@Override
public PathExpander<STATE> reverse()
{
final AtlasCollector<STATE> collector =
new AtlasCollector<STATE>( visitor, startNodes, connection, view, viewNode, admin, types,
direction.reverse() );
collector.setUseSelections( useSelections );
return collector;
}
}