Project

General

Profile

Java REST Services

Introduction

FWD supports REST services written directly in Java, with no runtime constraints related to the legacy converted application. Although these will be deployed in the same FWD server, they will be mapped to the same basepath as the legacy REST services.

Writing Java REST Services

Java REST services can be written as any Java method, instance or static. For both cases, both the declaring class and the methods must be public; for instance methods, the declaring class must be non-abstract and must have a no-argument constructor (implicit or explicit) - an instance of this class will be created for each invocation of a REST service mapped to an instance method.

Once a Java class has defined REST services, the cfg/name_map_merge.xml must register it, like this:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<name-map>
  <rest-service jname="JavaRestTest" /> 
  <rest-service jname="SimpleRestTest" /> 
</name-map>

This file must be configured in cfg/p2j.cfg.xml, using this node:

<cfg>
<global>
...
<parameter name="name_map_merge" value="${P2J_HOME}/cfg/name_map_merge.xml" />

Here, we have two classes - JavaRestTest and SimpleRestTest, which reside in the converted application's base package (i.e. com.goldencode.p2j.testcases, pkgroot configuration in p2j.cfg.xml or directory). You must specify the:
  • jname - the Java class name, a relative package to the converted application's base package. Full packages are not supported.
The Java class will have a LegacyService annotation, with these settings:
Annotation Attribute Required Data Type Description
type = "REST" yes String Mark this class as a REST service.
executionMode = "java" yes String Mark this as a REST service implemented in Java.
address no String A common prefix for all services defined in this class.
produces yes String The content-type for the response, usually "application/json".
consumes yes String The content-type for the request, usually @"application/json".
Each Java method which exposes a REST service must be annotated with a LegacyService annotation; some settings, if not specified, will be inherited from the defining class LegacyService annotation:
Annotation Attribute Required Data Type Description
type = "REST" yes String Marks this method as a REST service.
address no String If not specified, is inherited from the defining class LegacyService annotation.
produces no String If not specified, are inherited from the defining class LegacyService annotation.
consumes no String If not specified, are inherited from the defining class LegacyService annotation.
name yes String A description of this service.
path yes String The path of this service, relative to the path resolved after combining the REST service basepath and the address specified at the annotation.
verb yes String The HTTP method associated with this service.
parameters array no The list of the service parameters, each a LegacyServiceParameter instance
Each LegacyServiceParameter can have:
Annotation Attribute Required Data Type Description
ordinal yes int The 1-based index of this argument in the method's signature. Must be set to 0 for a parameter mapping to the method's return value.
returnValue = true no boolean Mandatory for the argument which is mapped to the method's return value. This argument will have ordinal = 0.
extent no int The array's length, only if the parameter is a Java array.
source Required if input = true. String The source of this argument, as it will be parsed from the request, for input arguments.
target Required if output = true. String The target of this argument, as it will be serialized to the response, for output arguments.
input Required if output is not set or set to false. boolean Flag indicating this argument will be populated from the request.
output Required if input is not set or set to false. boolean Flag indicating this argument will be written to the response. For an output argument to be written to the response, it must be mutable. Any change performed to a mutable output argument will be reflected in the response. If the parameter's reference gets changed, the response will not be able to be aware of these changes, and the original argument (if the argument was also input) will be written to the response. If both input = true and output = true are specified at the annotation, then this argument is in INPUT-OUTPUT mode.
The source and target follow a specific format. input arguments must always have the source specified, which can be:
Name Parameter Data Type Description
rest.verb String The argument will be parsed from the HTTP method. The parameter must be a string.
http.body Any The argument will be parsed from the request body. The parameter can be of any type.
http.headers String The argument will be initialized with all the HTTP headers at the request. The parameter must be a string.
http.uristring String The argument will be initialized with the URI string. The parameter must be a string.
http.header['<name>'] String The argument will be initialized the header <name>.
rest.queryparam['<name>'] Any The argument will be initialized with the query parameter <name>.
rest.formparam['<name>'] Any The argument will be initialized with the form parameter <name>.
rest.cookieparam['<name>'] Any The argument will be initialized with the cookie <name>.
rest.pathparam['/this/is/the/api/path/{<name>};<name>'] Any The argument will be initialized from a API path parameter.
json.object['request'].['<name>'] Any The argument will be initialized with the JSON value from request.<name>. Note that all request JSON payloads (used for mapping the arguments) have this format:
{
   "request" : 
   {
      "param1" : "value1",
      "param2" : 1234,
      "param3" : 2022-03-22
   }
}

