org.neo4j.consistency.report.ConsistencyReporter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-consistency-check Show documentation
Show all versions of neo4j-consistency-check Show documentation
Tool for checking consistency of a Neo4j data store.
/*
* Copyright (c) 2002-2020 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.consistency.report;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import org.neo4j.consistency.RecordType;
import org.neo4j.consistency.checking.CheckerEngine;
import org.neo4j.consistency.checking.ComparativeRecordChecker;
import org.neo4j.consistency.checking.RecordCheck;
import org.neo4j.consistency.report.ConsistencyReport.DynamicLabelConsistencyReport;
import org.neo4j.consistency.report.ConsistencyReport.RelationshipGroupConsistencyReport;
import org.neo4j.consistency.store.DirectRecordReference;
import org.neo4j.consistency.store.RecordAccess;
import org.neo4j.consistency.store.RecordReference;
import org.neo4j.consistency.store.synthetic.CountsEntry;
import org.neo4j.consistency.store.synthetic.IndexEntry;
import org.neo4j.consistency.store.synthetic.LabelScanDocument;
import org.neo4j.kernel.impl.annotations.DocumentedUtils;
import org.neo4j.kernel.impl.store.record.AbstractBaseRecord;
import org.neo4j.kernel.impl.store.record.DynamicRecord;
import org.neo4j.kernel.impl.store.record.LabelTokenRecord;
import org.neo4j.kernel.impl.store.record.NodeRecord;
import org.neo4j.kernel.impl.store.record.PropertyKeyTokenRecord;
import org.neo4j.kernel.impl.store.record.PropertyRecord;
import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord;
import org.neo4j.kernel.impl.store.record.RelationshipRecord;
import org.neo4j.kernel.impl.store.record.RelationshipTypeTokenRecord;
import static java.util.Arrays.asList;
import static org.neo4j.helpers.Exceptions.stringify;
public class ConsistencyReporter implements ConsistencyReport.Reporter
{
private static final ProxyFactory SCHEMA_REPORT =
ProxyFactory.create( ConsistencyReport.SchemaConsistencyReport.class );
private static final ProxyFactory NODE_REPORT =
ProxyFactory.create( ConsistencyReport.NodeConsistencyReport.class );
private static final ProxyFactory RELATIONSHIP_REPORT =
ProxyFactory.create( ConsistencyReport.RelationshipConsistencyReport.class );
private static final ProxyFactory PROPERTY_REPORT =
ProxyFactory.create( ConsistencyReport.PropertyConsistencyReport.class );
private static final ProxyFactory RELATIONSHIP_TYPE_REPORT =
ProxyFactory.create( ConsistencyReport.RelationshipTypeConsistencyReport.class );
private static final ProxyFactory LABEL_KEY_REPORT =
ProxyFactory.create( ConsistencyReport.LabelTokenConsistencyReport.class );
private static final ProxyFactory PROPERTY_KEY_REPORT =
ProxyFactory.create( ConsistencyReport.PropertyKeyTokenConsistencyReport.class );
private static final ProxyFactory DYNAMIC_REPORT =
ProxyFactory.create( ConsistencyReport.DynamicConsistencyReport.class );
private static final ProxyFactory DYNAMIC_LABEL_REPORT =
ProxyFactory.create( ConsistencyReport.DynamicLabelConsistencyReport.class );
private static final ProxyFactory LABEL_SCAN_REPORT =
ProxyFactory.create( ConsistencyReport.LabelScanConsistencyReport.class );
private static final ProxyFactory INDEX =
ProxyFactory.create( ConsistencyReport.IndexConsistencyReport.class );
private static final ProxyFactory RELATIONSHIP_GROUP_REPORT =
ProxyFactory.create( ConsistencyReport.RelationshipGroupConsistencyReport.class );
private static final ProxyFactory COUNTS_REPORT =
ProxyFactory.create( ConsistencyReport.CountsConsistencyReport.class );
private final RecordAccess records;
private final InconsistencyReport report;
private final Monitor monitor;
public interface Monitor
{
void reported( Class> report, String method, String message );
}
public static final Monitor NO_MONITOR = ( report, method, message ) ->
{
};
public ConsistencyReporter( RecordAccess records, InconsistencyReport report )
{
this( records, report, NO_MONITOR );
}
public ConsistencyReporter( RecordAccess records, InconsistencyReport report, Monitor monitor )
{
this.records = records;
this.report = report;
this.monitor = monitor;
}
private
void dispatch( RecordType type, ProxyFactory factory, RECORD record, RecordCheck checker )
{
ReportInvocationHandler handler = new ReportHandler<>( report, factory, type, records, record,
monitor );
try
{
checker.check( record, handler, records );
}
catch ( Exception e )
{
// This is a rare event and exposing the stack trace is a good idea, otherwise we
// can only see that something went wrong, not at all what.
handler.report.error( type, record, "Failed to check record: " + stringify( e ),
new Object[0] );
}
handler.updateSummary();
}
static void dispatchReference( CheckerEngine engine, ComparativeRecordChecker checker,
AbstractBaseRecord referenced, RecordAccess records )
{
ReportInvocationHandler handler = (ReportInvocationHandler) engine;
handler.checkReference( engine, checker, referenced, records );
handler.updateSummary();
}
static String pendingCheckToString( CheckerEngine engine, ComparativeRecordChecker checker )
{
ReportInvocationHandler handler = (ReportInvocationHandler) engine;
return handler.pendingCheckToString(checker);
}
static void dispatchChangeReference( CheckerEngine engine, ComparativeRecordChecker checker,
AbstractBaseRecord oldReferenced, AbstractBaseRecord newReferenced,
RecordAccess records )
{
ReportInvocationHandler handler = (ReportInvocationHandler) engine;
handler.checkDiffReference( engine, checker, oldReferenced, newReferenced, records );
handler.updateSummary();
}
static void dispatchSkip( CheckerEngine engine )
{
((ReportInvocationHandler) engine ).updateSummary();
}
public REPORT report( RECORD record,
Class cls, RecordType recordType )
{
ProxyFactory proxyFactory = ProxyFactory.get( cls );
ReportInvocationHandler handler =
new ReportHandler( report, proxyFactory, recordType, records, record, monitor )
{
@Override
protected void inconsistencyReported()
{
updateSummary();
}
};
return handler.report();
}
public static FormattingDocumentedHandler formattingHandler( InconsistencyReport report, RecordType type )
{
return new FormattingDocumentedHandler( report, type );
}
public static class FormattingDocumentedHandler implements InvocationHandler
{
private final InconsistencyReport report;
private final RecordType type;
private int errors;
private int warnings;
FormattingDocumentedHandler( InconsistencyReport report, RecordType type )
{
this.report = report;
this.type = type;
}
@Override
public Object invoke( Object proxy, Method method, Object[] args )
{
String message = DocumentedUtils.extractFormattedMessage( method, args );
if ( method.getAnnotation( ConsistencyReport.Warning.class ) == null )
{
errors++;
report.error( message );
}
else
{
warnings++;
report.warning( message );
}
return null;
}
public void updateSummary()
{
report.updateSummary( type, errors, warnings );
}
}
public abstract static class ReportInvocationHandler
implements CheckerEngine, InvocationHandler
{
final InconsistencyReport report;
private final ProxyFactory factory;
final RecordType type;
private short errors;
private short warnings;
private short references = 1/*this*/;
private final RecordAccess records;
private final Monitor monitor;
private ReportInvocationHandler( InconsistencyReport report, ProxyFactory factory, RecordType type,
RecordAccess records, Monitor monitor )
{
this.report = report;
this.factory = factory;
this.type = type;
this.records = records;
this.monitor = monitor;
}
synchronized void updateSummary()
{
if ( --references == 0 )
{
report.updateSummary( type, errors, warnings );
}
}
String pendingCheckToString( ComparativeRecordChecker checker )
{
String checkName;
try
{
if ( checker.getClass().getMethod( "toString" ).getDeclaringClass() == Object.class )
{
checkName = checker.getClass().getSimpleName();
if ( checkName.length() == 0 )
{
checkName = checker.getClass().getName();
}
}
else
{
checkName = checker.toString();
}
}
catch ( NoSuchMethodException e )
{
checkName = checker.toString();
}
return String.format( "ReferenceCheck{%s[%s]/%s}", type, recordId(), checkName );
}
abstract long recordId();
@Override
public void comparativeCheck(
RecordReference reference, ComparativeRecordChecker checker )
{
references++;
reference.dispatch( new PendingReferenceCheck<>( this, checker ) );
}
@Override
public REPORT report()
{
return factory.create( this );
}
/**
* Invoked when an inconsistency is encountered.
*
* @param args array of the items referenced from this record with which it is inconsistent.
*/
@Override
public Object invoke( Object proxy, Method method, Object[] args )
{
String message = DocumentedUtils.extractMessage( method );
if ( method.getAnnotation( ConsistencyReport.Warning.class ) == null )
{
errors++;
args = getRealRecords( args );
logError( message, args );
}
else
{
warnings++;
args = getRealRecords( args );
logWarning( message, args );
}
monitor.reported( factory.type(), method.getName(), message );
inconsistencyReported();
return null;
}
protected void inconsistencyReported()
{
}
private Object[] getRealRecords( Object[] args )
{
if ( args == null )
{
return args;
}
for ( int i = 0; i < args.length; i++ )
{
// We use "created" flag here. Consistency checking code revolves around records and so
// even in scenarios where records are built from other sources, f.ex half-and-purpose-built from cache,
// this flag is used to signal that the real record needs to be read in order to be used as a general
// purpose record.
if ( args[i] instanceof AbstractBaseRecord && ((AbstractBaseRecord) args[i]).isCreated() )
{ // get the real record
if ( args[i] instanceof NodeRecord )
{
args[i] = ((DirectRecordReference) records.node(
((NodeRecord) args[i]).getId() )).record();
}
else if ( args[i] instanceof RelationshipRecord )
{
args[i] = ((DirectRecordReference) records.relationship(
((RelationshipRecord) args[i]).getId() )).record();
}
}
}
return args;
}
protected abstract void logError( String message, Object[] args );
protected abstract void logWarning( String message, Object[] args );
abstract void checkReference( CheckerEngine engine, ComparativeRecordChecker checker,
AbstractBaseRecord referenced, RecordAccess records );
abstract void checkDiffReference( CheckerEngine engine, ComparativeRecordChecker checker,
AbstractBaseRecord oldReferenced, AbstractBaseRecord newReferenced,
RecordAccess records );
}
public static class ReportHandler
extends ReportInvocationHandler
{
private final AbstractBaseRecord record;
public ReportHandler( InconsistencyReport report, ProxyFactory factory, RecordType type,
RecordAccess records, AbstractBaseRecord record, Monitor monitor )
{
super( report, factory, type, records, monitor );
this.record = record;
}
@Override
long recordId()
{
return record.getId();
}
@Override
protected void logError( String message, Object[] args )
{
report.error( type, record, message, args );
}
@Override
protected void logWarning( String message, Object[] args )
{
report.warning( type, record, message, args );
}
@Override
@SuppressWarnings( "unchecked" )
void checkReference( CheckerEngine engine, ComparativeRecordChecker checker, AbstractBaseRecord referenced,
RecordAccess records )
{
checker.checkReference( record, referenced, this, records );
}
@Override
@SuppressWarnings( "unchecked" )
void checkDiffReference( CheckerEngine engine, ComparativeRecordChecker checker,
AbstractBaseRecord oldReferenced, AbstractBaseRecord newReferenced,
RecordAccess records )
{
checker.checkReference( record, newReferenced, this, records );
}
}
@Override
public void forSchema( DynamicRecord schema,
RecordCheck checker )
{
dispatch( RecordType.SCHEMA, SCHEMA_REPORT, schema, checker );
}
@Override
public void forNode( NodeRecord node,
RecordCheck checker )
{
dispatch( RecordType.NODE, NODE_REPORT, node, checker );
}
@Override
public void forRelationship( RelationshipRecord relationship,
RecordCheck checker )
{
dispatch( RecordType.RELATIONSHIP, RELATIONSHIP_REPORT, relationship, checker );
}
@Override
public void forProperty( PropertyRecord property,
RecordCheck checker )
{
dispatch( RecordType.PROPERTY, PROPERTY_REPORT, property, checker );
}
@Override
public void forRelationshipTypeName( RelationshipTypeTokenRecord relationshipTypeTokenRecord,
RecordCheck checker )
{
dispatch( RecordType.RELATIONSHIP_TYPE, RELATIONSHIP_TYPE_REPORT, relationshipTypeTokenRecord, checker );
}
@Override
public void forLabelName( LabelTokenRecord label,
RecordCheck checker )
{
dispatch( RecordType.LABEL, LABEL_KEY_REPORT, label, checker );
}
@Override
public void forNodeLabelScan( LabelScanDocument document,
RecordCheck checker )
{
dispatch( RecordType.LABEL_SCAN_DOCUMENT, LABEL_SCAN_REPORT, document, checker );
}
@Override
public void forIndexEntry( IndexEntry entry,
RecordCheck checker )
{
dispatch( RecordType.INDEX, INDEX, entry, checker );
}
@Override
public void forPropertyKey( PropertyKeyTokenRecord key,
RecordCheck checker )
{
dispatch( RecordType.PROPERTY_KEY, PROPERTY_KEY_REPORT, key, checker );
}
@Override
public void forDynamicBlock( RecordType type, DynamicRecord record,
RecordCheck checker )
{
dispatch( type, DYNAMIC_REPORT, record, checker );
}
@Override
public void forDynamicLabelBlock( RecordType type, DynamicRecord record,
RecordCheck checker )
{
dispatch( type, DYNAMIC_LABEL_REPORT, record, checker );
}
@Override
public void forRelationshipGroup( RelationshipGroupRecord record,
RecordCheck checker )
{
dispatch( RecordType.RELATIONSHIP_GROUP, RELATIONSHIP_GROUP_REPORT, record, checker );
}
@Override
public void forCounts( CountsEntry countsEntry,
RecordCheck checker )
{
dispatch( RecordType.COUNTS, COUNTS_REPORT, countsEntry, checker );
}
public static class ProxyFactory
{
private static Map,ProxyFactory>> instances = new HashMap<>();
private Constructor extends T> constructor;
private final Class type;
@SuppressWarnings( "unchecked" )
static ProxyFactory get( Class cls )
{
return (ProxyFactory) instances.get( cls );
}
@SuppressWarnings( "unchecked" )
ProxyFactory( Class type ) throws LinkageError
{
this.type = type;
try
{
this.constructor = (Constructor extends T>) Proxy
.getProxyClass( ConsistencyReporter.class.getClassLoader(), type )
.getConstructor( InvocationHandler.class );
instances.put( type, this );
}
catch ( NoSuchMethodException e )
{
throw new LinkageError( "Cannot access Proxy constructor for " + type.getName(), e );
}
}
@Override
public String toString()
{
return getClass().getSimpleName() + asList( constructor.getDeclaringClass().getInterfaces() );
}
Class> type()
{
return type;
}
public T create( InvocationHandler handler )
{
try
{
return constructor.newInstance( handler );
}
catch ( Exception e )
{
throw new LinkageError( "Failed to create proxy instance", e );
}
}
public static ProxyFactory create( Class type )
{
return new ProxyFactory<>( type );
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy