Skip to content

Commit e32e404

Browse files
mbwhitejt-nti
authored andcommitted
[FABCJ-183] Extra properties appear in JSON from Java Objects
- Objects returned from a transaction function now will not have any spurious properties in them - In testing located handling of array types was wrong; provided improved handling for those - Updating number of original tests for different cases Signed-off-by: Matthew B. White <[email protected]>
1 parent 7f3c4e3 commit e32e404

File tree

11 files changed

+353
-22
lines changed

11 files changed

+353
-22
lines changed

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/JSONTransactionSerializer.java

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.nio.charset.StandardCharsets;
1515
import java.util.Iterator;
1616
import java.util.Map;
17+
import java.util.Set;
1718

1819
import org.hyperledger.fabric.Logger;
1920
import org.hyperledger.fabric.contract.ContractRuntimeException;
@@ -57,7 +58,7 @@ public byte[] toBuffer(Object value, TypeSchema ts) {
5758
if (type != null) {
5859
switch (type) {
5960
case "array":
60-
JSONArray array = new JSONArray(value);
61+
JSONArray array = normalizeArray(new JSONArray(value),ts);
6162
buffer = array.toString().getBytes(UTF_8);
6263
break;
6364
case "string":
@@ -70,13 +71,73 @@ public byte[] toBuffer(Object value, TypeSchema ts) {
7071
buffer = (value).toString().getBytes(UTF_8);
7172
}
7273
} else {
73-
JSONObject obj = new JSONObject(value);
74+
// at this point we can assert that the value is
75+
// representing a complex data type
76+
// so we can get this from
77+
// the type registry, and get the list of propertyNames
78+
// it should have
79+
DataTypeDefinition dtd = this.typeRegistry.getDataType(ts);
80+
Set<String> keySet = dtd.getProperties().keySet();
81+
String[] propNames = keySet.toArray(new String[keySet.size()]);
82+
83+
// Note: whilst the current JSON library does pretty much
84+
// everything is required, this part is hard.
85+
// we want to create a JSON Object based on the value,
86+
// with certain property names.
87+
88+
// Based on the constructors available we need to have a two
89+
// step process, create a JSON Object, then create the object
90+
// we really want based on the propNames
91+
JSONObject obj = new JSONObject(new JSONObject(value),propNames);
7492
buffer = obj.toString().getBytes(UTF_8);
7593
}
7694
}
7795
return buffer;
7896
}
7997

