Skip to content

Commit 2332e52

Browse files
committed
DATACOUCH-12: Refactor mapping layer to accomodate complex types.
This changeset is a complete refactor of the mapping layer to make sure that _class complex type mapping works. It also decouples the actual JSON mapping and abstracts it into a CouchbaseDocument mapping. This ensures forward compatibility by potentially swapping out JSON to a different format.
1 parent 870cbac commit 2332e52

22 files changed

+1858
-301
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ Full documentation is still in the making, so this README outlines the basic ste
99

1010
- Templates
1111
- JavaConfig
12+
- Mapping of arbitrary Objects (Value Objects)
13+
- View support in template
1214
- CRUD Repository (aside *All and count methods, see planned)
1315
- Basic Auditing (JMX)
1416
- Additional: transparent @Cacheable support
1517

1618
### Planned (before 1.0)
1719

18-
- Mapping of arbitrary Objects (Value Objects)
19-
- View support in template
2020
- XML Config (namespace for template + repositories)
2121
- find*-based methods on repositories through Views
2222
- @View annotation for customization

src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626

2727
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
2828
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
29-
import org.springframework.data.couchbase.core.mapping.ConvertedCouchbaseDocument;
29+
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
30+
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
31+
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
3032
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
3133
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
34+
import org.springframework.data.couchbase.core.mapping.CouchbaseStorable;
3235
import org.springframework.data.mapping.context.MappingContext;
3336

