Commit 78b1332de95b2a0e6f9fa77814c8a8c05f369b74

Authored by Benjamin Rühl
1 parent edc95751

Add EAV model implementation including json serialization and deserialization

services/Common/src/main/java/de/bht/beuthbot/dataAccess/GenericDAO.java
... ... @@ -2,12 +2,14 @@ package de.bht.beuthbot.dataAccess;
2 2  
3 3 import de.bht.beuthbot.model.entities.Entity;
4 4  
  5 +import javax.ejb.Remote;
5 6 import java.io.Serializable;
6 7 import java.util.List;
7 8  
8 9 /**
9 10 * Created by Benjamin Rühl on 19.12.2017.
10 11 */
  12 +@Remote
11 13 public interface GenericDAO<T extends Entity, ID extends Serializable> {
12 14  
13 15 T findById(ID id);
... ...
services/Common/src/main/java/de/bht/beuthbot/dataAccess/UserDAO.java
... ... @@ -9,4 +9,6 @@ import javax.ejb.Remote;
9 9 */
10 10 @Remote
11 11 public interface UserDAO extends GenericDAO<User, Long> {
  12 +
  13 + User createUser();
12 14 }
... ...
services/Global/build.gradle
... ... @@ -14,6 +14,7 @@ dependencies {
14 14 "commons-io:commons-io:2.5"
15 15  
16 16 compile group: 'org.postgresql', name: 'postgresql', version: '9.3-1100-jdbc4'
  17 + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.0.1'
17 18  
18 19 providedCompile "org.slf4j:slf4j-api:1.7.25"
19 20  
... ...
services/Global/src/main/java/de/bht/beuthbot/daos/AppUserDAO.java
... ... @@ -15,4 +15,9 @@ public class AppUserDAO extends GenericHibernateDAO&lt;User, Long&gt; implements UserD
15 15 public AppUserDAO() {
16 16 setEntityClass(AppUser.class);
17 17 }
  18 +
  19 + @Override
  20 + public User createUser() {
  21 + return new AppUser();
  22 + }
18 23 }
... ...
services/Global/src/main/java/de/bht/beuthbot/daos/GenericHibernateDAO.java
... ... @@ -6,8 +6,12 @@ import org.hibernate.Criteria;
6 6 import org.hibernate.Session;
7 7 import org.hibernate.criterion.Criterion;
8 8  
  9 +import javax.ejb.Stateless;
  10 +import javax.enterprise.inject.InjectionException;
9 11 import javax.persistence.EntityManager;
  12 +import javax.persistence.EntityManagerFactory;
10 13 import javax.persistence.PersistenceContext;
  14 +import javax.persistence.PersistenceUnit;
11 15 import java.io.Serializable;
12 16 import java.lang.reflect.ParameterizedType;
13 17 import java.util.List;
... ... @@ -15,16 +19,23 @@ import java.util.List;
15 19 /**
16 20 * Created by Benjamin Rühl on 19.12.2017.
17 21 */
  22 +@Stateless
18 23 public class GenericHibernateDAO<T extends Entity, ID extends Serializable> implements GenericDAO<T, ID> {
19 24  
20 25 private Class<? extends T> entityClass;
21 26 private Session session;
22 27  
23   - @PersistenceContext
  28 + @PersistenceUnit(unitName = "PostgreSQLDS")
  29 + private EntityManagerFactory emf;
  30 +
  31 + @PersistenceContext(unitName = "PostgreSQLDS")
24 32 private EntityManager em;
25 33  
26 34 @SuppressWarnings("unchecked cast")
27 35 public GenericHibernateDAO() {
  36 + if (em == null)
  37 + throw new RuntimeException("EntityManager must not be null");
  38 +
28 39 entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
29 40 setSession( (Session)em.getDelegate() );
30 41 }
... ...
services/Global/src/main/java/de/bht/beuthbot/entities/AppUser.java
1 1 package de.bht.beuthbot.entities;
2 2  
  3 +import de.bht.beuthbot.persistence.GenericEntityAccessFacade;
  4 +import de.bht.beuthbot.persistence.GenericEntityJsonConverter;
  5 +import de.bht.beuthbot.persistence.JsonHelper;
  6 +
  7 +import javax.persistence.CascadeType;
3 8 import javax.persistence.Entity;
  9 +import javax.persistence.OneToOne;
4 10 import javax.persistence.Table;
  11 +import java.io.IOException;
  12 +import java.util.List;
  13 +import java.util.stream.Collectors;
5 14  
6 15 /**
7 16 * Created by Benjamin Rühl on 19.11.2017.
... ... @@ -13,11 +22,20 @@ import javax.persistence.Table;
13 22 public class AppUser extends EntityBase implements de.bht.beuthbot.model.entities.User {
14 23  
15 24 private String facebookUserId;
  25 +
16 26 private String telegramUserId;
17 27  
  28 + @OneToOne(cascade = CascadeType.ALL)
  29 + private GenericEntity additionalData;
  30 +
18 31 //@Type(type = "JsonMapType")
19 32 //private Map<String, String> additionalData = new HashMap<>();
20 33  
  34 + public AppUser() {
  35 + additionalData = new GenericEntity();
  36 + additionalData.setName("User");
  37 + }
  38 +
21 39 @Override
22 40 public String getFacebookUserId() {
23 41 return facebookUserId;
... ... @@ -30,12 +48,12 @@ public class AppUser extends EntityBase implements de.bht.beuthbot.model.entitie
30 48  
31 49 @Override
32 50 public <T> T getProperty(String propertyName, Class<T> propertyType) {
33   - return null;
  51 + return GenericEntityAccessFacade.getAttribute(additionalData, propertyName, propertyType);
34 52 }
35 53  
36 54 @Override
37 55 public void setProperty(String propertyName, Object propertyValue) {
38   -
  56 + GenericEntityAccessFacade.setAttribute(additionalData, propertyName, propertyValue);
39 57 }
40 58  
41 59 //@Override
... ...
services/Global/src/main/java/de/bht/beuthbot/entities/GenericEntity.java 0 → 100644
  1 +package de.bht.beuthbot.entities;
  2 +
  3 +import javax.persistence.CascadeType;
  4 +import javax.persistence.Entity;
  5 +import javax.persistence.OneToMany;
  6 +import javax.persistence.Table;
  7 +import java.util.ArrayList;
  8 +import java.util.List;
  9 +import java.util.stream.Collectors;
  10 +
  11 +/**
  12 + * Created by Benjamin Rühl on 22.12.2017.
  13 + * A generic container entity that holds references to a set of generic attributes which can be specified at runtime.
  14 + * Follows the Entity-Attribute-Value model of database design.
  15 + */
  16 +@Entity
  17 +@Table
  18 +public class GenericEntity extends EntityBase {
  19 +
  20 + /**
  21 + * name is not mandatory, but can help to identify the entity's purpose for the developer
  22 + */
  23 + private String name;
  24 +
  25 + @OneToMany(cascade = CascadeType.ALL)
  26 + private List<GenericEntityAttribute> attributes = new ArrayList<>();
  27 +
  28 + public String getName() {
  29 + return name;
  30 + }
  31 +
  32 + public void setName(String name) {
  33 + this.name = name;
  34 + }
  35 +
  36 + public List<GenericEntityAttribute> getAttributes() {
  37 + return attributes;
  38 + }
  39 +
  40 + public void setAttribute(GenericEntityAttribute attribute) {
  41 + List<GenericEntityAttribute> existingAttributesForName = this.attributes.stream().filter(a -> a.getName().equals(attribute.getName())).collect(Collectors.toList());
  42 +
  43 + if (!existingAttributesForName.isEmpty())
  44 + getAttributes().removeAll(existingAttributesForName);
  45 +
  46 + this.attributes.add(attribute);
  47 + }
  48 +}
... ...
services/Global/src/main/java/de/bht/beuthbot/entities/GenericEntityAttribute.java 0 → 100644
  1 +package de.bht.beuthbot.entities;
  2 +
  3 +import javax.persistence.CascadeType;
  4 +import javax.persistence.Entity;
  5 +import javax.persistence.OneToMany;
  6 +import javax.persistence.Table;
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * Created by Benjamin Rühl on 22.12.2017.
  11 + * An attribute of a generic entity that can be specified at runtime.
  12 + * Holds a collection of generic values.
  13 + */
  14 +@Entity
  15 +@Table
  16 +public class GenericEntityAttribute extends EntityBase {
  17 +
  18 + private String name;
  19 +
  20 + @OneToMany(cascade = CascadeType.ALL)
  21 + private List<GenericEntityAttributeValue> values;
  22 +
  23 + public String getName() {
  24 + return name;
  25 + }
  26 +
  27 + public void setName(String name) {
  28 + this.name = name;
  29 + }
  30 +
  31 + public List<GenericEntityAttributeValue> getValues() {
  32 + return values;
  33 + }
  34 +
  35 + public void addValue(GenericEntityAttributeValue value) {
  36 + this.values.add(value);
  37 + }
  38 +}
... ...
services/Global/src/main/java/de/bht/beuthbot/entities/GenericEntityAttributeValue.java 0 → 100644
  1 +package de.bht.beuthbot.entities;
  2 +
  3 +import javax.persistence.*;
  4 +
  5 +/**
  6 + * Created by Benjamin Rühl on 22.12.2017.
  7 + * The value of a generic attribute in supported data formats.
  8 + * Supports complex nested types using a reference to a generic sub entity.
  9 + */
  10 +@Entity
  11 +@Table
  12 +public class GenericEntityAttributeValue extends EntityBase {
  13 +
  14 + private Boolean valueAsBool;
  15 +
  16 + private Long valueAsLong;
  17 +
  18 + private Double valueAsDouble;
  19 +
  20 + private String valueAsString;
  21 +
  22 + @OneToOne(cascade = CascadeType.ALL)
  23 + private GenericEntity valueAsEntity;
  24 +
  25 + public Boolean getValueAsBool() {
  26 + return valueAsBool;
  27 + }
  28 +
  29 + public void setValueAsBool(boolean valueAsBool) {
  30 + this.valueAsBool = valueAsBool;
  31 + }
  32 +
  33 + public Long getValueAsLong() {
  34 + return valueAsLong;
  35 + }
  36 +
  37 + public void setValueAsLong(long valueAsLong) {
  38 + this.valueAsLong = valueAsLong;
  39 + }
  40 +
  41 + public Double getValueAsDouble() {
  42 + return valueAsDouble;
  43 + }
  44 +
  45 + public void setValueAsDouble(double valueAsDouble) {
  46 + this.valueAsDouble = valueAsDouble;
  47 + }
  48 +
  49 + public String getValueAsString() {
  50 + return valueAsString;
  51 + }
  52 +
  53 + public void setValueAsString(String valueAsString) {
  54 + this.valueAsString = valueAsString;
  55 + }
  56 +
  57 + public GenericEntity getValueAsEntity() {
  58 + return valueAsEntity;
  59 + }
  60 +
  61 + public void setValueAsEntity(GenericEntity valueAsEntity) {
  62 + this.valueAsEntity = valueAsEntity;
  63 + }
  64 +}
... ...
services/Global/src/main/java/de/bht/beuthbot/persistence/GenericEntityAccessFacade.java 0 → 100644
  1 +package de.bht.beuthbot.persistence;
  2 +
  3 +import com.fasterxml.jackson.databind.ObjectMapper;
  4 +import com.fasterxml.jackson.databind.module.SimpleModule;
  5 +import de.bht.beuthbot.entities.GenericEntity;
  6 +import de.bht.beuthbot.entities.GenericEntityAttribute;
  7 +
  8 +import java.io.IOException;
  9 +import java.util.List;
  10 +import java.util.stream.Collectors;
  11 +
  12 +/**
  13 + * Created by Benjamin Rühl on 22.12.2017.
  14 + * Provides convenience methods for accessing GenericEntity structures
  15 + */
  16 +public class GenericEntityAccessFacade {
  17 +
  18 + public static <T> T getAttribute(GenericEntity contextEntity, String attributeName, Class<T> attributeType) {
  19 + List<GenericEntityAttribute> propertiesWithName = contextEntity.getAttributes().stream().filter(a -> a.getName().equals(attributeName)).limit(1).collect(Collectors.toList());
  20 +
  21 + if (propertiesWithName == null || propertiesWithName.isEmpty())
  22 + return null;
  23 +
  24 + GenericEntityAttribute property = propertiesWithName.get(0);
  25 +
  26 + try {
  27 + String propertyAsJson = GenericEntityJsonConverter.toJson(property);
  28 + return (T) JsonHelper.fromJson(propertyAsJson, attributeType);
  29 + } catch (IOException e) {
  30 + e.printStackTrace();
  31 + }
  32 +
  33 + return null;
  34 + }
  35 +
  36 + public static void setAttribute(GenericEntity contextEntity, String attributeName, Object attributeValue) {
  37 + String attributeValueAsJson = JsonHelper.toJson(attributeValue, attributeValue.getClass());
  38 +
  39 + GenericEntityAttribute deserializedGenericAttribute = null;
  40 +
  41 + try {
  42 + deserializedGenericAttribute = createGenericEntityAttributeFromJson(attributeValueAsJson);
  43 + } catch (IOException e) {
  44 + e.printStackTrace();
  45 + }
  46 +
  47 + if (deserializedGenericAttribute == null)
  48 + return;
  49 +
  50 + deserializedGenericAttribute.setName(attributeName);
  51 +
  52 + contextEntity.setAttribute(deserializedGenericAttribute);
  53 + }
  54 +
  55 + private static GenericEntityAttribute createGenericEntityAttributeFromJson(String json) throws IOException {
  56 + ObjectMapper mapper = new ObjectMapper();
  57 + SimpleModule module = new SimpleModule();
  58 + module.addDeserializer(GenericEntity.class, new GenericEntityDeserializer());
  59 + module.addDeserializer(GenericEntityAttribute.class, new GenericEntityAttributeDeserializer());
  60 + mapper.registerModule(module);
  61 +
  62 + return mapper.readValue(json, GenericEntityAttribute.class);
  63 + }
  64 +}
... ...
services/Global/src/main/java/de/bht/beuthbot/persistence/GenericEntityAttributeDeserializer.java 0 → 100644
  1 +package de.bht.beuthbot.persistence;
  2 +
  3 +import com.fasterxml.jackson.core.JsonParser;
  4 +import com.fasterxml.jackson.core.JsonProcessingException;
  5 +import com.fasterxml.jackson.core.JsonToken;
  6 +import com.fasterxml.jackson.databind.DeserializationContext;
  7 +import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  8 +import de.bht.beuthbot.entities.GenericEntity;
  9 +import de.bht.beuthbot.entities.GenericEntityAttribute;
  10 +import de.bht.beuthbot.entities.GenericEntityAttributeValue;
  11 +
  12 +import javax.naming.OperationNotSupportedException;
  13 +import java.io.IOException;
  14 +import java.util.ArrayList;
  15 +import java.util.List;
  16 +
  17 +/**
  18 + * Created by Benjamin Rühl on 22.12.2017.
  19 + */
  20 +public class GenericEntityAttributeDeserializer extends StdDeserializer<GenericEntityAttribute> {
  21 +
  22 + public GenericEntityAttributeDeserializer() {
  23 + this(GenericEntityAttribute.class);
  24 + }
  25 +
  26 + public GenericEntityAttributeDeserializer(Class<? extends GenericEntityAttribute> t) {
  27 + super(t);
  28 + }
  29 +
  30 + @Override
  31 + public GenericEntityAttribute deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
  32 + if (jp.isClosed())
  33 + return null;
  34 +
  35 + GenericEntityAttribute deserializedAttribute = new GenericEntityAttribute();
  36 +
  37 + JsonToken jsonToken = jp.getCurrentToken();
  38 +
  39 + if (jsonToken.equals(JsonToken.FIELD_NAME)) {
  40 + String fieldName = jp.getCurrentName();
  41 + deserializedAttribute.setName(fieldName);
  42 +
  43 + jsonToken = jp.nextToken();
  44 +
  45 + if (jsonToken.equals(JsonToken.START_ARRAY)) {
  46 + try {
  47 + for (GenericEntityAttributeValue attributeValue : readAttributeValuesUntilArrayEndToken(jp, ctxt)) {
  48 + deserializedAttribute.addValue(attributeValue);
  49 + }
  50 + } catch (OperationNotSupportedException e) {
  51 + e.printStackTrace();
  52 + }
  53 + } else {
  54 + deserializedAttribute.addValue(readAttributeValueFromCurrentToken(jp, ctxt));
  55 + }
  56 + }
  57 +
  58 + return deserializedAttribute;
  59 + }
  60 +
  61 + private List<GenericEntityAttributeValue> readAttributeValuesUntilArrayEndToken(JsonParser jp, DeserializationContext ctxt) throws IOException, OperationNotSupportedException {
  62 + List<GenericEntityAttributeValue> attributes = new ArrayList<>();
  63 + JsonToken jsonToken = jp.getCurrentToken();
  64 +
  65 + if (!jsonToken.equals(JsonToken.START_ARRAY))
  66 + throw new OperationNotSupportedException("Method should not be called if current token is not START_ARRAY");
  67 +
  68 + while (!jp.isClosed() || jsonToken.equals(JsonToken.END_ARRAY)) {
  69 + jsonToken = jp.nextToken();
  70 + attributes.add(readAttributeValueFromCurrentToken(jp, ctxt));
  71 + }
  72 +
  73 + return attributes;
  74 + }
  75 +
  76 + private GenericEntityAttributeValue readAttributeValueFromCurrentToken(JsonParser jp, DeserializationContext ctxt) throws IOException {
  77 + GenericEntityAttributeValue attributeValue = new GenericEntityAttributeValue();
  78 + JsonToken jsonToken = jp.getCurrentToken();
  79 +
  80 + if (jsonToken.equals(JsonToken.VALUE_FALSE) || jsonToken.equals(JsonToken.VALUE_TRUE)) {
  81 + attributeValue.setValueAsBool(jp.getBooleanValue());
  82 + } else if (jsonToken.equals(JsonToken.VALUE_NUMBER_INT)) {
  83 + attributeValue.setValueAsLong(jp.getLongValue());
  84 + } else if (jsonToken.equals(JsonToken.VALUE_NUMBER_FLOAT)) {
  85 + attributeValue.setValueAsDouble(jp.getFloatValue());
  86 + } else if (jsonToken.equals(JsonToken.VALUE_STRING)) {
  87 + attributeValue.setValueAsString(jp.getText());
  88 + } else if (jsonToken.equals(JsonToken.START_OBJECT)) {
  89 + GenericEntity embeddedEntity = new GenericEntityDeserializer().deserialize(jp, ctxt);
  90 + attributeValue.setValueAsEntity(embeddedEntity);
  91 + }
  92 +
  93 + return attributeValue;
  94 + }
  95 +}
... ...
services/Global/src/main/java/de/bht/beuthbot/persistence/GenericEntityDeserializer.java 0 → 100644
  1 +package de.bht.beuthbot.persistence;
  2 +
  3 +import com.fasterxml.jackson.core.JsonParser;
  4 +import com.fasterxml.jackson.core.JsonProcessingException;
  5 +import com.fasterxml.jackson.core.JsonToken;
  6 +import com.fasterxml.jackson.databind.DeserializationContext;
  7 +import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  8 +import de.bht.beuthbot.entities.GenericEntity;
  9 +import de.bht.beuthbot.entities.GenericEntityAttribute;
  10 +
  11 +import java.io.IOException;
  12 +
  13 +/**
  14 + * Created by Benjamin Rühl on 23.12.2017.
  15 + */
  16 +public class GenericEntityDeserializer extends StdDeserializer<GenericEntity> {
  17 +
  18 + public GenericEntityDeserializer() {
  19 + this(GenericEntity.class);
  20 + }
  21 +
  22 + public GenericEntityDeserializer(Class<? extends GenericEntity> t) {
  23 + super(t);
  24 + }
  25 +
  26 + @Override
  27 + public GenericEntity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
  28 + GenericEntity deserializedEntity = new GenericEntity();
  29 + GenericEntityAttributeDeserializer attributeDeserializer = new GenericEntityAttributeDeserializer();
  30 +
  31 + while (!jp.isClosed()) {
  32 + JsonToken jsonToken = jp.nextToken();
  33 +
  34 + if (jsonToken.equals(JsonToken.FIELD_NAME)) {
  35 + GenericEntityAttribute attribute = attributeDeserializer.deserialize(jp, ctxt);
  36 + deserializedEntity.setAttribute(attribute);
  37 + }
  38 + }
  39 +
  40 + return deserializedEntity;
  41 + }
  42 +}
