/**
* 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.checking.full;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.neo4j.consistency.RecordType;
import org.neo4j.consistency.checking.CheckDecorator;
import org.neo4j.consistency.checking.ComparativeRecordChecker;
import org.neo4j.consistency.checking.DynamicStore;
import org.neo4j.consistency.checking.PrimitiveRecordCheck;
import org.neo4j.consistency.checking.RecordCheck;
import org.neo4j.consistency.report.ConsistencyReport;
import org.neo4j.consistency.store.DiffRecordAccess;
import org.neo4j.consistency.store.RecordAccess;
import org.neo4j.helpers.progress.ProgressListener;
import org.neo4j.helpers.progress.ProgressMonitorFactory;
import org.neo4j.kernel.impl.nioneo.store.AbstractNameRecord;
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.PrimitiveRecord;
import org.neo4j.kernel.impl.nioneo.store.PropertyBlock;
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.Record;
import org.neo4j.kernel.impl.nioneo.store.RelationshipRecord;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord;
import static java.util.Collections.unmodifiableMap;
import static org.neo4j.consistency.RecordType.ARRAY_PROPERTY;
import static org.neo4j.consistency.RecordType.PROPERTY_KEY_NAME;
import static org.neo4j.consistency.RecordType.RELATIONSHIP_LABEL_NAME;
import static org.neo4j.consistency.RecordType.STRING_PROPERTY;
class OwnerCheck implements CheckDecorator
{
private final ConcurrentMap<Long, PropertyOwner> owners;
private final Map<RecordType, ConcurrentMap<Long, DynamicOwner>> dynamics;
OwnerCheck( boolean active, DynamicStore... stores )
{
this.owners = active ? new ConcurrentHashMap<Long, PropertyOwner>( 16, 0.75f, 4 ) : null;
this.dynamics = active ? initialize( stores ) : null;
}
private static Map<RecordType, ConcurrentMap<Long, DynamicOwner>> initialize( DynamicStore[] stores )
{
EnumMap<RecordType, ConcurrentMap<Long, DynamicOwner>> map =
new EnumMap<RecordType, ConcurrentMap<Long, DynamicOwner>>( RecordType.class );
for ( DynamicStore store : stores )
{
map.put( store.type, new ConcurrentHashMap<Long, DynamicOwner>( 16, 0.75f, 4 ) );
}
return unmodifiableMap( map );
}
void scanForOrphanChains( ProgressMonitorFactory progressFactory )
{
List<Runnable> tasks = new ArrayList<Runnable>();
ProgressMonitorFactory.MultiPartBuilder progress = progressFactory
.multipleParts( "Checking for orphan chains" );
if ( owners != null )
{
tasks.add( new OrphanCheck( RecordType.PROPERTY, owners, progress ) );
}
if ( dynamics != null )
{
for ( Map.Entry<RecordType, ConcurrentMap<Long, DynamicOwner>> entry : dynamics.entrySet() )
{
tasks.add( new OrphanCheck( entry.getKey(), entry.getValue(), progress ) );
}
}
for ( Runnable task : tasks )
{
task.run();
}
}
private static class OrphanCheck implements Runnable
{
private final ConcurrentMap<Long, ? extends Owner> owners;
private final ProgressListener progress;
OrphanCheck( RecordType property, ConcurrentMap<Long, ? extends Owner> owners,
ProgressMonitorFactory.MultiPartBuilder progress )
{
this.owners = owners;
this.progress = progress.progressForPart( "Checking for orphan " + property.name() + " chains",
owners.size() );
}
@Override
public void run()
{
for ( Owner owner : owners.values() )
{
owner.checkOrphanage();
progress.add( 1 );
}
progress.done();
}
}
@Override
public RecordCheck<NeoStoreRecord, ConsistencyReport.NeoStoreConsistencyReport> decorateNeoStoreChecker(
PrimitiveRecordCheck<NeoStoreRecord, ConsistencyReport.NeoStoreConsistencyReport> checker )
{
if ( owners == null )
{
return checker;
}
return new PrimitiveCheckerDecorator<NeoStoreRecord, ConsistencyReport.NeoStoreConsistencyReport>( checker )
{
PropertyOwner owner( NeoStoreRecord record )
{
return PropertyOwner.OWNING_GRAPH;
}
};
}
@Override
public RecordCheck<NodeRecord, ConsistencyReport.NodeConsistencyReport> decorateNodeChecker(
PrimitiveRecordCheck<NodeRecord, ConsistencyReport.NodeConsistencyReport> checker )
{
if ( owners == null )
{
return checker;
}
return new PrimitiveCheckerDecorator<NodeRecord, ConsistencyReport.NodeConsistencyReport>( checker )
{
PropertyOwner owner( NodeRecord record )
{
return new PropertyOwner.OwningNode( record );
}
};
}
@Override
public RecordCheck<RelationshipRecord, ConsistencyReport.RelationshipConsistencyReport> decorateRelationshipChecker(
PrimitiveRecordCheck<RelationshipRecord, ConsistencyReport.RelationshipConsistencyReport> checker )
{
if ( owners == null )
{
return checker;
}
return new PrimitiveCheckerDecorator<RelationshipRecord, ConsistencyReport.RelationshipConsistencyReport>(
checker )
{
PropertyOwner owner( RelationshipRecord record )
{
return new PropertyOwner.OwningRelationship( record );
}
};
}
@Override
public RecordCheck<PropertyRecord, ConsistencyReport.PropertyConsistencyReport> decoratePropertyChecker(
final RecordCheck<PropertyRecord, ConsistencyReport.PropertyConsistencyReport> checker )
{
if ( owners == null && dynamics == null )
{
return checker;
}
return new RecordCheck<PropertyRecord, ConsistencyReport.PropertyConsistencyReport>()
{
@Override
public void check( PropertyRecord record, ConsistencyReport.PropertyConsistencyReport report,
RecordAccess records )
{
if ( record.inUse() )
{
if ( owners != null && Record.NO_PREVIOUS_PROPERTY.is( record.getPrevProp() ) )
{ // this record is first in a chain
PropertyOwner.UnknownOwner owner = new PropertyOwner.UnknownOwner();
report.forReference( owner, ORPHAN_CHECKER );
if ( null == owners.putIfAbsent( record.getId(), owner ) )
{
owner.markInCustody();
}
}
if ( dynamics != null )
{
List<PropertyBlock> blocks = record.getPropertyBlocks();
for ( PropertyBlock block : blocks )
{
RecordType type = recordType( block.forceGetType() );
if ( type != null )
{
ConcurrentMap<Long, DynamicOwner> dynamicOwners = dynamics.get( type );
if ( dynamicOwners != null )
{
long id = block.getSingleValueLong();
DynamicOwner.Property owner = new DynamicOwner.Property( type, record );
DynamicOwner prev = dynamicOwners.put( id, owner );
if ( prev != null )
{
report.forReference( prev.record( records ), owner );
}
}
}
}
}
}
checker.check( record, report, records );
}
@Override
public void checkChange( PropertyRecord oldRecord, PropertyRecord newRecord,
ConsistencyReport.PropertyConsistencyReport report, DiffRecordAccess records )
{
checker.checkChange( oldRecord, newRecord, report, records );
}
};
}
private RecordType recordType( PropertyType type )
{
if ( type != null )
{
switch ( type )
{
case STRING:
return STRING_PROPERTY;
case ARRAY:
return ARRAY_PROPERTY;
}
}
return null;
}
@Override
public RecordCheck<PropertyIndexRecord, ConsistencyReport.PropertyKeyConsistencyReport> decoratePropertyKeyChecker(
RecordCheck<PropertyIndexRecord, ConsistencyReport.PropertyKeyConsistencyReport> checker )
{
ConcurrentMap<Long, DynamicOwner> dynamicOwners = dynamicOwners( PROPERTY_KEY_NAME );
if ( dynamicOwners == null )
{
return checker;
}
return new NameCheckerDecorator
<PropertyIndexRecord, ConsistencyReport.PropertyKeyConsistencyReport>( checker, dynamicOwners )
{
@Override
DynamicOwner.NameOwner owner( PropertyIndexRecord record )
{
return new DynamicOwner.PropertyKey( record );
}
};
}
@Override
public RecordCheck<RelationshipTypeRecord, ConsistencyReport.LabelConsistencyReport> decorateLabelChecker(
RecordCheck<RelationshipTypeRecord, ConsistencyReport.LabelConsistencyReport> checker )
{
ConcurrentMap<Long, DynamicOwner> dynamicOwners = dynamicOwners( RELATIONSHIP_LABEL_NAME );
if ( dynamicOwners == null )
{
return checker;
}
return new NameCheckerDecorator
<RelationshipTypeRecord, ConsistencyReport.LabelConsistencyReport>( checker, dynamicOwners )
{
@Override
DynamicOwner.NameOwner owner( RelationshipTypeRecord record )
{
return new DynamicOwner.RelationshipLabel( record );
}
};
}
RecordCheck<DynamicRecord, ConsistencyReport.DynamicConsistencyReport> decorateDynamicChecker(
final RecordType type, final RecordCheck<DynamicRecord, ConsistencyReport.DynamicConsistencyReport> checker )
{
final ConcurrentMap<Long, DynamicOwner> dynamicOwners = dynamicOwners( type );
if ( dynamicOwners == null )
{
return checker;
}
return new RecordCheck<DynamicRecord, ConsistencyReport.DynamicConsistencyReport>()
{
@Override
public void check( DynamicRecord record, ConsistencyReport.DynamicConsistencyReport report,
RecordAccess records )
{
if ( record.inUse() )
{
DynamicOwner.Unknown owner = new DynamicOwner.Unknown();
report.forReference( owner, DynamicOwner.ORPHAN_CHECK );
if ( null == dynamicOwners.putIfAbsent( record.getId(), owner ) )
{
owner.markInCustody();
}
if ( !Record.NO_NEXT_BLOCK.is( record.getNextBlock() ) )
{
DynamicOwner.Dynamic nextOwner = new DynamicOwner.Dynamic( type, record );
DynamicOwner prevOwner = dynamicOwners.put( record.getNextBlock(), nextOwner );
if ( prevOwner != null )
{
report.forReference( prevOwner.record( records ), nextOwner );
}
}
}
checker.check( record, report, records );
}
@Override
public void checkChange( DynamicRecord oldRecord, DynamicRecord newRecord,
ConsistencyReport.DynamicConsistencyReport report, DiffRecordAccess records )
{
checker.checkChange( oldRecord, newRecord, report, records );
}
};
}
private ConcurrentMap<Long, DynamicOwner> dynamicOwners( RecordType type )
{
return dynamics == null ? null : dynamics.get( type );
}
private abstract class PrimitiveCheckerDecorator<RECORD extends PrimitiveRecord,
REPORT extends ConsistencyReport.PrimitiveConsistencyReport<RECORD, REPORT>>
implements RecordCheck<RECORD, REPORT>
{
private final PrimitiveRecordCheck<RECORD, REPORT> checker;
PrimitiveCheckerDecorator( PrimitiveRecordCheck<RECORD, REPORT> checker )
{
this.checker = checker;
}
@Override
@SuppressWarnings("unchecked")
public void check( RECORD record, REPORT report, RecordAccess records )
{
if ( record.inUse() )
{
long prop = record.getNextProp();
if ( !Record.NO_NEXT_PROPERTY.is( prop ) )
{
PropertyOwner previous = owners.put( prop, owner( record ) );
if ( previous != null )
{
report.forReference( previous.record( records ), checker.ownerCheck );
}
}
}
checker.check( record, report, records );
}
@Override
public void checkChange( RECORD oldRecord, RECORD newRecord, REPORT report, DiffRecordAccess records )
{
checker.checkChange( oldRecord, newRecord, report, records );
}
abstract PropertyOwner owner( RECORD record );
}
private static abstract class NameCheckerDecorator
<RECORD extends AbstractNameRecord, REPORT extends ConsistencyReport.NameConsistencyReport<RECORD, REPORT>>
implements RecordCheck<RECORD, REPORT>
{
private final RecordCheck<RECORD, REPORT> checker;
private final ConcurrentMap<Long, DynamicOwner> owners;
public NameCheckerDecorator( RecordCheck<RECORD, REPORT> checker, ConcurrentMap<Long, DynamicOwner> owners )
{
this.checker = checker;
this.owners = owners;
}
@Override
public void check( RECORD record, REPORT report, RecordAccess records )
{
if ( record.inUse() )
{
DynamicOwner.NameOwner owner = owner( record );
DynamicOwner prev = owners.put( (long)record.getNameId(), owner );
if ( prev != null )
{
report.forReference( prev.record( records ), owner );
}
}
checker.check( record, report, records );
}
abstract DynamicOwner.NameOwner owner( RECORD record );
@Override
public void checkChange( RECORD oldRecord, RECORD newRecord, REPORT report, DiffRecordAccess records )
{
checker.checkChange( oldRecord, newRecord, report, records );
}
}
private static final ComparativeRecordChecker<PropertyRecord, PrimitiveRecord, ConsistencyReport.PropertyConsistencyReport> ORPHAN_CHECKER =
new ComparativeRecordChecker<PropertyRecord, PrimitiveRecord, ConsistencyReport.PropertyConsistencyReport>()
{
@Override
public void checkReference( PropertyRecord record, PrimitiveRecord primitiveRecord,
ConsistencyReport.PropertyConsistencyReport report, RecordAccess records )
{
report.orphanPropertyChain();
}
};
}