diff --git a/platform-api/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImpl.java b/platform-api/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImpl.java
index 87ba257f4..1f0198a28 100644
--- a/platform-api/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImpl.java
+++ b/platform-api/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImpl.java
@@ -17,6 +17,7 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonWriter;
@@ -254,6 +255,10 @@ public static boolean isMap(Class> returnType) {
return returnType.equals(Map.class);
}
+ public static boolean isAny(Class> returnType) {
+ return returnType.equals(JsonElement.class);
+ }
+
/**
* Expands the type and its first generic parameter (which can also have a first generic parameter (...)).
*
diff --git a/platform-api/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImplServerTemplate.java b/platform-api/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImplServerTemplate.java
index 73f195d52..4c972c8e4 100644
--- a/platform-api/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImplServerTemplate.java
+++ b/platform-api/che-core-api-dto/src/main/java/org/eclipse/che/dto/generator/DtoImplServerTemplate.java
@@ -22,6 +22,7 @@
import org.eclipse.che.dto.shared.SerializationIndex;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;
+import com.google.gson.JsonElement;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
@@ -40,6 +41,7 @@ public class DtoImplServerTemplate extends DtoImpl {
private static final String JSON_ARRAY_IMPL = JsonArrayImpl.class.getCanonicalName();
private static final String JSON_MAP_IMPL = JsonStringMapImpl.class.getCanonicalName();
private static final String SERVER_DTO_MARKER = " @" + DTOImpl.class.getCanonicalName() + "(\"server\")\n";
+ private static final String COPY_JSONS_PARAM = "copyJsons";
DtoImplServerTemplate(DtoTemplate template, Class> superInterface) {
super(template, superInterface);
@@ -280,6 +282,10 @@ private void emitDelegateMethods(StringBuilder builder) {
private void emitSerializer(List getters, StringBuilder builder) {
builder.append(" public JsonElement toJsonElement() {\n");
+ // The default toJsonElement() returns JSONs for unsafe use thus 'any' properties should be copied
+ builder.append(" return toJsonElement(true);\n");
+ builder.append(" }\n");
+ builder.append(" public JsonElement toJsonElement(boolean ").append(COPY_JSONS_PARAM).append(") {\n");
if (isCompactJson()) {
builder.append(" JsonArray result = new JsonArray();\n");
for (Method method : getters) {
@@ -296,7 +302,8 @@ private void emitSerializer(List getters, StringBuilder builder) {
builder.append("\n");
builder.append(" @Override\n");
builder.append(" public String toJson() {\n");
- builder.append(" return gson.toJson(toJsonElement());\n");
+ // The default toJson() creates its own JSON for internal printing, thus keeping JSONs values is safe
+ builder.append(" return gson.toJson(toJsonElement(false));\n");
builder.append(" }\n");
builder.append("\n");
builder.append(" @Override\n");
@@ -391,7 +398,7 @@ private void emitSerializerImpl(List expandedTypes, int depth, StringBuild
} else if (getEnclosingTemplate().isDtoInterface(rawClass)) {
builder.append(i).append("JsonElement ").append(outVar).append(" = ").append(depth == 0 ? "this." + inVar : inVar).append(
" == null ? JsonNull.INSTANCE : ((").append(getImplNameForDto((Class>)expandedTypes.get(depth))).append(")")
- .append(depth == 0 ? "this." + inVar : inVar).append(").toJsonElement();\n");
+ .append(depth == 0 ? "this." + inVar : inVar).append(").toJsonElement(").append(COPY_JSONS_PARAM).append(");\n");
} else if (rawClass.equals(String.class)) {
builder.append(i).append("JsonElement ").append(outVar).append(" = (").append(depth == 0 ? "this." + inVar : inVar).append(
" == null) ? JsonNull.INSTANCE : new JsonPrimitive(").append(depth == 0 ? "this." + inVar : inVar).append(");\n");
@@ -413,12 +420,18 @@ private void emitSerializerImpl(List expandedTypes, int depth, StringBuild
|| rawClass == Byte.class) {
builder.append(i).append("JsonElement ").append(outVar).append(depth == 0 ? " = this." + inVar : inVar).append(
" == null ? JsonNull.INSTANCE : new JsonPrimitive(").append(depth == 0 ? "this." + inVar : inVar).append(");\n");
+ } else if (isAny(rawClass)) {
+ // TODO JsonElement.deepCopy() is package-protected, JSONs are serialized to strings then parsed for copying them
+ // outVar = inVar == null ? JsonNull.INSTNACE : (copyJsons ? new JsonParser().parse(inVar) : inVar);
+ builder.append(i).append("JsonElement ").append(outVar).append(depth == 0 ? " = this." + inVar : inVar)
+ .append(" == null ? JsonNull.INSTANCE : (");
+ appendCopyJsonExpression(inVar, builder).append(");\n");
} else {
final Class> dtoImplementation = getEnclosingTemplate().getDtoImplementation(rawClass);
if (dtoImplementation != null) {
builder.append(i).append("JsonElement ").append(outVar).append(" = ").append(depth == 0 ? "this." + inVar : inVar).append(
" == null ? JsonNull.INSTANCE : ((").append(dtoImplementation.getCanonicalName()).append(")")
- .append(depth == 0 ? "this." + inVar : inVar).append(").toJsonElement();\n");
+ .append(depth == 0 ? "this." + inVar : inVar).append(").toJsonElement(").append(COPY_JSONS_PARAM).append(");\n");
} else {
throw new IllegalArgumentException("Unable to generate server implementation for DTO interface " +
getDtoInterface().getCanonicalName() + ". Type " + rawClass +
@@ -441,7 +454,11 @@ private void emitSerializerImpl(List expandedTypes, int depth, StringBuild
/** Generates a static factory method that creates a new instance based on a JsonElement. */
private void emitDeserializer(List getters, StringBuilder builder) {
+ // The default fromJsonElement(json) works in unsafe mode and clones the JSON's for 'any' properties
builder.append(" public static ").append(getImplClassName()).append(" fromJsonElement(JsonElement jsonElem) {\n");
+ builder.append(" return fromJsonElement(jsonElem, true);\n");
+ builder.append(" }\n");
+ builder.append(" public static ").append(getImplClassName()).append(" fromJsonElement(JsonElement jsonElem, boolean ").append(COPY_JSONS_PARAM).append(") {\n");
builder.append(" if (jsonElem == null || jsonElem.isJsonNull()) {\n");
builder.append(" return null;\n");
builder.append(" }\n\n");
@@ -468,7 +485,8 @@ private void emitDeserializerShortcut(StringBuilder builder) {
builder.append(" if (jsonString == null) {\n");
builder.append(" return null;\n");
builder.append(" }\n\n");
- builder.append(" return fromJsonElement(new JsonParser().parse(jsonString));\n");
+ // The default fromJsonElement(json) creates its own JSON thus keeping parts of its as value is OK
+ builder.append(" return fromJsonElement(new JsonParser().parse(jsonString), false);\n");
builder.append(" }\n\n");
}
@@ -562,18 +580,23 @@ private void emitDeserializerImpl(List expandedTypes, int depth, StringBui
} else if (getEnclosingTemplate().isDtoInterface(rawClass)) {
String className = getImplName(rawClass, false);
builder.append(i).append(className).append(" ").append(outVar).append(" = ").append(getImplNameForDto(rawClass))
- .append(".fromJsonElement(").append(inVar).append(");\n");
+ .append(".fromJsonElement(").append(inVar).append(", ").append(COPY_JSONS_PARAM).append(");\n");
} else if (rawClass.isPrimitive()) {
String primitiveName = rawClass.getSimpleName();
String primitiveNameCap = primitiveName.substring(0, 1).toUpperCase() + primitiveName.substring(1);
builder.append(i).append(primitiveName).append(" ").append(outVar).append(" = ").append(inVar).append(
".getAs").append(primitiveNameCap).append("();\n");
+ } else if (isAny(rawClass)) {
+ // TODO JsonElement.deepCopy() is package-protected, JSONs are serialized to strings then parsed for copying them
+ // outVar = copyJsons ? new JsonParser().parse(inVar) : inVar;
+ builder.append(i).append("JsonElement ").append(outVar).append(" = ");
+ appendCopyJsonExpression(inVar, builder).append(";\n");
} else {
final Class> dtoImplementation = getEnclosingTemplate().getDtoImplementation(rawClass);
if (dtoImplementation != null) {
String className = getImplName(rawClass, false);
builder.append(i).append(className).append(" ").append(outVar).append(" = ")
- .append(dtoImplementation.getCanonicalName()).append(".fromJsonElement(").append(inVar).append(");\n");
+ .append(dtoImplementation.getCanonicalName()).append(".fromJsonElement(").append(inVar).append(", ").append(COPY_JSONS_PARAM).append(");\n");
} else {
// Use gson to handle all other types.
String rawClassName = rawClass.getName().replace('$', '.');
@@ -582,6 +605,20 @@ private void emitDeserializerImpl(List expandedTypes, int depth, StringBui
}
}
}
+
+ /**
+ * Append the expression that clones the given JsonElement variable into a new value. If the copyJons run-time
+ * parameter is set to false, then the expression won't perform a clone but instead will reuse the variable by
+ * reference.
+ */
+ private static StringBuilder appendCopyJsonExpression(String inVar, StringBuilder builder) {
+ builder.append(COPY_JSONS_PARAM).append(" ? ");
+ appendNaiveCopyJsonExpression(inVar, builder).append(" : ").append(inVar);
+ return builder;
+ }
+ private static StringBuilder appendNaiveCopyJsonExpression(String inValue, StringBuilder builder) {
+ return builder.append("new JsonParser().parse((").append(inValue).append(").toString())");
+ }
private void emitPreamble(Class> dtoInterface, StringBuilder builder) {
builder.append(SERVER_DTO_MARKER);
@@ -754,6 +791,9 @@ private void emitDeepCopyForGetters(List expandedTypes, int depth, StringB
emitDeepCopyCollections(expandedTypes, depth, builder, fieldNameIn, fieldNameOut, i);
builder.append(i).append(" ").append("this.").append(fieldName).append(" = ").append(fieldNameOut).append(";\n");
builder.append(i).append("}\n");
+ } else if (isAny(rawClass)) {
+ builder.append(i).append("this.").append(fieldName).append(" = ");
+ appendNaiveCopyJsonExpression(origin + "." + getterName + "()", builder).append(";\n");
} else if (getEnclosingTemplate().isDtoInterface(rawClass)) {
builder.append(i).append(rawTypeName).append(" ").append(fieldNameIn).append(" = ").append(origin).append(".")
.append(getterName).append("();\n");
diff --git a/platform-api/che-core-api-dto/src/test/java/org/eclipse/che/dto/ServerDtoTest.java b/platform-api/che-core-api-dto/src/test/java/org/eclipse/che/dto/ServerDtoTest.java
index 1981c3dbb..9125230e2 100644
--- a/platform-api/che-core-api-dto/src/test/java/org/eclipse/che/dto/ServerDtoTest.java
+++ b/platform-api/che-core-api-dto/src/test/java/org/eclipse/che/dto/ServerDtoTest.java
@@ -11,11 +11,14 @@
package org.eclipse.che.dto;
import org.eclipse.che.dto.definitions.ComplicatedDto;
+import org.eclipse.che.dto.definitions.DtoWithAny;
import org.eclipse.che.dto.definitions.DtoWithDelegate;
import org.eclipse.che.dto.definitions.DtoWithFieldNames;
import org.eclipse.che.dto.definitions.SimpleDto;
import org.eclipse.che.dto.server.DtoFactory;
+
import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
@@ -110,6 +113,32 @@ public void testDeerializerWithFieldNames() throws Exception {
Assert.assertEquals(dto.getTheDefault(), _default);
}
+ @Test
+ public void testSerializerWithAny() throws Exception {
+ DtoWithAny dto = dtoFactory.createDto(DtoWithAny.class).withStuff(createTestValueForAny());
+ final String json = dtoFactory.toJson(dto);
+
+ JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
+ Assert.assertEquals(jsonObject.get("stuff"), createTestValueForAny());
+ }
+
+ @Test
+ public void testDeerializerWithAny() throws Exception {
+ final JsonElement stuff = createTestValueForAny();
+
+ JsonObject json = new JsonObject();
+ json.add("stuff", stuff);
+
+ DtoWithAny dto = dtoFactory.createDtoFromJson(json.toString(), DtoWithAny.class);
+
+ Assert.assertEquals(dto.getStuff(), createTestValueForAny());
+ }
+
+ /** Intentionally call several times to ensure non-reference equality */
+ private static JsonElement createTestValueForAny() {
+ return new JsonParser().parse("{a:100,b:{c:'blah',d:null}}");
+ }
+
@Test
public void testListSimpleDtoDeserializer() throws Exception {
final String fooString_1 = "Something 1";
diff --git a/platform-api/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/DtoWithAny.java b/platform-api/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/DtoWithAny.java
new file mode 100644
index 000000000..afd33c6a3
--- /dev/null
+++ b/platform-api/che-core-api-dto/src/test/java/org/eclipse/che/dto/definitions/DtoWithAny.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2012-2015 Codenvy, S.A.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Codenvy, S.A. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.che.dto.definitions;
+
+import org.eclipse.che.dto.shared.DTO;
+
+import com.google.gson.JsonElement;
+
+/**
+ * Makes use of the 'any' (JsonElement) property type feature.
+ *
+ * @author Tareq Sharafy (tareq.sharafy@sap.com)
+ */
+@DTO
+public interface DtoWithAny {
+ int getId();
+
+ JsonElement getStuff();
+
+ void setStuff(JsonElement stuff);
+
+ DtoWithAny withStuff(JsonElement stuff);
+}