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,12 +2,14 @@ package de.bht.beuthbot.dataAccess; | ||
2 | 2 | ||
3 | import de.bht.beuthbot.model.entities.Entity; | 3 | import de.bht.beuthbot.model.entities.Entity; |
4 | 4 | ||
5 | +import javax.ejb.Remote; | ||
5 | import java.io.Serializable; | 6 | import java.io.Serializable; |
6 | import java.util.List; | 7 | import java.util.List; |
7 | 8 | ||
8 | /** | 9 | /** |
9 | * Created by Benjamin Rühl on 19.12.2017. | 10 | * Created by Benjamin Rühl on 19.12.2017. |
10 | */ | 11 | */ |
12 | +@Remote | ||
11 | public interface GenericDAO<T extends Entity, ID extends Serializable> { | 13 | public interface GenericDAO<T extends Entity, ID extends Serializable> { |
12 | 14 | ||
13 | T findById(ID id); | 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,6 +14,7 @@ dependencies { | ||
14 | "commons-io:commons-io:2.5" | 14 | "commons-io:commons-io:2.5" |
15 | 15 | ||
16 | compile group: 'org.postgresql', name: 'postgresql', version: '9.3-1100-jdbc4' | 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 | providedCompile "org.slf4j:slf4j-api:1.7.25" | 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<User, Long> implements UserD | @@ -15,4 +15,9 @@ public class AppUserDAO extends GenericHibernateDAO<User, Long> implements UserD | ||
15 | public AppUserDAO() { | 15 | public AppUserDAO() { |
16 | setEntityClass(AppUser.class); | 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,8 +6,12 @@ import org.hibernate.Criteria; | ||
6 | import org.hibernate.Session; | 6 | import org.hibernate.Session; |
7 | import org.hibernate.criterion.Criterion; | 7 | import org.hibernate.criterion.Criterion; |
8 | 8 | ||
9 | +import javax.ejb.Stateless; | ||
10 | +import javax.enterprise.inject.InjectionException; | ||
9 | import javax.persistence.EntityManager; | 11 | import javax.persistence.EntityManager; |
12 | +import javax.persistence.EntityManagerFactory; | ||
10 | import javax.persistence.PersistenceContext; | 13 | import javax.persistence.PersistenceContext; |
14 | +import javax.persistence.PersistenceUnit; | ||
11 | import java.io.Serializable; | 15 | import java.io.Serializable; |
12 | import java.lang.reflect.ParameterizedType; | 16 | import java.lang.reflect.ParameterizedType; |
13 | import java.util.List; | 17 | import java.util.List; |
@@ -15,16 +19,23 @@ import java.util.List; | @@ -15,16 +19,23 @@ import java.util.List; | ||
15 | /** | 19 | /** |
16 | * Created by Benjamin Rühl on 19.12.2017. | 20 | * Created by Benjamin Rühl on 19.12.2017. |
17 | */ | 21 | */ |
22 | +@Stateless | ||
18 | public class GenericHibernateDAO<T extends Entity, ID extends Serializable> implements GenericDAO<T, ID> { | 23 | public class GenericHibernateDAO<T extends Entity, ID extends Serializable> implements GenericDAO<T, ID> { |
19 | 24 | ||
20 | private Class<? extends T> entityClass; | 25 | private Class<? extends T> entityClass; |
21 | private Session session; | 26 | private Session session; |
22 | 27 | ||
23 | - @PersistenceContext | 28 | + @PersistenceUnit(unitName = "PostgreSQLDS") |
29 | + private EntityManagerFactory emf; | ||
30 | + | ||
31 | + @PersistenceContext(unitName = "PostgreSQLDS") | ||
24 | private EntityManager em; | 32 | private EntityManager em; |
25 | 33 | ||
26 | @SuppressWarnings("unchecked cast") | 34 | @SuppressWarnings("unchecked cast") |
27 | public GenericHibernateDAO() { | 35 | public GenericHibernateDAO() { |
36 | + if (em == null) | ||
37 | + throw new RuntimeException("EntityManager must not be null"); | ||
38 | + | ||
28 | entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; | 39 | entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; |
29 | setSession( (Session)em.getDelegate() ); | 40 | setSession( (Session)em.getDelegate() ); |
30 | } | 41 | } |
services/Global/src/main/java/de/bht/beuthbot/entities/AppUser.java
1 | package de.bht.beuthbot.entities; | 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 | import javax.persistence.Entity; | 8 | import javax.persistence.Entity; |
9 | +import javax.persistence.OneToOne; | ||
4 | import javax.persistence.Table; | 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 | * Created by Benjamin Rühl on 19.11.2017. | 16 | * Created by Benjamin Rühl on 19.11.2017. |
@@ -13,11 +22,20 @@ import javax.persistence.Table; | @@ -13,11 +22,20 @@ import javax.persistence.Table; | ||
13 | public class AppUser extends EntityBase implements de.bht.beuthbot.model.entities.User { | 22 | public class AppUser extends EntityBase implements de.bht.beuthbot.model.entities.User { |
14 | 23 | ||
15 | private String facebookUserId; | 24 | private String facebookUserId; |
25 | + | ||
16 | private String telegramUserId; | 26 | private String telegramUserId; |
17 | 27 | ||
28 | + @OneToOne(cascade = CascadeType.ALL) | ||
29 | + private GenericEntity additionalData; | ||
30 | + | ||
18 | //@Type(type = "JsonMapType") | 31 | //@Type(type = "JsonMapType") |
19 | //private Map<String, String> additionalData = new HashMap<>(); | 32 | //private Map<String, String> additionalData = new HashMap<>(); |
20 | 33 | ||
34 | + public AppUser() { | ||
35 | + additionalData = new GenericEntity(); | ||
36 | + additionalData.setName("User"); | ||
37 | + } | ||
38 | + | ||
21 | @Override | 39 | @Override |
22 | public String getFacebookUserId() { | 40 | public String getFacebookUserId() { |
23 | return facebookUserId; | 41 | return facebookUserId; |
@@ -30,12 +48,12 @@ public class AppUser extends EntityBase implements de.bht.beuthbot.model.entitie | @@ -30,12 +48,12 @@ public class AppUser extends EntityBase implements de.bht.beuthbot.model.entitie | ||
30 | 48 | ||
31 | @Override | 49 | @Override |
32 | public <T> T getProperty(String propertyName, Class<T> propertyType) { | 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 | @Override | 54 | @Override |
37 | public void setProperty(String propertyName, Object propertyValue) { | 55 | public void setProperty(String propertyName, Object propertyValue) { |
38 | - | 56 | + GenericEntityAccessFacade.setAttribute(additionalData, propertyName, propertyValue); |
39 | } | 57 | } |
40 | 58 | ||
41 | //@Override | 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 | \ No newline at end of file | 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 | +} |