In other words, there is always a root request node, under which the parameters are mapped. This constraint does not exist if the parameter is mapped to the http.body.
The target must be specified always for output parameters, and it can be:
Name Parameter Data Type Description
http.body Any The parameter will be written to the HTTP response body.
http.statuscode int The parameter will be set as the HTTP Status Code.
http.header['<name>'] Any The string representation of the parameter will be set as header <name>.
rest.cookieparam['<name>'] Any The string representation of the parameter will be set as cookie <name>.
json.object['response'].['<name>'] Any The parameter will be set to the JSON response payload, using the <name> key. Similar for the request, the response payload will have all output parameters rooted to a response node, like:
{
   "response" : 
   {
      "param1" : "value1",
      "param2" : 1234,
      "param3" : 2022-03-22
   }
}
The parameters will be parsed and serialized depending on their type. FWD supports these types by default, parsing and serializing them in the following way:
  • Java native types, should be used only for input. If the parameter does not have a corresponding argument, it will be initialized to the native type's default value. The serialization will be done considering their JSON corresponding type.
  • java.math.BigInteger, java.math.BigDecimal, java.lang.String and the java.lang counterparts for the Java native types: these instances are immutable, so they should be used only for input. By default, they will be initialized to null, if there is no argument corresponding for this parameter.
  • Java collections are supported, with explicit serializers added for Deque, SortedSet, Queue and Set. The serializer will be chosen based on the closest match: if you have a SortedSet instance, then the SortedSet serializer will be used. If there is no match at all, it will default to a standard Collection serializer. Map and SortedMap also have explicit serializers in FWD. If the parameter's type is using generics, then the elements in that collection will be serialized using that type; otherwise, the serialization will be done using each element's type, as it exists in the collection.

Following is a comprehensive list of the supported types (or their sub-types):

