Commit 78b1332de95b2a0e6f9fa77814c8a8c05f369b74
1 parent
edc95751
Add EAV model implementation including json serialization and deserialization
Showing
13 changed files
with
561 additions
and
3 deletions
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
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
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 | +} | ... | ... |