/** * Copyright (c) 2002-2012 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.consistency.store; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.neo4j.consistency.checking.ComparativeRecordChecker; import org.neo4j.consistency.report.ConsistencyReport; import org.neo4j.consistency.report.PendingReferenceCheck; import org.neo4j.kernel.impl.nioneo.store.AbstractBaseRecord; import org.neo4j.kernel.impl.nioneo.store.DynamicRecord; import org.neo4j.kernel.impl.nioneo.store.NeoStoreRecord; import org.neo4j.kernel.impl.nioneo.store.NodeRecord; import org.neo4j.kernel.impl.nioneo.store.PropertyIndexRecord; import org.neo4j.kernel.impl.nioneo.store.PropertyRecord; import org.neo4j.kernel.impl.nioneo.store.PropertyType; import org.neo4j.kernel.impl.nioneo.store.RelationshipRecord; import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord; import static java.util.Collections.singletonMap; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; public class RecordAccessStub implements RecordAccess, DiffRecordAccess { @SuppressWarnings("unchecked") public <RECORD extends AbstractBaseRecord, REPORT extends ConsistencyReport<RECORD, REPORT>> REPORT mockReport( Class<REPORT> reportClass, RECORD record ) { REPORT report = mock( reportClass ); doAnswer( new DeferredReferenceDispatch( report, record ) ) .when( report ).forReference( any( RecordReference.class ), any( ComparativeRecordChecker.class ) ); return report; } @SuppressWarnings("unchecked") public <RECORD extends AbstractBaseRecord, REPORT extends ConsistencyReport<RECORD, REPORT>> REPORT mockReport( Class<REPORT> reportClass, RECORD oldRecord, RECORD newRecord ) { REPORT report = mock( reportClass ); doAnswer( new DeferredReferenceDispatch( report, oldRecord, newRecord ) ) .when( report ).forReference( any( RecordReference.class ), any( ComparativeRecordChecker.class ) ); return report; } private class DeferredReferenceDispatch<RECORD extends AbstractBaseRecord, REPORT extends ConsistencyReport<RECORD, REPORT>> implements Answer<Void> { private final REPORT report; private final RECORD oldRecord; private final RECORD newRecord; DeferredReferenceDispatch( REPORT report, RECORD oldRecord, RECORD newRecord ) { this.report = report; this.oldRecord = oldRecord; this.newRecord = newRecord; } DeferredReferenceDispatch( REPORT report, RECORD record ) { this.report = report; this.oldRecord = null; this.newRecord = record; } @Override public Void answer( InvocationOnMock invocation ) throws Throwable { Object[] arguments = invocation.getArguments(); forReference( (RecordReference) arguments[0], (ComparativeRecordChecker) arguments[1] ); return null; } private void forReference( final RecordReference reference, final ComparativeRecordChecker checker ) { deferredTasks.add( new Runnable() { @Override @SuppressWarnings("unchecked") public void run() { PendingReferenceCheck mock = mock( PendingReferenceCheck.class ); DeferredReferenceCheck check = new DeferredReferenceCheck( DeferredReferenceDispatch.this, checker ); doAnswer( check ).when( mock ).checkReference( any( AbstractBaseRecord.class ), any( RecordAccess.class ) ); doAnswer( check ).when( mock ).checkDiffReference( any( AbstractBaseRecord.class ), any( AbstractBaseRecord.class ), any( RecordAccess.class ) ); reference.dispatch( mock ); } } ); } void checkReference( final ComparativeRecordChecker checker, final AbstractBaseRecord oldReference, final AbstractBaseRecord newReference ) { deferredTasks.add( new Runnable() { @Override @SuppressWarnings("unchecked") public void run() { checker.checkReference( newRecord, newReference, report, RecordAccessStub.this ); } } ); } } private static class DeferredReferenceCheck implements Answer<Void> { private final DeferredReferenceDispatch dispatch; private final ComparativeRecordChecker checker; DeferredReferenceCheck( DeferredReferenceDispatch dispatch, ComparativeRecordChecker checker ) { this.dispatch = dispatch; this.checker = checker; } @Override public Void answer( InvocationOnMock invocation ) throws Throwable { Object[] arguments = invocation.getArguments(); AbstractBaseRecord oldReference = null, newReference; if ( arguments.length == 3 ) { oldReference = (AbstractBaseRecord) arguments[0]; newReference = (AbstractBaseRecord) arguments[1]; } else { newReference = (AbstractBaseRecord) arguments[0]; } dispatch.checkReference( checker, oldReference, newReference ); return null; } } private final Queue<Runnable> deferredTasks = new LinkedList<Runnable>(); public void checkDeferred() { for ( Runnable task; null != (task = deferredTasks.poll()); ) { task.run(); } } private final Map<Long, Delta<NodeRecord>> nodes = new HashMap<Long, Delta<NodeRecord>>(); private final Map<Long, Delta<RelationshipRecord>> relationships = new HashMap<Long, Delta<RelationshipRecord>>(); private final Map<Long, Delta<PropertyRecord>> properties = new HashMap<Long, Delta<PropertyRecord>>(); private final Map<Long, Delta<DynamicRecord>> strings = new HashMap<Long, Delta<DynamicRecord>>(); private final Map<Long, Delta<DynamicRecord>> arrays = new HashMap<Long, Delta<DynamicRecord>>(); private final Map<Long, Delta<RelationshipTypeRecord>> labels = new HashMap<Long, Delta<RelationshipTypeRecord>>(); private final Map<Long, Delta<PropertyIndexRecord>> keys = new HashMap<Long, Delta<PropertyIndexRecord>>(); private final Map<Long, Delta<DynamicRecord>> labelNames = new HashMap<Long, Delta<DynamicRecord>>(); private final Map<Long, Delta<DynamicRecord>> keyNames = new HashMap<Long, Delta<DynamicRecord>>(); private Delta<NeoStoreRecord> graph; private static class Delta<R extends AbstractBaseRecord> { final R oldRecord, newRecord; Delta( R record ) { this.oldRecord = null; this.newRecord = record; } Delta( R oldRecord, R newRecord ) { this.oldRecord = oldRecord; this.newRecord = newRecord; } } private enum Version { PREV { @Override <R extends AbstractBaseRecord> R get( Delta<R> delta ) { return delta.oldRecord == null ? delta.newRecord : delta.oldRecord; } }, LATEST { @Override <R extends AbstractBaseRecord> R get( Delta<R> delta ) { return delta.newRecord; } }, NEW { @Override <R extends AbstractBaseRecord> R get( Delta<R> delta ) { return delta.oldRecord == null ? null : delta.newRecord; } }; abstract <R extends AbstractBaseRecord> R get( Delta<R> delta ); } private static <R extends AbstractBaseRecord> R add( Map<Long, Delta<R>> records, R record ) { records.put( record.getLongId(), new Delta<R>( record ) ); return record; } private static <R extends AbstractBaseRecord> void add( Map<Long, Delta<R>> records, R oldRecord, R newRecord ) { records.put( newRecord.getLongId(), new Delta<R>( oldRecord, newRecord ) ); } public DynamicRecord addString( DynamicRecord string ) { return add( strings, string ); } public DynamicRecord addArray( DynamicRecord array ) { return add( arrays, array ); } public DynamicRecord addKeyName( DynamicRecord name ) { return add( keyNames, name ); } public DynamicRecord addLabelName( DynamicRecord name ) { return add( labelNames, name ); } public <R extends AbstractBaseRecord> R addChange( R oldRecord, R newRecord ) { if ( newRecord instanceof NodeRecord ) { add( nodes, (NodeRecord) oldRecord, (NodeRecord) newRecord ); } else if ( newRecord instanceof RelationshipRecord ) { add( relationships, (RelationshipRecord) oldRecord, (RelationshipRecord) newRecord ); } else if ( newRecord instanceof PropertyRecord ) { add( properties, (PropertyRecord) oldRecord, (PropertyRecord) newRecord ); } else if ( newRecord instanceof DynamicRecord ) { DynamicRecord dyn = (DynamicRecord) newRecord; if ( dyn.getType() == PropertyType.STRING.intValue() ) { add( strings, (DynamicRecord) oldRecord, dyn ); } else if ( dyn.getType() == PropertyType.ARRAY.intValue() ) { add( arrays, (DynamicRecord) oldRecord, dyn ); } else { throw new IllegalArgumentException( "Invalid dynamic record type" ); } } else if ( newRecord instanceof RelationshipTypeRecord ) { add( labels, (RelationshipTypeRecord) oldRecord, (RelationshipTypeRecord) newRecord ); } else if ( newRecord instanceof PropertyIndexRecord ) { add( keys, (PropertyIndexRecord) oldRecord, (PropertyIndexRecord) newRecord ); } else if ( newRecord instanceof NeoStoreRecord ) { this.graph = new Delta<NeoStoreRecord>( (NeoStoreRecord) oldRecord, (NeoStoreRecord) newRecord ); } else { throw new IllegalArgumentException( "Invalid record type" ); } return newRecord; } public <R extends AbstractBaseRecord> R add( R record ) { if ( record instanceof NodeRecord ) { add( nodes, (NodeRecord) record ); } else if ( record instanceof RelationshipRecord ) { add( relationships, (RelationshipRecord) record ); } else if ( record instanceof PropertyRecord ) { add( properties, (PropertyRecord) record ); } else if ( record instanceof DynamicRecord ) { DynamicRecord dyn = (DynamicRecord) record; if ( dyn.getType() == PropertyType.STRING.intValue() ) { addString( dyn ); } else if ( dyn.getType() == PropertyType.ARRAY.intValue() ) { addArray( dyn ); } else { throw new IllegalArgumentException( "Invalid dynamic record type" ); } } else if ( record instanceof RelationshipTypeRecord ) { add( labels, (RelationshipTypeRecord) record ); } else if ( record instanceof PropertyIndexRecord ) { add( keys, (PropertyIndexRecord) record ); } else if ( record instanceof NeoStoreRecord ) { this.graph = new Delta<NeoStoreRecord>( (NeoStoreRecord) record ); } else { throw new IllegalArgumentException( "Invalid record type" ); } return record; } private <R extends AbstractBaseRecord> DirectRecordReference<R> reference( Map<Long, Delta<R>> records, long id, Version version ) { return new DirectRecordReference<R>( record( records, id, version ), this ); } private static <R extends AbstractBaseRecord> R record( Map<Long, Delta<R>> records, long id, Version version ) { Delta<R> delta = records.get( id ); if ( delta == null ) { if ( version == Version.NEW ) { return null; } throw new AssertionError( String.format( "Access to record with id=%d not expected.", id ) ); } return version.get( delta ); } @Override public RecordReference<NodeRecord> node( long id ) { return reference( nodes, id, Version.LATEST ); } @Override public RecordReference<RelationshipRecord> relationship( long id ) { return reference( relationships, id, Version.LATEST ); } @Override public RecordReference<PropertyRecord> property( long id ) { return reference( properties, id, Version.LATEST ); } @Override public RecordReference<RelationshipTypeRecord> relationshipLabel( int id ) { return reference( labels, id, Version.LATEST ); } @Override public RecordReference<PropertyIndexRecord> propertyKey( int id ) { return reference( keys, id, Version.LATEST ); } @Override public RecordReference<DynamicRecord> string( long id ) { return reference( strings, id, Version.LATEST ); } @Override public RecordReference<DynamicRecord> array( long id ) { return reference( arrays, id, Version.LATEST ); } @Override public RecordReference<DynamicRecord> relationshipLabelName( int id ) { return reference( labelNames, id, Version.LATEST ); } @Override public RecordReference<DynamicRecord> propertyKeyName( int id ) { return reference( keyNames, id, Version.LATEST ); } @Override public RecordReference<NeoStoreRecord> graph() { return reference( singletonMap( -1L, graph ), -1, Version.LATEST ); } @Override public RecordReference<NodeRecord> previousNode( long id ) { return reference( nodes, id, Version.PREV ); } @Override public RecordReference<RelationshipRecord> previousRelationship( long id ) { return reference( relationships, id, Version.PREV ); } @Override public RecordReference<PropertyRecord> previousProperty( long id ) { return reference( properties, id, Version.PREV ); } @Override public NodeRecord changedNode( long id ) { return record( nodes, id, Version.NEW ); } @Override public RelationshipRecord changedRelationship( long id ) { return record( relationships, id, Version.NEW ); } @Override public PropertyRecord changedProperty( long id ) { return record( properties, id, Version.NEW ); } @Override public DynamicRecord changedString( long id ) { return record( strings, id, Version.NEW ); } @Override public DynamicRecord changedArray( long id ) { return record( arrays, id, Version.NEW ); } @Override public RecordReference<NeoStoreRecord> previousGraph() { return reference( singletonMap( -1L, graph ), -1, Version.PREV ); } }