Skip to content

Commit

Permalink
DTO streaming
Browse files Browse the repository at this point in the history
down-port eclipse-che/che#3103

Change-Id: Iea2704e625105ad483ee9f1348b217b0bc493d6e
Signed-off-by: Tareq Sharafy <tareq.sha@gmail.com>
  • Loading branch information
tareksha committed Feb 13, 2017
1 parent 78efd43 commit 693320f
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 375 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/** Abstract base class for the source generating template for a single DTO. */
abstract class DtoImpl {
Expand Down Expand Up @@ -176,6 +177,19 @@ protected List<Method> getDtoGetters(Class<?> dto) {
return new ArrayList<>(getters.values());
}

/**
* Get the names of all the getters in the super DTO interface and upper ancestors.
*/
protected Set<String> getSuperGetterNames(Class<?> dto) {
final Map<String, Method> getters = new HashMap<>();
Class<?> superDto = getSuperDtoInterface(dto);
if (superDto != null) {
addDtoGetters(superDto, getters);
addSuperGetters(superDto, getters);
}
return getters.keySet();
}

/**
* Adds all getters from parent <b>NOT DTO</b> interfaces for given {@code dto} interface.
* Does not add method when it is already present in getters map.
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ private void emitPreamble(StringBuilder builder) {
}
builder.append(" {\n\n");
if ("server".equals(implType)) {
builder.append(" private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();\n\n");
builder.append(" private static final Gson gson = org.eclipse.che.dto.server.DtoFactory.getInstance().getGson();\n\n");
builder.append(" @Override\n" +
" public void accept(org.eclipse.che.dto.server.DtoFactory dtoFactory) {\n");
for (DtoImpl dto : getDtoInterfaces()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,23 @@
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -42,7 +50,6 @@
* @author andrew00x
*/
public final class DtoFactory {
private static final Gson gson = new GsonBuilder().serializeNulls().create();

private static final Cache<Type, ParameterizedType> listTypeCache = new SynchronizedCache<>(
new LoadingValueSLRUCache<Type, ParameterizedType>(16, 16) {
Expand All @@ -65,6 +72,15 @@ public static DtoFactory getInstance() {
return INSTANCE;
}

/**
* Ge a {@link Gson} serializer that is configured to serializes/deserializes DTOs correctly.
*
* @return A Gson.
*/
public Gson getGson() {
return dtoGson;
}

/**
* Creates new instance of class which implements specified DTO interface.
*
Expand All @@ -81,6 +97,10 @@ public static <T> T newDto(Class<T> dtoInterface) {
// Additional mapping for implementation of DTO interfaces.
// It helps avoid reflection when need create copy of exited DTO instance.
private final Map<Class<?>, DtoProvider<?>> dtoImpl2Providers = new ConcurrentHashMap<>();
private final Gson dtoGson = new GsonBuilder()
.registerTypeAdapterFactory(new NullAsEmptyTAF<>(Collection.class, Collections.emptyList()))
.registerTypeAdapterFactory(new NullAsEmptyTAF<>(Map.class, Collections.emptyMap()))
.registerTypeAdapterFactory(new DtoInterfaceTAF()).create();

/**
* Created deep copy of DTO object.
Expand Down Expand Up @@ -165,7 +185,11 @@ public <T> T createDto(Class<T> dtoInterface) {
* if can't provide any implementation for specified interface
*/
public <T> T createDtoFromJson(String json, Class<T> dtoInterface) {
return getDtoProvider(dtoInterface).fromJson(json);
try {
return createDtoFromJson(new StringReader(json), dtoInterface);
} catch (IOException e) {
throw new RuntimeException(e); // won't happen
}
}

/**
Expand Down Expand Up @@ -197,14 +221,8 @@ public <T> T createDtoFromJson(JsonElement json, Class<T> dtoInterface) {
* if an i/o error occurs
*/
public <T> T createDtoFromJson(Reader json, Class<T> dtoInterface) throws IOException {
DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(json);
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
return dtoProvider.fromJson(sb.toString());
getDtoProvider(dtoInterface);
return dtoGson.fromJson(json, dtoInterface);
}

/**
Expand Down Expand Up @@ -238,16 +256,13 @@ public <T> T createDtoFromJson(InputStream json, Class<T> dtoInterface) throws I
* if can't provide any implementation for specified interface
*/
public <T> JsonArray<T> createListDtoFromJson(String json, Class<T> dtoInterface) {
final DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
final List<JsonElement> list = gson.fromJson(json, listTypeCache.get(JsonElement.class));
final List<T> result = new ArrayList<>(list.size());
for (JsonElement e : list) {
result.add(dtoProvider.fromJson(e));
try {
return createListDtoFromJson(new StringReader(json), dtoInterface);
} catch (IOException e) {
throw new RuntimeException(e); // won't happen
}
return new JsonArrayImpl<>(result);
}


/**
* Parses the JSON data from the specified reader into list of objects of the specified type.
*
Expand All @@ -260,22 +275,9 @@ public <T> JsonArray<T> createListDtoFromJson(String json, Class<T> dtoInterface
* if can't provide any implementation for specified interface
*/
public <T> JsonArray<T> createListDtoFromJson(Reader json, Class<T> dtoInterface) throws IOException {
final DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
final List<JsonElement> list;
try {
list = gson.fromJson(json, listTypeCache.get(JsonElement.class));
} catch (JsonSyntaxException e) {
final Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException)cause;
}
throw e;
}
final List<T> result = new ArrayList<>(list.size());
for (JsonElement e : list) {
result.add(dtoProvider.fromJson(e));
}
return new JsonArrayImpl<>(result);
getDtoProvider(dtoInterface);
final List<T> list = parseDto(json, listTypeCache.get(dtoInterface));
return new JsonArrayImpl<>(list);
}

/**
Expand Down Expand Up @@ -309,13 +311,11 @@ public <T> JsonArray<T> createListDtoFromJson(InputStream json, Class<T> dtoInte
* if can't provide any implementation for specified interface
*/
public <T> JsonStringMap<T> createMapDtoFromJson(String json, Class<T> dtoInterface) {
final DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
final Map<String, JsonElement> map = gson.fromJson(json, mapTypeCache.get(JsonElement.class));
final Map<String, T> result = new LinkedHashMap<>(map.size());
for (Map.Entry<String, JsonElement> e : map.entrySet()) {
result.put(e.getKey(), dtoProvider.fromJson(e.getValue()));
try {
return createMapDtoFromJson(new StringReader(json), dtoInterface);
} catch (IOException e) {
throw new RuntimeException(e); // won't happen
}
return new JsonStringMapImpl<>(result);
}


Expand All @@ -332,24 +332,10 @@ public <T> JsonStringMap<T> createMapDtoFromJson(String json, Class<T> dtoInterf
* @throws IOException
* if an i/o error occurs
*/
@SuppressWarnings("unchecked")
public <T> JsonStringMap<T> createMapDtoFromJson(Reader json, Class<T> dtoInterface) throws IOException {
final DtoProvider<T> dtoProvider = getDtoProvider(dtoInterface);
final Map<String, JsonElement> map;
try {
map = gson.fromJson(json, mapTypeCache.get(JsonElement.class));
} catch (JsonSyntaxException e) {
final Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException)cause;
}
throw e;
}
final Map<String, T> result = new LinkedHashMap<>(map.size());
for (Map.Entry<String, JsonElement> e : map.entrySet()) {
result.put(e.getKey(), dtoProvider.fromJson(e.getValue()));
}
return new JsonStringMapImpl<>(result);
getDtoProvider(dtoInterface);
final Map<String, T> map = parseDto(json, mapTypeCache.get(dtoInterface));
return new JsonStringMapImpl<>(map);
}

/**
Expand Down Expand Up @@ -380,6 +366,22 @@ private <T> DtoProvider<T> getDtoProvider(Class<T> dtoInterface) {
return (DtoProvider<T>)dtoProvider;
}

/**
* Parse a JSON string that contains DTOs, propagating JSON exceptions correctly if they are caused by failures in
* the given Reader. Real JSON syntax exceptions are propagated as-is.
*/
private <T> T parseDto(Reader json, Type type) throws IOException {
try {
return dtoGson.fromJson(json, type);
} catch (JsonSyntaxException e) {
final Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
}
throw e;
}
}

/**
* Registers DtoProvider for DTO interface.
*
Expand Down Expand Up @@ -412,6 +414,59 @@ public boolean hasProvider(Class<?> dtoInterface) {
return dtoInterface2Providers.get(dtoInterface) != null;
}

/**
* A specialization of Gson's {@link ReflectiveTypeAdapterFactory} delegates operation on DTO interfaces to the
* corresponding implementation classes. The implementation classes generated correctly by the DTO Gson.
*
* @author tareq.sha@gmail.com
*/
private class DtoInterfaceTAF implements TypeAdapterFactory {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
DtoProvider<?> prov = dtoInterface2Providers.get(type.getRawType());
if (prov != null) {
return (TypeAdapter<T>) gson.getAdapter(prov.getImplClass());
}
return null;
}
}

/**
* Wraps Gson's default List/Map adapter factories serialize null List/Map fields as empty instead.
*
* @author tareq.sha@gmail.com
*/
private static class NullAsEmptyTAF<U> implements TypeAdapterFactory {
final Class<?> matchedClass;
final U defaultValue;

NullAsEmptyTAF(Class<U> matchedClass, U defaultValue) {
this.matchedClass = matchedClass;
this.defaultValue = defaultValue;
}

@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!matchedClass.isAssignableFrom(type.getRawType())) {
return null;
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value != null ? value : (T) defaultValue);
}

