Server-Side Java Mapping for Interfaces

The server-side mapping for interfaces provides an up-call API for the Ice run time: by implementing member functions in a servant class, you provide the hook that gets the thread of control from the Ice server-side run time into your application code.

On this page:

Skeleton Types in Java

On the client side, interfaces map to proxy types. On the server side, interfaces map to skeleton types. A skeleton is a class or interface that defines a method for each operation on the corresponding Slice interface. For example, consider our Slice definition for the Node interface:

Slice
module Filesystem
{
    interface Node
    {
        idempotent string name();
    }
    // ...
}

The Slice compiler generates the following definition for this interface:

Java
package Filesystem;

public interface Node extends com.zeroc.Ice.Object
{
    static final String ice_staticId = "::Filesystem::Node";

    String name(com.zeroc.Ice.Current current);

    // Mapping-internal code here...
}

There are two important points here:

  • As for the client side, Slice modules are mapped to Java packages with the same name, so the skeleton class definitions are part of the Filesystem package.
  • For each Slice interface <interface-name>, the compiler generates a Java interface of the same name that extends com.zeroc.Ice.Object, defines a method for each operation in the Slice interface, and defines the constant ice_staticId with the corresponding Slice type ID. This interface supplies the skeleton code; your servant must implement this interface.

Object Base Interface for Java Servants

Object is mapped to the com.zeroc.Ice.Object interface in Java:

Java
package com.zeroc.Ice;

public interface Object
{
    public class Ice_invokeResult
    {
        ...
    }

    default boolean ice_isA(String s, Current current) { ... }
    default void ice_ping(Current current) { ... }
    default String[] ice_ids(Current current) { ... }
    default String ice_id(Current current) { ... }
   
    public static String ice_staticId() { ... }
    
    default CompletionStage<OutputStream> ice_dispatch(Request request) throws UserException { ... }
    
    ...
} 

The methods of Object behave as follows:

  • ice_isA
    This method returns true if target object implements the given type ID, and false otherwise.
  • ice_ping
    ice_ping provides a basic reachability test for the servant.
  • ice_ids
    This method returns a string sequence representing all of the type IDs implemented by this servant, including ::Ice::Object.
  • ice_id
    This method returns the type ID of the most-derived interface implemented by this servant.
  • ice_staticId
    This static method returns the type ID of the target interface: ::Ice::Object when called on com.zeroc.Ice.Object.
  • ice_dispatch
    This method dispatches an incoming request to a servant. It is used in the implementation of dispatch interceptors.

The nested class Ice_invokeResult supplies the result for calls to ice_invoke and ice_invokeAsync on proxies.

Servant Classes in Java

In order to provide an implementation for an Ice object, you must create a servant class that inherits from the corresponding skeleton type. For example, to create a servant for the Node interface, you could write:

Java
package Filesystem;

public final class NodeI implements Node
{
    public NodeI(String name)
    {
        _name = name;
    }

    @Override
    public String name(com.zeroc.Ice.Current current)
    {
        return _name;
    }

    private String _name;
}

By convention, servant classes have the name of their interface with an I-suffix, so the servant class for the Node interface is called NodeI. (This is a convention only: as far as the Ice run time is concerned, you can choose any name you prefer for your servant classes.)

As far as Ice is concerned, the NodeI class must implement only a single method: the name method that it inherits from its skeleton. This makes the servant class a concrete class that can be instantiated. You can add other member functions and data members as you see fit to support your implementation. For example, in the preceding definition, we added a _name member and a constructor. (Obviously, the constructor initializes the _name member and the name function returns its value.)

Normal and idempotent Operations in Java

Whether an operation is an ordinary operation or an idempotent operation has no influence on the way the operation is mapped. To illustrate this, consider the following interface:

Slice
interface Example
{
    void   normalOp();
    idempotent void idempotentOp();
    idempotent string readonlyOp();
}

The methods for this interface look like this:

Java
void normalOp(Current current);
void idempotentOp(Current current);
String readonlyOp(Current current);

Note that the signatures of the member functions are unaffected by the idempotent qualifier.

See Also