0 43 \ No newline at end of file
... ...
services/Global/src/main/java/de/bht/beuthbot/persistence/GenericEntityJsonConverter.java 0 → 100644
  1 +package de.bht.beuthbot.persistence;
  2 +
  3 +import com.fasterxml.jackson.core.JsonFactory;
  4 +import com.fasterxml.jackson.core.JsonGenerator;
  5 +import de.bht.beuthbot.entities.GenericEntity;
  6 +import de.bht.beuthbot.entities.GenericEntityAttribute;
  7 +import de.bht.beuthbot.entities.GenericEntityAttributeValue;
  8 +
  9 +import java.io.IOException;
  10 +import java.io.StringWriter;
  11 +import java.lang.reflect.Field;
  12 +import java.lang.reflect.ParameterizedType;
  13 +import java.lang.reflect.Type;
  14 +import java.util.Arrays;
  15 +import java.util.List;
  16 +
  17 +/**
  18 + * Created by Benjamin Rühl on 22.12.2017.
  19 + * Helper class to serialize GenericEntity as if its generic attributes were normal fields
  20 + */
  21 +public class GenericEntityJsonConverter {
  22 +
  23 + /**
  24 + * Serializes a GenericEntity to json as if its generic attributes were normal fields.
  25 + * Other normal fields are serialized as well.
  26 + * @param genericEntity the context entity to be serialized with all its sub structure
  27 + * @return a serialized string in json format
  28 + * @throws IOException
  29 + */
  30 + public static String toJson(GenericEntity genericEntity) throws IOException {
  31 + JsonFactory jFactory = new JsonFactory();
  32 + StringWriter writer = new StringWriter();
  33 + JsonGenerator jsonGenerator = jFactory.createJsonGenerator(writer);
  34 +
  35 + jsonGenerator.writeStartObject();
  36 + writeGenericEntityWithoutStart(jsonGenerator, genericEntity);
  37 +
  38 + jsonGenerator.close();
  39 + return writer.toString();
  40 + }
  41 +
  42 + /**
  43 + * Serializes a GenericEntityAttribute to json as if it would be a normal field.
  44 + * @param genericAttribute the context attribute to be serialized with all its sub structure
  45 + * @return a serialized string in json format
  46 + * @throws IOException
  47 + */
  48 + public static String toJson(GenericEntityAttribute genericAttribute) throws IOException {
  49 + JsonFactory jFactory = new JsonFactory();
  50 + StringWriter writer = new StringWriter();
  51 + JsonGenerator jsonGenerator = jFactory.createJsonGenerator(writer);
  52 +
  53 + writeGenericAttribute(jsonGenerator, genericAttribute);
  54 +
  55 + jsonGenerator.close();
  56 + return writer.toString();
  57 + }
  58 +
  59 + /**
  60 + * Uses a JsonGenerator to append a GenericEntity and its content to json.
  61 + * Does not open the json object because this part depends on whether the object stands for itself or is the value of a field.
  62 + * However it does close the object.
  63 + * @param jsonGenerator the generator used for building the json
  64 + * @param genericEntity the context entity that is serialized
  65 + * @throws IOException
  66 + */
  67 + private static void writeGenericEntityWithoutStart(JsonGenerator jsonGenerator, GenericEntity genericEntity) throws IOException {
  68 + writeClassFieldsWithoutGenericEntityHierarchy(jsonGenerator, genericEntity);
  69 +
  70 + for (GenericEntityAttribute genericAttribute : genericEntity.getAttributes()) {
  71 + writeGenericAttribute(jsonGenerator, genericAttribute);
  72 + }
  73 +
  74 + jsonGenerator.writeEndObject();
  75 + }
  76 +
  77 + private static void writeGenericAttribute(JsonGenerator jsonGenerator, GenericEntityAttribute genericAttribute) throws IOException {
  78 + List<GenericEntityAttributeValue> attributeValues = genericAttribute.getValues();
  79 +
  80 + if (attributeValues == null || attributeValues.isEmpty()) {
  81 + jsonGenerator.writeNullField(genericAttribute.getName());
  82 + } else if (attributeValues.size() == 1) {
  83 + writeGenericAttributeValue(jsonGenerator, genericAttribute.getValues().get(0), genericAttribute.getName());
  84 + } else {
  85 + writeGenericAttributeMultiValue(jsonGenerator, genericAttribute);
  86 + }
  87 + }
  88 +
  89 + private static void writeGenericAttributeMultiValue(JsonGenerator jsonGenerator, GenericEntityAttribute genericAttribute) throws IOException {
  90 + jsonGenerator.writeArrayFieldStart(genericAttribute.getName());
  91 +
  92 + for (GenericEntityAttributeValue genericValue : genericAttribute.getValues()) {
  93 + writeGenericAttributeValueInArray(jsonGenerator, genericValue);
  94 + }
  95 +
  96 + jsonGenerator.writeEndArray();
  97 + }
  98 +
  99 + private static void writeGenericAttributeValue(JsonGenerator jsonGenerator, GenericEntityAttributeValue genericAttributeValue, String attributeName) throws IOException {
  100 + if (genericAttributeValue.getValueAsBool() != null) {
  101 + jsonGenerator.writeBooleanField(attributeName, genericAttributeValue.getValueAsBool());
  102 + } else if (genericAttributeValue.getValueAsLong() != null) {
  103 + jsonGenerator.writeNumberField(attributeName, genericAttributeValue.getValueAsLong());
  104 + } else if (genericAttributeValue.getValueAsDouble() != null) {
  105 + jsonGenerator.writeNumberField(attributeName, genericAttributeValue.getValueAsDouble());
  106 + } else if (genericAttributeValue.getValueAsString() != null) {
  107 + jsonGenerator.writeStringField(attributeName, genericAttributeValue.getValueAsString());
  108 + } else if (genericAttributeValue.getValueAsEntity() != null) {
  109 + jsonGenerator.writeObjectFieldStart(attributeName);
  110 + writeGenericEntityWithoutStart(jsonGenerator, genericAttributeValue.getValueAsEntity());
  111 + }
  112 + }
  113 +
  114 + private static void writeGenericAttributeValueInArray(JsonGenerator jsonGenerator, GenericEntityAttributeValue genericAttributeValue) throws IOException {
  115 + if (genericAttributeValue.getValueAsBool() != null) {
  116 + jsonGenerator.writeBoolean(genericAttributeValue.getValueAsBool());
  117 + } else if (genericAttributeValue.getValueAsLong() != null) {
  118 + jsonGenerator.writeNumber(genericAttributeValue.getValueAsLong());
  119 + } else if (genericAttributeValue.getValueAsDouble() != null) {
  120 + jsonGenerator.writeNumber(genericAttributeValue.getValueAsDouble());
  121 + } else if (genericAttributeValue.getValueAsString() != null) {
  122 + jsonGenerator.writeString(genericAttributeValue.getValueAsString());
  123 + } else if (genericAttributeValue.getValueAsEntity() != null) {
  124 + jsonGenerator.writeStartObject();
  125 + writeGenericEntityWithoutStart(jsonGenerator, genericAttributeValue.getValueAsEntity());
  126 + }
  127 + }
  128 +
  129 + /**
  130 + * Uses a JsonGenerator to append all fields and their values of the targetObject to json.
  131 + * Ignores fields that have GenericEntity, GenericEntityAttribute or GenericEntityAttributeValue as type or generic list parameter.
  132 + * @throws IOException
  133 + */
  134 + private static void writeClassFieldsWithoutGenericEntityHierarchy(JsonGenerator jsonGenerator, Object targetObject) throws IOException {
  135 + for (Field field : targetObject.getClass().getFields()) {
  136 + if (isGenericEntityHierarchyType(field))
  137 + continue;
  138 +
  139 + try {
  140 + jsonGenerator.writeObjectField(field.getName(), field.get(targetObject));
  141 + } catch (Exception e) {
  142 + jsonGenerator.writeStringField(field.getName(), e.getClass().getName());
  143 + e.printStackTrace();
  144 + }
  145 + }
  146 + }
  147 +
  148 + /**
  149 + * Determines whether the field's type is GenericEntity, GenericEntityAttribute or GenericEntityAttributeValue
  150 + * or a list type with one of those types as generic type parameter.
  151 + */
  152 + private static boolean isGenericEntityHierarchyType(Field field) {
  153 + List<Class> typesOfGenericEntityHierarchy = Arrays.asList(GenericEntity.class, GenericEntityAttribute.class, GenericEntityAttributeValue.class);
  154 +
  155 + // check for list types
  156 + try {
  157 + if (Iterable.class.isAssignableFrom(field.getType())) {
  158 + Type fieldFirstGenericType = ((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0];
  159 + if (typesOfGenericEntityHierarchy.contains(fieldFirstGenericType.getClass()))
  160 + return true;
  161 + }
  162 + } catch (Exception e) {
  163 + // not a list or not generic
  164 + }
  165 +
  166 + return typesOfGenericEntityHierarchy.contains(field.getType());
  167 + }
  168 +}
... ...