javacallablemethod-interception

how to hijack a Callable and execute hidden methods before call()


I'm adding implementations to some subsystems in a bigger project (HypergraphDB), and I should avoid to modify important code. In this project, there are about 70 Callable objects which define transaction blocks for some database operations. I'm replacing that database and mine (redis) needs all affected keys before defining the transaction block. So I need to access parts within those Callables, and perform something ("watch") just before executing call().

Some of the Callables are non-trivial and for some of them I would need to declare final fields in that Callable, and have them defined "outside" of call, so both call() and getHandles() can access them.

In order to be able to access the getHandles method, I defined an interface that has just the getHandles method. To access getHandles, I can temporarily cast the Callable to that Interface, in the IDE now it "sees" the method, but atm I have no means to test it.

is this going to work?

Code before:

       private HGLiveHandle addLink(final Object payload, 
                             final HGHandle typeHandle, 
                             final HGLink outgoingSet, 
                             final byte flags)
{
    return getTransactionManager().ensureTransaction(new Callable<HGLiveHandle>() 
    { public HGLiveHandle call() {
        HGAtomType type = typeSystem.getType(typeHandle);
        HGPersistentHandle pTypeHandle = getPersistentHandle(typeHandle);
        HGPersistentHandle valueHandle = TypeUtils.storeValue(HyperGraph.this, payload, type);            
......
}

Code after:

         private HGLiveHandle addLink(final Object payload, 
                             final HGHandle typeHandle, 
                             final HGLink outgoingSet, 
                             final byte flags)
{
    return getTransactionManager().ensureTransaction(new Callable<HGLiveHandle>() 
    {

        final HGAtomType type = typeSystem.getType(typeHandle);
        final HGPersistentHandle pTypeHandle = getPersistentHandle(typeHandle);
        final HGPersistentHandle valueHandle = TypeUtils.storeValue(HyperGraph.this, payload, type);

        public HGPersistentHandle[] getHandles() {
               HGPersistentHandle[] result = new HGPersistentHandle[3+outgoingSet.getArity()];
           result[0] = atomHandle.getPersistent();
           result[1] = typeHandle.getPersistent();
           result[2] = valueHandle.getPersistent();
           result[3] = pTypeHandle;
           result[4] = .valueHandle;
           return result;
        }

        public HGLiveHandle call() {.........

Interface Handable:

     public interface Handable {

public HGPersistentHandle[] getHandles();

}

And finally where it is being called:

     public <V> V ensureTransaction(Callable<V> transaction, HGTransactionConfig config)
{
    if (getContext().getCurrent() != null)
        try 
        {
            for (HGPersistentHandle ph: ((Handable) transaction).getHandles());
            .... writeJedis.watch(ph.toByteArray);
            return transaction.call();

Solution

  • If you really want to intercept the Callables, and not modify existing code, then your best bet is to swap in a different implementation when getTransactionManager() is called, since that is where you are passing in your Callables.

    public class MyTransactionManager implements TransactionManager {
    
    private final TransactionManager originalManager = ...;
    
    public <V> V ensureTransaction(Callable<V> transaction, HGTransactionConfig config) {
        // Check for appropriate type
        if( transaction instanceof Handable) {
            for (HGPersistentHandle ph: ((Handable) transaction).getHandles()) {
                writeJedis.watch(ph.toByteArray);
            }
        }
        // Delegate to the original implementation
        return originalManager.ensureTransaction(transaction, config);
    }
    

    }