98+
/**
99+
* We need to take the JSON array, and if there are complex datatypes within it
100+
* ensure that they don't get spurious JSON properties appearing
101+
*
102+
* This method needs to be general so has to copy with nested arrays
103+
* and with primitive and Object types
104+
*/
105+
private JSONArray normalizeArray(JSONArray jsonArray, TypeSchema ts){
106+
JSONArray normalizedArray;
107+
108+
// Need to work with what type of array this is
109+
TypeSchema items = ts.getItems();
110+
String type = items.getType();
111+
112+
if (type != null && type != "array" ){
113+
// primitive - can return this directly
114+
normalizedArray = jsonArray;
115+
} else if ( type != null && type == "array") {
116+
// nested arrays, get the type of what it makes up
117+
// Need to loop over all elements and normalize each one
118+
normalizedArray = new JSONArray();
119+
for (int i=0; i<jsonArray.length(); i++){
120+
normalizedArray.put(i,normalizeArray(jsonArray.getJSONArray(i),items));
121+
}
122+
} else {
123+
// get the permitted propeties in the type,
124+
// then loop over the array and ensure they are correct
125+
DataTypeDefinition dtd = this.typeRegistry.getDataType(items);
126+
Set<String> keySet = dtd.getProperties().keySet();
127+
String[] propNames = keySet.toArray(new String[keySet.size()]);
128+
129+
normalizedArray = new JSONArray();
130+
// array of objects
131+
// iterate over said array
132+
for (int i=0; i<jsonArray.length(); i++){
133+
JSONObject obj = new JSONObject(jsonArray.getJSONObject(i),propNames);
134+
normalizedArray.put(i,obj);
135+
}
136+
137+
}
138+
return normalizedArray;
139+
}
140+
80141
/**
81142
* Take the byte buffer and return the object as required
82143
*
@@ -103,6 +164,46 @@ public Object fromBuffer(byte[] buffer, TypeSchema ts) {
103164
}
104165
}
105166

167+
/** We need to be able to map between the primative class types
168+
* and the object variants. In the case where this is needed
169+
* Java auto-boxing doesn't actually help.
170+
*
171+
* For other types the parameter is passed directly back
172+
*
173+
* @param primitive class for the primitive
174+
* @return Class for the Object variant
175+
*/
176+
private Class<?> mapPrimitive(Class<?> primitive){
177+
String primitiveType;
178+
boolean isArray = primitive.isArray();
179+
if (isArray){
180+
primitiveType = primitive.getComponentType().getName();
181+
} else {
182+
primitiveType = primitive.getName();
183+
}
184+
185+
switch (primitiveType) {
186+
case "int":
187+
return isArray ? Integer[].class : Integer.class;
188+
case "long":
189+
return isArray ? Long[].class: Long.class;
190+
case "float":
191+
return isArray ? Float[].class:Float.class;
192+
case "double":
193+
return isArray ? Double[].class:Double.class;
194+
case "short":
195+
return isArray ? Short[].class:Short.class;
196+
case "byte":
197+
return isArray ? Byte[].class:Byte.class;
198+
case "char":
199+
return isArray ? Character[].class:Character.class;
200+
case "boolean":
201+
return isArray ? Boolean[].class:Boolean.class;
202+
default:
203+
return primitive;
204+
}
205+
}
206+
106207
/*
107208
* Internal method to do the conversion
108209
*/
@@ -124,8 +225,14 @@ private Object _convert(String stringData, TypeSchema ts)
124225
String intFormat = ts.getFormat();
125226
if (intFormat.contentEquals("int32")) {
126227
value = Integer.parseInt(stringData);
127-
} else {
228+
} else if (intFormat.contentEquals("int8")) {
229+
value = Byte.parseByte(stringData);
230+
} else if (intFormat.contentEquals("int16")){
231+
value = Short.parseShort(stringData);
232+
} else if (intFormat.contentEquals("int64")){
128233
value = Long.parseLong(stringData);
234+
} else {
235+
throw new RuntimeException("Unknown format for integer "+intFormat);
129236
}
130237
} else if (type.contentEquals("number")) {
131238
String numFormat = ts.getFormat();
@@ -141,17 +248,27 @@ private Object _convert(String stringData, TypeSchema ts)
141248
} else if (type.contentEquals("array")) {
142249
JSONArray jsonArray = new JSONArray(stringData);
143250
TypeSchema itemSchema = ts.getItems();
144-
Object[] data = (Object[]) Array.newInstance(itemSchema.getTypeClass(this.typeRegistry),
251+
252+
// note here that the type has to be converted in the case of primitives
253+
Object[] data = (Object[]) Array.newInstance(mapPrimitive(itemSchema.getTypeClass(this.typeRegistry)),
145254
jsonArray.length());
146255
for (int i = 0; i < jsonArray.length(); i++) {
147-
data[i] = _convert(jsonArray.get(i).toString(), itemSchema);
256+
Object convertedData = _convert(jsonArray.get(i).toString(), itemSchema);
257+
data[i] = convertedData;
148258
}
149259
value = data;
150260

151261
}
152262
return value;
153263
}
154264

