I'm trying to implement a 2-player network quiz game using RMI. I have a DispatcherInterface (interface) and Dispatcher (implementing the for the former) respecting RMI architecture.
The server model has User and Theme where a Quiz would be in a specific theme, my idea was that when a User connects into a Theme they'd be added into a Map<User,Theme> waitingList until another User comes along to play against them.
My problem lies in the implementation, while trying to implement a simple setter method for adding users to the waiting list I get the following error:
Error occurred in server thread; nested exception is:
java.lang.NoClassDefFoundError: java/sql/SQLException
Which did not make sense to me since my class path is well defined for the RMI registry and the compile/run commands.
public interface DispatcherInterface extends Remote {
public User login(ReceiverInterface client, String email, String password) throws RemoteException;
public void addToWaitingList(int userID, Theme theme) throws RemoteException;
public void addToWaitingList(User user, Theme theme) throws RemoteException;
}
The first method works perfectly with no problems while the other one doesn't regardless of its implementation (tried a simple System.out.print("test")), however I find it inconvenient as I have to loop over the list of connected users in order to get the User instance I need to add to my waitingList. So idealy I am hoping to be able to pass an instance of User to the method.
public class User implements Serializable {
public ReceiverInterface client;
public int id;
public String pseudo;
...
}
I do not understand why I can't pass the a User object to the method, especially since it's serializable.
For completeness sake, for the client part, I have implemented similarly a ReceiverInterface and Receiver that communicate with the server through a proxy (DispatcherInterface)
public class Receiver implements ReceiverInterface {
private DispatcherInterface = proxy;
private User user;
private String serverIP;
public Receiver(String serverIP) throws RemoteException, NotBoundException {
this.serverIP = serverIP;
Registry registry = LocateRegistry.getRegistry(serverIP);
this.proxy = (DispatcherInterface) registry.lookup("QuizApp");
}
...
}
Any help is appreciated.
EDIT:
I use an IDE (IntelliJ) to run my code, it uses the following command:
/usr/lib/jvm/java-10-openjdk/bin/java -javaagent:/opt/intellij-idea-ultimate-edition/lib/idea_rt.jar=41683:/opt/intellij-idea-ultimate-edition/bin -Dfile.encoding=UTF-8 -classpath /home/rand/gm4/JAVA/QuizApp/out/production/QuizApp:/home/rand/gm4/JAVA/QuizApp/lib/miglayout-swing-5.2.jar:/home/rand/gm4/JAVA/QuizApp/lib/miglayout-core-5.2.jar:/home/rand/gm4/JAVA/QuizApp/lib/sqlite-jdbc-3.23.1.jar server.Run
Which is in better terms (without the .jar dependencies), run from the project path is:
javac -d out/production/[project_name] src/server/Run.java
javac -d out/production/[project_name] src/client/Run.java
rmiregistry -J-Djava.class.path=out/production/[project_name]/
java -cp out/production/[project_name] server.Run
java -cp out/production/[project_name] client.Run
You are using jdk 10 to execute your code. In jdk 9 some of the code base was moved into modules and no longer avaliable by default, java.sql is in one of these non root module. You need to use "--add-modules java.sql" with the java command to make the java.sql package avaliable at runtime.
Since you are using intellij to run the code the following doc might help explain how to setup intellij to include this module when run from the ide. https://www.jetbrains.com/help/idea/getting-started-with-java-9-module-system.html
Related
I've no problem in creating a REST Server or a Picocli CLI Application.
But what if I want to have both in one Application?
The thing is, I want to have an Application which provides some business logic via REST Server (no problem there), but in some other cases I want to trigger the business logic via CLI without starting the HTTP Server (eg. for CI/CD).
I'm not sure if I run into problems if I start the app via
PicocliRunner.run(Application.class, args) and if a specific argument is given run the Server with Micronaut.run(Application.class);, since they create a different context.
Does anyone know a proper way to achieve this?
This is how I solved it:
import io.micronaut.configuration.picocli.PicocliRunner;
import io.micronaut.runtime.Micronaut;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
#Command(
name = "RestAndCliExample",
description = "...",
mixinStandardHelpOptions = true
)
public class Application implements Runnable {
private enum Mode {serve, run}
#Parameters(index = "0", description = "Execution mode: ${COMPLETION-CANDIDATES}")
private Mode mode;
public static void main(String[] args) throws Exception {
args = new String[]{"run"};
PicocliRunner.run(Application.class, args);
}
public void run() {
if (Mode.serve.equals(mode)) {
// Start REST API
Micronaut.run(Application.class);
} else {
// TODO run code directly
}
}
}
One way to accomplish this is to #Inject the ApplicationContext into your #Command-annotated class. This allows your command to use the same application context instead of needing to start a separate one.
Then, in your run method, you can start the REST server by obtaining the EmbeddedServer from the application context and calling start on it, or you can execute the functionality directly without the REST server.
See also this answer for more detail: https://stackoverflow.com/a/56751733/1446916
I want to write an RMI application using two laptops which adds two numbers? i have made one laptop as my server and another laptop as my client. when we want to define the interface which is extending from Remote interface on which machine should i define this interface the client side or the server side? please help.
i Have made an RMI application using one machine it works fine I have defined the Interface in the same package but when i work on different machines it does not work.
public interface AdditionI extends Remote {
public int add(int x ,int y) throws RemoteException;
}
public class Server extends UnicastRemoteObject implements AdditionI {
public Server() throws RemoteException {}
#Override
public int add(int x, int y) throws RemoteException {
return x+y;
}
public static void main(String ar [])throws RemoteException {
try
{
Registry reg = LocateRegistry.createRegistry(2177);
reg.rebind("Add", new Server());
System.out.println("Server is ready");
}
catch(Exception e)
{
System.out.println("Error "+ e);
}
}
}
public class Client {
public static void main(String ar[])throws RemoteException {
try {
Registry reg = LocateRegistry.getRegistry("localhost",2177);
AdditionI ad = (AdditionI)reg.lookup("Add");
System.out.println("REsult:"+(ad.add(10, 5)));
} catch (Exception e) {
System.out.println("Error"+e);
}
}
}
when i run this code on the same machine it works fine the result of the method add is displayed, but on different machine it displays the following message.
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: java.lang.ClassNotFoundException:
Where should we Define our Interface Client side or Server side?
Simple: you need the interface on both sides.
The Client knows that interface, and basically it is the "only thing" it knows: there is some interface that defines the behavior (the methods) that the client code can use.
The Server knows that interface and implements it.
That interface is the basic thing that "links" client and server (conceptually). They both know that there is some interface AdditionI. The client will need it so it
first identify a service that supports that interface
when such an service is found, the client knows how to invoke the corresponding add method
The server on the other hand uses the interface to register its implementation as a service ... that clients then can call.
Therefore, you basically have three different parts in your source code:
common: that contains that AdditionI interface
client: the additional code required to identify and later use that addition service
server: the additional code to implement and register the service
And note: that exception java.lang.ClassNotFoundException is really basic. It tells you that the JVM running some code did not find some class.
In other words: your classpath setup is somehow broken. Simply research that exception (you can find endless endless documentation about such basic things, see here for example). Most likely, it boils down to: making sure that some .class files are in the classpath ... where you need them. And the first part already tells you where which classes need to go to!
I am trying to make a Java app that can load plugins implementing an abstract class and am having an AbstractMethodError with the instances generated from ServiceLoader. The code is a bit heavy so I've made a simplification below.
First, I have an abstract class:
package stuff.TheAbstractClass;
public abstract class TheAbstractClass implements ClassInterface{
//Stuff happens
}
Which implements the below interface:
package stuff.ClassInterface;
public interface ClassInterface {
public String getClassName();
}
I have a service provider NotAbstractClass which extends TheAbstractClass and states so in a meta-inf/services folder:
package anotherstuff.NotAbstractClass;
public final class NotAbstractClass extends TheAbstractClass implements ClassInterface{
private String name = "Silent Bob";
#Override
public String getClassName() { return name; }
}
Then on the main application (which is actually a plugin inside another application), I want to find all classes which extend TheAbstractClass:
package stuff.TheApp;
import java.util.ServiceLoader;
public class TheApp {
private String name;
public final static TheApp INSTANCE = new TheApp();
private TheApp() {
ServiceLoader<TheAbstractClass> serviceLoader =
ServiceLoader.load(TheAbstractClass.class);
for (TheAbstractClass class: serviceLoader) {
name = class.getClassName;
}
}
My application does find NotAbstractClass. I know this since, in the for loop, I can do class.getName() and it'll give me anotherstuff.NotAbstractClass) but gives the error:
java.lang.AbstractMethodError: stuff.TheAbstractClass.getClassName()Ljava/lang/String;
I'm stumped. Any suggestion? Thank you, Pedro
According to the API for AbstractMethodError you get this:
Thrown when an application tries to call an abstract method. Normally,
this error is caught by the compiler; this error can only occur at run
time if the definition of some class has incompatibly changed since
the currently executing method was last compiled.
Just by looking at your code and your comment I see that this could only have happened at runtime.
If that is the case then:
some class has incompatibly changed since the currently executing
method was last compiled
I've tested your logic after some adjustments in a Java compatible form and I had no problems. The only thing that seems to be happening is a change in any of the subclasses of TheAbstractClass.
Another thing I did was to declare the dependencies using the dependency files in: resources/META-INF/services:
file: <full-package>.TheAbstractClass
content: <full-package>.NotAbstractClass
After this I had no problems.
It seems the issue wasn't in the code, but in the IDE (IntelliJ). I deleted all previously packaged jars and made new jars without changing anything and it magically worked... So it's an IDE bug, and not a language issue!
Thanks to #Joao and #hotzst for taking time to read however.
Best, Pedro
I have an application FooApplication (code in foo.jar) with a plugin BarPlugin (code in foo-bar-plugin.jar). The application instantiates an instance of the plugin dynamically. It works great.
FooApplication has some functionality accessible via an RMI interface FooRemote. That also works great, except for one thing. FooRemote has a method to access Remote objects exported by plugins, and I get an java.rmi.UnmarshalException when I try to hand out one of those plugins to an RMI client.
public interface FooRemote extends Remote
{
/* other methods */
public RemoteControl getPluginRemoteControl(int i) throws RemoteException;
}
/** just a named Remote object for debugging purposes */
public interface RemoteControl extends Remote
{
public String getName() throws RemoteException;
}
What I am doing in my FooRemoteImpl is the following:
/* just a test object */
private static class RC0 extends UnicastRemoteObject implements RemoteControl
{
public RC0() throws RemoteException { super(); }
#Override public String getName() throws RemoteException { return "RC0"; }
}
#Override public RemoteControl getPluginRemoteControl(int i)
throws RemoteException
{
int j = i;
if (j <= 0)
return new RC0();
Collection<RemoteControl> rclist = this.model.getApplicationPluginRemotes();
for (RemoteControl rc : rclist)
{
if (--j == 0)
return rc;
}
return null;
}
When I call FooRemote.getPluginRemoteControl(0), it hands out an instance of my dummy class RC0, and works fine from a client. When I call FooRemote.getPluginRemoteControl(1), it tries to hand out one of the real plugin remotes, and it fails:
??? Java exception occurred:
java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.lang.ClassNotFoundException: com.example.plugin.BarPluginRemoteControl (no security manager: RMI class loader disabled)
at sun.rmi.server.UnicastRef.invoke(Unknown Source)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)
at $Proxy5.getPluginRemoteControl(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.example.plugin.BarPluginRemoteControl (no security manager: RMI class loader disabled)
[ more stuff deleted ]
What gives?
You probably need to set up a SecurityManager. Your client cannot find the class com.example.plugin.BarPluginRemoteControl because there is no security manager in place to give it access.
Try the following:
To the client code add:
System.setSecurityManager(new java.rmi.RMISecurityManager());
Create a policy file called client.policy containing:
grant{
permission java.security.AllPermission;
};
Pass the policy file into the client's startup command:
java -Djava.security.policy=client.policy ... <main-class>...
Please, please, don't put the classes on the RMI Registry classpath. That works around the problem, but it is not the correct solution. Doing so means that each time you update your server code, you'll need to synch the classes with your RMI Registry and all your clients. The proper solution is to provide the needed classes on the server's codebase using an http or ftp URI, (not a file URI, please!). Then, the RMI Registry and your clients will be able to access the needed classes dynamically via http or ftp.
The only classes you should include on the codebase would be the Remote interfaces to your server, and any classes that appear as parameters to or return values from methods in those interfaces, as well as any exception classes thrown by those interfaces. I believe (but I'm not sure) that best-practices for this is to create a separate jar with a '-dl' suffix which includes only these class files.
The RMI Registry also needs the classes on it's own classpath, in case you are running it in a separate VM. I remember wasting too much time learning this the hard way.
Glad I could help!
edit: apparently this is not the correct approach, see answer by the commenter.
I'm using GWT (currenly working with google's eclipse plugin), and I'm trying to throw an exception from the server to the client.
My exception is something like
class LoginException extends Exception implements IsSerializable
But I get (upon loading in hosted mode):
[ERROR] Errors in '[...]/src/myPackage/client/services/Session.java'
[ERROR] Line 25: No source code is available for type hugobarrera.gestorAlumnos.server.LoginException; did you forget to inherit a required module?
Session.java is:
[...]
public interface Session extends RemoteService {
[...] (Line 25:)
String newSession(String usr, String pwd) throws LoginException;
[...]
Where am I going wrong? I've read like a MILLION places where people have problems with throwing exceptions, but none of those solutions applied.
All classes that need to be serialized must be in the [...].client package or a sub package.
Apparently they may not have a constructor either.
[edit] You need to have a no-argument constructor in the serializable classes.
skaffman: LoginException was not in the same package as Session.
Hugo: Moving them to the same package solved the problem.