From f1c0157feef2f4ee524e0acb312d5ea9c92fd21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Mon, 29 Jun 2015 19:49:26 +0200 Subject: [PATCH 1/3] fix template setting version even if no such field --- .../data/couchbase/core/CouchbaseTemplate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index 8f31c1fa..dfbce61a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -359,7 +359,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP final String operationDesc = failOnExist ? "Insert" : failOnMissing ? "Update" : "Upsert"; final BeanWrapper beanWrapper = BeanWrapper.create(objectToPersist, converter.getConversionService()); - CouchbasePersistentEntity persistentEntity = mappingContext.getPersistentEntity(objectToPersist.getClass()); + final CouchbasePersistentEntity persistentEntity = mappingContext.getPersistentEntity(objectToPersist.getClass()); final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty(); final Long version = versionProperty != null ? beanWrapper.getProperty(versionProperty, Long.class) : null; @@ -384,7 +384,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP storedDoc = client.insert(doc, persistTo, replicateTo); } - if (storedDoc != null && storedDoc.cas() != 0) { + if (persistentEntity.hasVersionProperty() && storedDoc != null && storedDoc.cas() != 0) { //inject new cas into the bean beanWrapper.setProperty(versionProperty, storedDoc.cas()); return true; From 81d13ff6f75de34ec348e51977e6d7e0926ca0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Mon, 29 Jun 2015 19:54:08 +0200 Subject: [PATCH 2/3] allow adhoc unmarshalling for individual n1ql results --- .../core/CouchbaseTemplateTests.java | 75 +++++++++++++++---- .../couchbase/core/CouchbaseTemplate.java | 14 +++- .../JacksonTranslationService.java | 12 ++- .../translation/TranslationService.java | 12 +++ .../JacksonTranslationServiceTests.java | 14 ++++ 5 files changed, 110 insertions(+), 17 deletions(-) diff --git a/src/integration/java/org/springframework/data/couchbase/core/CouchbaseTemplateTests.java b/src/integration/java/org/springframework/data/couchbase/core/CouchbaseTemplateTests.java index 67624d4f..e2acebb6 100644 --- a/src/integration/java/org/springframework/data/couchbase/core/CouchbaseTemplateTests.java +++ b/src/integration/java/org/springframework/data/couchbase/core/CouchbaseTemplateTests.java @@ -17,12 +17,15 @@ package org.springframework.data.couchbase.core; import static com.couchbase.client.java.query.Select.select; +import static com.couchbase.client.java.query.dsl.Expression.i; +import static com.couchbase.client.java.query.dsl.Expression.s; import static com.couchbase.client.java.query.dsl.Expression.x; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; @@ -41,7 +44,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; import org.junit.runner.RunWith; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.OptimisticLockingFailureException; @@ -219,7 +221,7 @@ public class CouchbaseTemplateTests { @Test public void shouldQueryRaw() { - Query query = Query.simple(select("name").from(Expression.i(client.name())) + Query query = Query.simple(select("name").from(i(client.name())) .where(x("name").isNotMissing())); QueryResult queryResult = template.queryN1QL(query); @@ -228,15 +230,22 @@ public class CouchbaseTemplateTests { assertFalse(queryResult.allRows().isEmpty()); } - @Test(expected = NotImplementedException.class) //TODO remove when implemented + @Test public void shouldQueryWithMapping() { - Query query = Query.simple(select("name").from(Expression.i(client.name())) - .where(x("name").isNotMissing())); + FullFragment ff1 = new FullFragment("fullFragment1", 1, "fullFragment", "test1"); + FullFragment ff2 = new FullFragment("fullFragment2", 2, "fullFragment", "test2"); + template.save(Arrays.asList(ff1, ff2)); - List fragments = template.findByN1QL(query, BeerFragment.class); + Query query = Query.simple(select(i("value")) //"value" is a n1ql keyword apparently + .from(i(client.name())) + .where(x("type").eq(s("fullFragment")) + .and(x("criteria").gt(1)))); + + List fragments = template.findByN1QL(query, Fragment.class); assertNotNull(fragments); assertFalse(fragments.isEmpty()); - //TODO assert the content of the fragments, etc... + assertEquals(1, fragments.size()); + assertEquals("test2", fragments.get(0).value); } @Test @@ -580,15 +589,55 @@ public class CouchbaseTemplateTests { } } - static class BeerFragment { - private String name; + @Document + static class FullFragment { - public String getName() { - return name; + @Id + private String id; + + private long criteria; + + private String type; + + private String value; + + public FullFragment(String id, long criteria, String type, String value) { + this.id = id; + this.criteria = criteria; + this.type = type; + this.value = value; } - public void setName(String name) { - this.name = name; + public String getId() { + return id; + } + + public long getCriteria() { + return criteria; + } + + public String getType() { + return type; + } + + public String getValue() { + return value; + } + + public void setCriteria(long criteria) { + this.criteria = criteria; + } + + public void setType(String type) { + this.type = type; + } + + public void setValue(String value) { + this.value = value; } } + + static class Fragment { + public String value; + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index dfbce61a..d4a99b1b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -34,12 +34,12 @@ import com.couchbase.client.java.document.RawJsonDocument; import com.couchbase.client.java.error.CASMismatchException; import com.couchbase.client.java.query.Query; import com.couchbase.client.java.query.QueryResult; +import com.couchbase.client.java.query.QueryRow; import com.couchbase.client.java.view.ViewQuery; import com.couchbase.client.java.view.ViewResult; import com.couchbase.client.java.view.ViewRow; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -286,9 +286,17 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP @Override public List findByN1QL(Query n1ql, Class entityClass) { - //TODO find a way of mapping content to T + QueryResult queryResult = queryN1QL(n1ql); + + List allRows = queryResult.allRows(); + List result = new ArrayList(allRows.size()); + for (QueryRow row : allRows) { + String json = row.value().toString(); + T decoded = translationService.decodeFragment(json, entityClass); + result.add(decoded); + } //TODO error handling - throw new NotImplementedException(); + return result; } @Override diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationService.java b/src/main/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationService.java index a178f679..84968d6f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationService.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationService.java @@ -236,7 +236,17 @@ public class JacksonTranslationService implements TranslationService, Initializi case VALUE_NULL: return null; default: - throw new MappingException("Could not decode primitve value " + token); + throw new MappingException("Could not decode primitive value " + token); + } + } + + @Override + public T decodeFragment(String source, Class target) { + try { + return objectMapper.readValue(source, target); + } + catch (IOException e) { + throw new RuntimeException("Cannot decode ad-hoc JSON", e); } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/translation/TranslationService.java b/src/main/java/org/springframework/data/couchbase/core/convert/translation/TranslationService.java index 00ca6271..cc12a912 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/translation/TranslationService.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/translation/TranslationService.java @@ -16,6 +16,8 @@ package org.springframework.data.couchbase.core.convert.translation; +import com.couchbase.client.java.query.QueryRow; + import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.mapping.CouchbaseStorable; @@ -42,4 +44,14 @@ public interface TranslationService { * @return a properly populated document to work with. */ CouchbaseStorable decode(String source, CouchbaseStorable target); + + /** + * Decodes an ad-hoc JSON object into a corresponding "case" class. + * + * @param source the JSON for the ad-hoc JSON object (from a N1QL {@link QueryRow} for instance). + * @param target the target class information. + * @param the target class. + * @return an ad-hoc instance of the decoded JSON into the corresponding "case" class. + */ + T decodeFragment(String source, Class target); } diff --git a/src/test/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationServiceTests.java b/src/test/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationServiceTests.java index dad6ed97..f566ffaf 100644 --- a/src/test/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationServiceTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationServiceTests.java @@ -17,6 +17,7 @@ package org.springframework.data.couchbase.core.convert.translation; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import org.junit.Before; import org.junit.Test; @@ -35,6 +36,7 @@ public class JacksonTranslationServiceTests { @Before public void setup() { service = new JacksonTranslationService(); + ((JacksonTranslationService) service).afterPropertiesSet(); } @Test @@ -52,4 +54,16 @@ public class JacksonTranslationServiceTests { service.decode(source, target); assertEquals("русский", target.get("language")); } + + @Test + public void shouldDecodeAdHocFragment() { + String source = "{\"language\":\"french\"}"; + LanguageFragment f = service.decodeFragment(source, LanguageFragment.class); + assertNotNull(f); + assertEquals("french", f.language); + } + + private static class LanguageFragment { + public String language; + } } From 5c34b7d0c43ca03090dfd194bd5b2cdcbdb1bb11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Mon, 29 Jun 2015 19:56:14 +0200 Subject: [PATCH 3/3] add error handling for Query and ViewQuery methods --- .../core/CouchbaseExceptionTranslator.java | 8 +++ .../CouchbaseQueryExecutionException.java | 33 +++++++++++ .../couchbase/core/CouchbaseTemplate.java | 55 +++++++++++++------ 3 files changed, 79 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/core/CouchbaseQueryExecutionException.java diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java index 03f43d87..00230549 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java @@ -38,11 +38,13 @@ import com.couchbase.client.java.error.InvalidPasswordException; import com.couchbase.client.java.error.RequestTooBigException; import com.couchbase.client.java.error.TemporaryFailureException; import com.couchbase.client.java.error.TemporaryLockFailureException; +import com.couchbase.client.java.error.TranscodingException; import com.couchbase.client.java.error.ViewDoesNotExistException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.QueryTimeoutException; @@ -115,6 +117,12 @@ public class CouchbaseExceptionTranslator implements PersistenceExceptionTransla return new QueryTimeoutException(ex.getMessage(), ex); } + if (ex instanceof TranscodingException) { + //note: the more specific CouchbaseQueryExecutionException should be thrown by the template + //when dealing with TranscodingException in the query/n1ql methods. + return new DataRetrievalFailureException(ex.getMessage(), ex); + } + // Unable to translate exception, therefore just throw the original! throw ex; } diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseQueryExecutionException.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseQueryExecutionException.java new file mode 100644 index 00000000..7cb25a90 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseQueryExecutionException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * 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 org.springframework.data.couchbase.core; + +import org.springframework.dao.DataRetrievalFailureException; + +/** + * An {@link DataRetrievalFailureException} that denotes an error during a query (N1QL). + */ +public class CouchbaseQueryExecutionException extends DataRetrievalFailureException { + + public CouchbaseQueryExecutionException(String msg) { + super(msg); + } + + public CouchbaseQueryExecutionException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index d4a99b1b..d78b4e5e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -31,7 +31,9 @@ import com.couchbase.client.java.PersistTo; import com.couchbase.client.java.ReplicateTo; import com.couchbase.client.java.document.Document; import com.couchbase.client.java.document.RawJsonDocument; +import com.couchbase.client.java.document.json.JsonObject; import com.couchbase.client.java.error.CASMismatchException; +import com.couchbase.client.java.error.TranscodingException; import com.couchbase.client.java.query.Query; import com.couchbase.client.java.query.QueryResult; import com.couchbase.client.java.query.QueryRow; @@ -262,16 +264,24 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP query.includeDocs(false); query.reduce(false); - final ViewResult response = queryView(query); - List allRows = response.allRows(); - //TODO error handling + try { + final ViewResult response = queryView(query); + if (response.error() != null) { + throw new CouchbaseQueryExecutionException("Unable to execute view query due to the following view error: " + + response.error().toString()); + } - final List result = new ArrayList(allRows.size()); - for (final ViewRow row : allRows) { - result.add(mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass)); + List allRows = response.allRows(); + + final List result = new ArrayList(allRows.size()); + for (final ViewRow row : allRows) { + result.add(mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass)); + } + + return result; + } catch (TranscodingException e) { + throw new CouchbaseQueryExecutionException("Unable to execute view query", e); } - - return result; } @Override @@ -286,17 +296,28 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP @Override public List findByN1QL(Query n1ql, Class entityClass) { - QueryResult queryResult = queryN1QL(n1ql); + try { + QueryResult queryResult = queryN1QL(n1ql); - List allRows = queryResult.allRows(); - List result = new ArrayList(allRows.size()); - for (QueryRow row : allRows) { - String json = row.value().toString(); - T decoded = translationService.decodeFragment(json, entityClass); - result.add(decoded); + if (queryResult.finalSuccess()) { + List allRows = queryResult.allRows(); + List result = new ArrayList(allRows.size()); + for (QueryRow row : allRows) { + String json = row.value().toString(); + T decoded = translationService.decodeFragment(json, entityClass); + result.add(decoded); + } + return result; + } else { + StringBuilder message = new StringBuilder("Unable to execute query due to the following n1ql errors: "); + for (JsonObject error : queryResult.errors()) { + message.append('\n').append(error); + } + throw new CouchbaseQueryExecutionException(message.toString()); + } + } catch (TranscodingException e) { + throw new CouchbaseQueryExecutionException("Unable to execute query", e); } - //TODO error handling - return result; } @Override