Monday 21 January 2013

RMI example with NetBeans

nbRMIoracle is the server and nbRMIoracleClient is the client.

There are two classes Compute.java and Task.java in the compute package. Both are used by the server and the client. The server ComputeEngine.java resides in the engine package.

nbRMIoracleClient project has the client package. There are two classesComputePi.java and Pi.java in the client package.
IMPORTANT NOTE: Copyright notice given below covers all the source code lines in this blog article.

Task.java
-------------
/* * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved. *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
* are met: *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer. *
* - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution. *
* - Neither the name of Oracle or the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission. *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

package compute;

public interface Task {    
       T execute();
}
Task is an interface that has a single function execute() without params but returns the object with
the same Template type passed to it.


Compute.java:
--------------------
package compute;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Compute extends Remote {
    T executeTask(Task t) throws RemoteException;
}

Compute java has a single function executeTask which has the param t of type Task.

ComputeEngine.java
---------------------------
package engine;
 
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import compute.Compute;
import compute.Task;

public class ComputeEngine implements Compute {

    public ComputeEngine() {
        super();
    }

    public T executeTask(Task t) {
        return t.execute();
    }

 

    public static void main(String[] args) {

        if (System.getSecurityManager() == null) {

            System.setSecurityManager(new SecurityManager());

        }

        try {

            String name = "Compute";

            Compute engine = new ComputeEngine();

            Compute stub =

                (Compute) UnicastRemoteObject.exportObject(engine, 0);

            Registry registry = LocateRegistry.getRegistry();

            registry.rebind(name, stub);

            System.out.println("ComputeEngine bound");

        } catch (Exception e) {

            System.err.println("ComputeEngine exception:");

            e.printStackTrace();

        }

    }

}

ComputeEngine implements the Compute interface.  So it implements the executeTask function.

executeTask function simply executes the execute() function of the Task that is passed to it as param.

The main function of ComputeEngine sets the security manager first then creates an instance of itself.  Then exports this object cast as Compute to the variable stub.  After this it locates registry and binds the name compute to the object stub.

To run this program you must first define the classpath as given below.

Secondly you must set the vm options as given below:

-Djava.security.debug=access,failure 
-Djava.security.manager
-Djava.security.policy="C:\Users\ars\Desktop\nbRMIoracle\src\engine\server.policy" 
-Djava.rmi.server.codebase=file:/C:\Users\ars\Desktop\nbRMIoracle\build\classes/
 
The OUTPUT:
-------------------
ComputeEngine bound

Pi.java
---------
package client;

import compute.Task;
import java.io.Serializable;
import java.math.BigDecimal

public class Pi implements Task, Serializable {

    private static final long serialVersionUID = 227L;

    /** constants used in pi computation */
    private static final BigDecimal FOUR =
        BigDecimal.valueOf(4); 

    /** rounding mode to use during pi computation */
    private static final int roundingMode =
        BigDecimal.ROUND_HALF_EVEN

    /** digits of precision after the decimal point */
    private final int digits 

    /**
     * Construct a task to calculate pi to the specified
     * precision.
     */
    public Pi(int digits) {
        this.digits = digits;
    }

    /**
     * Calculate pi.
     */
    public BigDecimal execute() {
        return computePi(digits);
    } 

    /**
     * Compute the value of pi to the specified number of
     * digits after the decimal point.  The value is
     * computed using Machin's formula:
     *
     *          pi/4 = 4*arctan(1/5) - arctan(1/239)
     *
     * and a power series expansion of arctan(x) to
     * sufficient precision.
     */
    public static BigDecimal computePi(int digits) {

        int scale = digits + 5;
        BigDecimal arctan1_5 = arctan(5, scale);
        BigDecimal arctan1_239 = arctan(239, scale);
        BigDecimal pi = arctan1_5.multiply(FOUR).subtract(
                                  arctan1_239).multiply(FOUR);
        return pi.setScale(digits,
                           BigDecimal.ROUND_HALF_UP);
    }

    /**
     * Compute the value, in radians, of the arctangent of
     * the inverse of the supplied integer to the specified
     * number of digits after the decimal point.  The value
     * is computed using the power series expansion for the
     * arc tangent:
     *
     * arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 +
     *     (x^9)/9 ...
     */  

    public static BigDecimal arctan(int inverseX,
                                    int scale)
    {
        BigDecimal result, numer, term;
        BigDecimal invX = BigDecimal.valueOf(inverseX);
        BigDecimal invX2 =
            BigDecimal.valueOf(inverseX * inverseX);

        numer = BigDecimal.ONE.divide(invX,
                                      scale, roundingMode);
 
        result = numer;
        int i = 1;
 
        do {
            numer =
                numer.divide(invX2, scale, roundingMode);
            int denom = 2 * i + 1;
            term =
                numer.divide(BigDecimal.valueOf(denom),
                             scale, roundingMode);

            if ((i % 2) != 0) {
                result = result.subtract(term);
            } else {
                result = result.add(term);
            }
            i++;
        } while (term.compareTo(BigDecimal.ZERO) != 0);
        return result;
    }
}