265+
/** Create new instance of the specificied object from the supplied JSON String
266+
*
267+
* @param format Details of the format needed
268+
* @param jsonString JSON string
269+
* @param ts TypeSchema
270+
* @return new object
271+
*/
155272
Object createComponentInstance(String format, String jsonString, TypeSchema ts) {
156273

157274
DataTypeDefinition dtd = this.typeRegistry.getDataType(format);

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractExecutionService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public Chaincode.Response executeRequest(TxFunction txFn, InvocationRequest req,
5050

5151
final List<Object> args = convertArgs(req.getArgs(), txFn);
5252
args.add(0, context); // force context into 1st position, other elements move up
53-
53+
5454
contractObject.beforeTransaction(context);
5555
Object value = rd.getMethod().invoke(contractObject, args.toArray());
5656
contractObject.afterTransaction(context, value);

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/metadata/TypeSchema.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,37 @@ public Class<?> getTypeClass(TypeRegistry typeRegistry) {
100100
if (type.contentEquals("string")) {
101101
clz = String.class;
102102
} else if (type.contentEquals("integer")) {
103-
clz = int.class;
103+
// need to check the format
104+
String format = getFormat();
105+
switch(format) {
106+
case "int8":
107+
clz = byte.class;
108+
break;
109+
case "int16":
110+
clz = short.class;
111+
break;
112+
case "int32":
113+
clz = int.class;
114+
break;
115+
case "int64":
116+
clz = long.class;
117+
break;
118+
default:
119+
throw new RuntimeException("Unkown format for integer of "+format);
120+
}
121+
} else if (type.contentEquals("number")) {
122+
// need to check the format
123+
String format = getFormat();
124+
switch(format) {
125+
case "double":
126+
clz = double.class;
127+
break;
128+
case "float":
129+
clz = float.class;
130+
break;
131+
default:
132+
throw new RuntimeException("Unkown format for number of "+format);
133+
}
104134
} else if (type.contentEquals("boolean")) {
105135
clz = boolean.class;
106136
} else if (type.contentEquals("object")) {
@@ -132,8 +162,17 @@ public static TypeSchema typeConvert(Class<?> clz) {
132162
if (clz.isArray()) {
133163
returnschema.put("type", "array");
134164
schema = new TypeSchema();
135-
returnschema.put("items", schema);
136-
className = className.substring(0, className.length() - 2);
165+
166+
// double check the componentType
167+
Class<?> componentClass = clz.getComponentType();
168+
if (componentClass.isArray()){
169+
// nested arrays
170+
returnschema.put("items",TypeSchema.typeConvert(componentClass));
171+
} else {
172+
returnschema.put("items", schema);
173+
}
174+
175+
className = componentClass.getTypeName();
137176
} else {
138177
schema = returnschema;
139178
}
@@ -177,7 +216,6 @@ public static TypeSchema typeConvert(Class<?> clz) {
177216
schema.put("type", "boolean");
178217
break;
179218
default:
180-
181219
schema.put("$ref", "#/components/schemas/" + className.substring(className.lastIndexOf('.') + 1));
182220
}
183221

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/DataTypeDefinition.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.hyperledger.fabric.contract.routing;
77

88
import java.util.Map;
9+
import org.hyperledger.fabric.contract.metadata.TypeSchema;
910

1011
public interface DataTypeDefinition {
1112

@@ -15,5 +16,7 @@ public interface DataTypeDefinition {
1516

1617
String getSimpleName();
1718

18-
Class<?> getTypeClass();
19+
Class<?> getTypeClass();
20+
21+
TypeSchema getSchema();
1922
}

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TypeRegistry.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
package org.hyperledger.fabric.contract.routing;
77

88
import java.util.Collection;
9-
109
import org.hyperledger.fabric.contract.routing.impl.TypeRegistryImpl;
10+
import org.hyperledger.fabric.contract.metadata.TypeSchema;
1111

1212
public interface TypeRegistry {
1313

@@ -21,6 +21,8 @@ static TypeRegistry getRegistry(){
2121

2222
DataTypeDefinition getDataType(String name);
2323

24+
DataTypeDefinition getDataType(TypeSchema schema);
25+
2426
Collection<DataTypeDefinition> getAllDataTypes();
2527

2628
}

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/DataTypeDefinitionImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public Class<?> getTypeClass() {
7070
return this.clazz;
7171
}
7272

73+
public TypeSchema getSchema() {
74+
return TypeSchema.typeConvert(this.clazz);
75+
}
76+
7377
/*
7478
* (non-Javadoc)
7579
*
@@ -102,4 +106,9 @@ public String getSimpleName() {
102106
return simpleName;
103107
}
104108

109+
@Override
110+
public String toString() {
111+
return this.simpleName + " " + properties;
112+
}
113+
105114
}

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/SerializerRegistryImpl.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ public SerializerRegistryImpl() {
4949
*/
5050
public SerializerInterface getSerializer(String name, Serializer.TARGET target) {
5151
String key = name+":"+target;
52-
System.out.println("Getting "+key);
5352
return contents.get(key);
5453
}
5554

@@ -58,7 +57,6 @@ private SerializerInterface add(String name, Serializer.TARGET target, Class<Ser
5857
try{
5958
String key = name+":"+target;
6059
SerializerInterface newObj = clazz.newInstance();
61-
System.out.println("Addding "+key);
6260
this.contents.put(key,newObj);
6361

6462
return newObj;

fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TypeRegistryImpl.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import java.util.Collection;
1010
import java.util.HashMap;
1111
import java.util.Map;
12-
12+
import org.hyperledger.fabric.contract.metadata.TypeSchema;
1313
import org.hyperledger.fabric.contract.routing.DataTypeDefinition;
1414
import org.hyperledger.fabric.contract.routing.TypeRegistry;
1515

@@ -20,7 +20,6 @@
2020
*/
2121
public class TypeRegistryImpl implements TypeRegistry {
2222

23-
2423
private static TypeRegistryImpl singletonInstance;
2524

2625
public static TypeRegistry getInstance(){
@@ -60,4 +59,11 @@ public DataTypeDefinition getDataType(String name) {
6059
return this.components.get(name);
6160
}
6261

62+
@Override
63+
public DataTypeDefinition getDataType(TypeSchema schema) {
64+
String ref = schema.getRef();
65+
String format = ref.substring(ref.lastIndexOf("/") + 1);
66+
return getDataType(format);
67+
}
68+
6369
}

fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/MyType.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,23 @@ public class MyType {
1515
@Property()
1616
private String value;
1717

18+
private String state="";
19+
20+
public final static String STARTED = "STARTED";
21+
public final static String STOPPED = "STOPPED";
22+
23+
public void setState(String state){
24+
this.state = state;
25+
}
26+
27+
public boolean isStarted(){
28+
return state.equals(STARTED);
29+
}
30+
31+
public boolean isStopped(){
32+
return state.equals(STARTED);
33+
}
34+
1835
public MyType setValue(String value) {
1936
this.value = value;
2037
return this;

0 commit comments

Comments
 (0)