Transactional objects over RMI
Asked Answered
L

0

0

I'm trying to do something that is quite impossible with Java, but maybe there's a solution.

The code below tries to pass a transactional object via RMI

public class FileRepositoryImpl extends UnicastRemoteObject
    implements FileRepository {

  @Override
  public byte[] get(String appId, String filePath) throws RemoteException, NotBoundException {
    Registry registry = LocateRegistry.getRegistry();
    XodusRepository repository = (XodusRepository) registry.lookup(XodusRepository.class.getName());
    final byte[][] bytes = {null};
    repository.transact(appId, true, new Command<byte[]>() {
      @Override public byte[] execute(jetbrains.exodus.entitystore.StoreTransaction txn) {
         Entity entity = txn.findWithBlob(Constants.ENTITYSTORE_FILE, filePath).getLast();
         if (entity != null) {
          InputStream blobStream = entity.getBlob(filePath);
          bytes[0] = Try.of(() -> ByteStreams.toByteArray(blobStream)).getOrNull();
         }
         return bytes[0];
      }
    });
    return bytes[0];
  }

}

However this code throws:

java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
    java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
    java.lang.ClassNotFoundException: com.mycompany.backend.repository.FileRepositoryImpl$1 (no security manager: RMI class loader disabled)
    at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:391)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
    at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:562)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:796)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:677)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:676)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
    at java.rmi/sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:283)
    at java.rmi/sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:260)
    at java.rmi/sun.rmi.server.UnicastRef.invoke(UnicastRef.java:161)
    at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:209)
    at java.rmi/java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:161)
    at com.sun.proxy.$Proxy57.transact(Unknown Source)
    at com.mycompany.backend.repository.FileRepositoryImpl.get(FileRepositoryImpl.java:48)
    at com.mycompany.backend.hosting.MyCompanyFileRepresentation.write(MyCompanyFileRepresentation.java:91)

On the other side, the other Java process it have:

  @Override public <T> T transact(String appId, boolean isReadOnly, Command<T> command)
      throws NotBoundException, RemoteException {
    AtomicReference<T> result = null;
    manager.transactPersistentEntityStore(xodusRoot, appId, isReadOnly, txn -> {
      result.set(command.execute(txn));
    });
    return Try.of(() -> result.get()).getOrNull();
  }

Is there a way to use transactional objects over RMI?

Lacerate answered 29/9, 2020 at 11:47 Comment(6)
The root exception seems to be a ClassNotFoundException for the FileRepositoryImpl$1 class. We had similar symptoms when the rmiregistry could not access our business classes. We had to make our classes visible to the rmiregistry.exe using the "code base" property (don't recall the details right now).Additament
@RalfKleberhoff did you mean that you were able to do this? It's seem impossible atm.Lacerate
We did it. What worked for us was to System.setProperty("java.rmi.server.codebase", codebaseUrl) in a very early stage of initialization, with a URL where the rmiregistry.exe could find the relevant class definitions. As a special hack, we integrated an HTTP server into our app serving class files based on getResourceAsStream(), so the class version seen by rmiregistry was always up to date. As a security measure, we published only a limited list of classes this way.Additament
'Transactional' has nothing to do with it. The class has to be available on the receiving JVM's classpath, or via the codebase feature if you must.Juratory
@MarquisofLorne as you can see from the code the "bytes" variable resides from the "client-side" of the RMI, while at the same time it is being accessed inside the "Command object" which is passed to the "server-side" of the RMI, thus bytes is on the client side and also being updated on the server thus, how can the "server-side" RMI even assign value to that variable from the other end of the spectrum? Can RMI access objects across difference processes?Lacerate
@Lacerate Only if they are remote objects.Juratory

© 2022 - 2024 — McMap. All rights reserved.