diff --git a/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSerializationException.java b/ojcms-beans/src/main/java/de/adito/ojcms/beans/exceptions/bean/BeanSerializationException.java similarity index 62% rename from ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSerializationException.java rename to ojcms-beans/src/main/java/de/adito/ojcms/beans/exceptions/bean/BeanSerializationException.java index ff8bef6..7e08cd3 100644 --- a/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSerializationException.java +++ b/ojcms-beans/src/main/java/de/adito/ojcms/beans/exceptions/bean/BeanSerializationException.java @@ -1,4 +1,4 @@ -package de.adito.ojcms.sql.datasource.util; +package de.adito.ojcms.beans.exceptions.bean; /** * Exception for bean serialization problems. @@ -20,10 +20,11 @@ public BeanSerializationException(String pMessage) /** * Creates a new serialization exception. * - * @param pThrowable the cause for the exception + * @param pMessage a detailed error message + * @param pThrowable the cause of the exception */ - public BeanSerializationException(Throwable pThrowable) + public BeanSerializationException(String pMessage, Throwable pThrowable) { - super(pThrowable); + super(pMessage, pThrowable); } } diff --git a/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/application/OJRestApplication.java b/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/application/OJRestApplication.java index a3d9a93..be8e98c 100644 --- a/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/application/OJRestApplication.java +++ b/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/application/OJRestApplication.java @@ -1,6 +1,6 @@ package de.adito.ojcms.rest.application; -import de.adito.ojcms.rest.serialization.GSONSerializationProvider; +import de.adito.ojcms.rest.serialization.*; import javax.ws.rs.*; import javax.ws.rs.core.Application; @@ -36,6 +36,7 @@ protected OJRestApplication(Class... pRestResources) } providerAndResourceTypes.add(GSONSerializationProvider.class); + providerAndResourceTypes.add(BeanSerializationProvider.class); } @Override diff --git a/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/serialization/BeanSerializationProvider.java b/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/serialization/BeanSerializationProvider.java new file mode 100644 index 0000000..178249d --- /dev/null +++ b/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/serialization/BeanSerializationProvider.java @@ -0,0 +1,102 @@ +package de.adito.ojcms.rest.serialization; + +import de.adito.ojcms.beans.IBean; +import de.adito.ojcms.beans.exceptions.bean.BeanSerializationException; +import de.adito.ojcms.beans.literals.fields.serialization.ISerializableField; +import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; + +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import javax.ws.rs.ext.*; +import java.io.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.util.Map; + +import static de.adito.ojcms.rest.auth.util.GSONFactory.GSON; +import static java.util.stream.Collectors.toMap; + +/** + * JSON serialization provider for {@link IBean} instances. Only possible for beans that contain {@link ISerializableField} only. + * + * @author Simon Danner, 21.04.2020 + */ +@Provider +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class BeanSerializationProvider implements MessageBodyReader, MessageBodyWriter +{ + @Override + public boolean isReadable(Class pType, Type pGenericType, Annotation[] pAnnotations, MediaType pMediaType) + { + return true; + } + + @Override + public IBean readFrom(Class pType, Type pGenericType, Annotation[] pAnnotations, MediaType pMediaType, + MultivaluedMap pHttpHeaders, InputStream pEntityStream) throws IOException, WebApplicationException + { + try (InputStreamReader reader = new InputStreamReader(pEntityStream)) + { + final ParameterizedType mapType = ParameterizedTypeImpl.make(Map.class, new Type[]{String.class, Serializable.class}, IBean.class); + final Map serialBeanValues = GSON.fromJson(reader, mapType); + + try + { + final IBean bean = createEmptyBeanInstance(pType); + for (Map.Entry entry : serialBeanValues.entrySet()) + { + final ISerializableField field = (ISerializableField) bean.getFieldByName(entry.getKey()); + //noinspection unchecked + bean.setValue(field, field.fromPersistent(entry.getValue())); + } + + return bean; + } + catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException pE) + { + throw new BeanSerializationException("Provide a default constructor for " + pType.getName() + "! May be private!", pE); + } + } + } + + @Override + public boolean isWriteable(Class pType, Type pGenericType, Annotation[] pAnnotations, MediaType pMediaType) + { + return true; + } + + @Override + public void writeTo(IBean pBean, Class pType, Type pGenericType, Annotation[] pAnnotations, MediaType pMediaType, + MultivaluedMap pHttpHeaders, OutputStream pEntityStream) throws IOException, WebApplicationException + { + if (pBean.streamFields().allMatch(pField -> pField instanceof ISerializableField)) + throw new BeanSerializationException("Bean of type " + pType.getName() + " has non serializable fields!"); + + //noinspection unchecked + final Map serialBeanValues = pBean.stream() // + .collect(toMap(pTuple -> pTuple.getField().getName(), + pTuple -> ((ISerializableField) pTuple.getField()).toPersistent(pTuple.getValue()))); + + try (PrintWriter writer = new PrintWriter(pEntityStream)) + { + writer.write(GSON.toJson(serialBeanValues)); + } + } + + /** + * Creates an empty bean instance by calling its default constructor. + * + * @param pBeanType the bean type to instantiate + * @return the create bean instance + */ + private static IBean createEmptyBeanInstance(Class pBeanType) + throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException + { + final Constructor defaultConstructor = pBeanType.getDeclaredConstructor(); + if (!defaultConstructor.isAccessible()) + defaultConstructor.setAccessible(true); + + return defaultConstructor.newInstance(); + } +} diff --git a/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/serialization/GSONSerializationProvider.java b/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/serialization/GSONSerializationProvider.java index c038163..4066c2d 100644 --- a/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/serialization/GSONSerializationProvider.java +++ b/ojcms-rest/ojcms-rest-server/src/main/java/de/adito/ojcms/rest/serialization/GSONSerializationProvider.java @@ -24,32 +24,32 @@ public class GSONSerializationProvider implements MessageBodyReader, MessageBodyWriter { @Override - public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) + public boolean isReadable(Class pType, Type pGenericType, Annotation[] pAnnotations, MediaType pMediaType) { return true; } @Override - public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, - MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException + public Object readFrom(Class pType, Type pGenericType, Annotation[] pAnnotations, MediaType pMediaType, + MultivaluedMap pHttpHeaders, InputStream pEntityStream) throws IOException, WebApplicationException { - try (InputStreamReader reader = new InputStreamReader(entityStream)) + try (InputStreamReader reader = new InputStreamReader(pEntityStream)) { - return GSON.fromJson(reader, type); + return GSON.fromJson(reader, pType); } } @Override - public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) + public boolean isWriteable(Class pType, Type pGenericType, Annotation[] pAnnotations, MediaType pMediaType) { return true; } @Override - public void writeTo(Object pObject, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, - MultivaluedMap httpHeaders, OutputStream entityStream) throws WebApplicationException + public void writeTo(Object pObject, Class pType, Type pGenericType, Annotation[] pAnnotations, MediaType pMediaType, + MultivaluedMap pHttpHeaders, OutputStream pEntityStream) throws WebApplicationException { - try (PrintWriter writer = new PrintWriter(entityStream)) + try (PrintWriter writer = new PrintWriter(pEntityStream)) { writer.write(GSON.toJson(pObject)); } diff --git a/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSQLSerializer.java b/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSQLSerializer.java index d89358a..988e3f7 100644 --- a/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSQLSerializer.java +++ b/ojcms-sqldatasource/src/main/java/de/adito/ojcms/sql/datasource/util/BeanSQLSerializer.java @@ -1,5 +1,6 @@ package de.adito.ojcms.sql.datasource.util; +import de.adito.ojcms.beans.exceptions.bean.BeanSerializationException; import de.adito.ojcms.beans.literals.fields.IField; import de.adito.ojcms.beans.literals.fields.serialization.ISerializableField; import de.adito.ojcms.beans.literals.fields.util.*;