Pi implements task with the template type BigDecimal.  There are original comments in the code that provides sufficient info.  The crucial function is computePi which returns the BigDecimal value of Pi.

arctan is a utility function and used by the computePi.

    public static BigDecimal computePi(int digits) {
 
ComputePi.java
---------------------
package client
 
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.math.BigDecimal;
import compute.Compute;

public class ComputePi {

    public static void main(String args[]) {

        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new SecurityManager());
        }

        try {
            String name = "Compute";
            Registry registry = LocateRegistry.getRegistry(args[0]);
            Compute comp = (Compute) registry.lookup(name);
            Pi task = new Pi(Integer.parseInt(args[1]));
            BigDecimal pi = comp.executeTask(task);
            System.out.println(pi);
        } catch (Exception e) {
            System.err.println("ComputePi exception:");
            e.printStackTrace();
        }
    }   
}

ComputePi sets theSecurity Manager first.  Then sets the stub name to the name var.  It gets the registry name from the program params.  It looks up the name in the registry and gets the interface compliant Compute object, comp.    It creates a Pitask task using the number of digits passes in the programs vars, 2nd var.  It runs the executeTask of the comp object.

 

To run this client program:  You must first run the server with success.

Namely it should write: ComputeEngine bound

Then you should specify the classpath as given below:

You should specify the vm options and arguments as below:

-Djava.security.debug=access,failure
 -Djava.security.manager
 -Djava.security.policy="C:\Users\ars\Desktop\nbRMIoracleClient\src\client\client.policy" 
 -Djava.rmi.server.codebase=file:/C:\Users\ars\Desktop\nbRMIoracleClient\build\classes/

The OUTPUT:
3.141592653589793238462643383279502884197169399
BUILD SUCCESSFUL (total time: 0 seconds)

The security issue:
------------------------
You must specify the security policy for the server and client seperately.  If you check the first picture in this blog article you will notice server.policy and the client.policy:

Server.policy:
-----------------
grant codeBase "file:/C:/Users/ars/Desktop/nbRMIoracle/src/-" {
    permission java.security.AllPermission;
};

grant codeBase "file:/C:/Users/ars/Desktop/nbRMIoracle/build/-" {
    permission java.security.AllPermission;
};

The location of the server.policy is specified in the vm options as below:
-Djava.security.debug=access,failure 
-Djava.security.manager
-Djava.security.policy="C:\Users\ars\Desktop\nbRMIoracle\src\engine\server.policy" 
-Djava.rmi.server.codebase=file:/C:\Users\ars\Desktop\nbRMIoracle\build\classes/

The first line produces debug info.  The third line specifies the location of the server policy.
The fourth line specifies where the binary executables reside.

Client.policy:
---------------
grant codeBase "file:/C:/Users/ars/Desktop/nbRMIoracleClient/src/-" {
    permission java.security.AllPermission;
};

grant codeBase "file:/C:/Users/ars/Desktop/nbRMIoracleClient/build/classes/-" {
    permission java.security.AllPermission;
};

 

Similar to the server:
-Djava.security.debug=access,failure
 -Djava.security.manager
 -Djava.security.policy="C:\Users\ars\Desktop\nbRMIoracleClient\src\client\client.policy" 
 -Djava.rmi.server.codebase=file:/C:\Users\ars\Desktop\nbRMIoracleClient\build\classes/

Be carefull, many difficulties related to RMI are not RMI related but the problems of specifying these VM options.  URL and simple dir specifications may cause large difficulties.  One simpleway to get around these is to use the policyTool that you can find in:

C:\Program Files\Java\jdk1.6.0_26\bin

Using this tool if you check the below policy:

C:\Program Files\Java\jre6\lib\security\java.policy

You may have a realistic idea of what your system does and how to specify the params.


Otherwise you will first get:

access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve)

Then:

Server exception: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:

    java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:

    java.lang.ClassNotFoundException: compute.Compute

Cheers.

Ali R+