@Override
public T read(JsonReader in) throws IOException {
return delegate.read(in);
}
};
}
}

static {
for (DtoFactoryVisitor visitor : ServiceLoader.load(DtoFactoryVisitor.class)) {
visitor.accept(INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;

import java.io.Writer;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
Expand Down Expand Up @@ -154,6 +155,11 @@ public String toJson() {
return gson.toJson(this);
}

@Override
public void toJson(Writer w) {
gson.toJson(this, w);
}

@Override
public JsonElement toJsonElement() {
return gson.toJsonTree(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.eclipse.che.dto.server;

import java.io.Serializable;
import java.io.Writer;

import com.google.gson.JsonElement;

Expand All @@ -23,6 +24,9 @@ public interface JsonSerializable extends Serializable {
/** Serializes DTO to JSON format. */
String toJson();

/** Serialize the DTO to JSON text through the given Writer. */
void toJson(Writer w);

/** Serializes DTO to JSON object. */
JsonElement toJsonElement();
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;

import java.io.Writer;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -101,6 +102,11 @@ public String toJson() {
return gson.toJson(this);
}

@Override
public void toJson(Writer w) {
gson.toJson(this, w);
}

@Override
public JsonElement toJsonElement() {
return gson.toJsonTree(this);
Expand Down
Loading

0 comments on commit 693320f

Please sign in to comment.