Data type Input Output Initial Value Serialization details
boolean yes no false JSON boolean type
byte yes no 0 JSON number type
short yes no 0 JSON number type
int yes no 0 JSON number type
long yes no 0 JSON number type
float yes no 0.0 JSON number type
double yes no 0.0 JSON number type
char yes no "\0" JSON text
java.lang.Boolean yes no null JSON boolean type
java.lang.Byte yes no null JSON number type
java.lang.Short yes no null JSON number type
java.lang.Integer yes no null JSON number type
java.lang.Long yes no null JSON number type
java.lang.Float yes no null JSON number type
java.lang.Double yes no null JSON number type
java.lang.Character yes no null JSON text
java.math.BigInteger yes no null JSON number type
java.math.BigDecimal yes no null JSON number type
java.lang.String yes no null JSON text
java.util.Date yes yes Current date JSON text, ISO-8601 format
java.util.Calendar yes yes Current date JSON text, ISO-8601 format
java.lang.Object no yes null Serialization depends on the concrete type. Can be used as output only if the concrete type is mutable (and the reference isn't changed). Can be used for collection's or Java array's element type.
Java arrays yes yes An array with the length specified at the annotation JSON array, each element serialized according to element's type. Can't be resized. For output, it is mandatory to specify its length, via the extent setting. Its component can be of any supported type, which is documented in this section (so it can be parsed and serialized by FWD).
byte[] yes yes null JSON text, base64-encoded byte array
java.util.Deque yes yes java.util.ArrayDeque JSON array
java.util.SortedSet yes yes java.util.TreeSet JSON array
java.util.Queue yes yes java.util.ArrayDeque JSON array
java.util.Set yes yes java.util.HashSet JSON array
java.util.Map yes yes java.util.HashMap JSON object
java.util.SortedMap yes yes java.util.TreeMap JSON object
java.util.Collection yes yes java.util.ArrayList JSON array
com.goldencode.p2j.rest.serializers.PojoType yes yes of parameter's type JSON object. Any POJO type must be marked by implementing the com.goldencode.p2j.rest.serializers.PojoType interface. The serialization format follows a JSON object with keys having the Java field's name and as values the actual field value. All fields which need to be part of the JSON request or response must have a getter and setter defined, and not be transient.
com.goldencode.p2j.persist.DataModelObject yes yes of parameter's type JSON object. Legacy records can be either read or returned if a parameter is defined as the DMO interface for that table. They are serialized as an JSON object, having as keys the legacy field name - keep in mind that the keys are case-sensitive. These DMOs can be used in collections and Java arrays, too.
character yes yes unknown JSON text
longchar yes yes unknown JSON text
clob yes yes unknown JSON text
date yes yes unknown JSON text, ISO-8601 format
datetime yes yes unknown JSON text, ISO-8601 format
datetime-tz yes yes unknown JSON text, ISO-8601 format
logical yes yes unknown JSON boolean
decimal yes yes unknown JSON number
integer yes yes unknown JSON number
int64 yes yes unknown JSON number
blob yes yes unknown base-64 encoded byte array
raw yes yes unknown base-64 encoded byte array
recid yes yes unknown JSON number
rowid yes yes unknown JSON text
comhandle n/a n/a n/a unsupported
object n/a n/a n/a unsupported
jobject n/a n/a n/a unsupported
handle n/a n/a n/a unsupported
table n/a n/a n/a unsupported
table-handle n/a n/a n/a unsupported
dataset n/a n/a n/a unsupported
dataset-handle n/a n/a n/a unsupported
memptr n/a n/a n/a unsupported
Custom serializers can be defined, and:
  • they all must be placed in the same package, the one defined in the rest/custom-java-serializers node in directory.xml.
  • must extend the com.goldencode.p2j.rest.serializers.JavaTypeSerializer class and have this structure:
    public class FooSerializer
    extends JavaTypeSerializer<Foo>
    {
       public FooSerializer()
       {
          super(Foo.class);
       }
    
       @Override
       public Foo initialize(Class<? extends Foo> definitionType) 
       throws InstantiationException, 
              IllegalAccessException
       {
          Foo foo; 
          // if the type is mutable, create an initial instance for it; otherwise, return null.
          return foo;
       }
    
       @Override
       public Foo parse(Foo arg, String sval) 
       throws RequestArgumentError
       {
          // explicit parsing of this type; the instance returned by the 'parse' method will be passed as the argument for the method call.
          return arg;
       }
    
       @Override
       public JsonNode toJson(Foo val)
       {
          // explicit serialization of this type
          return res;
       }
    }
    

    where:
  • Foo is the type for which a custom serializer is added
  • initialize is a method used to create an initial instance for mutable types. The following APIs can be used:
    • if this is a DMO interface, then use JavaTypeSerializer.newRecord to create a new instance of this record.
    • JavaTypeSerializer.newInstance(Class, to create a new instance of type T, or if the type is abstract, use the supplier function to create one.
  • parse(T arg, String sval), used to parse the JSON string representation (in sval) and initialize the argument. The arg parameter is the one returned by initialize, but is not mandatory to return it - another instance can be returned. There are helper APIs in JavaTypeSerializer, to allow you to:
    • load the JSON object in a map, via readMap. The key and value's serializers can be specified, but they are not mandatory.
    • load the JSON array in a Java list, via readArray. The array's component serializer can be specified, but it is not mandatory.
  • JsonNode toJson(T val), used to serialize a certain instance to JSON. JavaTypeSerializer provides helper APIs to:
    • toJsonArray(Collection c, JavaTypeSerializer serializer), serialize the Java collection as a JSON array.
    • toJsonObject(Map map, JavaTypeSerializer pkey, JavaTypeSerializer pval), serialize the map as a JSON object.

No more than one serializer can be defined for a certain exact type. If there are multiple serializers matching a certain type, the closest serializer in the type's hierarchy will be used.

Java Types

Any Java types (native or Object) can be used:
  • as a standalone INPUT parameter (OUTPUT or INPUT-OUTPUT is not supported, as these require the instance to be mutable)
  • as a collection's or array's element, or a POJO property.
  • method's return.

Following example shows how these can be used as standalone INPUT parameters; the parameter's are read from the request JSON, and are returned in a map, as a JSON object:

Service Definition

   @LegacyService(type = "REST", name = "test java types", path = "/javaTypes", verb = "POST", parameters = 
   {
      @LegacyServiceParameter(ordinal = 0, target = "json.object['response'].['returnValue']  ", returnValue = true),
      @LegacyServiceParameter(ordinal = 1, source = "json.object['request'].['p1']", input = true),
      @LegacyServiceParameter(ordinal = 2, source = "json.object['request'].['p2']", input = true),
      @LegacyServiceParameter(ordinal = 3, source = "json.object['request'].['p3']", input = true),
      @LegacyServiceParameter(ordinal = 4, source = "json.object['request'].['p4']", input = true),
      @LegacyServiceParameter(ordinal = 5, source = "json.object['request'].['p5']", input = true),
      @LegacyServiceParameter(ordinal = 6, source = "json.object['request'].['p6']", input = true),
      @LegacyServiceParameter(ordinal = 7, source = "json.object['request'].['p7']", input = true),
      @LegacyServiceParameter(ordinal = 8, source = "json.object['request'].['p8']", input = true),
      @LegacyServiceParameter(ordinal = 9, source = "json.object['request'].['p9']", input = true),
      @LegacyServiceParameter(ordinal = 10, source = "json.object['request'].['p10']", input = true),
      @LegacyServiceParameter(ordinal = 11, source = "json.object['request'].['p11']", input = true),
      @LegacyServiceParameter(ordinal = 12, source = "json.object['request'].['p12']", input = true),
      @LegacyServiceParameter(ordinal = 13, source = "json.object['request'].['p13']", input = true),
      @LegacyServiceParameter(ordinal = 14, source = "json.object['request'].['p14']", input = true),
      @LegacyServiceParameter(ordinal = 15, source = "json.object['request'].['p15']", input = true),
      @LegacyServiceParameter(ordinal = 16, source = "json.object['request'].['p16']", input = true),
      @LegacyServiceParameter(ordinal = 17, source = "json.object['request'].['p17']", input = true),
      @LegacyServiceParameter(ordinal = 18, source = "json.object['request'].['p18']", input = true),
      @LegacyServiceParameter(ordinal = 19, source = "json.object['request'].['p19']", input = true),
      @LegacyServiceParameter(ordinal = 20, source = "json.object['request'].['p20']", input = true),
      @LegacyServiceParameter(ordinal = 21, source = "json.object['request'].['p21']", input = true),
      @LegacyServiceParameter(ordinal = 22, source = "json.object['request'].['p22']", input = true),
   })
   public Map<String, Object> javaTypes(BigDecimal p1,
                                        BigInteger p2,
                                        Boolean    p3,
                                        boolean    p4,
                                        byte[]     p5,
                                        byte       p6,
                                        Byte       p7,
                                        Calendar   p8,
                                        Character  p9,
                                        char       p10,
                                        Date       p11,
                                        double     p12,
                                        Double     p13,
                                        float      p14,
                                        Float      p15,
                                        Integer    p16,
                                        int        p17,
                                        long       p18,
                                        Long       p19,
                                        short      p20,
                                        Short      p21,
                                        String     p22)
   {
      Map<String, Object> res = new LinkedHashMap<>();
      res.put("p1", p1);
      res.put("p2", p2);
      res.put("p3", p3);
      res.put("p4", p4);
      res.put("p5", p5);
      res.put("p6", p6);
      res.put("p7", p7);
      res.put("p8", p8);
      res.put("p9", p9);
      res.put("p10", p10);
      res.put("p11", p11);
      res.put("p12", p12);
      res.put("p13", p13);
      res.put("p14", p14);
      res.put("p15", p15);
      res.put("p16", p16);
      res.put("p17", p17);
      res.put("p18", p18);
      res.put("p19", p19);
      res.put("p20", p20);
      res.put("p21", p21);
      res.put("p22", p22);
      return res;
   }

Request

{
    "request":
    {
        "p1": 12345.6789,
        "p2": 1234567890,
        "p3": null,
        "p4": true,
        "p5": "enhjdmJubQ==",
        "p6": 1,
        "p7": 125,
        "p8": "2022-04-20T12:57:48.323+03:00",
        "p9": "Z",
        "p10": "A",
        "p11": "2022-12-25",
        "p12": 2,
        "p13": 12345.6789,
        "p14": 3.3,
        "p15": 123.456,
        "p16": 123,
        "p18": 123456,
        "p19": null,
        "p20": 12,
        "p21": null,
        "p22": "abcdefgh" 
    }
}

Response

{
    "response": 
    {
        "returnValue": 
        {
            "p1": 12345.6789,
            "p2": 1234567890,
            "p3": null,
            "p4": true,
            "p5": "enhjdmJubQ==",
            "p6": 1,
            "p7": 125,
            "p8": "2022-04-20T10:57:48.323+02:00",
            "p9": "Z",
            "p10": "A",
            "p11": "2022-12-25",
            "p12": 2.0,
            "p13": 12345.6789,
            "p14": 3.3,
            "p15": 123.456,
            "p16": 123,
            "p17": 0,
            "p18": 123456,
            "p19": null,
            "p20": 12,
            "p21": null,
            "p22": "abcdefgh" 
        }
    }
}

Legacy 4GL Types

The legacy 4GL types can be used the same way as Java native types. As they are mutable, they can be used for OUTPUT and INPUT-OUTPUT parameters, too. In the example bellow, all parameters are received as INPUT-OUTPUT, and they are changed - this change is reflected back in the response.

Service Definition

   @LegacyService(type = "REST", name = "test legacy types", path = "/legacyTypes", verb = "POST", parameters = 
   {
      @LegacyServiceParameter(ordinal = 1, target = "json.object['response'].['p1']", source = "json.object['request'].['p1']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 2, target = "json.object['response'].['p2']", source = "json.object['request'].['p2']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 3, target = "json.object['response'].['p3']", source = "json.object['request'].['p3']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 4, target = "json.object['response'].['p4']", source = "json.object['request'].['p4']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 5, target = "json.object['response'].['p5']", source = "json.object['request'].['p5']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 6, target = "json.object['response'].['p6']", source = "json.object['request'].['p6']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 7, target = "json.object['response'].['p7']", source = "json.object['request'].['p7']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 8, target = "json.object['response'].['p8']", source = "json.object['request'].['p8']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 9, target = "json.object['response'].['p9']", source = "json.object['request'].['p9']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 10, target = "json.object['response'].['p10']", source = "json.object['request'].['p10']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 11, target = "json.object['response'].['p11']", source = "json.object['request'].['p11']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 12, target = "json.object['response'].['p12']", source = "json.object['request'].['p12']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 13, target = "json.object['response'].['p13']", source = "json.object['request'].['p13']", input = true, output = true),
      @LegacyServiceParameter(ordinal = 14, target = "json.object['response'].['p14']", source = "json.object['request'].['p14']", input = true, output = true),
   })
   public void legacyTypes(blob       p1,
                           clob       p2,
                           character  p3,
                           date       p4,
                           datetime   p5,
                           datetimetz p6,
                           decimal    p7,
                           integer    p8,
                           int64      p9,
                           logical    p10,
                           longchar   p11,
                           raw        p12,
                           recid      p13,
                           rowid      p14)
   {
      if (!p1.isUnknown())
      {
         int length = BinaryData.length(p1).intValue();
         byte[] p1bytes = new byte[length * 2];
         System.arraycopy(p1.asByteArray(0, length), 0, p1bytes, 0, length);
         System.arraycopy(p1.asByteArray(0, length), 0, p1bytes, length, length);
         p1.assign(new blob(p1bytes));
      }

      p2.assign(p2.getValue() + p2.getValue());
      p3.assign(p3.getValue() + p3.getValue());
      p4.increment();
      p5.increment();
      p6.increment();
      p7.increment();
      p8.increment();
      p9.increment();
      p10.assign(!p10.getValue());
      p11.assign(p11.getValue() + p11.getValue());

      if (!p12.isUnknown() && BinaryData.length(p12).intValue() > 0)
      {
         int length = BinaryData.length(p12).intValue();
         byte[] p12bytes = new byte[length * 2];
         System.arraycopy(p12.getByteArray(), 0, p12bytes, 0, length);
         System.arraycopy(p12.getByteArray(), 0, p12bytes, length, length);
         p12.assign(p12bytes);
      }

      p13.increment();
      p14.setUnknown();
   }

Request

{
    "request": 
    {
        "p1": "enhjdmJubQ==",
        "p2": "abc",
        "p3": "def",
        "p4": "2022-01-03",
        "p5": "2022-01-04T01:02:03.005",
        "p6": "2022-01-05T02:03:04.006+02:00",
        "p7": 12345.789,
        "p8": 123456789,
        "p9": 123456789,
        "p10": true,
        "p11": "zxc",
        "p12": "111222333",
        "p13": 1234,
        "p14": 5678
    }
}

Response

{
    "response": 
    {
        "p1": "enhjdmJubXp4Y3Zibm0=",
        "p2": "abcabc",
        "p3": "defdef",
        "p4": "2022-01-04",
        "p5": "2022-01-05T01:02:03.005",
        "p6": "2022-01-06T02:03:04.006+02:00",
        "p7": 12346.789,
        "p8": 123456790,
        "p9": 123456790,
        "p10": false,
        "p11": "zxczxc",
        "p12": "1112223311122233",
        "p13": 1235,
        "p14": null
    }
}

Java Arrays

Java arrays can be used as INPUT, OUTPUT or INPUT-OUTPUT parameters. The example bellow copies the input p1 argument to p2 array, after it multiplies all elements with 100. For the p3 argument, which is INPUT-OUTPUT, all its content is doubled.

The return is a 'object' array, so elements can be of different type, and the serialization is done by each element's type.

Service Definition

   @LegacyService(type = "REST", name = "test Java arrays", path = "/array", verb = "POST", parameters = 
   {
      @LegacyServiceParameter(ordinal = 0, target = "json.object['response'].['returnValue']", returnValue = true),
      @LegacyServiceParameter(ordinal = 1, source = "json.object['request'].['p1']", input = true, extent = 3),
      @LegacyServiceParameter(ordinal = 2, target = "json.object['response'].['p2']", output = true, extent = 3),
      @LegacyServiceParameter(ordinal = 3, target = "json.object['response'].['p3']", source = "json.object['request'].['p3']", input = true, output = true),
   })
   public Object[] arrays(int[] arr1, int[] arr2, String[] arr3)
   {
      for (int i = 0; i < arr1.length; i++)
      {
         arr1[i] *= 100;
      }

      System.arraycopy(arr1, 0, arr2, 0, arr1.length);

      for (int i = 0; i < arr3.length; i++)
      {
         arr3[i] += arr3[i];
      }

      Object[] ret = new Object[4];
      ret[0] = 1;
      ret[1] = "abc";
      ret[2] = new byte[] {1, 2, 3, 4};
      ret[3] = new date(12, 25, 2021);

      return ret;
   }

Request

{
    "request": 
    {
        "p1": [ 1, 2, 3 ],
        "p3": [ "a", "b", "c", "d"]
    }
}

Response

{
    "response": 
    {
        "returnValue": [ 1, "abc", "AQIDBA==", "2021-12-25" ],
        "p2": [ 100, 200, 300 ],
        "p3": [ "aa", "bb", "cc", "dd" ]
    }
}

Legacy Records

Records of any converted legacy table (permanent or temporary) can be used as parameters, either standalone, in an array, collection, etc. The example bellow copies the record from argument p1 to argument p2, while it also alters argument p3, to show how all INPUT, OUTPUT, and INPUT-OUTPUT modes work.

Keep in mind that a record received as an argument to a REST API call, should not be attached to a database session or otherwise persisted; instead, use the FWD ORM framework to create open a transaction, create a new record, populate it from the argument and after that persist it as needed.

Service Definition

   @LegacyService(type = "REST", name = "test records", path = "/record", verb = "POST", parameters = 
   {
      @LegacyServiceParameter(ordinal = 1, source = "json.object['request'].['p1']", input = true),
      @LegacyServiceParameter(ordinal = 2, target = "json.object['response'].['p2']", output = true),
      @LegacyServiceParameter(ordinal = 3, target = "json.object['response'].['p3']", source = "json.object['request'].['p3']", input = true, output = true),
   })
   public void records(Item p1, Item p2, Item p3)
   {
      p2.setItemname(p1.getItemname());
      p2.setItemnum(p1.getItemnum());
      p2.setPrice(p1.getPrice());
      p2.setWeight(p1.getWeight());

      p3.setItemname(new character("this has changed"));
      p3.setPrice(new integer(12345));
   }

Request

{
    "request": 
    {
        "p1": 
        {
            "itemName": "copied to p2",
            "itemNum": 1,
            "price": 2,
            "weight": 3
        },
       "p3": 
       {
            "itemName": "this is new",
            "itemNum": 1234,
            "price": 9999,
            "weight": 12
        }
    }
}

Response

{
    "response": 
    {
        "p2": 
        {
            "itemName": "copied to p2",
            "itemNum": 1,
            "price": 2,
            "weight": 3
        },
        "p3": 
        {
            "itemName": "this has changed",
            "itemNum": 1234,
            "price": 12345,
            "weight": 12
        }
    }
}

POJOs

POJOs (Plain Old Java Objects) are classes which implement the com.goldencode.p2j.rest.serializers.PojoType interface and have a public getter and a setter for each non-transient field which needs to be serialized. In the example bellow, there is a Country POJO with this structure:

public class Country
implements PojoType
{
   private String name;

   private String code;

   private int idx = 0;

   private boolean visit = false;

   public String getName()
   {
      return name;
   }

   public String getCode()
   {
      return code;
   }

   public void setName(String name)
   {
      this.name = name;
   }

   public void setCode(String code)
   {
      this.code = code;
   }

   public int getIdx()
   {
      return idx;
   }

   public void setIdx(int idx)
   {
      this.idx = idx;
   }

   public boolean isVisit()
   {
      return visit;
   }

   public void setVisit(boolean visit)
   {
      this.visit = visit;
   }
}

and the REST service (as in the previous examples), copies parameter p1 to parameter p2, while altering parameter p3, to show the INPUT, OUTPUT and INPUT-OUTPUT modes:

Service Definition

   @LegacyService(type = "REST", name = "test POJOs", path = "/pojo", verb = "POST", parameters = 
   {
      @LegacyServiceParameter(ordinal = 1, source = "json.object['request'].['p1']", input = true),
      @LegacyServiceParameter(ordinal = 2, target = "json.object['response'].['p2']", output = true),
      @LegacyServiceParameter(ordinal = 3, target = "json.object['response'].['p3']", source = "json.object['request'].['p3']", input = true, output = true),
   })
   public void pojos(Country p1, Country p2, Country p3)
   {
      p2.setCode(p1.getCode());
      p2.setName(p1.getName());
      p2.setIdx(p1.getIdx());
      p2.setVisit(p1.isVisit());

      p3.setName("United States");
      p3.setVisit(true);
   }

Request

{
    "request": 
    {
       "p1": 
       {
            "code": "GB",
            "idx": 2,
            "name": "Great Britain",
            "visit": false
        },
        "p3": 
        {
            "code": "US",
            "idx": 1,
            "name": null,
            "visit": false
        }
    }
}

Response

{
    "response": 
    {
        "p2": 
        {
            "code": "GB",
            "idx": 2,
            "name": "Great Britain",
            "visit": false
        },
        "p3": {
            "code": "US",
            "idx": 1,
            "name": "United States",
            "visit": true
        }
    }
}

Java Lists

Any Java collection can be used as a parameter. The serialization will always be done using JSON arrays, and each element will be serialized using its type. The example bellow copies argument p1 to argument p2, and alters p3:

Service Definition

   @LegacyService(type = "REST", name = "test lists", path = "/list", verb = "POST", parameters = 
   {
      @LegacyServiceParameter(ordinal = 1, source = "json.object['request'].['p1']", input = true),
      @LegacyServiceParameter(ordinal = 2, target = "json.object['response'].['p2']", output = true),
      @LegacyServiceParameter(ordinal = 3, target = "json.object['response'].['p3']", source = "json.object['request'].['p3']", input = true, output = true),
   })
   public void lists(List<Integer> p1, List<Integer> p2, List<Integer> p3)
   {
      p2.addAll(p1);

      if (!p3.isEmpty())
      {
         p3.remove(0);
      }
      p3.add(12345);
   }

Request

{
    "request": 
    {
        "p1": [ 1, 2, 3, 4 ],
       "p3": [ 9999, 8888 ]
    }
}

Response

{
    "response": 
    {
        "p2": [ 1, 2, 3, 4 ],
        "p3": [ 8888, 12345 ]
    }
}

Java Maps

Any Java collection can be used as a parameter. The serialization will always be done using JSON objects, and each key and value will be serialized using its type. The example bellow copies argument p1 to argument p2, and alters p3:

Service Definition

   @LegacyService(type = "REST", name = "test maps", path = "/map", verb = "POST", parameters = 
   {
      @LegacyServiceParameter(ordinal = 1, source = "json.object['request'].['p1']", input = true),
      @LegacyServiceParameter(ordinal = 2, target = "json.object['response'].['p2']", output = true),
      @LegacyServiceParameter(ordinal = 3, target = "json.object['response'].['p3']", source = "json.object['request'].['p3']", input = true, output = true),
   })
   public void maps(LinkedHashMap p1, LinkedHashMap p2, LinkedHashMap p3)
   {
      p2.putAll(p1);

      if (!p3.isEmpty())
      {
         Iterator iter = p3.keySet().iterator();
         iter.next();
         iter.remove();
      }
      p3.put("something", "added");
   }

Request

{
    "request": 
    {
        "p1": 
        {
            "v1" : 1,
            "v2": 2
        },
        "p3": 
        {
            "this will be removed" : 999,
            "this remains": 1234
        }
    }
}

Response

{
    "response": 
    {
        "p2": 
        {
            "v1": 1,
            "v2": 2
        },
        "p3": 
        {
            "this remains": 1234,
            "something": "added" 
        }
    }
}

Java Sets

Any Java set can be used as a parameter. The serialization will always be done using JSON arrays, and each element will be serialized using its type. The example bellow copies argument p1 to argument p2, and alters p3:

Service Definition

   @LegacyService(type = "REST", name = "test sets", path = "/set", verb = "POST", parameters = 
   {
         @LegacyServiceParameter(ordinal = 1, source = "json.object['request'].['p1']", input = true),
         @LegacyServiceParameter(ordinal = 2, target = "json.object['response'].['p2']", output = true),
         @LegacyServiceParameter(ordinal = 3, target = "json.object['response'].['p3']", source = "json.object['request'].['p3']", input = true, output = true),
   })
   public void sets(LinkedHashSet p1, LinkedHashSet p2, LinkedHashSet p3)
   {
      p2.addAll(p1);

      if (!p3.isEmpty())
      {
         Iterator iter = p3.iterator();
         iter.next();
         iter.remove();
      }
      p3.add("something added");
   }

Request

{
    "request": 
    {
       "p1": [ "v1", "v2", 1, 2, 3 ],
       "p3": [ "this will be removed", "this remains" ]
    }
}

Response

{
    "response": 
    {
        "p2": [ "v1", "v2", 1, 2, 3 ],
        "p3": [ "this remains", "something added" ]
    }
}

© 2004-2026 Golden Code Development Corporation. ALL RIGHTS RESERVED.