diff --git a/pom.xml b/pom.xml index 86e1b5c2..dc62902e 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ org.codehaus.jackson - jackson-core-asl + jackson-mapper-asl 1.9.12 diff --git a/src/main/java/com/couchbase/spring/core/CouchbaseTemplate.java b/src/main/java/com/couchbase/spring/core/CouchbaseTemplate.java index 5be3427f..5c342ba0 100644 --- a/src/main/java/com/couchbase/spring/core/CouchbaseTemplate.java +++ b/src/main/java/com/couchbase/spring/core/CouchbaseTemplate.java @@ -76,7 +76,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { ConvertedCouchbaseDocument converted = new ConvertedCouchbaseDocument(); couchbaseConverter.write(objectToSave, converted); - client.add(converted.getId(), converted.getExpiry(), converted.getValue()); + client.add(converted.getId(), converted.getExpiry(), converted.getRawValue()); } public void insert(Collection batchToSave) { @@ -91,7 +91,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { ConvertedCouchbaseDocument converted = new ConvertedCouchbaseDocument(); couchbaseConverter.write(objectToSave, converted); - client.set(converted.getId(), converted.getExpiry(), converted.getValue()); + client.set(converted.getId(), converted.getExpiry(), converted.getRawValue()); } public void save(Collection batchToSave) { @@ -106,7 +106,7 @@ public class CouchbaseTemplate implements CouchbaseOperations { ConvertedCouchbaseDocument converted = new ConvertedCouchbaseDocument(); couchbaseConverter.write(objectToSave, converted); - client.replace(converted.getId(), converted.getExpiry(), converted.getValue()); + client.replace(converted.getId(), converted.getExpiry(), converted.getRawValue()); } public void update(Collection batchToSave) { diff --git a/src/main/java/com/couchbase/spring/core/convert/AbstractCouchbaseConverter.java b/src/main/java/com/couchbase/spring/core/convert/AbstractCouchbaseConverter.java index cf54d9ff..ecc69ae8 100644 --- a/src/main/java/com/couchbase/spring/core/convert/AbstractCouchbaseConverter.java +++ b/src/main/java/com/couchbase/spring/core/convert/AbstractCouchbaseConverter.java @@ -25,11 +25,13 @@ package com.couchbase.spring.core.convert; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.convert.EntityInstantiators; public abstract class AbstractCouchbaseConverter implements CouchbaseConverter, InitializingBean { protected final GenericConversionService conversionService; + protected EntityInstantiators instantiators = new EntityInstantiators(); public AbstractCouchbaseConverter( GenericConversionService conversionService) { diff --git a/src/main/java/com/couchbase/spring/core/convert/MappingCouchbaseConverter.java b/src/main/java/com/couchbase/spring/core/convert/MappingCouchbaseConverter.java index 5a58134b..373c5549 100644 --- a/src/main/java/com/couchbase/spring/core/convert/MappingCouchbaseConverter.java +++ b/src/main/java/com/couchbase/spring/core/convert/MappingCouchbaseConverter.java @@ -25,26 +25,38 @@ package com.couchbase.spring.core.convert; import com.couchbase.spring.core.mapping.ConvertedCouchbaseDocument; import com.couchbase.spring.core.mapping.CouchbasePersistentEntity; import com.couchbase.spring.core.mapping.CouchbasePersistentProperty; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; +import java.util.Map; import org.codehaus.jackson.JsonEncoding; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.BeanWrapper; +import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mapping.model.ParameterValueProvider; +import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.mapping.model.SpELContext; +import org.springframework.data.mapping.model.SpELExpressionEvaluator; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.data.mapping.PropertyHandler; +import org.springframework.util.Assert; public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implements ApplicationContextAware { @@ -52,6 +64,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter protected ApplicationContext applicationContext; protected final MappingContext, CouchbasePersistentProperty> mappingContext; + protected boolean useFieldAccessOnly = true; @SuppressWarnings("deprecation") public MappingCouchbaseConverter(MappingContext, @@ -67,46 +80,58 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter return mappingContext; } + + private ParameterValueProvider getParameterProvider( + CouchbasePersistentEntity entity, ConvertedCouchbaseDocument source, Object parent) { + + CouchbasePropertyValueProvider provider = new CouchbasePropertyValueProvider(source, parent); + PersistentEntityParameterValueProvider parameterProvider = + new PersistentEntityParameterValueProvider( + entity, provider, parent); + + return parameterProvider; + } + @Override public R read(Class type, ConvertedCouchbaseDocument doc) { - CouchbasePersistentEntity entity = mappingContext.getPersistentEntity(type); + return read(type, doc, null); + } + + public R read(Class type, final ConvertedCouchbaseDocument doc, Object parent) { + final CouchbasePersistentEntity entity = (CouchbasePersistentEntity) + mappingContext.getPersistentEntity(type); - R decoded = null; - try { - decoded = type.getConstructor(String.class).newInstance(doc.getId()); - } catch(Exception e) { - throw new MappingException("Could not instantiate object while converting " - + doc.getId()); - } + ParameterValueProvider provider = + getParameterProvider(entity, doc, parent); + EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); + R instance = instantiator.createInstance(entity, provider); - JsonFactory jsonFactory = new JsonFactory(); - try { - JsonParser parser = jsonFactory.createJsonParser(doc.getValue()); - parser.nextToken(); - while(parser.nextToken() != JsonToken.END_OBJECT) { - String fieldname = parser.getCurrentName(); - parser.nextToken(); - CouchbasePersistentProperty property = entity.getPersistentProperty(fieldname); - if(property == null) { - continue; - } - Field field = type.getDeclaredField(property.getOriginalName()); - field.setAccessible(true); - - if(property.getType().equals(boolean.class)) { - field.set(decoded, parser.getValueAsBoolean()); - } else if(property.getType().equals(String.class)) { - field.set(decoded, parser.getText()); - } else { - throw new MappingException("Unknown type in JSON found: " + property.getType()); - } - } - } catch(Exception e) { - throw new MappingException("Could not read from JSON while converting " - + doc.getId(), e); - } - - return decoded; + final BeanWrapper, R> wrapper = + BeanWrapper.create(instance, conversionService); + final R result = wrapper.getBean(); + + // Set properties not already set in the constructor + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(CouchbasePersistentProperty prop) { + + boolean isConstructorProperty = entity.isConstructorArgument(prop); + boolean hasValueForProperty = doc.containsField(prop.getFieldName()); + + if (!hasValueForProperty || isConstructorProperty) { + return; + } + + Object obj = null; + if(prop.getFieldName() == "id") { + obj = doc.getId(); + } else { + obj = doc.get(prop.getFieldName()); + } + wrapper.setProperty(prop, obj, useFieldAccessOnly); + } + }); + + return result; } @Override @@ -178,7 +203,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter jsonGenerator.writeEndObject(); jsonGenerator.close(); - target.setValue(jsonStream.toString()); + target.setRawValue(jsonStream.toString()); } @Override @@ -186,5 +211,34 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter throws BeansException { this.applicationContext = applicationContext; } + + private class CouchbasePropertyValueProvider implements PropertyValueProvider { + + private final ConvertedCouchbaseDocument source; + private final Object parent; + + public CouchbasePropertyValueProvider(ConvertedCouchbaseDocument source, Object parent) { + Assert.notNull(source); + this.source = source; + this.parent = parent; + } + + public T getPropertyValue(CouchbasePersistentProperty property) { + String fieldName = property.getFieldName(); + T value = null; + + if(fieldName == "id") { + value = (T) source.getId(); + } else { + value = (T) source.get(fieldName); + } + + if (value == null) { + return null; + } + + return value; + } + } } diff --git a/src/main/java/com/couchbase/spring/core/mapping/BasicCouchbasePersistentProperty.java b/src/main/java/com/couchbase/spring/core/mapping/BasicCouchbasePersistentProperty.java index 252cd38e..b2db7d6b 100644 --- a/src/main/java/com/couchbase/spring/core/mapping/BasicCouchbasePersistentProperty.java +++ b/src/main/java/com/couchbase/spring/core/mapping/BasicCouchbasePersistentProperty.java @@ -76,21 +76,5 @@ public class BasicCouchbasePersistentProperty return annotation != null && StringUtils.hasText(annotation.value()) ? annotation.value() : field.getName(); } - - /** - * Return the name of the property. - * - * This overrides the default implementation to make sure that when - * the property is retrieved from a JSON document it can be found - * even when a alias is used. - */ - @Override - public String getName() { - return getFieldName(); - } - - public String getOriginalName() { - return name; - } } diff --git a/src/main/java/com/couchbase/spring/core/mapping/ConvertedCouchbaseDocument.java b/src/main/java/com/couchbase/spring/core/mapping/ConvertedCouchbaseDocument.java index cf6bc368..4ff45218 100644 --- a/src/main/java/com/couchbase/spring/core/mapping/ConvertedCouchbaseDocument.java +++ b/src/main/java/com/couchbase/spring/core/mapping/ConvertedCouchbaseDocument.java @@ -22,26 +22,37 @@ package com.couchbase.spring.core.mapping; +import java.util.HashMap; +import java.util.Map; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; +import org.springframework.data.mapping.model.MappingException; + public class ConvertedCouchbaseDocument { private String id; - private String value; + private String rawValue; private int expiry; + + private Map decoded; public ConvertedCouchbaseDocument() { this("", "", 0); } - public ConvertedCouchbaseDocument(String id, String value) { - this(id, value, 0); + public ConvertedCouchbaseDocument(String id, String rawValue) { + this(id, rawValue, 0); } - public ConvertedCouchbaseDocument(String id, String value, int expiry) { + public ConvertedCouchbaseDocument(String id, String rawValue, int expiry) { this.id = id; - this.value = value; + this.rawValue = rawValue; this.expiry = expiry; + this.decoded = new HashMap(); + parseJson(); } public void setId(String id) { @@ -52,12 +63,14 @@ public class ConvertedCouchbaseDocument { return id; } - public String getValue() { - return value; + public String getRawValue() { + return rawValue; } - public void setValue(String value) { - this.value = value; + public void setRawValue(String value) { + this.rawValue = value; + parseJson(); + } public int getExpiry() { @@ -67,5 +80,26 @@ public class ConvertedCouchbaseDocument { public void setExpiry(int expiry) { this.expiry = expiry; } + + public boolean containsField(String fieldname) { + return decoded.containsKey(fieldname); + } + + public Object get(String fieldname) { + return decoded.get(fieldname); + } + + private void parseJson() { + ObjectMapper mapper = new ObjectMapper(); + try { + if(!getRawValue().isEmpty()) { + Map converted = mapper.readValue(getRawValue(), + new TypeReference>() { }); + this.decoded = converted; + } + } catch(Exception e) { + throw new MappingException("Error while decoding JSON object.", e); + } + } } diff --git a/src/main/java/com/couchbase/spring/core/mapping/CouchbasePersistentProperty.java b/src/main/java/com/couchbase/spring/core/mapping/CouchbasePersistentProperty.java index 536f3c33..cc26e5a2 100644 --- a/src/main/java/com/couchbase/spring/core/mapping/CouchbasePersistentProperty.java +++ b/src/main/java/com/couchbase/spring/core/mapping/CouchbasePersistentProperty.java @@ -35,6 +35,5 @@ public interface CouchbasePersistentProperty extends * custom annotation. */ String getFieldName(); - - String getOriginalName(); + } diff --git a/src/test/java/com/couchbase/spring/core/CouchbaseTemplateTest.java b/src/test/java/com/couchbase/spring/core/CouchbaseTemplateTest.java index 993c88b6..e7c50181 100644 --- a/src/test/java/com/couchbase/spring/core/CouchbaseTemplateTest.java +++ b/src/test/java/com/couchbase/spring/core/CouchbaseTemplateTest.java @@ -107,7 +107,6 @@ public class CouchbaseTemplateTest { assertEquals(id, found.getId()); assertEquals(name, found.getName()); assertEquals(active, found.getActive()); - } /**