diff --git a/CHANGELOG.md b/CHANGELOG.md index 2adb8716c0..92846c8497 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Not yet released * Only expose `Serializable` `EntityViewManager` wrapper in special methods or callbacks * Support `@PostCreate` and other lifecycle methods with default and even private visibility * Support `@ViewFilter` inheritance +* Fix collection deserialization issues with Jackson ### Backwards-incompatible changes diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java index b45a426f6c..30b4b2498a 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java @@ -63,11 +63,13 @@ import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; +import javassist.CtMember; import javassist.CtMethod; import javassist.CtPrimitiveType; import javassist.Modifier; import javassist.NotFoundException; import javassist.bytecode.AccessFlag; +import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.AttributeInfo; import javassist.bytecode.BadBytecode; import javassist.bytecode.Bytecode; @@ -81,6 +83,8 @@ import javassist.bytecode.SignatureAttribute; import javassist.bytecode.StackMap; import javassist.bytecode.StackMapTable; +import javassist.bytecode.annotation.Annotation; +import javassist.bytecode.annotation.EnumMemberValue; import javassist.compiler.CompileError; import javassist.compiler.JvstCodeGen; import javassist.compiler.Lex; @@ -385,6 +389,13 @@ private Class createProxyClass(EntityViewManager entityViewMana CtClass cc = pool.makeClass(proxyClassName); CtClass superCc; +// ConstPool constPool = cc.getClassFile().getConstPool(); +// AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); +// Annotation annotation = new Annotation("javax.xml.bind.annotation.XmlAccessorType", constPool); +// annotation.addMemberValue("value", new EnumMemberValue(constPool.addUtf8Info("javax.xml.bind.annotation.XmlAccessType"), constPool.addUtf8Info("PROPERTY"), constPool)); +// annotationsAttribute.addAnnotation(annotation); +// cc.getClassFile().addAttribute(annotationsAttribute); + ClassPath classPath = new ClassClassPath(clazz); pool.insertClassPath(classPath); @@ -423,6 +434,7 @@ private Class createProxyClass(EntityViewManager entityViewMana cc.addInterface(pool.get(DirtyStateTrackable.class.getName())); initialStateField = new CtField(pool.get(Object[].class.getName()), "$$_initialState", cc); initialStateField.setModifiers(getModifiers(false)); + annotateInternalField(initialStateField); cc.addField(initialStateField); addGetter(cc, initialStateField, "$$_getInitialState"); @@ -432,6 +444,7 @@ private Class createProxyClass(EntityViewManager entityViewMana cc.addInterface(pool.get(DirtyTracker.class.getName())); mutableStateField = new CtField(pool.get(Object[].class.getName()), "$$_mutableState", cc); mutableStateField.setModifiers(getModifiers(false)); + annotateInternalField(mutableStateField); cc.addField(mutableStateField); CtField initializedField = new CtField(CtClass.booleanType, "$$_initialized", cc); @@ -441,12 +454,15 @@ private Class createProxyClass(EntityViewManager entityViewMana readOnlyParentsField = new CtField(pool.get(List.class.getName()), "$$_readOnlyParents", cc); readOnlyParentsField.setModifiers(getModifiers(true)); readOnlyParentsField.setGenericSignature(Descriptor.of(List.class.getName()) + "<" + Descriptor.of(Object.class.getName()) + ">;"); + annotateInternalField(readOnlyParentsField); cc.addField(readOnlyParentsField); parentField = new CtField(pool.get(DirtyTracker.class.getName()), "$$_parent", cc); parentField.setModifiers(getModifiers(true)); + annotateInternalField(parentField); cc.addField(parentField); parentIndexField = new CtField(CtClass.intType, "$$_parentIndex", cc); parentIndexField.setModifiers(getModifiers(true)); + annotateInternalField(parentIndexField); cc.addField(parentIndexField); dirtyChecking = true; @@ -479,6 +495,8 @@ private Class createProxyClass(EntityViewManager entityViewMana idAttribute = (AbstractMethodAttribute) viewType.getIdAttribute(); versionAttribute = (AbstractMethodAttribute) viewType.getVersionAttribute(); idField = addMembersForAttribute(idAttribute, clazz, cc, null, false, true, mutableStateField != null); + // TODO: Copy JAXB annotations from getter and setter? + annotateField(idField, "javax.xml.bind.annotation.XmlElement"); fieldMap.put(idAttribute.getName(), idField); attributeFields[0] = idField; attributeTypes[0] = idField.getType(); @@ -533,6 +551,7 @@ private Class createProxyClass(EntityViewManager entityViewMana } else { dirtyField = new CtField(CtClass.longType, "$$_dirty", cc); dirtyField.setModifiers(getModifiers(true)); + annotateInternalField(dirtyField); cc.addField(dirtyField); boolean allSupportDirtyTracking = true; @@ -572,6 +591,7 @@ private Class createProxyClass(EntityViewManager entityViewMana if (hasEmptyConstructor) { // Create constructor for create models + cc.addConstructor(createEmptyConstructor(cc)); cc.addConstructor(createCreateConstructor(entityViewManager, managedViewType, cc, attributeFields, attributeTypes, idField, initialStateField, mutableStateField, methodAttributes, mutableAttributeCount, alwaysDirtyMask, unsafe)); } @@ -617,6 +637,13 @@ private Class createProxyClass(EntityViewManager entityViewMana } } + private CtConstructor createEmptyConstructor(CtClass cc) throws CannotCompileException { + CtConstructor constructor = new CtConstructor(new CtClass[0], cc); + constructor.setModifiers(Modifier.PUBLIC); + constructor.setBody("this((" + cc.getName() + ") null, " + cc.getName() + "#" + SerializableEntityViewManager.EVM_FIELD_NAME + ".getOptionalParameters());"); + return constructor; + } + private void createSerializationSubclass(ManagedViewTypeImplementor managedViewType, CtClass cc) throws Exception { boolean hasSelfConstructor = false; OUTER: for (MappingConstructor constructor : managedViewType.getConstructors()) { @@ -1148,6 +1175,7 @@ private void addIsNewMembers(ManagedViewType managedViewType, CtClass cc, Cla if (managedViewType.isCreatable()) { CtField isNewField = new CtField(CtClass.booleanType, "$$_isNew", cc); isNewField.setModifiers(getModifiers(true)); + annotateInternalField(isNewField); cc.addField(isNewField); addGetter(cc, isNewField, "$$_isNew"); @@ -1162,6 +1190,21 @@ private void addIsNewMembers(ManagedViewType managedViewType, CtClass cc, Cla } } } + + private void annotateInternalField(CtMember member) { + annotateField(member, "javax.xml.bind.annotation.XmlTransient"); + } + + private void annotateField(CtMember member, String fqn) { +// ConstPool constPool = member.getDeclaringClass().getClassFile().getConstPool(); +// AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); +// annotationsAttribute.addAnnotation(new Annotation(fqn, constPool)); +// if (member instanceof CtField) { +// ((CtField) member).getFieldInfo().addAttribute(annotationsAttribute); +// } else { +// ((CtMethod) member).getMethodInfo().addAttribute(annotationsAttribute); +// } + } private CtMethod addGetter(CtClass cc, CtField field, String methodName) throws CannotCompileException { return addGetter(cc, field, methodName, field.getFieldInfo().getDescriptor(), false); @@ -1240,6 +1283,9 @@ private void createGettersAndSetters(AbstractMethodAttribute attribute, Cl List bridgeGetters = getBridgeGetters(clazz, attribute, getter); CtMethod attributeGetter = addGetter(cc, attributeField, getter.getName()); + if (isId) { + annotateInternalField(attributeGetter); + } if (genericSignature != null) { String getterGenericSignature = "()" + genericSignature; diff --git a/entity-view/processor/src/main/java/com/blazebit/persistence/view/processor/ImplementationClassWriter.java b/entity-view/processor/src/main/java/com/blazebit/persistence/view/processor/ImplementationClassWriter.java index ad0e528518..4d467a43a3 100644 --- a/entity-view/processor/src/main/java/com/blazebit/persistence/view/processor/ImplementationClassWriter.java +++ b/entity-view/processor/src/main/java/com/blazebit/persistence/view/processor/ImplementationClassWriter.java @@ -1434,6 +1434,7 @@ private static void printConstructors(StringBuilder sb, MetaEntityView entity, C boolean postLoadReflection = preparePostLoad(sb, entity, context); if (entity.hasEmptyConstructor()) { if (entity.getMembers().size() > 0) { + printEmptyConstructor(sb, entity, context); printCreateConstructor(sb, entity, context); sb.append(NEW_LINE); } @@ -1760,6 +1761,12 @@ private static void printTupleAssignmentConstructor(StringBuilder sb, MetaConstr sb.append(NEW_LINE); } + private static void printEmptyConstructor(StringBuilder sb, MetaEntityView entity, Context context) { + sb.append(" public ").append(entity.getSimpleName()).append(IMPL_CLASS_NAME_SUFFIX).append("() {").append(NEW_LINE); + sb.append(" super((").append(entity.getSimpleName()).append(IMPL_CLASS_NAME_SUFFIX).append(") null, ").append(EVM_FIELD_NAME).append(".getOptionalParameters());").append(NEW_LINE); + sb.append(" }").append(NEW_LINE); + } + private static void printCreateConstructor(StringBuilder sb, MetaEntityView entity, Context context) { boolean postCreateReflection = false; if (entity.getPostCreate() != null) { diff --git a/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/controller/CatRestController.java b/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/controller/CatRestController.java index 57e08d5663..87d188dfc5 100644 --- a/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/controller/CatRestController.java +++ b/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/controller/CatRestController.java @@ -76,14 +76,14 @@ public class CatRestController { @Autowired private CatViewRepository catViewRepository; - @RequestMapping(path = "/cats", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(path = "/cats", method = RequestMethod.POST, consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) public ResponseEntity createCat(@RequestBody CatCreateView catCreateView) { catViewRepository.save(catCreateView); return ResponseEntity.ok(catCreateView.getId().toString()); } - @RequestMapping(path = "/cats/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(path = "/cats/{id}", method = RequestMethod.PUT, consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) public ResponseEntity updateCat(@PathVariable("id") long id, @RequestBody CatUpdateView catUpdateView) { catViewRepository.save(catUpdateView); diff --git a/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/view/CatCreateView.java b/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/view/CatCreateView.java index db1e3ddfb3..d64b6cc07e 100644 --- a/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/view/CatCreateView.java +++ b/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/view/CatCreateView.java @@ -20,12 +20,15 @@ import com.blazebit.persistence.view.CreatableEntityView; import com.blazebit.persistence.view.EntityView; +import javax.xml.bind.annotation.XmlRootElement; + /** * @author Christian Beikov * @since 1.4.0 */ @CreatableEntityView @EntityView(Cat.class) +@XmlRootElement(name = "cat") public interface CatCreateView extends CatUpdateView { PersonIdView getOwner(); diff --git a/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/view/CatUpdateView.java b/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/view/CatUpdateView.java index 7d3f7f7e43..8e086a5f92 100644 --- a/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/view/CatUpdateView.java +++ b/examples/spring-data-webmvc/src/main/java/com/blazebit/persistence/examples/spring/data/webmvc/view/CatUpdateView.java @@ -20,12 +20,16 @@ import com.blazebit.persistence.view.EntityView; import com.blazebit.persistence.view.UpdatableEntityView; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + /** * @author Christian Beikov * @since 1.4.0 */ @UpdatableEntityView @EntityView(Cat.class) +@XmlRootElement(name = "cat") public interface CatUpdateView extends CatSimpleView { void setName(String name); diff --git a/integration/jackson/src/main/java/com/blazebit/persistence/integration/jackson/EntityViewAwareObjectMapper.java b/integration/jackson/src/main/java/com/blazebit/persistence/integration/jackson/EntityViewAwareObjectMapper.java index da19c4d2ce..9cfb3f0889 100644 --- a/integration/jackson/src/main/java/com/blazebit/persistence/integration/jackson/EntityViewAwareObjectMapper.java +++ b/integration/jackson/src/main/java/com/blazebit/persistence/integration/jackson/EntityViewAwareObjectMapper.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; @@ -33,6 +34,7 @@ import java.lang.reflect.Method; import java.util.Collection; +import java.util.Map; /** * @author Christian Beikov @@ -61,6 +63,10 @@ public JsonDeserializer modifyDeserializer(DeserializationConfig config, Bean } }); objectMapper.registerModule(module); + // We need this property, otherwise Jackson thinks it can use non-visible setters as mutators + objectMapper.configure(MapperFeature.INFER_PROPERTY_MUTATORS, false); + // Obviously, we don't want fields to be set which are final because these are read-only + objectMapper.configure(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS, false); objectMapper.setVisibility(new VisibilityChecker.Std(JsonAutoDetect.Visibility.DEFAULT) { @Override public boolean isGetterVisible(Method m) { @@ -81,6 +87,20 @@ public boolean isIsGetterVisible(Method m) { public boolean isIsGetterVisible(AnnotatedMethod m) { return metamodel.managedView(m.getDeclaringClass()) == null && super.isGetterVisible(m); } + + // We make setters for collections "invisible" so that is uses a SetterlessProperty to always merge values into existing collections + + @Override + public boolean isSetterVisible(Method m) { + Class rawParameterType; + return super.isSetterVisible(m) && !Collection.class.isAssignableFrom(rawParameterType = m.getParameterTypes()[0]) && !Map.class.isAssignableFrom(rawParameterType); + } + + @Override + public boolean isSetterVisible(AnnotatedMethod m) { + Class rawParameterType; + return super.isSetterVisible(m) && !Collection.class.isAssignableFrom(rawParameterType = m.getRawParameterType(0)) && !Map.class.isAssignableFrom(rawParameterType); + } }); this.objectMapper = objectMapper; diff --git a/integration/jackson/src/test/java/com/blazebit/persistence/integration/jackson/EntityViewAwareObjectMapperTest.java b/integration/jackson/src/test/java/com/blazebit/persistence/integration/jackson/EntityViewAwareObjectMapperTest.java index ac31f42aa3..c11110f234 100644 --- a/integration/jackson/src/test/java/com/blazebit/persistence/integration/jackson/EntityViewAwareObjectMapperTest.java +++ b/integration/jackson/src/test/java/com/blazebit/persistence/integration/jackson/EntityViewAwareObjectMapperTest.java @@ -36,6 +36,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; +import java.util.Set; /** * @author Christian Beikov @@ -233,4 +234,49 @@ interface CreatableAndUpdatableViewWithNested { CreatableAndUpdatableViewWithSetters getParent(); void setParent(CreatableAndUpdatableViewWithSetters parent); } + + @Test + public void testCreatableWithCollection() throws Exception { + EntityViewAwareObjectMapper mapper = mapper(CreatableWithCollection.class, CreatableAndUpdatableViewWithSetters.class, NameView.class); + ObjectReader objectReader = mapper.readerFor(mapper.getObjectMapper().constructType(CreatableWithCollection.class)); + CreatableWithCollection view = objectReader.readValue("{\"name\": \"test\", \"children\": [{\"name\": \"parent\"}]}"); + Assert.assertTrue(((EntityViewProxy) view).$$_isNew()); + Assert.assertEquals("test", view.getName()); + Assert.assertEquals(mapper.getEntityViewManager().getChangeModel(view).get("children").getInitialState(), view.getChildren()); + Assert.assertEquals(1, view.getChildren().size()); + } + + @EntityView(SomeEntity.class) + @CreatableEntityView + @UpdatableEntityView + interface CreatableWithCollection { + @IdMapping + long getId(); + String getName(); + void setName(String name); + Set getChildren(); + } + + @Test + public void testCreatableWithCollectionWithSetter() throws Exception { + EntityViewAwareObjectMapper mapper = mapper(CreatableWithCollectionWithSetter.class, CreatableAndUpdatableViewWithSetters.class, NameView.class); + ObjectReader objectReader = mapper.readerFor(mapper.getObjectMapper().constructType(CreatableWithCollectionWithSetter.class)); + CreatableWithCollectionWithSetter view = objectReader.readValue("{\"name\": \"test\", \"children\": [{\"name\": \"parent\"}]}"); + Assert.assertTrue(((EntityViewProxy) view).$$_isNew()); + Assert.assertEquals("test", view.getName()); + Assert.assertEquals(mapper.getEntityViewManager().getChangeModel(view).get("children").getInitialState(), view.getChildren()); + Assert.assertEquals(1, view.getChildren().size()); + } + + @EntityView(SomeEntity.class) + @CreatableEntityView + @UpdatableEntityView + interface CreatableWithCollectionWithSetter { + @IdMapping + long getId(); + String getName(); + void setName(String name); + Set getChildren(); + void setChildren(Set children); + } } diff --git a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/BlazePersistenceWebConfiguration.java b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/BlazePersistenceWebConfiguration.java index 591b3d0acf..e08eb15c31 100644 --- a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/BlazePersistenceWebConfiguration.java +++ b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/BlazePersistenceWebConfiguration.java @@ -18,9 +18,6 @@ import com.blazebit.persistence.integration.jackson.EntityViewIdValueAccessor; import com.blazebit.persistence.spring.data.webmvc.KeysetPageableArgumentResolver; -import com.blazebit.persistence.spring.data.webmvc.impl.json.EntityViewAwareMappingJackson2HttpMessageConverter; -import com.blazebit.persistence.spring.data.webmvc.impl.json.EntityViewIdHandlerInterceptor; -import com.blazebit.persistence.spring.data.webmvc.impl.json.EntityViewIdValueHolder; import com.blazebit.persistence.view.EntityViewManager; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -73,6 +70,7 @@ public void addArgumentResolvers(List argumentRes public void extendMessageConverters(List> converters) { // Add it to the beginning so it has precedence over the builtin converters.add(0, new EntityViewAwareMappingJackson2HttpMessageConverter(entityViewManager, idAttributeAccessor())); + converters.add(1, new EntityViewAwareJaxb2RootElementHttpMessageConverter(entityViewManager)); } @Override diff --git a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewAwareJaxb2RootElementHttpMessageConverter.java b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewAwareJaxb2RootElementHttpMessageConverter.java new file mode 100644 index 0000000000..4f9bdd6b50 --- /dev/null +++ b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewAwareJaxb2RootElementHttpMessageConverter.java @@ -0,0 +1,146 @@ +/* + * Copyright 2014 - 2019 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.spring.data.webmvc.impl; + +import com.blazebit.persistence.view.EntityViewManager; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; +import org.springframework.util.Assert; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.UnmarshalException; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.transform.Source; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author Christian Beikov + * @since 1.5.0 + */ +public class EntityViewAwareJaxb2RootElementHttpMessageConverter extends Jaxb2RootElementHttpMessageConverter { + + private final ConcurrentMap, JAXBContext> jaxbContexts = new ConcurrentHashMap, JAXBContext>(64); + private final EntityViewManager entityViewManager; + + public EntityViewAwareJaxb2RootElementHttpMessageConverter(EntityViewManager entityViewManager) { + this.entityViewManager = entityViewManager; + } + + @Override + public boolean canRead(Class clazz, MediaType mediaType) { + if (entityViewManager.getMetamodel().view(clazz) == null) { + return false; + } + return super.canRead(clazz, mediaType); + } + + @Override + protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException { + try { + Class implementationClass = entityViewManager.getReference(clazz, null).getClass(); + source = processSource(source); + // TODO: Need the serialization class here instead + Unmarshaller unmarshaller = createUnmarshallerInternal(implementationClass); + if (clazz.isAnnotationPresent(XmlRootElement.class)) { + return unmarshaller.unmarshal(source); + } + else { + JAXBElement jaxbElement = unmarshaller.unmarshal(source, implementationClass); + return jaxbElement.getValue(); + } + } + catch (NullPointerException ex) { + if (!isSupportDtd()) { + throw new HttpMessageNotReadableException("NPE while unmarshalling. " + + "This can happen on JDK 1.6 due to the presence of DTD " + + "declarations, which are disabled.", ex); + } + throw ex; + } + catch (UnmarshalException ex) { + throw new HttpMessageNotReadableException("Could not unmarshal to [" + clazz + "]: " + ex.getMessage(), ex); + + } + catch (JAXBException ex) { + throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex); + } + } + + protected Marshaller createMarshallerInternal(Class clazz) { + try { + JAXBContext jaxbContext = getJaxbContextInternal(clazz); + Marshaller marshaller = jaxbContext.createMarshaller(); + customizeMarshaller(marshaller); + return marshaller; + } + catch (JAXBException ex) { + throw new HttpMessageConversionException( + "Could not create Marshaller for class [" + clazz + "]: " + ex.getMessage(), ex); + } + } + + protected Unmarshaller createUnmarshallerInternal(Class clazz) throws JAXBException { + try { + JAXBContext jaxbContext = getJaxbContextInternal(clazz); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + // Register adapters + try { + unmarshaller.setAdapter((XmlAdapter) Class.forName("com.blazebit.persistence.examples.spring.data.webmvc.view.CatAdapter").newInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } + customizeUnmarshaller(unmarshaller); + return unmarshaller; + } + catch (JAXBException ex) { + throw new HttpMessageConversionException( + "Could not create Unmarshaller for class [" + clazz + "]: " + ex.getMessage(), ex); + } + } + + protected JAXBContext getJaxbContextInternal(Class clazz) { + Assert.notNull(clazz, "'clazz' must not be null"); + JAXBContext jaxbContext = this.jaxbContexts.get(clazz); + if (jaxbContext == null) { + try { +// Map, Class> map = new HashMap<>(); +// map.put(DirtyTracker.class, Object.class); +// jaxbContext = JAXBContext.newInstance(new Class[]{ clazz }, Collections.singletonMap("com.sun.xml.bind.subclassReplacements", map)); + jaxbContext = JAXBContext.newInstance(new Class[]{ clazz }); + this.jaxbContexts.putIfAbsent(clazz, jaxbContext); + } + catch (JAXBException ex) { + throw new HttpMessageConversionException( + "Could not instantiate JAXBContext for class [" + clazz + "]: " + ex.getMessage(), ex); + } + } + return jaxbContext; + } +} diff --git a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewAwareMappingJackson2HttpMessageConverter.java b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewAwareMappingJackson2HttpMessageConverter.java similarity index 98% rename from integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewAwareMappingJackson2HttpMessageConverter.java rename to integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewAwareMappingJackson2HttpMessageConverter.java index 05ab6e3bb0..199b33ddad 100644 --- a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewAwareMappingJackson2HttpMessageConverter.java +++ b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewAwareMappingJackson2HttpMessageConverter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.blazebit.persistence.spring.data.webmvc.impl.json; +package com.blazebit.persistence.spring.data.webmvc.impl; import com.blazebit.persistence.integration.jackson.EntityViewAwareObjectMapper; import com.blazebit.persistence.integration.jackson.EntityViewIdValueAccessor; diff --git a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewIdHandlerInterceptor.java b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewIdHandlerInterceptor.java similarity index 98% rename from integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewIdHandlerInterceptor.java rename to integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewIdHandlerInterceptor.java index 0b68aa06af..0bf77de9f7 100644 --- a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewIdHandlerInterceptor.java +++ b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewIdHandlerInterceptor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.blazebit.persistence.spring.data.webmvc.impl.json; +package com.blazebit.persistence.spring.data.webmvc.impl; import com.blazebit.persistence.spring.data.webmvc.EntityViewId; import com.blazebit.persistence.view.EntityViewManager; diff --git a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewIdValueHolder.java b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewIdValueHolder.java similarity index 95% rename from integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewIdValueHolder.java rename to integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewIdValueHolder.java index 9f46f4be45..c43a4ca9d6 100644 --- a/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/json/EntityViewIdValueHolder.java +++ b/integration/spring-data/webmvc/src/main/java/com/blazebit/persistence/spring/data/webmvc/impl/EntityViewIdValueHolder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.blazebit.persistence.spring.data.webmvc.impl.json; +package com.blazebit.persistence.spring.data.webmvc.impl; import com.blazebit.persistence.integration.jackson.EntityViewIdValueAccessor; import com.fasterxml.jackson.core.JsonParser;