/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.neo4j.driver.internal; import org.hamcrest.Matcher; import java.io.PrintStream; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import org.neo4j.driver.internal.util.MatcherFactory; import org.neo4j.driver.v1.EventLogger; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.any; import static org.hamcrest.Matchers.not; import static org.neo4j.driver.internal.util.MatcherFactory.containsAtLeast; import static org.neo4j.driver.internal.util.MatcherFactory.count; public final class EventHandler { private final List<Event> events = new ArrayList<>(); private final ConcurrentMap<Class<?>,List<?>> handlers = new ConcurrentHashMap<>(); public void add( Event<?> event ) { synchronized ( events ) { events.add( event ); } List<?> handlers = this.handlers.get( event.handlerType ); if ( handlers != null ) { for ( Object handler : handlers ) { try { dispatch( event, handler ); } catch ( Exception e ) { System.err.println( "Failed to dispatch event: " + event + " to handler: " + handler ); e.printStackTrace( System.err ); } } } } @SuppressWarnings( "unchecked" ) public <Handler> void registerHandler( Class<Handler> type, Handler handler ) { List handlers = this.handlers.get( type ); if ( handlers == null ) { List<?> candidate = new CopyOnWriteArrayList<>(); handlers = this.handlers.putIfAbsent( type, candidate ); if ( handlers == null ) { handlers = candidate; } } handlers.add( handler ); } @SafeVarargs public final void assertContains( MatcherFactory<? extends Event>... matchers ) { synchronized ( events ) { assertThat( events, containsAtLeast( (MatcherFactory[]) matchers ) ); } } @SafeVarargs public final void assertContains( Matcher<? extends Event>... matchers ) { synchronized ( events ) { assertThat( events, containsAtLeast( (Matcher[]) matchers ) ); } } public final void assertCount( Matcher<? extends Event> matcher, Matcher<Integer> count ) { synchronized ( events ) { assertThat( "Unexpected count " + count, events, (Matcher) count( matcher, count ) ); } } public void assertNone( Matcher<? extends Event> matcher ) { synchronized ( events ) { assertThat( events, not( containsAtLeast( (Matcher) matcher ) ) ); } } public void printEvents( PrintStream out ) { printEvents( any( Event.class ), out ); } public void printEvents( Matcher<? extends Event> matcher, PrintStream out ) { try ( PrintWriter writer = new PrintWriter( out ) ) { printEvents( matcher, writer ); } } public void printEvents( PrintWriter out ) { printEvents( any( Event.class ), out ); } public void printEvents( Matcher<? extends Event> matcher, PrintWriter out ) { synchronized ( events ) { for ( Event event : events ) { if ( matcher.matches( event ) ) { write( event, out ); out.println(); } } } } public void forEach( Object handler ) { synchronized ( events ) { for ( Event event : events ) { if ( event.handlerType.isInstance( handler ) ) { dispatch( event, handler ); } } } } private static <Handler> void dispatch( Event<Handler> event, Object handler ) { event.dispatch( event.handlerType.cast( handler ) ); } static <Handler> void write( Event<Handler> event, PrintWriter out ) { dispatch( event, proxy( event.handlerType, new WriteHandler( out ) ) ); } private static <Handler> Handler proxy( Class<Handler> handlerType, InvocationHandler handler ) { if ( handlerType.isInstance( handler ) ) { return handlerType.cast( handler ); } try { return handlerType.cast( proxies.get( handlerType ).newInstance( handler ) ); } catch ( RuntimeException e ) { throw e; } catch ( Exception e ) { throw new RuntimeException( e ); } } private static final ClassValue<Constructor> proxies = new ClassValue<Constructor>() { @Override protected Constructor computeValue( Class<?> type ) { Class<?> proxy = Proxy.getProxyClass( type.getClassLoader(), type ); try { return proxy.getConstructor( InvocationHandler.class ); } catch ( NoSuchMethodException e ) { throw new RuntimeException( e ); } } }; private static class WriteHandler implements InvocationHandler, EventLogger.Sink { private final PrintWriter out; WriteHandler( PrintWriter out ) { this.out = out; } @Override public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { out.append( method.getName() ).append( '(' ); String sep = " "; for ( Object arg : args ) { out.append( sep ); if ( arg == null || !arg.getClass().isArray() ) { out.append( Objects.toString( arg ) ); } else if ( arg instanceof Object[] ) { out.append( Arrays.toString( (Object[]) arg ) ); } else { out.append( Arrays.class.getMethod( "toString", arg.getClass() ).invoke( null, arg ).toString() ); } sep = ", "; } if ( args.length > 0 ) { out.append( ' ' ); } out.append( ')' ); return null; } @Override public void log( String name, EventLogger.Level level, Throwable cause, String message, Object... params ) { out.append( level.name() ).append( ": " ); if ( params != null ) { try { out.format( message, params ); } catch ( Exception e ) { out.format( "InvalidFormat(message=\"%s\", params=%s, failure=%s)", message, Arrays.toString( params ), e ); } out.println(); } else { out.println( message ); } if ( cause != null ) { cause.printStackTrace( out ); } } } }