DATACOUCH-504 - Migrate to Couchbase SDK 3

This commit is contained in:
David Kelly
2019-09-19 09:13:53 -06:00
committed by Michael Nitschinger
parent 819923af64
commit c3e66d74a3
399 changed files with 14616 additions and 27269 deletions

View File

@@ -94,7 +94,7 @@ public class Config extends AbstractCouchbaseConfiguration {
}
@Override
protected String getBucketPassword() {
protected String getPassword() {
return "";
}
}

61
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>4.0.0.BUILD-SNAPSHOT</version>
<version>4.0.0-BUILD-SNAPSHOT</version>
<name>Spring Data Couchbase</name>
<description>Spring Data integration for Couchbase</description>
@@ -18,8 +18,8 @@
</parent>
<properties>
<couchbase>2.7.12</couchbase>
<couchbase.osgi>2.7.12</couchbase.osgi>
<couchbase>3.0.1</couchbase>
<couchbase.osgi>3.0.1</couchbase.osgi>
<springdata.commons>2.3.0.BUILD-SNAPSHOT</springdata.commons>
<java-module-name>spring.data.couchbase</java-module-name>
</properties>
@@ -37,7 +37,6 @@
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
@@ -99,35 +98,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava-reactive-streams</artifactId>
<version>${rxjava-reactive-streams}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava2}</version>
<optional>true</optional>
</dependency>
<!-- Couchbase field level encryption library -->
<dependency>
<groupId>com.couchbase.client</groupId>
<artifactId>encryption</artifactId>
<version>1.0.0</version>
<version>3.2.12.RELEASE</version>
</dependency>
<dependency>
@@ -182,6 +153,20 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.Couchbase</groupId>
<artifactId>CouchbaseMock</artifactId>
<version>73e493d259</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.4.0</version>
<scope>test</scope>
</dependency>
<!-- Kotlin extension -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
@@ -208,6 +193,16 @@
<id>spring-libs-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
<repository>
<id>sonatype-snapshot</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<pluginRepositories>

View File

@@ -1,21 +1,20 @@
[[couchbase.ansijoins]]
= ANSI Joins
This chapter describes hows ANSI joins can be used across entities. Since 5.5 version, Couchbase server provides
support for ANSI joins for joining documents using fields. Previous versions allowed index & lookup joins, which were
supported in SDC only by querying directly through the SDK.
This chapter describes hows ANSI joins can be used across entities.
Since 5.5 version, Couchbase server provides support for ANSI joins for joining documents using fields.
Previous versions allowed index & lookup joins, which were supported in SDC only by querying directly through the SDK.
Relationships between entities across repositories can be one to one or one to many. By defining such relationships, a
synchronized view of associated entities can be fetched.
Relationships between entities across repositories can be one to one or one to many.
By defining such relationships, a synchronized view of associated entities can be fetched.
[[couchbase.ansijoins.configuration]]
== Configuration
Associated entities can be fetched by annotating the entity's property reference with `@N1qlJoin`. The prefix `lks` refers
to left-hand side key space (current entity) and `rks` refers to the right-hand side key space (associated entity). The
required element for `@N1qlJoin` annotation is the `on` clause, a boolean expression representing the join condition
between the left-hand side (`lks`) and the right-hand side (`rks`), which can be fields, constant expressions or any complex
N1QL expression. There could also be an optional `where` clause specified on the annotation for the join, similarly using
Associated entities can be fetched by annotating the entity's property reference with `@N1qlJoin`.
The prefix `lks` refers to left-hand side key space (current entity) and `rks` refers to the right-hand side key space (associated entity).
The required element for `@N1qlJoin` annotation is the `on` clause, a boolean expression representing the join condition between the left-hand side (`lks`) and the right-hand side (`rks`), which can be fields, constant expressions or any complex N1QL expression.
There could also be an optional `where` clause specified on the annotation for the join, similarly using
`lks` to refer the current entity and `rks` to refer the associated entity.
.Annotation for ANSI Join
@@ -42,9 +41,10 @@ public class Author {
[[couchbase.ansijoins.fetchtype]]
== Lazy fetching
Associated entities can be lazily fetched upon the first access of the property, this could save on fetching more data than
required when loading the entity. To load the associated entities lazily, `@N1qlJoin` annotation's element `fetchType`
has to be set to `FetchType.LAZY`. The default is `FetchType.IMMEDIATE`.
Associated entities can be lazily fetched upon the first access of the property, this could save on fetching more data than required when loading the entity.
To load the associated entities lazily, `@N1qlJoin` annotation's element `fetchType`
has to be set to `FetchType.LAZY`.
The default is `FetchType.IMMEDIATE`.
.Configuration for lazy fetch
====
@@ -63,11 +63,10 @@ List<Book> books;
`index` element on the `@N1qlJoin` can be used to provided the hint for the `lks` (current entity) index and `rightIndex`
element can be used to provided the `rks` (associated entity) index.
=== Hash Join Hint
If the join type is going to be hash join, the hash side can be specified for the `rks` (associated entity). If the associated
entity is on the build side, it can be specified as `HashSide.BUILD` else `HashSide.PROBE`.
If the join type is going to be hash join, the hash side can be specified for the `rks` (associated entity).
If the associated entity is on the build side, it can be specified as `HashSide.BUILD` else `HashSide.PROBE`.
=== Use Keys Hint

View File

@@ -4,18 +4,20 @@
This chapter describes how couchbase document keys can be auto-generated using builtin mechanisms.
There are two types of auto-generation strategies supported.
- <<couchbase.autokeygeneration.usingattributes>>
- <<couchbase.autokeygeneration.unique>>
- <<couchbase.autokeygeneration.usingattributes>>
- <<couchbase.autokeygeneration.unique>>
NOTE: The maximum key length supported by couchbase is 250 bytes.
[[couchbase.autokeygeneration.configuration]]
== Configuration
Keys to be auto-generated should be annotated with `@GeneratedValue`. The default strategy is `USE_ATTRIBUTES`. Prefix
and suffix for the key can be provided as part of the entity itself, these values are not persisted, they are only
used for key generation. The prefixes and suffixes are ordered using the `order` value. The default order is `0`, multiple
prefixes without order will overwrite the previous. If a value for id is already available, auto-generation will be skipped.
Keys to be auto-generated should be annotated with `@GeneratedValue`.
The default strategy is `USE_ATTRIBUTES`.
Prefix and suffix for the key can be provided as part of the entity itself, these values are not persisted, they are only used for key generation.
The prefixes and suffixes are ordered using the `order` value.
The default order is `0`, multiple prefixes without order will overwrite the previous.
If a value for id is already available, auto-generation will be skipped.
The delimiter for concatenation can be provided using `delimiter`, the default delimiter is `.`.
.Annotation for GeneratedValue
@@ -35,8 +37,9 @@ public class User {
----
====
Common prefix and suffix for all entities keys can be added to `CouchbaseTemplate` directly. Once added to the `CouchbaseTemplate`,
they become immutable. These settings are always applied irrespective of the `GeneratedValue` annotation.
Common prefix and suffix for all entities keys can be added to `CouchbaseTemplate` directly.
Once added to the `CouchbaseTemplate`, they become immutable.
These settings are always applied irrespective of the `GeneratedValue` annotation.
.Common key settings in CouchbaseTemplate
====
@@ -68,8 +71,8 @@ repo.exists(id);
[[couchbase.autokeygeneration.usingattributes]]
== Key generation using attributes
It is a common practice to generate keys using a combination of the document attributes. Key generation using attributes
concatenates all the attribute values annotated with `IdAttribute`, based on the ordering provided similar to prefixes and suffixes.
It is a common practice to generate keys using a combination of the document attributes.
Key generation using attributes concatenates all the attribute values annotated with `IdAttribute`, based on the ordering provided similar to prefixes and suffixes.
.Annotation for IdAttribute
====
@@ -89,8 +92,8 @@ public class User {
[[couchbase.autokeygeneration.unique]]
== Key generation using uuid
This auto-generation uses UUID random generator to generate document keys consuming 16 bytes of key space. This mechanism
is only recommended for test scaffolding.
This auto-generation uses UUID random generator to generate document keys consuming 16 bytes of key space.
This mechanism is only recommended for test scaffolding.
.Annotation for Unique key generation
====

View File

@@ -6,7 +6,8 @@ This chapter describes the common installation and configuration steps needed wh
[[installation]]
== Installation
All versions intended for production use are distributed across Maven Central and the Spring release repository. As a result, the library can be included like any other maven dependency:
All versions intended for production use are distributed across Maven Central and the Spring release repository.
As a result, the library can be included like any other maven dependency:
.Including the dependency through maven
====
@@ -22,7 +23,8 @@ All versions intended for production use are distributed across Maven Central an
This will pull in several dependencies, including the underlying Couchbase Java SDK, common Spring dependencies and also Jackson as the JSON mapping infrastructure.
You can also grab snapshots from the https://repo.spring.io/libs-snapshot[spring snapshot repository] and milestone releases from the https://repo.spring.io/libs-milestone[milestone repository]. Here is an example on how to use the current SNAPSHOT dependency:
You can also grab snapshots from the https://repo.spring.io/libs-snapshot[spring snapshot repository] and milestone releases from the https://repo.spring.io/libs-milestone[milestone repository].
Here is an example on how to use the current SNAPSHOT dependency:
.Using a snapshot version
====
@@ -42,12 +44,16 @@ You can also grab snapshots from the https://repo.spring.io/libs-snapshot[spring
----
====
Once you have all needed dependencies on the classpath, you can start configuring it. Both Java and XML config are supported. The next sections describe both approaches in detail.
Once you have all needed dependencies on the classpath, you can start configuring it.
Both Java and XML config are supported.
The next sections describe both approaches in detail.
[[configuration-java]]
== Annotation-based Configuration ("JavaConfig")
The annotation based configuration approach is getting more and more popular. It allows you to get rid of XML configuration and treat configuration as part of your code directly. To get started, all you need to do is subclcass the `AbstractCouchbaseConfiguration` and implement the abstract methods.
The annotation based configuration approach is getting more and more popular.
It allows you to get rid of XML configuration and treat configuration as part of your code directly.
To get started, all you need to do is subclcass the `AbstractCouchbaseConfiguration` and implement the abstract methods.
Please make sure to have cglib support in the classpath so that the annotation based configuration works.
@@ -70,24 +76,30 @@ public class Config extends AbstractCouchbaseConfiguration {
}
@Override
protected String getBucketPassword() {
protected String getPassword() {
return "";
}
}
----
====
All you need to provide is a list of Couchbase nodes to bootstrap into (without any ports, just the IP address or hostname). Please note that while one host is sufficient in development, it is recommended to add 3 to 5 bootstrap nodes here. Couchbase will pick up all nodes from the cluster automatically, but it could be the case that the only node you've provided is experiencing issues while you are starting the application.
All you need to provide is a list of Couchbase nodes to bootstrap into (without any ports, just the IP address or hostname).
Please note that while one host is sufficient in development, it is recommended to add 3 to 5 bootstrap nodes here.
Couchbase will pick up all nodes from the cluster automatically, but it could be the case that the only node you've provided is experiencing issues while you are starting the application.
The `bucketName` and `password` should be the same as configured in Couchbase Server itself. In the example given, we are connecting to the `beer-sample` bucket which is one of the sample buckets shipped with Couchbase Server and has no password set by default.
The `bucketName` and `password` should be the same as configured in Couchbase Server itself.
In the example given, we are connecting to the `beer-sample` bucket which is one of the sample buckets shipped with Couchbase Server and has no password set by default.
Depending on how your environment is set up, the configuration will be automatically picked up by the context or you need to instantiate your own one. How to manage configurations is not in scope of this manual, please refer to the spring documentation for more information on that topic.
Depending on how your environment is set up, the configuration will be automatically picked up by the context or you need to instantiate your own one.
How to manage configurations is not in scope of this manual, please refer to the spring documentation for more information on that topic.
Additionally, the SDK environment can be tuned by overriding the `getEnvironment()` method to return a properly tuned `CouchbaseEnvironment`.
While not immediately obvious, much more things can be customized and overridden as custom beans from this configuration (for example repositories, query consistency, validation and custom converters).
TIP: If you use `SyncGateway` and `CouchbaseMobile`, you may run into problem with fields prefixed by `_`. Since Spring Data Couchbase by default stores the type information as a `_class` attribute this can be problematic. Override `typeKey()` (for example to return `MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE`) to change the name of said attribute.
TIP: If you use `SyncGateway` and `CouchbaseMobile`, you may run into problem with fields prefixed by `_`.
Since Spring Data Couchbase by default stores the type information as a `_class` attribute this can be problematic.
Override `typeKey()` (for example to return `MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE`) to change the name of said attribute.
TIP: For generated queries, if you want strong consistency (at the expense of performance), you can override `getDefaultConsistency()` and return `Consistency.READ_YOUR_OWN_WRITES`.
@@ -121,9 +133,13 @@ The library provides a custom namespace that you can use in your XML configurati
</beans:beans>
----
====
This code is equivalent to the java configuration approach shown above. You can customize the SDK `CouchbaseEnvironment` via the `<couchbase:env/>` tag, that supports most tuning parameters as attributes. It is also possible to configure templates and repositories, which is shown in the appropriate sections.
This code is equivalent to the java configuration approach shown above.
You can customize the SDK `CouchbaseEnvironment` via the `<couchbase:env/>` tag, that supports most tuning parameters as attributes.
It is also possible to configure templates and repositories, which is shown in the appropriate sections.
IMPORTANT: The XML configuration **must** include the `clusterInfo` credentials, in order to be able to detect N1QL feature.
If you start your application, you should see Couchbase INFO level logging in the logs, indicating that the underlying Couchbase Java SDK is connecting to the database. If any errors are reported, make sure that the given credentials and host information are correct.
If you start your application, you should see Couchbase INFO level logging in the logs, indicating that the underlying Couchbase Java SDK is connecting to the database.
If any errors are reported, make sure that the given credentials and host information are correct.

View File

@@ -10,11 +10,14 @@ include::{spring-data-commons-docs}/object-mapping.adoc[leveloffset=+1]
All entities should be annotated with the `@Document` annotation.
Also, every field in the entity should be annotated with the `@Field` annotation from the Couchbase SDK. While this is - strictly speaking - optional, it helps to reduce edge cases and clearly shows the intent and design of the entity. It can also be used to store the field under a different name.
Also, every field in the entity should be annotated with the `@Field` annotation from the Couchbase SDK. While this is - strictly speaking - optional, it helps to reduce edge cases and clearly shows the intent and design of the entity.
It can also be used to store the field under a different name.
There is also a special `@Id` annotation which needs to be always in place. Best practice is to also name the property `id`.
There is also a special `@Id` annotation which needs to be always in place.
Best practice is to also name the property `id`.
TIP: Both the Couchbase SDK and Spring Data define their own `@Id` annotation. Either can be used (the Spring Data one will get priority if both are found on different fields).
TIP: Both the Couchbase SDK and Spring Data define their own `@Id` annotation.
Either can be used (the Spring Data one will get priority if both are found on different fields).
Here is a very simple `User` entity:
@@ -60,22 +63,31 @@ public class User {
----
====
Couchbase Server supports automatic expiration for documents. The library implements support for it through the `@Document` annotation. You can set a `expiry` value which translates to the number of seconds until the document gets removed automatically. If you want to make it expire in 10 seconds after mutation, set it like `@Document(expiry = 10)`. Alternatively, you can configure the expiry using Spring's property support and the `expiryExpression` parameter, to allow for dynamically changing the expiry value. For example: `@Document(expiryExpression = "${valid.document.expiry}")`. The property must be resolvable to an int value and the two approaches cannot be mixed.
Couchbase Server supports automatic expiration for documents.
The library implements support for it through the `@Document` annotation.
You can set a `expiry` value which translates to the number of seconds until the document gets removed automatically.
If you want to make it expire in 10 seconds after mutation, set it like `@Document(expiry = 10)`.
Alternatively, you can configure the expiry using Spring's property support and the `expiryExpression` parameter, to allow for dynamically changing the expiry value.
For example: `@Document(expiryExpression = "${valid.document.expiry}")`.
The property must be resolvable to an int value and the two approaches cannot be mixed.
If you want a different representation of the field name inside the document in contrast to the field name used in your entity, you can set a different name on the `@Field` annotation.
For example if you want to keep your documents small you can set the firstname field to `@Field("fname")`.
In the JSON document, you'll see `{"fname": ".."}` instead of `{"firstname": ".."}`.
If you want a different representation of the field name inside the document in contrast to the field name used in your entity, you can set a different name on the `@Field` annotation. For example if you want to keep your documents small you can set the firstname field to `@Field("fname")`. In the JSON document, you'll see `{"fname": ".."}` instead of `{"firstname": ".."}`.
The `@Id` annotation needs to be present because every document in Couchbase needs a unique key. This key needs to be any string with a length of maximum 250 characters. Feel free to use whatever fits your use case, be it a UUID, an email address or anything else.
The `@Id` annotation needs to be present because every document in Couchbase needs a unique key.
This key needs to be any string with a length of maximum 250 characters.
Feel free to use whatever fits your use case, be it a UUID, an email address or anything else.
[[datatypes]]
== Datatypes and Converters
The storage format of choice is JSON. It is great, but like many data representations it allows less datatypes than you could express in Java directly. Therefore, for all non-primitive types some form of conversion to and from supported types needs to happen.
The storage format of choice is JSON. It is great, but like many data representations it allows less datatypes than you could express in Java directly.
Therefore, for all non-primitive types some form of conversion to and from supported types needs to happen.
For the following entity field types, you don't need to add special handling:
[cols="2", options="header"]
[cols="2",options="header"]
.Primitive Types
|===
| Java Type
@@ -108,7 +120,10 @@ For the following entity field types, you don't need to add special handling:
| null
| Ignored on write
|===
Since JSON supports objects ("maps") and lists, `Map` and `List` types can be converted naturally. If they only contain primitive field types from the last paragraph, you don't need to add special handling too. Here is an example:
Since JSON supports objects ("maps") and lists, `Map` and `List` types can be converted naturally.
If they only contain primitive field types from the last paragraph, you don't need to add special handling too.
Here is an example:
.A Document with Map and List
====
@@ -159,7 +174,9 @@ Storing a user with some sample data could look like this as a JSON representati
----
====
You don't need to break everything down to primitive types and Lists/Maps all the time. Of course, you can also compose other objects out of those primitive values. Let's modify the last example so that we want to store a `List` of `Children`:
You don't need to break everything down to primitive types and Lists/Maps all the time.
Of course, you can also compose other objects out of those primitive values.
Let's modify the last example so that we want to store a `List` of `Children`:
.A Document with composed objects
====
@@ -226,7 +243,12 @@ A populated object can look like:
----
====
Most of the time, you also need to store a temporal value like a `Date`. Since it can't be stored directly in JSON, a conversion needs to happen. The library implements default converters for `Date`, `Calendar` and JodaTime types (if on the classpath). All of those are represented by default in the document as a unix timestamp (number). You can always override the default behavior with custom converters as shown later. Here is an example:
Most of the time, you also need to store a temporal value like a `Date`.
Since it can't be stored directly in JSON, a conversion needs to happen.
The library implements default converters for `Date`, `Calendar` and JodaTime types (if on the classpath).
All of those are represented by default in the document as a unix timestamp (number).
You can always override the default behavior with custom converters as shown later.
Here is an example:
.A Document with Date and Calendar
====
@@ -274,7 +296,10 @@ A populated object can look like:
====
Optionally, Date can be converted to and from ISO-8601 compliant strings by setting system property `org.springframework.data.couchbase.useISOStringConverterForDate` to true.
If you want to override a converter or implement your own one, this is also possible. The library implements the general Spring Converter pattern. You can plug in custom converters on bean creation time in your configuration. Here's how you can configure it (in your overridden `AbstractCouchbaseConfiguration`):
If you want to override a converter or implement your own one, this is also possible.
The library implements the general Spring Converter pattern.
You can plug in custom converters on bean creation time in your configuration.
Here's how you can configure it (in your overridden `AbstractCouchbaseConfiguration`):
.Custom Converters
====
@@ -311,17 +336,26 @@ public static enum BarToFooConverter implements Converter<Bar, Foo> {
There are a few things to keep in mind with custom conversions:
* To make it unambiguous, always use the `@WritingConverter` and `@ReadingConverter` annotations on your converters. Especially if you are dealing with primitive type conversions, this will help to reduce possible wrong conversions.
* If you implement a writing converter, make sure to decode into primitive types, maps and lists only. If you need more complex object types, use the `CouchbaseDocument` and `CouchbaseList` types, which are also understood by the underlying translation engine. Your best bet is to stick with as simple as possible conversions.
* To make it unambiguous, always use the `@WritingConverter` and `@ReadingConverter` annotations on your converters.
Especially if you are dealing with primitive type conversions, this will help to reduce possible wrong conversions.
* If you implement a writing converter, make sure to decode into primitive types, maps and lists only.
If you need more complex object types, use the `CouchbaseDocument` and `CouchbaseList` types, which are also understood by the underlying translation engine.
Your best bet is to stick with as simple as possible conversions.
* Always put more special converters before generic converters to avoid the case where the wrong converter gets executed.
* For dates, reading converters should be able to read from any `Number` (not just `Long`). This is required for N1QL support.
* For dates, reading converters should be able to read from any `Number` (not just `Long`).
This is required for N1QL support.
[[version]]
== Optimistic Locking
Couchbase Server does not support multi-document transactions or rollback. To implement optimistic locking, Couchbase uses a CAS (compare and swap) approach. When a document is mutated, the CAS value also changes. The CAS is opaque to the client, the only thing you need to know is that it changes when the content or a meta information changes too.
Couchbase Server does not support multi-document transactions or rollback.
To implement optimistic locking, Couchbase uses a CAS (compare and swap) approach.
When a document is mutated, the CAS value also changes.
The CAS is opaque to the client, the only thing you need to know is that it changes when the content or a meta information changes too.
In other datastores, similar behavior can be achieved through an arbitrary version field with a incrementing counter. Since Couchbase supports this in a much better fashion, it is easy to implement. If you want automatic optimistic locking support, all you need to do is add a `@Version` annotation on a long field like this:
In other datastores, similar behavior can be achieved through an arbitrary version field with a incrementing counter.
Since Couchbase supports this in a much better fashion, it is easy to implement.
If you want automatic optimistic locking support, all you need to do is add a `@Version` annotation on a long field like this:
.A Document with optimistic locking.
====
@@ -338,14 +372,20 @@ public class User {
----
====
If you load a document through the template or repository, the version field will be automatically populated with the current CAS value. It is important to note that you shouldn't access the field or even change it on your own. Once you save the document back, it will either succeed or fail with a `OptimisticLockingFailureException`. If you get such an exception, the further approach depends on what you want to achieve application wise. You should either retry the complete load-update-write cycle or propagate the error to the upper layers for proper handling.
If you load a document through the template or repository, the version field will be automatically populated with the current CAS value.
It is important to note that you shouldn't access the field or even change it on your own.
Once you save the document back, it will either succeed or fail with a `OptimisticLockingFailureException`.
If you get such an exception, the further approach depends on what you want to achieve application wise.
You should either retry the complete load-update-write cycle or propagate the error to the upper layers for proper handling.
[[validation]]
== Validation
The library supports JSR 303 validation, which is based on annotations directly in your entities. Of course you can add all kinds of validation in your service layer, but this way its nicely coupled to your actual entities.
The library supports JSR 303 validation, which is based on annotations directly in your entities.
Of course you can add all kinds of validation in your service layer, but this way its nicely coupled to your actual entities.
To make it work, you need to include two additional dependencies. JSR 303 and a library that implements it, like the one supported by hibernate:
To make it work, you need to include two additional dependencies.
JSR 303 and a library that implements it, like the one supported by hibernate:
.Validation dependencies
====
@@ -361,6 +401,7 @@ To make it work, you need to include two additional dependencies. JSR 303 and a
</dependency>
----
====
Now you need to add two beans to your configuration:
.Validation beans
@@ -379,7 +420,8 @@ public ValidatingCouchbaseEventListener validationEventListener() {
----
====
Now you can annotate your fields with JSR303 annotations. If a validation on `save()` fails, a `ConstraintViolationException` is thrown.
Now you can annotate your fields with JSR303 annotations.
If a validation on `save()` fails, a `ConstraintViolationException` is thrown.
.Sample Validation Annotation
====
@@ -393,13 +435,18 @@ private String name;
[[auditing]]
== Auditing
Entities can be automatically audited (tracing which user created the object, updated the object, and at what times) through Spring Data auditing mechanisms.
First, note that only entities that have a `@Version` annotated field can be audited for creation (otherwise the framework will interpret a creation as an update).
Auditing works by annotating fields with `@CreatedBy`, `@CreatedDate`, `@LastModifiedBy` and `@LastModifiedDate`. The framework will automatically inject the correct values on those fields when persisting the entity. The xxxDate annotations must be put on a `Date` field (or compatible, eg. jodatime classes) while the xxxBy annotations can be put on fields of any class `T` (albeit both fields must be of the same type).
Auditing works by annotating fields with `@CreatedBy`, `@CreatedDate`, `@LastModifiedBy` and `@LastModifiedDate`.
The framework will automatically inject the correct values on those fields when persisting the entity.
The xxxDate annotations must be put on a `Date` field (or compatible, eg. jodatime classes) while the xxxBy annotations can be put on fields of any class `T` (albeit both fields must be of the same type).
To configure auditing, first you need to have an auditor aware bean in the context. Said bean must be of type `AuditorAware<T>` (allowing to produce a value that can be stored in the xxxBy fields of type `T` we saw earlier). Secondly, you must activate auditing in your `@Configuration` class by using the `@EnableCouchbaseAuditing` annotation.
To configure auditing, first you need to have an auditor aware bean in the context.
Said bean must be of type `AuditorAware<T>` (allowing to produce a value that can be stored in the xxxBy fields of type `T` we saw earlier).
Secondly, you must activate auditing in your `@Configuration` class by using the `@EnableCouchbaseAuditing` annotation.
Here is an example:
@@ -434,6 +481,7 @@ public class AuditedItem {
}
----
====
Notice both `@CreatedBy` and `@LastModifiedBy` are both put on a `String` field, so our `AuditorAware` must work with `String`.
.Sample AuditorAware implementation

View File

@@ -26,7 +26,6 @@ include::template.adoc[]
include::ansijoins.adoc[]
:leveloffset: -1
[[appendix]]
= Appendix

View File

@@ -5,9 +5,11 @@ This chapter is a quick reference of what major changes have been introduced in
[[couchbase.migrating.configuration]]
== Configuration
The configuration, xml schema, etc... has changed to take the evolution of the 2.x SDK API into account.
Where a single `CouchbaseClient` bean was previously the only bean declarable, you can now declare a `Cluster` bean (`<couchbase:cluster>`), one or more `Bucket` beans (`<couchbase:bucket>`) and even tune the SDK via a `CouchbaseEnvironment` bean (`<couchbase:env>`). All of these can also be created via Java Config method by extending `AbstractCouchbaseConfig`.
Where a single `CouchbaseClient` bean was previously the only bean declarable, you can now declare a `Cluster` bean (`<couchbase:cluster>`), one or more `Bucket` beans (`<couchbase:bucket>`) and even tune the SDK via a `CouchbaseEnvironment` bean (`<couchbase:env>`).
All of these can also be created via Java Config method by extending `AbstractCouchbaseConfig`.
The cluster bean lists the nodes to connect through (and references the environment bean if tuning is necessary) while the bucket beans map to bucket names and passwords and actually opens the connections internally.
@@ -17,12 +19,14 @@ For more information, see <<couchbase.configuration>>.
[[couchbase.migrating.repository-queries]]
== Repository queries
The view-backed query method has evolved and support for N1QL has been introduced. As a result, there are now 4 ways of doing repository queries:
* Simple View query (to return all elements emitted by a view) - @View annotated without `viewName`
* Intermediate View query by query derivation (to provide some criteria for the view) @View annotated with `viewName`
* N1QL with explicit statements inline - `@Query` annotated with value
* N1QL query derivation - `@Query` annotated without value / no annotation (default)
The view-backed query method has evolved and support for N1QL has been introduced.
As a result, there are now 4 ways of doing repository queries:
* Simple View query (to return all elements emitted by a view) - @View annotated without `viewName`
* Intermediate View query by query derivation (to provide some criteria for the view) @View annotated with `viewName`
* N1QL with explicit statements inline - `@Query` annotated with value
* N1QL query derivation - `@Query` annotated without value / no annotation (default)
View backed queries are associated with the `@View` annotation, while N1QL backed queries are associated with the `@Query` annotation.
@@ -32,6 +36,7 @@ See <<couchbase.repository.n1ql>> and <<couchbase.repository.views>> for more in
[[couchbase.migrating.backing-views]]
== Backing views and view query changes
IMPORTANT: The `all` view is still backing most CRUD operations, but custom repository methods are now by default backed by N1QL.
To instead back them with views, use the `@View` annotation explicitly.
@@ -41,6 +46,7 @@ Otherwise, query derivation will be used to parameterize the view query from the
[[couchbase.migrating.view-query]]
=== Passing a ViewQuery object as a parameter to a custom repository method
This behavior has been removed and the recommended approach is now to either use query derivation (if the query parameters are simple enough) or <<repositories.single-repository-behaviour>>.
For instance, for a view emitting user lastNames, the following:
@@ -61,6 +67,7 @@ List<User> findFirst3ByLastnameEquals(String lastName);
[[couchbase.migrating.reduce-in-views]]
=== Reduce in views
Reduce is now supported in view-based querying.
It can be triggered by prefixing the method name with `count` instead of `find`.
@@ -70,6 +77,6 @@ Alternatively, it can be explicitly be activated by setting `reduce = true` on t
Be sure to construct your view correctly:
* specify a reduce function that matches the method return type, which can be anything, eg. long or JSON object
* emit a simple key (not `null` nor a compound key).
* emit a value suitable for the reduce to work (typically `_count` doesn't need any particular value, but `_stats` will need a numerical value, in addition to the key).
* specify a reduce function that matches the method return type, which can be anything, eg. long or JSON object
* emit a simple key (not `null` nor a compound key).
* emit a value suitable for the reduce to work (typically `_count` doesn't need any particular value, but `_stats` will need a numerical value, in addition to the key).

View File

@@ -4,7 +4,8 @@
[[couchbase.reactiverepository.intro]]
== Introduction
This chapter describes the reactive repository support for couchbase. This builds on the core repository support explained in <<couchbase.repository>>.
This chapter describes the reactive repository support for couchbase.
This builds on the core repository support explained in <<couchbase.repository>>.
So make sure youve got a sound understanding of the basic concepts explained there.
[[couchbase.reactiverepository.libraries]]
@@ -20,11 +21,13 @@ Reactive Couchbase repositories provide project Reactor wrapper types and can be
* ReactiveSortingRepository
Spring-data-couchbase converts RxJava 1 observables to reactor types by using reactive-streams adapters from https://github.com/ReactiveX/[RxJavaReactiveStreams]
for convenience since these conversions can easily clutter application code. This transformation happens on same thread. It also provides direct access to RxJava1 observable
sequence API from SDK through RxJavaCouchbaseOperations methods.
for convenience since these conversions can easily clutter application code.
This transformation happens on same thread.
It also provides direct access to RxJava1 observable sequence API from SDK through RxJavaCouchbaseOperations methods.
[[couchbase.reactiverepository.usage]]
== Usage
To access domain entities stored in a Couchbase bucket you can leverage our sophisticated repository support that eases implementing those quite significantly.
To do so, simply create an interface for your repository:
@@ -64,7 +67,9 @@ public interface ReactivePersonRepository extends ReactiveSortingRepository<Pers
----
====
For JavaConfig use the `@EnableReactiveCouchbaseRepositories` annotation. The annotation carries the very same attributes like the namespace element. If no base package is configured the infrastructure will scan the package of the annotated configuration class.
For JavaConfig use the `@EnableReactiveCouchbaseRepositories` annotation.
The annotation carries the very same attributes like the namespace element.
If no base package is configured the infrastructure will scan the package of the annotated configuration class.
.JavaConfig for repositories
====
@@ -85,14 +90,15 @@ class ApplicationConfig extends AbstractReactiveCouchbaseConfiguration {
}
@Override
protected String getBucketPassword() {
protected String getPassword() {
return "";
}
}
----
====
As our domain repository extends `ReactiveSortingRepository` it provides you with CRUD operations as well as methods for sorted access to the entities. Working with the repository instance is just a matter of dependency injecting it into a client.
As our domain repository extends `ReactiveSortingRepository` it provides you with CRUD operations as well as methods for sorted access to the entities.
Working with the repository instance is just a matter of dependency injecting it into a client.
.Sorted access to Person entities
====
@@ -115,4 +121,4 @@ public class PersonRepositoryTests {
[[couchbase.reactiverepository.querying]]
== Repositories and Querying
Spring Data's Reactive Couchbase comes with full querying support already provided by the blocking <<couchbase.repository.querying>>
Spring Data's Reactive Couchbase comes with full querying support already provided by the blocking <<couchbase.repository.querying>>

View File

@@ -5,9 +5,9 @@ The goal of Spring Data repository abstraction is to significantly reduce the am
There are three backing mechanisms in Couchbase for repositories, described in the sections of this chapter:
- <<couchbase.repository.n1ql>>
- <<couchbase.repository.views.querying>>
- <<couchbase.repository.spatial>>
- <<couchbase.repository.n1ql>>
- <<couchbase.repository.views.querying>>
- <<couchbase.repository.spatial>>
CRUD operations are still mostly backed by Couchbase views (see <<couchbase.repository.views>>).
Such views (and, for N1QL, equivalent indexes) can be automatically built, but note this is **discouraged in production** and can be an **expensive operation** (see <<couchbase.repository.indexing>>).
@@ -17,7 +17,9 @@ Note that you can tune the consistency you want for your queries (see <<couchbas
[[couchbase.repository.configuration]]
== Configuration
While support for repositories is always present, you need to enable them in general or for a specific namespace. If you extend `AbstractCouchbaseConfiguration`, just use the `@EnableCouchbaseRepositories` annotation. It provides lots of possible options to narrow or customize the search path, one of the most common ones is `basePackages`.
While support for repositories is always present, you need to enable them in general or for a specific namespace.
If you extend `AbstractCouchbaseConfiguration`, just use the `@EnableCouchbaseRepositories` annotation.
It provides lots of possible options to narrow or customize the search path, one of the most common ones is `basePackages`.
.Annotation-Based Repository Setup
====
@@ -30,6 +32,7 @@ public class Config extends AbstractCouchbaseConfiguration {
}
----
====
An advanced usage is described in <<couchbase.repository.multibucket>>.
XML-based configuration is also available:
@@ -45,7 +48,8 @@ XML-based configuration is also available:
[[couchbase.repository.usage]]
== Usage
In the simplest case, your repository will extend the `CrudRepository<T, String>`, where T is the entity that you want to expose. Let's look at a repository for a UserInfo:
In the simplest case, your repository will extend the `CrudRepository<T, String>`, where T is the entity that you want to expose.
Let's look at a repository for a UserInfo:
.A UserInfo repository
====
@@ -58,11 +62,14 @@ public interface UserRepository extends CrudRepository<UserInfo, String> {
----
====
Please note that this is just an interface and not an actual class. In the background, when your context gets initialized, actual implementations for your repository descriptions get created and you can access them through regular beans. This means you will save lots of boilerplate code while still exposing full CRUD semantics to your service layer and application.
Please note that this is just an interface and not an actual class.
In the background, when your context gets initialized, actual implementations for your repository descriptions get created and you can access them through regular beans.
This means you will save lots of boilerplate code while still exposing full CRUD semantics to your service layer and application.
Now, let's imagine we `@Autowire` the `UserRepository` to a class that makes use of it. What methods do we have available?
Now, let's imagine we `@Autowire` the `UserRepository` to a class that makes use of it.
What methods do we have available?
[cols="2", options="header"]
[cols="2",options="header"]
.Exposed methods on the UserRepository
|===
| Method
@@ -102,19 +109,24 @@ Now, let's imagine we `@Autowire` the `UserRepository` to a class that makes use
| Delete all entities by type in the bucket.
|===
Now that's awesome! Just by defining an interface we get full CRUD functionality on top of our managed entity. All methods suffixed with (*) in the table are backed by Views, which is explained later.
Now that's awesome!
Just by defining an interface we get full CRUD functionality on top of our managed entity.
All methods suffixed with (*) in the table are backed by Views, which is explained later.
While the exposed methods provide you with a great variety of access patterns, very often you need to define custom ones. You can do this by adding method declarations to your interface, which will be automatically resolved to requests in the background, as we'll see in the next sections.
While the exposed methods provide you with a great variety of access patterns, very often you need to define custom ones.
You can do this by adding method declarations to your interface, which will be automatically resolved to requests in the background, as we'll see in the next sections.
[[couchbase.repository.querying]]
== Repositories and Querying
[[couchbase.repository.n1ql]]
=== N1QL based querying
As of version `4.0`, Couchbase Server ships with a new query language called `N1QL`. In `Spring-Data-Couchbase 2.0`, N1QL is the default way of doing queries and will allow you to fully derive queries from a method name.
Prerequisite is to have a N1QL-compatible cluster and to have created a PRIMARY INDEX on the bucket where the entities will be stored. DML queries
are supported from Couchbase server version `4.1`.
As of version `4.0`, Couchbase Server ships with a new query language called `N1QL`.
In `Spring-Data-Couchbase 2.0`, N1QL is the default way of doing queries and will allow you to fully derive queries from a method name.
Prerequisite is to have a N1QL-compatible cluster and to have created a PRIMARY INDEX on the bucket where the entities will be stored.
DML queries are supported from Couchbase server version `4.1`.
WARNING: If it is detected at configuration time that the cluster doesn't support N1QL while there are `@Query` annotated methods or non-annotated methods in your repository interface, a `UnsupportedCouchbaseFeatureException` will be thrown.
@@ -136,19 +148,23 @@ public interface UserRepository extends CrudRepository<UserInfo, String> {
Here we see two N1QL-backed ways of querying.
The first method uses the `Query` annotation to provide a N1QL statement inline. SpEL (Spring Expression Language) is supported by surrounding SpEL expression blocks between `#{` and `}`.
The first method uses the `Query` annotation to provide a N1QL statement inline.
SpEL (Spring Expression Language) is supported by surrounding SpEL expression blocks between `#{` and `}`.
A few N1QL-specific values are provided through SpEL:
- `#n1ql.selectEntity` allows to easily make sure the statement will select all the fields necessary to build the full entity (including document ID and CAS value).
- `#n1ql.filter` in the WHERE clause adds a criteria matching the entity type with the field that Spring Data uses to store type information.
- `#n1ql.bucket` will be replaced by the name of the bucket the entity is stored in, escaped in backticks.
- `#n1ql.fields` will be replaced by the list of fields (eg. for a SELECT clause) necessary to reconstruct the entity.
- `#n1ql.delete` will be replaced by the `delete from` statement.
- `#n1ql.returning` will be replaced by returning clause needed for reconstructing entity.
- `#n1ql.selectEntity` allows to easily make sure the statement will select all the fields necessary to build the full entity (including document ID and CAS value).
- `#n1ql.filter` in the WHERE clause adds a criteria matching the entity type with the field that Spring Data uses to store type information.
- `#n1ql.bucket` will be replaced by the name of the bucket the entity is stored in, escaped in backticks.
- `#n1ql.fields` will be replaced by the list of fields (eg. for a SELECT clause) necessary to reconstruct the entity.
- `#n1ql.delete` will be replaced by the `delete from` statement.
- `#n1ql.returning` will be replaced by returning clause needed for reconstructing entity.
IMPORTANT: We recommend that you always use the `selectEntity` SpEL and a WHERE clause with a `filter` SpEL (since otherwise your query could be impacted by entities from other repositories).
String-based queries support parametrized queries. You can either use positional placeholders like "`$1`", in which case each of the method parameters will map, in order, to `$1`, `$2`, `$3`... Alternatively, you can use named placeholders using the "`$someString`" syntax. Method parameters will be matched with their corresponding placeholder using the parameter's name, which can be overridden by annotating each parameter (except a `Pageable` or `Sort`) with `@Param` (eg. `@Param("someString")`). You cannot mix the two approaches in your query and will get an `IllegalArgumentException` if you do.
String-based queries support parametrized queries.
You can either use positional placeholders like "`$1`", in which case each of the method parameters will map, in order, to `$1`, `$2`, `$3`... Alternatively, you can use named placeholders using the "`$someString`" syntax.
Method parameters will be matched with their corresponding placeholder using the parameter's name, which can be overridden by annotating each parameter (except a `Pageable` or `Sort`) with `@Param` (eg. `@Param("someString")`).
You cannot mix the two approaches in your query and will get an `IllegalArgumentException` if you do.
Note that you can mix N1QL placeholders and SpEL. N1QL placeholders will still consider all method parameters, so be sure to use the correct index like in the example below:
@@ -163,7 +179,9 @@ public List<User> findUsersByDynamicCriteria(String criteriaField, Object criter
This allows you to generate queries that would work similarly to eg. `AND name = "someName"` or `AND age = 3`, with a single method declaration.
You can also do single projections in your N1QL queries (provided it selects only one field and returns only one result, usually an aggregation like `COUNT`, `AVG`, `MAX`...). Such projection would have a simple return type like `long`, `boolean` or `String`. This is *NOT* intended for projections to DTOs.
You can also do single projections in your N1QL queries (provided it selects only one field and returns only one result, usually an aggregation like `COUNT`, `AVG`, `MAX`...).
Such projection would have a simple return type like `long`, `boolean` or `String`.
This is *NOT* intended for projections to DTOs.
Another example: +
`#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1` +
@@ -176,6 +194,7 @@ SpEL can be useful when you want to do a query depending on data injected by oth
Here is what you need to do to extend the SpEL context to get access to such external data.
First, you need to implement an `EvaluationContextExtension` (use the support class as below):
[source,java]
----
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
@@ -194,6 +213,7 @@ class SecurityEvaluationContextExtension extends EvaluationContextExtensionSuppo
----
Then all you need to do for Spring Data Couchbase to be able to access associated SpEL values is to declare a corresponding bean in your configuration:
[source,java]
----
@Bean
@@ -203,6 +223,7 @@ EvaluationContextExtension securityExtension() {
----
This could be useful to craft a query according to the role of the connected user for instance:
[source,java]
----
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND " +
@@ -211,6 +232,7 @@ List<UserInfo> findAllAdmins(); //only ROLE_ADMIN users will see hidden admins
----
Delete query example:
[source,java]
----
@Query("#{#n1ql.delete} WHERE #{#n1ql.filter} AND " +
@@ -220,13 +242,16 @@ UserInfo removeUser(String username);
****
The second method uses Spring-Data's query derivation mechanism to build a N1QL query from the method name and parameters. This will produce a query looking like this: `SELECT ... FROM ... WHERE firstName = "valueOfFnameAtRuntime"`. You can combine these criteria, even do a count with a name like `countByFirstname` or a limit with a name like `findFirst3ByLastname`...
The second method uses Spring-Data's query derivation mechanism to build a N1QL query from the method name and parameters.
This will produce a query looking like this: `SELECT ... FROM ... WHERE firstName = "valueOfFnameAtRuntime"`.
You can combine these criteria, even do a count with a name like `countByFirstname` or a limit with a name like `findFirst3ByLastname`...
NOTE: Actually the generated N1QL query will also contain an additional N1QL criteria in order to only select documents that match the repository's entity class.
Most Spring-Data keywords are supported:
.Supported keywords inside @Query (N1QL) method names
[options = "header, autowidth"]
[options = "header,autowidth"]
|===============
|Keyword|Sample|N1QL WHERE clause snippet
|`And`|`findByLastnameAndFirstname`|`lastName = a AND firstName = b`
@@ -260,7 +285,8 @@ You can use both counting queries and <<repositories.limit-query-result>> featur
With N1QL, another possible interface for the repository is the `PagingAndSortingRepository` one (which extends CRUDRepository).
It adds two methods:
[cols="2", options="header"]
[cols="2",options="header"]
.Exposed methods on the PagingAndSortingRepository
|===
| Method
@@ -281,7 +307,9 @@ The second way of querying, supported also in older versions of Couchbase Server
[[couchbase.repository.views]]
=== Backing Views
This is the historical way of secondary indexing in Couchbase. Views are much more limited in terms of querying flexibility, and each custom method may very well need its own backing view, to be prepared in the cluster beforehand.
This is the historical way of secondary indexing in Couchbase.
Views are much more limited in terms of querying flexibility, and each custom method may very well need its own backing view, to be prepared in the cluster beforehand.
We'll only cover views to the extent to which they are needed, if you need in-depth information about them please refer to the official Couchbase Server manual and the Couchbase Java SDK manual.
@@ -289,9 +317,12 @@ As a rule of thumb, all repository CRUD access methods which are not "by a speci
IMPORTANT: This is only true for the methods directly defined by the `CrudRepository` interface (the one marked with a `*` in `Table 3.` above), since your additional methods can now be backed by N1QL.
To cover the basic CRUD methods from the `CrudRepository`, one view needs to be implemented in Couchbase Server. It basically returns all documents for the specific entity and also adds the optional reduce function `_count`.
To cover the basic CRUD methods from the `CrudRepository`, one view needs to be implemented in Couchbase Server.
It basically returns all documents for the specific entity and also adds the optional reduce function `_count`.
Since every view has a design document and view name, by convention we default to `all` as the view name and the uncapitalized (lowercase first letter) entity name as the design document name. So if your entity is named `UserInfo`, then the code expects the `all` view in the `userInfo` design document. It needs to look like this:
Since every view has a design document and view name, by convention we default to `all` as the view name and the uncapitalized (lowercase first letter) entity name as the design document name.
So if your entity is named `UserInfo`, then the code expects the `all` view in the `userInfo` design document.
It needs to look like this:
.The all view map function
====
@@ -306,22 +337,32 @@ function (doc, meta) {
----
====
Note that the important part in this map function is to only include the document IDs which correspond to our entity. Because the library always adds the `_class` property, this is a quick and easy way to do it. If you have another property in your JSON which does the same job (like a explicit `type` field), then you can use that as well - you don't have to stick to `_class` all the time.
Note that the important part in this map function is to only include the document IDs which correspond to our entity.
Because the library always adds the `_class` property, this is a quick and easy way to do it.
If you have another property in your JSON which does the same job (like a explicit `type` field), then you can use that as well - you don't have to stick to `_class` all the time.
Also make sure to publish your design documents into production so that they can be picked up by the library! Also, if you are curious why we use `emit(meta.id, null)` in the view despite the document id being always sent over to the client implicitly, it is so the view can be queried with a list of ids, eg. in the `findAll(Iterable<ID> ids)` CRUD method.
Also make sure to publish your design documents into production so that they can be picked up by the library!
Also, if you are curious why we use `emit(meta.id, null)` in the view despite the document id being always sent over to the client implicitly, it is so the view can be queried with a list of ids, eg. in the `findAll(Iterable<ID> ids)` CRUD method.
[[couchbase.repository.indexing]]
=== Automatic Index Management
We've seen that the repositories default methods can be backed by two broad kind of features: views and N1QL (in the case of paging and sorting).
In order for the CRUD operations to work, the adequate view must have been created beforehand, and this is usually left for the user to do. First because view creation (and index creation) is an expensive operation that can take quite some time if the quantity of documents is high. Second, because in production it is considered best practice to avoid administration of the cluster elements like buckets, indexes and view by an application code.
In order for the CRUD operations to work, the adequate view must have been created beforehand, and this is usually left for the user to do.
First because view creation (and index creation) is an expensive operation that can take quite some time if the quantity of documents is high.
Second, because in production it is considered best practice to avoid administration of the cluster elements like buckets, indexes and view by an application code.
In the case where the index creation cost isn't considered too high and you are not in a production environment, it can be triggered automatically instead, in two steps. You will first need to annotate the repositories you want managed with the relevant annotation(s):
In the case where the index creation cost isn't considered too high and you are not in a production environment, it can be triggered automatically instead, in two steps.
You will first need to annotate the repositories you want managed with the relevant annotation(s):
- `@ViewIndexed` will create a view like the "all" view previously seen, to list all entities in the bucket.
- `@N1qlPrimaryIndexed` can be used to ensure a general-purpose PRIMARY INDEX is available in N1QL.
- `@N1qlSecondaryIndexed` will create a more specific N1QL index that does the same kind of filtering on entity type that the view does. It'll allow for efficient listing of all documents that correspond to a Repository's associated domain object.
- `@ViewIndexed` will create a view like the "all" view previously seen, to list all entities in the bucket.
- `@N1qlPrimaryIndexed` can be used to ensure a general-purpose PRIMARY INDEX is available in N1QL.
- `@N1qlSecondaryIndexed` will create a more specific N1QL index that does the same kind of filtering on entity type that the view does.
It'll allow for efficient listing of all documents that correspond to a Repository's associated domain object.
Secondly, you'll need to opt-in to this feature by customizing the `indexManager()` bean of your env-specific `AbstractCouchbaseConfiguration` to take certain types of annotations into account. This is done through the `IndexManager(boolean processViews, boolean processN1qlPrimary, boolean processN1qlSecondary)` constructor. Set the flags for the category of annotations you want processed to true, or false to deactivate the automatic creation feature.
Secondly, you'll need to opt-in to this feature by customizing the `indexManager()` bean of your env-specific `AbstractCouchbaseConfiguration` to take certain types of annotations into account.
This is done through the `IndexManager(boolean processViews, boolean processN1qlPrimary, boolean processN1qlSecondary)` constructor.
Set the flags for the category of annotations you want processed to true, or false to deactivate the automatic creation feature.
The `@Profile` annotation is one possible Spring annotation to be used to differentiate configurations (or individual beans) per environment.
@@ -348,11 +389,12 @@ public class ExampleDevApplicationConfig extends AbstractCouchbaseConfiguration
In `2.0`, since N1QL has been introduced as a more powerful concept, view-backed queries have changed a bit outside of the CRUD methods:
- the `@View` annotation is mandatory.
- if you just want all the results from the view, you can let the framework guess the view name to use by just using the plain annotation `@View`. **You won't be able to customize** the `ViewQuery` (eg. adding limits and specifying a `startkey`) using this method anymore.
- if you want your view query to have restrictions, those can be derived from the method name but in this case you **must** explicitly provide the `viewName` attribute in the annotation.
- View based query derivation is limited to a few keywords and only works on simple keys (not compound keys like `[ age, fname ]`).
- View based query derivation still needs you to include *one* valid property before keywords in the method name.
- the `@View` annotation is mandatory.
- if you just want all the results from the view, you can let the framework guess the view name to use by just using the plain annotation `@View`.
**You won't be able to customize** the `ViewQuery` (eg. adding limits and specifying a `startkey`) using this method anymore.
- if you want your view query to have restrictions, those can be derived from the method name but in this case you **must** explicitly provide the `viewName` attribute in the annotation.
- View based query derivation is limited to a few keywords and only works on simple keys (not compound keys like `[ age, fname ]`).
- View based query derivation still needs you to include *one* valid property before keywords in the method name.
.An extended UserInfo repository with View queries
====
@@ -369,7 +411,10 @@ public interface UserRepository extends CrudRepository<UserInfo, String> {
----
====
Implementing your custom repository finder methods also needs backing views. The `findAllAdmins` guesses to use the `allAdmins` view in the `userInfo` design document, by convention. Imagine we have a field on our entity which looks like `boolean isAdmin`. We can write a view like this to expose them (we don't need a reduce function for this one, unless you plan to call one by prefixing your method with `count` instead of `find`!):
Implementing your custom repository finder methods also needs backing views.
The `findAllAdmins` guesses to use the `allAdmins` view in the `userInfo` design document, by convention.
Imagine we have a field on our entity which looks like `boolean isAdmin`.
We can write a view like this to expose them (we don't need a reduce function for this one, unless you plan to call one by prefixing your method with `count` instead of `find`!):
.The allAdmins map function
====
@@ -383,7 +428,8 @@ function (doc, meta) {
----
====
By now, we've never actually customized our view at query time. This is where the alternative, query derivation, comes along - like in our `findByFirstnameStartingWith(String fnamePrefix)` method.
By now, we've never actually customized our view at query time.
This is where the alternative, query derivation, comes along - like in our `findByFirstnameStartingWith(String fnamePrefix)` method.
.The firstNames view map function
====
@@ -397,7 +443,8 @@ function (doc, meta) {
----
====
This view not only emits the document id, but also the firstname of every UserInfo as the key. We can now run a `ViewQuery` which returns us all users with a firstname of "Michael" or "Michele".
This view not only emits the document id, but also the firstname of every UserInfo as the key.
We can now run a `ViewQuery` which returns us all users with a firstname of "Michael" or "Michele".
.Query a repository method with custom params.
====
@@ -419,7 +466,7 @@ For more details on behavior, please consult the Couchbase Server and Java SDK d
For view-based query derivation, here are the supported keywords (A and B are method parameters in this table):
.Supported keywords inside @View method names
[options = "header, autowidth"]
[options = "header,autowidth"]
|===============
|`Is,Equals`|`findAllByUsername`,`findByFieldEquals`|`key=A` - if only keyword, the method can have no parameter (return all items from the view)
|`Between`|`findByFieldBetween`|`startkey=A&endkey=B`
@@ -432,29 +479,38 @@ For view-based query derivation, here are the supported keywords (A and B are me
Note that both `reduce functions` and <<repositories.limit-query-result>> are also supported.
TIP: In order to trigger a `reduce`, you can use the `count` prefix instead of `find`. But sometimes is doesn't make much sense (eg. because you actually use the `_stats` built in function, which returns a JSON object). So alternatively you can also explicitly ask for reduce to be executed by setting `reduce = true` in the `@View` annotation. Be sure to specify a return type that make sense for the reduce function of your view.
WARNING: Compound keys are not supported, and neither are Or composition, Ignore Case and Order By. You have to include a valid entity property in the naming of your method.
TIP: In order to trigger a `reduce`, you can use the `count` prefix instead of `find`.
But sometimes is doesn't make much sense (eg. because you actually use the `_stats` built in function, which returns a JSON object).
So alternatively you can also explicitly ask for reduce to be executed by setting `reduce = true` in the `@View` annotation.
Be sure to specify a return type that make sense for the reduce function of your view.
WARNING: Compound keys are not supported, and neither are Or composition, Ignore Case and Order By.
You have to include a valid entity property in the naming of your method.
Last method of querying in Couchbase (from Couchbase Server 4.0, like for N1QL) is querying for dimensional data through *Spatial Views*, as we'll see in the next section.
[[couchbase.repository.spatial]]
=== Spatial View based querying
Couchbase can accommodate multi-dimensional data and query it with the use of special views, the Spatial Views. Such views allows to perform multi-dimensional queries, not only limited to geographical data.
Integration of these views in `Spring Data Couchbase` repositories is done through the `@Dimensional` annotation. Like `@View`, the annotation allows to indicate usage of a Spatial View as the backing mechanism for the annotated method. The annotation requires you to give the name of the `designDocument` and the `spatialViewName` to use. Additionally, you should specify the number of `dimensions` the view works with (unless it is the default classical 2).
Couchbase can accommodate multi-dimensional data and query it with the use of special views, the Spatial Views.
Such views allows to perform multi-dimensional queries, not only limited to geographical data.
Integration of these views in `Spring Data Couchbase` repositories is done through the `@Dimensional` annotation.
Like `@View`, the annotation allows to indicate usage of a Spatial View as the backing mechanism for the annotated method.
The annotation requires you to give the name of the `designDocument` and the `spatialViewName` to use.
Additionally, you should specify the number of `dimensions` the view works with (unless it is the default classical 2).
Multi-dimensionality concept is interesting, it means you can craft views that allows you to answer questions like "find all shops that are within Manhattan and open between 14:00 and 23:00" (the third dimension of the view being the opening hours).
Couchbase's Spatial View support querying through ranges that represent "lowest" and "highest" values in each dimension, so for 2D it represents a bounding box, with the southwest-most point [x,y] as `startRange` and northeast-most point [x,y] as `endRange`.
TIP: Even though Couchbase Spatial View engine only support Bounding Box querying, the Spring Data Couchbase framework will attempt to remove false positives for you when querying with a `Polygon` or a `Circle` (in TRACE log level each false positive elimination will be logged). Note that a point on the edge of a `Polygon` is *not* considered within (whereas it is when dealing with a `Circle`).
TIP: Even though Couchbase Spatial View engine only support Bounding Box querying, the Spring Data Couchbase framework will attempt to remove false positives for you when querying with a `Polygon` or a `Circle` (in TRACE log level each false positive elimination will be logged).
Note that a point on the edge of a `Polygon` is *not* considered within (whereas it is when dealing with a `Circle`).
The following query derivation keywords and parameters relative to geographical data in Spring Data are supported for Spatial Views:
.Supported keywords inside @Dimensional method names
[options = "header, autowidth"]
[options = "header,autowidth"]
|===============
|Keyword|Sample|Remarks
|`Within,IsWithin`|`findByLocationWithin`|
@@ -464,32 +520,40 @@ The following query derivation keywords and parameters relative to geographical
|`LessThan,LessThanEqual,Before`|`findByLocationWithinAndOpeningHoursBefore`|useful for dimensions beyond 2, adds a numerical value to the endRange
|===============
IMPORTANT: For "within" types of queries, the expected parameters map to geographical 2D data. Classes from the `org.springframework.data.geo` package are usually expected, but Polygon and Boxes can also be expressed as arrays of `Point`s.
IMPORTANT: For "within" types of queries, the expected parameters map to geographical 2D data.
Classes from the `org.springframework.data.geo` package are usually expected, but Polygon and Boxes can also be expressed as arrays of `Point`s.
Further dimensions are supported through keywords other than Within and Near and require numerical input.
[[couchbase.repository.consistency]]
=== Querying with consistency
One aspect that is often needed and doesn't have a direct equivalent in the Spring Data query derivation mechanism is
`query consistency`. In both view-based queries and N1QL, you have this concept that the secondary index can return stale
data, because the latest version hasn't been indexed yet. This gives the best performance at the expense of consistency.
`query consistency`.
In both view-based queries and N1QL, you have this concept that the secondary index can return stale data, because the latest version hasn't been indexed yet.
This gives the best performance at the expense of consistency.
Note that weaker consistencies can lead to data being returned that doesn't match the criteria of a derived query.
One trickier case is when documents are deleted from Couchbase but views have not yet caught up to the deletion. With weak consistency this can mean that a view would return IDs that are not in the database anymore, leading to null entities. The `CouchbaseTemplate`s `findByView` and `findBySpatialView` methods will remove such stale deleted entities from their result in order to avoid having nulls in the returned collections. Similarly, `CouchbaseRepository`'s `deleteAll` method will ignore documents that the backing view provided but the SDK remove operation couldn't find.
One trickier case is when documents are deleted from Couchbase but views have not yet caught up to the deletion.
With weak consistency this can mean that a view would return IDs that are not in the database anymore, leading to null entities.
The `CouchbaseTemplate`s `findByView` and `findBySpatialView` methods will remove such stale deleted entities from their result in order to avoid having nulls in the returned collections.
Similarly, `CouchbaseRepository`'s `deleteAll` method will ignore documents that the backing view provided but the SDK remove operation couldn't find.
If one wants to have stronger consistency, there are three possibilities described in the next sections.
==== Configure it on a global level
A global consistency can be defined using the `Consistency` enumeration (eg. `Consistency.READ_YOUR_OWN_WRITE`):
- in xml, this is done via the `consistency` attribute on `<couchbase:template>`.
- in javaConfig, this is done by overriding the `getDefaultConsistency()` method.
- in xml, this is done via the `consistency` attribute on `<couchbase:template>`.
- in javaConfig, this is done by overriding the `getDefaultConsistency()` method.
By default it is `Consistency.READ_YOUR_OWN_WRITES` (which means consistency is prioritized over speed, especially when a large number of documents has been created recently).
IMPORTANT: This is **only used in repositories**, either for index-backed methods automatically provided by the repository interface (`findAll()`, `findAll(keys)`, `count()`, `deleteAll()`...) or methods you define in your specific interface using query derivation.
==== Set consistency for N1QL queries
For N1QL queries, the `ScanConsistency` can be set using the `@WithConsistency` annotation.
This is independent of whether the query is derived from the method name or specified using `@Query`.
If the annotation is omitted, the global default consistency is used.
@@ -516,31 +580,33 @@ public interface UserRepository extends CrudRepository<UserInfo, String> {
<2> The N1QL query is derived automatically but executed with the given consistency, i.e. `NOT_BOUNDED`.
<3> The N1QL query is specified using `@Query` and is being executed with the given consistency.
IMPORTANT: The `@WithConsistency` annotation is currently only evaluated **for N1QL queries**. View queries use the global default consistency.
IMPORTANT: The `@WithConsistency` annotation is currently only evaluated **for N1QL queries**.
View queries use the global default consistency.
==== Provide an implementation
Provide the implementation and directly use `queryView` and `queryN1QL` methods on the template with a specific consistency
(see <<couchbase.repository.changing-repository-behaviour>>).
- one can specify the consistency on those via their respective query classes, according to the Couchbase Java SDK documentation.
- for example for views `ViewQuery.stale(Stale.FALSE)`
- for example for N1QL `Query.simple("SELECT * FROM default", QueryParams.build().consistency(ScanConsistency.REQUEST_PLUS));`
Provide the implementation and directly use `queryView` and `queryN1QL` methods on the template with a specific consistency (see <<couchbase.repository.changing-repository-behaviour>>).
- one can specify the consistency on those via their respective query classes, according to the Couchbase Java SDK documentation.
- for example for views `ViewQuery.stale(Stale.FALSE)`
- for example for N1QL `Query.simple("SELECT * FROM default", QueryParams.build().consistency(ScanConsistency.REQUEST_PLUS));`
[[couchbase.repository.multibucket]]
== Working with multiple buckets
The Java Config version allows you to define multiple `Bucket` and `CouchbaseTemplate`, but in order to have different
repositories use different underlying buckets/templates, you need to follow these steps:
* in your `AbstractCouchbaseConfiguration` implementation, override the `configureRepositoryOperationsMapping` method.
* mutate the provided `RepositoryOperationsMapping` as needed (it defaults to mapping everything to the default template).
* configure the mapping by chaining calls to `map`, `mapEntity` and `setDefault`.
** `map` maps a specific repository interface to the `CouchbaseOperations` it should use
** `mapEntity` maps all unmapped repositories of a domain type / entity class to a common `CouchbaseOperations`
** `setDefault` maps all remaining unmapped repositories to a default
The Java Config version allows you to define multiple `Bucket` and `CouchbaseTemplate`, but in order to have different repositories use different underlying buckets/templates, you need to follow these steps:
* in your `AbstractCouchbaseConfiguration` implementation, override the `configureRepositoryOperationsMapping` method.
* mutate the provided `RepositoryOperationsMapping` as needed (it defaults to mapping everything to the default template).
* configure the mapping by chaining calls to `map`, `mapEntity` and `setDefault`.
** `map` maps a specific repository interface to the `CouchbaseOperations` it should use
** `mapEntity` maps all unmapped repositories of a domain type / entity class to a common `CouchbaseOperations`
** `setDefault` maps all remaining unmapped repositories to a default
`CouchaseOperations` (the default, using `couchbaseTemplate` bean unless modified).
The idea is that the framework will look for an entry corresponding to the repository's interface when instantiating it.
If none is found it will look at the mapping for the repository's domain type. Eventually it will fallback to the default setting.
If none is found it will look at the mapping for the repository's domain type.
Eventually it will fallback to the default setting.
Here is an example:
.Example of configuring multiple templates and repositories.
@@ -584,13 +650,18 @@ public class ConcreteCouchbaseConfig extends AbstractCouchbaseConfig {
[[couchbase.repository.changing-repository-behaviour]]
== Changing repository behaviour
Sometimes you don't simply want the repository to create methods for you, but instead you want to tune the base repository's behaviour. You can either do that for *all* repositories - by changing the _base class_ for them - or just for a single repository - by adding custom implementations for either new or existing methods - (see <<repositories.custom-implementations>> for a generic introduction to these concepts).
Sometimes you don't simply want the repository to create methods for you, but instead you want to tune the base repository's behaviour.
You can either do that for *all* repositories - by changing the _base class_ for them - or just for a single repository - by adding custom implementations for either new or existing methods - (see <<repositories.custom-implementations>> for a generic introduction to these concepts).
=== Couchbase specifics about changing the base class
This follows the standard procedure for changing all repositories' base class:
. Create an generic interface for your base that extends `CouchbaseRepository` (CRUD) or `CouchbasePagingAndSortingRepository`. Declare any method you want to add to all repositories there.
. Create an implementation (eg. `MyRepositoryImpl`). This should extend one the concrete base classes (`SimpleCouchbaseRepository` or `N1qlCouchbaseRepository`) and you can also override existing methods from the Spring Data interfaces.
. Create an generic interface for your base that extends `CouchbaseRepository` (CRUD) or `CouchbasePagingAndSortingRepository`.
Declare any method you want to add to all repositories there.
. Create an implementation (eg. `MyRepositoryImpl`).
This should extend one the concrete base classes (`SimpleCouchbaseRepository` or `N1qlCouchbaseRepository`) and you can also override existing methods from the Spring Data interfaces.
. Declare your repository interfaces as extending `MyRepository` instead of eg. `CRUDRepository` or `CouchbaseRepository`.
. In the `@EnableCouchbaseRepositories` annotation of your configuration, use the `repositoryBaseClass` parameter.
@@ -632,6 +703,7 @@ public class MyConfig extends AbstractCouchbaseConfiguration { /** ... */ }
<8> Weaving it all in by changing the repository base class.
=== Couchbase specifics about adding methods to a single repository
Again following the standard procedure for custom repository methods, here is a complete example that you can find in `RepositoryCustomMethodTest` in the integration tests:
.Adding and overriding methods in a single repository
@@ -688,7 +760,8 @@ public class MyRepositoryImpl implements MyRepositoryCustom { <3>
<3> This is the implementation of the custom interface.
<4> The custom implementation doesn't have access to the original base implementation, so use dependency injection to get access to necessary resources.
<5> Here is a couchbase specificity: if you need to use the `CouchbaseTemplate`, be sure to use the one that would be associated with the customized repository or associated entity type.
<6> We use `N1QLUtils` to prepare a complete `N1QL` statement for counting. It relies on the information above that we got from the correct template.
<6> We use `N1QLUtils` to prepare a complete `N1QL` statement for counting.
It relies on the information above that we got from the correct template.
<7> We want to make sure that the default consistency configured in the associated template is used for this query.
<8> Using `CouchbaseTemplate.findByN1qlProjection`, we execute the count query and store the single aggregation result into a `CountFragment`.
<9> Now we return this count result with a twist: it is negated.
@@ -702,7 +775,10 @@ By storing 3 items using a `MyRepository` instance and calling `count()` then `c
----
=== DTO Projections
Spring Data Repositories usually return the domain model when using query methods. However, sometimes, you may need to alter the view of that model for various reasons. In this section, you will learn how to define projections to serve up simplified and reduced views of resources.
Spring Data Repositories usually return the domain model when using query methods.
However, sometimes, you may need to alter the view of that model for various reasons.
In this section, you will learn how to define projections to serve up simplified and reduced views of resources.
Look at the following domain model:
@@ -747,16 +823,20 @@ interface PersonRepository extends CrudRepository<Person, Long> {
}
----
Spring Data will return the domain object including all of its attributes. There are two options just to retrieve the `address` attribute. One option is to define a repository for `Address` objects like this:
Spring Data will return the domain object including all of its attributes.
There are two options just to retrieve the `address` attribute.
One option is to define a repository for `Address` objects like this:
[source,java]
----
interface AddressRepository extends CrudRepository<Address, Long> {}
----
In this situation, using `PersonRepository` will still return the whole `Person` object. Using `AddressRepository` will return just the `Address`.
In this situation, using `PersonRepository` will still return the whole `Person` object.
Using `AddressRepository` will return just the `Address`.
However, what if you do not want to expose `address` details at all? You can offer the consumer of your repository service an alternative by defining one or more projections.
However, what if you do not want to expose `address` details at all?
You can offer the consumer of your repository service an alternative by defining one or more projections.
.Simple Projection
====
@@ -769,6 +849,7 @@ interface NoAddresses { <1>
String getLastName(); <3>
}
----
This projection has the following details:
<1> A plain Java interface making it declarative.
@@ -776,7 +857,8 @@ This projection has the following details:
<3> Export the `lastName`.
====
The `NoAddresses` projection only has getters for `firstName` and `lastName` meaning that it will not serve up any address information. The query method definition returns in this case `NoAdresses` instead of `Person`.
The `NoAddresses` projection only has getters for `firstName` and `lastName` meaning that it will not serve up any address information.
The query method definition returns in this case `NoAdresses` instead of `Person`.
[source,java]
----
@@ -786,4 +868,6 @@ interface PersonRepository extends CrudRepository<Person, Long> {
}
----
Projections declare a contract between the underlying type and the method signatures related to the exposed properties. Hence it is required to name getter methods according to the property name of the underlying type. If the underlying property is named `firstName`, then the getter method must be named `getFirstName` otherwise Spring Data is not able to look up the source property.
Projections declare a contract between the underlying type and the method signatures related to the exposed properties.
Hence it is required to name getter methods according to the property name of the underlying type.
If the underlying property is named `firstName`, then the getter method must be named `getFirstName` otherwise Spring Data is not able to look up the source property.

View File

@@ -1,20 +1,28 @@
[[couchbase.template]]
= Template & direct operations
The template provides lower level access to the underlying database and also serves as the foundation for repositories. Any time a repository is too high-level for you needs chances are good that the templates will serve you well.
The template provides lower level access to the underlying database and also serves as the foundation for repositories.
Any time a repository is too high-level for you needs chances are good that the templates will serve you well.
[[template.ops]]
== Supported operations
The template can be accessed through the `couchbaseTemplate` bean out of your context. Once you've got a reference to it, you can run all kinds of operations against it. Other than through a repository, in a template you need to always specify the target entity type which you want to get converted.
The template can be accessed through the `couchbaseTemplate` bean out of your context.
Once you've got a reference to it, you can run all kinds of operations against it.
Other than through a repository, in a template you need to always specify the target entity type which you want to get converted.
To mutate documents, you'll find `save`, `insert` and `update` methods exposed. Saving will insert or update the document, insert will fail if it has been created already and update only works against documents that have already been created.
To mutate documents, you'll find `save`, `insert` and `update` methods exposed.
Saving will insert or update the document, insert will fail if it has been created already and update only works against documents that have already been created.
Since Couchbase Server has different levels of persistence (by default you'll get a positive response if it has been acknowledged in the managed cache), you can provide higher durability options through the overloaded `PersistTo` and/or `ReplicateTo` options. The behaviour is part of the Couchbase Java SDK, please refer to the official documentation for more details.
Since Couchbase Server has different levels of persistence (by default you'll get a positive response if it has been acknowledged in the managed cache), you can provide higher durability options through the overloaded `PersistTo` and/or `ReplicateTo` options.
The behaviour is part of the Couchbase Java SDK, please refer to the official documentation for more details.
Removing documents through the `remove` methods works exactly the same.
If you want to load documents, you can do that through the `findById` method, which is the fastest and if possible your tool of choice. The find methods for views are `findByView` which converts it into the target entity, but also `queryView` which exposes lower level semantics. Similarly, find methods using N1QL are provided in `findByN1QL` and `queryN1QL`. Additionally, since N1QL allows you to select specific fields in documents (or even across documents using joins), `findByN1QLProjection` will allow you to skip full `Document` conversion and map these fields to an ad-hoc class.
If you want to load documents, you can do that through the `findById` method, which is the fastest and if possible your tool of choice.
The find methods for views are `findByView` which converts it into the target entity, but also `queryView` which exposes lower level semantics.
Similarly, find methods using N1QL are provided in `findByN1QL` and `queryN1QL`.
Additionally, since N1QL allows you to select specific fields in documents (or even across documents using joins), `findByN1QLProjection` will allow you to skip full `Document` conversion and map these fields to an ad-hoc class.
WARNING: If it is detected at runtime that the cluster doesn't support N1QL, these methods will throw a `UnsupportedCouchbaseFeatureException`.
@@ -22,6 +30,7 @@ If you really need low-level semantics, the `couchbaseBucket` is also always in
[[couchbase.template.xml]]
== Xml Configuration
The template can be configured via xml, including setting a custom `TranslationService`.
.XML Based Template Declaration
@@ -48,5 +57,6 @@ The template can be configured via xml, including setting a custom `TranslationS
----
====
NOTE: In the example above most tags assume their default values, that is a localhost cluster and bucket "default". In production you would have to also provide specifics to these tags.
NOTE: In the example above most tags assume their default values, that is a localhost cluster and bucket "default".
In production you would have to also provide specifics to these tags.

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2020 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;
import java.io.Closeable;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.Scope;
public interface CouchbaseClientFactory extends Closeable {
Cluster getCluster();
Bucket getBucket();
Scope getScope();
PersistenceExceptionTranslator getExceptionTranslator();
Collection getCollection(String name);
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2012-2020 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;
import com.couchbase.client.java.env.ClusterEnvironment;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator;
import com.couchbase.client.core.env.Authenticator;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.ClusterOptions;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.Scope;
public class SimpleCouchbaseClientFactory implements CouchbaseClientFactory {
private final Cluster cluster;
private final Bucket bucket;
private final Scope scope;
private final PersistenceExceptionTranslator exceptionTranslator;
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
final String bucketName) {
this(connectionString, authenticator, bucketName, null);
}
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
final String bucketName, final String scopeName) {
this(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator)), bucketName, scopeName);
}
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
final String bucketName, final String scopeName, final ClusterEnvironment environment) {
this(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator).environment(environment)), bucketName, scopeName);
}
SimpleCouchbaseClientFactory(final Cluster cluster, final String bucketName, final String scopeName) {
this.cluster = cluster;
this.bucket = cluster.bucket(bucketName);
this.scope = scopeName == null ? bucket.defaultScope() : bucket.scope(scopeName);
this.exceptionTranslator = new CouchbaseExceptionTranslator();
}
public CouchbaseClientFactory withScope(final String scopeName) {
return new SimpleCouchbaseClientFactory(cluster, bucket.name(), scopeName);
}
@Override
public Cluster getCluster() {
return cluster;
}
@Override
public Bucket getBucket() {
return bucket;
}
@Override
public Scope getScope() {
return scope;
}
@Override
public Collection getCollection(final String collectionName) {
final Scope scope = getScope();
if (collectionName == null) {
if (!scope.name().equals(CollectionIdentifier.DEFAULT_SCOPE)) {
throw new IllegalStateException("A collectionName must be provided if a non-default scope is used!");
}
return getBucket().defaultCollection();
}
return scope.collection(collectionName);
}
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return exceptionTranslator;
}
@Override
public void close() {
cluster.disconnect();
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2012-2020 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.cache;
import org.springframework.util.Assert;
/**
* {@link CacheKeyPrefix} provides a hook for creating custom prefixes prepended to the actual {@literal key} stored in
* Couchbase.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Michael Nitschinger
* @since 4.0.0
*/
@FunctionalInterface
public interface CacheKeyPrefix {
/**
* Default separator.
*
* @since 2.3
*/
String SEPARATOR = "::";
/**
* Creates a default {@link CacheKeyPrefix} scheme that prefixes cache keys with {@code cacheName} followed by double
* colons. A cache named {@code myCache} will prefix all cache keys with {@code myCache::}.
*
* @return the default {@link CacheKeyPrefix} scheme.
*/
static CacheKeyPrefix simple() {
return name -> name + SEPARATOR;
}
/**
* Creates a {@link CacheKeyPrefix} scheme that prefixes cache keys with the given {@code prefix}. The prefix is
* prepended to the {@code cacheName} followed by double colons. A prefix {@code cb-} with a cache named
* {@code myCache} results in {@code cb-myCache::}.
*
* @param prefix must not be {@literal null}.
* @return the default {@link CacheKeyPrefix} scheme.
* @since 4.0.0
*/
static CacheKeyPrefix prefixed(final String prefix) {
Assert.notNull(prefix, "Prefix must not be null!");
return name -> prefix + name + SEPARATOR;
}
/**
* Compute the prefix for the actual {@literal key} stored in Couchbase.
*
* @param cacheName will never be {@literal null}.
* @return never {@literal null}.
*/
String compute(String cacheName);
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2012-2020 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.cache;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.Callable;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
public class CouchbaseCache extends AbstractValueAdaptingCache {
private final String name;
private final CouchbaseCacheWriter cacheWriter;
private final CouchbaseCacheConfiguration cacheConfig;
private final ConversionService conversionService;
protected CouchbaseCache(final String name, final CouchbaseCacheWriter cacheWriter,
final CouchbaseCacheConfiguration cacheConfig) {
super(cacheConfig.getAllowCacheNullValues());
Assert.notNull(name, "Name must not be null!");
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
Assert.notNull(cacheConfig, "CacheConfig must not be null!");
this.name = name;
this.cacheWriter = cacheWriter;
this.cacheConfig = cacheConfig;
this.conversionService = cacheConfig.getConversionService();
}
private static <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
try {
return valueLoader.call();
} catch (Exception e) {
throw new ValueRetrievalException(key, valueLoader, e);
}
}
@Override
public String getName() {
return name;
}
@Override
public CouchbaseCacheWriter getNativeCache() {
return cacheWriter;
}
@Override
protected Object lookup(final Object key) {
return cacheWriter.get(cacheConfig.getCollectionName(), createCacheKey(key), cacheConfig.getValueTranscoder());
}
@Override
@SuppressWarnings("unchecked")
public synchronized <T> T get(final Object key, final Callable<T> valueLoader) {
ValueWrapper result = get(key);
if (result != null) {
return (T) result.get();
}
T value = valueFromLoader(key, valueLoader);
put(key, value);
return value;
}
@Override
public void put(final Object key, final Object value) {
if (!isAllowNullValues() && value == null) {
throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or "
+ "configure CouchbaseCache to allow 'null' via CouchbaseCacheConfiguration.",
name));
}
cacheWriter.put(cacheConfig.getCollectionName(), createCacheKey(key), value, cacheConfig.getExpiry(),
cacheConfig.getValueTranscoder());
}
@Override
public ValueWrapper putIfAbsent(final Object key, final Object value) {
if (!isAllowNullValues() && value == null) {
return get(key);
}
Object result = cacheWriter.putIfAbsent(cacheConfig.getCollectionName(), createCacheKey(key), value,
cacheConfig.getExpiry(), cacheConfig.getValueTranscoder());
if (result == null) {
return null;
}
return new SimpleValueWrapper(result);
}
@Override
public void evict(final Object key) {
cacheWriter.remove(cacheConfig.getCollectionName(), createCacheKey(key));
}
@Override
public boolean evictIfPresent(final Object key) {
return cacheWriter.remove(cacheConfig.getCollectionName(), createCacheKey(key));
}
@Override
public boolean invalidate() {
return cacheWriter.clear(cacheConfig.getKeyPrefixFor(name)) > 0;
}
@Override
public void clear() {
cacheWriter.clear(cacheConfig.getKeyPrefixFor(name));
}
/**
* Customization hook for creating cache key before it gets serialized.
*
* @param key will never be {@literal null}.
* @return never {@literal null}.
*/
protected String createCacheKey(final Object key) {
String convertedKey = convertKey(key);
if (!cacheConfig.usePrefix()) {
return convertedKey;
}
return prefixCacheKey(convertedKey);
}
/**
* Convert {@code key} to a {@link String} representation used for cache key creation.
*
* @param key will never be {@literal null}.
* @return never {@literal null}.
* @throws IllegalStateException if {@code key} cannot be converted to {@link String}.
*/
protected String convertKey(final Object key) {
if (key instanceof String) {
return (String) key;
}
TypeDescriptor source = TypeDescriptor.forObject(key);
if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) {
try {
return conversionService.convert(key, String.class);
} catch (ConversionFailedException e) {
// may fail if the given key is a collection
if (isCollectionLikeOrMap(source)) {
return convertCollectionLikeOrMapKey(key, source);
}
throw e;
}
}
Method toString = ReflectionUtils.findMethod(key.getClass(), "toString");
if (toString != null && !Object.class.equals(toString.getDeclaringClass())) {
return key.toString();
}
throw new IllegalStateException(String.format(
"Cannot convert cache key %s to String. Please register a suitable Converter via "
+ "'CouchbaseCacheConfiguration.configureKeyConverters(...)' or override '%s.toString()'.",
source, key.getClass().getSimpleName()));
}
private String prefixCacheKey(final String key) {
// allow contextual cache names by computing the key prefix on every call.
return cacheConfig.getKeyPrefixFor(name) + key;
}
private boolean isCollectionLikeOrMap(final TypeDescriptor source) {
return source.isArray() || source.isCollection() || source.isMap();
}
private String convertCollectionLikeOrMapKey(final Object key, final TypeDescriptor source) {
if (source.isMap()) {
StringBuilder target = new StringBuilder("{");
for (Map.Entry<?, ?> entry : ((Map<?, ?>) key).entrySet()) {
target.append(convertKey(entry.getKey())).append("=").append(convertKey(entry.getValue()));
}
target.append("}");
return target.toString();
} else if (source.isCollection() || source.isArray()) {
StringJoiner sj = new StringJoiner(",");
Collection<?> collection = source.isCollection() ? (Collection<?>) key
: Arrays.asList(ObjectUtils.toObjectArray(key));
for (Object val : collection) {
sj.add(convertKey(val));
}
return "[" + sj.toString() + "]";
}
throw new IllegalArgumentException(String.format("Cannot convert cache key %s to String.", key));
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright 2012-2020 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.cache;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.util.Assert;
import com.couchbase.client.java.codec.SerializableTranscoder;
import com.couchbase.client.java.codec.Transcoder;
public class CouchbaseCacheConfiguration {
private final Duration expiry;
private final boolean cacheNullValues;
private final CacheKeyPrefix keyPrefix;
private final boolean usePrefix;
private final Transcoder valueTranscoder;
private final ConversionService conversionService;
private final String collectionName;
private CouchbaseCacheConfiguration(final Duration expiry, final boolean cacheNullValues, final boolean usePrefix,
final CacheKeyPrefix keyPrefix, final ConversionService conversionService, final Transcoder valueTranscoder,
final String collectionName) {
this.expiry = expiry;
this.cacheNullValues = cacheNullValues;
this.usePrefix = usePrefix;
this.keyPrefix = keyPrefix;
this.conversionService = conversionService;
this.valueTranscoder = valueTranscoder;
this.collectionName = collectionName;
}
public static CouchbaseCacheConfiguration defaultCacheConfig() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
registerDefaultConverters(conversionService);
return new CouchbaseCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(), conversionService,
SerializableTranscoder.INSTANCE, null);
}
/**
* Registers default cache key converters. The following converters get registered:
* <ul>
* <li>{@link String} to {@link byte byte[]} using UTF-8 encoding.</li>
* <li>{@link SimpleKey} to {@link String}</li>
*
* @param registry must not be {@literal null}.
*/
public static void registerDefaultConverters(final ConverterRegistry registry) {
Assert.notNull(registry, "ConverterRegistry must not be null!");
registry.addConverter(String.class, byte[].class, source -> source.getBytes(StandardCharsets.UTF_8));
registry.addConverter(SimpleKey.class, String.class, SimpleKey::toString);
}
/**
* Set the expiry to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache.
*
* @param expiry must not be {@literal null}.
* @return new {@link CouchbaseCacheConfiguration}.
*/
public CouchbaseCacheConfiguration entryExpiry(final Duration expiry) {
Assert.notNull(expiry, "Expiry duration must not be null!");
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, usePrefix, keyPrefix, conversionService,
valueTranscoder, collectionName);
}
/**
* Sets a custom transcoder to use for reads and writes.
*
* @param valueTranscoder the transcoder that should be used.
* @return new {@link CouchbaseCacheConfiguration}.
*/
public CouchbaseCacheConfiguration valueTranscoder(final Transcoder valueTranscoder) {
Assert.notNull(valueTranscoder, "Transcoder must not be null!");
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, usePrefix, keyPrefix, conversionService,
valueTranscoder, collectionName);
}
/**
* Disable caching {@literal null} values. <br />
* <strong>NOTE</strong> any {@link org.springframework.cache.Cache#put(Object, Object)} operation involving
* {@literal null} value will error. Nothing will be written to Couchbase, nothing will be removed. An already
* existing key will still be there afterwards with the very same value as before.
*
* @return new {@link CouchbaseCacheConfiguration}.
*/
public CouchbaseCacheConfiguration disableCachingNullValues() {
return new CouchbaseCacheConfiguration(expiry, false, usePrefix, keyPrefix, conversionService, valueTranscoder,
collectionName);
}
/**
* Prefix the {@link CouchbaseCache#getName() cache name} with the given value. <br />
* The generated cache key will be: {@code prefix + cache name + "::" + cache entry key}.
*
* @param prefix the prefix to prepend to the cache name.
* @return this.
* @see #computePrefixWith(CacheKeyPrefix)
* @see CacheKeyPrefix#prefixed(String)
*/
public CouchbaseCacheConfiguration prefixCacheNameWith(final String prefix) {
return computePrefixWith(CacheKeyPrefix.prefixed(prefix));
}
/**
* Use the given {@link CacheKeyPrefix} to compute the prefix for the actual Couchbase {@literal key} given the
* {@literal cache name} as function input.
*
* @param cacheKeyPrefix must not be {@literal null}.
* @return new {@link CouchbaseCacheConfiguration}.
* @see CacheKeyPrefix
*/
public CouchbaseCacheConfiguration computePrefixWith(CacheKeyPrefix cacheKeyPrefix) {
Assert.notNull(cacheKeyPrefix, "Function for computing prefix must not be null!");
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, true, cacheKeyPrefix, conversionService,
valueTranscoder, collectionName);
}
/**
* @return The expiration time (ttl) for cache entries. Never {@literal null}.
*/
public Duration getExpiry() {
return expiry;
}
/**
* @return {@literal true} if caching {@literal null} is allowed.
*/
public boolean getAllowCacheNullValues() {
return cacheNullValues;
}
/**
* @return The {@link ConversionService} used for cache key to {@link String} conversion. Never {@literal null}.
*/
public ConversionService getConversionService() {
return conversionService;
}
/**
* @return {@literal true} if cache keys need to be prefixed with the {@link #getKeyPrefixFor(String)} if present or
* the default which resolves to {@link Cache#getName()}.
*/
public boolean usePrefix() {
return usePrefix;
}
/**
* Get the computed {@literal key} prefix for a given {@literal cacheName}.
*
* @return never {@literal null}.
*/
public String getKeyPrefixFor(final String cacheName) {
Assert.notNull(cacheName, "Cache name must not be null!");
return keyPrefix.compute(cacheName);
}
/**
* Get the transcoder for encoding and decoding cache values.
*/
public Transcoder getValueTranscoder() {
return valueTranscoder;
}
/**
* The name of the collection to use for this cache - if empty uses the default collection.
*/
public String getCollectionName() {
return collectionName;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2012-2020 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.cache;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.cache.Cache;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public class CouchbaseCacheManager extends AbstractCacheManager {
private final CouchbaseCacheWriter cacheWriter;
private final CouchbaseCacheConfiguration defaultCacheConfig;
private final Map<String, CouchbaseCacheConfiguration> initialCacheConfiguration;
private final boolean allowInFlightCacheCreation;
/**
* Creates new {@link CouchbaseCacheManager} using given {@link CouchbaseCacheWriter} and default
* {@link CouchbaseCacheConfiguration}.
*
* @param cacheWriter must not be {@literal null}.
* @param defaultCacheConfiguration must not be {@literal null}. Maybe just use
* {@link CouchbaseCacheConfiguration#defaultCacheConfig()}.
* @param allowInFlightCacheCreation allow create unconfigured caches.
*/
private CouchbaseCacheManager(final CouchbaseCacheWriter cacheWriter,
final CouchbaseCacheConfiguration defaultCacheConfiguration, final boolean allowInFlightCacheCreation) {
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
this.cacheWriter = cacheWriter;
this.defaultCacheConfig = defaultCacheConfiguration;
this.initialCacheConfiguration = new LinkedHashMap<>();
this.allowInFlightCacheCreation = allowInFlightCacheCreation;
}
public static CouchbaseCacheManager create(final CouchbaseClientFactory connectionFactory) {
return new CouchbaseCacheManager(new DefaultCouchbaseCacheWriter(connectionFactory),
CouchbaseCacheConfiguration.defaultCacheConfig(), true);
}
@Override
protected Collection<? extends Cache> loadCaches() {
final List<CouchbaseCache> caches = new LinkedList<>();
for (Map.Entry<String, CouchbaseCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
caches.add(createCouchbaseCache(entry.getKey(), entry.getValue()));
}
return caches;
}
@Override
protected CouchbaseCache getMissingCache(final String name) {
return allowInFlightCacheCreation ? createCouchbaseCache(name, defaultCacheConfig) : null;
}
/**
* Configuration hook for creating {@link CouchbaseCache} with given name and {@code cacheConfig}.
*
* @param name must not be {@literal null}.
* @param cacheConfig can be {@literal null}.
* @return never {@literal null}.
*/
protected CouchbaseCache createCouchbaseCache(final String name,
@Nullable final CouchbaseCacheConfiguration cacheConfig) {
return new CouchbaseCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2012-2020 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.cache;
import java.time.Duration;
import org.springframework.lang.Nullable;
import com.couchbase.client.java.codec.Transcoder;
public interface CouchbaseCacheWriter {
/**
* Write the given key/value pair to Couchbase an set the expiration time if defined.
*
* @param collectionName The cache name must not be {@literal null}.
* @param key The key for the cache entry. Must not be {@literal null}.
* @param value The value stored for the key. Must not be {@literal null}.
* @param expiry Optional expiration time. Can be {@literal null}.
* @param transcoder Optional transcoder to use. Can be {@literal null}.
*/
void put(String collectionName, String key, Object value, @Nullable Duration expiry, @Nullable Transcoder transcoder);
/**
* Write the given value to Couchbase if the key does not already exist.
*
* @param collectionName The cache name must not be {@literal null}.
* @param key The key for the cache entry. Must not be {@literal null}.
* @param value The value stored for the key. Must not be {@literal null}.
* @param expiry Optional expiration time. Can be {@literal null}.
* @param transcoder Optional transcoder to use. Can be {@literal null}.
*/
@Nullable
Object putIfAbsent(String collectionName, String key, Object value, @Nullable Duration expiry,
@Nullable Transcoder transcoder);
/**
* Get the binary value representation from Couchbase stored for the given key.
*
* @param collectionName must not be {@literal null}.
* @param key must not be {@literal null}.
* @param transcoder Optional transcoder to use. Can be {@literal null}.
* @return {@literal null} if key does not exist.
*/
@Nullable
Object get(String collectionName, String key, @Nullable Transcoder transcoder);
/**
* Remove the given key from Couchbase.
*
* @param collectionName The cache name must not be {@literal null}.
* @param key The key for the cache entry. Must not be {@literal null}.
* @return true if the document existed on removal, false otherwise.
*/
boolean remove(String collectionName, String key);
/**
* Clears the cache with the given key pattern prefix.
*
* @param pattern the pattern to clear.
* @return the number of cleared items.
*/
long clear(String pattern);
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2012-2020 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.cache;
import static com.couchbase.client.java.kv.GetOptions.*;
import static com.couchbase.client.java.kv.InsertOptions.*;
import static com.couchbase.client.java.kv.UpsertOptions.*;
import static com.couchbase.client.java.query.QueryOptions.*;
import java.time.Duration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import com.couchbase.client.core.error.DocumentExistsException;
import com.couchbase.client.core.error.DocumentNotFoundException;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.Scope;
import com.couchbase.client.java.codec.Transcoder;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.UpsertOptions;
import com.couchbase.client.java.query.QueryMetrics;
import com.couchbase.client.java.query.QueryResult;
public class DefaultCouchbaseCacheWriter implements CouchbaseCacheWriter {
private final CouchbaseClientFactory clientFactory;
public DefaultCouchbaseCacheWriter(final CouchbaseClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public void put(final String collectionName, final String key, final Object value, final Duration expiry,
final Transcoder transcoder) {
UpsertOptions options = upsertOptions();
if (expiry != null) {
options.expiry(expiry);
}
if (transcoder != null) {
options.transcoder(transcoder);
}
getCollection(collectionName).upsert(key, value, options);
}
@Override
public Object putIfAbsent(final String collectionName, final String key, final Object value, final Duration expiry,
final Transcoder transcoder) {
InsertOptions options = insertOptions();
if (expiry != null) {
options.expiry(expiry);
}
if (transcoder != null) {
options.transcoder(transcoder);
}
try {
getCollection(collectionName).insert(key, value, options);
return null;
} catch (final DocumentExistsException ex) {
// If the document exists, return the current one per contract
return get(collectionName, key, transcoder);
}
}
@Override
public Object get(final String collectionName, final String key, final Transcoder transcoder) {
// TODO .. the decoding side transcoding needs to be figured out?
try {
return getCollection(collectionName).get(key, getOptions().transcoder(transcoder)).contentAs(Object.class);
} catch (DocumentNotFoundException ex) {
return null;
}
}
@Override
public boolean remove(final String collectionName, final String key) {
try {
getCollection(collectionName).remove(key);
return true;
} catch (final DocumentNotFoundException ex) {
return false;
}
}
@Override
public long clear(final String pattern) {
QueryResult result = clientFactory.getCluster().query(
"DELETE FROM `" + clientFactory.getBucket().name() + "` where meta().id LIKE $pattern",
queryOptions().metrics(true).parameters(JsonObject.create().put("pattern", pattern + "%")));
return result.metaData().metrics().map(QueryMetrics::mutationCount).orElse(0L);
}
private Collection getCollection(final String collectionName) {
final Scope scope = clientFactory.getScope();
if (collectionName == null) {
if (!scope.name().equals(CollectionIdentifier.DEFAULT_SCOPE)) {
throw new IllegalStateException("A collectionName must be provided if a non-default scope is used!");
}
return clientFactory.getBucket().defaultCollection();
}
return scope.collection(collectionName);
}
}

View File

@@ -16,18 +16,39 @@
package org.springframework.data.couchbase.config;
import java.lang.reflect.Proxy;
import java.util.List;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature;
import com.couchbase.client.core.env.Authenticator;
import com.couchbase.client.core.env.PasswordAuthenticator;
import com.couchbase.client.java.env.ClusterEnvironment;
import com.couchbase.client.java.json.JacksonTransformers;
/**
* Base class for Spring Data Couchbase configuration using JavaConfig.
@@ -38,109 +59,191 @@ import org.springframework.context.annotation.Configuration;
* @author Subhashni Balakrishnan
*/
@Configuration
public abstract class AbstractCouchbaseConfiguration
extends AbstractCouchbaseDataConfiguration implements CouchbaseConfigurer {
public abstract class AbstractCouchbaseConfiguration {
/**
* The list of hostnames (or IP addresses) to bootstrap from.
*
* @return the list of bootstrap hosts.
*/
protected abstract List<String> getBootstrapHosts();
public abstract String getConnectionString();
/**
* The name of the bucket to connect to.
*
* @return the name of the bucket.
*/
protected abstract String getBucketName();
public abstract String getUserName();
/**
* The user of the bucket. Override the method for users in Couchbase Server 5.0+.
*
* @return user name.
*/
protected String getUsername() { return getBucketName(); }
public abstract String getPassword();
/**
* The password of the bucket (can be an empty string).
*
* @return the password of the bucket.
*/
protected abstract String getBucketPassword();
public abstract String getBucketName();
/**
* Is the {@link #getEnvironment()} to be destroyed by Spring?
*
* @return true if Spring should destroy the environment with the context, false otherwise.
*/
protected boolean isEnvironmentManagedBySpring() {
return true;
}
protected String getScopeName() {
return null;
}
/**
* Override this method if you want a customized {@link CouchbaseEnvironment}.
* This environment will be managed by Spring, which will call its shutdown()
* method upon bean destruction, unless you override {@link #isEnvironmentManagedBySpring()}
* as well to return false.
*
* @return a customized environment, defaults to a {@link DefaultCouchbaseEnvironment}.
*/
protected CouchbaseEnvironment getEnvironment() {
return DefaultCouchbaseEnvironment.create();
}
protected Authenticator authenticator() {
return PasswordAuthenticator.create(getUserName(), getPassword());
}
@Override
protected CouchbaseConfigurer couchbaseConfigurer() {
return this;
}
@Bean
public CouchbaseClientFactory couchbaseClientFactory(ClusterEnvironment clusterEnvironment) {
return new SimpleCouchbaseClientFactory(getConnectionString(), authenticator(), getBucketName(),
getScopeName(), clusterEnvironment);
}
@Override
@Bean(destroyMethod = "shutdown", name = BeanNames.COUCHBASE_ENV)
public CouchbaseEnvironment couchbaseEnvironment() {
if (isEnvironmentManagedBySpring()) {
return getEnvironment();
} else {
CouchbaseEnvironment proxy = (CouchbaseEnvironment) Proxy.newProxyInstance(CouchbaseEnvironment.class.getClassLoader(),
new Class[]{CouchbaseEnvironment.class},
new CouchbaseEnvironmentNoShutdownInvocationHandler(getEnvironment()));
return proxy;
}
}
@Bean(destroyMethod = "shutdown")
protected ClusterEnvironment clusterEnvironment() {
ClusterEnvironment.Builder builder = ClusterEnvironment.builder();
configureEnvironment(builder);
return builder.build();
}
/**
* Returns the {@link Cluster} instance to connect to.
*
* @throws Exception on Bean construction failure.
*/
@Override
@Bean(destroyMethod = "disconnect", name = BeanNames.COUCHBASE_CLUSTER)
public Cluster couchbaseCluster() throws Exception {
return CouchbaseCluster.create(couchbaseEnvironment(), getBootstrapHosts());
}
protected void configureEnvironment(final ClusterEnvironment.Builder builder) {
@Override
@Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().clusterManager(getUsername(), getBucketPassword()).info();
}
}
/**
* Return the {@link Bucket} instance to connect to.
*
* @throws Exception on Bean construction failure.
*/
@Override
@Bean(destroyMethod = "close", name = BeanNames.COUCHBASE_BUCKET)
public Bucket couchbaseClient() throws Exception {
//@Bean method can use another @Bean method in the same @Configuration by directly invoking it
Cluster cluster = couchbaseCluster();
@Bean
public CouchbaseTemplate couchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
MappingCouchbaseConverter mappingCouchbaseConverter) {
return new CouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter);
}
if(!getUsername().contentEquals(getBucketName())){
cluster.authenticate(getUsername(), getBucketPassword());
} else if (!getBucketPassword().isEmpty()) {
return cluster.openBucket(getBucketName(), getBucketPassword());
}
return cluster.openBucket(getBucketName());
}
@Bean
public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
MappingCouchbaseConverter mappingCouchbaseConverter) {
return new ReactiveCouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter);
}
@Bean
public RepositoryOperationsMapping couchbaseRepositoryOperationsMapping(CouchbaseTemplate couchbaseTemplate) {
// create a base mapping that associates all repositories to the default template
RepositoryOperationsMapping baseMapping = new RepositoryOperationsMapping(couchbaseTemplate);
// let the user tune it
configureRepositoryOperationsMapping(baseMapping);
return baseMapping;
}
/**
* In order to customize the mapping between repositories/entity types to couchbase templates, use the provided
* mapping's api (eg. in order to have different buckets backing different repositories).
*
* @param mapping the default mapping (will associate all repositories to the default template).
*/
protected void configureRepositoryOperationsMapping(RepositoryOperationsMapping mapping) {
// NO_OP
}
/**
* Scans the mapping base package for classes annotated with {@link Document}.
*
* @throws ClassNotFoundException if initial entity sets could not be loaded.
*/
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
String basePackage = getMappingBasePackage();
Set<Class<?>> initialEntitySet = new HashSet<Class<?>>();
if (StringUtils.hasText(basePackage)) {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(
false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
initialEntitySet.add(
ClassUtils.forName(candidate.getBeanClassName(), AbstractCouchbaseConfiguration.class.getClassLoader()));
}
}
return initialEntitySet;
}
/**
* Determines the name of the field that will store the type information for complex types when using the
* {@link #mappingCouchbaseConverter()}. Defaults to {@value MappingCouchbaseConverter#TYPEKEY_DEFAULT}.
*
* @see MappingCouchbaseConverter#TYPEKEY_DEFAULT
* @see MappingCouchbaseConverter#TYPEKEY_SYNCGATEWAY_COMPATIBLE
*/
public String typeKey() {
return MappingCouchbaseConverter.TYPEKEY_DEFAULT;
}
/**
* Creates a {@link MappingCouchbaseConverter} using the configured {@link #couchbaseMappingContext}.
*
* @throws Exception on Bean construction failure.
*/
@Bean
public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingContext couchbaseMappingContext,
CouchbaseCustomConversions couchbaseCustomConversions) {
MappingCouchbaseConverter converter = new MappingCouchbaseConverter(couchbaseMappingContext, typeKey());
converter.setCustomConversions(couchbaseCustomConversions);
return converter;
}
/**
* Creates a {@link TranslationService}.
*
* @return TranslationService, defaulting to JacksonTranslationService.
*/
@Bean
public TranslationService couchbaseTranslationService() {
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
jacksonTranslationService.afterPropertiesSet();
// for sdk3, we need to ask the mapper _it_ uses to ignore extra fields...
JacksonTransformers.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return jacksonTranslationService;
}
/**
* Creates a {@link CouchbaseMappingContext} equipped with entity classes scanned from the mapping base package.
*
* @throws Exception on Bean construction failure.
*/
@Bean
public CouchbaseMappingContext couchbaseMappingContext() throws Exception {
CouchbaseMappingContext mappingContext = new CouchbaseMappingContext();
mappingContext.setInitialEntitySet(getInitialEntitySet());
mappingContext.setSimpleTypeHolder(couchbaseCustomConversions().getSimpleTypeHolder());
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
return mappingContext;
}
/**
* Register custom Converters in a {@link CustomConversions} object if required. These {@link CustomConversions} will
* be registered with the {@link #mappingCouchbaseConverter(CouchbaseMappingContext)} )} and
* {@link #couchbaseMappingContext()}. Returns an empty {@link CustomConversions} instance by default.
*
* @return must not be {@literal null}.
*/
@Bean
public CustomConversions couchbaseCustomConversions() {
return new CouchbaseCustomConversions(Collections.emptyList());
}
/**
* Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration
* class (the concrete class, not this one here) by default.
* <p/>
* <p>
* So if you have a {@code com.acme.AppConfig} extending {@link AbstractCouchbaseConfiguration} the base package will
* be considered {@code com.acme} unless the method is overridden to implement alternate behavior.
* </p>
*
* @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for
* entities.
*/
protected String getMappingBasePackage() {
return getClass().getPackage().getName();
}
/**
* Set to true if field names should be abbreviated with the {@link CamelCaseAbbreviatingFieldNamingStrategy}.
*
* @return true if field names should be abbreviated, default is false.
*/
protected boolean abbreviateFieldNames() {
return false;
}
/**
* Configures a {@link FieldNamingStrategy} on the {@link CouchbaseMappingContext} instance created.
*
* @return the naming strategy.
*/
protected FieldNamingStrategy fieldNamingStrategy() {
return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy()
: PropertyNameFieldNamingStrategy.INSTANCE;
}
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
/**
* Provides beans to setup SDC using {@link CouchbaseConfigurer}.
* This is used by Spring boot to provide auto configuration support.
*
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
@Configuration
public abstract class AbstractCouchbaseDataConfiguration extends CouchbaseConfigurationSupport {
protected abstract CouchbaseConfigurer couchbaseConfigurer();
/**
* Creates a {@link CouchbaseTemplate}.
*
* This uses {@link #mappingCouchbaseConverter()}, {@link #translationService()} and {@link #getDefaultConsistency()}
* for construction.
*
* Additionally, it will expect injection of a {@link ClusterInfo} and a {@link Bucket} beans from the context (most
* probably from another configuration). For a self-sufficient configuration that defines such beans, see
* {@link AbstractCouchbaseConfiguration}.
*
* @throws Exception on Bean construction failure.
*/
@Bean(name = BeanNames.COUCHBASE_TEMPLATE)
public CouchbaseTemplate couchbaseTemplate() throws Exception {
CouchbaseTemplate template = new CouchbaseTemplate(couchbaseConfigurer().couchbaseClusterInfo(),
couchbaseConfigurer().couchbaseClient(), mappingCouchbaseConverter(), translationService());
template.setDefaultConsistency(getDefaultConsistency());
return template;
}
/**
* Creates the {@link RepositoryOperationsMapping} bean which will be used by the framework to choose which
* {@link CouchbaseOperations} should back which {@link CouchbaseRepository}.
* Override {@link #configureRepositoryOperationsMapping(RepositoryOperationsMapping)} in order to customize this.
*
* @throws Exception
*/
@Bean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING)
public RepositoryOperationsMapping repositoryOperationsMapping(CouchbaseTemplate couchbaseTemplate) throws Exception {
//create a base mapping that associates all repositories to the default template
RepositoryOperationsMapping baseMapping = new RepositoryOperationsMapping(couchbaseTemplate);
//let the user tune it
configureRepositoryOperationsMapping(baseMapping);
return baseMapping;
}
/**
* In order to customize the mapping between repositories/entity types to couchbase templates,
* use the provided mapping's api (eg. in order to have different buckets backing different repositories).
*
* @param mapping the default mapping (will associate all repositories to the default template).
*/
protected void configureRepositoryOperationsMapping(RepositoryOperationsMapping mapping) {
//NO_OP
}
}

View File

@@ -1,144 +0,0 @@
/*
* Copyright 2017-2020 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.config;
import java.util.List;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import java.lang.reflect.Proxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
* Base class for Reactive Spring Data Couchbase configuration java config
*
* @author Subhashni Balakrishnan
*/
@Configuration
public abstract class AbstractReactiveCouchbaseConfiguration
extends AbstractReactiveCouchbaseDataConfiguration implements CouchbaseConfigurer {
/**
* The list of hostnames (or IP addresses) to bootstrap from.
*
* @return the list of bootstrap hosts.
*/
protected abstract List<String> getBootstrapHosts();
/**
* The name of the bucket to connect to.
*
* @return the name of the bucket.
*/
protected abstract String getBucketName();
/**
* The user of the bucket. Override the method for users in Couchbase Server 5.0+.
*
* @return the user name.
*/
protected String getUsername() { return getBucketName(); }
/**
* The password of the bucket/User of the bucket (can be an empty string).
*
* @return the password of the bucket/user.
*/
protected abstract String getBucketPassword();
/**
* Is the {@link #getEnvironment()} to be destroyed by Spring?
*
* @return true if Spring should destroy the environment with the context, false otherwise.
*/
protected boolean isEnvironmentManagedBySpring() {
return true;
}
/**
* Override this method if you want a customized {@link CouchbaseEnvironment}.
* This environment will be managed by Spring, which will call its shutdown()
* method upon bean destruction, unless you override {@link #isEnvironmentManagedBySpring()}
* as well to return false.
*
* @return a customized environment, defaults to a {@link DefaultCouchbaseEnvironment}.
*/
protected CouchbaseEnvironment getEnvironment() {
return DefaultCouchbaseEnvironment.create();
}
@Override
protected CouchbaseConfigurer couchbaseConfigurer() {
return this;
}
@Override
@Bean(destroyMethod = "shutdown", name = BeanNames.COUCHBASE_ENV)
public CouchbaseEnvironment couchbaseEnvironment() {
if (isEnvironmentManagedBySpring()) {
return getEnvironment();
} else {
CouchbaseEnvironment proxy = (CouchbaseEnvironment) java.lang.reflect.Proxy.newProxyInstance(CouchbaseEnvironment.class.getClassLoader(),
new Class[]{CouchbaseEnvironment.class},
new CouchbaseEnvironmentNoShutdownInvocationHandler(getEnvironment()));
return proxy;
}
}
/**
* Returns the {@link Cluster} instance to connect to.
*
* @throws Exception on Bean construction failure.
*/
@Override
@Bean(destroyMethod = "disconnect", name = BeanNames.COUCHBASE_CLUSTER)
public Cluster couchbaseCluster() throws Exception {
return CouchbaseCluster.create(couchbaseEnvironment(), getBootstrapHosts());
}
@Override
@Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().clusterManager(getUsername(), getBucketPassword()).info();
}
/**
* Return the {@link Bucket} instance to connect to.
*
* @throws Exception on Bean construction failure.
*/
@Override
@Bean(destroyMethod = "close", name = BeanNames.COUCHBASE_BUCKET)
public Bucket couchbaseClient() throws Exception {
//@Bean method can use another @Bean method in the same @Configuration by directly invoking it
Cluster cluster = couchbaseCluster();
if(!getUsername().contentEquals(getBucketName())){
cluster.authenticate(getUsername(), getBucketPassword());
} else if (!getBucketPassword().isEmpty()) {
return cluster.openBucket(getBucketName(), getBucketPassword());
}
return cluster.openBucket(getBucketName());
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2017-2020 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.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations;
/**
* Provides beans to setup reactive repositories in SDC using {@link CouchbaseConfigurer}.
*
* @author Subhashni Balakrishnan
*/
@Configuration
public abstract class AbstractReactiveCouchbaseDataConfiguration extends CouchbaseConfigurationSupport {
protected abstract CouchbaseConfigurer couchbaseConfigurer();
/**
* Creates a {@link RxJavaCouchbaseTemplate}.
*
* This uses {@link #mappingCouchbaseConverter()}, {@link #translationService()} and {@link #getDefaultConsistency()}
* for construction.
*
*
* @throws Exception on Bean construction failure.
*/
@Bean(name = BeanNames.RXJAVA1_COUCHBASE_TEMPLATE)
public RxJavaCouchbaseTemplate reactiveCouchbaseTemplate() throws Exception {
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(couchbaseConfigurer().couchbaseClusterInfo(),
couchbaseConfigurer().couchbaseClient(), mappingCouchbaseConverter(), translationService());
template.setDefaultConsistency(getDefaultConsistency());
return template;
}
/**
* Creates the {@link ReactiveRepositoryOperationsMapping} bean which will be used by the framework to choose which
* {@link RxJavaCouchbaseOperations} should back which {@link ReactiveCouchbaseRepository}.
* Override {@link #configureReactiveRepositoryOperationsMapping} in order to customize this.
*
* @throws Exception
*/
@Bean(name = BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING)
public ReactiveRepositoryOperationsMapping reactiveRepositoryOperationsMapping(RxJavaCouchbaseTemplate couchbaseTemplate) throws Exception {
//create a base mapping that associates all repositories to the default template
ReactiveRepositoryOperationsMapping baseMapping = new ReactiveRepositoryOperationsMapping(couchbaseTemplate);
//let the user tune it
configureReactiveRepositoryOperationsMapping(baseMapping);
return baseMapping;
}
/**
* In order to customize the mapping between repositories/entity types to couchbase templates,
* use the provided mapping's api (eg. in order to have different buckets backing different repositories).
*
* @param mapping the default mapping (will associate all repositories to the default template).
*/
protected void configureReactiveRepositoryOperationsMapping(ReactiveRepositoryOperationsMapping mapping) {
//NO_OP
}
}

View File

@@ -16,130 +16,44 @@
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
/**
* Contains default bean names for Couchbase beans.
*
* These are the names of the beans used by Spring Data Couchbase, unless an explicit id is given to the bean
* either in the xml configuration or the {@link AbstractCouchbaseConfiguration java configuration}.
* Contains default bean names for Couchbase beans. These are the names of the beans used by Spring Data Couchbase,
* unless an explicit id is given to the bean either in the xml configuration or the
* {@link AbstractCouchbaseConfiguration java configuration}.
*
* @author Michael Nitschinger
* @author Simon Baslé
*/
public class BeanNames {
/**
* The name for the default {@link CouchbaseEnvironment} bean.
*
* See {@link AbstractCouchbaseConfiguration#couchbaseEnvironment()} for java config, and
* the "&lt;couchbase:env /&gt;" element for xml config.
*/
public static final String COUCHBASE_ENV = "couchbaseEnv";
/**
* The name for the default {@link CouchbaseOperations} bean. See
* {@link AbstractCouchbaseConfiguration#couchbaseTemplate(CouchbaseClientFactory, MappingCouchbaseConverter)} )}
* for java config, and the "&lt;couchbase:template /&gt;"
* element for xml config.
*/
public static final String COUCHBASE_TEMPLATE = "couchbaseTemplate";
/**
* The name for the default {@link Cluster} bean.
*
* See {@link AbstractCouchbaseConfiguration#couchbaseCluster()} for java config, and
* the "&lt;couchbase:cluster /&gt;" element for xml config.
*/
public static final String COUCHBASE_CLUSTER = "couchbaseCluster";
/**
* The name for the bean that stores custom mapping between repositories and their backing couchbaseOperations.
*/
public static final String COUCHBASE_OPERATIONS_MAPPING = "couchbaseRepositoryOperationsMapping";
/**
* The name for the default {@link Bucket} bean.
*
* See {@link AbstractCouchbaseConfiguration#couchbaseClient()} for java config, and
* the "&lt;couchbase:bucket /&gt;" element for xml config.
*/
public static final String COUCHBASE_BUCKET = "couchbaseBucket";
/**
* The name for the default {@link CouchbaseOperations} bean.
*
* See {@link AbstractCouchbaseConfiguration#couchbaseTemplate()} for java config, and
* the "&lt;couchbase:template /&gt;" element for xml config.
*/
public static final String COUCHBASE_TEMPLATE = "couchbaseTemplate";
/**
* The name for the default {@link TranslationService} bean.
*
* See {@link AbstractCouchbaseConfiguration#translationService()} for java config, and
* the "&lt;couchbase:translation-service /&gt;" element for xml config.
*/
public static final String COUCHBASE_TRANSLATION_SERVICE = "couchbaseTranslationService";
/**
* The name for the default {@link ClusterInfo} bean.
*
* See {@link AbstractCouchbaseConfiguration#couchbaseClusterInfo()} for java config, and
* the "&lt;couchbase:clusterInfo /&gt;" element for xml config.
*/
public static final String COUCHBASE_CLUSTER_INFO = "couchbaseClusterInfo";
/**
* The name for the bean that stores custom mapping between repositories and their backing couchbaseOperations.
*/
public static final String COUCHBASE_OPERATIONS_MAPPING = "couchbaseRepositoryOperationsMapping";
/**
* The name for the bean that stores custom mapping between reactive repositories and their backing reactiveCouchbaseOperations.
*/
public static final String REACTIVE_COUCHBASE_OPERATIONS_MAPPING = "reactiveCouchbaseRepositoryOperationsMapping";
/**
* The name for the bean that stores custom mapping between rxjava repositories and their backing rxjavaCouchbaseOperations.
*/
public static final String RXJAVA_COUCHBASE_OPERATIONS_MAPPING = "rxJavaCouchbaseRepositoryOperationsMapping";
/**
* The name for the bean that drives how some indexes are automatically created.
*/
public static final String COUCHBASE_INDEX_MANAGER = "couchbaseIndexManager";
/**
* The name for the bean that performs conversion to/from representation suitable for storage in couchbase.
*/
public static final String COUCHBASE_MAPPING_CONVERTER = "couchbaseMappingConverter";
/**
* The name for the bean that stores mapping metadata for entities stored in couchbase.
*/
public static final String COUCHBASE_MAPPING_CONTEXT = "couchbaseMappingContext";
/**
* The name for the bean that registers custom {@link Converter Converters} to encode/decode entity members.
*/
public static final String COUCHBASE_CUSTOM_CONVERSIONS = "couchbaseCustomConversions";
/**
* The name for the bean that will handle audit trail marking of entities.
*/
public static final String COUCHBASE_AUDITING_HANDLER = "couchbaseAuditingHandler";
/**
* The name for the default {@link ReactiveCouchbaseOperations} bean.
*
* See {@link AbstractReactiveCouchbaseConfiguration#reactiveCouchbaseTemplate()} for java config, and
* the "&lt;couchbase:template /&gt;" element for xml config.
*/
public static final String REACTIVE_COUCHBASE_TEMPLATE = "reactiveCouchbaseTemplate";
/**
* The name for the default {@link RxJavaCouchbaseOperations} bean.
*
* See {@link AbstractRxJavaCouchbaseConfiguration#rxjava1CouchbaseTemplate()} for java config, and
* the "&lt;couchbase:template /&gt;" element for xml config.
*/
public static final String RXJAVA1_COUCHBASE_TEMPLATE = "rxjava1CouchbaseTemplate";
/**
* The name for the bean that stores custom mapping between reactive repositories and their backing
* reactiveCouchbaseOperations.
*/
public static final String REACTIVE_COUCHBASE_OPERATIONS_MAPPING = "reactiveCouchbaseRepositoryOperationsMapping";
/**
* The name for the bean that stores mapping metadata for entities stored in couchbase.
*/
public static final String COUCHBASE_MAPPING_CONTEXT = "couchbaseMappingContext";
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseBucket;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator;
/**
* The Factory Bean to help {@link CouchbaseBucketParser} constructing a {@link Bucket} from a given
* {@link Cluster} reference.
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
public class CouchbaseBucketFactoryBean extends AbstractFactoryBean<Bucket> implements PersistenceExceptionTranslator {
private final Cluster cluster;
private final String bucketName;
private final String username;
private final String password;
private final PersistenceExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator();
public CouchbaseBucketFactoryBean(Cluster cluster) {
this(cluster, null, null, null);
}
public CouchbaseBucketFactoryBean(Cluster cluster, String bucketName) {
this(cluster, bucketName, bucketName, null);
}
public CouchbaseBucketFactoryBean(Cluster cluster, String bucketName, String username, String password) {
this.cluster = cluster;
this.bucketName = bucketName;
this.username = username;
this.password = password;
}
@Override
public Class<?> getObjectType() {
return CouchbaseBucket.class;
}
@Override
protected Bucket createInstance() throws Exception {
if (bucketName == null) {
return cluster.openBucket();
}
else if (password == null) {
return cluster.openBucket(bucketName);
}
else if (bucketName.contentEquals(username)) {
return cluster.openBucket(bucketName, password);
}
else {
cluster.authenticate(username, password);
return cluster.openBucket(bucketName);
}
}
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
}
}

View File

@@ -1,113 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* The parser for XML definition of a {@link Bucket}, to be constructed from a {@link Cluster} reference.
* If no reference is given, the default reference <code>{@value BeanNames#COUCHBASE_CLUSTER}</code> is used.
*
* See attributes {@link #CLUSTER_REF_ATTR}, {@link #BUCKETNAME_ATTR}, {@link #USERNAME_ATTR} and {@link #BUCKETPASSWORD_ATTR}.
*
* @author Simon Baslé
*/
public class CouchbaseBucketParser extends AbstractSingleBeanDefinitionParser {
/**
* The <code>cluster-ref</code> attribute in a bucket definition defines the cluster to build from.
*/
public static final String CLUSTER_REF_ATTR = "cluster-ref";
/**
* The <code>bucketName</code> attribute in a bucket definition defines the name of the bucket to open.
*/
public static final String BUCKETNAME_ATTR = "bucketName";
/*
* The <code>username</code> attribute in a bucket definition defines the user of the bucket to open.
*/
public static final String USERNAME_ATTR = "username";
/**
* The <code>bucketPassword</code> attribute in a bucket definition defines the password of the bucket/user of the bucket to open.
*/
public static final String BUCKETPASSWORD_ATTR = "bucketPassword";
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_BUCKET;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseBucketFactoryBean.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param builder the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder builder) {
String clusterRef = element.getAttribute(CLUSTER_REF_ATTR);
if (!StringUtils.hasText(clusterRef)) {
clusterRef = BeanNames.COUCHBASE_CLUSTER;
}
builder.addConstructorArgReference(clusterRef);
String bucketName = element.getAttribute(BUCKETNAME_ATTR);
if (StringUtils.hasText(bucketName)) {
builder.addConstructorArgValue(bucketName);
}
String username = element.getAttribute(USERNAME_ATTR);
if (StringUtils.hasText(username)) {
builder.addConstructorArgValue(username);
}
String password = element.getAttribute(BUCKETPASSWORD_ATTR);
if (StringUtils.hasText(password)) {
builder.addConstructorArgValue(password);
}
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator;
/**
* The Factory Bean to help {@link CouchbaseClusterInfoParser} constructing a {@link ClusterInfo} from a given
* {@link Cluster} reference.
*
* @author Simon Baslé
*/
public class CouchbaseClusterInfoFactoryBean extends AbstractFactoryBean<ClusterInfo> implements PersistenceExceptionTranslator {
private final Cluster cluster;
private final String login;
private final String password;
private final PersistenceExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator();
public CouchbaseClusterInfoFactoryBean(Cluster cluster, String login, String password) {
this.cluster = cluster;
this.login = login;
this.password = password;
}
@Override
public Class<?> getObjectType() {
return ClusterInfo.class;
}
@Override
protected ClusterInfo createInstance() throws Exception {
return cluster.clusterManager(login, password).info();
}
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* The parser for XML definition of a {@link ClusterInfo}, to be constructed from a {@link Cluster} reference.
* If no reference is given, the default reference <code>{@value BeanNames#COUCHBASE_CLUSTER_INFO}</code> is used.
* <p/>
* See attributes {@link #CLUSTER_REF_ATTR}, {@link #LOGIN_ATTR} and {@link #PASSWORD_ATTR}.
*
* @author Simon Baslé
*/
public class CouchbaseClusterInfoParser extends AbstractSingleBeanDefinitionParser {
/**
* The <code>cluster-ref</code> attribute in a cluster info definition defines the cluster to build from.
*/
public static final String CLUSTER_REF_ATTR = "cluster-ref";
/**
* The <code>login</code> attribute in a cluster info definition defines the credential to use (can also be a
* bucket level credential).
*/
public static final String LOGIN_ATTR = "login";
/**
* The <code>password</code> attribute in a cluster info definition defines the credential's password.
*/
public static final String PASSWORD_ATTR = "password";
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_CLUSTER_INFO;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseClusterInfoFactoryBean.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param builder the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder builder) {
String clusterRef = element.getAttribute(CLUSTER_REF_ATTR);
if (!StringUtils.hasText(clusterRef)) {
clusterRef = BeanNames.COUCHBASE_CLUSTER;
}
builder.addConstructorArgReference(clusterRef);
String login = element.getAttribute(LOGIN_ATTR);
if (!StringUtils.hasText(login)) {
login = "default";
}
builder.addConstructorArgValue(login);
String password = element.getAttribute(PASSWORD_ATTR);
if (!StringUtils.hasText(password)) {
password = "";
}
builder.addConstructorArgValue(password);
}
}

View File

@@ -1,156 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import java.util.ArrayList;
import java.util.List;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* The XML parser for a {@link Cluster} definition.
*
* Such a definition can be tuned by either referencing a {@link CouchbaseEnvironment} via
* the {@value #CLUSTER_ENVIRONMENT_REF} attribute or define a custom environment inline via
* the &lt;{@value #CLUSTER_ENVIRONMENT_TAG}&gt; tag (not recommended, environments should be
* shared as possible). If no environment reference or inline description is provided, the
* default environment reference {@value BeanNames#COUCHBASE_ENV} is used.
*
* To bootstrap the connection, one can provide IPs or hostnames of nodes to connect to
* via 1 or more &lt;{@value #CLUSTER_NODE_TAG}&gt; tags.
*
* @author Simon Baslé
*/
public class CouchbaseClusterParser extends AbstractSingleBeanDefinitionParser {
/**
* The &lt;node&gt; elements in a cluster definition define the bootstrap hosts to use
*/
public static final String CLUSTER_NODE_TAG = "node";
/**
* The unique &lt;env&gt; element in a cluster definition define the environment customizations.
*
* @see CouchbaseEnvironmentParser CouchbaseEnvironmentParser for the possible fields.
* @see #CLUSTER_ENVIRONMENT_REF CLUSTER_ENVIRONMENT_REF as an alternative (giving a reference to
* an env instead of inline description, lower precedence)
*/
public static final String CLUSTER_ENVIRONMENT_TAG = "env";
/**
* The &lt;env-ref&gt; attribute allows to use a reference to an {@link CouchbaseEnvironment} to
* tune the connection.
*
* @see #CLUSTER_ENVIRONMENT_TAG CLUSTER_ENVIRONMENT_TAG for an inline alternative
* (which takes priority over this reference)
*/
public static final String CLUSTER_ENVIRONMENT_REF = "env-ref";
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_CLUSTER;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseCluster.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param bean the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
bean.setFactoryMethod("create");
bean.setDestroyMethodName("disconnect");
parseEnvironment(bean, element);
List<Element> nodes = DomUtils.getChildElementsByTagName(element, CLUSTER_NODE_TAG);
if (nodes != null && nodes.size() > 0) {
List<String> bootstrapUrls = new ArrayList<String>(nodes.size());
for (int i = 0; i < nodes.size(); i++) {
bootstrapUrls.add(nodes.get(i).getTextContent());
}
bean.addConstructorArgValue(bootstrapUrls);
}
}
/**
* @return true if a custom environment was parsed and injected (either reference or inline), false if
* the default environment reference was used.
*/
protected boolean parseEnvironment(BeanDefinitionBuilder clusterBuilder, Element clusterElement) {
//any inline environment description would take precedence over a reference
Element envElement = DomUtils.getChildElementByTagName(clusterElement, CLUSTER_ENVIRONMENT_TAG);
if (envElement != null && envElement.hasAttributes()) {
injectEnvElement(clusterBuilder, envElement);
return true;
}
//secondly try to see if an env has been referenced
String envRef = clusterElement.getAttribute(CLUSTER_ENVIRONMENT_REF);
if (StringUtils.hasText(envRef)) {
injectEnvReference(clusterBuilder, envRef);
return true;
}
//if no custom value provided, consider it a reference to the default bean for Couchbase Environment
injectEnvReference(clusterBuilder, BeanNames.COUCHBASE_ENV);
return false;
}
protected void injectEnvElement(BeanDefinitionBuilder clusterBuilder, Element envElement) {
BeanDefinitionBuilder envDefinitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(CouchbaseEnvironmentFactoryBean.class);
new CouchbaseEnvironmentParser().doParse(envElement, envDefinitionBuilder);
clusterBuilder.addConstructorArgValue(envDefinitionBuilder.getBeanDefinition());
}
protected void injectEnvReference(BeanDefinitionBuilder clusterBuilder, String envRef) {
clusterBuilder.addConstructorArgReference(envRef);
}
}

View File

@@ -1,196 +0,0 @@
/*
* Copyright 2017-2020 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.config;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.view.ViewQuery;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.core.query.ViewIndexed;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Provides configuration support for common beans in both {@link AbstractCouchbaseDataConfiguration}
* and {@link AbstractReactiveCouchbaseDataConfiguration} configurations
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
* @author Mark Paluch
*/
public class CouchbaseConfigurationSupport {
/**
* Scans the mapping base package for classes annotated with {@link Document}.
*
* @throws ClassNotFoundException if initial entity sets could not be loaded.
*/
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
String basePackage = getMappingBasePackage();
Set<Class<?>> initialEntitySet = new HashSet<Class<?>>();
if (StringUtils.hasText(basePackage)) {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false);
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class));
componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class));
for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) {
initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), AbstractReactiveCouchbaseConfiguration.class.getClassLoader()));
}
}
return initialEntitySet;
}
/**
* Determines the name of the field that will store the type information for complex types when
* using the {@link #mappingCouchbaseConverter()}.
* Defaults to {@value MappingCouchbaseConverter#TYPEKEY_DEFAULT}.
*
* @see MappingCouchbaseConverter#TYPEKEY_DEFAULT
* @see MappingCouchbaseConverter#TYPEKEY_SYNCGATEWAY_COMPATIBLE
*/
public String typeKey() {
return MappingCouchbaseConverter.TYPEKEY_DEFAULT;
}
/**
* Creates a {@link MappingCouchbaseConverter} using the configured {@link #couchbaseMappingContext}.
*
* @throws Exception on Bean construction failure.
*/
@Bean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER)
public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception {
MappingCouchbaseConverter converter = new MappingCouchbaseConverter(couchbaseMappingContext(), typeKey());
converter.setCustomConversions(customConversions());
return converter;
}
/**
* Creates a {@link TranslationService}.
*
* @return TranslationService, defaulting to JacksonTranslationService.
*/
@Bean(name = BeanNames.COUCHBASE_TRANSLATION_SERVICE)
public TranslationService translationService() {
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
jacksonTranslationService.afterPropertiesSet();
return jacksonTranslationService;
}
/**
* Creates a {@link CouchbaseMappingContext} equipped with entity classes scanned from the mapping base package.
*
* @throws Exception on Bean construction failure.
*/
@Bean(name = BeanNames.COUCHBASE_MAPPING_CONTEXT)
public CouchbaseMappingContext couchbaseMappingContext() throws Exception {
CouchbaseMappingContext mappingContext = new CouchbaseMappingContext();
mappingContext.setInitialEntitySet(getInitialEntitySet());
mappingContext.setSimpleTypeHolder(customConversions().getSimpleTypeHolder());
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
return mappingContext;
}
/**
* Register custom Converters in a {@link CustomConversions} object if required. These
* {@link CustomConversions} will be registered with the {@link #mappingCouchbaseConverter()} and
* {@link #couchbaseMappingContext()}. Returns an empty {@link CustomConversions} instance by default.
*
* @return must not be {@literal null}.
*/
@Bean(name = BeanNames.COUCHBASE_CUSTOM_CONVERSIONS)
public CustomConversions customConversions() {
return new CouchbaseCustomConversions(Collections.emptyList());
}
/**
* Register an {@link IndexManager} bean that will be used to process {@link ViewIndexed},
* {@link N1qlPrimaryIndexed} and {@link N1qlSecondaryIndexed} annotations on repositories
* to automatically create indexes. By default, since such automatic creations are discouraged in
* production envrironment, the configuration will assume the worst and will ignore these annotations.
* <p/>
* If you are sure this configuration used in a context where such automatic creations are desired (eg.
* you want automatic index creation in Dev, just not in Prod, and this configuration is the Dev one),
* override the bean and use the {@link IndexManager#IndexManager()} constructor (or
* {@link IndexManager#IndexManager(boolean, boolean, boolean)} constructor with appropriate flags set to true to
* activate).
*/
@Bean(name = BeanNames.COUCHBASE_INDEX_MANAGER)
public IndexManager indexManager() {
return new IndexManager(false, false, false); //this ignores view, N1QL primary and secondary annotations
}
/**
* Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration
* class (the concrete class, not this one here) by default.
* <p/>
* <p>So if you have a {@code com.acme.AppConfig} extending {@link AbstractCouchbaseConfiguration} the base package
* will be considered {@code com.acme} unless the method is overridden to implement alternate behavior.</p>
*
* @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for
* entities.
*/
protected String getMappingBasePackage() {
return getClass().getPackage().getName();
}
/**
* Set to true if field names should be abbreviated with the {@link CamelCaseAbbreviatingFieldNamingStrategy}.
*
* @return true if field names should be abbreviated, default is false.
*/
protected boolean abbreviateFieldNames() {
return false;
}
/**
* Configures a {@link FieldNamingStrategy} on the {@link CouchbaseMappingContext} instance created.
*
* @return the naming strategy.
*/
protected FieldNamingStrategy fieldNamingStrategy() {
return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy() : PropertyNameFieldNamingStrategy.INSTANCE;
}
/**
* Configures the default consistency for generated {@link ViewQuery view queries}
* and {@link N1qlQuery N1QL queries} in repositories.
*
* @return the {@link Consistency consistency} to apply by default on generated queries.
*/
protected Consistency getDefaultConsistency() {
return Consistency.DEFAULT_CONSISTENCY;
}
}

View File

@@ -1,50 +0,0 @@
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import org.springframework.data.couchbase.core.CouchbaseOperations;
/**
* Strategy interface for users to provide as a factory for custom components needed
* by the Couchbase integration.
*
* This allows to centralize instantiation of Couchbase SDK core elements.
*
* @author Stephane Nicoll
*/
public interface CouchbaseConfigurer {
/**
* Set up the underlying main {@link CouchbaseEnvironment}, allowing tuning of the Couchbase SDK.
*
* @throws Exception in case of error during the CouchbaseEnvironment instantiation.
*/
CouchbaseEnvironment couchbaseEnvironment() throws Exception;
/**
* Set up the underlying main Couchbase {@link Cluster} reference to be used by the Spring Data framework
* when storing into Couchbase.
*
* @throws Exception in case of error during the Cluster instantiation.
*/
Cluster couchbaseCluster() throws Exception;
/**
* Set up the underlying main {@link ClusterInfo}, allowing to check feature availability and cluster configuration.
*
* @throws Exception in case of error during the ClusterInfo instantiation.
*/
ClusterInfo couchbaseClusterInfo() throws Exception;
/**
* Set up the underlying main {@link Bucket}, the primary Couchbase SDK entry point to be used by the Spring Data
* framework for the {@link CouchbaseOperations}.
*
* @throws Exception in case of error during the bucket instantiation.
*/
Bucket couchbaseClient() throws Exception;
}

View File

@@ -1,248 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import com.couchbase.client.core.retry.BestEffortRetryStrategy;
import com.couchbase.client.core.retry.FailFastRetryStrategy;
import com.couchbase.client.core.retry.RetryStrategy;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment.Builder;
import org.springframework.beans.factory.config.AbstractFactoryBean;
/**
* Factory Bean to help create a CouchbaseEnvironment (by offering setters for supported tuning methods).
*
* @author Simon Baslé
* @author Simon Bland
* @author Subhashni Balakrishnan
*/
/*package*/ class CouchbaseEnvironmentFactoryBean extends AbstractFactoryBean<CouchbaseEnvironment> {
public static final String RETRYSTRATEGY_FAILFAST = "FailFast";
public static final String RETRYSTRATEGY_BESTEFFORT = "BestEffort";
private final Builder couchbaseEnvBuilder = DefaultCouchbaseEnvironment.builder();
/*
These are tunings that are not practical to be exposed in a xml configuration
or not supposed to be modified that easily:
observeIntervalDelay
reconnectDelay
retryDelay
userAgent
packageNameAndVersion
ioPool
scheduler
eventBus
systemMetricsCollectorConfig
networkLatencyMetricsCollectorConfig
requestBufferWaitStrategy
sslKeystore
memcachedHashingStrategy
kvIoPool
queryIoPool
searchIoPool
viewIoPool
kvServiceConfig
queryServiceConfig
searchServiceConfig
viewServiceConfig
cryptoManager
tracer
orphanResponseReporter
*/
@Override
public Class<?> getObjectType() {
return DefaultCouchbaseEnvironment.class;
}
@Override
protected CouchbaseEnvironment createInstance() throws Exception {
return couchbaseEnvBuilder.build();
}
/**
* Sets the {@link RetryStrategy} to use from an enum-like String value.
* Either "FailFast" or "BestEffort" are recognized.
*
* @param retryStrategy the string value enum from which to choose a strategy.
*/
public void setRetryStrategy(String retryStrategy) {
if (RETRYSTRATEGY_FAILFAST.equals(retryStrategy)) {
this.couchbaseEnvBuilder.retryStrategy(FailFastRetryStrategy.INSTANCE);
} else if (RETRYSTRATEGY_BESTEFFORT.equals(retryStrategy)) {
this.couchbaseEnvBuilder.retryStrategy(BestEffortRetryStrategy.INSTANCE);
}
}
//==== SETTERS for the factory bean ====
public void setManagementTimeout(long managementTimeout) {
this.couchbaseEnvBuilder.managementTimeout(managementTimeout);
}
public void setQueryTimeout(long queryTimeout) {
this.couchbaseEnvBuilder.queryTimeout(queryTimeout);
}
public void setViewTimeout(long viewTimeout) {
this.couchbaseEnvBuilder.viewTimeout(viewTimeout);
}
public void setKvTimeout(long kvTimeout) {
this.couchbaseEnvBuilder.kvTimeout(kvTimeout);
}
public void setConnectTimeout(long connectTimeout) {
this.couchbaseEnvBuilder.connectTimeout(connectTimeout);
}
public void setDisconnectTimeout(long disconnectTimeout) {
this.couchbaseEnvBuilder.disconnectTimeout(disconnectTimeout);
}
public void setDnsSrvEnabled(boolean dnsSrvEnabled) {
this.couchbaseEnvBuilder.dnsSrvEnabled(dnsSrvEnabled);
}
public void setSslEnabled(boolean sslEnabled) {
this.couchbaseEnvBuilder.sslEnabled(sslEnabled);
}
public void setSslKeystoreFile(String sslKeystoreFile) {
this.couchbaseEnvBuilder.sslKeystoreFile(sslKeystoreFile);
}
public void setSslKeystorePassword(String sslKeystorePassword) {
this.couchbaseEnvBuilder.sslKeystorePassword(sslKeystorePassword);
}
public void setBootstrapHttpEnabled(boolean bootstrapHttpEnabled) {
this.couchbaseEnvBuilder.bootstrapHttpEnabled(bootstrapHttpEnabled);
}
public void setBootstrapCarrierEnabled(boolean bootstrapCarrierEnabled) {
this.couchbaseEnvBuilder.bootstrapCarrierEnabled(bootstrapCarrierEnabled);
}
public void setBootstrapHttpDirectPort(int bootstrapHttpDirectPort) {
this.couchbaseEnvBuilder.bootstrapHttpDirectPort(bootstrapHttpDirectPort);
}
public void setBootstrapHttpSslPort(int bootstrapHttpSslPort) {
this.couchbaseEnvBuilder.bootstrapHttpSslPort(bootstrapHttpSslPort);
}
public void setBootstrapCarrierDirectPort(int bootstrapCarrierDirectPort) {
this.couchbaseEnvBuilder.bootstrapCarrierDirectPort(bootstrapCarrierDirectPort);
}
public void setBootstrapCarrierSslPort(int bootstrapCarrierSslPort) {
this.couchbaseEnvBuilder.bootstrapCarrierSslPort(bootstrapCarrierSslPort);
}
public void setIoPoolSize(int ioPoolSize) {
this.couchbaseEnvBuilder.ioPoolSize(ioPoolSize);
}
public void setComputationPoolSize(int computationPoolSize) {
this.couchbaseEnvBuilder.computationPoolSize(computationPoolSize);
}
public void setResponseBufferSize(int responseBufferSize) {
this.couchbaseEnvBuilder.responseBufferSize(responseBufferSize);
}
public void setRequestBufferSize(int requestBufferSize) {
this.couchbaseEnvBuilder.requestBufferSize(requestBufferSize);
}
public void setKvEndpoints(int kvEndpoints) {
this.couchbaseEnvBuilder.kvEndpoints(kvEndpoints);
}
public void setViewEndpoints(int viewEndpoints) {
this.couchbaseEnvBuilder.viewEndpoints(viewEndpoints);
}
public void setQueryEndpoints(int queryEndpoints) {
this.couchbaseEnvBuilder.queryEndpoints(queryEndpoints);
}
public void setMaxRequestLifetime(long maxRequestLifetime) {
this.couchbaseEnvBuilder.maxRequestLifetime(maxRequestLifetime);
}
public void setKeepAliveInterval(long keepAliveInterval) {
this.couchbaseEnvBuilder.keepAliveInterval(keepAliveInterval);
}
public void setAutoreleaseAfter(long autoreleaseAfter) {
this.couchbaseEnvBuilder.autoreleaseAfter(autoreleaseAfter);
}
public void setBufferPoolingEnabled(boolean bufferPoolingEnabled) {
this.couchbaseEnvBuilder.bufferPoolingEnabled(bufferPoolingEnabled);
}
public void setTcpNodelayEnabled(boolean tcpNodelayEnabled) {
this.couchbaseEnvBuilder.tcpNodelayEnabled(tcpNodelayEnabled);
}
public void setMutationTokensEnabled(boolean mutationTokensEnabled) {
this.couchbaseEnvBuilder.mutationTokensEnabled(mutationTokensEnabled);
}
public void setAnalyticsTimeout(long analyticsTimeout) {
this.couchbaseEnvBuilder.analyticsTimeout(analyticsTimeout);
}
public void setConfigPollInterval(long configPollInterval) {
this.couchbaseEnvBuilder.configPollInterval(configPollInterval);
}
public void setConfigPollFloorInterval(long configPollFloorInterval) {
this.couchbaseEnvBuilder.configPollFloorInterval(configPollFloorInterval);
}
public void setCertAuthEnabled(boolean certAuthEnabled) {
this.couchbaseEnvBuilder.certAuthEnabled(certAuthEnabled);
}
public void setOperationTracingEnabled(boolean operationTracingEnabled) {
this.couchbaseEnvBuilder.operationTracingEnabled(operationTracingEnabled);
}
public void setOperationTracingServerDurationEnabled(boolean operationTracingServerDurationEnabled) {
this.couchbaseEnvBuilder.operationTracingServerDurationEnabled(operationTracingServerDurationEnabled);
}
public void setOrphanResponseReportingEnabled(boolean orphanResponseReportingEnabled) {
this.couchbaseEnvBuilder.orphanResponseReportingEnabled(orphanResponseReportingEnabled);
}
public void setCompressionMinSize(int compressionMinSize) {
this.couchbaseEnvBuilder.compressionMinSize(compressionMinSize);
}
public void setCompressionMinRatio(double compressionMinRatio) {
this.couchbaseEnvBuilder.compressionMinRatio(compressionMinRatio);
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import com.couchbase.client.java.env.CouchbaseEnvironment;
/**
* A dynamic proxy around a {@link CouchbaseEnvironment} that prevents its {@link CouchbaseEnvironment#shutdown()} method
* to be invoked. Useful when the delegate is not to be lifecycle-managed by Spring.
*
* @author Simon Baslé
* @author Jonathan Edwards
* @author Subhashni Balakrishnan
*/
public class CouchbaseEnvironmentNoShutdownInvocationHandler implements InvocationHandler {
private final CouchbaseEnvironment environment;
public CouchbaseEnvironmentNoShutdownInvocationHandler(CouchbaseEnvironment environment) {
this.environment = environment;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().contentEquals("shutdown")) {
return false;
}
return method.invoke(this.environment, args);
}
}

View File

@@ -1,150 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import static org.springframework.data.config.ParsingUtils.setPropertyValue;
import com.couchbase.client.core.retry.RetryStrategy;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Allows creation of a {@link DefaultCouchbaseEnvironment} via spring XML configuration.
* <p>
* The following properties are supported:<br/><ul>
* <li>{@link DefaultCouchbaseEnvironment.Builder#managementTimeout(long) managementTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#queryTimeout(long) queryTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#viewTimeout(long) viewTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#kvTimeout(long) kvTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#connectTimeout(long) connectTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#disconnectTimeout(long) disconnectTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#dnsSrvEnabled(boolean) dnsSrvEnabled}</li>
*
* <li>{@link DefaultCouchbaseEnvironment.Builder#sslEnabled(boolean) sslEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#sslKeystoreFile(String) sslKeystoreFile}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#sslKeystorePassword(String) sslKeystorePassword}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpEnabled(boolean) bootstrapHttpEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierEnabled(boolean) bootstrapCarrierEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpDirectPort(int) bootstrapHttpDirectPort}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpSslPort(int) bootstrapHttpSslPort}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierDirectPort(int) bootstrapCarrierDirectPort}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierSslPort(int) bootstrapCarrierSslPort}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#ioPoolSize(int) ioPoolSize}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#computationPoolSize(int) computationPoolSize}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#responseBufferSize(int) responseBufferSize}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#requestBufferSize(int) requestBufferSize}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#kvEndpoints(int) kvEndpoints}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#viewEndpoints(int) viewEndpoints}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#queryEndpoints(int) queryEndpoints}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#retryStrategy(RetryStrategy) retryStrategy}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#maxRequestLifetime(long) maxRequestLifetime}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#keepAliveInterval(long) keepAliveInterval}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#autoreleaseAfter(long) autoreleaseAfter}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bufferPoolingEnabled(boolean) bufferPoolingEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#tcpNodelayEnabled(boolean) tcpNodelayEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#mutationTokensEnabled(boolean) mutationTokensEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#analyticsTimeout(long) analyticsTimeout}</li>
* </ul>
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
public class CouchbaseEnvironmentParser extends AbstractSingleBeanDefinitionParser {
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_ENV;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseEnvironmentFactoryBean.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param envElement the XML element which contains the attributes.
* @param envDefinitionBuilder the builder which builds the bean.
*/
@Override
protected void doParse(final Element envElement, final BeanDefinitionBuilder envDefinitionBuilder) {
setPropertyValue(envDefinitionBuilder, envElement, "managementTimeout", "managementTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "queryTimeout", "queryTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "viewTimeout", "viewTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "kvTimeout", "kvTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "connectTimeout", "connectTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "disconnectTimeout", "disconnectTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "dnsSrvEnabled", "dnsSrvEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "sslEnabled", "sslEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "sslKeystoreFile", "sslKeystoreFile");
setPropertyValue(envDefinitionBuilder, envElement, "sslKeystorePassword", "sslKeystorePassword");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapHttpEnabled", "bootstrapHttpEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapCarrierEnabled", "bootstrapCarrierEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapHttpDirectPort", "bootstrapHttpDirectPort");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapHttpSslPort", "bootstrapHttpSslPort");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapCarrierDirectPort", "bootstrapCarrierDirectPort");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapCarrierSslPort", "bootstrapCarrierSslPort");
setPropertyValue(envDefinitionBuilder, envElement, "ioPoolSize", "ioPoolSize");
setPropertyValue(envDefinitionBuilder, envElement, "computationPoolSize", "computationPoolSize");
setPropertyValue(envDefinitionBuilder, envElement, "responseBufferSize", "responseBufferSize");
setPropertyValue(envDefinitionBuilder, envElement, "requestBufferSize", "requestBufferSize");
setPropertyValue(envDefinitionBuilder, envElement, "kvEndpoints", "kvEndpoints");
setPropertyValue(envDefinitionBuilder, envElement, "viewEndpoints", "viewEndpoints");
setPropertyValue(envDefinitionBuilder, envElement, "queryEndpoints", "queryEndpoints");
setPropertyValue(envDefinitionBuilder, envElement, "maxRequestLifetime", "maxRequestLifetime");
setPropertyValue(envDefinitionBuilder, envElement, "keepAliveInterval", "keepAliveInterval");
setPropertyValue(envDefinitionBuilder, envElement, "autoreleaseAfter", "autoreleaseAfter");
setPropertyValue(envDefinitionBuilder, envElement, "bufferPoolingEnabled", "bufferPoolingEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "tcpNodelayEnabled", "tcpNodelayEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "mutationTokensEnabled", "mutationTokensEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "analyticsTimeout", "analyticsTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "configPollInterval", "configPollInterval");
setPropertyValue(envDefinitionBuilder, envElement, "configPollFloorInterval", "configPollFloorInterval");
setPropertyValue(envDefinitionBuilder, envElement, "certAuthEnabled", "certAuthEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "operationTracingEnabled", "operationTracingEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "operationTracingServerDurationEnabled", "operationTracingServerDurationEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "orphanResponseReportingEnabled", "orphanResponseReportingEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "compressionMinSize", "compressionMinSize");
setPropertyValue(envDefinitionBuilder, envElement, "compressionMinRatio", "compressionMinRatio");
//retry strategy is particular, in the xsd this is an enum (FailFast, BestEffort)
setPropertyValue(envDefinitionBuilder, envElement, "retryStrategy", "retryStrategy");
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.util.StringUtils;
/**
* The XML parser for a {@link IndexManager} definition.
*
* @author Simon Baslé
*/
public class CouchbaseIndexManagerParser extends AbstractSingleBeanDefinitionParser {
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_INDEX_MANAGER;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return IndexManager.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param bean the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
//the following values default to false, so you must opt-in by providing them
boolean processViews = Boolean.parseBoolean(element.getAttribute("processViews"));
boolean processPrimary = Boolean.parseBoolean(element.getAttribute("processPrimary"));
boolean processSecondary = Boolean.parseBoolean(element.getAttribute("processSecondary"));
bean.addConstructorArgValue(processViews);
bean.addConstructorArgValue(processPrimary);
bean.addConstructorArgValue(processSecondary);
}
}

View File

@@ -1,96 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import com.couchbase.client.java.Bucket;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.couchbase.monitor.ClientInfo;
import org.springframework.data.couchbase.monitor.ClusterInfo;
import org.springframework.util.StringUtils;
/**
* Enables Parsing of the "<couchbase:jmx />" configuration bean.
* <p/>
* In order to enable JMX, different JmxComponents need to be registered. The dependency to the original
* {@link Bucket} object is solved through the "bucket-ref" attribute.
*
* @author Michael Nitschinger
* @author Simon Baslé
*/
public class CouchbaseJmxParser implements BeanDefinitionParser {
/**
* Parse the element and dispatch the registration of the JMX components.
*
* @param element the XML element which contains the attributes.
* @param parserContext encapsulates the parsing state and configuration.
* @return null, because no bean instance needs to be returned.
*/
public BeanDefinition parse(final Element element, final ParserContext parserContext) {
String bucketName = element.getAttribute("bucket-ref");
if (!StringUtils.hasText(bucketName)) {
bucketName = BeanNames.COUCHBASE_BUCKET;
}
registerJmxComponents(bucketName, element, parserContext);
return null;
}
/**
* Register the JMX components in the context.
*
* @param element the XML element which contains the attributes.
* @param parserContext encapsulates the parsing state and configuration.
* @parma refBucketName the reference name to the couchbase bucket.
*/
protected void registerJmxComponents(final String refBucketName,
final Element element, final ParserContext parserContext) {
Object eleSource = parserContext.extractSource(element);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
createBeanDefEntry(ClientInfo.class, compositeDef, refBucketName, eleSource, parserContext);
createBeanDefEntry(ClusterInfo.class, compositeDef, refBucketName, eleSource, parserContext);
parserContext.registerComponent(compositeDef);
}
/**
* Creates Bean Definitions for JMX components and adds them as a nested component.
*
* @param clazz the class type to register.
* @param compositeDef component that can hold nested components.
* @param refName the reference name to the couchbase bucket.
* @param eleSource source element to reference.
* @param parserContext encapsulates the parsing state and configuration.
*/
protected void createBeanDefEntry(final Class<?> clazz, final CompositeComponentDefinition compositeDef,
final String refName, final Object eleSource, final ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
builder.getRawBeanDefinition().setSource(eleSource);
builder.addConstructorArgReference(refName);
BeanDefinition assertDef = builder.getBeanDefinition();
String assertName = parserContext.getReaderContext().registerWithGeneratedName(assertDef);
compositeDef.addNestedComponent(new BeanComponentDefinition(assertDef, assertName));
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.data.couchbase.repository.config.CouchbaseRepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryBeanDefinitionParser;
/**
* {@link NamespaceHandler} for Couchbase configuration.
* <p/>
* This handler acts as a container for one or more bean parsers and registers them. During parsing, the elements
* get analyzed and the appropriate registered parser is called.
*
* @author Michael Nitschinger
*/
public class CouchbaseNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register bean definition parsers in the namespace handler.
*/
public final void init() {
CouchbaseRepositoryConfigurationExtension extension = new CouchbaseRepositoryConfigurationExtension();
registerBeanDefinitionParser("repositories", new RepositoryBeanDefinitionParser(extension));
registerBeanDefinitionParser("env", new CouchbaseEnvironmentParser());
registerBeanDefinitionParser("cluster", new CouchbaseClusterParser());
registerBeanDefinitionParser("clusterInfo", new CouchbaseClusterInfoParser());
registerBeanDefinitionParser("bucket", new CouchbaseBucketParser());
registerBeanDefinitionParser("jmx", new CouchbaseJmxParser());
registerBeanDefinitionParser("template", new CouchbaseTemplateParser());
registerBeanDefinitionParser("translation-service", new CouchbaseTranslationServiceParser());
registerBeanDefinitionParser("indexManager", new CouchbaseIndexManagerParser());
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.util.StringUtils;
/**
* Parser for "<couchbase:template />" bean definitions.
* <p/>
* The outcome of this bean definition parser will be a constructed {@link CouchbaseTemplate}.
*
* @author Michael Nitschinger
*/
public class CouchbaseTemplateParser extends AbstractSingleBeanDefinitionParser {
private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTemplateParser.class);
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_TEMPLATE;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseTemplate.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param bean the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
String clusterInfoRef = element.getAttribute("clusterInfo-ref");
String bucketRef = element.getAttribute("bucket-ref");
String converterRef = element.getAttribute("converter-ref");
String translationServiceRef = element.getAttribute("translation-service-ref");
bean.addConstructorArgReference(StringUtils.hasText(clusterInfoRef) ? clusterInfoRef : BeanNames.COUCHBASE_CLUSTER_INFO);
bean.addConstructorArgReference(StringUtils.hasText(bucketRef) ? bucketRef : BeanNames.COUCHBASE_BUCKET);
if (StringUtils.hasText(converterRef)) {
bean.addConstructorArgReference(converterRef);
}
if (StringUtils.hasText(translationServiceRef)) {
bean.addConstructorArgReference(translationServiceRef);
}
String consistencyValue = element.getAttribute("consistency");
if (consistencyValue != null) {
try {
Consistency consistency = Consistency.valueOf(consistencyValue);
bean.addPropertyValue("defaultConsistency", consistency);
} catch (IllegalArgumentException e) {
//bad consistency, leave default and log
LOGGER.warn("Parsed bad consistency value " + consistencyValue + " in xml template configuration, using default");
}
}
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2012-2020 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.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.util.StringUtils;
/**
* Enables Parsing of the "<couchbase:translation-service />" configuration bean.
*
* @author David Harrigan
*/
public class CouchbaseTranslationServiceParser extends AbstractSingleBeanDefinitionParser {
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return JacksonTranslationService.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param bean the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
final String objectMapper = element.getAttribute("objectMapper");
if (StringUtils.hasText(objectMapper)) {
bean.addPropertyReference("objectMapper", objectMapper);
}
else {
bean.addPropertyValue("objectMapper", new ObjectMapper());
}
}
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with (e.g., "couchbaseTranslationService")
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_TRANSLATION_SERVICE;
}
}

View File

@@ -1,5 +1,4 @@
/**
* This package contains all classes needed for specific configuration of
* Spring Data Couchbase.
* This package contains all classes needed for specific configuration of Spring Data Couchbase.
*/
package org.springframework.data.couchbase.config;

View File

@@ -24,16 +24,16 @@ import java.util.concurrent.TimeoutException;
*
* @author Michael Nitschinger
*/
public interface BucketCallback<T> {
public interface CollectionCallback<T> {
/**
* The enclosed body will be executed on the connected bucket.
*
* @return the result of the enclosed execution.
* @throws TimeoutException if the enclosed operation timed out.
* @throws ExecutionException if the result could not be retrieved because of a thrown exception before.
* @throws InterruptedException if the enclosed operation was interrupted.
*/
T doInBucket() throws TimeoutException, ExecutionException, InterruptedException;
/**
* The enclosed body will be executed on the connected bucket.
*
* @return the result of the enclosed execution.
* @throws TimeoutException if the enclosed operation timed out.
* @throws ExecutionException if the result could not be retrieved because of a thrown exception before.
* @throws InterruptedException if the enclosed operation was interrupted.
*/
T doInCollection() throws TimeoutException, ExecutionException, InterruptedException;
}

View File

@@ -25,14 +25,14 @@ import org.springframework.dao.DataIntegrityViolationException;
*/
public class CouchbaseDataIntegrityViolationException extends DataIntegrityViolationException {
private static final long serialVersionUID = -3724991479213025850L;
private static final long serialVersionUID = -3724991479213025850L;
public CouchbaseDataIntegrityViolationException(String msg) {
super(msg);
}
public CouchbaseDataIntegrityViolationException(String msg) {
super(msg);
}
public CouchbaseDataIntegrityViolationException(String msg, Throwable cause) {
super(msg, cause);
}
public CouchbaseDataIntegrityViolationException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -16,32 +16,9 @@
package org.springframework.data.couchbase.core;
import java.util.ConcurrentModificationException;
import java.util.concurrent.TimeoutException;
import com.couchbase.client.core.BackpressureException;
import com.couchbase.client.core.BucketClosedException;
import com.couchbase.client.core.DocumentConcurrentlyModifiedException;
import com.couchbase.client.core.ReplicaNotConfiguredException;
import com.couchbase.client.core.RequestCancelledException;
import com.couchbase.client.core.ServiceNotAvailableException;
import com.couchbase.client.core.config.ConfigurationException;
import com.couchbase.client.core.endpoint.SSLException;
import com.couchbase.client.core.endpoint.kv.AuthenticationException;
import com.couchbase.client.core.env.EnvironmentException;
import com.couchbase.client.core.state.NotConnectedException;
import com.couchbase.client.java.error.BucketDoesNotExistException;
import com.couchbase.client.java.error.CASMismatchException;
import com.couchbase.client.java.error.DesignDocumentException;
import com.couchbase.client.java.error.DocumentAlreadyExistsException;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.error.DurabilityException;
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;
@@ -52,6 +29,7 @@ import org.springframework.dao.QueryTimeoutException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import com.couchbase.client.core.error.*;
/**
* Simple {@link PersistenceExceptionTranslator} for Couchbase.
@@ -65,71 +43,59 @@ import org.springframework.dao.support.PersistenceExceptionTranslator;
*/
public class CouchbaseExceptionTranslator implements PersistenceExceptionTranslator {
/**
* Translate Couchbase specific exceptions to spring exceptions if possible.
*
* @param ex the exception to translate.
* @return the translated exception or null.
*/
@Override
public final DataAccessException translateExceptionIfPossible(final RuntimeException ex) {
/**
* Translate Couchbase specific exceptions to spring exceptions if possible.
*
* @param ex the exception to translate.
* @return the translated exception or null.
*/
@Override
public final DataAccessException translateExceptionIfPossible(final RuntimeException ex) {
if (ex instanceof InvalidPasswordException
|| ex instanceof NotConnectedException
|| ex instanceof ConfigurationException
|| ex instanceof EnvironmentException
|| ex instanceof InvalidPasswordException
|| ex instanceof SSLException
|| ex instanceof ServiceNotAvailableException
|| ex instanceof BucketClosedException
|| ex instanceof BucketDoesNotExistException
|| ex instanceof AuthenticationException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof ConfigException || ex instanceof ServiceNotAvailableException
|| ex instanceof CollectionNotFoundException || ex instanceof ScopeNotFoundException
|| ex instanceof BucketNotFoundException) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof DocumentAlreadyExistsException) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
if (ex instanceof DocumentExistsException) {
return new DuplicateKeyException(ex.getMessage(), ex);
}
if (ex instanceof DocumentDoesNotExistException) {
return new DataRetrievalFailureException(ex.getMessage(), ex);
}
if (ex instanceof DocumentNotFoundException) {
return new DataRetrievalFailureException(ex.getMessage(), ex);
}
if (ex instanceof CASMismatchException
|| ex instanceof DocumentConcurrentlyModifiedException
|| ex instanceof ReplicaNotConfiguredException
|| ex instanceof DurabilityException) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
if (ex instanceof CasMismatchException || ex instanceof ConcurrentModificationException
|| ex instanceof ReplicaNotConfiguredException || ex instanceof DurabilityLevelNotAvailableException
|| ex instanceof DurabilityImpossibleException || ex instanceof DurabilityAmbiguousException) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
if (ex instanceof RequestCancelledException
|| ex instanceof BackpressureException) {
return new OperationCancellationException(ex.getMessage(), ex);
}
if (ex instanceof RequestCanceledException) {
return new OperationCancellationException(ex.getMessage(), ex);
}
if (ex instanceof ViewDoesNotExistException
|| ex instanceof RequestTooBigException
|| ex instanceof DesignDocumentException) {
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
}
if (ex instanceof DesignDocumentNotFoundException || ex instanceof ValueTooLargeException) {
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
}
if (ex instanceof TemporaryLockFailureException
|| ex instanceof TemporaryFailureException) {
return new TransientDataAccessResourceException(ex.getMessage(), ex);
}
if (ex instanceof TemporaryFailureException || ex instanceof DocumentLockedException) {
return new TransientDataAccessResourceException(ex.getMessage(), ex);
}
if ((ex instanceof RuntimeException && ex.getCause() instanceof TimeoutException)) {
return new QueryTimeoutException(ex.getMessage(), ex);
}
if ((ex instanceof RuntimeException && ex.getCause() instanceof TimeoutException)) {
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);
}
if (ex instanceof EncodingFailureException || ex instanceof DecodingFailureException) {
// 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;
}
// Unable to translate exception, therefore just throw the original!
throw ex;
}
}

View File

@@ -16,400 +16,20 @@
package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.List;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.N1qlQueryResult;
import com.couchbase.client.java.query.N1qlParams;
import com.couchbase.client.java.query.Statement;
import com.couchbase.client.java.view.SpatialViewQuery;
import com.couchbase.client.java.view.SpatialViewResult;
import com.couchbase.client.java.view.ViewQuery;
import com.couchbase.client.java.view.ViewResult;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.KeySettings;
import org.springframework.data.couchbase.core.query.Consistency;
/**
* Defines common operations on the Couchbase data source, most commonly implemented by {@link CouchbaseTemplate}.
*
* @author Michael Nitschinger
* @author Simon Baslé
*/
public interface CouchbaseOperations {
public interface CouchbaseOperations extends FluentCouchbaseOperations {
/**
* Save the given object.
* <p/>
* <p>When the document already exists (specified by its unique id), then it will be overriden. Otherwise it will be
* created.</p>
*
* @param objectToSave the object to store in the bucket.
*/
void save(Object objectToSave);
CouchbaseConverter getConverter();
/**
* Save the given object.
* <p/>
* <p>When the document already exists (specified by its unique id), then it will be overriden. Otherwise it will be
* created.</p>
*
* @param objectToSave the object to store in the bucket.
*/
void save(Object objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
String getBucketName();
/**
* Save a list of objects.
* <p/>
* <p>When one of the documents already exists (specified by its unique id), then it will be overriden. Otherwise it
* will be created.</p>
*
* @param batchToSave the list of objects to store in the bucket.
*/
void save(Collection<?> batchToSave);
String getScopeName();
/**
* Save a list of objects.
* <p/>
* <p>When one of the documents already exists (specified by its unique id), then it will be overriden. Otherwise it
* will be created.</p>
*
* @param batchToSave the list of objects to store in the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void save(Collection<?> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Insert the given object.
* <p/>
* <p>When the document already exists (specified by its unique id), then it will not be overriden. Use the
* {@link CouchbaseOperations#save} method for this task.</p>
*
* @param objectToInsert the object to add to the bucket.
*/
void insert(Object objectToInsert);
/**
* Insert the given object.
* <p/>
* <p>When the document already exists (specified by its unique id), then it will not be overriden. Use the
* {@link CouchbaseOperations#save} method for this task.</p>
*
* @param objectToInsert the object to add to the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void insert(Object objectToInsert, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Insert a list of objects.
* <p/>
* <p>When one of the documents already exists (specified by its unique id), then it will not be overriden. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param batchToInsert the list of objects to add to the bucket.
*/
void insert(Collection<?> batchToInsert);
/**
* Insert a list of objects.
* <p/>
* <p>When one of the documents already exists (specified by its unique id), then it will not be overriden. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param batchToInsert the list of objects to add to the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void insert(Collection<?> batchToInsert, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Update the given object.
* <p/>
* <p>When the document does not exist (specified by its unique id) it will not be created. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param objectToUpdate the object to add to the bucket.
*/
void update(Object objectToUpdate);
/**
* Update the given object.
* <p/>
* <p>When the document does not exist (specified by its unique id) it will not be created. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param objectToUpdate the object to add to the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void update(Object objectToUpdate, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Insert a list of objects.
* <p/>
* <p>If one of the documents does not exist (specified by its unique id), then it will not be created. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param batchToUpdate the list of objects to add to the bucket.
*/
void update(Collection<?> batchToUpdate);
/**
* Insert a list of objects.
* <p/>
* <p>If one of the documents does not exist (specified by its unique id), then it will not be created. Use the
* {@link CouchbaseOperations#save} method for this.</p>
*
* @param batchToUpdate the list of objects to add to the bucket.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void update(Collection<?> batchToUpdate, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Find an object by its given Id and map it to the corresponding entity.
*
* @param id the unique ID of the document.
* @param entityClass the entity to map to.
* @return returns the found object or null otherwise.
*/
<T> T findById(String id, Class<T> entityClass);
/**
* Query a View for a list of documents of type T.
* <p/>
* <p>There is no need to {@link ViewQuery#includeDocs(boolean) set includeDocs} explicitly, since this method will
* manage the retrieval of documents internally. It is valid to pass in a empty constructed {@link ViewQuery} object.</p>
* <p/>
* <p>Weak consistency in the query (<code>stale(Stale.TRUE)</code>) can lead to some documents being unreachable due
* to their deletion not having been indexed. These deleted null documents are eliminated from the result of this
* method.</p>
* </p>
* <p>This method does not work with reduced views, because they by design do not contain references to original
* objects. Use the provided {@link #queryView} method for more flexibility and direct access.</p>
*
* @param query the Query object (also specifying view design document and view name).
* @param entityClass the entity to map to.
* @return the converted collection
*/
<T> List<T> findByView(ViewQuery query, Class<T> entityClass);
/**
* Query a View with direct access to the {@link ViewResult}.
* <p>This method is available to ease the working with views by still wrapping exceptions into the Spring
* infrastructure.</p>
* <p>It is especially needed if you want to run reduced viewName queries, because they can't be mapped onto entities
* directly.</p>
*
* @param query the Query object (also specifying view design document and view name).
* @return ViewResult containing the results of the query.
*/
ViewResult queryView(ViewQuery query);
/**
* Query a Spatial View for a list of documents of type T.
* </p>
* <p>There is no need to {@link SpatialViewQuery#includeDocs(boolean) set includeDocs} explicitly, since this method
* will manage the retrieval of documents internally. It is valid to pass in a empty constructed {@link SpatialViewQuery} object.</p>
* <p/>
* <p>Weak consistency in the query (<code>stale(Stale.TRUE)</code>) can lead to some documents being unreachable due
* to their deletion not having been indexed. These deleted null documents are eliminated from the result of this
* method.</p>
*
* @param query the SpatialViewQuery object (also specifying view design document and view name).
* @param entityClass the entity to map to.
* @return the converted collection
*/
<T> List<T> findBySpatialView(SpatialViewQuery query, Class<T> entityClass);
/**
* Query a Spatial View with direct access to the {@link SpatialViewResult}.
* <p>This method is available to ease the working with spatial views by still wrapping exceptions into the Spring
* infrastructure.</p>
*
* @param query the SpatialViewQuery object (also specifying view design document and view name).
* @return SpatialViewResult containing the results of the query.
*/
SpatialViewResult querySpatialView(SpatialViewQuery query);
/**
* Query the N1QL Service for JSON data of type T. Enough data to construct the full
* entity is expected to be selected, including the metadata (document id and cas), obtained through N1QL's query.
* <p>This is done via a {@link N1qlQuery} that contains a {@link Statement} and possibly
* additional query parameters ({@link N1qlParams}) and placeholder values if the
* statement contains placeholders.
* <br/>
* Use {@link N1qlQuery}'s factory methods to construct such a Query.</p>
* <p/>
* <p>Weak consistency in the query (eg. <code>ScanConsistency.NOT_BOUND</code>) can lead to some documents being
* unreachable due to their deletion not having been indexed. These deleted null documents are eliminated from the
* result of this method.</p>
*
* @param n1ql the N1QL query.
* @param entityClass the target class for the returned entities.
* @param <T> the entity class
* @return the list of entities matching this query.
* @throws CouchbaseQueryExecutionException if the id and cas are not selected.
*/
<T> List<T> findByN1QL(N1qlQuery n1ql, Class<T> entityClass);
/**
* Query the N1QL Service for partial JSON data of type T. The selected field will be
* used in a {@link TranslationService#decodeFragment(String, Class) straightforward decoding}
* (no document, metadata like id nor cas) to map to a "fragment class".
* <p>This is done via a {@link N1qlQuery} that contains a {@link Statement} and possibly
* additional query parameters ({@link N1qlParams}) and placeholder values if the
* statement contains placeholders.
* <br/>
* Use {@link N1qlQuery}'s factory methods to construct such a Query.</p>
* <p/>
* <p>Weak consistency in the query (eg. <code>ScanConsistency.NOT_BOUND</code>) can lead to some documents being
* unreachable due to their deletion not having been indexed. These deleted null documents are eliminated from the
* result of this method.</p>
*
* @param n1ql the N1QL query.
* @param fragmentClass the target class for the returned fragments.
* @param <T> the fragment class
* @return the list of entities matching this query.
*/
<T> List<T> findByN1QLProjection(N1qlQuery n1ql, Class<T> fragmentClass);
/**
* Query the N1QL Service with direct access to the {@link N1qlQueryResult}.
* <p>
* This is done via a {@link N1qlQuery} that can
* contain a {@link Statement}, additional query parameters ({@link N1qlParams})
* and placeholder values if the statement contains placeholders.</p>
* <p>
* Use {@link N1qlQuery}'s factory methods to construct this.</p>
*
* @param n1ql the N1QL query.
* @return {@link N1qlQueryResult} containing the results of the n1ql query.
*/
N1qlQueryResult queryN1QL(N1qlQuery n1ql);
/**
* Checks if the given document exists.
*
* @param id the unique ID of the document.
* @return whether the document could be found or not.
*/
boolean exists(String id);
/**
* Remove the given object from the bucket by id.
* <p/>
* If the object is a String, it will be treated as the document key
* directly.
*
* @param objectToRemove the Object to remove.
*/
void remove(Object objectToRemove);
/**
* Remove the given object from the bucket by id.
* <p/>
* If the object is a String, it will be treated as the document key
* directly.
*
* @param objectToRemove the Object to remove.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void remove(Object objectToRemove, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Remove a list of objects from the bucket by id.
*
* @param batchToRemove the list of Objects to remove.
*/
void remove(Collection<?> batchToRemove);
/**
* Remove a list of objects from the bucket by id.
*
* @param batchToRemove the list of Objects to remove.
* @param persistTo the persistence constraint setting.
* @param replicateTo the replication constraint setting.
*/
void remove(Collection<?> batchToRemove, PersistTo persistTo, ReplicateTo replicateTo);
/**
* Executes a BucketCallback translating any exceptions as necessary.
* <p/>
* Allows for returning a result object, that is a domain object or a collection of domain objects.
*
* @param action the action to execute in the callback.
* @param <T> the return type.
* @return the return type.
*/
<T> T execute(BucketCallback<T> action);
/**
* Returns the linked {@link Bucket} to this template.
*
* @return the client used for the template.
*/
Bucket getCouchbaseBucket();
/**
* Returns the {@link ClusterInfo} about the cluster linked to this template.
*
* @return the info about the cluster the template connects to.
*/
ClusterInfo getCouchbaseClusterInfo();
/**
* Returns the underlying {@link CouchbaseConverter}.
*
* @return CouchbaseConverter.
*/
CouchbaseConverter getConverter();
/**
* Returns the {@link Consistency consistency} parameter to be used by default for generated queries (views and N1QL)
* in repositories. Defaults to {@link Consistency#DEFAULT_CONSISTENCY}.
*
* @return the consistency to use for generated repository queries.
*/
Consistency getDefaultConsistency();
/**
* Add common key settings
*
* Throws {@link UnsupportedOperationException} if KeySettings is already set. It becomes immutable.
*
* @param settings {@link KeySettings}
*/
void keySettings(final KeySettings settings);
/**
* Get key settings associated with the template
*
* @return {@link KeySettings}
*/
KeySettings keySettings();
/**
* Get generated id - applies both using prefix and suffix through entity as well as template {@link KeySettings}
*
* ** NOTE: UNIQUE strategy will generate different ids each time ***
*
* @param entity the entity object.
* @return id the couchbase document key.
*/
String getGeneratedId(Object entity);
CouchbaseClientFactory getCouchbaseClientFactory();
}

View File

@@ -16,824 +16,126 @@
package org.springframework.data.couchbase.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
import com.couchbase.client.java.cluster.ClusterInfo;
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.DocumentAlreadyExistsException;
import com.couchbase.client.java.error.TranscodingException;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.N1qlQueryResult;
import com.couchbase.client.java.query.N1qlQueryRow;
import com.couchbase.client.java.util.features.CouchbaseFeature;
import com.couchbase.client.java.view.AsyncViewResult;
import com.couchbase.client.java.view.AsyncViewRow;
import com.couchbase.client.java.view.SpatialViewQuery;
import com.couchbase.client.java.view.SpatialViewResult;
import com.couchbase.client.java.view.SpatialViewRow;
import com.couchbase.client.java.view.ViewQuery;
import com.couchbase.client.java.view.ViewResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.convert.join.N1qlJoinResolver;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.mapping.CouchbaseStorable;
import org.springframework.data.couchbase.core.mapping.KeySettings;
import org.springframework.data.couchbase.core.query.N1qlJoin;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.util.TypeInformation;
import rx.Observable;
import rx.functions.Func1;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.event.AfterDeleteEvent;
import org.springframework.data.couchbase.core.mapping.event.AfterSaveEvent;
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
import org.springframework.data.couchbase.core.mapping.event.BeforeDeleteEvent;
import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_ID;
import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_CAS;
import com.couchbase.client.java.Collection;
public class CouchbaseTemplate implements CouchbaseOperations {
private final CouchbaseClientFactory clientFactory;
private final CouchbaseConverter converter;
private final PersistenceExceptionTranslator exceptionTranslator;
private final CouchbaseTemplateSupport templateSupport;
private final ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this.clientFactory = clientFactory;
this.converter = converter;
this.exceptionTranslator = clientFactory.getExceptionTranslator();
this.templateSupport = new CouchbaseTemplateSupport(converter);
this.reactiveCouchbaseTemplate = new ReactiveCouchbaseTemplate(clientFactory, converter);
}
@Override
public <T> ExecutableUpsertById<T> upsertById(final Class<T> domainType) {
return new ExecutableUpsertByIdOperationSupport(this).upsertById(domainType);
}
@Override
public <T> ExecutableInsertById<T> insertById(Class<T> domainType) {
return new ExecutableInsertByIdOperationSupport(this).insertById(domainType);
}
@Override
public <T> ExecutableReplaceById<T> replaceById(Class<T> domainType) {
return new ExecutableReplaceByIdOperationSupport(this).replaceById(domainType);
}
@Override
public <T> ExecutableFindById<T> findById(Class<T> domainType) {
return new ExecutableFindByIdOperationSupport(this).findById(domainType);
}
@Override
public <T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ExecutableFindFromReplicasByIdOperationSupport(this).findFromReplicasById(domainType);
}
@Override
public <T> ExecutableFindByQuery<T> findByQuery(Class<T> domainType) {
return new ExecutableFindByQueryOperationSupport(this).findByQuery(domainType);
}
@Override
public <T> ExecutableFindByAnalytics<T> findByAnalytics(Class<T> domainType) {
return new ExecutableFindByAnalyticsOperationSupport(this).findByAnalytics(domainType);
}
@Override
public ExecutableRemoveById removeById() {
return new ExecutableRemoveByIdOperationSupport(this).removeById();
}
@Override
public ExecutableExistsById existsById() {
return new ExecutableExistsByIdOperationSupport(this).existsById();
}
@Override
public <T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ExecutableRemoveByQueryOperationSupport(this).removeByQuery(domainType);
}
@Override
public String getBucketName() {
return clientFactory.getBucket().name();
}
@Override
public String getScopeName() {
return clientFactory.getScope().name();
}
@Override
public CouchbaseClientFactory getCouchbaseClientFactory() {
return clientFactory;
}
/**
* Provides access to a {@link Collection} on the configured {@link CouchbaseClientFactory}.
*
* @param collectionName the name of the collection, if null is passed in the default collection is assumed.
* @return the collection instance.
*/
public Collection getCollection(final String collectionName) {
return clientFactory.getCollection(collectionName);
}
@Override
public CouchbaseConverter getConverter() {
return converter;
}
CouchbaseTemplateSupport support() {
return templateSupport;
}
public ReactiveCouchbaseTemplate reactive() {
return reactiveCouchbaseTemplate;
}
/**
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
* exception if the conversation failed. Thus allows safe re-throwing of the return value.
*
* @param ex the exception to translate
*/
RuntimeException potentiallyConvertRuntimeException(RuntimeException ex) {
RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
return resolved == null ? ex : resolved;
}
/**
* @author Michael Nitschinger
* @author Oliver Gierke
* @author Simon Baslé
* @author Young-Gu Chae
* @author Mark Paluch
* @author Tayeb Chlyah
*/
public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventPublisherAware {
private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTemplate.class);
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
private static final Collection<String> ITERABLE_CLASSES;
static {
final Set<String> iterableClasses = new HashSet<String>();
iterableClasses.add(List.class.getName());
iterableClasses.add(Collection.class.getName());
iterableClasses.add(Iterator.class.getName());
ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses);
}
private final Bucket client;
private final CouchbaseConverter converter;
private final TranslationService translationService;
private final ClusterInfo clusterInfo;
private KeySettings keySettings;
private ApplicationEventPublisher eventPublisher;
private WriteResultChecking writeResultChecking = DEFAULT_WRITE_RESULT_CHECKING;
private PersistenceExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator();
protected final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
//default value is in case the template isn't constructed through configuration mechanisms that use the setter.
private Consistency configuredConsistency = Consistency.DEFAULT_CONSISTENCY;
public CouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client) {
this(clusterInfo, client, null, null);
}
public CouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client, final TranslationService translationService) {
this(clusterInfo, client, null, translationService);
}
public CouchbaseTemplate(final ClusterInfo clusterInfo,
final Bucket client,
final CouchbaseConverter converter,
final TranslationService translationService) {
this.clusterInfo = clusterInfo;
this.client = client;
this.converter = converter == null ? getDefaultConverter() : converter;
this.translationService = translationService == null ? getDefaultTranslationService() : translationService;
this.mappingContext = this.converter.getMappingContext();
}
private TranslationService getDefaultTranslationService() {
JacksonTranslationService t = new JacksonTranslationService();
t.afterPropertiesSet();
return t;
}
private CouchbaseConverter getDefaultConverter() {
MappingCouchbaseConverter c = new MappingCouchbaseConverter(new CouchbaseMappingContext());
c.afterPropertiesSet();
return c;
}
/**
* Encode a {@link CouchbaseDocument} into a storable representation (JSON) then prepare
* it for storage as a {@link Document}.
*/
private Document<String> encodeAndWrap(final CouchbaseDocument source, Long version) {
String encodedContent = translationService.encode(source);
if (version == null) {
return RawJsonDocument.create(source.getId(), source.getExpiration(), encodedContent);
}
else {
return RawJsonDocument.create(source.getId(), source.getExpiration(), encodedContent, version);
}
}
/**
* Decode a {@link Document Document&lt;String&gt;} containing a JSON string
* into a {@link CouchbaseStorable}
*/
private CouchbaseStorable decodeAndUnwrap(final Document<String> source, final CouchbaseStorable target) {
//TODO at some point the necessity of CouchbaseStorable should be re-evaluated
return translationService.decode(source.content(), target);
}
/**
* Make sure the given object is not a iterable.
*
* @param o the object to verify.
*/
protected static void ensureNotIterable(Object o) {
if (null != o) {
if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) {
throw new IllegalArgumentException("Cannot use a collection here.");
}
}
}
/**
* Handle write errors according to the set {@link #writeResultChecking} setting.
*
* @param message the message to use.
*/
private void handleWriteResultError(String message, Exception cause) {
if (writeResultChecking == WriteResultChecking.NONE) {
return;
}
if (writeResultChecking == WriteResultChecking.EXCEPTION) {
throw new CouchbaseDataIntegrityViolationException(message, cause);
}
else {
LOGGER.error(message, cause);
}
}
/**
* Configures the WriteResultChecking to be used with the template. Setting null will reset
* the default of DEFAULT_WRITE_RESULT_CHECKING. This can be configured to capture couchbase
* specific exceptions like Temporary failure, Authentication failure..
*
* @param writeResultChecking the setting to use.
*/
public void setWriteResultChecking(WriteResultChecking writeResultChecking) {
this.writeResultChecking = writeResultChecking == null ? DEFAULT_WRITE_RESULT_CHECKING : writeResultChecking;
}
@Override
public void setApplicationEventPublisher(final ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
/**
* Helper method to publish an event if the event publisher is set.
*
* @param event the event to emit.
* @param <T> the enclosed type.
*/
protected <T> void maybeEmitEvent(final CouchbaseMappingEvent<T> event) {
if (eventPublisher != null) {
eventPublisher.publishEvent(event);
}
}
@Override
public void save(Object objectToSave) {
save(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void save(Object objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToSave, persistTo, replicateTo, PersistType.SAVE);
}
@Override
public void save(Collection<?> batchToSave) {
save(batchToSave, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void save(Collection<?> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToSave) {
doPersist(o, persistTo, replicateTo, PersistType.SAVE);
}
}
@Override
public void insert(Object objectToInsert) {
insert(objectToInsert, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void insert(Object objectToInsert, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToInsert, persistTo, replicateTo, PersistType.INSERT);
}
@Override
public void insert(Collection<?> batchToInsert) {
insert(batchToInsert, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void insert(Collection<?> batchToInsert, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToInsert) {
doPersist(o, persistTo, replicateTo, PersistType.INSERT);
}
}
@Override
public void update(Object objectToUpdate) {
update(objectToUpdate, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void update(Object objectToUpdate, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToUpdate, persistTo, replicateTo, PersistType.UPDATE);
}
@Override
public void update(Collection<?> batchToUpdate) {
update(batchToUpdate, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void update(Collection<?> batchToUpdate, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToUpdate) {
doPersist(o, persistTo, replicateTo, PersistType.UPDATE);
}
}
@Override
public <T> T findById(final String id, Class<T> entityClass) {
final CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityClass);
RawJsonDocument result = execute(new BucketCallback<RawJsonDocument>() {
@Override
public RawJsonDocument doInBucket() {
if (entity.isTouchOnRead()) {
return client.getAndTouch(id, entity.getExpiry(), RawJsonDocument.class);
} else {
return client.get(id, RawJsonDocument.class);
}
}
});
return mapToEntity(id, result, entityClass);
}
@Override
public <T> List<T> findByView(ViewQuery query, final Class<T> entityClass) {
//we'll always need to get documents, as a RawJsonDocument, so we should force that target class
//so that the caller doesn't set a bad target class unintentionally, pre-loading with a bad type.
if (!query.isIncludeDocs() || !query.includeDocsTarget().equals(RawJsonDocument.class)) {
if (query.isOrderRetained()) {
query.includeDocsOrdered(RawJsonDocument.class);
} else {
query.includeDocs(RawJsonDocument.class);
}
}
//we'll always map the document to the entity, hence reduce never makes sense.
query.reduce(false);
return executeAsync(client.async().query(query))
.flatMap(new Func1<AsyncViewResult, Observable<AsyncViewRow>>() {
@Override
public Observable<AsyncViewRow> call(AsyncViewResult asyncViewResult) {
return asyncViewResult
.error()
.flatMap(new Func1<JsonObject, Observable<AsyncViewRow>>() {
@Override
public Observable<AsyncViewRow> call(JsonObject error) {
return Observable.error(new CouchbaseQueryExecutionException("Unable to execute view query due to the following view error: " + error.toString()));
}})
.switchIfEmpty(asyncViewResult.rows());
}
})
.flatMap(new Func1<AsyncViewRow, Observable<T>>() {
@Override
public Observable<T> call(AsyncViewRow row) {
final String id = row.id();
return row
.document(RawJsonDocument.class)
.map(new Func1<RawJsonDocument, T>() {
@Override
public T call(RawJsonDocument rawJsonDocument) {
//cope with potential weak consistency and deletions
T entity = mapToEntity(id, rawJsonDocument, entityClass);
return entity;
}
});
}})
.filter(new Func1<T, Boolean>() {
@Override
public Boolean call(T t) {
return t != null;
}
})
.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
@Override
public Observable<T> call(Throwable throwable) {
if (throwable instanceof TranscodingException) {
return Observable.error(new CouchbaseQueryExecutionException("Unable to execute view query", throwable));
} else {
return Observable.error(throwable);
}
}
})
.toList()
.toBlocking()
.single();
}
@Override
public ViewResult queryView(final ViewQuery query) {
return execute(new BucketCallback<ViewResult>() {
@Override
public ViewResult doInBucket() {
return client.query(query);
}
});
}
@Override
public <T> List<T> findBySpatialView(SpatialViewQuery query, Class<T> entityClass) {
//we'll always need to get documents, as a RawJsonDocument, so we should force includeDocs(false)
//so that the caller doesn't set a bad target class unintentionally, pre-loading with a bad type.
query.includeDocs(false);
try {
final SpatialViewResult response = querySpatialView(query);
if (response.error() != null) {
throw new CouchbaseQueryExecutionException("Unable to execute spatial view query due to the following view error: " +
response.error().toString());
}
List<SpatialViewRow> allRows = response.allRows();
final List<T> result = new ArrayList<T>(allRows.size());
for (final SpatialViewRow row : allRows) {
//cope with potential weak consistency and deletions
T entity = mapToEntity(row.id(), row.document(RawJsonDocument.class), entityClass);
if (entity != null) {
result.add(entity);
}
}
return result;
}
catch (TranscodingException e) {
throw new CouchbaseQueryExecutionException("Unable to execute view query", e);
}
}
@Override
public SpatialViewResult querySpatialView(final SpatialViewQuery query) {
return execute(new BucketCallback<SpatialViewResult>() {
@Override
public SpatialViewResult doInBucket() throws TimeoutException, ExecutionException, InterruptedException {
return client.query(query);
}
});
}
@Override
public <T> List<T> findByN1QL(N1qlQuery n1ql, Class<T> entityClass) {
checkN1ql();
try {
N1qlQueryResult queryResult = queryN1QL(n1ql);
if (queryResult.finalSuccess()) {
List<N1qlQueryRow> allRows = queryResult.allRows();
List<T> result = new ArrayList<T>(allRows.size());
for (N1qlQueryRow row : allRows) {
JsonObject json = row.value();
String id = json.getString(SELECT_ID);
Long cas = json.getLong(SELECT_CAS);
if (id == null || cas == null) {
throw new CouchbaseQueryExecutionException("Unable to retrieve enough metadata for N1QL to entity mapping, " +
"have you selected " + SELECT_ID + " and " + SELECT_CAS + "?");
}
json = json.removeKey(SELECT_ID).removeKey(SELECT_CAS);
RawJsonDocument entityDoc = RawJsonDocument.create(id, json.toString(), cas);
T decoded = mapToEntity(id, entityDoc, 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
public <T> List<T> findByN1QLProjection(N1qlQuery n1ql, Class<T> entityClass) {
checkN1ql();
try {
N1qlQueryResult queryResult = queryN1QL(n1ql);
if (queryResult.finalSuccess()) {
List<N1qlQueryRow> allRows = queryResult.allRows();
List<T> result = new ArrayList<T>(allRows.size());
for (N1qlQueryRow row : allRows) {
JsonObject json = row.value();
T decoded = translationService.decodeFragment(json.toString(), 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
public N1qlQueryResult queryN1QL(final N1qlQuery query) {
checkN1ql();
return execute(new BucketCallback<N1qlQueryResult>() {
@Override
public N1qlQueryResult doInBucket() throws TimeoutException, ExecutionException, InterruptedException {
return client.query(query);
}
});
}
@Override
public boolean exists(final String id) {
return execute(new BucketCallback<Boolean>() {
@Override
public Boolean doInBucket() throws TimeoutException, ExecutionException, InterruptedException {
return client.exists(id);
}
});
}
@Override
public void remove(Object objectToRemove) {
remove(objectToRemove, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void remove(Object objectToRemove, PersistTo persistTo, ReplicateTo replicateTo) {
doRemove(objectToRemove, persistTo, replicateTo);
}
@Override
public void remove(Collection<?> batchToRemove) {
remove(batchToRemove, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public void remove(Collection<?> batchToRemove, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToRemove) {
doRemove(o, persistTo, replicateTo);
}
}
@Override
public <T> T execute(BucketCallback<T> action) {
try {
return action.doInBucket();
}
catch (RuntimeException e) {
throw exceptionTranslator.translateExceptionIfPossible(e);
}
catch (TimeoutException e) {
throw new QueryTimeoutException(e.getMessage(), e);
}
catch (InterruptedException e) {
throw new OperationInterruptedException(e.getMessage(), e);
}
catch (ExecutionException e) {
throw new OperationInterruptedException(e.getMessage(), e);
}
}
public <T> Observable<T> executeAsync(Observable<T> asyncAction) {
return asyncAction
.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
@Override
public Observable<T> call(Throwable e) {
if (e instanceof RuntimeException) {
return Observable.error(exceptionTranslator.translateExceptionIfPossible((RuntimeException) e));
} else if (e instanceof TimeoutException) {
return Observable.error(new QueryTimeoutException(e.getMessage(), e));
} else if (e instanceof InterruptedException) {
return Observable.error(new OperationInterruptedException(e.getMessage(), e));
} else if (e instanceof ExecutionException) {
return Observable.error(new OperationInterruptedException(e.getMessage(), e));
} else {
return Observable.error(e);
}
}
});
}
private void doPersist(Object objectToPersist, final PersistTo persistTo, final ReplicateTo replicateTo,
final PersistType persistType) {
ensureNotIterable(objectToPersist);
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(objectToPersist);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(objectToPersist.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
final Long version = versionProperty != null ? accessor.getProperty(versionProperty, Long.class) : null;
maybeEmitEvent(new BeforeConvertEvent<Object>(objectToPersist));
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(objectToPersist, converted);
maybeEmitEvent(new BeforeSaveEvent<Object>(objectToPersist, converted));
execute(new BucketCallback<Boolean>() {
@Override
public Boolean doInBucket() throws InterruptedException, ExecutionException {
String generatedId = addCommonPrefixAndSuffix(converted.getId());
converted.setId(generatedId);
Document<String> doc = encodeAndWrap(converted, version);
Document<String> storedDoc;
//We will check version only if required
boolean versionPresent = versionProperty != null;
//If version is not set - assumption that document is new, otherwise updating
boolean existingDocument = version != null && version > 0L;
try {
switch (persistType) {
case SAVE:
if (!versionPresent) {
//No version field - no cas
storedDoc = client.upsert(doc, persistTo, replicateTo);
} else if (existingDocument) {
//Updating existing document with cas
storedDoc = client.replace(doc, persistTo, replicateTo);
} else {
//Creating new document
storedDoc = client.insert(doc, persistTo, replicateTo);
}
break;
case UPDATE:
storedDoc = client.replace(doc, persistTo, replicateTo);
break;
case INSERT:
default:
storedDoc = client.insert(doc, persistTo, replicateTo);
break;
}
CouchbasePersistentProperty idProperty = persistentEntity.getIdProperty();
Object entityId = accessor.getProperty(idProperty);
if (!generatedId.equals(entityId)) {
accessor.setProperty(idProperty, generatedId);
}
if (storedDoc != null && storedDoc.cas() != 0) {
//inject new cas into the bean
if (versionProperty != null) {
accessor.setProperty(versionProperty, storedDoc.cas());
}
return true;
}
return false;
} catch (DocumentAlreadyExistsException e) {
throw new OptimisticLockingFailureException(persistType.getSpringDataOperationName() +
" document with version value failed: " + version, e);
} catch (CASMismatchException e) {
throw new OptimisticLockingFailureException(persistType.getSpringDataOperationName() +
" document with version value failed: " + version, e);
} catch (Exception e) {
handleWriteResultError(persistType.getSpringDataOperationName() + " document failed: " + e.getMessage(), e);
return false; //this could be skipped if WriteResultChecking.EXCEPTION
}
}
});
maybeEmitEvent(new AfterSaveEvent<Object>(objectToPersist, converted));
}
private void doRemove(final Object objectToRemove, final PersistTo persistTo, final ReplicateTo replicateTo) {
ensureNotIterable(objectToRemove);
maybeEmitEvent(new BeforeDeleteEvent<Object>(objectToRemove));
if (objectToRemove instanceof String) {
execute(new BucketCallback<Boolean>() {
@Override
public Boolean doInBucket() throws InterruptedException, ExecutionException {
try {
RawJsonDocument deletedDoc = client.remove((String) objectToRemove , persistTo, replicateTo, RawJsonDocument.class);
return deletedDoc != null;
} catch (Exception e) {
handleWriteResultError("Delete document failed: " + e.getMessage(), e);
return false; //this could be skipped if WriteResultChecking.EXCEPTION
}
}
});
maybeEmitEvent(new AfterDeleteEvent<Object>(objectToRemove));
return;
}
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(objectToRemove, converted);
execute(new BucketCallback<Boolean>() {
@Override
public Boolean doInBucket() {
try {
RawJsonDocument deletedDoc = client.remove(addCommonPrefixAndSuffix(converted.getId()), persistTo, replicateTo
, RawJsonDocument.class);
return deletedDoc != null;
} catch (Exception e) {
handleWriteResultError("Delete document failed: " + e.getMessage(), e);
return false; //this could be skipped if WriteResultChecking.EXCEPTION
}
}
});
maybeEmitEvent(new AfterDeleteEvent<Object>(objectToRemove));
}
private <T> T mapToEntity(String id, Document<String> data, Class<T> entityClass) {
if (data == null) {
return null;
}
final CouchbaseDocument converted = new CouchbaseDocument(id);
T readEntity = converter.read(entityClass, (CouchbaseDocument) decodeAndUnwrap(data, converted));
final ConvertingPropertyAccessor<T> accessor = getPropertyAccessor(readEntity);
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(readEntity.getClass());
if (persistentEntity.getVersionProperty() != null) {
accessor.setProperty(persistentEntity.getVersionProperty(), data.cas());
}
persistentEntity.doWithProperties((PropertyHandler<CouchbasePersistentProperty>) prop -> {
if (prop.isAnnotationPresent(N1qlJoin.class)) {
N1qlJoin definition = prop.findAnnotation(N1qlJoin.class);
TypeInformation type = prop.getTypeInformation().getActualType();
Class clazz = type.getType();
N1qlJoinResolver.N1qlJoinResolverParameters parameters = new N1qlJoinResolver.N1qlJoinResolverParameters(definition, id, persistentEntity.getTypeInformation(), type);
if (N1qlJoinResolver.isLazyJoin(definition)) {
N1qlJoinResolver.N1qlJoinProxy proxy = new N1qlJoinResolver.N1qlJoinProxy(this, parameters);
accessor.setProperty(prop, java.lang.reflect.Proxy.newProxyInstance(List.class.getClassLoader(),
new Class[]{List.class}, proxy));
} else {
accessor.setProperty(prop, N1qlJoinResolver.doResolve(this, parameters, clazz));
}
}
});
return accessor.getBean();
}
private final <T> ConvertingPropertyAccessor<T> getPropertyAccessor(T source) {
CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(source.getClass());
PersistentPropertyAccessor<T> accessor = entity.getPropertyAccessor(source);
return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService());
}
private void checkN1ql() {
if (!getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL)) {
throw new UnsupportedCouchbaseFeatureException("Detected usage of N1QL in template, which is unsupported on this cluster",
CouchbaseFeature.N1QL);
}
}
@Override
public Bucket getCouchbaseBucket() {
return this.client;
}
@Override
public ClusterInfo getCouchbaseClusterInfo() {
return this.clusterInfo;
}
@Override
public CouchbaseConverter getConverter() {
return this.converter;
}
@Override
public Consistency getDefaultConsistency() {
return configuredConsistency;
}
public void setDefaultConsistency(Consistency consistency) {
this.configuredConsistency = consistency;
}
private enum PersistType {
SAVE("Save", "Upsert"),
INSERT("Insert", "Insert"),
UPDATE("Update", "Replace");
private final String sdkOperationName;
private final String springDataOperationName;
PersistType(String sdkOperationName, String springDataOperationName) {
this.sdkOperationName = sdkOperationName;
this.springDataOperationName = springDataOperationName;
}
public String getSdkOperationName() {
return sdkOperationName;
}
public String getSpringDataOperationName() {
return springDataOperationName;
}
}
@Override
public void keySettings(KeySettings settings) {
if (this.keySettings != null) {
throw new UnsupportedOperationException("Key settings is already set, it is no longer mutable");
}
this.keySettings = settings;
}
@Override
public KeySettings keySettings() {
return this.keySettings;
}
@Override
public String getGeneratedId(Object entity) {
ensureNotIterable(entity);
CouchbaseDocument converted = new CouchbaseDocument();
converter.write(entity, converted);
return addCommonPrefixAndSuffix(converted.getId());
}
private String addCommonPrefixAndSuffix(final String id) {
String convertedKey = id;
if (this.keySettings == null) {
return id;
}
String prefix = this.keySettings.prefix();
String delimiter = this.keySettings.delimiter();
String suffix = this.keySettings.suffix();
if (prefix != null && !prefix.equals("")) {
convertedKey = prefix + delimiter + convertedKey;
}
if (suffix != null && !suffix.equals("")) {
convertedKey = convertedKey + delimiter + suffix;
}
return convertedKey;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2012-2020 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.core;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
public class CouchbaseTemplateSupport {
private final CouchbaseConverter converter;
private final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
// TODO: this should be replaced I think
private final TranslationService translationService;
public CouchbaseTemplateSupport(final CouchbaseConverter converter) {
this.converter = converter;
this.mappingContext = converter.getMappingContext();
this.translationService = new JacksonTranslationService();
}
public CouchbaseDocument encodeEntity(final Object entityToEncode) {
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(entityToEncode, converted);
return converted;
}
public <T> T decodeEntity(String id, String source, long cas, Class<T> entityClass) {
final CouchbaseDocument converted = new CouchbaseDocument(id);
converted.setId(id);
T readEntity = converter.read(entityClass, (CouchbaseDocument) translationService.decode(source, converted));
final ConvertingPropertyAccessor<T> accessor = getPropertyAccessor(readEntity);
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(readEntity.getClass());
if (persistentEntity.getVersionProperty() != null) {
accessor.setProperty(persistentEntity.getVersionProperty(), cas);
}
return accessor.getBean();
}
public void applyUpdatedCas(final Object entity, final long cas) {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
if (versionProperty != null) {
accessor.setProperty(versionProperty, cas);
}
}
public String getJavaNameForEntity(final Class<?> clazz) {
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
return info.getJavaType().getName();
}
private <T> ConvertingPropertyAccessor<T> getPropertyAccessor(final T source) {
CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(source.getClass());
PersistentPropertyAccessor<T> accessor = entity.getPropertyAccessor(source);
return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService());
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import java.util.Map;
public interface ExecutableExistsByIdOperation {
ExecutableExistsById existsById();
interface TerminatingExistsById {
boolean one(String id);
Map<String, Boolean> all(Collection<String> ids);
}
interface ExistsByIdWithCollection extends TerminatingExistsById {
TerminatingExistsById inCollection(String collection);
}
interface ExecutableExistsById extends ExistsByIdWithCollection {}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import java.util.Map;
import org.springframework.util.Assert;
public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByIdOperation {
private final CouchbaseTemplate template;
ExecutableExistsByIdOperationSupport(CouchbaseTemplate template) {
this.template = template;
}
@Override
public ExecutableExistsById existsById() {
return new ExecutableExistsByIdSupport(template, null);
}
static class ExecutableExistsByIdSupport implements ExecutableExistsById {
private final CouchbaseTemplate template;
private final ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport reactiveSupport;
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final String collection) {
this.template = template;
this.reactiveSupport = new ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport(template.reactive(),
collection);
}
@Override
public boolean one(final String id) {
return reactiveSupport.one(id).block();
}
@Override
public Map<String, Boolean> all(final Collection<String> ids) {
return reactiveSupport.all(ids).block();
}
@Override
public TerminatingExistsById inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableExistsByIdSupport(template, collection);
}
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2012-2020 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.core;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
import org.springframework.lang.Nullable;
public interface ExecutableFindByAnalyticsOperation {
<T> ExecutableFindByAnalytics<T> findByAnalytics(Class<T> domainType);
interface TerminatingFindByAnalytics<T> {
/**
* Get exactly zero or one result.
*
* @return {@link Optional#empty()} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
default Optional<T> one() {
return Optional.ofNullable(oneValue());
}
/**
* Get exactly zero or one result.
*
* @return {@literal null} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
@Nullable
T oneValue();
/**
* Get the first or no result.
*
* @return {@link Optional#empty()} if no match found.
*/
default Optional<T> first() {
return Optional.ofNullable(firstValue());
}
/**
* Get the first or no result.
*
* @return {@literal null} if no match found.
*/
@Nullable
T firstValue();
/**
* Get all matching elements.
*
* @return never {@literal null}.
*/
List<T> all();
/**
* Stream all matching elements.
*
* @return a {@link Stream} of results. Never {@literal null}.
*/
Stream<T> stream();
/**
* Get the number of matching elements.
*
* @return total number of matching elements.
*/
long count();
/**
* Check for the presence of matching elements.
*
* @return {@literal true} if at least one matching element exists.
*/
boolean exists();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindByAnalyticsWithQuery<T> extends TerminatingFindByAnalytics<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindByAnalytics}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindByAnalytics<T> matching(AnalyticsQuery query);
}
interface ExecutableFindByAnalytics<T> extends FindByAnalyticsWithQuery<T> {}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2012-2020 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.core;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFindByAnalyticsOperation {
private static final AnalyticsQuery ALL_QUERY = new AnalyticsQuery();
private final CouchbaseTemplate template;
public ExecutableFindByAnalyticsOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
return new ExecutableFindByAnalyticsSupport<>(template, domainType, ALL_QUERY);
}
static class ExecutableFindByAnalyticsSupport<T> implements ExecutableFindByAnalytics<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport<T> reactiveSupport;
ExecutableFindByAnalyticsSupport(final CouchbaseTemplate template, final Class<T> domainType,
final AnalyticsQuery query) {
this.template = template;
this.domainType = domainType;
this.reactiveSupport = new ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport<>(
template.reactive(), domainType, query);
}
@Override
public T oneValue() {
return reactiveSupport.one().block();
}
@Override
public T firstValue() {
return reactiveSupport.first().block();
}
@Override
public List<T> all() {
return reactiveSupport.all().collectList().block();
}
@Override
public TerminatingFindByAnalytics<T> matching(final AnalyticsQuery query) {
return new ExecutableFindByAnalyticsSupport<>(template, domainType, query);
}
@Override
public Stream<T> stream() {
return reactiveSupport.all().toStream();
}
@Override
public long count() {
return reactiveSupport.count().block();
}
@Override
public boolean exists() {
return count() > 0;
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
public interface ExecutableFindByIdOperation {
<T> ExecutableFindById<T> findById(Class<T> domainType);
interface TerminatingFindById<T> {
T one(String id);
Collection<? extends T> all(Collection<String> ids);
}
interface FindByIdWithCollection<T> extends TerminatingFindById<T> {
TerminatingFindById<T> inCollection(String collection);
}
interface FindByIdWithProjection<T> extends FindByIdWithCollection<T> {
FindByIdWithCollection<T> project(String... fields);
}
interface ExecutableFindById<T> extends FindByIdWithProjection<T> {}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.util.Assert;
public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOperation {
private final CouchbaseTemplate template;
ExecutableFindByIdOperationSupport(CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableFindById<T> findById(Class<T> domainType) {
return new ExecutableFindByIdSupport<>(template, domainType, null, null);
}
static class ExecutableFindByIdSupport<T> implements ExecutableFindById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final List<String> fields;
private final ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport<T> reactiveSupport;
ExecutableFindByIdSupport(CouchbaseTemplate template, Class<T> domainType, String collection, List<String> fields) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.fields = fields;
this.reactiveSupport = new ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport<>(template.reactive(),
domainType, collection, fields);
}
@Override
public T one(final String id) {
return reactiveSupport.one(id).block();
}
@Override
public Collection<? extends T> all(final Collection<String> ids) {
return reactiveSupport.all(ids).collectList().block();
}
@Override
public TerminatingFindById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, collection, fields);
}
@Override
public FindByIdWithCollection<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields));
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2012-2020 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.core;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.lang.Nullable;
import com.couchbase.client.java.query.QueryScanConsistency;
public interface ExecutableFindByQueryOperation {
<T> ExecutableFindByQuery<T> findByQuery(Class<T> domainType);
interface TerminatingFindByQuery<T> {
/**
* Get exactly zero or one result.
*
* @return {@link Optional#empty()} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
default Optional<T> one() {
return Optional.ofNullable(oneValue());
}
/**
* Get exactly zero or one result.
*
* @return {@literal null} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
@Nullable
T oneValue();
/**
* Get the first or no result.
*
* @return {@link Optional#empty()} if no match found.
*/
default Optional<T> first() {
return Optional.ofNullable(firstValue());
}
/**
* Get the first or no result.
*
* @return {@literal null} if no match found.
*/
@Nullable
T firstValue();
/**
* Get all matching elements.
*
* @return never {@literal null}.
*/
List<T> all();
/**
* Stream all matching elements.
*
* @return a {@link Stream} of results. Never {@literal null}.
*/
Stream<T> stream();
/**
* Get the number of matching elements.
*
* @return total number of matching elements.
*/
long count();
/**
* Check for the presence of matching elements.
*
* @return {@literal true} if at least one matching element exists.
*/
boolean exists();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindByQueryWithQuery<T> extends TerminatingFindByQuery<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindByQuery}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindByQuery<T> matching(Query query);
}
interface FindByQueryConsistentWith<T> extends FindByQueryWithQuery<T> {
FindByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface ExecutableFindByQuery<T> extends FindByQueryConsistentWith<T> {}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2012-2020 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.core;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQueryOperation {
private static final Query ALL_QUERY = new Query();
private final CouchbaseTemplate template;
public ExecutableFindByQueryOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableFindByQuery<T> findByQuery(final Class<T> domainType) {
return new ExecutableFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED);
}
static class ExecutableFindByQuerySupport<T> implements ExecutableFindByQuery<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final Query query;
private final ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport<T> reactiveSupport;
private final QueryScanConsistency scanConsistency;
ExecutableFindByQuerySupport(final CouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.reactiveSupport = new ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport<T>(template.reactive(),
domainType, query, scanConsistency);
this.scanConsistency = scanConsistency;
}
@Override
public T oneValue() {
return reactiveSupport.one().block();
}
@Override
public T firstValue() {
return reactiveSupport.first().block();
}
@Override
public List<T> all() {
return reactiveSupport.all().collectList().block();
}
@Override
public TerminatingFindByQuery<T> matching(final Query query) {
return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public FindByQueryWithQuery<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public Stream<T> stream() {
return reactiveSupport.all().toStream();
}
@Override
public long count() {
return reactiveSupport.count().block();
}
@Override
public boolean exists() {
return count() > 0;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
public interface ExecutableFindFromReplicasByIdOperation {
<T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType);
interface TerminatingFindFromReplicasById<T> {
T any(String id);
Collection<? extends T> any(Collection<String> ids);
}
interface FindFromReplicasByIdWithCollection<T> extends TerminatingFindFromReplicasById<T> {
TerminatingFindFromReplicasById<T> inCollection(String collection);
}
interface ExecutableFindFromReplicasById<T> extends FindFromReplicasByIdWithCollection<T> {}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import org.springframework.util.Assert;
public class ExecutableFindFromReplicasByIdOperationSupport implements ExecutableFindFromReplicasByIdOperation {
private final CouchbaseTemplate template;
ExecutableFindFromReplicasByIdOperationSupport(CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, null);
}
static class ExecutableFindFromReplicasByIdSupport<T> implements ExecutableFindFromReplicasById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport<T> reactiveSupport;
ExecutableFindFromReplicasByIdSupport(CouchbaseTemplate template, Class<T> domainType, String collection) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.reactiveSupport = new ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport<>(
template.reactive(), domainType, collection);
}
@Override
public T any(String id) {
return reactiveSupport.any(id).block();
}
@Override
public Collection<? extends T> any(Collection<String> ids) {
return reactiveSupport.any(ids).collectList().block();
}
@Override
public TerminatingFindFromReplicasById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, collection);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ExecutableInsertByIdOperation {
<T> ExecutableInsertById<T> insertById(Class<T> domainType);
interface TerminatingInsertById<T> {
T one(T object);
Collection<? extends T> all(Collection<? extends T> objects);
}
interface InsertByIdWithCollection<T> extends TerminatingInsertById<T> {
TerminatingInsertById<T> inCollection(String collection);
}
interface InsertByIdWithDurability<T> extends InsertByIdWithCollection<T> {
InsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
InsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ExecutableInsertById<T> extends InsertByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByIdOperation {
private final CouchbaseTemplate template;
public ExecutableInsertByIdOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableInsertById<T> insertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ExecutableInsertByIdSupport<T> implements ExecutableInsertById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport<T> reactiveSupport;
ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveSupport = new ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public T one(final T object) {
return reactiveSupport.one(object).block();
}
@Override
public Collection<? extends T> all(Collection<? extends T> objects) {
return reactiveSupport.all(objects).collectList().block();
}
@Override
public TerminatingInsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public InsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public InsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import java.util.List;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ExecutableRemoveByIdOperation {
ExecutableRemoveById removeById();
interface TerminatingRemoveById {
RemoveResult one(String id);
List<RemoveResult> all(Collection<String> ids);
}
interface RemoveByIdWithCollection extends TerminatingRemoveById {
TerminatingRemoveById inCollection(String collection);
}
interface RemoveByIdWithDurability extends RemoveByIdWithCollection {
RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel);
RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ExecutableRemoveById extends RemoveByIdWithDurability {}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import java.util.List;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByIdOperation {
private final CouchbaseTemplate template;
public ExecutableRemoveByIdOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public ExecutableRemoveById removeById() {
return new ExecutableRemoveByIdSupport(template, null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE);
}
static class ExecutableRemoveByIdSupport implements ExecutableRemoveById {
private final CouchbaseTemplate template;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport reactiveRemoveByIdSupport;
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final String collection, final PersistTo persistTo,
final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport(
template.reactive(), collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveResult one(final String id) {
return reactiveRemoveByIdSupport.one(id).block();
}
@Override
public List<RemoveResult> all(final Collection<String> ids) {
return reactiveRemoveByIdSupport.all(ids).collectList().block();
}
@Override
public TerminatingRemoveById inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2020 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.core;
import java.util.List;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public interface ExecutableRemoveByQueryOperation {
<T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType);
interface TerminatingRemoveByQuery<T> {
List<RemoveResult> all();
}
interface RemoveByQueryWithQuery<T> extends TerminatingRemoveByQuery<T> {
TerminatingRemoveByQuery<T> matching(Query query);
}
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryWithQuery<T> {
RemoveByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface ExecutableRemoveByQuery<T> extends RemoveByQueryConsistentWith<T> {}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2012-2020 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.core;
import java.util.List;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemoveByQueryOperation {
private static final Query ALL_QUERY = new Query();
private final CouchbaseTemplate template;
public ExecutableRemoveByQueryOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED);
}
static class ExecutableRemoveByQuerySupport<T> implements ExecutableRemoveByQuery<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final Query query;
private final ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport<T> reactiveSupport;
private final QueryScanConsistency scanConsistency;
ExecutableRemoveByQuerySupport(final CouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.reactiveSupport = new ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport<>(
template.reactive(), domainType, query, scanConsistency);
this.scanConsistency = scanConsistency;
}
@Override
public List<RemoveResult> all() {
return reactiveSupport.all().collectList().block();
}
@Override
public TerminatingRemoveByQuery<T> matching(final Query query) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public RemoveByQueryWithQuery<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ExecutableReplaceByIdOperation {
<T> ExecutableReplaceById<T> replaceById(Class<T> domainType);
interface TerminatingReplaceById<T> {
T one(T object);
Collection<? extends T> all(Collection<? extends T> objects);
}
interface ReplaceByIdWithCollection<T> extends TerminatingReplaceById<T> {
TerminatingReplaceById<T> inCollection(String collection);
}
interface ReplaceByIdWithDurability<T> extends ReplaceByIdWithCollection<T> {
ReplaceByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
ReplaceByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ExecutableReplaceById<T> extends ReplaceByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceByIdOperation {
private final CouchbaseTemplate template;
public ExecutableReplaceByIdOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableReplaceById<T> replaceById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ExecutableReplaceByIdSupport<T> implements ExecutableReplaceById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport<T> reactiveSupport;
ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveSupport = new ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public T one(final T object) {
return reactiveSupport.one(object).block();
}
@Override
public Collection<? extends T> all(Collection<? extends T> objects) {
return reactiveSupport.all(objects).collectList().block();
}
@Override
public TerminatingReplaceById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ExecutableUpsertByIdOperation {
<T> ExecutableUpsertById<T> upsertById(Class<T> domainType);
interface TerminatingUpsertById<T> {
T one(T object);
Collection<? extends T> all(Collection<? extends T> objects);
}
interface UpsertByIdWithCollection<T> extends TerminatingUpsertById<T> {
TerminatingUpsertById<T> inCollection(String collection);
}
interface UpsertByIdWithDurability<T> extends UpsertByIdWithCollection<T> {
UpsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
UpsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ExecutableUpsertById<T> extends UpsertByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012-2020 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.core;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByIdOperation {
private final CouchbaseTemplate template;
public ExecutableUpsertByIdOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableUpsertById<T> upsertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ExecutableUpsertByIdSupport<T> implements ExecutableUpsertById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport<T> reactiveSupport;
ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveSupport = new ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public T one(final T object) {
return reactiveSupport.one(object).block();
}
@Override
public Collection<? extends T> all(Collection<? extends T> objects) {
return reactiveSupport.all(objects).collectList().block();
}
@Override
public TerminatingUpsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
}
}

View File

@@ -13,14 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.repository.join;
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
package org.springframework.data.couchbase.core;
/**
* @author Tayeb Chlyah
*/
@N1qlSecondaryIndexed(indexName = "addressIndex")
interface AddressRepository extends CouchbaseRepository<Address, String> {
}
public interface FluentCouchbaseOperations extends ExecutableUpsertByIdOperation, ExecutableInsertByIdOperation,
ExecutableReplaceByIdOperation, ExecutableFindByIdOperation, ExecutableFindFromReplicasByIdOperation,
ExecutableFindByQueryOperation, ExecutableFindByAnalyticsOperation, ExecutableExistsByIdOperation,
ExecutableRemoveByIdOperation, ExecutableRemoveByQueryOperation {}

View File

@@ -25,23 +25,23 @@ import org.springframework.dao.TransientDataAccessException;
*/
public class OperationCancellationException extends TransientDataAccessException {
/**
* Constructor for OperationCancellationException.
*
* @param msg the detail message
*/
public OperationCancellationException(final String msg) {
super(msg);
}
/**
* Constructor for OperationCancellationException.
*
* @param msg the detail message
*/
public OperationCancellationException(final String msg) {
super(msg);
}
/**
* Constructor for OperationCancellationException.
*
* @param msg the detail message
* @param cause the root cause from the data access API in use
*/
public OperationCancellationException(final String msg, final Throwable cause) {
super(msg, cause);
}
/**
* Constructor for OperationCancellationException.
*
* @param msg the detail message
* @param cause the root cause from the data access API in use
*/
public OperationCancellationException(final String msg, final Throwable cause) {
super(msg, cause);
}
}

View File

@@ -25,23 +25,23 @@ import org.springframework.dao.TransientDataAccessException;
*/
public class OperationInterruptedException extends TransientDataAccessException {
/**
* Constructor for OperationInterruptedException.
*
* @param msg the detail message
*/
public OperationInterruptedException(final String msg) {
super(msg);
}
/**
* Constructor for OperationInterruptedException.
*
* @param msg the detail message
*/
public OperationInterruptedException(final String msg) {
super(msg);
}
/**
* Constructor for OperationInterruptedException.
*
* @param msg the detail message
* @param cause the root cause from the data access API in use
*/
public OperationInterruptedException(final String msg, final Throwable cause) {
super(msg, cause);
}
/**
* Constructor for OperationInterruptedException.
*
* @param msg the detail message
* @param cause the root cause from the data access API in use
*/
public OperationInterruptedException(final String msg, final Throwable cause) {
super(msg, cause);
}
}

View File

@@ -13,37 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.repository.join;
import org.springframework.data.annotation.Id;
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
/**
* @author Tayeb Chlyah
* Defines common operations on the Couchbase data source, most commonly implemented by {@link CouchbaseTemplate}.
*/
public class Address {
public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOperations {
@Id
String id;
CouchbaseConverter getConverter();
String name;
String getBucketName();
String country;
String getScopeName();
public Address(String id, String name, String country) {
this.id = id;
this.name = name;
this.country = country;
}
CouchbaseClientFactory getCouchbaseClientFactory();
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getCountry() {
return country;
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2012-2020 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.core;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import com.couchbase.client.java.Collection;
public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations {
private final CouchbaseClientFactory clientFactory;
private final CouchbaseConverter converter;
private final PersistenceExceptionTranslator exceptionTranslator;
private final CouchbaseTemplateSupport templateSupport;
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this.clientFactory = clientFactory;
this.converter = converter;
this.exceptionTranslator = clientFactory.getExceptionTranslator();
this.templateSupport = new CouchbaseTemplateSupport(converter);
}
@Override
public <T> ReactiveFindById<T> findById(Class<T> domainType) {
return new ReactiveFindByIdOperationSupport(this).findById(domainType);
}
@Override
public ReactiveExistsById existsById() {
return new ReactiveExistsByIdOperationSupport(this).existsById();
}
@Override
public <T> ReactiveFindByAnalytics<T> findByAnalytics(Class<T> domainType) {
return new ReactiveFindByAnalyticsOperationSupport(this).findByAnalytics(domainType);
}
@Override
public <T> ReactiveFindByQuery<T> findByQuery(Class<T> domainType) {
return new ReactiveFindByQueryOperationSupport(this).findByQuery(domainType);
}
@Override
public <T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ReactiveFindFromReplicasByIdOperationSupport(this).findFromReplicasById(domainType);
}
@Override
public <T> ReactiveInsertById<T> insertById(Class<T> domainType) {
return new ReactiveInsertByIdOperationSupport(this).insertById(domainType);
}
@Override
public ReactiveRemoveById removeById() {
return new ReactiveRemoveByIdOperationSupport(this).removeById();
}
@Override
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ReactiveRemoveByQueryOperationSupport(this).removeByQuery(domainType);
}
@Override
public <T> ReactiveReplaceById<T> replaceById(Class<T> domainType) {
return new ReactiveReplaceByIdOperationSupport(this).replaceById(domainType);
}
@Override
public <T> ReactiveUpsertById<T> upsertById(Class<T> domainType) {
return new ReactiveUpsertByIdOperationSupport(this).upsertById(domainType);
}
@Override
public String getBucketName() {
return clientFactory.getBucket().name();
}
@Override
public String getScopeName() {
return clientFactory.getScope().name();
}
@Override
public CouchbaseClientFactory getCouchbaseClientFactory() {
return clientFactory;
}
/**
* Provides access to a {@link Collection} on the configured {@link CouchbaseClientFactory}.
*
* @param collectionName the name of the collection, if null is passed in the default collection is assumed.
* @return the collection instance.
*/
public Collection getCollection(final String collectionName) {
return clientFactory.getCollection(collectionName);
}
@Override
public CouchbaseConverter getConverter() {
return converter;
}
CouchbaseTemplateSupport support() {
return templateSupport;
}
/**
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
* exception if the conversation failed. Thus allows safe re-throwing of the return value.
*
* @param ex the exception to translate
*/
RuntimeException potentiallyConvertRuntimeException(RuntimeException ex) {
RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
return resolved == null ? ex : resolved;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.Map;
public interface ReactiveExistsByIdOperation {
ReactiveExistsById existsById();
interface TerminatingExistsById {
Mono<Boolean> one(String id);
Mono<Map<String, Boolean>> all(Collection<String> ids);
}
interface ExistsByIdWithCollection extends TerminatingExistsById {
TerminatingExistsById inCollection(String collection);
}
interface ReactiveExistsById extends ExistsByIdWithCollection {}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2020 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.core;
import static com.couchbase.client.java.kv.ExistsOptions.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import java.util.Collection;
import java.util.Map;
import org.springframework.util.Assert;
import com.couchbase.client.java.kv.ExistsResult;
public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOperation {
private final ReactiveCouchbaseTemplate template;
ReactiveExistsByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public ReactiveExistsById existsById() {
return new ReactiveExistsByIdSupport(template, null);
}
static class ReactiveExistsByIdSupport implements ReactiveExistsById {
private final ReactiveCouchbaseTemplate template;
private final String collection;
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final String collection) {
this.template = template;
this.collection = collection;
}
@Override
public Mono<Boolean> one(final String id) {
return Mono.just(id).flatMap(
docId -> template.getCollection(collection).reactive().exists(id, existsOptions()).map(ExistsResult::exists))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Mono<Map<String, Boolean>> all(final Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(id -> one(id).map(result -> Tuples.of(id, result)))
.collectMap(Tuple2::getT1, Tuple2::getT2);
}
@Override
public TerminatingExistsById inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveExistsByIdSupport(template, collection);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
public interface ReactiveFindByAnalyticsOperation {
<T> ReactiveFindByAnalytics<T> findByAnalytics(Class<T> domainType);
/**
* Compose find execution by calling one of the terminating methods.
*/
interface TerminatingFindByAnalytics<T> {
Mono<T> one();
Mono<T> first();
Flux<T> all();
Mono<Long> count();
Mono<Boolean> exists();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindByAnalyticsWithQuery<T> extends TerminatingFindByAnalytics<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindByAnalytics}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindByAnalytics<T> matching(AnalyticsQuery query);
}
interface ReactiveFindByAnalytics<T> extends FindByAnalyticsWithQuery<T> {}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
import com.couchbase.client.java.analytics.ReactiveAnalyticsResult;
public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAnalyticsOperation {
private static final AnalyticsQuery ALL_QUERY = new AnalyticsQuery();
private final ReactiveCouchbaseTemplate template;
public ReactiveFindByAnalyticsOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY);
}
static class ReactiveFindByAnalyticsSupport<T> implements ReactiveFindByAnalytics<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final AnalyticsQuery query;
ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final AnalyticsQuery query) {
this.template = template;
this.domainType = domainType;
this.query = query;
}
@Override
public TerminatingFindByAnalytics<T> matching(AnalyticsQuery query) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query);
}
@Override
public Mono<T> one() {
return all().single();
}
@Override
public Mono<T> first() {
return all().next();
}
@Override
public Flux<T> all() {
return Flux.defer(() -> {
String statement = assembleEntityQuery(false);
return template.getCouchbaseClientFactory().getCluster().reactive().analyticsQuery(statement)
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).map(row -> {
String id = row.getString("__id");
long cas = row.getLong("__cas");
row.removeKey("__id");
row.removeKey("__cas");
return template.support().decodeEntity(id, row.toString(), cas, domainType);
});
});
}
@Override
public Mono<Long> count() {
return Mono.defer(() -> {
String statement = assembleEntityQuery(true);
return template.getCouchbaseClientFactory().getCluster().reactive().analyticsQuery(statement)
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).map(row -> row.getLong("__count")).next();
});
}
@Override
public Mono<Boolean> exists() {
return count().map(count -> count > 0);
}
private String assembleEntityQuery(final boolean count) {
final String bucket = "`" + template.getBucketName() + "`";
final StringBuilder statement = new StringBuilder("SELECT ");
if (count) {
statement.append("count(*) as __count");
} else {
statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*");
}
final String dataset = template.support().getJavaNameForEntity(domainType);
statement.append(" FROM ").append(dataset);
query.appendSort(statement);
query.appendSkipAndLimit(statement);
return statement.toString();
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
public interface ReactiveFindByIdOperation {
<T> ReactiveFindById<T> findById(Class<T> domainType);
interface TerminatingFindById<T> {
Mono<T> one(String id);
Flux<? extends T> all(Collection<String> ids);
}
interface FindByIdWithCollection<T> extends TerminatingFindById<T> {
TerminatingFindById<T> inCollection(String collection);
}
interface FindByIdWithProjection<T> extends FindByIdWithCollection<T> {
FindByIdWithCollection<T> project(String... fields);
}
interface ReactiveFindById<T> extends FindByIdWithProjection<T> {}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2020 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.core;
import static com.couchbase.client.java.kv.GetOptions.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.util.Assert;
import com.couchbase.client.java.codec.RawJsonTranscoder;
import com.couchbase.client.java.kv.GetOptions;
public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperation {
private final ReactiveCouchbaseTemplate template;
ReactiveFindByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindById<T> findById(Class<T> domainType) {
return new ReactiveFindByIdSupport<>(template, domainType, null, null);
}
static class ReactiveFindByIdSupport<T> implements ReactiveFindById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final List<String> fields;
ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class<T> domainType, String collection,
List<String> fields) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.fields = fields;
}
@Override
public Mono<T> one(final String id) {
return Mono.just(id).flatMap(docId -> {
GetOptions options = getOptions().transcoder(RawJsonTranscoder.INSTANCE);
if (fields != null && !fields.isEmpty()) {
options.project(fields);
}
return template.getCollection(collection).reactive().get(docId, options);
}).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> all(final Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(this::one);
}
@Override
public TerminatingFindById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, collection, fields);
}
@Override
public FindByIdWithCollection<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields));
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public interface ReactiveFindByQueryOperation {
<T> ReactiveFindByQuery<T> findByQuery(Class<T> domainType);
/**
* Compose find execution by calling one of the terminating methods.
*/
interface TerminatingFindByQuery<T> {
Mono<T> one();
Mono<T> first();
Flux<T> all();
Mono<Long> count();
Mono<Boolean> exists();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindByQueryWithQuery<T> extends TerminatingFindByQuery<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindByQuery}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindByQuery<T> matching(Query query);
}
interface FindByQueryConsistentWith<T> extends FindByQueryWithQuery<T> {
FindByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface ReactiveFindByQuery<T> extends FindByQueryConsistentWith<T> {}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2012-2020 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.core;
import org.springframework.data.couchbase.core.query.QueryCriteria;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
import com.couchbase.client.java.query.ReactiveQueryResult;
public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryOperation {
private static final Query ALL_QUERY = new Query();
private final ReactiveCouchbaseTemplate template;
public ReactiveFindByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindByQuery<T> findByQuery(final Class<T> domainType) {
return new ReactiveFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED);
}
static class ReactiveFindByQuerySupport<T> implements ReactiveFindByQuery<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final Query query;
private final QueryScanConsistency scanConsistency;
ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.scanConsistency = scanConsistency;
}
@Override
public TerminatingFindByQuery<T> matching(Query query) {
return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public FindByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency) {
return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public Mono<T> one() {
return all().single();
}
@Override
public Mono<T> first() {
return all().next();
}
@Override
public Flux<T> all() {
return Flux.defer(() -> {
String statement = assembleEntityQuery(false);
return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions())
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> {
String id = row.getString("__id");
long cas = row.getLong("__cas");
row.removeKey("__id");
row.removeKey("__cas");
return template.support().decodeEntity(id, row.toString(), cas, domainType);
});
});
}
@Override
public Mono<Long> count() {
return Mono.defer(() -> {
String statement = assembleEntityQuery(true);
return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions())
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> row.getLong("__count")).next();
});
}
@Override
public Mono<Boolean> exists() {
return count().map(count -> count > 0);
}
private String assembleEntityQuery(final boolean count) {
final String bucket = "`" + template.getBucketName() + "`";
final StringBuilder statement = new StringBuilder("SELECT ");
if (count) {
statement.append("count(*) as __count");
} else {
statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*");
}
statement.append(" FROM ").append(bucket);
String typeKey = template.getConverter().getTypeKey();
String typeValue = template.support().getJavaNameForEntity(domainType);
query.addCriteria(QueryCriteria.where(typeKey).is(typeValue));
query.appendWhere(statement);
query.appendSort(statement);
query.appendSkipAndLimit(statement);
return statement.toString();
}
private QueryOptions buildQueryOptions() {
final QueryOptions options = QueryOptions.queryOptions();
if (scanConsistency != null) {
options.scanConsistency(scanConsistency);
}
return options;
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
public interface ReactiveFindFromReplicasByIdOperation {
<T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType);
interface TerminatingFindFromReplicasById<T> {
Mono<T> any(String id);
Flux<? extends T> any(Collection<String> ids);
}
interface FindFromReplicasByIdWithCollection<T> extends TerminatingFindFromReplicasById<T> {
TerminatingFindFromReplicasById<T> inCollection(String collection);
}
interface ReactiveFindFromReplicasById<T> extends FindFromReplicasByIdWithCollection<T> {}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2012-2020 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.core;
import static com.couchbase.client.java.kv.GetAnyReplicaOptions.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.java.codec.RawJsonTranscoder;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFindFromReplicasByIdOperation {
private final ReactiveCouchbaseTemplate template;
ReactiveFindFromReplicasByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, null);
}
static class ReactiveFindFromReplicasByIdSupport<T> implements ReactiveFindFromReplicasById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
ReactiveFindFromReplicasByIdSupport(ReactiveCouchbaseTemplate template, Class<T> domainType, String collection) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
}
@Override
public Mono<T> any(final String id) {
return Mono.just(id).flatMap(docId -> {
GetAnyReplicaOptions options = getAnyReplicaOptions().transcoder(RawJsonTranscoder.INSTANCE);
return template.getCollection(collection).reactive().getAnyReplica(docId, options);
}).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> any(Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(this::any);
}
@Override
public TerminatingFindFromReplicasById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, collection);
}
}
}

View File

@@ -16,25 +16,7 @@
package org.springframework.data.couchbase.core;
/**
* How failing write results should be handled.
*
* @author Michael Nitschinger
*/
public enum WriteResultChecking {
/**
* Ignore failing write results.
*/
NONE,
/**
* Log failing write results.
*/
LOG,
/**
* Throw Exceptions on failing write results.
*/
EXCEPTION
}
public interface ReactiveFluentCouchbaseOperations extends ReactiveUpsertByIdOperation, ReactiveInsertByIdOperation,
ReactiveReplaceByIdOperation, ReactiveFindByIdOperation, ReactiveExistsByIdOperation,
ReactiveFindByAnalyticsOperation, ReactiveFindFromReplicasByIdOperation, ReactiveFindByQueryOperation,
ReactiveRemoveByIdOperation, ReactiveRemoveByQueryOperation {}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ReactiveInsertByIdOperation {
<T> ReactiveInsertById<T> insertById(Class<T> domainType);
interface TerminatingInsertById<T> {
Mono<T> one(T object);
Flux<? extends T> all(Collection<? extends T> objects);
}
interface InsertByIdWithCollection<T> extends TerminatingInsertById<T> {
TerminatingInsertById<T> inCollection(String collection);
}
interface InsertByIdWithDurability<T> extends InsertByIdWithCollection<T> {
InsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
InsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReactiveInsertById<T> extends InsertByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
private final ReactiveCouchbaseTemplate template;
public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveInsertById<T> insertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ReactiveInsertByIdSupport<T> implements ReactiveInsertById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return template.getCollection(collection).reactive()
.insert(converted.getId(), converted.getPayload(), buildInsertOptions()).map(result -> {
template.support().applyUpdatedCas(object, result.cas());
return o;
});
}).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> all(Collection<? extends T> objects) {
return Flux.fromIterable(objects).flatMap(this::one);
}
private InsertOptions buildInsertOptions() {
final InsertOptions options = InsertOptions.insertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
return options;
}
@Override
public TerminatingInsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public InsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public InsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ReactiveRemoveByIdOperation {
ReactiveRemoveById removeById();
interface TerminatingRemoveById {
Mono<RemoveResult> one(String id);
Flux<RemoveResult> all(Collection<String> ids);
}
interface RemoveByIdWithCollection extends TerminatingRemoveById {
TerminatingRemoveById inCollection(String collection);
}
interface RemoveByIdWithDurability extends RemoveByIdWithCollection {
RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel);
RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReactiveRemoveById extends RemoveByIdWithDurability {}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOperation {
private final ReactiveCouchbaseTemplate template;
public ReactiveRemoveByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public ReactiveRemoveById removeById() {
return new ReactiveRemoveByIdSupport(template, null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE);
}
static class ReactiveRemoveByIdSupport implements ReactiveRemoveById {
private final ReactiveCouchbaseTemplate template;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
}
@Override
public Mono<RemoveResult> one(final String id) {
return Mono.just(id).flatMap(docId -> template.getCollection(collection).reactive()
.remove(id, buildRemoveOptions()).map(r -> RemoveResult.from(docId, r))).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<RemoveResult> all(final Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(this::one);
}
private RemoveOptions buildRemoveOptions() {
final RemoveOptions options = RemoveOptions.removeOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
return options;
}
@Override
public TerminatingRemoveById inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public interface ReactiveRemoveByQueryOperation {
<T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType);
interface TerminatingRemoveByQuery<T> {
Flux<RemoveResult> all();
}
interface RemoveByQueryWithQuery<T> extends TerminatingRemoveByQuery<T> {
TerminatingRemoveByQuery<T> matching(Query query);
}
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryWithQuery<T> {
RemoveByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface ReactiveRemoveByQuery<T> extends RemoveByQueryConsistentWith<T> {}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import java.util.Optional;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
import com.couchbase.client.java.query.ReactiveQueryResult;
public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQueryOperation {
private static final Query ALL_QUERY = new Query();
private final ReactiveCouchbaseTemplate template;
public ReactiveRemoveByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED);
}
static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final Query query;
private final QueryScanConsistency scanConsistency;
ReactiveRemoveByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.scanConsistency = scanConsistency;
}
@Override
public Flux<RemoveResult> all() {
return Flux.defer(() -> {
String bucket = "`" + template.getBucketName() + "`";
String typeKey = template.getConverter().getTypeKey();
String typeValue = template.support().getJavaNameForEntity(domainType);
String where = " WHERE `" + typeKey + "` = \"" + typeValue + "\"";
String returning = " RETURNING meta().*";
String statement = "DELETE FROM " + bucket + " " + where + returning;
return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions())
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject)
.map(row -> new RemoveResult(row.getString("id"), row.getLong("cas"), Optional.empty()));
});
}
private QueryOptions buildQueryOptions() {
final QueryOptions options = QueryOptions.queryOptions();
if (scanConsistency != null) {
options.scanConsistency(scanConsistency);
}
return options;
}
@Override
public TerminatingRemoveByQuery<T> matching(final Query query) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public RemoveByQueryWithQuery<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ReactiveReplaceByIdOperation {
<T> ReactiveReplaceById<T> replaceById(Class<T> domainType);
interface TerminatingReplaceById<T> {
Mono<T> one(T object);
Flux<? extends T> all(Collection<? extends T> objects);
}
interface ReplaceByIdWithCollection<T> extends TerminatingReplaceById<T> {
TerminatingReplaceById<T> inCollection(String collection);
}
interface ReplaceByIdWithDurability<T> extends ReplaceByIdWithCollection<T> {
ReplaceByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
ReplaceByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReactiveReplaceById<T> extends ReplaceByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
private final ReactiveCouchbaseTemplate template;
public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveReplaceById<T> replaceById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ReactiveReplaceByIdSupport<T> implements ReactiveReplaceById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return template.getCollection(collection).reactive()
.replace(converted.getId(), converted.getPayload(), buildReplaceOptions()).map(result -> {
template.support().applyUpdatedCas(object, result.cas());
return o;
});
}).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> all(Collection<? extends T> objects) {
return Flux.fromIterable(objects).flatMap(this::one);
}
private ReplaceOptions buildReplaceOptions() {
final ReplaceOptions options = ReplaceOptions.replaceOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
return options;
}
@Override
public TerminatingReplaceById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ReactiveUpsertByIdOperation {
<T> ReactiveUpsertById<T> upsertById(Class<T> domainType);
interface TerminatingUpsertById<T> {
Mono<T> one(T object);
Flux<? extends T> all(Collection<? extends T> objects);
}
interface UpsertByIdWithCollection<T> extends TerminatingUpsertById<T> {
TerminatingUpsertById<T> inCollection(String collection);
}
interface UpsertByIdWithDurability<T> extends UpsertByIdWithCollection<T> {
UpsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
UpsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReactiveUpsertById<T> extends UpsertByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012-2020 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.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.kv.UpsertOptions;
public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOperation {
private final ReactiveCouchbaseTemplate template;
public ReactiveUpsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveUpsertById<T> upsertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ReactiveUpsertByIdSupport<T> implements ReactiveUpsertById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return template.getCollection(collection).reactive()
.upsert(converted.getId(), converted.getPayload(), buildUpsertOptions()).map(result -> {
template.support().applyUpdatedCas(object, result.cas());
return o;
});
}).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> all(Collection<? extends T> objects) {
return Flux.fromIterable(objects).flatMap(this::one);
}
private UpsertOptions buildUpsertOptions() {
final UpsertOptions options = UpsertOptions.upsertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
return options;
}
@Override
public TerminatingUpsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
}
}

View File

@@ -0,0 +1,56 @@
package org.springframework.data.couchbase.core;
import java.util.Objects;
import java.util.Optional;
import com.couchbase.client.core.msg.kv.MutationToken;
import com.couchbase.client.java.kv.MutationResult;
public class RemoveResult {
private final String id;
private final long cas;
private final Optional<MutationToken> mutationToken;
public RemoveResult(String id, long cas, Optional<MutationToken> mutationToken) {
this.id = id;
this.cas = cas;
this.mutationToken = mutationToken;
}
public static RemoveResult from(final String id, final MutationResult result) {
return new RemoveResult(id, result.cas(), result.mutationToken());
}
public String getId() {
return id;
}
public long getCas() {
return cas;
}
public Optional<MutationToken> getMutationToken() {
return mutationToken;
}
@Override
public String toString() {
return "RemoveResult{" + "id='" + id + '\'' + ", cas=" + cas + ", mutationToken=" + mutationToken + '}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
RemoveResult that = (RemoveResult) o;
return cas == that.cas && Objects.equals(id, that.id) && Objects.equals(mutationToken, that.mutationToken);
}
@Override
public int hashCode() {
return Objects.hash(id, cas, mutationToken);
}
}

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2017-2020 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.core;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.query.AsyncN1qlQueryResult;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.view.AsyncSpatialViewResult;
import com.couchbase.client.java.view.AsyncViewResult;
import com.couchbase.client.java.view.SpatialViewQuery;
import com.couchbase.client.java.view.ViewQuery;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.query.Consistency;
import rx.Observable;
/**
* @author Subhashni Balakrishnan
* @author Alex Derkach
* @since 3.0
*/
public interface RxJavaCouchbaseOperations {
<T>Observable<T> save(T objectToSave);
<T>Observable<T> save(Iterable<T> batchToSave);
<T>Observable<T> save(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> save(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> insert(T objectToSave);
<T>Observable<T> insert(Iterable<T> batchToSave);
<T>Observable<T> insert(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> insert(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> update(T objectToSave);
<T>Observable<T> update(Iterable<T> batchToSave);
<T>Observable<T> update(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> update(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> remove(T objectToRemove);
<T>Observable<T> remove(T objectToRemove, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> remove(Iterable<T> batchToRemove);
<T>Observable<T> remove(Iterable<T> batchToRemove, PersistTo persistTo, ReplicateTo replicateTo);
Observable<Boolean> exists(String id);
<T>Observable<T> findById(String id, Class<T> entityClass);
Observable<AsyncN1qlQueryResult> queryN1QL(N1qlQuery n1ql);
Observable<AsyncViewResult> queryView(ViewQuery query);
Observable<AsyncSpatialViewResult> querySpatialView(SpatialViewQuery query);
<T>Observable<T> findByView(ViewQuery query, Class<T> entityClass);
<T>Observable<T> findByN1QL(N1qlQuery n1ql, Class<T> entityClass);
<T>Observable<T> findBySpatialView(SpatialViewQuery query, Class<T> entityClass);
<T>Observable<T> findByN1QLProjection(N1qlQuery n1ql, Class<T> fragmentClass);
Consistency getDefaultConsistency();
/**
* Returns the linked {@link Bucket} to this template.
*
* @return the client used for the template.
*/
Bucket getCouchbaseBucket();
CouchbaseConverter getConverter();
ClusterInfo getCouchbaseClusterInfo();
}

View File

@@ -1,513 +0,0 @@
/*
* Copyright 2017-2020 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.core;
import static org.springframework.data.couchbase.core.CouchbaseTemplate.ensureNotIterable;
import com.couchbase.client.java.AsyncBucket;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
import com.couchbase.client.java.cluster.ClusterInfo;
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.DocumentAlreadyExistsException;
import com.couchbase.client.java.query.*;
import com.couchbase.client.java.view.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.*;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.data.couchbase.core.support.TemplateUtils;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import rx.Observable;
import rx.functions.Func3;
/**
* RxJavaCouchbaseTemplate implements operations using rxjava1 observables
* @author Subhashni Balakrishnan
* @author Mark Paluch
* @author Alex Derkach
* @since 3.0
*/
public class RxJavaCouchbaseTemplate implements RxJavaCouchbaseOperations {
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
protected final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
private Bucket syncClient;
private AsyncBucket client;
private final ClusterInfo clusterInfo;
private final CouchbaseConverter converter;
private final TranslationService translationService;
private Consistency configuredConsistency = Consistency.DEFAULT_CONSISTENCY;
private WriteResultChecking writeResultChecking = DEFAULT_WRITE_RESULT_CHECKING;
public <T> Observable<T> save(T objectToSave) {
return save(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
}
public <T> Observable<T> save(Iterable<T> batchToSave) {
return Observable.from(batchToSave)
.flatMap(this::save);
}
public <T> Observable<T> save(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return doPersist(objectToSave, PersistType.SAVE, persistTo, replicateTo);
}
public <T> Observable<T> save(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return Observable.from(batchToSave)
.flatMap(object -> save(object, persistTo, replicateTo));
}
@Override
public <T> Observable<T> insert(T objectToSave) {
return insert(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public <T> Observable<T> insert(Iterable<T> batchToSave) {
return Observable.from(batchToSave)
.flatMap(this::insert);
}
@Override
public <T> Observable<T> insert(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return doPersist(objectToSave, PersistType.INSERT, persistTo, replicateTo);
}
@Override
public <T> Observable<T> insert(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return Observable.from(batchToSave)
.flatMap(objectToSave -> insert(objectToSave, persistTo, replicateTo));
}
@Override
public <T> Observable<T> update(T objectToSave) {
return update(objectToSave, PersistTo.NONE, ReplicateTo.NONE);
}
@Override
public <T> Observable<T> update(Iterable<T> batchToSave) {
return Observable.from(batchToSave)
.flatMap(this::update);
}
@Override
public <T> Observable<T> update(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return doPersist(objectToSave, PersistType.UPDATE, persistTo, replicateTo);
}
@Override
public <T> Observable<T> update(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
return Observable.from(batchToSave)
.flatMap(objectToSave -> update(objectToSave, persistTo, replicateTo));
}
public <T> Observable<T> remove(T objectToRemove) {
return doRemove(objectToRemove, PersistTo.NONE, ReplicateTo.NONE);
}
public <T> Observable<T> remove(Iterable<T> batchToRemove) {
return Observable.from(batchToRemove)
.flatMap(this::remove);
}
public <T> Observable<T> remove(T objectToRemove, PersistTo persistTo, ReplicateTo replicateTo) {
return doRemove(objectToRemove, persistTo, replicateTo);
}
public <T> Observable<T> remove(Iterable<T> batchToRemove, PersistTo persistTo, ReplicateTo replicateTo) {
return Observable.from(batchToRemove)
.flatMap(object -> remove(object, persistTo, replicateTo));
}
public RxJavaCouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client) {
this(clusterInfo, client, null, null);
}
public RxJavaCouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client, final TranslationService translationService) {
this(clusterInfo, client, null, translationService);
}
public void setWriteResultChecking(WriteResultChecking writeResultChecking) {
this.writeResultChecking = writeResultChecking == null ? DEFAULT_WRITE_RESULT_CHECKING : writeResultChecking;
}
public RxJavaCouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client,
final CouchbaseConverter converter,
final TranslationService translationService) {
this.syncClient = client;
this.clusterInfo = clusterInfo;
this.client = client.async();
this.converter = converter == null ? getDefaultConverter() : converter;
this.translationService = translationService == null ? getDefaultTranslationService() : translationService;
this.mappingContext = this.converter.getMappingContext();
}
private RawJsonDocument encodeAndWrap(final CouchbaseDocument source, Long version) {
String encodedContent = translationService.encode(source);
if (version == null) {
return RawJsonDocument.create(source.getId(), source.getExpiration(), encodedContent);
} else {
return RawJsonDocument.create(source.getId(), source.getExpiration(), encodedContent, version);
}
}
private TranslationService getDefaultTranslationService() {
JacksonTranslationService t = new JacksonTranslationService();
t.afterPropertiesSet();
return t;
}
private CouchbaseConverter getDefaultConverter() {
MappingCouchbaseConverter c = new MappingCouchbaseConverter(new CouchbaseMappingContext());
c.afterPropertiesSet();
return c;
}
private final <T> ConvertingPropertyAccessor<T> getPropertyAccessor(T source) {
CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(source.getClass());
PersistentPropertyAccessor<T> accessor = entity.getPropertyAccessor(source);
return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService());
}
private <T> Observable<T> doPersist(T objectToPersist, PersistType persistType, PersistTo persistTo, ReplicateTo replicateTo) {
// If version is not set - assumption that document is new, otherwise updating
Long version = getVersion(objectToPersist);
Func3<RawJsonDocument, PersistTo, ReplicateTo, Observable<RawJsonDocument>> persistFunction;
switch (persistType) {
case SAVE:
if (version == null) {
//No version field - no cas
persistFunction = client::upsert;
} else if (version > 0) {
//Updating existing document with cas
persistFunction = client::replace;
} else {
//Creating new document
persistFunction = client::insert;
}
break;
case UPDATE:
persistFunction = client::replace;
break;
case INSERT:
default:
persistFunction = client::insert;
break;
}
return persistFunction.call(toJsonDocument(objectToPersist), persistTo, replicateTo)
.flatMap(storedDoc -> {
if (storedDoc != null) {
if (storedDoc.cas() != 0) {
setVersion(objectToPersist, storedDoc.cas());
}
// Only set the id if the objectToPersist doesn't have it. That only
// happens when you have generated ids, and you are first persisting the
// document.
if (storedDoc.id() != null && getId(objectToPersist) == null) {
setId(objectToPersist, storedDoc.id());
}
}
return Observable.just(objectToPersist);
})
.onErrorResumeNext(e -> {
if (e instanceof DocumentAlreadyExistsException) {
throw new OptimisticLockingFailureException(persistType.springDataOperationName +
" document with version value failed: " + version, e);
}
if (e instanceof CASMismatchException) {
throw new OptimisticLockingFailureException(persistType.springDataOperationName +
" document with version value failed: " + version, e);
}
return TemplateUtils.translateError(e);
});
}
private <T> RawJsonDocument toJsonDocument(T object) {
ensureNotIterable(object);
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(object, converted);
return encodeAndWrap(converted, getVersion(object));
}
private <T> CouchbasePersistentProperty versionProperty(T object) {
return mappingContext.getRequiredPersistentEntity(object.getClass()).getVersionProperty();
}
private<T> CouchbasePersistentProperty idProperty(T object) {
return mappingContext.getRequiredPersistentEntity(object.getClass()).getIdProperty();
}
private <T> Long getVersion(T object) {
CouchbasePersistentProperty versionProperty = versionProperty(object);
return versionProperty == null //
? null //
: getPropertyAccessor(object).getProperty(versionProperty, Long.class);
}
private <T> T setVersion(T object, long version) {
CouchbasePersistentProperty versionProperty = versionProperty(object);
if (versionProperty == null) {
return object;
}
final ConvertingPropertyAccessor<T> accessor = getPropertyAccessor(object);
accessor.setProperty(versionProperty, version);
return accessor.getBean();
}
private<T> String getId(T object) {
CouchbasePersistentProperty idProperty = idProperty(object);
return idProperty == null
? null //
: getPropertyAccessor(object).getProperty(idProperty, String.class);
}
private<T> T setId(T object, String id) {
CouchbasePersistentProperty idProperty = idProperty(object);
if (idProperty == null) {
return object;
}
final ConvertingPropertyAccessor<T> accessor = getPropertyAccessor(object);
accessor.setProperty(idProperty, id);
return accessor.getBean();
}
private <T> Observable<T> doRemove(T objectToRemove, final PersistTo persistTo, final ReplicateTo replicateTo) {
if(objectToRemove instanceof String) {
return client.remove((String) objectToRemove, persistTo, replicateTo)
.flatMap(rawJsonDocument -> Observable.just(objectToRemove))
.doOnError(e -> TemplateUtils.translateError(e));
} else {
RawJsonDocument doc = toJsonDocument(objectToRemove);
return client.remove(doc, persistTo, replicateTo)
.flatMap(rawJsonDocument -> Observable.just(objectToRemove))
.doOnError(e -> TemplateUtils.translateError(e));
}
}
@Override
public Observable<Boolean> exists(String id) {
return client.exists(id)
.doOnError(e -> TemplateUtils.translateError(e));
}
@Override
public Observable<AsyncN1qlQueryResult> queryN1QL(N1qlQuery query) {
return client.query(query)
.doOnError(e -> TemplateUtils.translateError(e));
}
@Override
public Observable<AsyncViewResult> queryView(ViewQuery query) {
return client.query(query)
.doOnError(e -> TemplateUtils.translateError(e));
}
@Override
public Observable<AsyncSpatialViewResult> querySpatialView(SpatialViewQuery query){
return client.query(query)
.doOnError(e -> TemplateUtils.translateError(e));
}
@Override
public <T> Observable<T> findById(String id, Class<T> entityClass) {
final CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityClass);
if (entity.isTouchOnRead()) {
return client.getAndTouch(id, entity.getExpiry(), RawJsonDocument.class)
.switchIfEmpty(Observable.just(null))
.map(doc -> mapToEntity(id, doc, entityClass))
.doOnError(e -> TemplateUtils.translateError(e));
} else {
return client.get(id, RawJsonDocument.class)
.switchIfEmpty(Observable.just(null))
.map(doc -> mapToEntity(id, doc, entityClass))
.doOnError(e -> TemplateUtils.translateError(e));
}
}
@Override
public <T>Observable<T> findByView(ViewQuery query, Class<T> entityClass) {
if (!query.isIncludeDocs() || !query.includeDocsTarget().equals(RawJsonDocument.class)) {
if (query.isOrderRetained()) {
query.includeDocsOrdered(RawJsonDocument.class);
} else {
query.includeDocs(RawJsonDocument.class);
}
}
//we'll always map the document to the entity, hence reduce never makes sense.
query.reduce(false);
return queryView(query)
.flatMap(asyncViewResult -> asyncViewResult.error()
.flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute view query due to error:" + error.toString())))
.switchIfEmpty(asyncViewResult.rows()))
.map(row -> {
AsyncViewRow asyncViewRow = (AsyncViewRow) row;
return asyncViewRow.document(RawJsonDocument.class)
.map(doc -> mapToEntity(doc.id(), doc, entityClass)).toBlocking().single();
})
.doOnError(throwable -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute view query", throwable)));
}
@Override
public <T>Observable<T> findByN1QL(N1qlQuery query, Class<T> entityClass) {
return queryN1QL(query)
.flatMap(asyncN1qlQueryResult -> asyncN1qlQueryResult.errors()
.flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query due to error:" + error.toString())))
.switchIfEmpty(asyncN1qlQueryResult.rows()))
.map(row -> {
JsonObject json = ((AsyncN1qlQueryRow)row).value();
String id = json.getString(TemplateUtils.SELECT_ID);
Long cas = json.getLong(TemplateUtils.SELECT_CAS);
if (id == null || cas == null) {
throw new CouchbaseQueryExecutionException("Unable to retrieve enough metadata for N1QL to entity mapping, " +
"have you selected " + TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + "?");
}
json = json.removeKey(TemplateUtils.SELECT_ID).removeKey(TemplateUtils.SELECT_CAS);
RawJsonDocument entityDoc = RawJsonDocument.create(id, json.toString(), cas);
T decoded = mapToEntity(id, entityDoc, entityClass);
return decoded;
})
.doOnError(throwable -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query", throwable)));
}
@Override
public <T>Observable<T> findBySpatialView(SpatialViewQuery query, Class<T> entityClass) {
return querySpatialView(query)
.flatMap(spatialViewResult -> spatialViewResult.error()
.flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute spatial view query due to error:" + error.toString())))
.switchIfEmpty(spatialViewResult.rows()))
.map(row -> {
AsyncSpatialViewRow asyncSpatialViewRow = (AsyncSpatialViewRow) row;
return asyncSpatialViewRow.document(RawJsonDocument.class)
.map(doc -> mapToEntity(doc.id(), doc, entityClass))
.toBlocking().single();
})
.doOnError(throwable -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute spatial view query", throwable)));
}
@Override
public <T>Observable<T> findByN1QLProjection(N1qlQuery query, Class<T> entityClass) {
return queryN1QL(query)
.flatMap(asyncN1qlQueryResult -> asyncN1qlQueryResult.errors()
.flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query due to error:" + error.toString())))
.switchIfEmpty(asyncN1qlQueryResult.rows()))
.map(row -> {
JsonObject json = ((AsyncN1qlQueryRow)row).value();
T decoded = translationService.decodeFragment(json.toString(), entityClass);
return decoded;
})
.doOnError(throwable -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query", throwable)));
}
@Override
public Consistency getDefaultConsistency() {
return configuredConsistency;
}
public void setDefaultConsistency(Consistency consistency) {
this.configuredConsistency = consistency;
}
@Override
public CouchbaseConverter getConverter() {
return this.converter;
}
private <T> T mapToEntity(String id, Document<String> data, Class<T> entityClass) {
if (data == null) {
return null;
}
final CouchbaseDocument converted = new CouchbaseDocument(id);
Object readEntity = converter.read(entityClass, (CouchbaseDocument) decodeAndUnwrap(data, converted));
final ConvertingPropertyAccessor accessor = getPropertyAccessor(readEntity);
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(readEntity.getClass());
CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
if (versionProperty != null) {
accessor.setProperty(versionProperty, data.cas());
}
return (T) readEntity;
}
/**
* Decode a {@link Document Document&lt;String&gt;} containing a JSON string
* into a {@link CouchbaseStorable}
*/
private CouchbaseStorable decodeAndUnwrap(final Document<String> source, final CouchbaseStorable target) {
//TODO at some point the necessity of CouchbaseStorable should be re-evaluated
return translationService.decode(source.content(), target);
}
@Override
public Bucket getCouchbaseBucket() {
return this.syncClient;
}
@Override
public ClusterInfo getCouchbaseClusterInfo() {
return this.clusterInfo;
}
private enum PersistType {
SAVE("Save", "Upsert"),
INSERT("Insert", "Insert"),
UPDATE("Update", "Replace");
private final String sdkOperationName;
private final String springDataOperationName;
PersistType(String sdkOperationName, String springDataOperationName) {
this.sdkOperationName = sdkOperationName;
this.springDataOperationName = springDataOperationName;
}
}
}

View File

@@ -16,34 +16,34 @@
package org.springframework.data.couchbase.core;
import com.couchbase.client.java.util.features.CouchbaseFeature;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.NonTransientDataAccessException;
import com.couchbase.client.core.service.ServiceType;
/**
* A {@link NonTransientDataAccessException} that denotes that a particular feature is expected
* on the server side but is not available.
* A {@link NonTransientDataAccessException} that denotes that a particular feature is expected on the server side but
* is not available.
*/
public class UnsupportedCouchbaseFeatureException extends InvalidDataAccessApiUsageException {
private final CouchbaseFeature feature;
private final ServiceType feature;
public UnsupportedCouchbaseFeatureException(String msg, CouchbaseFeature feature) {
super(msg);
this.feature = feature;
}
public UnsupportedCouchbaseFeatureException(String msg, ServiceType feature) {
super(msg);
this.feature = feature;
}
public UnsupportedCouchbaseFeatureException(String msg, CouchbaseFeature feature, Throwable cause) {
super(msg, cause);
this.feature = feature;
}
public UnsupportedCouchbaseFeatureException(String msg, ServiceType feature, Throwable cause) {
super(msg, cause);
this.feature = feature;
}
/**
* @return the {@link CouchbaseFeature} that was missing (could be null if not
* a registered CouchbaseFeature, in which case see {@link #getMessage()}).
*/
public CouchbaseFeature getFeature() {
return feature;
}
/**
* @return the {@link ServiceType} that was missing (could be null if not a registered CouchbaseFeature, in which case
* see {@link #getMessage()}).
*/
public ServiceType getFeature() {
return feature;
}
}

View File

@@ -16,13 +16,13 @@
package org.springframework.data.couchbase.core.convert;
import java.util.Collections;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.convert.CustomConversions;
import java.util.Collections;
import org.springframework.data.convert.EntityInstantiators;
/**
* An abstract {@link CouchbaseConverter} that provides the basics for the {@link MappingCouchbaseConverter}.
@@ -32,79 +32,79 @@ import java.util.Collections;
*/
public abstract class AbstractCouchbaseConverter implements CouchbaseConverter, InitializingBean {
/**
* Contains the conversion service.
*/
protected final GenericConversionService conversionService;
/**
* Contains the conversion service.
*/
protected final GenericConversionService conversionService;
/**
* Contains the entity instantiators.
*/
protected EntityInstantiators instantiators = new EntityInstantiators();
/**
* Contains the entity instantiators.
*/
protected EntityInstantiators instantiators = new EntityInstantiators();
/**
* Holds the custom conversions.
*/
protected CustomConversions conversions = new CouchbaseCustomConversions(Collections.emptyList());
/**
* Holds the custom conversions.
*/
protected CustomConversions conversions = new CouchbaseCustomConversions(Collections.emptyList());
/**
* Create a new converter and hand it over the {@link ConversionService}
*
* @param conversionService the conversion service to use.
*/
protected AbstractCouchbaseConverter(final GenericConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Create a new converter and hand it over the {@link ConversionService}
*
* @param conversionService the conversion service to use.
*/
protected AbstractCouchbaseConverter(final GenericConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Return the conversion service.
*
* @return the conversion service.
*/
@Override
public ConversionService getConversionService() {
return conversionService;
}
/**
* Return the conversion service.
*
* @return the conversion service.
*/
@Override
public ConversionService getConversionService() {
return conversionService;
}
/**
* Set the custom conversions.
*
* @param conversions the conversions.
*/
public void setCustomConversions(final CustomConversions conversions) {
this.conversions = conversions;
}
/**
* Set the custom conversions.
*
* @param conversions the conversions.
*/
public void setCustomConversions(final CustomConversions conversions) {
this.conversions = conversions;
}
/**
* Set the entity instantiators.
*
* @param instantiators the instantiators.
*/
public void setInstantiators(final EntityInstantiators instantiators) {
this.instantiators = instantiators;
}
/**
* Set the entity instantiators.
*
* @param instantiators the instantiators.
*/
public void setInstantiators(final EntityInstantiators instantiators) {
this.instantiators = instantiators;
}
/**
* Do nothing after the properties set on the bean.
*/
@Override
public void afterPropertiesSet() {
conversions.registerConvertersIn(conversionService);
}
/**
* Do nothing after the properties set on the bean.
*/
@Override
public void afterPropertiesSet() {
conversions.registerConvertersIn(conversionService);
}
@Override
public Object convertForWriteIfNeeded(Object value) {
if (value == null) {
return null;
}
@Override
public Object convertForWriteIfNeeded(Object value) {
if (value == null) {
return null;
}
return this.conversions.getCustomWriteTarget(value.getClass()) //
.map(it -> (Object) this.conversionService.convert(value, it)) //
.orElse(value);
}
return this.conversions.getCustomWriteTarget(value.getClass()) //
.map(it -> (Object) this.conversionService.convert(value, it)) //
.orElse(value);
}
@Override
public Class<?> getWriteClassFor(Class<?> clazz) {
return this.conversions.getCustomWriteTarget(clazz).orElse(clazz);
}
@Override
public Class<?> getWriteClassFor(Class<?> clazz) {
return this.conversions.getCustomWriteTarget(clazz).orElse(clazz);
}
}

View File

@@ -28,89 +28,89 @@ import org.springframework.util.Assert;
*/
class ConverterRegistration {
private final ConvertiblePair convertiblePair;
private final boolean reading;
private final boolean writing;
private final ConvertiblePair convertiblePair;
private final boolean reading;
private final boolean writing;
/**
* Creates a new {@link ConverterRegistration}.
*
* @param convertiblePair must not be {@literal null}.
* @param isReading whether to force to consider the converter for reading.
* @param isWriting whether to force to consider the converter for reading.
*/
public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) {
Assert.notNull(convertiblePair, "ConvertiblePair must not be null!");
/**
* Creates a new {@link ConverterRegistration}.
*
* @param convertiblePair must not be {@literal null}.
* @param isReading whether to force to consider the converter for reading.
* @param isWriting whether to force to consider the converter for reading.
*/
public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) {
Assert.notNull(convertiblePair, "ConvertiblePair must not be null!");
this.convertiblePair = convertiblePair;
reading = isReading;
writing = isWriting;
}
this.convertiblePair = convertiblePair;
reading = isReading;
writing = isWriting;
}
/**
* Creates a new {@link ConverterRegistration} from the given source and target type and read/write flags.
*
* @param source the source type to be converted from, must not be {@literal null}.
* @param target the target type to be converted to, must not be {@literal null}.
* @param isReading whether to force to consider the converter for reading.
* @param isWriting whether to force to consider the converter for writing.
*/
public ConverterRegistration(Class<?> source, Class<?> target, boolean isReading, boolean isWriting) {
this(new ConvertiblePair(source, target), isReading, isWriting);
}
/**
* Creates a new {@link ConverterRegistration} from the given source and target type and read/write flags.
*
* @param source the source type to be converted from, must not be {@literal null}.
* @param target the target type to be converted to, must not be {@literal null}.
* @param isReading whether to force to consider the converter for reading.
* @param isWriting whether to force to consider the converter for writing.
*/
public ConverterRegistration(Class<?> source, Class<?> target, boolean isReading, boolean isWriting) {
this(new ConvertiblePair(source, target), isReading, isWriting);
}
/**
* Returns whether the converter shall be used for writing.
*
* @return
*/
public boolean isWriting() {
return writing == true || (!reading && isSimpleTargetType());
}
/**
* Returns whether the given type is a type that Couchbase can handle basically.
*
* @param type
* @return
*/
private static boolean isCouchbaseBasicType(Class<?> type) {
return CouchbaseSimpleTypes.JSON_TYPES.isSimpleType(type);
}
/**
* Returns whether the converter shall be used for reading.
*
* @return
*/
public boolean isReading() {
return reading == true || (!writing && isSimpleSourceType());
}
/**
* Returns whether the converter shall be used for writing.
*
* @return
*/
public boolean isWriting() {
return writing == true || (!reading && isSimpleTargetType());
}
/**
* Returns the actual conversion pair.
*
* @return
*/
public ConvertiblePair getConvertiblePair() {
return convertiblePair;
}
/**
* Returns whether the converter shall be used for reading.
*
* @return
*/
public boolean isReading() {
return reading == true || (!writing && isSimpleSourceType());
}
/**
* Returns whether the source type is a Couchbase simple one.
*
* @return
*/
public boolean isSimpleSourceType() {
return isCouchbaseBasicType(convertiblePair.getSourceType());
}
/**
* Returns the actual conversion pair.
*
* @return
*/
public ConvertiblePair getConvertiblePair() {
return convertiblePair;
}
/**
* Returns whether the target type is a Couchbase simple one.
*
* @return
*/
public boolean isSimpleTargetType() {
return isCouchbaseBasicType(convertiblePair.getTargetType());
}
/**
* Returns whether the source type is a Couchbase simple one.
*
* @return
*/
public boolean isSimpleSourceType() {
return isCouchbaseBasicType(convertiblePair.getSourceType());
}
/**
* Returns whether the given type is a type that Couchbase can handle basically.
*
* @param type
* @return
*/
private static boolean isCouchbaseBasicType(Class<?> type) {
return CouchbaseSimpleTypes.JSON_TYPES.isSimpleType(type);
}
/**
* Returns whether the target type is a Couchbase simple one.
*
* @return
*/
public boolean isSimpleTargetType() {
return isCouchbaseBasicType(convertiblePair.getTargetType());
}
}

View File

@@ -29,31 +29,29 @@ import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProper
* @author Simon Baslé
*/
public interface CouchbaseConverter
extends EntityConverter<CouchbasePersistentEntity<?>,
CouchbasePersistentProperty, Object, CouchbaseDocument>,
CouchbaseWriter<Object, CouchbaseDocument>,
EntityReader<Object, CouchbaseDocument> {
extends EntityConverter<CouchbasePersistentEntity<?>, CouchbasePersistentProperty, Object, CouchbaseDocument>,
CouchbaseWriter<Object, CouchbaseDocument>, EntityReader<Object, CouchbaseDocument> {
/**
* Convert the value if necessary to the class that would actually be stored,
* or leave it as is if no conversion needed.
*
* @param value the value to be converted to the class that would actually be stored.
* @return the converted value (or the same value if no conversion necessary).
*/
Object convertForWriteIfNeeded(Object value);
/**
* Convert the value if necessary to the class that would actually be stored, or leave it as is if no conversion
* needed.
*
* @param value the value to be converted to the class that would actually be stored.
* @return the converted value (or the same value if no conversion necessary).
*/
Object convertForWriteIfNeeded(Object value);
/**
* Return the Class that would actually be stored for a given Class.
*
* @param clazz the source class.
* @return the target class that would actually be stored.
* @see #convertForWriteIfNeeded(Object)
*/
Class<?> getWriteClassFor(Class<?> clazz);
/**
* Return the Class that would actually be stored for a given Class.
*
* @param clazz the source class.
* @return the target class that would actually be stored.
* @see #convertForWriteIfNeeded(Object)
*/
Class<?> getWriteClassFor(Class<?> clazz);
/**
* @return the name of the field that will hold type information.
*/
String getTypeKey();
/**
* @return the name of the field that will hold type information.
*/
String getTypeKey();
}

View File

@@ -16,17 +16,19 @@
package org.springframework.data.couchbase.core.convert;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mapping.model.SimpleTypeHolder;
/**
* Value object to capture custom conversion.
* <p/>
* <p>Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper
* inspection nor nested conversion.</p>
* <p>
* Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper inspection
* nor nested conversion.
* </p>
*
* @author Michael Nitschinger
* @author Oliver Gierke
@@ -38,27 +40,27 @@ import java.util.List;
*/
public class CouchbaseCustomConversions extends org.springframework.data.convert.CustomConversions {
private static final StoreConversions STORE_CONVERSIONS;
private static final StoreConversions STORE_CONVERSIONS;
private static final List<Object> STORE_CONVERTERS;
private static final List<Object> STORE_CONVERTERS;
static {
static {
List<Object> converters = new ArrayList<>();
List<Object> converters = new ArrayList<>();
converters.addAll(DateConverters.getConvertersToRegister());
converters.addAll(CouchbaseJsr310Converters.getConvertersToRegister());
converters.addAll(DateConverters.getConvertersToRegister());
converters.addAll(CouchbaseJsr310Converters.getConvertersToRegister());
STORE_CONVERTERS = Collections.unmodifiableList(converters);
STORE_CONVERSIONS = StoreConversions.of(SimpleTypeHolder.DEFAULT, STORE_CONVERTERS);
}
STORE_CONVERTERS = Collections.unmodifiableList(converters);
STORE_CONVERSIONS = StoreConversions.of(SimpleTypeHolder.DEFAULT, STORE_CONVERTERS);
}
/**
* Create a new instance with a given list of converters.
*
* @param converters the list of custom converters.
*/
public CouchbaseCustomConversions(final List<?> converters) {
super(STORE_CONVERSIONS, converters);
}
/**
* Create a new instance with a given list of converters.
*
* @param converters the list of custom converters.
*/
public CouchbaseCustomConversions(final List<?> converters) {
super(STORE_CONVERSIONS, converters);
}
}

Some files were not shown because too many files have changed in this diff Show More