Java REST Services¶
- 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:Here, we have two classes -
<cfg>
<global>
...
<parameter name="name_map_merge" value="${P2J_HOME}/cfg/name_map_merge.xml" />
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.
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". |
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 |
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. |
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: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. |
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: |
- 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.Stringand thejava.langcounterparts for the Java native types: these instances are immutable, so they should be used only forinput. By default, they will be initialized tonull, if there is no argument corresponding for this parameter.- Java collections are supported, with explicit serializers added for
Deque,SortedSet,QueueandSet. The serializer will be chosen based on the closest match: if you have aSortedSetinstance, then theSortedSetserializer will be used. If there is no match at all, it will default to a standardCollectionserializer.MapandSortedMapalso 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 |
- they all must be placed in the same package, the one defined in the
rest/custom-java-serializersnode indirectory.xml. - must extend the
com.goldencode.p2j.rest.serializers.JavaTypeSerializerclass 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: Foois the type for which a custom serializer is addedinitializeis 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.newRecordto create a new instance of this record. JavaTypeSerializer.newInstance(Class extends T, Supplier extends T>, to create a new instance of type T, or if the type is abstract, use the supplier function to create one.
- if this is a DMO interface, then use
parse(T arg, String sval), used to parse the JSON string representation (insval) and initialize the argument. Theargparameter is the one returned byinitialize, but is not mandatory to return it - another instance can be returned. There are helper APIs inJavaTypeSerializer, 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.
- load the JSON object in a map, via
JsonNode toJson(T val), used to serialize a certain instance to JSON.JavaTypeSerializerprovides 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.