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