Reputation: 5808
I get an exception in the load call when I try to do the following:
Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension");
datasourceExtensionField.setAccessible(true);
RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceExtensionField.get(grid);
Field activeItemHandlerField = RpcDataProviderExtension.class.getDeclaredField("activeItemHandler");
activeItemHandlerField.setAccessible(true);
Object activeItemHandler = activeItemHandlerField.get(rpcDataProviderExtension);
Field keyMapperField = activeItemHandler.getClass().getDeclaredField("keyMapper");
keyMapperField.setAccessible(true);
KeyMapper original = (KeyMapper) keyMapperField.get(activeItemHandler);
KeyMapper wrapper = new ByteBuddy() //
.subclass(KeyMapper.class) //
.defineField("original", KeyMapper.class, Visibility.PUBLIC) //
.method(ElementMatchers.any()) //
.intercept(Forwarding.toField("original")) //
.method(ElementMatchers.named("get")) //
.intercept(MethodDelegation.to(new KeyMapperWrapper(grid, original))) //
.make() //
.load(KeyMapperWrapper.class.getClassLoader()) //
.getLoaded() //
.newInstance();
// give wrapper the reference to the original
wrapper.getClass().getDeclaredField("original").set(wrapper, original);
// replace original with wrapper
keyMapperField.set(activeItemHandler, wrapper);
The exception:
java.lang.VerifyError: Bad access to protected data in invokevirtual
Exception Details:
Location:
com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa.clone()Ljava/lang/Object; @4: invokevirtual
Reason:
Type 'com/vaadin/server/KeyMapper' (current frame, stack[0]) is not assignable to 'com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa'
Current Frame:
bci: @4
flags: { }
locals: { 'com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa' }
stack: { 'com/vaadin/server/KeyMapper' }
Bytecode:
0x0000000: 2ab4 000c b600 1cb0
at java.lang.Class.getDeclaredFields0(Native Method)
at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
at java.lang.Class.getDeclaredField(Class.java:2068)
at net.bytebuddy.implementation.LoadedTypeInitializer$ForStaticField.onLoad(LoadedTypeInitializer.java:101)
at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:180)
at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:75)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4525)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4514)
at test.KeyMapperWrapper.patch(KeyMapperWrapper.java:62)
I'm apparently not understanding how Forwarding
is supposed to work, what am I doing wrong?
My intention is to replace the existing KeyMapper
with a proxy where I override one single method, and delegate the rest to the original.
Edit: I now also tried with MethodDelegation
, which throws the same exception:
.method(ElementMatchers.any()) //
.intercept(MethodDelegation.to(original)) //
.method(ElementMatchers.named("get")) //
.intercept(MethodDelegation.to(new KeyMapperWrapper(grid, original))) //
Upvotes: 2
Views: 1209
Reputation: 5808
Not really an answer to the question I asked, but rather two more alternative solutions to my problem:
I convinced myself that I could replace the desired object reference at a time point where it has not been in use yet. So I decided to not delegate/proxy anything, and throw the original object instance away, completely replacing it with the byte buddy dynamic type:
public class GridKeyMapperPatch
{
@SuppressWarnings("unused")
private static final Logger LOGGER = Logger.getLogger(GridKeyMapperPatch.class.getName());
private Grid m_grid = null;
public GridKeyMapperPatch(Grid grid)
{
m_grid = grid;
}
/**
* call immediately after setting container data source
*/
public static void patch(Grid grid)
{
try
{
GridKeyMapperPatch gridKeyMapperPatch = new GridKeyMapperPatch(grid);
KeyMapper patchedKeyMapper = new ByteBuddy() //
.subclass(KeyMapper.class) //
.method(ElementMatchers.named("get")) //
.intercept(MethodDelegation.to(gridKeyMapperPatch)) //
.make() //
.load(GridKeyMapperPatch.class.getClassLoader()) //
.getLoaded() //
.newInstance();
Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension");
datasourceExtensionField.setAccessible(true);
RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceExtensionField.get(grid);
Field activeItemHandlerField = RpcDataProviderExtension.class.getDeclaredField("activeItemHandler");
activeItemHandlerField.setAccessible(true);
Object activeItemHandler = activeItemHandlerField.get(rpcDataProviderExtension);
Field keyMapperField = activeItemHandler.getClass().getDeclaredField("keyMapper");
keyMapperField.setAccessible(true);
keyMapperField.set(activeItemHandler, patchedKeyMapper);
}
catch(Throwable t)
{
throw new RuntimeException(t);
}
}
public Object get(@SuperCall Callable superCall, String key) throws Exception
{
Object staleItemId = superCall.call();
Optional freshItemId = m_grid.getContainerDataSource().getItemIds().stream().filter(i -> i.equals(staleItemId)).findAny();
LOGGER.log(Level.INFO, "intercept: stale=" + staleItemId + ", fresh=" + freshItemId);
return freshItemId.isPresent() ? freshItemId.get() : null;
}
}
Taking a step back, I then found that, since the class and method in question were public, I could just normally subclass it:
public class GridTools
{
@SuppressWarnings("unused")
private static final Logger LOGGER = Logger.getLogger(GridTools.class.getName());
/**
* call immediately after setting container data source
*/
public static void replaceKeyMapper(Grid grid) throws Exception
{
Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension");
datasourceExtensionField.setAccessible(true);
RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceExtensionField.get(grid);
Field activeItemHandlerField = RpcDataProviderExtension.class.getDeclaredField("activeItemHandler");
activeItemHandlerField.setAccessible(true);
Object activeItemHandler = activeItemHandlerField.get(rpcDataProviderExtension);
Field keyMapperField = activeItemHandler.getClass().getDeclaredField("keyMapper");
keyMapperField.setAccessible(true);
keyMapperField.set(activeItemHandler, new NonCachingKeyMapper(grid));
}
private static class NonCachingKeyMapper extends KeyMapper
{
private Grid m_grid = null;
public NonCachingKeyMapper(Grid grid)
{
m_grid = grid;
}
@Override
public Object get(String key)
{
Object staleItemId = super.get(key);
Optional freshItemId = m_grid.getContainerDataSource().getItemIds().stream().filter(i -> i.equals(staleItemId)).findAny();
LOGGER.log(Level.INFO, "intercept: stale=" + staleItemId + ", fresh=" + freshItemId);
return freshItemId.isPresent() ? freshItemId.get() : null;
}
}
}
Upvotes: 0
Reputation: 44032
This is a bug in Byte Buddy. The type you are however trying to create is not legal, Byte Buddy fails to provide you a proper error message. When subclassing, it is legitimate to override protected
methods. It is however not always legal to invoke these methods on another type due to visibility constraints. When you are forwarding a call, you can therefore only override public
methods. What you want to do is to match:
.method(ElementMatchers.isPublic())
where the error does not longer occur. I already added a fix to provide you this error message in the next release version (1.5.8) of Byte Buddy.
Upvotes: 1
Reputation: 5808
Found a solution with InvocationHandlerAdapter. Still don't understand why my original attempts didn't work, though.
public class KeyMapperProxyHandler implements InvocationHandler
{
@SuppressWarnings("unused")
private static final Logger LOGGER = Logger.getLogger(KeyMapperProxyHandler.class.getName());
private Grid m_grid = null;
private KeyMapper m_originalKeyMapper = null;
public KeyMapperProxyHandler(Grid grid, KeyMapper originalKeyMapper)
{
m_grid = grid;
m_originalKeyMapper = originalKeyMapper;
}
/**
* call after container data source has been set
*/
public static void patch(Grid grid)
{
try
{
Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension");
datasourceExtensionField.setAccessible(true);
RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceExtensionField.get(grid);
Field activeItemHandlerField = RpcDataProviderExtension.class.getDeclaredField("activeItemHandler");
activeItemHandlerField.setAccessible(true);
Object activeItemHandler = activeItemHandlerField.get(rpcDataProviderExtension);
Field keyMapperField = activeItemHandler.getClass().getDeclaredField("keyMapper");
keyMapperField.setAccessible(true);
KeyMapper original = (KeyMapper) keyMapperField.get(activeItemHandler);
KeyMapperProxyHandler proxyHandler = new KeyMapperProxyHandler(grid, original);
KeyMapper proxy = new ByteBuddy() //
.subclass(KeyMapper.class) //
.method(ElementMatchers.any()) //
.intercept(InvocationHandlerAdapter.of(proxyHandler)) //
.method(ElementMatchers.named("get")) //
.intercept(MethodDelegation.to(proxyHandler)) //
.make() //
.load(KeyMapperProxyHandler.class.getClassLoader()) //
.getLoaded() //
.newInstance();
keyMapperField.set(activeItemHandler, proxy);
}
catch(Throwable t)
{
throw new RuntimeException(t);
}
}
/**
* override for get method
*/
public Object get(String key)
{
Object staleItemId = m_originalKeyMapper.get(key);
Optional freshItemId = m_grid.getContainerDataSource().getItemIds().stream().filter(i -> i.equals(staleItemId)).findAny();
// LOGGER.log(Level.INFO, "intercept: stale=" + staleItemId + ", fresh=" + freshItemId);
return freshItemId.isPresent() ? freshItemId.get() : null;
}
/**
* proxy all other methods
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
return method.invoke(m_originalKeyMapper, args);
}
}
Upvotes: 0