Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to inject POJO with generated classes #14

Open
jellelicht opened this issue Dec 3, 2021 · 6 comments
Open

How to inject POJO with generated classes #14

jellelicht opened this issue Dec 3, 2021 · 6 comments

Comments

@jellelicht
Copy link
Contributor

jellelicht commented Dec 3, 2021

I’m trying to define agents in an interactive environment;
Using the recently released jadex.bdiv3.runtime.BDIAgent I can easily define my agents without a separate bytecode enhancement step, but I still need to have a ComponentFactory for the generated class;

Problem is that component factories seem to assume/require an on-disk .class file, which naturally does not exist for classes that only exist in-memory of the JVM process.

Provided I stick to POJO’s that I manually construct, Is there a trick or short-circuit I can use to not need a ComponentFactory? Alternatively, is there a way for ComponentFactory to support classes that only exist in-process? Obviously this is about explorative programming, so there is no need to support with distributed/networked/multi-process JVM situations.

For some context, the problematic stack trace I get on trying to load my in-process defined agent POJO, a subclass of BDIAgent:

2. Unhandled jadex.bridge.service.search.ServiceNotFoundException                                                                      
                                                                                                                                       
1. Caused by jadex.bridge.service.search.ServiceNotFoundException                                                                      
   filter=FactoryFilter(xyz.jlicht.integration_test.proxy$jadex.bdiv3.runtime.BDIAgent$Foobar$144232d7.class)                          
                                                                                                                                       
    SComponentFactory.java:  810  jadex.bridge.service.types.factory.SComponentFactory/doFindFactory                                   
    SComponentFactory.java:  801  jadex.bridge.service.types.factory.SComponentFactory$11/customResultAvailable                        
ExceptionDelegationResultListener.java:   55  jadex.commons.future.ExceptionDelegationResultListener/resultAvailable                   
ExceptionDelegationResultListener.java:  122  jadex.commons.future.ExceptionDelegationResultListener/resultAvailableIfUndone

NB, everything works as expected if I have the same code, but pre-compile it to class files. I just would like the option to make changes in a running process (perhaps restarting the jadex platform) without having to also write class files to disk.

@jellelicht jellelicht changed the title How to inject POJO from generated classes How to inject POJO with generated classes Dec 3, 2021
@lb-actoron
Copy link
Contributor

Jadex uses kernels to extends the platform with different kinds of components that can each bring their completely own definitions and execution engines. One component of a kernel is the component factory that has the purpose to create a running component for some model (or file) name.

Besides that it is also possible to start pojo agents directly. We have an example for that in jadex.micro.testcases.pojostart. It just creates an agent pojo and calls addComponent to start it:

PojoStartAgent pojo = new PojoStartAgent();
platform.addComponent(pojo).get();
pojo.started.get();

Is that sufficient for your use case?

@jellelicht
Copy link
Contributor Author

The PojoStartAgent approach is still dependent on a PojoStartAgent.class being somewhere on the class path: I would like to know of a way on how to call platform.addComponent for a pojo with a class that is dynamically created (e.g. generated/injected via the ASM library), and therefore has no class file anywhere.

From what I can see, all of the existing machinery seems to depend on class files being able to be found: this is how Factories are matched by filter as well I believe:

In my case, I have a dynamically constructed class (and pojo is an instance of that class), calling
platform.addComponent(pojo).get() leads to:

2. Caused by jadex.bridge.service.search.ServiceNotFoundException                                                                      
                                                                                                                                       
1. Caused by jadex.bridge.service.search.ServiceNotFoundException                                                                      
   filter=FactoryFilter(xyz.jlicht.helpers.Agent9599.class)                                                                            
                                                                                                                                       
    SComponentFactory.java:  810  jadex.bridge.service.types.factory.SComponentFactory/doFindFactory                                   
    SComponentFactory.java:  801  jadex.bridge.service.types.factory.SComponentFactory$11/customResultAvailable                        
ExceptionDelegationResultListener.java:   55  jadex.commons.future.ExceptionDelegationResultListener/resultAvailable    

Since I have an actual instance of the class, I don’t see why reading any class files is required. Yet by tracing execution, it seems that all existing component factories seem to have this as an implicit requirement. Could there perhaps be a custom/dummy component factory that only works for injecting a pojo? By definition, it can then skip all the ‘can I load this model’ steps, as the class data can simply be found by running pojo.getClass() and classloader via pojo.getClass().getClassLoader().

Is there a chance that injecting pojo’s without Jadex having access to the corresponding class files can be supported?

@lb-actoron
Copy link
Contributor

The factory uses the pojo class to read the model, i.e. beliefs, plans and goals. I think that this use case could be supported by trying to fallback to pojo class when class could not be found otherwise. Could you provide us with a minimal example in which that error occurs so that we can test/try out with it?

@jellelicht
Copy link
Contributor Author

jellelicht commented Dec 9, 2021

Given the following r/Hello.java:

package r;                                                                                                                             
import jadex.bdiv3.runtime.BDIAgent;                                                                                                   
public class Hello extends BDIAgent {}

We now compile this file using javac, and copy the Hello.class to a different machine/project/folder and call it Hello.bytes.
Here we have the following way to create new Hello Objects from a file called some.package.Loader.java:

package some.package;                                                                                                                             
import java.util.HashMap;                                                                                                              
//Define Custom ClassLoader                                                                                                            
public class Loader extends ClassLoader {                                                                                              
        private HashMap<String, byte[]> byteDataMap = new HashMap<>();                                                                 
                                                                                                                                       
        public Loader(ClassLoader parent) {                                                                                            
                super(parent);                                                                                                         
        }                                                                                                                              
                                                                                                                                       
        public void loadDataInBytes(byte[] byteData, String resourcesName) {                                                           
                byteDataMap.put(resourcesName, byteData);                                                                              
        }                                                                                                                              
                                                                                                                                       
        @Override                                                                                                                      
        protected Class<?> findClass(String className) throws ClassNotFoundException {                                                 
                if (byteDataMap.isEmpty())                                                                                             
                        throw new ClassNotFoundException("byte data is empty");                                                        
                                                                                                                                       
                String filePath = className.replaceAll("\\.", "/").concat(".class");                                                   
                byte[] extractedBytes = byteDataMap.get(filePath);                                                                     
                if (extractedBytes == null)                                                                                            
                        throw new ClassNotFoundException("Cannot find " + filePath + " in bytes");                                     
                                                                                                                                       
                return defineClass(className, extractedBytes, 0, extractedBytes.length);                                               
        }        

        public static Object getHelloInstance() throws IOException {                                                                            
        //prepare the bytes array                                                                                                      
                                                                                                                                       
        byte[] byteData = …; // Use the bytes from Hello.bytes file, loaded into an array of bytes                                                          
                                                                                                                                       
        Loader loader = new Loader(this.getClass().getClassLoader());                                       
        //Load bytes into hashmap                                                                                                      
        loader.loadDataInBytes(byteData, "r/Hello.class");                                                                    
                                                                                                                                       
        Class<?> rHello = loader.loadClass("r.Hello");                                                                        
        Object o = rHello.newInstance();
        return o;                                                                                           
        // Now start a jadex platform, and call platform.addComponent(o) to see my error                                               
}                                                                                                                                                                   
}                                                                                                                                      
                                                                                                                                       
    

NB, it is important to remember that at no point do you ever actually have access to the bytes of the class in my actual use case, as my classes are dynamically created in memory; the important thing to remember is that you can call Loader.getHelloInstance() to get Hello instances, and that I’d like to be able to inject them as Jadex agents.

@lb-actoron
Copy link
Contributor

Started looking into that issue. It is not trivial to support but hopefully there is some workaround for that case. I will report my findings

@lb-actoron
Copy link
Contributor

lb-actoron commented Jan 14, 2022

Good news. I have implemented a solution, so that agents can be created directly from pojo without the requirement of having the class on persistent storage. This required some internal API changes and of course only works for kernels with pojo support like BDI based on Java and micro agents. You can see how it works by looking at the new testcase in jadex.bdiv3.testcases.pojowithoutclass.Main (partially based on your code). Currently we need to fix another small issue to make our build get through but that should not take too long.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants