1919import java .time .Instant ;
2020import java .util .Map ;
2121import java .util .Set ;
22+ import java .util .function .BiFunction ;
2223
23- import com .couchbase .client .core .annotation .Stability ;
2424import org .slf4j .Logger ;
2525import org .slf4j .LoggerFactory ;
2626import org .springframework .context .ApplicationContext ;
4040import org .springframework .data .mapping .model .ConvertingPropertyAccessor ;
4141import org .springframework .util .ClassUtils ;
4242
43+ import com .couchbase .client .core .annotation .Stability ;
4344import com .couchbase .client .core .error .CouchbaseException ;
4445
45-
4646/**
4747 * Base shared by Reactive and non-Reactive TemplateSupport
4848 *
4949 * @author Michael Reiche
50+ * @author Emilien Bevierre
5051 */
5152@ Stability .Internal
5253public abstract class AbstractTemplateSupport {
@@ -70,61 +71,96 @@ public AbstractTemplateSupport(ReactiveCouchbaseTemplate template, CouchbaseConv
7071
7172 public <T > T decodeEntityBase (Object id , String source , Long cas , Instant expiryTime , Class <T > entityClass ,
7273 String scope , String collection , Object txResultHolder , CouchbaseResourceHolder holder ) {
74+ return decodeEntityBase (id , cas , expiryTime , entityClass , scope , collection , txResultHolder , holder ,
75+ (ts , converted ) -> (CouchbaseDocument ) ts .decode (source , converted ));
76+ }
7377
74- // this is the entity class defined for the repository. It may not be the class of the document that was read
75- // we will reset it after reading the document
76- //
77- // This will fail for the case where:
78- // 1) The version is defined in the concrete class, but not in the abstract class; and
79- // 2) The constructor takes a "long version" argument resulting in an exception would be thrown if version in
80- // the source is null.
81- // We could expose from the MappingCouchbaseConverter determining the persistent entity from the source,
82- // but that is a lot of work to do every time just for this very rare and avoidable case.
83- // TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
78+ public <T > T decodeEntityBase (Object id , byte [] source , Long cas , Instant expiryTime , Class <T > entityClass ,
79+ String scope , String collection , Object txResultHolder , CouchbaseResourceHolder holder ) {
80+ return decodeEntityBase (id , cas , expiryTime , entityClass , scope , collection , txResultHolder , holder ,
81+ (ts , converted ) -> (CouchbaseDocument ) ts .decode (source , converted ));
82+ }
8483
84+ private <T > T decodeEntityBase (Object id , Long cas , Instant expiryTime , Class <T > entityClass , String scope ,
85+ String collection , Object txResultHolder , CouchbaseResourceHolder holder ,
86+ BiFunction <TranslationService , CouchbaseDocument , CouchbaseDocument > translatorFn ) {
8587 CouchbasePersistentEntity persistentEntity = couldBePersistentEntity (entityClass );
8688
87- if (persistentEntity == null ) { // method could return a Long, Boolean, String etc.
88- // QueryExecutionConverters.unwrapWrapperTypes will recursively unwrap until there is nothing left
89- // to unwrap. This results in List<String[]> being unwrapped past String[] to String, so this may also be a
90- // Collection (or Array) of entityClass. We have no way of knowing - so just assume it is what we are told.
91- // if this is a Collection or array, only the first element will be returned.
92- final CouchbaseDocument converted = new CouchbaseDocument (id );
93- Set <Map .Entry <String , Object >> set = ((CouchbaseDocument ) translationService .decode (source , converted ))
94- .getContent ().entrySet ();
89+ if (persistentEntity == null ) {
90+ CouchbaseDocument converted = new CouchbaseDocument (id );
91+ Set <Map .Entry <String , Object >> set = translatorFn .apply (translationService , converted ).getContent ()
92+ .entrySet ();
9593 return (T ) set .iterator ().next ().getValue ();
9694 }
9795
96+ CouchbaseDocument converted = prepareConvertedDocument (id , cas , persistentEntity );
97+ T readEntity = converter .read (entityClass , translatorFn .apply (translationService , converted ));
98+ return finalizeEntity (readEntity , id , cas , expiryTime , scope , collection , txResultHolder , holder );
99+ }
100+
101+ private CouchbaseDocument prepareConvertedDocument (Object id , Long cas ,
102+ CouchbasePersistentEntity persistentEntity ) {
103+ // persistentEntity is derived from the entityClass declared in the
104+ // repository definition. It may be an abstract class rather than the
105+ // concrete class of the document being read. The concrete type is only
106+ // known after converter.read() is called, therefore version/cas is set again
107+ // on the final entity in finalizeEntity().
108+ //
109+ // Pre-populating the CAS/version into the source document (done in getDocument)
110+ // is a best-effort step to avoid a construction failure when the concrete
111+ // class constructor takes a primitive "long version" argument (null is not a
112+ // valid value for a primitive). If the version property is only declared on the
113+ // concrete subclass and not on the abstract base, pre-population is not
114+ // possible here and the issue can be avoided by using "Long" instead of "long".
115+ //
116+ // An alternative would be to resolve the actual concrete type from the source
117+ // document's type metadata before constructing it (see the comment
118+ // below), but that adds overhead for every decode to solve a rare and avoidable
119+ // case.
120+ // TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
121+
98122 if (id == null ) {
99- throw new CouchbaseException (TemplateUtils . SELECT_ID + " was null. Either use #{#n1ql.selectEntity} or project "
100- + TemplateUtils .SELECT_ID );
123+ throw new CouchbaseException ("%s was null. Either use #{#n1ql.selectEntity} or project %s "
124+ . formatted ( TemplateUtils . SELECT_ID , TemplateUtils .SELECT_ID ) );
101125 }
102126
103- final CouchbaseDocument converted = new CouchbaseDocument (id );
104-
105- // if possible, set the version property in the source so that if the constructor has a long version argument,
106- // it will have a value and not fail (as null is not a valid argument for a long argument). This possible failure
107- // can be avoid by defining the argument as Long instead of long.
108- // persistentEntity is still the (possibly abstract) class specified in the repository definition
109- // it's possible that the abstract class does not have a version property, and this won't be able to set the version
110- if (persistentEntity .getVersionProperty () != null ) {
111- if (cas == null ) {
112- throw new CouchbaseException ("version/cas in the entity but " + TemplateUtils .SELECT_CAS
113- + " was not in result. Either use #{#n1ql.selectEntity} or project " + TemplateUtils .SELECT_CAS );
114- }
115- if (cas != 0 ) {
116- converted .put (persistentEntity .getVersionProperty ().getName (), cas );
117- }
118- }
127+ return getDocument (id , cas , persistentEntity );
128+ }
119129
120- // if the constructor has an argument that is long version, then construction will fail if the 'version'
121- // is not available as 'null' is not a legal value for a long. Changing the arg to "Long version" would solve this.
122- // (Version doesn't come from 'source', it comes from the cas argument to decodeEntity)
123- T readEntity = converter .read (entityClass , (CouchbaseDocument ) translationService .decode (source , converted ));
124- final ConvertingPropertyAccessor <T > accessor = getPropertyAccessor (readEntity );
130+ private static CouchbaseDocument getDocument (Object id , Long cas , CouchbasePersistentEntity persistentEntity ) {
131+ CouchbaseDocument converted = new CouchbaseDocument (id );
132+
133+ // If possible, set the version property in the source so that if the
134+ // constructor has a long version argument, it will have a value and succeed,
135+ // as null is not a valid argument for a long. This failure can be avoided by
136+ // defining the argument as Long instead of long.
137+ // Note that persistentEntity is still the (possibly abstract) class specified
138+ // in the repository definition, so it's possible that the abstract class does
139+ // not have a version property, in which case this won't be able to set the version.
140+ if (persistentEntity .getVersionProperty () != null ) {
141+ if (cas == null ) {
142+ throw new CouchbaseException (
143+ "version/cas in the entity but %s was not in result. Either use #{#n1ql.selectEntity} or project %s"
144+ .formatted (TemplateUtils .SELECT_CAS , TemplateUtils .SELECT_CAS ));
145+ }
146+ if (cas != 0 ) {
147+ converted .put (persistentEntity .getVersionProperty ().getName (), cas );
148+ }
149+ }
150+ return converted ;
151+ }
152+
153+ private <T > T finalizeEntity (T readEntity , Object id , Long cas , Instant expiryTime , String scope , String collection ,
154+ Object txResultHolder , CouchbaseResourceHolder holder ) {
155+ ConvertingPropertyAccessor <T > accessor = getPropertyAccessor (readEntity );
125156
126- persistentEntity = couldBePersistentEntity (readEntity .getClass ());
157+ CouchbasePersistentEntity persistentEntity = couldBePersistentEntity (readEntity .getClass ());
127158
159+ // If the constructor has a long version argument, construction will fail if
160+ // 'version' is not available, as null is not a legal value for a long.
161+ // We therefore use the object-wrapped Long type.
162+ // (Version doesn't come from 'source', it comes from the cas argument to
163+ // decodeEntity.)
128164 if (cas != null && cas != 0 && persistentEntity .getVersionProperty () != null ) {
129165 accessor .setProperty (persistentEntity .getVersionProperty (), cas );
130166 }
@@ -133,7 +169,8 @@ public <T> T decodeEntityBase(Object id, String source, Long cas, Instant expiry
133169 accessor .setProperty (persistentEntity .getExpiryProperty (), expiryTime );
134170 }
135171
136- N1qlJoinResolver .handleProperties (persistentEntity , accessor , getReactiveTemplate (), id .toString (), scope , collection );
172+ N1qlJoinResolver .handleProperties (persistentEntity , accessor , getReactiveTemplate (), id .toString (), scope ,
173+ collection );
137174
138175 if (holder != null ) {
139176 holder .transactionResultHolder (txResultHolder , (T ) accessor .getBean ());
@@ -158,15 +195,15 @@ public <T> T applyResultBase(T entity, CouchbaseDocument converted, Object id, l
158195 Object txResultHolder , CouchbaseResourceHolder holder ) {
159196 ConvertingPropertyAccessor <Object > accessor = getPropertyAccessor (entity );
160197
161- final CouchbasePersistentEntity <?> persistentEntity = converter .getMappingContext ()
198+ CouchbasePersistentEntity <?> persistentEntity = converter .getMappingContext ()
162199 .getRequiredPersistentEntity (entity .getClass ());
163200
164- final CouchbasePersistentProperty idProperty = persistentEntity .getIdProperty ();
201+ CouchbasePersistentProperty idProperty = persistentEntity .getIdProperty ();
165202 if (idProperty != null ) {
166203 accessor .setProperty (idProperty , id );
167204 }
168205
169- final CouchbasePersistentProperty versionProperty = persistentEntity .getVersionProperty ();
206+ CouchbasePersistentProperty versionProperty = persistentEntity .getVersionProperty ();
170207 if (versionProperty != null ) {
171208 accessor .setProperty (versionProperty , cas );
172209 }
@@ -179,10 +216,11 @@ public <T> T applyResultBase(T entity, CouchbaseDocument converted, Object id, l
179216
180217 }
181218
182- public Long getCas (final Object entity ) {
183- final ConvertingPropertyAccessor <Object > accessor = getPropertyAccessor (entity );
184- final CouchbasePersistentEntity <?> persistentEntity = mappingContext .getRequiredPersistentEntity (entity .getClass ());
185- final CouchbasePersistentProperty versionProperty = persistentEntity .getVersionProperty ();
219+ public Long getCas (Object entity ) {
220+ ConvertingPropertyAccessor <Object > accessor = getPropertyAccessor (entity );
221+ CouchbasePersistentEntity <?> persistentEntity = mappingContext
222+ .getRequiredPersistentEntity (entity .getClass ());
223+ CouchbasePersistentProperty versionProperty = persistentEntity .getVersionProperty ();
186224 long cas = 0 ;
187225 if (versionProperty != null ) {
188226 Object casObject = accessor .getProperty (versionProperty );
@@ -193,24 +231,25 @@ public Long getCas(final Object entity) {
193231 return cas ;
194232 }
195233
196- public Object getId (final Object entity ) {
197- final ConvertingPropertyAccessor <Object > accessor = getPropertyAccessor (entity );
198- final CouchbasePersistentEntity <?> persistentEntity = mappingContext .getRequiredPersistentEntity (entity .getClass ());
199- final CouchbasePersistentProperty idProperty = persistentEntity .getIdProperty ();
234+ public Object getId (Object entity ) {
235+ ConvertingPropertyAccessor <Object > accessor = getPropertyAccessor (entity );
236+ CouchbasePersistentEntity <?> persistentEntity = mappingContext
237+ .getRequiredPersistentEntity (entity .getClass ());
238+ CouchbasePersistentProperty idProperty = persistentEntity .getIdProperty ();
200239 Object id = null ;
201240 if (idProperty != null ) {
202241 id = accessor .getProperty (idProperty );
203242 }
204243 return id ;
205244 }
206245
207- public String getJavaNameForEntity (final Class <?> clazz ) {
208- final CouchbasePersistentEntity <?> persistentEntity = mappingContext .getRequiredPersistentEntity (clazz );
246+ public String getJavaNameForEntity (Class <?> clazz ) {
247+ CouchbasePersistentEntity <?> persistentEntity = mappingContext .getRequiredPersistentEntity (clazz );
209248 MappingCouchbaseEntityInformation <?, Object > info = new MappingCouchbaseEntityInformation <>(persistentEntity );
210249 return info .getJavaType ().getName ();
211250 }
212251
213- <T > ConvertingPropertyAccessor <T > getPropertyAccessor (final T source ) {
252+ <T > ConvertingPropertyAccessor <T > getPropertyAccessor (T source ) {
214253 CouchbasePersistentEntity <?> entity = mappingContext .getRequiredPersistentEntity (source .getClass ());
215254 PersistentPropertyAccessor <T > accessor = entity .getPropertyAccessor (source );
216255 return new ConvertingPropertyAccessor <>(accessor , converter .getConversionService ());
0 commit comments