Before going into detail about how to write services in general, we shall
demonstrate how to add a new service with an example. The new service,
Rand, returns a randomly-generated integer
The first thing to do is to write the Pizza code that implements the service. Each service routine that is to be made available should be a publicly available static method in a class that you define. Any other ancillary code should be made private. For our random service we first define a private java object which will generate the random numbers for us, called svcRand. The publicly available service function Rand then uses this private object to return randomly generated integers:
import java.util.*; public class NewService { private static Random svcRand = new Random(); public static int Rand () { return svcRand.nextInt(); } }
The next thing to do is to make the service functions fit the API required by the PLAN router: each service function should fit the following prototype:
public static Value Name( List<Value> vlist, ActivePacket p) throws ExecException, PLANExceptionwhere Name is the name of the function to be exported to PLAN. Let's look at the rest of the prototype.
Value is a pizza class that defines the form of all PLAN values. It is an algebraic type described by the class basis/Value.pizza. Since a PLAN value should be returned by all services invoked from a PLAN program, all service functions should return type Value. In addition, it is possible that a service could raise a PLAN exception or cause an execution error. Both of these possibilities are handled by raising Java exceptions of type PLANException, and ExecException, respectively.
The arguments to the service function are a list of Value's, and an ActivePacket. When this function is invoked as a service call from PLAN, each element of the Value list shall correspond to the PLAN value that was provided as an actual argument. The ActivePacket parameter shall be bound to the invoking PLAN packet, and may contain information of interest to the service, such as the currently available resource bound of the packet.
All of the Java types described here are present in the basis package, which must imported by the Java class implementing the service.
Applying this API, the code now looks like:
import PLAN.basis.*; import java.util.*; import pizza.lang.List; public class NewService { private static Random svcRand = new Random(); public static Value Rand( List<Value> vlist, ActivePacket p) throws ExecException, PLANException { if (vlist.length() != 0) { throw new ExecException("Error: rand takes no args"); } else { return Value.Int(svcRand.nextInt()); } } }
Notice how the service does its own typechecking: the number of arguments provided to the service are checked, and if they are not correct, an ExecException is raised. If everything proceeds correctly, a PLAN value which corresponds to a PLAN integer is returned.
Finally, we must add an install method to the class to install its new services. This method serves to place the name of each Java function corresponding to a PLAN service in the router service symbol table. Here is the final version of the code:
import PLAN.basis.*; import PLAN.interpreter.*; import pizza.lang.List; import java.lang.reflect.*; import java.util.*; public class NewService { private static Random svcRand = new Random(); public static Value Rand( List<Value> vlist, ActivePacket p) throws ExecException, PLANException { if (vlist.length() != 0) { throw new ExecException("Error: rand takes no args"); } else { return Value.Int(svcRand.nextInt()); } } public static void install(SymbolTable svcs_symtab, Class thisClass) { Method[] methods = thisClass.getDeclaredMethods(); for (int i = 0; i<methods.length; i++) { if (methods[i].getName().equals("Rand")) { svcs_symtab.put ("Rand", Binding.Service(List.Nil, PLType.IntType, methods[i])); } } } }
The install routine must obtain a Method object for each of the methods that it wants to install for services. Each method is then added to the services symbol table, which is passed as an argument to install. Notice that the PLAN.interpreter and java.lang.reflect packages must now be imported in order to obtain definitions of the SymbolTable and Binding classes, and for the Method class. Finally, notice that the Class object that represents the NewService class is passed as a parameter to install. We'll see why this occurs in a moment.
The next thing to do is to compile the class. Since this is pizza code, the source (above) should be in the file NewService.pizza, and the pizza compiler should be used to compile it. The result is the creation of the class file NewService.class. This class may now be dynamically installed in a running router, using the installServices service function. Assuming there is a router running on kamagarini, here is how you would install the service, using the PLANStart tool:
% java PLANStart dumb.plan 1 kamagarini installServices([("NewService",getBlobFromFile("NewService.class"))]) Warning: Function to execute installServices is not bound locally File length = 1690 bytes %This causes the NewService class to be dynamically loaded into kamagarini's symbol table. Note that we have to pass PLANStart the file dumb.plan to make it happy; it must contain a legal program but we don't have to invoke it.
One problem with loading the service this way is that we have no way of knowing whether the install actually succeeded. If a problem arises during a service installation, it will raise the PLAN exception BadService; therefore, we can try to catch this exception to determine if things worked out. Here is PLAN function to do the install (in the file, say, install.plan):
fun install(name:string, file:blob) : unit = try (installServices([(name,file)]); print("Install succeeded\n")) handle BadService => print ("Install failed!\n")Here is its invocation:
% java PLANStart install.plan 1 kamagarini install("NewService",getBlobFromFile("NewService.class")) File length = 1690 bytes Install succeeded %If the install were to fail, the message ``Install failed!'' would be printed. Note that the name and file are arguments to the install function; this is not just good programming style: since the getBlobFromFile routine is only available to PLANStart, we want that executed on the local machine, not on the router. Therefore it must be in the argument list to install during the invocation, and if it's there, we might as well stick the name there, too.
Now that the service is loaded, we can invoke it from PLAN, using the following plan function (in the file, say, rand.plan):
fun foo() : unit = print(Rand())Here is the invocation:
% java PLANStart rand.plan 1 kamagarini foo() 164667826 %
For more details about how services are installed, please refer to the PLAN Programmer's Guide [4].