/** * 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.backup.log; import java.io.File; import java.util.Map; import org.neo4j.consistency.checking.old.ConsistencyRecordProcessor; import org.neo4j.consistency.checking.old.ConsistencyReporter; import org.neo4j.consistency.checking.old.InconsistencyType; import org.neo4j.consistency.checking.old.MonitoringConsistencyReporter; import org.neo4j.consistency.store.DiffRecordStore; import org.neo4j.consistency.store.DiffStore; import org.neo4j.helpers.collection.Visitor; import org.neo4j.kernel.impl.nioneo.store.AbstractBaseRecord; import org.neo4j.kernel.impl.nioneo.store.DataInconsistencyError; 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.RecordStore; import org.neo4j.kernel.impl.nioneo.store.RelationshipRecord; import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeRecord; import org.neo4j.kernel.impl.nioneo.xa.NeoStoreXaDataSource; import org.neo4j.kernel.impl.transaction.xaframework.LogEntry; import org.neo4j.kernel.impl.transaction.xaframework.TransactionInterceptor; import org.neo4j.kernel.impl.util.StringLogger; import org.neo4j.kernel.impl.util.StringLogger.LineLogger; @Deprecated class VerifyingTransactionInterceptor implements TransactionInterceptor { enum CheckerMode { FULL( true ) { @Override <R extends RecordStore.Processor & Runnable> void apply( DiffStore diffs, R checker ) { try { checker.run(); } catch ( AssertionError e ) { System.err.println( e.getMessage() ); } } }, DIFF( false ) { @Override <R extends RecordStore.Processor & Runnable> void apply( DiffStore diffs, R checker ) { diffs.applyToAll( checker ); } }; final boolean checkProp; private CheckerMode( boolean checkProp ) { this.checkProp = checkProp; } abstract <R extends RecordStore.Processor & Runnable> void apply( DiffStore diffs, R checker ); } private final boolean rejectInconsistentTransactions; private final DiffStore diffs; private final StringLogger msgLog; private LogEntry.Commit commitEntry; private LogEntry.Start startEntry; private TransactionInterceptor next; private final CheckerMode mode; private final StringLogger difflog; VerifyingTransactionInterceptor( NeoStoreXaDataSource ds, StringLogger stringLogger, CheckerMode mode, boolean rejectInconsistentTransactions, Map<String, String> extraConfig ) { this.rejectInconsistentTransactions = rejectInconsistentTransactions; this.diffs = new DiffStore( ds.getNeoStore() ); this.msgLog = stringLogger; this.mode = mode; String log = extraConfig.get( "log" ); this.difflog = log == null ? null : ( "true".equalsIgnoreCase( log ) ? msgLog : StringLogger.logger( new File( log ) ) ); } public void setStartEntry( LogEntry.Start startEntry ) { this.startEntry = startEntry; if ( next != null ) { next.setStartEntry( startEntry ); } } public void setCommitEntry( LogEntry.Commit commitEntry ) { this.commitEntry = commitEntry; if (next != null) { next.setCommitEntry( commitEntry ); } } public void setNext( TransactionInterceptor next ) { this.next = next; } public void complete() throws DataInconsistencyError { /* * Here goes the actual verification code. If it passes, * just return - if not, throw Error so that the * store remains safe. */ // diffs, mode.checkProp MonitoringConsistencyReporter reporter = new MonitoringConsistencyReporter( new ConsistencyReporter() { @Override public <R extends AbstractBaseRecord> void report( RecordStore<R> recordStore, R record, InconsistencyType inconsistency ) { if ( inconsistency.isWarning() ) return; StringBuilder log = messageHeader( "Inconsistencies" ).append( "\n\t" ); logRecord( log, recordStore, record ); log.append( inconsistency.message() ); msgLog.logMessage( log.toString() ); if ( difflog != null && difflog != msgLog ) difflog.logMessage( log.toString() ); } @Override public <R1 extends AbstractBaseRecord, R2 extends AbstractBaseRecord> void report( RecordStore<R1> recordStore, R1 record, RecordStore<? extends R2> referredStore, R2 referred, InconsistencyType inconsistency ) { if ( inconsistency.isWarning() ) return; if ( recordStore == referredStore && record.getLongId() == referred.getLongId() ) { // inconsistency between versions, logRecord() handles that, treat as single record report( recordStore, record, inconsistency ); return; } StringBuilder log = messageHeader( "Inconsistencies" ).append( "\n\t" ); logRecord( log, recordStore, record ); logRecord( log, referredStore, referred ); log.append( inconsistency.message() ); msgLog.logMessage( log.toString() ); if ( difflog != null && difflog != msgLog ) difflog.logMessage( log.toString() ); } }); mode.apply( diffs, new ConsistencyRecordProcessor( diffs, reporter ) ); DataInconsistencyError error = null; try { reporter.checkResult(); } catch ( AssertionError e ) { error = new DataInconsistencyError( "Inconsistencies in transaction\n\t" + ( startEntry == null ? "NO START ENTRY" : startEntry.toString() ) + "\n\t" + ( commitEntry == null ? "NO COMMIT ENTRY" : commitEntry.toString() ) + "\n\t" + e.getMessage() ); msgLog.logMessage( error.getMessage() ); if ( difflog != null && difflog != msgLog ) difflog.logMessage( error.getMessage() ); } if ( difflog != null || error != null ) { //new DiffLogger().log( messageHeader( "Changes" ).toString() ); final String header = messageHeader( "Changes" ).toString(); StringLogger target = null; Visitor<StringLogger.LineLogger> visitor = null; if ( error != null ) { target = msgLog; if ( difflog != null && difflog != msgLog ) { visitor = new Visitor<StringLogger.LineLogger>() { @Override public boolean visit( final LineLogger first ) { difflog.logLongMessage( header, new Visitor<StringLogger.LineLogger>() { @Override public boolean visit( final LineLogger other ) { other.logLine( startEntry == null ? "NO START ENTRY" : startEntry.toString() ); other.logLine( commitEntry == null ? "NO COMMIT ENTRY" : commitEntry.toString() ); logDiffLines( new LineLogger() { @Override public void logLine( String line ) { first.logLine( line ); other.logLine( line ); } } ); return false; } } ); return false; } }; } else { visitor = new Visitor<StringLogger.LineLogger>() { @Override public boolean visit( LineLogger lines ) { logDiffLines( lines ); return false; } }; } } else { target = difflog; visitor = new Visitor<StringLogger.LineLogger>() { @Override public boolean visit( LineLogger lines ) { lines.logLine( startEntry == null ? "NO START ENTRY" : startEntry.toString() ); lines.logLine( commitEntry == null ? "NO COMMIT ENTRY" : commitEntry.toString() ); logDiffLines( lines ); return false; } }; } target.logLongMessage( header, visitor ); } if ( difflog != null ) difflog.close(); // re-throw error if we are rejecting inconsistencies if ( error != null && rejectInconsistentTransactions ) throw error; // Chain of Responsibility continues if ( next != null ) next.complete(); } private void logDiffLines( final LineLogger logger ) { diffs.applyToAll( new RecordStore.Processor() { @Override protected <R extends AbstractBaseRecord> void processRecord( Class<R> type, RecordStore<R> store, R record ) { DiffRecordStore<R> diff = (DiffRecordStore<R>) store; if ( diff.isModified( record.getLongId() ) ) { logRecord( logger, store, record ); } } } ); for ( RecordStore<?> store : diffs.allStores() ) { logger.logLine( store + ": highId(before) = " + ( (DiffRecordStore<?>) store ).getRawHighId() + ", highId(after) = " + store.getHighId() ); } } private static <R extends AbstractBaseRecord> void logRecord( final StringBuilder log, RecordStore<? extends R> store, R record ) { logRecord( new LineLogger() { @Override public void logLine( String line ) { log.append( line ).append( "\n\t" ); } }, store, record ); } private static <R extends AbstractBaseRecord> void logRecord( LineLogger log, RecordStore<? extends R> store, R record ) { DiffRecordStore<R> diff = (DiffRecordStore<R>) store; String prefix = ""; if ( diff.isModified( record.getLongId() ) ) { log.logLine( "- " + diff.forceGetRaw( record ) ); prefix = "+ "; record = store.forceGetRecord( record.getLongId() ); } log.logLine( prefix + record ); } private StringBuilder messageHeader( String type ) { StringBuilder log = new StringBuilder( type ).append( " in transaction" ); if ( commitEntry != null ) log.append( " (txId=" ).append( commitEntry.getTxId() ).append( ")" ); else if ( startEntry != null ) log.append( " (log local id = " ).append( startEntry.getIdentifier() ).append( ")" ); return log.append( ':' ); } @Override public void visitNode( NodeRecord record ) { diffs.visitNode( record ); } @Override public void visitRelationship( RelationshipRecord record ) { diffs.visitRelationship( record ); } @Override public void visitProperty( PropertyRecord record ) { diffs.visitProperty( record ); } @Override public void visitRelationshipType( RelationshipTypeRecord record ) { diffs.visitRelationshipType( record ); } @Override public void visitPropertyIndex( PropertyIndexRecord record ) { diffs.visitPropertyIndex( record ); } @Override public void visitNeoStore( NeoStoreRecord record ) { diffs.visitNeoStore( record ); } }