All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.consistency.report.ConsistencyReporter Maven / Gradle / Ivy

There is a newer version: 5.26.0
Show newest version
/*
 * 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 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) 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