/** * 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.update; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.commonjava.cartographer.graph.ViewParams; import org.commonjava.maven.atlas.graph.model.EProjectCycle; import org.commonjava.maven.atlas.graph.rel.ProjectRelationship; import org.commonjava.cartographer.graph.spi.neo4j.GraphAdmin; import org.commonjava.cartographer.graph.spi.neo4j.io.Conversions; 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.traverse.AbstractTraverseVisitor; import org.commonjava.cartographer.graph.spi.neo4j.traverse.AtlasCollector; import org.commonjava.maven.atlas.ident.util.JoinString; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Path; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.Transaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CycleCacheUpdater extends AbstractTraverseVisitor { private final Logger logger = LoggerFactory.getLogger( getClass() ); private final Node viewNode; final Set<EProjectCycle> cycles = new HashSet<EProjectCycle>(); private final ViewParams view; private final GraphAdmin admin; public CycleCacheUpdater( final ViewParams view, final Node viewNode, final GraphAdmin admin ) { this.view = view; this.viewNode = viewNode; this.admin = admin; } @Override public void cycleDetected( final CyclePath cyclicPath, final Relationship injector ) { if ( cyclicPath.length() < 1 ) { logger.debug( "No paths in cycle!" ); return; } addCycleInternal( cyclicPath, injector ); } public void addCycle( final CyclePath cyclicPath, final Relationship injector ) { if ( cyclicPath.length() < 1 ) { logger.debug( "No paths in cycle!" ); return; } addCycleInternal( cyclicPath, injector ); } private void addCycleInternal( final CyclePath cyclicPath, final Relationship injector ) { final Transaction tx = admin.beginTransaction(); try { logger.debug( "Adding cycle: {} (via: {})", cyclicPath, injector ); Conversions.storeCachedCyclePath( cyclicPath, viewNode ); final List<ProjectRelationship<?, ?>> cycle = new ArrayList<ProjectRelationship<?, ?>>(cyclicPath.length()); cycle.addAll( Conversions.convertToRelationships( cyclicPath, admin ) ); logger.info( "CYCLES += {\n {}\n}", new JoinString( "\n ", cycle ) ); cycles.add( new EProjectCycle( cycle ) ); tx.success(); } finally { tx.finish(); } } public static CyclePath getTerminatingCycle( final Path path ) { final Logger logger = LoggerFactory.getLogger( CycleCacheUpdater.class ); logger.debug( "Looking for terminating cycle in: {}", path ); final List<Long> rids = new ArrayList<Long>(); final List<Long> starts = new ArrayList<Long>(); for ( final Relationship pathR : path.relationships() ) { rids.add( pathR.getId() ); final long sid = pathR.getStartNode() .getId(); final long eid = pathR.getEndNode() .getId(); final int idx = starts.indexOf( eid ); if ( idx > -1 ) { final CyclePath cp = new CyclePath( rids.subList( idx, rids.size() ) ); logger.debug( "Detected cycle: {}", cp ); return cp; } starts.add( sid ); } logger.debug( "No cycle detected" ); return null; } public static CyclePath getTerminatingCycle( final Neo4jGraphPath graphPath, final GraphAdmin admin ) { final Logger logger = LoggerFactory.getLogger( CycleCacheUpdater.class ); logger.debug( "Looking for terminating cycle in: {}", graphPath ); final Map<Long, Long> startNodesToRids = new HashMap<Long, Long>(); final long[] rids = graphPath.getRelationshipIds(); Long startRid = null; for ( final long rid : rids ) { final Relationship r = admin.getRelationship( rid ); final long eid = r.getEndNode() .getId(); startRid = startNodesToRids.get( eid ); if ( startRid != null ) { break; } startNodesToRids.put( r.getStartNode() .getId(), r.getId() ); } if ( startRid != null ) { int i = 0; for ( ; i < rids.length; i++ ) { if ( rids[i] == startRid ) { break; } } final long[] cycle = new long[rids.length - i]; System.arraycopy( rids, i, cycle, 0, cycle.length ); final CyclePath cp = new CyclePath( cycle ); logger.debug( "Detected cycle: {}", cp ); return cp; } logger.debug( "No cycle detected" ); return null; } public int getCycleCount() { return cycles.size(); } public Set<EProjectCycle> getCycles() { return cycles; } @Override public void traverseComplete( final AtlasCollector<?> collector ) { final Transaction tx = admin.beginTransaction(); try { logger.info( "Clearing PENDING cycle-detection for: {} of view: {}", viewNode, view.getShortId() ); Conversions.setCycleDetectionPending( viewNode, false ); tx.success(); } finally { tx.finish(); } } }