org.neo4j.kernel.ha.DelegateInvocationHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-ha Show documentation
Show all versions of neo4j-ha Show documentation
This component provides the means to set up a cluster of Neo4j instances that act together
as a cluster, providing Master-Slave replication and other features.
/*
* Copyright (c) 2002-2016 "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 .
*/
package org.neo4j.kernel.ha;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.neo4j.graphdb.TransactionFailureException;
import org.neo4j.graphdb.TransientDatabaseFailureException;
import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.kernel.impl.util.LazySingleReference;
/**
* InvocationHandler for dynamic proxies that delegate calls to a given backing implementation. This is mostly
* used to present a single object to others, while being able to switch implementation at runtime.
*
* {@link #cement()}: acquire a proxy that will have its delegate assigned the next call to
* {@link #setDelegate(Object)}. This is useful if one {@link DelegateInvocationHandler} depends on
* another which will have its delegate set later than this one.
*/
public class DelegateInvocationHandler implements InvocationHandler
{
private volatile T delegate;
// A concrete version of delegate, where a user can request to cement this delegate so that it gets concrete
// the next call to setDelegate and will never change since.
private final LazySingleReference concrete;
public DelegateInvocationHandler( final Class interfaceClass )
{
concrete = new LazySingleReference()
{
@SuppressWarnings( "unchecked" )
@Override
protected T create()
{
return (T) Proxy.newProxyInstance( DelegateInvocationHandler.class.getClassLoader(),
new Class[] {interfaceClass}, new Concrete<>() );
}
};
}
/**
* Updates the delegate for this handler, also {@link #harden() hardens} instances
* {@link #cement() cemented} from the last call to {@link #setDelegate(Object)}.
* This call will also dereference the {@link DelegateInvocationHandler.Concrete},
* such that future calls to {@link #harden()} cannot affect any reference received
* from {@link #cement()} prior to this call.
* @param delegate the new delegate to set.
*/
public void setDelegate( T delegate )
{
this.delegate = delegate;
harden();
concrete.invalidate();
}
/**
* Updates {@link #cement() cemented} delegates with the current delegate, making it concrete.
* Callers of {@link #cement()} in between this call and the previous call to {@link #setDelegate(Object)}
* will see the current delegate.
*/
@SuppressWarnings( "unchecked" )
void harden()
{
((Concrete) Proxy.getInvocationHandler( concrete.get() )).set( delegate );
}
@Override
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable
{
if ( delegate == null )
{
throw new StateChangedTransactionFailureException(
"This transaction made assumptions about the instance it is executing " +
"on that no longer hold true. This normally happens when a transaction " +
"expects the instance it is executing on to be in some specific cluster role" +
"(such as 'master' or 'slave') and the instance " +
"changing state while the transaction is executing. Simply retry your " +
"transaction and you should see a successful outcome." );
}
return proxyInvoke( delegate, method, args );
}
private static Object proxyInvoke( Object delegate, Method method, Object[] args )
throws Throwable
{
try
{
return method.invoke( delegate, args );
}
catch ( InvocationTargetException e )
{
throw e.getCause();
}
}
/**
* Cements this delegate, i.e. returns an instance which will have its delegate assigned and hardened
* later on so that it never will change after that point.
*/
public T cement()
{
return concrete.get();
}
@Override
public String toString()
{
return "Delegate[" + delegate + "]";
}
private static class Concrete implements InvocationHandler
{
private volatile T delegate;
void set( T delegate )
{
this.delegate = delegate;
}
@Override
public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable
{
if ( delegate == null )
{
throw new TransientDatabaseFailureException(
"Instance state is not valid. There is no master currently available. Possible causes " +
"include unavailability of a majority of the cluster members or network failure " +
"that caused this instance to be partitioned away from the cluster" );
}
return proxyInvoke( delegate, method, args );
}
@Override
public String toString()
{
return "Concrete[" + delegate + "]";
}
}
/**
* Because we don't want the public API to implement `HasStatus`, and because
* we don't want to change the API from throwing `TransactionFailureException` for
* backwards compat reasons, we throw this sub-class that adds a status code.
*/
static class StateChangedTransactionFailureException extends TransactionFailureException implements Status.HasStatus
{
public StateChangedTransactionFailureException( String msg )
{
super( msg );
}
@Override
public Status status()
{
return Status.Transaction.InstanceStateChanged;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy