In a simple RMI program I managed to pass Context between two Threads. Now I need to move setting/reporting from Context to AspectJ class.
My problem is: How to move Context if I need to use it as an argument in greeting(Context)
HelloIF
public interface HelloIF extends Remote {
String greeting(Context c) throws RemoteException;
}
Hello
public class Hello extends UnicastRemoteObject implements HelloIF {
public Hello() throws RemoteException {
}
public String greeting(Context c) throws RemoteException {
c.report();
return "greeting";
}
}
RMIServer
public class RMIServer {
public static void main(String[] args) throws RemoteException, MalformedURLException {
LocateRegistry.createRegistry(1099);
HelloIF hello = new Hello();
Naming.rebind("server.Hello", hello);
System.out.println("server.RMI Server is ready.");
}
}
RMIClient
public class RMIClient {
public static void main(String[] args) throws RemoteException, MalformedURLException, NotBoundException {
Context context = new Context("request1", Thread.currentThread().getName()+System.currentTimeMillis());
Registry registry = LocateRegistry.getRegistry("localhost");
HelloIF hello = (HelloIF) registry.lookup("server.Hello");
System.out.println(hello.greeting(context));
context.report();
}
}
Context
public class Context implements Serializable
{
private String requestType;
private String distributedThreadName;
public Context(String requestType, String distributedThreadName)
{
this.requestType = requestType;
this.distributedThreadName = distributedThreadName;
}
(...)
public void report() {
System.out.println("thread : "
+ Thread.currentThread().getName() + " "
+ Thread.currentThread().getId());
System.out.println("context : "
+ this.getDistributedThreadName() + " " + this.getRequestType());
}
}
and finally an empty AspectJ class
#Aspect
public class ReportingAspect {
#Before("call(void main(..))")
public void beforeReportClient(JoinPoint joinPoint) throws Throwable {
}
#After("call(void main(..))")
public void afterReportClient(JoinPoint joinPoint) throws Throwable {
}
#Before("call(String greeting(..))")
public void beforeReportGreeting(JoinPoint joinPoint) throws Throwable {
}
#After("call(String greeting(..))")
public void afterReportGreeting(JoinPoint joinPoint) throws Throwable {
}
}
How can I move from Hello and RMIClient Context() constructor and c/context.report()s to ReportingAspect?
You can pass the arguments to a function, and the underlying object, to Advice, thus:
#Before("execution(* greeting(..)) && target(target) && " +
"args(context)")
public void beforeReportGreeting(HelloIF target, Context context) {
context.doSomething();
target.doSomething();
}
Study the AspectJ annotation documentation for the full details. It can be done for all the advice types.
Edit Reading the question in more details, it sounds as if you want to make the Context object something constructed and controlled by the aspect, while still passing it as an argument to Hello.greeting().
That's not something that makes sense. Your underlying system ought to work OK without any AOP going on. So if the Context object is part of that underlying domain, then it's not a good idea for the Aspect to be in charge of its construction and management.
If the Context is only relevant to the Aspect, then you would remove all reference to the context from the domain classes (so greeting() would take no parameters) and build the Context object(s) in the Aspect.
Related
Iam trying to intercept the constructor RuntimeException(String). Iam trying to use Advice as mentioned here and shown here. But the methods onEnter(String message) or onExit(String message). My instrumenting class (inside a different jar):
public class Instrumenting {
private static final String CLASS_NAME = "java.lang.RuntimeException";
public static void instrument(Instrumentation instrumentation) throws Exception {
System.out.println("[Instrumenting] starting to instrument '" + CLASS_NAME + "'");
instrumentation.appendToBootstrapClassLoaderSearch(new JarFile("C:\\Users\\Moritz\\Instrumenting\\dist\\Instrumenting.jar"));
File temp = Files.createTempDirectory("tmp").toFile();
ClassInjector.UsingInstrumentation.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation).inject(Collections.singletonMap(
new TypeDescription.ForLoadedType(RuntimeExceptionIntercept.class),
ClassFileLocator.ForClassLoader.read(RuntimeExceptionIntercept.class)));
new AgentBuilder.Default()
.ignore(ElementMatchers.none())
.with(new AgentBuilder.InjectionStrategy.UsingInstrumentation(instrumentation, temp))
.type(ElementMatchers.named(CLASS_NAME))
.transform((DynamicType.Builder<?> builder, TypeDescription td, ClassLoader cl, JavaModule jm) ->
builder
.visit(Advice.to(RuntimeExceptionIntercept.class)
.on(ElementMatchers.isConstructor())
)
).installOn(instrumentation);
System.out.println("[Instrumenting] done");
}
public static class RuntimeExceptionIntercept {
#Advice.OnMethodEnter
public static void onEnter(String message) throws Exception {
System.err.println("onEnter: " + message);
}
#Advice.OnMethodExit
public static void onExit(String message) throws Exception {
System.err.println("onExit: " + message);
}
}
}
How its called:
public class Main {
public static void premain(String agentArgs, Instrumentation instrumentation) throws Exception {
Instrumenting.instrument(instrumentation);
}
public static void main(String[] args) throws MalformedURLException, IOException {
new RuntimeException("message");
}
}
Output:
[Instrumenting] starting to instrument 'java.lang.RuntimeException'
[Instrumenting] done
What am I doing wrong?
The class is already loaded when your agent is running and you have not specified for example RedefinitionStrategy.RETRANSFORM. Therefore, your agent will not reconsider already loaded classes. Note that you should set a more specific ignore matcher then that.
By the way, advice is inlined in the target class, your injection is not required.
My aim is to make a simple chat program. I'm new at RMI. What I've got so far is that the server works. I start it. Then I start the client, it transfers the strings to the server through RMI. But then it doesn't appear on the GUI I made. That's where my problem lies.
My project structure
My StartClient class. I created a chatClient, and put the chatServer stub as parameter.
public StartClient() throws RemoteException, NotBoundException, MalformedURLException {
chatServer = (ChatServer) Naming.lookup("rmi://localhost:1099/chatServer");
}
private void run() throws RemoteException, MalformedURLException, NotBoundException {
ChatClientImpl chatClient1 = new ChatClientImpl(chatServer, "ikke");
new ChatFrame(chatClient1);
ChatClientImpl chatClient2 = new ChatClientImpl(chatServer, "bla");
new ChatFrame(chatClient2);
}
public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {
StartClient start = new StartClient();
start.run();
}
In the ChatClientImpl constructor I use the remote method register.
public ChatClientImpl(ChatServer chatServer, String name) throws MalformedURLException, NotBoundException, RemoteException {
this.chatServer = chatServer;
this.name = name;
chatServer.register(this);
}
Now we're in the ChatServerImpl class, in the REGISTER method. I add the client to an ArrayList of clients. Then I use the method SENT to display the text. It calls the RECEIVE method that each client object has.
public class ChatServerImpl extends UnicastRemoteObject implements ChatServer {
private List<ChatClient> clients;
public ChatServerImpl() throws RemoteException {
this.clients = new ArrayList<ChatClient>();
}
public void register(ChatClientImpl client) throws RemoteException {
clients.add(client);
send("server", client.getName() + " has entered the room");
}
public void unregister(ChatClientImpl client) throws RemoteException {
clients.remove(client);
send("server", client.getName() + " has left the room");
}
public void send(String name, String message) throws RemoteException {
for(ChatClient client : clients) {
client.receive(name + ": " + message);
}
}
}
This is where things go wrong. The textReceiver is ALWAYS null. (textReceiver is attribute/field of the client object.)
public void receive(String message) {
if (textReceiver == null) return;
textReceiver.receive(message);
}
The ArrayList of clients are server-side and all the clients in there all have their textReceivers set on null. If you look back at StartClient there's an important line. The new ChatFrame(chatClient). In the ChatFrame's constructor is where I set the textReceiver.
public ChatFrame(ChatClientImpl chatClient) {
this.chatClient = chatClient;
chatClient.setTextReceiver(this);
String name = chatClient.getName();
setTitle("Chat: " + name);
createComponents(name);
layoutComponents();
addListeners();
setSize(300, 300);
setVisible(true);
}
This project works when I don't use RMI and they're in one package but once I separate them into client-server this problem arose. How do I communicate between them? Server-side I have an (irrelevant?) list of ChatClients that don't influence anything even though the text arrives.
Do I use RMI for every separate ChatClient and make the ChatServer connect with it and send the text like that? Seems very complicated to me. How do I go about this?
EDIT:
ChatClientImpl class
public class ChatClientImpl implements ChatClient, Serializable {
private ChatServer chatServer;
private TextReceiver textReceiver;
private String name;
public ChatClientImpl(ChatServer chatServer, String name) throws MalformedURLException, NotBoundException, RemoteException {
this.chatServer = chatServer;
this.name = name;
chatServer.register(this);
}
public String getName() {
return name;
}
public void send(String message) throws RemoteException {
chatServer.send(name, message);
}
public void receive(String message) {
if (textReceiver == null) return;
textReceiver.receive(message);
}
public void setTextReceiver(TextReceiver textReceiver) {
this.textReceiver = textReceiver;
}
public void unregister() throws RemoteException {
chatServer.unregister(this);
}
}
Your ChatClientImpl class isn't an exported remote object, so it will be serialized to the server, and execute there. And because register() happens during construction, it will be serialized before the setReceiverTextReceiver() method is called. So, the corresponding field will be null. At the server. This is not what you want and it is also not where you want it.
So, make it extend UnicastRemoteObject and implement your ChatClient (presumed) remote interface. If you have problems with doing that, solve them. Don't just mess around with things arbitrarily. And it should not implement Serializable.
NB The signature of register() should be register(ChatClient client). Nothing to do with the ChatClientImpl class. Ditto for unregister().
So I am testing a simple Google Guice interceptor -
My Annotation -
#Retention(RetentionPolicy.RUNTIME) #Target(ElementType.METHOD)
public #interface AppOpsOperation {
}
My Interceptor
public class AppOpsOperationDecorator implements MethodInterceptor {
private ServiceCallStack callStack = null ;
#Inject
public void setServiceCallStack(ServiceCallStack stack ){
callStack = stack ;
}
#Override
public Object invoke(MethodInvocation arg0) throws Throwable {
// Retrieve the call stack
// exclude service population if caller service is the same service
// else push the current service onto top of stack
System.out.println("Intercepting method -- :: " + arg0.getMethod().getName());
System.out.println("On object - :: " + arg0.getThis().getClass().getName());
System.out.println("On accessible object - :: " + arg0.getStaticPart().getClass().getName());
return invocation.proceed();
}
}
And now my Service interface and method
public interface MockCalledService extends AppOpsService {
#AppOpsOperation
public String methodOneCalled(String some);
#AppOpsOperation
public String methodTwoCalled(String some);
}
public class MockCalledServiceImpl extends BaseAppOpsService implements MockCalledService {
#Override
#AppOpsOperation
public String methodOneCalled(String some) {
System.out.println("MockCalledServiceImpl.methodOneCalled()");
return this.getClass().getCanonicalName() + "methodOneCalled";
}
#Override
public String methodTwoCalled(String some) {
System.out.println("MockCalledServiceImpl.methodTwoCalled()");
return this.getClass().getCanonicalName() + "methodTwoCalled";
}
}
And my Guice test module
public class MockTestGuiceModule extends AbstractModule {
#Override
protected void configure() {
bind(ServiceCallStack.class).toInstance(new ServiceCallStack());
AppOpsOperationDecorator decorator = new AppOpsOperationDecorator() ;
requestInjection(decorator);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AppOpsOperation.class),
decorator);
bind(MockCalledService.class).toInstance(new MockCalledServiceImpl());
}
}
This interceptor doesn't execute when I run the test below -
public class AppOpsOperationDecoratorTest {
private Injector injector = null ;
#Before
public void init(){
injector = Guice.createInjector(new MockTestGuiceModule());
}
#Test
public void testDecoratorInvocation() {
MockCalledService called = injector.getInstance(MockCalledService.class);
called.methodOneCalled("Test String");
}
}
Can you please highlight what I am doing wrong ?
I am answering after finding the real reason. Its so simple that its really tricky.
Method interception only works if you bind the interface with the class and not an instance of this implementation.
so instead of bind(MockCalledService.class).toInstance(new MockCalledServiceImpl());
we should write bind(MockCalledService.class).to(MockCalledServiceImpl.class);
Seems instances are not proxied :(
I have 2 projects. One works fine in ever department. I downloaded and modified it to better understand it. The 2nd one is a project in development phase.
Now, both these projects have almost exactly the same RMI package, which works fine in the first project, but not in the 2nd.
My test classes in each package are essentially identical as well.
The main difference is what objects there are attempting to access, which are both interfaces in a database package.
Now, the database package in the 2nd project otherwise works absolutely fine, it just wont work with the RMI.
In short:
database package works fine
RMI package works fine
RMI package and database together does not work fine.
Here is my DBInterface
public interface DB extends Remote {
public String[] read(int recNo) throws RecordNotFoundException;
public void update(int recNo, String[] data, long lockCookie)
throws RecordNotFoundException, SecurityException, IOException;
public void delete(int recNo, long lockCookie)
throws RecordNotFoundException, SecurityException, IOException;
public int[] find(String[] criteria);
public int create(String[] data) throws DuplicateKeyException, IOException;
public long lock(int recNo) throws RecordNotFoundException;
public void unlock(int recNo, long cookie)
throws RecordNotFoundException, SecurityException;
}
and here is my RMIInterface
public interface RMIInterface extends Remote{
public DB getClient() throws RemoteException;
}
My RMIImplementation
public class RMIImplementation extends UnicastRemoteObject
implements RMIInterface {
private static String dbLocation = null;
private DB a;
public RMIImplementation() throws RemoteException{
}
public RMIImplementation(String dbLocation) throws RemoteException{
System.out.println(dbLocation);
this.dbLocation = dbLocation;
}
public static DB getRemote(String hostname, String port)
throws RemoteException {
String url = "rmi://" + hostname + ":" + port + "/DvdMediator";
try {
RMIInterface factory
= (RMIInterface) Naming.lookup(url);
// at this point factory equals Proxy[RMIInterface,................etc
// i want the return to equal Proxy[DB,..............etc
return (DB) factory.getClient();
} catch (NotBoundException e) {
throw new RemoteException("Dvd Mediator not registered: ", e);
}
catch (java.net.MalformedURLException e) {
throw new RemoteException("cannot connect to " + hostname, e);
}
}
public DB getClient() throws RemoteException {
try {
a = new ContractorDatabase(dbLocation);
}
catch(Exception e){
System.out.println("NewClass exception: " + e.toString());
}
return a;
}
And my the RMI registry
public class RegDvdDatabase {
private RegDvdDatabase() {
}
public static void register()
throws RemoteException {
register(".", java.rmi.registry.Registry.REGISTRY_PORT);
}
public static void register(String dbLocation, int rmiPort)
throws RemoteException {
Registry r = java.rmi.registry.LocateRegistry.createRegistry(rmiPort);
r.rebind("DvdMediator", new RMIImplementation(dbLocation));
}
}
Getting these two to work together throws a
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to sjdproject.remote.RMIImplementation
Can u please help me find the database issue that prevents it from working.
You must cast it to the remote interface.
EDIT The Registry reference r in your server code must be static. I can't see any good reason for locating the client lookup code inside the implementation class. That class should only exist at the server, not the client.
If you dont have a debugger, I would suggest using reflection on the provided object and see which interfaces it implements. It appears to be a proxy object, so must implement some interfaces.
for(Class clazz : factory.getClass().getInterfaces()) {
System.out.println(clazz.getSimpleName());
}
My suspicion with multiple deployments is of course the jvm version and the classpath. Can you verify that they match?
I am trying to add arguments in RMI method. When I add e.g. String everything works fine. But I am not sure if I can pass an object I created. I am new to RMI so my code is very simple:
HelloIF
public interface HelloIF extends Remote {
String greeting(Context c) throws RemoteException;
}
Hello
public class Hello extends UnicastRemoteObject implements HelloIF {
public Hello() throws RemoteException {
}
public String greeting(Context c) throws RemoteException {
addToContext(c);
report(c);
return "greeting";
}
void addToContext(Context c) {
c.addID(Thread.currentThread().getId());
}
void report(Context c) {
System.out.println("Hello.greeting() thread : "
+ Thread.currentThread().getName() + " "
+ Thread.currentThread().getId());
System.out.println("Hello.greeting() context : "
+ c.getDistributedThreadName() + " " + c.getRequestType());
}
}
RMIServer
public class RMIServer {
public static void main(String[] args) throws RemoteException, MalformedURLException {
LocateRegistry.createRegistry(1099);
HelloIF hello = new Hello();
Naming.rebind("server.Hello", hello);
System.out.println("server.RMI Server is ready.");
System.out.println("RMIServer.main() thread : " + Thread.currentThread().getName()
+ " " + Thread.currentThread().getId());
}
}
RMIClient
public class RMIClient {
public static void main(String[] args) throws RemoteException, MalformedURLException, NotBoundException {
Context context = new Context("request1", Thread.currentThread().getName()+System.currentTimeMillis());
Registry registry = LocateRegistry.getRegistry("localhost");
HelloIF hello = (HelloIF) registry.lookup("server.Hello");
System.out.println(hello.greeting(context));
System.out.println("RMIClient.mian() thread : " + Thread.currentThread().getName()
+ " " + Thread.currentThread().getId());
}
}
and finally my class Context
public class Context
{
private String requestType;
private String distributedThreadName;
private List<Long> IDList;
(...) getters/setters
}
What should I do to make passing Context possible?
Your object should implement Serializable. As I can see this would be one problem. It is needed because the communication between both parts is done using serialization, so each object that needs to be sent to the other part, needs to be an instance of class implementing Serializable.
public class Context implements Serializable
{
private String requestType;
private String distributedThreadName;
private List<Long> IDList;
(...) getters/setters
}
and please add a serialVersionUID as a good practice. Something like:
private static final long serialVersionUID = 20120731125400L;