diff --git a/README.adoc b/README.adoc
index dec2a4bd..1135b881 100644
--- a/README.adoc
+++ b/README.adoc
@@ -94,7 +94,7 @@ public class Config extends AbstractCouchbaseConfiguration {
}
@Override
- protected String getBucketPassword() {
+ protected String getPassword() {
return "";
}
}
diff --git a/pom.xml b/pom.xml
index f02e2b72..7b3fdc8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.dataspring-data-couchbase
- 4.0.0.BUILD-SNAPSHOT
+ 4.0.0-BUILD-SNAPSHOTSpring Data CouchbaseSpring Data integration for Couchbase
@@ -18,8 +18,8 @@
- 2.7.13
- 2.7.13
+ 3.0.1
+ 3.0.12.3.0.BUILD-SNAPSHOTspring.data.couchbase
@@ -37,7 +37,6 @@
-
org.springframeworkspring-context
@@ -99,35 +98,7 @@
io.projectreactorreactor-core
- true
-
-
-
- io.reactivex
- rxjava
- ${rxjava}
- true
-
-
-
- io.reactivex
- rxjava-reactive-streams
- ${rxjava-reactive-streams}
- true
-
-
-
- io.reactivex.rxjava2
- rxjava
- ${rxjava2}
- true
-
-
-
-
- com.couchbase.client
- encryption
- 1.0.0
+ 3.2.12.RELEASE
@@ -182,6 +153,20 @@
test
+
+ com.github.Couchbase
+ CouchbaseMock
+ 73e493d259
+ test
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.4.0
+ test
+
+
org.jetbrains.kotlin
@@ -208,6 +193,16 @@
spring-libs-snapshothttps://repo.spring.io/libs-snapshot
+
+ sonatype-snapshot
+ https://oss.sonatype.org/content/repositories/snapshots
+ true
+ false
+
+
+ jitpack.io
+ https://jitpack.io
+
diff --git a/src/main/asciidoc/ansijoins.adoc b/src/main/asciidoc/ansijoins.adoc
index 7fdc27a4..732f39ae 100644
--- a/src/main/asciidoc/ansijoins.adoc
+++ b/src/main/asciidoc/ansijoins.adoc
@@ -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 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
diff --git a/src/main/asciidoc/autokeygeneration.adoc b/src/main/asciidoc/autokeygeneration.adoc
index 6998c2d7..16c64fa6 100644
--- a/src/main/asciidoc/autokeygeneration.adoc
+++ b/src/main/asciidoc/autokeygeneration.adoc
@@ -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.
- - <>
- - <>
+- <>
+- <>
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
====
diff --git a/src/main/asciidoc/configuration.adoc b/src/main/asciidoc/configuration.adoc
index 77749106..903927b7 100644
--- a/src/main/asciidoc/configuration.adoc
+++ b/src/main/asciidoc/configuration.adoc
@@ -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
----
====
-This code is equivalent to the java configuration approach shown above. You can customize the SDK `CouchbaseEnvironment` via the `` 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 `` 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.
diff --git a/src/main/asciidoc/entity.adoc b/src/main/asciidoc/entity.adoc
index 910912ca..ebbce57d 100644
--- a/src/main/asciidoc/entity.adoc
+++ b/src/main/asciidoc/entity.adoc
@@ -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 {
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
----
====
+
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` (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` (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
diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc
index 883dac30..701be78f 100644
--- a/src/main/asciidoc/index.adoc
+++ b/src/main/asciidoc/index.adoc
@@ -26,7 +26,6 @@ include::template.adoc[]
include::ansijoins.adoc[]
:leveloffset: -1
-
[[appendix]]
= Appendix
diff --git a/src/main/asciidoc/migrating.adoc b/src/main/asciidoc/migrating.adoc
index 3900e04d..bff3dbe4 100644
--- a/src/main/asciidoc/migrating.adoc
+++ b/src/main/asciidoc/migrating.adoc
@@ -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 (``), one or more `Bucket` beans (``) and even tune the SDK via a `CouchbaseEnvironment` bean (``). 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 (``), one or more `Bucket` beans (``) and even tune the SDK via a `CouchbaseEnvironment` bean (``).
+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.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 <> and <> 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 <>.
For instance, for a view emitting user lastNames, the following:
@@ -61,6 +67,7 @@ List 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).
\ No newline at end of file
+* 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).
\ No newline at end of file
diff --git a/src/main/asciidoc/reactiverepository.adoc b/src/main/asciidoc/reactiverepository.adoc
index 343906c6..a58959c0 100644
--- a/src/main/asciidoc/reactiverepository.adoc
+++ b/src/main/asciidoc/reactiverepository.adoc
@@ -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 <>.
+This chapter describes the reactive repository support for couchbase.
+This builds on the core repository support explained in <>.
So make sure you’ve 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>
\ No newline at end of file
+Spring Data's Reactive Couchbase comes with full querying support already provided by the blocking <>
diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc
index e42ede84..a9ce76e2 100644
--- a/src/main/asciidoc/repository.adoc
+++ b/src/main/asciidoc/repository.adoc
@@ -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:
- - <>
- - <>
- - <>
+- <>
+- <>
+- <>
CRUD operations are still mostly backed by Couchbase views (see <>).
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 <>).
@@ -17,7 +17,9 @@ Note that you can tune the consistency you want for your queries (see <>.
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`, 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`, 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 {
----
====
-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 {
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 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 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 <> 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 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 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 {
----
====
-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 <> 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 ``.
- - in javaConfig, this is done by overriding the `getDefaultConsistency()` method.
+- in xml, this is done via the `consistency` attribute on ``.
+- 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 {
<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 <>).
- - 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 <>).
+
+- 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 <> 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 <> 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 {
}
----
-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 {}
----
-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 {
}
----
-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.
diff --git a/src/main/asciidoc/template.adoc b/src/main/asciidoc/template.adoc
index 5374165e..faab8941 100644
--- a/src/main/asciidoc/template.adoc
+++ b/src/main/asciidoc/template.adoc
@@ -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.
diff --git a/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java
new file mode 100644
index 00000000..0586ea3a
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java
@@ -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);
+
+}
diff --git a/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java
new file mode 100644
index 00000000..b7e71999
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java
@@ -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();
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/couchbase/cache/CacheKeyPrefix.java b/src/main/java/org/springframework/data/couchbase/cache/CacheKeyPrefix.java
new file mode 100644
index 00000000..0421be76
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/cache/CacheKeyPrefix.java
@@ -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);
+
+}
diff --git a/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCache.java b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCache.java
new file mode 100644
index 00000000..bc9cbaea
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCache.java
@@ -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 valueFromLoader(Object key, Callable 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 get(final Object key, final Callable 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));
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java
new file mode 100644
index 00000000..841815b4
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheConfiguration.java
@@ -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:
+ *
+ *
{@link String} to {@link byte byte[]} using UTF-8 encoding.
+ *
{@link SimpleKey} to {@link String}
+ *
+ * @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.
+ * NOTE 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.
+ * 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;
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java
new file mode 100644
index 00000000..c501ca05
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheManager.java
@@ -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 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 caches = new LinkedList<>();
+
+ for (Map.Entry 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);
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheWriter.java b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheWriter.java
new file mode 100644
index 00000000..20eb4113
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheWriter.java
@@ -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);
+
+}
diff --git a/src/main/java/org/springframework/data/couchbase/cache/DefaultCouchbaseCacheWriter.java b/src/main/java/org/springframework/data/couchbase/cache/DefaultCouchbaseCacheWriter.java
new file mode 100644
index 00000000..8e501b7a
--- /dev/null
+++ b/src/main/java/org/springframework/data/couchbase/cache/DefaultCouchbaseCacheWriter.java
@@ -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);
+ }
+}
diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java
index 99730e2a..8f4404e1 100644
--- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java
+++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java
@@ -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 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> getInitialEntitySet() throws ClassNotFoundException {
+ String basePackage = getMappingBasePackage();
+ Set> initialEntitySet = new HashSet>();
+
+ 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.
+ *
+ *
+ * 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.
+ *
+ *
+ * @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;
+ }
}
diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfiguration.java
deleted file mode 100644
index fe75db01..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfiguration.java
+++ /dev/null
@@ -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
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseConfiguration.java
deleted file mode 100644
index 843080da..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseConfiguration.java
+++ /dev/null
@@ -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 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());
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseDataConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseDataConfiguration.java
deleted file mode 100644
index 50c9ed30..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/AbstractReactiveCouchbaseDataConfiguration.java
+++ /dev/null
@@ -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
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java
index 97e4e7ba..71997f7e 100644
--- a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java
+++ b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java
@@ -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 "<couchbase:env />" 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 "<couchbase:template />"
+ * 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 "<couchbase:cluster />" 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 "<couchbase:bucket />" 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 "<couchbase:template />" 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 "<couchbase:translation-service />" 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 "<couchbase:clusterInfo />" 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 "<couchbase:template />" 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 "<couchbase:template />" 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";
}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseBucketFactoryBean.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseBucketFactoryBean.java
deleted file mode 100644
index 4c33b690..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseBucketFactoryBean.java
+++ /dev/null
@@ -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 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);
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseBucketParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseBucketParser.java
deleted file mode 100644
index b68b28d2..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseBucketParser.java
+++ /dev/null
@@ -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 {@value BeanNames#COUCHBASE_CLUSTER} 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 cluster-ref attribute in a bucket definition defines the cluster to build from.
- */
- public static final String CLUSTER_REF_ATTR = "cluster-ref";
-
- /**
- * The bucketName attribute in a bucket definition defines the name of the bucket to open.
- */
- public static final String BUCKETNAME_ATTR = "bucketName";
-
- /*
- * The username attribute in a bucket definition defines the user of the bucket to open.
- */
- public static final String USERNAME_ATTR = "username";
-
- /**
- * The bucketPassword 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);
- }
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoFactoryBean.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoFactoryBean.java
deleted file mode 100644
index e1668a66..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoFactoryBean.java
+++ /dev/null
@@ -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 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);
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoParser.java
deleted file mode 100644
index daee95e2..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterInfoParser.java
+++ /dev/null
@@ -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 {@value BeanNames#COUCHBASE_CLUSTER_INFO} is used.
- *
- * See attributes {@link #CLUSTER_REF_ATTR}, {@link #LOGIN_ATTR} and {@link #PASSWORD_ATTR}.
- *
- * @author Simon Baslé
- */
-public class CouchbaseClusterInfoParser extends AbstractSingleBeanDefinitionParser {
-
- /**
- * The cluster-ref attribute in a cluster info definition defines the cluster to build from.
- */
- public static final String CLUSTER_REF_ATTR = "cluster-ref";
-
- /**
- * The login 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 password 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);
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterParser.java
deleted file mode 100644
index 79b14629..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseClusterParser.java
+++ /dev/null
@@ -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 <{@value #CLUSTER_ENVIRONMENT_TAG}> 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 <{@value #CLUSTER_NODE_TAG}> tags.
- *
- * @author Simon Baslé
- */
-public class CouchbaseClusterParser extends AbstractSingleBeanDefinitionParser {
-
- /**
- * The <node> elements in a cluster definition define the bootstrap hosts to use
- */
- public static final String CLUSTER_NODE_TAG = "node";
-
- /**
- * The unique <env> 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 <env-ref> 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 nodes = DomUtils.getChildElementsByTagName(element, CLUSTER_NODE_TAG);
- if (nodes != null && nodes.size() > 0) {
- List bootstrapUrls = new ArrayList(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);
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurationSupport.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurationSupport.java
deleted file mode 100644
index a2b83ed8..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurationSupport.java
+++ /dev/null
@@ -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> getInitialEntitySet() throws ClassNotFoundException {
- String basePackage = getMappingBasePackage();
- Set> initialEntitySet = new HashSet>();
-
- 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.
- *
- * 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.
- *
- *
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.
- *
- * @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;
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurer.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurer.java
deleted file mode 100644
index 4d238f9b..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseConfigurer.java
+++ /dev/null
@@ -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;
-
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentFactoryBean.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentFactoryBean.java
deleted file mode 100644
index de6351e0..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentFactoryBean.java
+++ /dev/null
@@ -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 {
-
- 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);
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownInvocationHandler.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownInvocationHandler.java
deleted file mode 100644
index 5dcb61d2..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownInvocationHandler.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentParser.java
deleted file mode 100644
index 3d8065f3..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentParser.java
+++ /dev/null
@@ -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.
- *
- *
- * @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");
- }
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseIndexManagerParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseIndexManagerParser.java
deleted file mode 100644
index ab22ab2c..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseIndexManagerParser.java
+++ /dev/null
@@ -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);
- }
-
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseJmxParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseJmxParser.java
deleted file mode 100644
index 8ce08c8b..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseJmxParser.java
+++ /dev/null
@@ -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 "" configuration bean.
- *
- * 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));
- }
-
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java
deleted file mode 100644
index 77b88c09..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseNamespaceHandler.java
+++ /dev/null
@@ -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.
- *
- * 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());
- }
-
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java
deleted file mode 100644
index 571875ec..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java
+++ /dev/null
@@ -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 "" bean definitions.
- *
- * 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");
- }
- }
- }
-
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTranslationServiceParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseTranslationServiceParser.java
deleted file mode 100644
index 0cd65665..00000000
--- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTranslationServiceParser.java
+++ /dev/null
@@ -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 "" 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;
- }
-
-}
diff --git a/src/main/java/org/springframework/data/couchbase/config/package-info.java b/src/main/java/org/springframework/data/couchbase/config/package-info.java
index 9de02508..f3b69b0a 100644
--- a/src/main/java/org/springframework/data/couchbase/config/package-info.java
+++ b/src/main/java/org/springframework/data/couchbase/config/package-info.java
@@ -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;
diff --git a/src/main/java/org/springframework/data/couchbase/core/BucketCallback.java b/src/main/java/org/springframework/data/couchbase/core/CollectionCallback.java
similarity index 63%
rename from src/main/java/org/springframework/data/couchbase/core/BucketCallback.java
rename to src/main/java/org/springframework/data/couchbase/core/CollectionCallback.java
index 166603a7..5d936b25 100644
--- a/src/main/java/org/springframework/data/couchbase/core/BucketCallback.java
+++ b/src/main/java/org/springframework/data/couchbase/core/CollectionCallback.java
@@ -24,16 +24,16 @@ import java.util.concurrent.TimeoutException;
*
* @author Michael Nitschinger
*/
-public interface BucketCallback {
+public interface CollectionCallback {
- /**
- * 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;
}
diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseDataIntegrityViolationException.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseDataIntegrityViolationException.java
index 9a35d8c6..e7006588 100644
--- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseDataIntegrityViolationException.java
+++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseDataIntegrityViolationException.java
@@ -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);
+ }
}
diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java
index b5b679ea..3ae3cb21 100644
--- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java
+++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java
@@ -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;
+ }
}
diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java
index c0487acd..b98fcaa2 100644
--- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java
+++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java
@@ -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.
- *
- *
When the document already exists (specified by its unique id), then it will be overriden. Otherwise it will be
- * created.
- *
- * @param objectToSave the object to store in the bucket.
- */
- void save(Object objectToSave);
+ CouchbaseConverter getConverter();
- /**
- * Save the given object.
- *
- *
When the document already exists (specified by its unique id), then it will be overriden. Otherwise it will be
- * created.
- *
- * @param objectToSave the object to store in the bucket.
- */
- void save(Object objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
+ String getBucketName();
- /**
- * Save a list of objects.
- *
- *
When one of the documents already exists (specified by its unique id), then it will be overriden. Otherwise it
- * will be created.
- *
- * @param batchToSave the list of objects to store in the bucket.
- */
- void save(Collection> batchToSave);
+ String getScopeName();
- /**
- * Save a list of objects.
- *
- *
When one of the documents already exists (specified by its unique id), then it will be overriden. Otherwise it
- * will be created.
- *
- * @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.
- *
- *
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.
- *
- * @param objectToInsert the object to add to the bucket.
- */
- void insert(Object objectToInsert);
-
- /**
- * Insert the given object.
- *
- *
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.
- *
- * @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.
- *
- *
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.
- *
- * @param batchToInsert the list of objects to add to the bucket.
- */
- void insert(Collection> batchToInsert);
-
- /**
- * Insert a list of objects.
- *
- *
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.
- *
- * @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.
- *
- *
When the document does not exist (specified by its unique id) it will not be created. Use the
- * {@link CouchbaseOperations#save} method for this.
- *
- * @param objectToUpdate the object to add to the bucket.
- */
- void update(Object objectToUpdate);
-
- /**
- * Update the given object.
- *
- *
When the document does not exist (specified by its unique id) it will not be created. Use the
- * {@link CouchbaseOperations#save} method for this.
- *
- * @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.
- *
- *
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.
- *
- * @param batchToUpdate the list of objects to add to the bucket.
- */
- void update(Collection> batchToUpdate);
-
- /**
- * Insert a list of objects.
- *
- *
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.
- *
- * @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 findById(String id, Class entityClass);
-
- /**
- * Query a View for a list of documents of type T.
- *
- *
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.
- *
- *
Weak consistency in the query (stale(Stale.TRUE)) 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.
- *
- *
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.
- *
- * @param query the Query object (also specifying view design document and view name).
- * @param entityClass the entity to map to.
- * @return the converted collection
- */
- List findByView(ViewQuery query, Class entityClass);
-
-
- /**
- * Query a View with direct access to the {@link ViewResult}.
- *
This method is available to ease the working with views by still wrapping exceptions into the Spring
- * infrastructure.
- *
It is especially needed if you want to run reduced viewName queries, because they can't be mapped onto entities
- * directly.
- *
- * @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.
- *
- *
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.
- *
- *
Weak consistency in the query (stale(Stale.TRUE)) 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.
- *
- * @param query the SpatialViewQuery object (also specifying view design document and view name).
- * @param entityClass the entity to map to.
- * @return the converted collection
- */
- List findBySpatialView(SpatialViewQuery query, Class entityClass);
-
- /**
- * Query a Spatial View with direct access to the {@link SpatialViewResult}.
- *
This method is available to ease the working with spatial views by still wrapping exceptions into the Spring
- * infrastructure.
- *
- * @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.
- *
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.
- *
- * Use {@link N1qlQuery}'s factory methods to construct such a Query.
- *
- *
Weak consistency in the query (eg. ScanConsistency.NOT_BOUND) 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.
- *
- * @param n1ql the N1QL query.
- * @param entityClass the target class for the returned entities.
- * @param the entity class
- * @return the list of entities matching this query.
- * @throws CouchbaseQueryExecutionException if the id and cas are not selected.
- */
- List findByN1QL(N1qlQuery n1ql, Class 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".
- *
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.
- *
- * Use {@link N1qlQuery}'s factory methods to construct such a Query.
- *
- *
Weak consistency in the query (eg. ScanConsistency.NOT_BOUND) 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.
- *
- * @param n1ql the N1QL query.
- * @param fragmentClass the target class for the returned fragments.
- * @param the fragment class
- * @return the list of entities matching this query.
- */
- List findByN1QLProjection(N1qlQuery n1ql, Class fragmentClass);
-
- /**
- * Query the N1QL Service with direct access to the {@link N1qlQueryResult}.
- *
- * 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.
- *
- * Use {@link N1qlQuery}'s factory methods to construct this.
- *
- * @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.
- *
- * 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.
- *
- * 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.
- *
- * 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 the return type.
- * @return the return type.
- */
- T execute(BucketCallback 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();
}
diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java
index 2f9da5f4..fa600aba 100644
--- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java
+++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java
@@ -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 ExecutableUpsertById upsertById(final Class domainType) {
+ return new ExecutableUpsertByIdOperationSupport(this).upsertById(domainType);
+ }
+
+ @Override
+ public ExecutableInsertById insertById(Class domainType) {
+ return new ExecutableInsertByIdOperationSupport(this).insertById(domainType);
+ }
+
+ @Override
+ public ExecutableReplaceById replaceById(Class domainType) {
+ return new ExecutableReplaceByIdOperationSupport(this).replaceById(domainType);
+ }
+
+ @Override
+ public ExecutableFindById findById(Class domainType) {
+ return new ExecutableFindByIdOperationSupport(this).findById(domainType);
+ }
+
+ @Override
+ public ExecutableFindFromReplicasById findFromReplicasById(Class domainType) {
+ return new ExecutableFindFromReplicasByIdOperationSupport(this).findFromReplicasById(domainType);
+ }
+
+ @Override
+ public ExecutableFindByQuery findByQuery(Class domainType) {
+ return new ExecutableFindByQueryOperationSupport(this).findByQuery(domainType);
+ }
+
+ @Override
+ public ExecutableFindByAnalytics findByAnalytics(Class 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 ExecutableRemoveByQuery removeByQuery(Class 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 ITERABLE_CLASSES;
-
- static {
- final Set iterableClasses = new HashSet();
- 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 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<String>} containing a JSON string
- * into a {@link CouchbaseStorable}
- */
- private CouchbaseStorable decodeAndUnwrap(final Document 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 the enclosed type.
- */
- protected void maybeEmitEvent(final CouchbaseMappingEvent 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 findById(final String id, Class entityClass) {
- final CouchbasePersistentEntity> entity = mappingContext.getRequiredPersistentEntity(entityClass);
- RawJsonDocument result = execute(new BucketCallback() {
- @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 List findByView(ViewQuery query, final Class 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>() {
- @Override
- public Observable call(AsyncViewResult asyncViewResult) {
- return asyncViewResult
- .error()
- .flatMap(new Func1>() {
- @Override
- public Observable 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>() {
- @Override
- public Observable call(AsyncViewRow row) {
- final String id = row.id();
- return row
- .document(RawJsonDocument.class)
- .map(new Func1() {
- @Override
- public T call(RawJsonDocument rawJsonDocument) {
- //cope with potential weak consistency and deletions
- T entity = mapToEntity(id, rawJsonDocument, entityClass);
- return entity;
- }
- });
- }})
- .filter(new Func1() {
- @Override
- public Boolean call(T t) {
- return t != null;
- }
- })
- .onErrorResumeNext(new Func1>() {
- @Override
- public Observable 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() {
- @Override
- public ViewResult doInBucket() {
- return client.query(query);
- }
- });
- }
-
- @Override
- public List findBySpatialView(SpatialViewQuery query, Class 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 allRows = response.allRows();
-
- final List result = new ArrayList(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() {
- @Override
- public SpatialViewResult doInBucket() throws TimeoutException, ExecutionException, InterruptedException {
- return client.query(query);
- }
- });
- }
-
- @Override
- public List findByN1QL(N1qlQuery n1ql, Class entityClass) {
- checkN1ql();
- try {
- N1qlQueryResult queryResult = queryN1QL(n1ql);
-
- if (queryResult.finalSuccess()) {
- List allRows = queryResult.allRows();
- List result = new ArrayList(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 List findByN1QLProjection(N1qlQuery n1ql, Class entityClass) {
- checkN1ql();
- try {
- N1qlQueryResult queryResult = queryN1QL(n1ql);
-
- if (queryResult.finalSuccess()) {
- List allRows = queryResult.allRows();
- List result = new ArrayList(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() {
- @Override
- public N1qlQueryResult doInBucket() throws TimeoutException, ExecutionException, InterruptedException {
- return client.query(query);
- }
- });
- }
-
- @Override
- public boolean exists(final String id) {
- return execute(new BucketCallback() {
- @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 execute(BucketCallback 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 Observable executeAsync(Observable asyncAction) {
- return asyncAction
- .onErrorResumeNext(new Func1>() {
- @Override
- public Observable 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