Add suport for JsonValue annotation on Enums and JsonValue/JsonCreator otherwise. (#1618)

Closes #1617.
This commit is contained in:
Michael Reiche
2022-12-15 13:31:19 -08:00
committed by GitHub
parent 7a05754161
commit 799b7dbd30
24 changed files with 1128 additions and 87 deletions

View File

@@ -90,6 +90,7 @@ class CouchbaseCacheIntegrationTests extends JavaIntegrationTests {
t0 = System.currentTimeMillis();
users = userRepository.getByFirstname(user.getFirstname());
assert (System.currentTimeMillis() - t0 < 100);
userRepository.delete(user);
}
@Test

View File

@@ -26,6 +26,8 @@ import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Expiration;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* Airport entity
*
@@ -39,7 +41,7 @@ public class Airport extends ComparableEntity {
String iata;
String icao;
@JsonFormat(pattern = "abc") String icao;
@Version Number version;
@@ -48,6 +50,15 @@ public class Airport extends ComparableEntity {
@Max(2) long size;
private long someNumber;
public AirportJsonValuedObject jvObject;
{
this.jvObject = new AirportJsonValuedObject();
this.jvObject.theValue = "first";
}
public Airport() {}
@PersistenceConstructor
public Airport(String key, String iata, String icao) {
this.key = key;
@@ -99,4 +110,5 @@ public class Airport extends ComparableEntity {
public void setSize(long size) {
this.size = size;
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2022 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
*
* https://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.domain;
import jakarta.validation.constraints.Max;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.Expiration;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* AirportJsonValue entity. From Airport.
*
* @author Michael Reiche
*/
@Document
@TypeAlias("airport")
public class AirportJsonValue extends ComparableEntity {
@Id String key;
String iata;
@JsonFormat(pattern = "abc") String icao;
ETurbulenceCategory turbulence1;
ETurbulenceCategory turbulence2;
EITurbulenceCategory turbulence3;
EJsonCreatorTurbulenceCategory turbulence4;
EBTurbulenceCategory turbulence5 ;
@Version Number version;
@CreatedBy private String createdBy;
@Expiration private long expiration;
@Max(2) long size;
public AirportJsonValuedObject jvObject;
{
this.jvObject = new AirportJsonValuedObject();
}
public AirportJsonValue() {}
@PersistenceConstructor
public AirportJsonValue(String key, String iata, String icao) {
this.key = key;
this.iata = iata;
this.icao = icao;
}
public String getId() {
return key;
}
public String getIata() {
return iata;
}
public String getIcao() {
return icao;
}
public long getExpiration() {
return expiration;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public void setTurbulence1(ETurbulenceCategory turbulence1) {
this.turbulence1 = turbulence1;
}
public ETurbulenceCategory getTurbulence1() {
return turbulence1;
}
public void setTurbulence2(ETurbulenceCategory turbulence) {
this.turbulence2 = turbulence;
}
public ETurbulenceCategory getTurbulence2() {
return turbulence2;
}
public void setTurbulence3(EITurbulenceCategory turbulence3) {
this.turbulence3 = turbulence3;
}
public EITurbulenceCategory getTurbulence3() {
return turbulence3;
}
public void setTurbulence4(EJsonCreatorTurbulenceCategory turbulence4) {
this.turbulence4 = turbulence4;
}
public EJsonCreatorTurbulenceCategory getTurbulence4() {
return turbulence4;
}
public void setTurbulence5(EBTurbulenceCategory turbulence5) {
this.turbulence5 = turbulence5;
}
public EBTurbulenceCategory getTurbulence5() {
return turbulence5;
}
public void setJvObject(AirportJsonValuedObject jvObject) {
this.jvObject = jvObject;
}
public AirportJsonValuedObject getJvObject() {
return jvObject;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2022 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
*
* https://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.domain;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.DynamicProxyable;
import org.springframework.stereotype.Repository;
/**
* AirportJsonValue repository for testing <br>
*
* @author Michael Reiche
*/
@Repository
public interface AirportJsonValueRepository
extends CouchbaseRepository<AirportJsonValue, String>, DynamicProxyable<AirportJsonValueRepository> {
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2022 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
*
* https://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.domain;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.data.annotation.PersistenceCreator;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* A non-Enum object that has @JsonValue annotated method.
*
* @author Michael Reiche
*/
public class AirportJsonValuedObject {
public AirportJsonValuedObject() {}
@PersistenceCreator
@JsonCreator
public AirportJsonValuedObject(String theValue) {
this.theValue = reverseMap.get(theValue);
if (this.theValue == null) {
throw new RuntimeException(
"invalid value for " + this.getClass().getName() + " : " + theValue + ". Must be one of " + reverseMap);
}
}
public String theValue;
static Map<String, String> map = Map.of("first", "1st", "second", "2nd");
static Map<String, String> reverseMap = new HashMap<>();
static {
map.entrySet().stream().map((entry) -> reverseMap.put(entry.getValue(), entry.getKey()))
.collect(Collectors.toList());
}
@JsonValue
public String getMapped() {
return map.get(theValue);
}
}

View File

@@ -0,0 +1,41 @@
package org.springframework.data.couchbase.domain;
/*
* Copyright 2022 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
*
* https://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.
*/
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* An enum that has an Boolean getCode() method.
*
* @author Michael Reiche
*/
public enum EBTurbulenceCategory {
T(true), F(false);
private final Boolean code;
@JsonCreator
EBTurbulenceCategory(Boolean code) {
this.code = code;
}
@JsonValue
public Boolean getCode() {
return code;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2022 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
*
* https://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.domain;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* An enum that has an Integer getCode() method.
*
* @author Michael Reiche
*/
public enum EITurbulenceCategory {
T10(10), T20(20), T30(30), T40(40);
private final Integer code;
EITurbulenceCategory(Integer code) {
this.code = code;
}
@JsonValue
public Integer getCode() {
return code;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2022 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
*
* https://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.domain;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* An enum that has an @JsonCreator method.
*
* @author Michael Reiche
*/
public enum EJsonCreatorTurbulenceCategory {
T10("10%"), T20("20%"), T30("30%"), T40("40%");
private final String code;
@JsonCreator
EJsonCreatorTurbulenceCategory(String code) {
this.code = code;
}
@JsonValue
public String getCode() {
return code;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2022 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
*
* https://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.domain;
import com.fasterxml.jackson.annotation.JsonValue;
/**
* An enum that has an String getCode() method.
*
* @author Michael Reiche
*/
public enum ETurbulenceCategory {
T10("10%"), T20("20%"), T30("30%");
private final String code;
ETurbulenceCategory(String code) {
this.code = code;
}
@JsonValue
public String getCode() {
return code;
}
}

View File

@@ -58,13 +58,13 @@ public class Person extends AbstractEntity implements Persistable<Object> {
public Person() {
setId(UUID.randomUUID());
setMiddlename("Nick");
}
public Person(String firstname, String lastname) {
this();
setFirstname(firstname);
setLastname(lastname);
setMiddlename("Nick");
isNew(true);
}

View File

@@ -69,10 +69,16 @@ import org.springframework.data.couchbase.core.query.QueryCriteria;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.AirlineRepository;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportJsonValue;
import org.springframework.data.couchbase.domain.AirportJsonValueRepository;
import org.springframework.data.couchbase.domain.AirportJsonValuedObject;
import org.springframework.data.couchbase.domain.AirportMini;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.AirportRepositoryScanConsistencyTest;
import org.springframework.data.couchbase.domain.Course;
import org.springframework.data.couchbase.domain.EITurbulenceCategory;
import org.springframework.data.couchbase.domain.EJsonCreatorTurbulenceCategory;
import org.springframework.data.couchbase.domain.ETurbulenceCategory;
import org.springframework.data.couchbase.domain.Iata;
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
import org.springframework.data.couchbase.domain.Person;
@@ -108,6 +114,7 @@ import com.couchbase.client.core.error.CouchbaseException;
import com.couchbase.client.core.error.IndexFailureException;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JsonArray;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.kv.GetResult;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.MutationState;
@@ -129,6 +136,8 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
@Autowired AirportRepository airportRepository;
@Autowired AirportJsonValueRepository airportJsonValueRepository;
@Autowired AirlineRepository airlineRepository;
@Autowired UserRepository userRepository;
@@ -316,6 +325,120 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
}
}
@Test
void sdcJsonValue() {
AirportJsonValue vie = null;
try {
vie = new AirportJsonValue("airports::vie", "vie", "low6");
vie.setTurbulence1(ETurbulenceCategory.T10);
vie.setTurbulence2(ETurbulenceCategory.T20);
vie.setTurbulence3(EITurbulenceCategory.T30);
vie.setTurbulence4(EJsonCreatorTurbulenceCategory.T40);
// sdk/jackson cannot handle vie.setTurbulence5(EBTurbulenceCategory.T);
vie.setJvObject(new AirportJsonValuedObject("1st"));
airportJsonValueRepository.save(vie);
// check that they were saved as expected
GetResult result = couchbaseTemplate.getCouchbaseClientFactory().getCluster().bucket(bucketName())
.defaultCollection().get(vie.getId());
JsonObject jo = JsonObject.fromJson(result.contentAsBytes());
assertEquals(ETurbulenceCategory.T10.getCode(), jo.get("turbulence1"));
assertEquals(ETurbulenceCategory.T20.getCode(), jo.get("turbulence2"));
assertEquals(EITurbulenceCategory.T30.getCode(), jo.get("turbulence3"));
assertEquals(EJsonCreatorTurbulenceCategory.T40.getCode(), jo.get("turbulence4"));
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T.getCode(), jo.get("turbulence5"));
assertEquals(new AirportJsonValuedObject("1st").getMapped(), jo.get("jvObject"));
// check that spring-data-couchbase deserializes as expected
AirportJsonValue airport = airportJsonValueRepository.findById(vie.getId()).get();
System.out.println(airport);
assertEquals(ETurbulenceCategory.T10, airport.getTurbulence1());
assertEquals(ETurbulenceCategory.T20, airport.getTurbulence2());
assertEquals(EITurbulenceCategory.T30, airport.getTurbulence3());
assertEquals(EJsonCreatorTurbulenceCategory.T40, airport.getTurbulence4());
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T, airport.getTurbulence5());
assertEquals(new AirportJsonValuedObject("1st").theValue, airport.getJvObject().theValue);
// check that java sdk deserializes as expected
airport = result.contentAs(AirportJsonValue.class);
assertEquals(ETurbulenceCategory.T10, airport.getTurbulence1());
assertEquals(ETurbulenceCategory.T20, airport.getTurbulence2());
assertEquals(EITurbulenceCategory.T30, airport.getTurbulence3());
assertEquals(EJsonCreatorTurbulenceCategory.T40, airport.getTurbulence4());
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T, airport.getTurbulence5());
assertEquals(new AirportJsonValuedObject("1st").theValue, airport.getJvObject().theValue);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
try {
airportJsonValueRepository.delete(vie);
} catch (Exception e) {
System.err.println(e);
}
}
}
@Test
void sdkJsonValue() {
AirportJsonValue vie = null;
try {
vie = new AirportJsonValue("airports::vie", "vie", "low6");
vie.setTurbulence1(ETurbulenceCategory.T10);
vie.setTurbulence2(ETurbulenceCategory.T20);
vie.setTurbulence3(EITurbulenceCategory.T30);
vie.setTurbulence4(EJsonCreatorTurbulenceCategory.T40);
// sdk/jackson cannot handle vie.setTurbulence5(EBTurbulenceCategory.T);
vie.setJvObject(new AirportJsonValuedObject("1st"));
//airportJsonValueRepository.save(vie);
couchbaseTemplate.getCouchbaseClientFactory().getCluster().bucket(bucketName())
.defaultCollection().upsert(vie.getId(), vie);
// check that they were saved as expected
GetResult result = couchbaseTemplate.getCouchbaseClientFactory().getCluster().bucket(bucketName())
.defaultCollection().get(vie.getId());
JsonObject jo = JsonObject.fromJson(result.contentAsBytes());
assertEquals(ETurbulenceCategory.T10.getCode(), jo.get("turbulence1"));
assertEquals(ETurbulenceCategory.T20.getCode(), jo.get("turbulence2"));
assertEquals(EITurbulenceCategory.T30.getCode(), jo.get("turbulence3"));
assertEquals(EJsonCreatorTurbulenceCategory.T40.getCode(), jo.get("turbulence4"));
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T.getCode(), jo.get("turbulence5"));
assertEquals(new AirportJsonValuedObject("1st").getMapped(), jo.get("jvObject"));
// check that spring-data-couchbase deserializes as expected
AirportJsonValue airport = airportJsonValueRepository.findById(vie.getId()).get();
System.out.println(airport);
assertEquals(ETurbulenceCategory.T10, airport.getTurbulence1());
assertEquals(ETurbulenceCategory.T20, airport.getTurbulence2());
assertEquals(EITurbulenceCategory.T30, airport.getTurbulence3());
assertEquals(EJsonCreatorTurbulenceCategory.T40, airport.getTurbulence4());
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T, airport.getTurbulence5());
assertEquals(new AirportJsonValuedObject("1st").theValue, airport.getJvObject().theValue);
// check that java sdk deserializes as expected
airport = result.contentAs(AirportJsonValue.class);
assertEquals(ETurbulenceCategory.T10, airport.getTurbulence1());
assertEquals(ETurbulenceCategory.T20, airport.getTurbulence2());
assertEquals(EITurbulenceCategory.T30, airport.getTurbulence3());
assertEquals(EJsonCreatorTurbulenceCategory.T40, airport.getTurbulence4());
// sdk/jackson cannot handle assertEquals(EBTurbulenceCategory.T, airport.getTurbulence5());
assertEquals(new AirportJsonValuedObject("1st").theValue, airport.getJvObject().theValue);
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
try {
airportJsonValueRepository.delete(vie);
} catch (Exception e) {
System.err.println(e);
}
}
}
@Test
public void saveNotBoundedRequestPlus() {
airportRepository.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).deleteAll();
@@ -867,11 +990,11 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try {
userSubmission.setUsername("updateObject");
userSubmissionRepository.save(userSubmission);
Course[] courses = new Course[]{ new Course("1", "2", "3"), new Course("4","5","6")};
Course[] courses = new Course[] { new Course("1", "2", "3"), new Course("4", "5", "6") };
userSubmissionRepository.setNamedCourses(userSubmission.getId(), courses);
Optional<UserSubmission> fetched = userSubmissionRepository.findById(userSubmission.getId());
assertEquals(courses.length, fetched.get().getCourses().size());
for(int i=0; i< courses.length; i++){
for (int i = 0; i < courses.length; i++) {
assertEquals(courses[i], fetched.get().getCourses().get(i));
}
} finally {
@@ -886,13 +1009,13 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try {
userSubmission.setUsername("updateObject");
userSubmissionRepository.save(userSubmission);
Course[] courses = new Course[]{ new Course("1", "2", "3"), new Course("4","5","6")};
Course[] courses = new Course[] { new Course("1", "2", "3"), new Course("4", "5", "6") };
userSubmissionRepository.setOrderedCourses(userSubmission.getId(), courses);
Optional<UserSubmission> fetched = userSubmissionRepository.findById(userSubmission.getId());
assertEquals(courses.length, fetched.get().getCourses().size());
for(int i=0; i< courses.length; i++){
for (int i = 0; i < courses.length; i++) {
assertEquals(courses[i], fetched.get().getCourses().get(i));
}
}
} finally {
userSubmissionRepository.deleteById(userSubmission.getId());
}

View File

@@ -211,7 +211,7 @@ public class CouchbaseRepositoryQueryCollectionIntegrationTests extends Collecti
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
assertEquals(0, notfound.size(), "should not have found what was saved");
assertEquals(0, notfound.size(), "should not have found what was saved "+notfound);
} finally {
try {
userColRepository.withScope(otherScope).withCollection(otherCollection).delete(user);
@@ -250,7 +250,7 @@ public class CouchbaseRepositoryQueryCollectionIntegrationTests extends Collecti
assertEquals(saved, found.get(0), "should have found what was saved");
List<UserCol> notfound = userColRepository.withScope(DEFAULT_SCOPE)
.withCollection(CollectionIdentifier.DEFAULT_COLLECTION).findByFirstname(user.getFirstname());
assertEquals(0, notfound.size(), "should not have found what was saved");
assertEquals(0, notfound.size(), "should not have found what was saved "+notfound);
userColRepository.withScope(scopeName).withCollection(collectionName).delete(user);
} finally {
try {