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 | +} |