/**
* 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.report;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.neo4j.consistency.RecordType;
import org.neo4j.consistency.checking.ComparativeRecordChecker;
import org.neo4j.consistency.checking.RecordCheck;
import org.neo4j.consistency.store.DiffRecordAccess;
import org.neo4j.consistency.store.RecordAccess;
import org.neo4j.consistency.store.RecordReference;
import org.neo4j.kernel.impl.annotations.Documented;
import org.neo4j.kernel.impl.nioneo.store.AbstractBaseRecord;
import org.neo4j.kernel.impl.nioneo.store.DynamicRecord;
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.RelationshipRecord;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord;
import static java.lang.reflect.Proxy.getInvocationHandler;
import static org.neo4j.helpers.Exceptions.launderedException;
import static org.neo4j.helpers.Exceptions.withCause;
public class ConsistencyReporter implements ConsistencyReport.Reporter
{
private static final Method FOR_REFERENCE;
static
{
try
{
FOR_REFERENCE = ConsistencyReport.class
.getDeclaredMethod( "forReference", RecordReference.class,
ComparativeRecordChecker.class );
}
catch ( NoSuchMethodException cause )
{
throw withCause( new LinkageError( "Could not find dispatch method of " +
ConsistencyReport.class.getName() ), cause );
}
}
private static final ProxyFactory<ConsistencyReport.NodeConsistencyReport> NODE_REPORT =
ProxyFactory.create( ConsistencyReport.NodeConsistencyReport.class );
private static final ProxyFactory<ConsistencyReport.RelationshipConsistencyReport> RELATIONSHIP_REPORT =
ProxyFactory.create( ConsistencyReport.RelationshipConsistencyReport.class );
private static final ProxyFactory<ConsistencyReport.PropertyConsistencyReport> PROPERTY_REPORT =
ProxyFactory.create( ConsistencyReport.PropertyConsistencyReport.class );
private static final ProxyFactory<ConsistencyReport.LabelConsistencyReport> LABEL_REPORT =
ProxyFactory.create( ConsistencyReport.LabelConsistencyReport.class );
private static final ProxyFactory<ConsistencyReport.PropertyKeyConsistencyReport> PROPERTY_KEY_REPORT =
ProxyFactory.create( ConsistencyReport.PropertyKeyConsistencyReport.class );
private static final ProxyFactory<ConsistencyReport.DynamicConsistencyReport> DYNAMIC_REPORT =
ProxyFactory.create( ConsistencyReport.DynamicConsistencyReport.class );
private final ConsistencyLogger logger;
private final DiffRecordAccess records;
private final ConsistencySummaryStatistics summary;
public ConsistencyReporter( ConsistencyLogger logger, DiffRecordAccess records,
ConsistencySummaryStatistics summary )
{
this.logger = logger;
this.records = records;
this.summary = summary;
}
private <RECORD extends AbstractBaseRecord, REPORT extends ConsistencyReport<RECORD, REPORT>>
void dispatch( RecordType type, ProxyFactory<REPORT> factory, RECORD record, RecordCheck<RECORD, REPORT> checker )
{
ReportHandler handler = new ReportHandler( logger, summary, type, record );
checker.check( record, factory.create( handler ), records );
handler.updateSummary();
}
private <RECORD extends AbstractBaseRecord, REPORT extends ConsistencyReport<RECORD, REPORT>>
void dispatchChange( RecordType type, ProxyFactory<REPORT> factory, RECORD oldRecord, RECORD newRecord,
RecordCheck<RECORD, REPORT> checker )
{
DiffReportHandler handler = new DiffReportHandler( logger, summary, type, oldRecord, newRecord );
checker.checkChange( oldRecord, newRecord, factory.create( handler ), records );
handler.updateSummary();
}
static void dispatchReference( ConsistencyReport report, ComparativeRecordChecker checker,
AbstractBaseRecord referenced, RecordAccess records )
{
ReportInvocationHandler handler = (ReportInvocationHandler) getInvocationHandler( report );
handler.checkReference( report, checker, referenced, records );
handler.updateSummary();
}
static String pendingCheckToString(ConsistencyReport report,ComparativeRecordChecker checker)
{
ReportInvocationHandler handler = (ReportInvocationHandler) getInvocationHandler( report );
return handler.pendingCheckToString(checker);
}
static void dispatchChangeReference( ConsistencyReport report, ComparativeRecordChecker checker,
AbstractBaseRecord oldReferenced, AbstractBaseRecord newReferenced,
RecordAccess records )
{
ReportInvocationHandler handler = (ReportInvocationHandler) getInvocationHandler( report );
handler.checkDiffReference( report, checker, oldReferenced, newReferenced, records );
handler.updateSummary();
}
static void dispatchSkip( ConsistencyReport report )
{
((ReportInvocationHandler) getInvocationHandler( report )).updateSummary();
}
private static abstract class ReportInvocationHandler implements InvocationHandler
{
final ReportSink sink;
final RecordType type;
private short errors = 0, warnings = 0, references = 1/*this*/;
private ReportInvocationHandler( ConsistencyLogger logger, ConsistencySummaryStatistics stats, RecordType type )
{
this.sink = new ReportSink( logger, stats );
this.type = type;
}
synchronized void updateSummary()
{
if ( --references == 0 )
{
sink.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();
/**
* 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 ) throws Throwable
{
if ( method.equals( FOR_REFERENCE ) )
{
RecordReference reference = (RecordReference) args[0];
ComparativeRecordChecker checker = (ComparativeRecordChecker) args[1];
dispatchForReference( (ConsistencyReport) proxy, reference, checker );
}
else
{
String message;
Documented annotation = method.getAnnotation( Documented.class );
if ( annotation != null && !"".equals( annotation.value() ) )
{
message = annotation.value();
}
else
{
message = method.getName();
}
if ( method.getAnnotation( ConsistencyReport.Warning.class ) == null )
{
errors++;
logError( message, args );
}
else
{
warnings++;
logWarning( message, args );
}
}
return null;
}
protected abstract void logError( String message, Object[] args );
protected abstract void logWarning( String message, Object[] args );
@SuppressWarnings("unchecked")
private void dispatchForReference( ConsistencyReport report, RecordReference reference,
ComparativeRecordChecker checker )
{
forReference( report, reference, checker );
}
final <REFERENCED extends AbstractBaseRecord>
void forReference( ConsistencyReport report, RecordReference<REFERENCED> reference,
ComparativeRecordChecker<?, REFERENCED, ?> checker )
{
references++;
reference.dispatch( new PendingReferenceCheck<REFERENCED>( report, checker ) );
}
abstract void checkReference( ConsistencyReport report, ComparativeRecordChecker checker,
AbstractBaseRecord referenced, RecordAccess records );
abstract void checkDiffReference( ConsistencyReport report, ComparativeRecordChecker checker,
AbstractBaseRecord oldReferenced, AbstractBaseRecord newReferenced,
RecordAccess records );
}
static class ReportHandler extends ReportInvocationHandler
{
private final AbstractBaseRecord record;
ReportHandler( ConsistencyLogger logger, ConsistencySummaryStatistics summary, RecordType type,
AbstractBaseRecord record )
{
super( logger, summary, type );
this.record = record;
}
@Override
long recordId()
{
return record.getLongId();
}
@Override
protected void logError( String message, Object[] args )
{
sink.error( type, record, message, args );
}
@Override
protected void logWarning( String message, Object[] args )
{
sink.warning( type, record, message, args );
}
@Override
@SuppressWarnings("unchecked")
void checkReference( ConsistencyReport report, ComparativeRecordChecker checker, AbstractBaseRecord referenced,
RecordAccess records )
{
checker.checkReference( record, referenced, report, records );
}
@Override
@SuppressWarnings("unchecked")
void checkDiffReference( ConsistencyReport report, ComparativeRecordChecker checker,
AbstractBaseRecord oldReferenced, AbstractBaseRecord newReferenced,
RecordAccess records )
{
checker.checkReference( record, newReferenced, report, records );
}
}
private static class DiffReportHandler extends ReportInvocationHandler
{
private final AbstractBaseRecord oldRecord;
private final AbstractBaseRecord newRecord;
private DiffReportHandler( ConsistencyLogger logger, ConsistencySummaryStatistics summary, RecordType type,
AbstractBaseRecord oldRecord, AbstractBaseRecord newRecord )
{
super( logger, summary, type );
this.oldRecord = oldRecord;
this.newRecord = newRecord;
}
@Override
long recordId()
{
return newRecord.getLongId();
}
@Override
protected void logError( String message, Object[] args )
{
sink.error( type, oldRecord, newRecord, message, args );
}
@Override
protected void logWarning( String message, Object[] args )
{
sink.warning( type, oldRecord, newRecord, message, args );
}
@Override
@SuppressWarnings("unchecked")
void checkReference( ConsistencyReport report, ComparativeRecordChecker checker, AbstractBaseRecord referenced,
RecordAccess records )
{
checker.checkReference( newRecord, referenced, report, records );
}
@Override
@SuppressWarnings("unchecked")
void checkDiffReference( ConsistencyReport report, ComparativeRecordChecker checker,
AbstractBaseRecord oldReferenced, AbstractBaseRecord newReferenced,
RecordAccess records )
{
checker.checkReference( newRecord, newReferenced, report, records );
}
}
@Override
public void forNode( NodeRecord node,
RecordCheck<NodeRecord, ConsistencyReport.NodeConsistencyReport> checker )
{
dispatch( RecordType.NODE, NODE_REPORT, node, checker );
}
@Override
public void forNodeChange( NodeRecord oldNode, NodeRecord newNode,
RecordCheck<NodeRecord, ConsistencyReport.NodeConsistencyReport> checker )
{
dispatchChange( RecordType.NODE, NODE_REPORT, oldNode, newNode, checker );
}
@Override
public void forRelationship( RelationshipRecord relationship,
RecordCheck<RelationshipRecord, ConsistencyReport.RelationshipConsistencyReport> checker )
{
dispatch( RecordType.RELATIONSHIP, RELATIONSHIP_REPORT, relationship, checker );
}
@Override
public void forRelationshipChange( RelationshipRecord oldRelationship, RelationshipRecord newRelationship,
RecordCheck<RelationshipRecord, ConsistencyReport.RelationshipConsistencyReport> checker )
{
dispatchChange( RecordType.RELATIONSHIP, RELATIONSHIP_REPORT, oldRelationship, newRelationship, checker );
}
@Override
public void forProperty( PropertyRecord property,
RecordCheck<PropertyRecord, ConsistencyReport.PropertyConsistencyReport> checker )
{
dispatch( RecordType.PROPERTY, PROPERTY_REPORT, property, checker );
}
@Override
public void forPropertyChange( PropertyRecord oldProperty, PropertyRecord newProperty,
RecordCheck<PropertyRecord, ConsistencyReport.PropertyConsistencyReport> checker )
{
dispatchChange( RecordType.PROPERTY, PROPERTY_REPORT, oldProperty, newProperty, checker );
}
@Override
public void forRelationshipLabel( RelationshipTypeRecord label,
RecordCheck<RelationshipTypeRecord, ConsistencyReport.LabelConsistencyReport> checker )
{
dispatch( RecordType.RELATIONSHIP_LABEL, LABEL_REPORT, label, checker );
}
@Override
public void forRelationshipLabelChange( RelationshipTypeRecord oldLabel, RelationshipTypeRecord newLabel,
RecordCheck<RelationshipTypeRecord, ConsistencyReport.LabelConsistencyReport> checker )
{
dispatchChange( RecordType.RELATIONSHIP_LABEL, LABEL_REPORT, oldLabel, newLabel, checker );
}
@Override
public void forPropertyKey( PropertyIndexRecord key,
RecordCheck<PropertyIndexRecord, ConsistencyReport.PropertyKeyConsistencyReport> checker )
{
dispatch( RecordType.PROPERTY_KEY, PROPERTY_KEY_REPORT, key, checker );
}
@Override
public void forPropertyKeyChange( PropertyIndexRecord oldKey, PropertyIndexRecord newKey,
RecordCheck<PropertyIndexRecord, ConsistencyReport.PropertyKeyConsistencyReport> checker )
{
dispatchChange( RecordType.PROPERTY_KEY, PROPERTY_KEY_REPORT, oldKey, newKey, checker );
}
@Override
public void forDynamicBlock( RecordType type, DynamicRecord record,
RecordCheck<DynamicRecord, ConsistencyReport.DynamicConsistencyReport> checker )
{
dispatch( type, DYNAMIC_REPORT, record, checker );
}
@Override
public void forDynamicBlockChange( RecordType type, DynamicRecord oldRecord, DynamicRecord newRecord,
RecordCheck<DynamicRecord, ConsistencyReport.DynamicConsistencyReport> checker )
{
dispatchChange( type, DYNAMIC_REPORT, oldRecord, newRecord, checker );
}
private static class ProxyFactory<T>
{
private Constructor<? extends T> constructor;
@SuppressWarnings("unchecked")
ProxyFactory( Class<T> type ) throws LinkageError
{
try
{
this.constructor = (Constructor<? extends T>) Proxy
.getProxyClass( ConsistencyReporter.class.getClassLoader(), type )
.getConstructor( InvocationHandler.class );
}
catch ( NoSuchMethodException e )
{
throw withCause( new LinkageError( "Cannot access Proxy constructor for " + type.getName() ), e );
}
}
public T create( InvocationHandler handler )
{
try
{
return constructor.newInstance( handler );
}
catch ( InvocationTargetException e )
{
throw launderedException( e );
}
catch ( Exception e )
{
throw new LinkageError( "Failed to create proxy instance" );
}
}
public static <T> ProxyFactory<T> create( Class<T> type )
{
return new ProxyFactory<T>( type );
}
}
}