diff --git a/cf/samples/java-samples/commons/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/cf/samples/java-samples/commons/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java new file mode 100644 index 0000000..baa56ed --- /dev/null +++ b/cf/samples/java-samples/commons/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.typeadapters; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * Adapts values whose runtime type may differ from their declaration type. This + * is necessary when a field's type is not the same type that GSON should create + * when deserializing that field. For example, consider these types: + *
   {@code
+ *   abstract class Shape {
+ *     int x;
+ *     int y;
+ *   }
+ *   class Circle extends Shape {
+ *     int radius;
+ *   }
+ *   class Rectangle extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Diamond extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Drawing {
+ *     Shape bottomShape;
+ *     Shape topShape;
+ *   }
+ * }
+ *

Without additional type information, the serialized JSON is ambiguous. Is + * the bottom shape in this drawing a rectangle or a diamond?

   {@code
+ *   {
+ *     "bottomShape": {
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * This class addresses this problem by adding type information to the + * serialized JSON and honoring that type information when the JSON is + * deserialized:
   {@code
+ *   {
+ *     "bottomShape": {
+ *       "type": "Diamond",
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "type": "Circle",
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * Both the type field name ({@code "type"}) and the type labels ({@code + * "Rectangle"}) are configurable. + * + *

Registering Types

+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field + * name to the {@link #of} factory method. If you don't supply an explicit type + * field name, {@code "type"} will be used.
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory
+ *       = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ * Next register all of your subtypes. Every subtype must be explicitly + * registered. This protects your application from injection attacks. If you + * don't supply an explicit type label, the type's simple name will be used. + *
   {@code
+ *   shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
+ *   shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
+ *   shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
+ * }
+ * Finally, register the type adapter factory in your application's GSON builder: + *
   {@code
+ *   Gson gson = new GsonBuilder()
+ *       .registerTypeAdapterFactory(shapeAdapterFactory)
+ *       .create();
+ * }
+ * Like {@code GsonBuilder}, this API supports chaining:
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory = RuntimeTypeAdapterFactory.of(Shape.class)
+ *       .registerSubtype(Rectangle.class)
+ *       .registerSubtype(Circle.class)
+ *       .registerSubtype(Diamond.class);
+ * }
+ */ +public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap>(); + private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); + private final boolean maintainType; + + private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName, boolean maintainType) { + if (typeFieldName == null || baseType == null) { + throw new NullPointerException(); + } + this.baseType = baseType; + this.typeFieldName = typeFieldName; + this.maintainType = maintainType; + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + * {@code maintainType} flag decide if the type will be stored in pojo or not. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName, maintainType); + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName, false); + } + + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as + * the type field name. + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory(baseType, "type", false); + } + + /** + * Registers {@code type} identified by {@code label}. Labels are case + * sensitive. + * + * @throws IllegalArgumentException if either {@code type} or {@code label} + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { + if (type == null || label == null) { + throw new NullPointerException(); + } + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { + throw new IllegalArgumentException("types and labels must be unique"); + } + labelToSubtype.put(label, type); + subtypeToLabel.put(type, label); + return this; + } + + /** + * Registers {@code type} identified by its {@link Class#getSimpleName simple + * name}. Labels are case sensitive. + * + * @throws IllegalArgumentException if either {@code type} or its simple name + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } + + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() != baseType) { + return null; + } + + final Map> labelToDelegate + = new LinkedHashMap>(); + final Map, TypeAdapter> subtypeToDelegate + = new LinkedHashMap, TypeAdapter>(); + for (Map.Entry> entry : labelToSubtype.entrySet()) { + TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); + labelToDelegate.put(entry.getKey(), delegate); + subtypeToDelegate.put(entry.getValue(), delegate); + } + + return new TypeAdapter() { + @Override public R read(JsonReader in) throws IOException { + JsonElement jsonElement = Streams.parse(in); + JsonElement labelJsonElement; + if (maintainType) { + labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); + } else { + labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + } + + if (labelJsonElement == null) { + throw new JsonParseException("cannot deserialize " + baseType + + " because it does not define a field named " + typeFieldName); + } + String label = labelJsonElement.getAsString(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); + if (delegate == null) { + throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + + label + "; did you forget to register a subtype?"); + } + return delegate.fromJsonTree(jsonElement); + } + + @Override public void write(JsonWriter out, R value) throws IOException { + Class srcType = value.getClass(); + String label = subtypeToLabel.get(srcType); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); + if (delegate == null) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + "; did you forget to register a subtype?"); + } + JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); + + if (maintainType) { + Streams.write(jsonObject, out); + return; + } + + JsonObject clone = new JsonObject(); + + if (jsonObject.has(typeFieldName)) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + " because it already defines a field named " + typeFieldName); + } + //clone.add(typeFieldName, new JsonPrimitive(label)); + + for (Map.Entry e : jsonObject.entrySet()) { + clone.add(e.getKey(), e.getValue()); + } + Streams.write(clone, out); + } + }.nullSafe(); + } +} \ No newline at end of file diff --git a/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloud.java b/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloud.java index 0ab8020..91128a3 100644 --- a/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloud.java +++ b/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloud.java @@ -2,7 +2,7 @@ import java.io.IOException; -import commons.model.gateway.Measure; +import commons.model.Measure; /** * An abstraction over Cloud Gateways. @@ -13,7 +13,8 @@ public void connect(String host) throws IOException; public void disconnect(); - + + @SuppressWarnings("rawtypes") public void sendMeasure(Measure measure) throws IOException; diff --git a/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloudHttp.java b/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloudHttp.java index fcb21fa..b6f2c21 100644 --- a/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloudHttp.java +++ b/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloudHttp.java @@ -10,7 +10,7 @@ import commons.connectivity.HttpClient; import commons.model.Device; import commons.model.gateway.Command; -import commons.model.gateway.Measure; +import commons.model.Measure; import commons.utils.Console; public class GatewayCloudHttp @@ -50,6 +50,7 @@ public void disconnect() { httpClient.disconnect(); } + @SuppressWarnings("rawtypes") @Override public void sendMeasure(Measure measure) throws IOException { diff --git a/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloudMqtt.java b/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloudMqtt.java index 1da401b..78777a4 100644 --- a/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloudMqtt.java +++ b/cf/samples/java-samples/commons/src/main/java/commons/api/GatewayCloudMqtt.java @@ -9,7 +9,7 @@ import commons.connectivity.MqttClient; import commons.connectivity.MqttMessageListener; import commons.model.Device; -import commons.model.gateway.Measure; +import commons.model.Measure; import commons.utils.Console; public class GatewayCloudMqtt @@ -49,6 +49,7 @@ public void disconnect() { mqttClient.disconnect(); } + @SuppressWarnings("rawtypes") @Override public void sendMeasure(Measure measure) throws IOException { diff --git a/cf/samples/java-samples/commons/src/main/java/commons/connectivity/AbstractClient.java b/cf/samples/java-samples/commons/src/main/java/commons/connectivity/AbstractClient.java index d48f417..57a5a5f 100644 --- a/cf/samples/java-samples/commons/src/main/java/commons/connectivity/AbstractClient.java +++ b/cf/samples/java-samples/commons/src/main/java/commons/connectivity/AbstractClient.java @@ -3,6 +3,12 @@ import java.io.IOException; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; + +import commons.model.Measure; +import commons.model.gateway.JSONMeasure; +import commons.model.gateway.StringArrayMeasure; /** * An abstraction over connectivity clients. @@ -11,8 +17,16 @@ public abstract class AbstractClient { protected Gson jsonParser; + @SuppressWarnings("rawtypes") public AbstractClient() { - jsonParser = new Gson(); + RuntimeTypeAdapterFactory runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory + .of(Measure.class) + .registerSubtype(StringArrayMeasure.class) + .registerSubtype(JSONMeasure.class); + + jsonParser = new GsonBuilder() + .registerTypeAdapterFactory(runtimeTypeAdapterFactory) + .create(); } public abstract void connect(String serverUri) diff --git a/cf/samples/java-samples/commons/src/main/java/commons/model/Measure.java b/cf/samples/java-samples/commons/src/main/java/commons/model/Measure.java index 6a24af8..05c4097 100644 --- a/cf/samples/java-samples/commons/src/main/java/commons/model/Measure.java +++ b/cf/samples/java-samples/commons/src/main/java/commons/model/Measure.java @@ -1,5 +1,17 @@ package commons.model; -public class Measure { +public interface Measure { + public String getSensorAlternateId(); + public String getSensorTypeAlternateId(); + public String getCapabilityAlternateId(); + public String getTimestamp(); + public T getMeasures(); + + public void setSensorAlternateId(String sensorAlternateId); + public void setSensorTypeAlternateId(String sensorTypeAlternateId); + public void setCapabilityAlternateId(String capabilityAlternateId); + public void setTimestamp(String timestamp); + public void setMeasures(T measures); + } diff --git a/cf/samples/java-samples/commons/src/main/java/commons/model/gateway/JSONMeasure.java b/cf/samples/java-samples/commons/src/main/java/commons/model/gateway/JSONMeasure.java new file mode 100644 index 0000000..388cb4d --- /dev/null +++ b/cf/samples/java-samples/commons/src/main/java/commons/model/gateway/JSONMeasure.java @@ -0,0 +1,58 @@ +package commons.model.gateway; + +import com.google.gson.JsonArray; + +public class JSONMeasure implements commons.model.Measure { + +private String sensorAlternateId; + + private String sensorTypeAlternateId; + + private String capabilityAlternateId; + + private String timestamp; + + private JsonArray measures; + + public String getSensorAlternateId() { + return sensorAlternateId; + } + + public void setSensorAlternateId(String sensorAlternateId) { + this.sensorAlternateId = sensorAlternateId; + } + + public String getSensorTypeAlternateId() { + return sensorTypeAlternateId; + } + + public void setSensorTypeAlternateId(String sensorTypeAlternateId) { + this.sensorAlternateId = sensorTypeAlternateId; + } + + public String getCapabilityAlternateId() { + return capabilityAlternateId; + } + + public void setCapabilityAlternateId(String capabilityAlternateId) { + this.capabilityAlternateId = capabilityAlternateId; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + + public JsonArray getMeasures() { + return measures; + } + + public void setMeasures(JsonArray measures) { + this.measures = measures; + } + +} diff --git a/cf/samples/java-samples/commons/src/main/java/commons/model/gateway/Measure.java b/cf/samples/java-samples/commons/src/main/java/commons/model/gateway/StringArrayMeasure.java old mode 100644 new mode 100755 similarity index 52% rename from cf/samples/java-samples/commons/src/main/java/commons/model/gateway/Measure.java rename to cf/samples/java-samples/commons/src/main/java/commons/model/gateway/StringArrayMeasure.java index 11580d3..576f1f1 --- a/cf/samples/java-samples/commons/src/main/java/commons/model/gateway/Measure.java +++ b/cf/samples/java-samples/commons/src/main/java/commons/model/gateway/StringArrayMeasure.java @@ -1,12 +1,32 @@ package commons.model.gateway; -public class Measure { +public class StringArrayMeasure implements commons.model.Measure{ + private String sensorAlternateId; + + private String sensorTypeAlternateId; + private String capabilityAlternateId; + + private String timestamp; + + private Object[][] measures; - private Object[][] measures; + public String getSensorAlternateId() { + return sensorAlternateId; + } - private String sensorAlternateId; + public void setSensorAlternateId(String sensorAlternateId) { + this.sensorAlternateId = sensorAlternateId; + } + + public String getSensorTypeAlternateId() { + return sensorTypeAlternateId; + } + + public void setSensorTypeAlternateId(String sensorTypeAlternateId) { + this.sensorAlternateId = sensorTypeAlternateId; + } public String getCapabilityAlternateId() { return capabilityAlternateId; @@ -16,20 +36,21 @@ public void setCapabilityAlternateId(String capabilityAlternateId) { this.capabilityAlternateId = capabilityAlternateId; } - public Object[][] getMeasures() { - return measures; + public String getTimestamp() { + return timestamp; } - - public void setMeasures(Object[][] measures) { - this.measures = measures; + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; } - public String getSensorAlternateId() { - return sensorAlternateId; + + public Object[][] getMeasures() { + return measures; } - public void setSensorAlternateId(String sensorAlternateId) { - this.sensorAlternateId = sensorAlternateId; + public void setMeasures(Object[][] measures) { + this.measures = measures; } } diff --git a/cf/samples/java-samples/commons/src/main/java/commons/utils/EntityFactory.java b/cf/samples/java-samples/commons/src/main/java/commons/utils/EntityFactory.java index e61d99a..17dea4f 100644 --- a/cf/samples/java-samples/commons/src/main/java/commons/utils/EntityFactory.java +++ b/cf/samples/java-samples/commons/src/main/java/commons/utils/EntityFactory.java @@ -6,6 +6,9 @@ import java.util.Random; import java.util.UUID; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + import commons.model.Capability; import commons.model.CapabilityType; import commons.model.Command; @@ -16,7 +19,8 @@ import commons.model.Sensor; import commons.model.SensorType; import commons.model.SensorTypeCapability; -import commons.model.gateway.Measure; +import commons.model.gateway.JSONMeasure; +import commons.model.gateway.StringArrayMeasure; public class EntityFactory { @@ -41,8 +45,8 @@ public class EntityFactory { private static final String TEMPERATURE_PROPERTY_UOM = "°C"; private static final String LIGHT_PROPERTY_UOM = "Lux"; - public static Measure buildAmbientMeasure(Sensor sensor, Capability capability) { - Measure measure = new Measure(); + public static StringArrayMeasure buildAmbientMeasure(Sensor sensor, Capability capability) { + StringArrayMeasure measure = new StringArrayMeasure(); measure.setCapabilityAlternateId(capability.getAlternateId()); measure.setSensorAlternateId(sensor.getAlternateId()); @@ -51,6 +55,22 @@ public static Measure buildAmbientMeasure(Sensor sensor, Capability capability) return measure; } + + public static JSONMeasure buildAmbientMeasure_v2(Sensor sensor, Capability capability) { + JSONMeasure measure = new JSONMeasure(); + + measure.setCapabilityAlternateId(capability.getAlternateId()); + measure.setSensorAlternateId(sensor.getAlternateId()); + JsonArray measureArray = new JsonArray(); + JsonObject measureObject = new JsonObject(); + measureObject.addProperty(HUMIDITY_PROPERTY_NAME, buildHumidityPercentage()); + measureObject.addProperty(TEMPERATURE_PROPERTY_NAME, buildDegreesCelsius()); + measureObject.addProperty(LIGHT_PROPERTY_NAME, buildLightIlluminance()); + measureArray.add(measureObject); + measure.setMeasures(measureArray); + + return measure; + } public static Command buildSwitchCommand(Sensor sensor, Capability capability) { Command command = new Command(); diff --git a/cf/samples/java-samples/send-measure/src/main/java/sample/SampleApp.java b/cf/samples/java-samples/send-measure/src/main/java/sample/SampleApp.java index 1769eae..cd7d371 100644 --- a/cf/samples/java-samples/send-measure/src/main/java/sample/SampleApp.java +++ b/cf/samples/java-samples/send-measure/src/main/java/sample/SampleApp.java @@ -20,7 +20,8 @@ import commons.model.GatewayProtocol; import commons.model.Sensor; import commons.model.SensorType; -import commons.model.gateway.Measure; +import commons.model.gateway.JSONMeasure; +import commons.model.gateway.StringArrayMeasure; import commons.utils.Console; import commons.utils.EntityFactory; import commons.utils.SecurityUtil; @@ -113,10 +114,12 @@ private void sendAmbientMeasures(final Sensor sensor, final Capability capabilit @Override public void run() { - Measure measure = EntityFactory.buildAmbientMeasure(sensor, capability); + StringArrayMeasure measure = EntityFactory.buildAmbientMeasure(sensor, capability); + JSONMeasure measure_v2 = EntityFactory.buildAmbientMeasure_v2(sensor, capability); try { gatewayCloud.sendMeasure(measure); + gatewayCloud.sendMeasure(measure_v2); } catch (IOException e) { Console.printError(e.getMessage());