3437
import com.couchbase.client.CouchbaseClient;
@@ -45,6 +48,7 @@ public class CouchbaseTemplate implements CouchbaseOperations {
4548
private static final Collection<String> ITERABLE_CLASSES;
4649
private final CouchbaseExceptionTranslator exceptionTranslator =
4750
new CouchbaseExceptionTranslator();
51+
private final TranslationService<Object> translationService;
4852

4953
static {
5054
Set<String> iterableClasses = new HashSet<String>();
@@ -61,8 +65,9 @@ public CouchbaseTemplate(final CouchbaseClient client) {
6165
public CouchbaseTemplate(final CouchbaseClient client,
6266
final CouchbaseConverter converter) {
6367
this.client = client;
64-
this.couchbaseConverter = converter == null ? getDefaultConverter(client) : converter;
65-
this.mappingContext = this.couchbaseConverter.getMappingContext();
68+
couchbaseConverter = converter == null ? getDefaultConverter(client) : converter;
69+
mappingContext = couchbaseConverter.getMappingContext();
70+
translationService = new JacksonTranslationService();
6671
}
6772

6873
private CouchbaseConverter getDefaultConverter(final CouchbaseClient client) {
@@ -72,40 +77,47 @@ private CouchbaseConverter getDefaultConverter(final CouchbaseClient client) {
7277
return converter;
7378
}
7479

80+
private Object translateEncode(final CouchbaseStorable source) {
81+
return translationService.encode(source);
82+
}
83+
84+
private CouchbaseStorable translateDecode(final String source, final CouchbaseStorable target) {
85+
return translationService.decode(source, target);
86+
}
87+
7588
public final void insert(final Object objectToSave) {
7689
ensureNotIterable(objectToSave);
7790

78-
final ConvertedCouchbaseDocument converted =
79-
new ConvertedCouchbaseDocument();
91+
final CouchbaseDocument converted = new CouchbaseDocument();
8092
couchbaseConverter.write(objectToSave, converted);
93+
8194
execute(new BucketCallback<OperationFuture<Boolean>>() {
8295
@Override
8396
public OperationFuture<Boolean> doInBucket() {
8497
return client.add(
85-
converted.getId(), converted.getExpiry(), converted.getRawValue());
98+
converted.getId(), converted.getExpiration(), translateEncode(converted));
8699
}
87100
});
88101
}
89102

90103
public final void insert(final Collection<? extends Object> batchToSave) {
91104
Iterator<? extends Object> iter = batchToSave.iterator();
92-
while(iter.hasNext()) {
105+
while (iter.hasNext()) {
93106
insert(iter.next());
94107
}
95108
}
96109

97110
public void save(final Object objectToSave) {
98111
ensureNotIterable(objectToSave);
99112

100-
final ConvertedCouchbaseDocument converted =
101-
new ConvertedCouchbaseDocument();
113+
final CouchbaseDocument converted = new CouchbaseDocument();
102114
couchbaseConverter.write(objectToSave, converted);
103115

104116
execute(new BucketCallback<OperationFuture<Boolean>>() {
105117
@Override
106118
public OperationFuture<Boolean> doInBucket() {
107119
return client.set(
108-
converted.getId(), converted.getExpiry(), converted.getRawValue());
120+
converted.getId(), converted.getExpiration(), translateEncode(converted));
109121
}
110122
});
111123
}
@@ -120,15 +132,14 @@ public void save(final Collection<? extends Object> batchToSave) {
120132
public void update(final Object objectToSave) {
121133
ensureNotIterable(objectToSave);
122134

123-
final ConvertedCouchbaseDocument converted =
124-
new ConvertedCouchbaseDocument();
135+
final CouchbaseDocument converted = new CouchbaseDocument();
125136
couchbaseConverter.write(objectToSave, converted);
126137

127138
execute(new BucketCallback<OperationFuture<Boolean>>() {
128139
@Override
129140
public OperationFuture<Boolean> doInBucket() {
130141
return client.replace(
131-
converted.getId(), converted.getExpiry(), converted.getRawValue());
142+
converted.getId(), converted.getExpiration(), translateEncode(converted));
132143
}
133144
});
134145

@@ -152,9 +163,9 @@ public String doInBucket() {
152163
if (result == null) {
153164
return null;
154165
}
155-
156-
ConvertedCouchbaseDocument converted = new ConvertedCouchbaseDocument(id, result);
157-
return couchbaseConverter.read(entityClass, converted);
166+
167+
CouchbaseDocument converted = new CouchbaseDocument(id);
168+
return couchbaseConverter.read(entityClass, (CouchbaseDocument) translateDecode(result, converted));
158169
}
159170

160171

@@ -173,9 +184,9 @@ public <T> List<T> findByView(final String designName, final String viewName,
173184

174185
List<T> result = new ArrayList<T>(response.size());
175186
for (ViewRow row : response) {
176-
ConvertedCouchbaseDocument converted =
177-
new ConvertedCouchbaseDocument(row.getId(), (String) row.getDocument());
178-
result.add(couchbaseConverter.read(entityClass, converted));
187+
CouchbaseDocument converted = new CouchbaseDocument(row.getId());
188+
result.add(couchbaseConverter.read(entityClass,
189+
(CouchbaseDocument) translateDecode((String) row.getDocument(), converted)));
179190
}
180191

181192
return result;
@@ -206,7 +217,7 @@ public OperationFuture<Boolean> doInBucket() {
206217
return;
207218
}
208219

209-
final ConvertedCouchbaseDocument converted = new ConvertedCouchbaseDocument();
220+
final CouchbaseDocument converted = new CouchbaseDocument();
210221
couchbaseConverter.write(objectToRemove, converted);
211222

212223
execute(new BucketCallback<OperationFuture<Boolean>>() {
@@ -257,7 +268,7 @@ protected final void ensureNotIterable(Object o) {
257268
}
258269

259270
private RuntimeException potentiallyConvertRuntimeException(final RuntimeException ex) {
260-
RuntimeException resolved = this.exceptionTranslator.translateExceptionIfPossible(ex);
271+
RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
261272
return resolved == null ? ex : resolved;
262273
}
263274

src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter,
2929

3030
protected final GenericConversionService conversionService;
3131
protected EntityInstantiators instantiators = new EntityInstantiators();
32+
protected CustomConversions conversions = new CustomConversions();
3233

3334
public AbstractCouchbaseConverter(
3435
GenericConversionService conversionService) {

src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseConverter.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import org.springframework.data.convert.EntityConverter;
2020
import org.springframework.data.convert.EntityReader;
21-
import org.springframework.data.couchbase.core.mapping.ConvertedCouchbaseDocument;
21+
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
2222
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
2323
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
2424

@@ -27,7 +27,7 @@
2727
*/
2828
public interface CouchbaseConverter extends
2929
EntityConverter<CouchbasePersistentEntity<?>,
30-
CouchbasePersistentProperty, Object, ConvertedCouchbaseDocument>,
31-
CouchbaseWriter<Object, ConvertedCouchbaseDocument>,
32-
EntityReader<Object, ConvertedCouchbaseDocument> {
30+
CouchbasePersistentProperty, Object, CouchbaseDocument>,
31+
CouchbaseWriter<Object, CouchbaseDocument>,
32+
EntityReader<Object, CouchbaseDocument> {
3333
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.couchbase.core.convert;
18+
19+
import org.springframework.context.expression.MapAccessor;
20+
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
21+
import org.springframework.expression.EvaluationContext;
22+
import org.springframework.expression.TypedValue;
23+
24+
import java.util.Map;
25+
26+
/**
27+
* @author Michael Nitschinger
28+
*/
29+
public class CouchbaseDocumentPropertyAccessor extends MapAccessor {
30+
31+
static MapAccessor INSTANCE = new CouchbaseDocumentPropertyAccessor();
32+
33+
@Override
34+
public Class<?>[] getSpecificTargetClasses() {
35+
return new Class[] {CouchbaseDocument.class};
36+
}
37+
38+
@Override
39+
public boolean canRead(EvaluationContext context, Object target, String name) {
40+
return true;
41+
}
42+
43+
@Override
44+
public TypedValue read(EvaluationContext contect, Object target, String name) {
45+
Map<String, Object> source = (Map<String, Object>) target;
46+
47+
Object value = source.get(name);
48+
return value == null ? TypedValue.NULL : new TypedValue(value);
49+
}
50+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.couchbase.core.convert;
18+
19+
import org.springframework.data.convert.TypeMapper;
20+
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
21+
22+
/**
23+
* @author Michael Nitschinger
24+
*/
25+
public interface CouchbaseTypeMapper extends TypeMapper<CouchbaseDocument> {
26+
27+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.couchbase.core.convert;
18+
19+
import org.springframework.data.mapping.model.SimpleTypeHolder;
20+
import org.springframework.util.Assert;
21+
22+
import java.util.ArrayList;
23+
import java.util.HashSet;
24+
import java.util.List;
25+
import java.util.Set;
26+
27+
/**
28+
* Value object to capture custom conversion.
29+
*
30+
* Types that can be mapped directly onto JSON are considered simple ones,
31+
* because they neither need deeper inspection nor nested conversion.
32+
*
33+
* @author Michael Nitschinger
34+
*/
35+
public class CustomConversions {
36+
37+
private final SimpleTypeHolder simpleTypeHolder;
38+
39+
CustomConversions() {
40+
this(new ArrayList<Object>());
41+
}
42+
43+
public CustomConversions(final List<?> converters) {
44+
Assert.notNull(converters);
45+
46+
simpleTypeHolder = new SimpleTypeHolder();
47+
}
48+
49+
public boolean isSimpleType(Class<?> type) {
50+
return simpleTypeHolder.isSimpleType(type);
51+
}
52+
53+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.couchbase.core.convert;
18+
19+
import org.springframework.data.convert.DefaultTypeMapper;
20+
import org.springframework.data.convert.TypeAliasAccessor;
21+
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
22+
23+
/**
24+
* @author Michael Nitschinger
25+
*/
26+
public class DefaultCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
27+
28+
public static final String DEFAULT_TYPE_KEY = "_class";
29+
30+
public DefaultCouchbaseTypeMapper(final String typeKey) {
31+
super(new CouchbaseDocumentTypeAliasAccessor(typeKey));
32+
}
33+
34+
public static final class CouchbaseDocumentTypeAliasAccessor implements TypeAliasAccessor<CouchbaseDocument> {
35+
36+
private final String typeKey;
37+
38+
public CouchbaseDocumentTypeAliasAccessor(final String typeKey) {
39+
this.typeKey = typeKey;
40+
}
41+
42+
@Override
43+
public Object readAliasFrom(final CouchbaseDocument source) {
44+
return source.get(typeKey);
45+
}
46+
47+
@Override
48+
public void writeTypeTo(final CouchbaseDocument sink, final Object alias) {
49+
if (typeKey != null) {
50+
sink.put(typeKey, alias);
51+
}
52+
}
53+
}
54+
55+
}

0 commit comments

Comments
 (0)