Merge pull request #50 from simonbasle/queryAdHoc
[SDK2] findByXXXQuery improvements implement findByQuery by allowing TranslationService to unmarshall an adhoc fragment of JSON (the N1QL rows). added error handling for both findByView and findByQuery. also fixed the template trying to set a Version field even if no such field is declared in the EntityClass.
This commit is contained in:
@@ -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<BeerFragment> 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<Fragment> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,17 @@ 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;
|
||||
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;
|
||||
@@ -262,16 +264,24 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
query.includeDocs(false);
|
||||
query.reduce(false);
|
||||
|
||||
final ViewResult response = queryView(query);
|
||||
List<ViewRow> 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<T> result = new ArrayList<T>(allRows.size());
|
||||
for (final ViewRow row : allRows) {
|
||||
result.add(mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass));
|
||||
List<ViewRow> allRows = response.allRows();
|
||||
|
||||
final List<T> result = new ArrayList<T>(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,9 +296,28 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
|
||||
@Override
|
||||
public <T> List<T> findByN1QL(Query n1ql, Class<T> entityClass) {
|
||||
//TODO find a way of mapping content to T
|
||||
//TODO error handling
|
||||
throw new NotImplementedException();
|
||||
try {
|
||||
QueryResult queryResult = queryN1QL(n1ql);
|
||||
|
||||
if (queryResult.finalSuccess()) {
|
||||
List<QueryRow> allRows = queryResult.allRows();
|
||||
List<T> result = new ArrayList<T>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -359,7 +388,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
|
||||
final String operationDesc = failOnExist ? "Insert" : failOnMissing ? "Update" : "Upsert";
|
||||
|
||||
final BeanWrapper<Object> 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 +413,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;
|
||||
|
||||
@@ -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> T decodeFragment(String source, Class<T> target) {
|
||||
try {
|
||||
return objectMapper.readValue(source, target);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("Cannot decode ad-hoc JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <T> the target class.
|
||||
* @return an ad-hoc instance of the decoded JSON into the corresponding "case" class.
|
||||
*/
|
||||
<T> T decodeFragment(String source, Class<T> target);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user