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.data spring-data-couchbase - 4.0.0.BUILD-SNAPSHOT + 4.0.0-BUILD-SNAPSHOT Spring Data Couchbase Spring Data integration for Couchbase @@ -18,8 +18,8 @@ - 2.7.13 - 2.7.13 + 3.0.1 + 3.0.1 2.3.0.BUILD-SNAPSHOT spring.data.couchbase @@ -37,7 +37,6 @@ - org.springframework spring-context @@ -99,35 +98,7 @@ io.projectreactor reactor-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-snapshot https://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 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. - *

    - * The following properties are supported:

      - *
    • {@link DefaultCouchbaseEnvironment.Builder#managementTimeout(long) managementTimeout}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#queryTimeout(long) queryTimeout}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#viewTimeout(long) viewTimeout}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#kvTimeout(long) kvTimeout}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#connectTimeout(long) connectTimeout}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#disconnectTimeout(long) disconnectTimeout}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#dnsSrvEnabled(boolean) dnsSrvEnabled}
    • - * - *
    • {@link DefaultCouchbaseEnvironment.Builder#sslEnabled(boolean) sslEnabled}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#sslKeystoreFile(String) sslKeystoreFile}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#sslKeystorePassword(String) sslKeystorePassword}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpEnabled(boolean) bootstrapHttpEnabled}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierEnabled(boolean) bootstrapCarrierEnabled}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpDirectPort(int) bootstrapHttpDirectPort}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpSslPort(int) bootstrapHttpSslPort}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierDirectPort(int) bootstrapCarrierDirectPort}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierSslPort(int) bootstrapCarrierSslPort}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#ioPoolSize(int) ioPoolSize}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#computationPoolSize(int) computationPoolSize}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#responseBufferSize(int) responseBufferSize}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#requestBufferSize(int) requestBufferSize}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#kvEndpoints(int) kvEndpoints}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#viewEndpoints(int) viewEndpoints}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#queryEndpoints(int) queryEndpoints}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#retryStrategy(RetryStrategy) retryStrategy}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#maxRequestLifetime(long) maxRequestLifetime}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#keepAliveInterval(long) keepAliveInterval}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#autoreleaseAfter(long) autoreleaseAfter}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#bufferPoolingEnabled(boolean) bufferPoolingEnabled}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#tcpNodelayEnabled(boolean) tcpNodelayEnabled}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#mutationTokensEnabled(boolean) mutationTokensEnabled}
    • - *
    • {@link DefaultCouchbaseEnvironment.Builder#analyticsTimeout(long) analyticsTimeout}
    • - *
    - * - * @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, 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 accessor = getPropertyAccessor(objectToPersist); - final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(objectToPersist.getClass()); - final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty(); - final Long version = versionProperty != null ? accessor.getProperty(versionProperty, Long.class) : null; - - maybeEmitEvent(new BeforeConvertEvent(objectToPersist)); - final CouchbaseDocument converted = new CouchbaseDocument(); - converter.write(objectToPersist, converted); - - maybeEmitEvent(new BeforeSaveEvent(objectToPersist, converted)); - execute(new BucketCallback() { - @Override - public Boolean doInBucket() throws InterruptedException, ExecutionException { - String generatedId = addCommonPrefixAndSuffix(converted.getId()); - converted.setId(generatedId); - Document doc = encodeAndWrap(converted, version); - Document storedDoc; - //We will check version only if required - boolean versionPresent = versionProperty != null; - //If version is not set - assumption that document is new, otherwise updating - boolean existingDocument = version != null && version > 0L; - - try { - switch (persistType) { - case SAVE: - if (!versionPresent) { - //No version field - no cas - storedDoc = client.upsert(doc, persistTo, replicateTo); - } else if (existingDocument) { - //Updating existing document with cas - storedDoc = client.replace(doc, persistTo, replicateTo); - } else { - //Creating new document - storedDoc = client.insert(doc, persistTo, replicateTo); - } - break; - case UPDATE: - storedDoc = client.replace(doc, persistTo, replicateTo); - break; - case INSERT: - default: - storedDoc = client.insert(doc, persistTo, replicateTo); - break; - } - CouchbasePersistentProperty idProperty = persistentEntity.getIdProperty(); - Object entityId = accessor.getProperty(idProperty); - if (!generatedId.equals(entityId)) { - accessor.setProperty(idProperty, generatedId); - } - - if (storedDoc != null && storedDoc.cas() != 0) { - //inject new cas into the bean - if (versionProperty != null) { - accessor.setProperty(versionProperty, storedDoc.cas()); - } - return true; - } - return false; - } catch (DocumentAlreadyExistsException e) { - throw new OptimisticLockingFailureException(persistType.getSpringDataOperationName() + - " document with version value failed: " + version, e); - } catch (CASMismatchException e) { - throw new OptimisticLockingFailureException(persistType.getSpringDataOperationName() + - " document with version value failed: " + version, e); - } catch (Exception e) { - handleWriteResultError(persistType.getSpringDataOperationName() + " document failed: " + e.getMessage(), e); - return false; //this could be skipped if WriteResultChecking.EXCEPTION - } - } - }); - maybeEmitEvent(new AfterSaveEvent(objectToPersist, converted)); - } - - private void doRemove(final Object objectToRemove, final PersistTo persistTo, final ReplicateTo replicateTo) { - ensureNotIterable(objectToRemove); - - maybeEmitEvent(new BeforeDeleteEvent(objectToRemove)); - if (objectToRemove instanceof String) { - execute(new BucketCallback() { - @Override - public Boolean doInBucket() throws InterruptedException, ExecutionException { - try { - RawJsonDocument deletedDoc = client.remove((String) objectToRemove , persistTo, replicateTo, RawJsonDocument.class); - return deletedDoc != null; - } catch (Exception e) { - handleWriteResultError("Delete document failed: " + e.getMessage(), e); - return false; //this could be skipped if WriteResultChecking.EXCEPTION - } - } - }); - maybeEmitEvent(new AfterDeleteEvent(objectToRemove)); - return; - } - - final CouchbaseDocument converted = new CouchbaseDocument(); - converter.write(objectToRemove, converted); - - execute(new BucketCallback() { - @Override - public Boolean doInBucket() { - try { - RawJsonDocument deletedDoc = client.remove(addCommonPrefixAndSuffix(converted.getId()), persistTo, replicateTo - , RawJsonDocument.class); - return deletedDoc != null; - } catch (Exception e) { - handleWriteResultError("Delete document failed: " + e.getMessage(), e); - return false; //this could be skipped if WriteResultChecking.EXCEPTION - } - } - }); - maybeEmitEvent(new AfterDeleteEvent(objectToRemove)); - } - - private T mapToEntity(String id, Document data, Class entityClass) { - - if (data == null) { - return null; - } - - final CouchbaseDocument converted = new CouchbaseDocument(id); - T readEntity = converter.read(entityClass, (CouchbaseDocument) decodeAndUnwrap(data, converted)); - - final ConvertingPropertyAccessor accessor = getPropertyAccessor(readEntity); - CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(readEntity.getClass()); - - if (persistentEntity.getVersionProperty() != null) { - accessor.setProperty(persistentEntity.getVersionProperty(), data.cas()); - } - - persistentEntity.doWithProperties((PropertyHandler) prop -> { - if (prop.isAnnotationPresent(N1qlJoin.class)) { - N1qlJoin definition = prop.findAnnotation(N1qlJoin.class); - TypeInformation type = prop.getTypeInformation().getActualType(); - Class clazz = type.getType(); - N1qlJoinResolver.N1qlJoinResolverParameters parameters = new N1qlJoinResolver.N1qlJoinResolverParameters(definition, id, persistentEntity.getTypeInformation(), type); - if (N1qlJoinResolver.isLazyJoin(definition)) { - N1qlJoinResolver.N1qlJoinProxy proxy = new N1qlJoinResolver.N1qlJoinProxy(this, parameters); - accessor.setProperty(prop, java.lang.reflect.Proxy.newProxyInstance(List.class.getClassLoader(), - new Class[]{List.class}, proxy)); - } else { - accessor.setProperty(prop, N1qlJoinResolver.doResolve(this, parameters, clazz)); - } - } - }); - - return accessor.getBean(); - } - - private final ConvertingPropertyAccessor getPropertyAccessor(T source) { - - CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(source.getClass()); - PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); - - return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService()); - } - - private void checkN1ql() { - if (!getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL)) { - throw new UnsupportedCouchbaseFeatureException("Detected usage of N1QL in template, which is unsupported on this cluster", - CouchbaseFeature.N1QL); - } - } - - @Override - public Bucket getCouchbaseBucket() { - return this.client; - } - - @Override - public ClusterInfo getCouchbaseClusterInfo() { - return this.clusterInfo; - } - - @Override - public CouchbaseConverter getConverter() { - return this.converter; - } - - @Override - public Consistency getDefaultConsistency() { - return configuredConsistency; - } - - public void setDefaultConsistency(Consistency consistency) { - this.configuredConsistency = consistency; - } - - private enum PersistType { - SAVE("Save", "Upsert"), - INSERT("Insert", "Insert"), - UPDATE("Update", "Replace"); - - private final String sdkOperationName; - private final String springDataOperationName; - - PersistType(String sdkOperationName, String springDataOperationName) { - this.sdkOperationName = sdkOperationName; - this.springDataOperationName = springDataOperationName; - } - - public String getSdkOperationName() { - return sdkOperationName; - } - - public String getSpringDataOperationName() { - return springDataOperationName; - } - } - - @Override - public void keySettings(KeySettings settings) { - if (this.keySettings != null) { - throw new UnsupportedOperationException("Key settings is already set, it is no longer mutable"); - } - this.keySettings = settings; - } - - @Override - public KeySettings keySettings() { - return this.keySettings; - } - - @Override - public String getGeneratedId(Object entity) { - ensureNotIterable(entity); - CouchbaseDocument converted = new CouchbaseDocument(); - converter.write(entity, converted); - return addCommonPrefixAndSuffix(converted.getId()); - } - - private String addCommonPrefixAndSuffix(final String id) { - String convertedKey = id; - if (this.keySettings == null) { - return id; - } - String prefix = this.keySettings.prefix(); - String delimiter = this.keySettings.delimiter(); - String suffix = this.keySettings.suffix(); - if (prefix != null && !prefix.equals("")) { - convertedKey = prefix + delimiter + convertedKey; - } - if (suffix != null && !suffix.equals("")) { - convertedKey = convertedKey + delimiter + suffix; - } - return convertedKey; - } } diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java new file mode 100644 index 00000000..77a8328c --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core; + +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService; +import org.springframework.data.couchbase.core.convert.translation.TranslationService; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; + +public class CouchbaseTemplateSupport { + + private final CouchbaseConverter converter; + private final MappingContext, CouchbasePersistentProperty> mappingContext; + // TODO: this should be replaced I think + private final TranslationService translationService; + + public CouchbaseTemplateSupport(final CouchbaseConverter converter) { + this.converter = converter; + this.mappingContext = converter.getMappingContext(); + this.translationService = new JacksonTranslationService(); + } + + public CouchbaseDocument encodeEntity(final Object entityToEncode) { + final CouchbaseDocument converted = new CouchbaseDocument(); + converter.write(entityToEncode, converted); + return converted; + } + + public T decodeEntity(String id, String source, long cas, Class entityClass) { + final CouchbaseDocument converted = new CouchbaseDocument(id); + converted.setId(id); + + T readEntity = converter.read(entityClass, (CouchbaseDocument) translationService.decode(source, converted)); + final ConvertingPropertyAccessor accessor = getPropertyAccessor(readEntity); + CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(readEntity.getClass()); + + if (persistentEntity.getVersionProperty() != null) { + accessor.setProperty(persistentEntity.getVersionProperty(), cas); + } + return accessor.getBean(); + } + + public void applyUpdatedCas(final Object entity, final long cas) { + final ConvertingPropertyAccessor accessor = getPropertyAccessor(entity); + final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass()); + final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty(); + + if (versionProperty != null) { + accessor.setProperty(versionProperty, cas); + } + } + + public String getJavaNameForEntity(final Class clazz) { + final CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(clazz); + MappingCouchbaseEntityInformation info = new MappingCouchbaseEntityInformation<>(persistentEntity); + return info.getJavaType().getName(); + } + + private ConvertingPropertyAccessor getPropertyAccessor(final T source) { + CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(source.getClass()); + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); + return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService()); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperation.java new file mode 100644 index 00000000..2ebf3740 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperation.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.core; + +import java.util.Collection; +import java.util.Map; + +public interface ExecutableExistsByIdOperation { + + ExecutableExistsById existsById(); + + interface TerminatingExistsById { + + boolean one(String id); + + Map all(Collection ids); + + } + + interface ExistsByIdWithCollection extends TerminatingExistsById { + + TerminatingExistsById inCollection(String collection); + } + + interface ExecutableExistsById extends ExistsByIdWithCollection {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java new file mode 100644 index 00000000..b8a63a9e --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableExistsByIdOperationSupport.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.util.Assert; + +public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByIdOperation { + + private final CouchbaseTemplate template; + + ExecutableExistsByIdOperationSupport(CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableExistsById existsById() { + return new ExecutableExistsByIdSupport(template, null); + } + + static class ExecutableExistsByIdSupport implements ExecutableExistsById { + + private final CouchbaseTemplate template; + private final ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport reactiveSupport; + + ExecutableExistsByIdSupport(final CouchbaseTemplate template, final String collection) { + this.template = template; + this.reactiveSupport = new ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport(template.reactive(), + collection); + } + + @Override + public boolean one(final String id) { + return reactiveSupport.one(id).block(); + } + + @Override + public Map all(final Collection ids) { + return reactiveSupport.all(ids).block(); + } + + @Override + public TerminatingExistsById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ExecutableExistsByIdSupport(template, collection); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperation.java new file mode 100644 index 00000000..dd33027f --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperation.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.springframework.data.couchbase.core.query.AnalyticsQuery; +import org.springframework.lang.Nullable; + +public interface ExecutableFindByAnalyticsOperation { + + ExecutableFindByAnalytics findByAnalytics(Class domainType); + + interface TerminatingFindByAnalytics { + /** + * Get exactly zero or one result. + * + * @return {@link Optional#empty()} if no match found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. + */ + default Optional one() { + return Optional.ofNullable(oneValue()); + } + + /** + * Get exactly zero or one result. + * + * @return {@literal null} if no match found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. + */ + @Nullable + T oneValue(); + + /** + * Get the first or no result. + * + * @return {@link Optional#empty()} if no match found. + */ + default Optional first() { + return Optional.ofNullable(firstValue()); + } + + /** + * Get the first or no result. + * + * @return {@literal null} if no match found. + */ + @Nullable + T firstValue(); + + /** + * Get all matching elements. + * + * @return never {@literal null}. + */ + List all(); + + /** + * Stream all matching elements. + * + * @return a {@link Stream} of results. Never {@literal null}. + */ + Stream stream(); + + /** + * Get the number of matching elements. + * + * @return total number of matching elements. + */ + long count(); + + /** + * Check for the presence of matching elements. + * + * @return {@literal true} if at least one matching element exists. + */ + boolean exists(); + + } + + /** + * Terminating operations invoking the actual query execution. + * + * @author Christoph Strobl + * @since 2.0 + */ + interface FindByAnalyticsWithQuery extends TerminatingFindByAnalytics { + + /** + * Set the filter query to be used. + * + * @param query must not be {@literal null}. + * @return new instance of {@link TerminatingFindByAnalytics}. + * @throws IllegalArgumentException if query is {@literal null}. + */ + TerminatingFindByAnalytics matching(AnalyticsQuery query); + + } + + interface ExecutableFindByAnalytics extends FindByAnalyticsWithQuery {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java new file mode 100644 index 00000000..d1380986 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByAnalyticsOperationSupport.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.data.couchbase.core.query.AnalyticsQuery; + +public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFindByAnalyticsOperation { + + private static final AnalyticsQuery ALL_QUERY = new AnalyticsQuery(); + + private final CouchbaseTemplate template; + + public ExecutableFindByAnalyticsOperationSupport(final CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableFindByAnalytics findByAnalytics(final Class domainType) { + return new ExecutableFindByAnalyticsSupport<>(template, domainType, ALL_QUERY); + } + + static class ExecutableFindByAnalyticsSupport implements ExecutableFindByAnalytics { + + private final CouchbaseTemplate template; + private final Class domainType; + private final ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport reactiveSupport; + + ExecutableFindByAnalyticsSupport(final CouchbaseTemplate template, final Class domainType, + final AnalyticsQuery query) { + this.template = template; + this.domainType = domainType; + this.reactiveSupport = new ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport<>( + template.reactive(), domainType, query); + } + + @Override + public T oneValue() { + return reactiveSupport.one().block(); + } + + @Override + public T firstValue() { + return reactiveSupport.first().block(); + } + + @Override + public List all() { + return reactiveSupport.all().collectList().block(); + } + + @Override + public TerminatingFindByAnalytics matching(final AnalyticsQuery query) { + return new ExecutableFindByAnalyticsSupport<>(template, domainType, query); + } + + @Override + public Stream stream() { + return reactiveSupport.all().toStream(); + } + + @Override + public long count() { + return reactiveSupport.count().block(); + } + + @Override + public boolean exists() { + return count() > 0; + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java new file mode 100644 index 00000000..b19d31e6 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperation.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +public interface ExecutableFindByIdOperation { + + ExecutableFindById findById(Class domainType); + + interface TerminatingFindById { + + T one(String id); + + Collection all(Collection ids); + + } + + interface FindByIdWithCollection extends TerminatingFindById { + + TerminatingFindById inCollection(String collection); + } + + interface FindByIdWithProjection extends FindByIdWithCollection { + + FindByIdWithCollection project(String... fields); + + } + + interface ExecutableFindById extends FindByIdWithProjection {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java new file mode 100644 index 00000000..ad4a333b --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByIdOperationSupport.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.util.Assert; + +public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOperation { + + private final CouchbaseTemplate template; + + ExecutableFindByIdOperationSupport(CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableFindById findById(Class domainType) { + return new ExecutableFindByIdSupport<>(template, domainType, null, null); + } + + static class ExecutableFindByIdSupport implements ExecutableFindById { + + private final CouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final List fields; + private final ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport reactiveSupport; + + ExecutableFindByIdSupport(CouchbaseTemplate template, Class domainType, String collection, List fields) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.fields = fields; + this.reactiveSupport = new ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport<>(template.reactive(), + domainType, collection, fields); + } + + @Override + public T one(final String id) { + return reactiveSupport.one(id).block(); + } + + @Override + public Collection all(final Collection ids) { + return reactiveSupport.all(ids).collectList().block(); + } + + @Override + public TerminatingFindById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ExecutableFindByIdSupport<>(template, domainType, collection, fields); + } + + @Override + public FindByIdWithCollection project(String... fields) { + Assert.notEmpty(fields, "Fields must not be null nor empty."); + return new ExecutableFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields)); + } + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java new file mode 100644 index 00000000..f51dd37b --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperation.java @@ -0,0 +1,125 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.lang.Nullable; + +import com.couchbase.client.java.query.QueryScanConsistency; + +public interface ExecutableFindByQueryOperation { + + ExecutableFindByQuery findByQuery(Class domainType); + + interface TerminatingFindByQuery { + /** + * Get exactly zero or one result. + * + * @return {@link Optional#empty()} if no match found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. + */ + default Optional one() { + return Optional.ofNullable(oneValue()); + } + + /** + * Get exactly zero or one result. + * + * @return {@literal null} if no match found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found. + */ + @Nullable + T oneValue(); + + /** + * Get the first or no result. + * + * @return {@link Optional#empty()} if no match found. + */ + default Optional first() { + return Optional.ofNullable(firstValue()); + } + + /** + * Get the first or no result. + * + * @return {@literal null} if no match found. + */ + @Nullable + T firstValue(); + + /** + * Get all matching elements. + * + * @return never {@literal null}. + */ + List all(); + + /** + * Stream all matching elements. + * + * @return a {@link Stream} of results. Never {@literal null}. + */ + Stream stream(); + + /** + * Get the number of matching elements. + * + * @return total number of matching elements. + */ + long count(); + + /** + * Check for the presence of matching elements. + * + * @return {@literal true} if at least one matching element exists. + */ + boolean exists(); + + } + + /** + * Terminating operations invoking the actual query execution. + * + * @author Christoph Strobl + * @since 2.0 + */ + interface FindByQueryWithQuery extends TerminatingFindByQuery { + + /** + * Set the filter query to be used. + * + * @param query must not be {@literal null}. + * @return new instance of {@link TerminatingFindByQuery}. + * @throws IllegalArgumentException if query is {@literal null}. + */ + TerminatingFindByQuery matching(Query query); + + } + + interface FindByQueryConsistentWith extends FindByQueryWithQuery { + + FindByQueryWithQuery consistentWith(QueryScanConsistency scanConsistency); + + } + + interface ExecutableFindByQuery extends FindByQueryConsistentWith {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java new file mode 100644 index 00000000..22dd038e --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindByQueryOperationSupport.java @@ -0,0 +1,100 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.data.couchbase.core.query.Query; + +import com.couchbase.client.java.query.QueryScanConsistency; + +public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQueryOperation { + + private static final Query ALL_QUERY = new Query(); + + private final CouchbaseTemplate template; + + public ExecutableFindByQueryOperationSupport(final CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableFindByQuery findByQuery(final Class domainType) { + return new ExecutableFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED); + } + + static class ExecutableFindByQuerySupport implements ExecutableFindByQuery { + + private final CouchbaseTemplate template; + private final Class domainType; + private final Query query; + private final ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport reactiveSupport; + private final QueryScanConsistency scanConsistency; + + ExecutableFindByQuerySupport(final CouchbaseTemplate template, final Class domainType, final Query query, + final QueryScanConsistency scanConsistency) { + this.template = template; + this.domainType = domainType; + this.query = query; + this.reactiveSupport = new ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport(template.reactive(), + domainType, query, scanConsistency); + this.scanConsistency = scanConsistency; + } + + @Override + public T oneValue() { + return reactiveSupport.one().block(); + } + + @Override + public T firstValue() { + return reactiveSupport.first().block(); + } + + @Override + public List all() { + return reactiveSupport.all().collectList().block(); + } + + @Override + public TerminatingFindByQuery matching(final Query query) { + return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency); + } + + @Override + public FindByQueryWithQuery consistentWith(final QueryScanConsistency scanConsistency) { + return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency); + } + + @Override + public Stream stream() { + return reactiveSupport.all().toStream(); + } + + @Override + public long count() { + return reactiveSupport.count().block(); + } + + @Override + public boolean exists() { + return count() > 0; + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperation.java new file mode 100644 index 00000000..96897e94 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperation.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +public interface ExecutableFindFromReplicasByIdOperation { + + ExecutableFindFromReplicasById findFromReplicasById(Class domainType); + + interface TerminatingFindFromReplicasById { + + T any(String id); + + Collection any(Collection ids); + + } + + interface FindFromReplicasByIdWithCollection extends TerminatingFindFromReplicasById { + + TerminatingFindFromReplicasById inCollection(String collection); + } + + interface ExecutableFindFromReplicasById extends FindFromReplicasByIdWithCollection {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java new file mode 100644 index 00000000..54e7f2c8 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableFindFromReplicasByIdOperationSupport.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +import org.springframework.util.Assert; + +public class ExecutableFindFromReplicasByIdOperationSupport implements ExecutableFindFromReplicasByIdOperation { + + private final CouchbaseTemplate template; + + ExecutableFindFromReplicasByIdOperationSupport(CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableFindFromReplicasById findFromReplicasById(Class domainType) { + return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, null); + } + + static class ExecutableFindFromReplicasByIdSupport implements ExecutableFindFromReplicasById { + + private final CouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport reactiveSupport; + + ExecutableFindFromReplicasByIdSupport(CouchbaseTemplate template, Class domainType, String collection) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.reactiveSupport = new ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport<>( + template.reactive(), domainType, collection); + } + + @Override + public T any(String id) { + return reactiveSupport.any(id).block(); + } + + @Override + public Collection any(Collection ids) { + return reactiveSupport.any(ids).collectList().block(); + } + + @Override + public TerminatingFindFromReplicasById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, collection); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperation.java new file mode 100644 index 00000000..44d6a763 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperation.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public interface ExecutableInsertByIdOperation { + + ExecutableInsertById insertById(Class domainType); + + interface TerminatingInsertById { + + T one(T object); + + Collection all(Collection objects); + + } + + interface InsertByIdWithCollection extends TerminatingInsertById { + + TerminatingInsertById inCollection(String collection); + } + + interface InsertByIdWithDurability extends InsertByIdWithCollection { + + InsertByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + + InsertByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + + } + + interface ExecutableInsertById extends InsertByIdWithDurability {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java new file mode 100644 index 00000000..df0b8f0f --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableInsertByIdOperationSupport.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +import org.springframework.util.Assert; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByIdOperation { + + private final CouchbaseTemplate template; + + public ExecutableInsertByIdOperationSupport(final CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableInsertById insertById(final Class domainType) { + Assert.notNull(domainType, "DomainType must not be null!"); + return new ExecutableInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, + DurabilityLevel.NONE); + } + + static class ExecutableInsertByIdSupport implements ExecutableInsertById { + + private final CouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final PersistTo persistTo; + private final ReplicateTo replicateTo; + private final DurabilityLevel durabilityLevel; + private final ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport reactiveSupport; + + ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, + final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.persistTo = persistTo; + this.replicateTo = replicateTo; + this.durabilityLevel = durabilityLevel; + this.reactiveSupport = new ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport<>(template.reactive(), + domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public T one(final T object) { + return reactiveSupport.one(object).block(); + } + + @Override + public Collection all(Collection objects) { + return reactiveSupport.all(objects).collectList().block(); + } + + @Override + public TerminatingInsertById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + @Override + public InsertByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + @Override + public InsertByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + Assert.notNull(persistTo, "PersistTo must not be null."); + Assert.notNull(replicateTo, "ReplicateTo must not be null."); + return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperation.java new file mode 100644 index 00000000..7287f696 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperation.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; +import java.util.List; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public interface ExecutableRemoveByIdOperation { + + ExecutableRemoveById removeById(); + + interface TerminatingRemoveById { + + RemoveResult one(String id); + + List all(Collection ids); + + } + + interface RemoveByIdWithCollection extends TerminatingRemoveById { + + TerminatingRemoveById inCollection(String collection); + } + + interface RemoveByIdWithDurability extends RemoveByIdWithCollection { + + RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + + RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + + } + + interface ExecutableRemoveById extends RemoveByIdWithDurability {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java new file mode 100644 index 00000000..27fed458 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByIdOperationSupport.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; +import java.util.List; + +import org.springframework.util.Assert; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByIdOperation { + + private final CouchbaseTemplate template; + + public ExecutableRemoveByIdOperationSupport(final CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableRemoveById removeById() { + return new ExecutableRemoveByIdSupport(template, null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE); + } + + static class ExecutableRemoveByIdSupport implements ExecutableRemoveById { + + private final CouchbaseTemplate template; + private final String collection; + private final PersistTo persistTo; + private final ReplicateTo replicateTo; + private final DurabilityLevel durabilityLevel; + private final ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport reactiveRemoveByIdSupport; + + ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final String collection, final PersistTo persistTo, + final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) { + this.template = template; + this.collection = collection; + this.persistTo = persistTo; + this.replicateTo = replicateTo; + this.durabilityLevel = durabilityLevel; + this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport( + template.reactive(), collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public RemoveResult one(final String id) { + return reactiveRemoveByIdSupport.one(id).block(); + } + + @Override + public List all(final Collection ids) { + return reactiveRemoveByIdSupport.all(ids).collectList().block(); + } + + @Override + public TerminatingRemoveById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public RemoveByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + Assert.notNull(persistTo, "PersistTo must not be null."); + Assert.notNull(replicateTo, "ReplicateTo must not be null."); + return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java new file mode 100644 index 00000000..397535e2 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperation.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.List; + +import org.springframework.data.couchbase.core.query.Query; + +import com.couchbase.client.java.query.QueryScanConsistency; + +public interface ExecutableRemoveByQueryOperation { + + ExecutableRemoveByQuery removeByQuery(Class domainType); + + interface TerminatingRemoveByQuery { + + List all(); + + } + + interface RemoveByQueryWithQuery extends TerminatingRemoveByQuery { + + TerminatingRemoveByQuery matching(Query query); + + } + + interface RemoveByQueryConsistentWith extends RemoveByQueryWithQuery { + + RemoveByQueryWithQuery consistentWith(QueryScanConsistency scanConsistency); + + } + + interface ExecutableRemoveByQuery extends RemoveByQueryConsistentWith {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java new file mode 100644 index 00000000..f1c4eced --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableRemoveByQueryOperationSupport.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.List; + +import org.springframework.data.couchbase.core.query.Query; + +import com.couchbase.client.java.query.QueryScanConsistency; + +public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemoveByQueryOperation { + + private static final Query ALL_QUERY = new Query(); + + private final CouchbaseTemplate template; + + public ExecutableRemoveByQueryOperationSupport(final CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableRemoveByQuery removeByQuery(Class domainType) { + return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED); + } + + static class ExecutableRemoveByQuerySupport implements ExecutableRemoveByQuery { + + private final CouchbaseTemplate template; + private final Class domainType; + private final Query query; + private final ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport reactiveSupport; + private final QueryScanConsistency scanConsistency; + + ExecutableRemoveByQuerySupport(final CouchbaseTemplate template, final Class domainType, final Query query, + final QueryScanConsistency scanConsistency) { + this.template = template; + this.domainType = domainType; + this.query = query; + this.reactiveSupport = new ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport<>( + template.reactive(), domainType, query, scanConsistency); + this.scanConsistency = scanConsistency; + } + + @Override + public List all() { + return reactiveSupport.all().collectList().block(); + } + + @Override + public TerminatingRemoveByQuery matching(final Query query) { + return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + } + + @Override + public RemoveByQueryWithQuery consistentWith(final QueryScanConsistency scanConsistency) { + return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java new file mode 100644 index 00000000..f613ffd5 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperation.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public interface ExecutableReplaceByIdOperation { + + ExecutableReplaceById replaceById(Class domainType); + + interface TerminatingReplaceById { + + T one(T object); + + Collection all(Collection objects); + + } + + interface ReplaceByIdWithCollection extends TerminatingReplaceById { + + TerminatingReplaceById inCollection(String collection); + } + + interface ReplaceByIdWithDurability extends ReplaceByIdWithCollection { + + ReplaceByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + + ReplaceByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + + } + + interface ExecutableReplaceById extends ReplaceByIdWithDurability {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java new file mode 100644 index 00000000..12ff3797 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableReplaceByIdOperationSupport.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +import org.springframework.util.Assert; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceByIdOperation { + + private final CouchbaseTemplate template; + + public ExecutableReplaceByIdOperationSupport(final CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableReplaceById replaceById(final Class domainType) { + Assert.notNull(domainType, "DomainType must not be null!"); + return new ExecutableReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, + DurabilityLevel.NONE); + } + + static class ExecutableReplaceByIdSupport implements ExecutableReplaceById { + + private final CouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final PersistTo persistTo; + private final ReplicateTo replicateTo; + private final DurabilityLevel durabilityLevel; + private final ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport reactiveSupport; + + ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, + final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.persistTo = persistTo; + this.replicateTo = replicateTo; + this.durabilityLevel = durabilityLevel; + this.reactiveSupport = new ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport<>(template.reactive(), + domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public T one(final T object) { + return reactiveSupport.one(object).block(); + } + + @Override + public Collection all(Collection objects) { + return reactiveSupport.all(objects).collectList().block(); + } + + @Override + public TerminatingReplaceById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + @Override + public ReplaceByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + @Override + public ReplaceByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + Assert.notNull(persistTo, "PersistTo must not be null."); + Assert.notNull(replicateTo, "ReplicateTo must not be null."); + return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperation.java new file mode 100644 index 00000000..dacd06d8 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperation.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public interface ExecutableUpsertByIdOperation { + + ExecutableUpsertById upsertById(Class domainType); + + interface TerminatingUpsertById { + + T one(T object); + + Collection all(Collection objects); + + } + + interface UpsertByIdWithCollection extends TerminatingUpsertById { + + TerminatingUpsertById inCollection(String collection); + } + + interface UpsertByIdWithDurability extends UpsertByIdWithCollection { + + UpsertByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + + UpsertByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + + } + + interface ExecutableUpsertById extends UpsertByIdWithDurability {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java new file mode 100644 index 00000000..3a4af6cd --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ExecutableUpsertByIdOperationSupport.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import java.util.Collection; + +import org.springframework.util.Assert; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByIdOperation { + + private final CouchbaseTemplate template; + + public ExecutableUpsertByIdOperationSupport(final CouchbaseTemplate template) { + this.template = template; + } + + @Override + public ExecutableUpsertById upsertById(final Class domainType) { + Assert.notNull(domainType, "DomainType must not be null!"); + return new ExecutableUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, + DurabilityLevel.NONE); + } + + static class ExecutableUpsertByIdSupport implements ExecutableUpsertById { + + private final CouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final PersistTo persistTo; + private final ReplicateTo replicateTo; + private final DurabilityLevel durabilityLevel; + private final ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport reactiveSupport; + + ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class domainType, final String collection, + final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.persistTo = persistTo; + this.replicateTo = replicateTo; + this.durabilityLevel = durabilityLevel; + this.reactiveSupport = new ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport<>(template.reactive(), + domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public T one(final T object) { + return reactiveSupport.one(object).block(); + } + + @Override + public Collection all(Collection objects) { + return reactiveSupport.all(objects).collectList().block(); + } + + @Override + public TerminatingUpsertById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + @Override + public UpsertByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + @Override + public UpsertByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + Assert.notNull(persistTo, "PersistTo must not be null."); + Assert.notNull(replicateTo, "ReplicateTo must not be null."); + return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/join/AddressRepository.java b/src/main/java/org/springframework/data/couchbase/core/FluentCouchbaseOperations.java similarity index 58% rename from src/test/java/org/springframework/data/couchbase/repository/join/AddressRepository.java rename to src/main/java/org/springframework/data/couchbase/core/FluentCouchbaseOperations.java index 35201b19..42a66082 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/join/AddressRepository.java +++ b/src/main/java/org/springframework/data/couchbase/core/FluentCouchbaseOperations.java @@ -13,14 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.couchbase.repository.join; -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.repository.CouchbaseRepository; +package org.springframework.data.couchbase.core; -/** - * @author Tayeb Chlyah - */ -@N1qlSecondaryIndexed(indexName = "addressIndex") -interface AddressRepository extends CouchbaseRepository { -} +public interface FluentCouchbaseOperations extends ExecutableUpsertByIdOperation, ExecutableInsertByIdOperation, + ExecutableReplaceByIdOperation, ExecutableFindByIdOperation, ExecutableFindFromReplicasByIdOperation, + ExecutableFindByQueryOperation, ExecutableFindByAnalyticsOperation, ExecutableExistsByIdOperation, + ExecutableRemoveByIdOperation, ExecutableRemoveByQueryOperation {} diff --git a/src/main/java/org/springframework/data/couchbase/core/OperationCancellationException.java b/src/main/java/org/springframework/data/couchbase/core/OperationCancellationException.java index cbee2680..9132a628 100644 --- a/src/main/java/org/springframework/data/couchbase/core/OperationCancellationException.java +++ b/src/main/java/org/springframework/data/couchbase/core/OperationCancellationException.java @@ -25,23 +25,23 @@ import org.springframework.dao.TransientDataAccessException; */ public class OperationCancellationException extends TransientDataAccessException { - /** - * Constructor for OperationCancellationException. - * - * @param msg the detail message - */ - public OperationCancellationException(final String msg) { - super(msg); - } + /** + * Constructor for OperationCancellationException. + * + * @param msg the detail message + */ + public OperationCancellationException(final String msg) { + super(msg); + } - /** - * Constructor for OperationCancellationException. - * - * @param msg the detail message - * @param cause the root cause from the data access API in use - */ - public OperationCancellationException(final String msg, final Throwable cause) { - super(msg, cause); - } + /** + * Constructor for OperationCancellationException. + * + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public OperationCancellationException(final String msg, final Throwable cause) { + super(msg, cause); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/OperationInterruptedException.java b/src/main/java/org/springframework/data/couchbase/core/OperationInterruptedException.java index 7890af54..8f9b5573 100644 --- a/src/main/java/org/springframework/data/couchbase/core/OperationInterruptedException.java +++ b/src/main/java/org/springframework/data/couchbase/core/OperationInterruptedException.java @@ -25,23 +25,23 @@ import org.springframework.dao.TransientDataAccessException; */ public class OperationInterruptedException extends TransientDataAccessException { - /** - * Constructor for OperationInterruptedException. - * - * @param msg the detail message - */ - public OperationInterruptedException(final String msg) { - super(msg); - } + /** + * Constructor for OperationInterruptedException. + * + * @param msg the detail message + */ + public OperationInterruptedException(final String msg) { + super(msg); + } - /** - * Constructor for OperationInterruptedException. - * - * @param msg the detail message - * @param cause the root cause from the data access API in use - */ - public OperationInterruptedException(final String msg, final Throwable cause) { - super(msg, cause); - } + /** + * Constructor for OperationInterruptedException. + * + * @param msg the detail message + * @param cause the root cause from the data access API in use + */ + public OperationInterruptedException(final String msg, final Throwable cause) { + super(msg, cause); + } } diff --git a/src/test/java/org/springframework/data/couchbase/repository/join/Address.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseOperations.java similarity index 54% rename from src/test/java/org/springframework/data/couchbase/repository/join/Address.java rename to src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseOperations.java index 5a0ee18d..f82bdbe9 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/join/Address.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseOperations.java @@ -13,37 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.couchbase.repository.join; -import org.springframework.data.annotation.Id; +package org.springframework.data.couchbase.core; + +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; /** - * @author Tayeb Chlyah + * Defines common operations on the Couchbase data source, most commonly implemented by {@link CouchbaseTemplate}. */ -public class Address { +public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOperations { - @Id - String id; + CouchbaseConverter getConverter(); - String name; + String getBucketName(); - String country; + String getScopeName(); - public Address(String id, String name, String country) { - this.id = id; - this.name = name; - this.country = country; - } + CouchbaseClientFactory getCouchbaseClientFactory(); - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public String getCountry() { - return country; - } } diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java new file mode 100644 index 00000000..1fffbd1b --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveCouchbaseTemplate.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; + +import com.couchbase.client.java.Collection; + +public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations { + + private final CouchbaseClientFactory clientFactory; + private final CouchbaseConverter converter; + private final PersistenceExceptionTranslator exceptionTranslator; + private final CouchbaseTemplateSupport templateSupport; + + public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) { + this.clientFactory = clientFactory; + this.converter = converter; + this.exceptionTranslator = clientFactory.getExceptionTranslator(); + this.templateSupport = new CouchbaseTemplateSupport(converter); + } + + @Override + public ReactiveFindById findById(Class domainType) { + return new ReactiveFindByIdOperationSupport(this).findById(domainType); + } + + @Override + public ReactiveExistsById existsById() { + return new ReactiveExistsByIdOperationSupport(this).existsById(); + } + + @Override + public ReactiveFindByAnalytics findByAnalytics(Class domainType) { + return new ReactiveFindByAnalyticsOperationSupport(this).findByAnalytics(domainType); + } + + @Override + public ReactiveFindByQuery findByQuery(Class domainType) { + return new ReactiveFindByQueryOperationSupport(this).findByQuery(domainType); + } + + @Override + public ReactiveFindFromReplicasById findFromReplicasById(Class domainType) { + return new ReactiveFindFromReplicasByIdOperationSupport(this).findFromReplicasById(domainType); + } + + @Override + public ReactiveInsertById insertById(Class domainType) { + return new ReactiveInsertByIdOperationSupport(this).insertById(domainType); + } + + @Override + public ReactiveRemoveById removeById() { + return new ReactiveRemoveByIdOperationSupport(this).removeById(); + } + + @Override + public ReactiveRemoveByQuery removeByQuery(Class domainType) { + return new ReactiveRemoveByQueryOperationSupport(this).removeByQuery(domainType); + } + + @Override + public ReactiveReplaceById replaceById(Class domainType) { + return new ReactiveReplaceByIdOperationSupport(this).replaceById(domainType); + } + + @Override + public ReactiveUpsertById upsertById(Class domainType) { + return new ReactiveUpsertByIdOperationSupport(this).upsertById(domainType); + } + + @Override + public String getBucketName() { + return clientFactory.getBucket().name(); + } + + @Override + public String getScopeName() { + return clientFactory.getScope().name(); + } + + @Override + public CouchbaseClientFactory getCouchbaseClientFactory() { + return clientFactory; + } + + /** + * Provides access to a {@link Collection} on the configured {@link CouchbaseClientFactory}. + * + * @param collectionName the name of the collection, if null is passed in the default collection is assumed. + * @return the collection instance. + */ + public Collection getCollection(final String collectionName) { + return clientFactory.getCollection(collectionName); + } + + @Override + public CouchbaseConverter getConverter() { + return converter; + } + + CouchbaseTemplateSupport support() { + return templateSupport; + } + + /** + * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original + * exception if the conversation failed. Thus allows safe re-throwing of the return value. + * + * @param ex the exception to translate + */ + RuntimeException potentiallyConvertRuntimeException(RuntimeException ex) { + RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex); + return resolved == null ? ex : resolved; + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperation.java new file mode 100644 index 00000000..81156db3 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperation.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Mono; + +import java.util.Collection; +import java.util.Map; + +public interface ReactiveExistsByIdOperation { + + ReactiveExistsById existsById(); + + interface TerminatingExistsById { + + Mono one(String id); + + Mono> all(Collection ids); + + } + + interface ExistsByIdWithCollection extends TerminatingExistsById { + + TerminatingExistsById inCollection(String collection); + } + + interface ReactiveExistsById extends ExistsByIdWithCollection {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java new file mode 100644 index 00000000..65523214 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveExistsByIdOperationSupport.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import static com.couchbase.client.java.kv.ExistsOptions.*; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.util.Assert; + +import com.couchbase.client.java.kv.ExistsResult; + +public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOperation { + + private final ReactiveCouchbaseTemplate template; + + ReactiveExistsByIdOperationSupport(ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveExistsById existsById() { + return new ReactiveExistsByIdSupport(template, null); + } + + static class ReactiveExistsByIdSupport implements ReactiveExistsById { + + private final ReactiveCouchbaseTemplate template; + private final String collection; + + ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final String collection) { + this.template = template; + this.collection = collection; + } + + @Override + public Mono one(final String id) { + return Mono.just(id).flatMap( + docId -> template.getCollection(collection).reactive().exists(id, existsOptions()).map(ExistsResult::exists)) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }); + } + + @Override + public Mono> all(final Collection ids) { + return Flux.fromIterable(ids).flatMap(id -> one(id).map(result -> Tuples.of(id, result))) + .collectMap(Tuple2::getT1, Tuple2::getT2); + } + + @Override + public TerminatingExistsById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ReactiveExistsByIdSupport(template, collection); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperation.java new file mode 100644 index 00000000..63beb527 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperation.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.data.couchbase.core.query.AnalyticsQuery; + +public interface ReactiveFindByAnalyticsOperation { + + ReactiveFindByAnalytics findByAnalytics(Class domainType); + + /** + * Compose find execution by calling one of the terminating methods. + */ + interface TerminatingFindByAnalytics { + + Mono one(); + + Mono first(); + + Flux all(); + + Mono count(); + + Mono exists(); + + } + + /** + * Terminating operations invoking the actual query execution. + * + * @author Christoph Strobl + * @since 2.0 + */ + interface FindByAnalyticsWithQuery extends TerminatingFindByAnalytics { + + /** + * Set the filter query to be used. + * + * @param query must not be {@literal null}. + * @return new instance of {@link TerminatingFindByAnalytics}. + * @throws IllegalArgumentException if query is {@literal null}. + */ + TerminatingFindByAnalytics matching(AnalyticsQuery query); + + } + + interface ReactiveFindByAnalytics extends FindByAnalyticsWithQuery {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java new file mode 100644 index 00000000..cdb0336c --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByAnalyticsOperationSupport.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.data.couchbase.core.query.AnalyticsQuery; + +import com.couchbase.client.java.analytics.ReactiveAnalyticsResult; + +public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAnalyticsOperation { + + private static final AnalyticsQuery ALL_QUERY = new AnalyticsQuery(); + + private final ReactiveCouchbaseTemplate template; + + public ReactiveFindByAnalyticsOperationSupport(final ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveFindByAnalytics findByAnalytics(final Class domainType) { + return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY); + } + + static class ReactiveFindByAnalyticsSupport implements ReactiveFindByAnalytics { + + private final ReactiveCouchbaseTemplate template; + private final Class domainType; + private final AnalyticsQuery query; + + ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class domainType, + final AnalyticsQuery query) { + this.template = template; + this.domainType = domainType; + this.query = query; + } + + @Override + public TerminatingFindByAnalytics matching(AnalyticsQuery query) { + return new ReactiveFindByAnalyticsSupport<>(template, domainType, query); + } + + @Override + public Mono one() { + return all().single(); + } + + @Override + public Mono first() { + return all().next(); + } + + @Override + public Flux all() { + return Flux.defer(() -> { + String statement = assembleEntityQuery(false); + return template.getCouchbaseClientFactory().getCluster().reactive().analyticsQuery(statement) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).map(row -> { + String id = row.getString("__id"); + long cas = row.getLong("__cas"); + row.removeKey("__id"); + row.removeKey("__cas"); + return template.support().decodeEntity(id, row.toString(), cas, domainType); + }); + }); + } + + @Override + public Mono count() { + return Mono.defer(() -> { + String statement = assembleEntityQuery(true); + return template.getCouchbaseClientFactory().getCluster().reactive().analyticsQuery(statement) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).map(row -> row.getLong("__count")).next(); + }); + } + + @Override + public Mono exists() { + return count().map(count -> count > 0); + } + + private String assembleEntityQuery(final boolean count) { + final String bucket = "`" + template.getBucketName() + "`"; + + final StringBuilder statement = new StringBuilder("SELECT "); + if (count) { + statement.append("count(*) as __count"); + } else { + statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*"); + } + + final String dataset = template.support().getJavaNameForEntity(domainType); + statement.append(" FROM ").append(dataset); + + query.appendSort(statement); + query.appendSkipAndLimit(statement); + return statement.toString(); + } + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java new file mode 100644 index 00000000..b801a855 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperation.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +public interface ReactiveFindByIdOperation { + + ReactiveFindById findById(Class domainType); + + interface TerminatingFindById { + + Mono one(String id); + + Flux all(Collection ids); + + } + + interface FindByIdWithCollection extends TerminatingFindById { + + TerminatingFindById inCollection(String collection); + } + + interface FindByIdWithProjection extends FindByIdWithCollection { + + FindByIdWithCollection project(String... fields); + + } + + interface ReactiveFindById extends FindByIdWithProjection {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java new file mode 100644 index 00000000..88475248 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByIdOperationSupport.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import static com.couchbase.client.java.kv.GetOptions.*; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.util.Assert; + +import com.couchbase.client.java.codec.RawJsonTranscoder; +import com.couchbase.client.java.kv.GetOptions; + +public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperation { + + private final ReactiveCouchbaseTemplate template; + + ReactiveFindByIdOperationSupport(ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveFindById findById(Class domainType) { + return new ReactiveFindByIdSupport<>(template, domainType, null, null); + } + + static class ReactiveFindByIdSupport implements ReactiveFindById { + + private final ReactiveCouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final List fields; + + ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class domainType, String collection, + List fields) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.fields = fields; + } + + @Override + public Mono one(final String id) { + return Mono.just(id).flatMap(docId -> { + GetOptions options = getOptions().transcoder(RawJsonTranscoder.INSTANCE); + if (fields != null && !fields.isEmpty()) { + options.project(fields); + } + return template.getCollection(collection).reactive().get(docId, options); + }).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType)) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }); + } + + @Override + public Flux all(final Collection ids) { + return Flux.fromIterable(ids).flatMap(this::one); + } + + @Override + public TerminatingFindById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ReactiveFindByIdSupport<>(template, domainType, collection, fields); + } + + @Override + public FindByIdWithCollection project(String... fields) { + Assert.notEmpty(fields, "Fields must not be null nor empty."); + return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields)); + } + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java new file mode 100644 index 00000000..af0d0110 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperation.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.data.couchbase.core.query.Query; + +import com.couchbase.client.java.query.QueryScanConsistency; + +public interface ReactiveFindByQueryOperation { + + ReactiveFindByQuery findByQuery(Class domainType); + + /** + * Compose find execution by calling one of the terminating methods. + */ + interface TerminatingFindByQuery { + + Mono one(); + + Mono first(); + + Flux all(); + + Mono count(); + + Mono exists(); + + } + + /** + * Terminating operations invoking the actual query execution. + * + * @author Christoph Strobl + * @since 2.0 + */ + interface FindByQueryWithQuery extends TerminatingFindByQuery { + + /** + * Set the filter query to be used. + * + * @param query must not be {@literal null}. + * @return new instance of {@link TerminatingFindByQuery}. + * @throws IllegalArgumentException if query is {@literal null}. + */ + TerminatingFindByQuery matching(Query query); + + } + + interface FindByQueryConsistentWith extends FindByQueryWithQuery { + + FindByQueryWithQuery consistentWith(QueryScanConsistency scanConsistency); + + } + + interface ReactiveFindByQuery extends FindByQueryConsistentWith {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java new file mode 100644 index 00000000..c4c953ed --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindByQueryOperationSupport.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import org.springframework.data.couchbase.core.query.QueryCriteria; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import org.springframework.data.couchbase.core.query.Query; + +import com.couchbase.client.java.query.QueryOptions; +import com.couchbase.client.java.query.QueryScanConsistency; +import com.couchbase.client.java.query.ReactiveQueryResult; + +public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryOperation { + + private static final Query ALL_QUERY = new Query(); + + private final ReactiveCouchbaseTemplate template; + + public ReactiveFindByQueryOperationSupport(final ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveFindByQuery findByQuery(final Class domainType) { + return new ReactiveFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED); + } + + static class ReactiveFindByQuerySupport implements ReactiveFindByQuery { + + private final ReactiveCouchbaseTemplate template; + private final Class domainType; + private final Query query; + private final QueryScanConsistency scanConsistency; + + ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class domainType, final Query query, + final QueryScanConsistency scanConsistency) { + this.template = template; + this.domainType = domainType; + this.query = query; + this.scanConsistency = scanConsistency; + } + + @Override + public TerminatingFindByQuery matching(Query query) { + return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency); + } + + @Override + public FindByQueryWithQuery consistentWith(QueryScanConsistency scanConsistency) { + return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency); + } + + @Override + public Mono one() { + return all().single(); + } + + @Override + public Mono first() { + return all().next(); + } + + @Override + public Flux all() { + return Flux.defer(() -> { + String statement = assembleEntityQuery(false); + return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions()) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> { + String id = row.getString("__id"); + long cas = row.getLong("__cas"); + row.removeKey("__id"); + row.removeKey("__cas"); + return template.support().decodeEntity(id, row.toString(), cas, domainType); + }); + }); + } + + @Override + public Mono count() { + return Mono.defer(() -> { + String statement = assembleEntityQuery(true); + return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions()) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> row.getLong("__count")).next(); + }); + } + + @Override + public Mono exists() { + return count().map(count -> count > 0); + } + + private String assembleEntityQuery(final boolean count) { + final String bucket = "`" + template.getBucketName() + "`"; + + final StringBuilder statement = new StringBuilder("SELECT "); + if (count) { + statement.append("count(*) as __count"); + } else { + statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*"); + } + + statement.append(" FROM ").append(bucket); + + String typeKey = template.getConverter().getTypeKey(); + String typeValue = template.support().getJavaNameForEntity(domainType); + query.addCriteria(QueryCriteria.where(typeKey).is(typeValue)); + + query.appendWhere(statement); + query.appendSort(statement); + query.appendSkipAndLimit(statement); + + return statement.toString(); + } + + private QueryOptions buildQueryOptions() { + final QueryOptions options = QueryOptions.queryOptions(); + if (scanConsistency != null) { + options.scanConsistency(scanConsistency); + } + return options; + } + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperation.java new file mode 100644 index 00000000..940f9872 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperation.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +public interface ReactiveFindFromReplicasByIdOperation { + + ReactiveFindFromReplicasById findFromReplicasById(Class domainType); + + interface TerminatingFindFromReplicasById { + + Mono any(String id); + + Flux any(Collection ids); + + } + + interface FindFromReplicasByIdWithCollection extends TerminatingFindFromReplicasById { + + TerminatingFindFromReplicasById inCollection(String collection); + } + + interface ReactiveFindFromReplicasById extends FindFromReplicasByIdWithCollection {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java new file mode 100644 index 00000000..7124927a --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFindFromReplicasByIdOperationSupport.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import static com.couchbase.client.java.kv.GetAnyReplicaOptions.*; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import org.springframework.util.Assert; + +import com.couchbase.client.java.codec.RawJsonTranscoder; +import com.couchbase.client.java.kv.GetAnyReplicaOptions; + +public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFindFromReplicasByIdOperation { + + private final ReactiveCouchbaseTemplate template; + + ReactiveFindFromReplicasByIdOperationSupport(ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveFindFromReplicasById findFromReplicasById(Class domainType) { + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, null); + } + + static class ReactiveFindFromReplicasByIdSupport implements ReactiveFindFromReplicasById { + + private final ReactiveCouchbaseTemplate template; + private final Class domainType; + private final String collection; + + ReactiveFindFromReplicasByIdSupport(ReactiveCouchbaseTemplate template, Class domainType, String collection) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + } + + @Override + public Mono any(final String id) { + return Mono.just(id).flatMap(docId -> { + GetAnyReplicaOptions options = getAnyReplicaOptions().transcoder(RawJsonTranscoder.INSTANCE); + return template.getCollection(collection).reactive().getAnyReplica(docId, options); + }).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType)) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }); + } + + @Override + public Flux any(Collection ids) { + return Flux.fromIterable(ids).flatMap(this::any); + } + + @Override + public TerminatingFindFromReplicasById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, collection); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/WriteResultChecking.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveFluentCouchbaseOperations.java similarity index 64% rename from src/main/java/org/springframework/data/couchbase/core/WriteResultChecking.java rename to src/main/java/org/springframework/data/couchbase/core/ReactiveFluentCouchbaseOperations.java index 2005b5b0..6de90efa 100644 --- a/src/main/java/org/springframework/data/couchbase/core/WriteResultChecking.java +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveFluentCouchbaseOperations.java @@ -16,25 +16,7 @@ package org.springframework.data.couchbase.core; -/** - * How failing write results should be handled. - * - * @author Michael Nitschinger - */ -public enum WriteResultChecking { - - /** - * Ignore failing write results. - */ - NONE, - - /** - * Log failing write results. - */ - LOG, - - /** - * Throw Exceptions on failing write results. - */ - EXCEPTION -} +public interface ReactiveFluentCouchbaseOperations extends ReactiveUpsertByIdOperation, ReactiveInsertByIdOperation, + ReactiveReplaceByIdOperation, ReactiveFindByIdOperation, ReactiveExistsByIdOperation, + ReactiveFindByAnalyticsOperation, ReactiveFindFromReplicasByIdOperation, ReactiveFindByQueryOperation, + ReactiveRemoveByIdOperation, ReactiveRemoveByQueryOperation {} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperation.java new file mode 100644 index 00000000..d578ef13 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperation.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public interface ReactiveInsertByIdOperation { + + ReactiveInsertById insertById(Class domainType); + + interface TerminatingInsertById { + + Mono one(T object); + + Flux all(Collection objects); + + } + + interface InsertByIdWithCollection extends TerminatingInsertById { + + TerminatingInsertById inCollection(String collection); + } + + interface InsertByIdWithDurability extends InsertByIdWithCollection { + + InsertByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + + InsertByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + + } + + interface ReactiveInsertById extends InsertByIdWithDurability {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java new file mode 100644 index 00000000..c182ca06 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveInsertByIdOperationSupport.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.util.Assert; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.InsertOptions; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation { + + private final ReactiveCouchbaseTemplate template; + + public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveInsertById insertById(final Class domainType) { + Assert.notNull(domainType, "DomainType must not be null!"); + return new ReactiveInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, + DurabilityLevel.NONE); + } + + static class ReactiveInsertByIdSupport implements ReactiveInsertById { + + private final ReactiveCouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final PersistTo persistTo; + private final ReplicateTo replicateTo; + private final DurabilityLevel durabilityLevel; + + ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, + final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, + final DurabilityLevel durabilityLevel) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.persistTo = persistTo; + this.replicateTo = replicateTo; + this.durabilityLevel = durabilityLevel; + } + + @Override + public Mono one(T object) { + return Mono.just(object).flatMap(o -> { + CouchbaseDocument converted = template.support().encodeEntity(o); + return template.getCollection(collection).reactive() + .insert(converted.getId(), converted.getPayload(), buildInsertOptions()).map(result -> { + template.support().applyUpdatedCas(object, result.cas()); + return o; + }); + }).onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }); + } + + @Override + public Flux all(Collection objects) { + return Flux.fromIterable(objects).flatMap(this::one); + } + + private InsertOptions buildInsertOptions() { + final InsertOptions options = InsertOptions.insertOptions(); + if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) { + options.durability(persistTo, replicateTo); + } else if (durabilityLevel != DurabilityLevel.NONE) { + options.durability(durabilityLevel); + } + return options; + } + + @Override + public TerminatingInsertById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public InsertByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public InsertByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + Assert.notNull(persistTo, "PersistTo must not be null."); + Assert.notNull(replicateTo, "ReplicateTo must not be null."); + return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java new file mode 100644 index 00000000..d26519af --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperation.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public interface ReactiveRemoveByIdOperation { + + ReactiveRemoveById removeById(); + + interface TerminatingRemoveById { + + Mono one(String id); + + Flux all(Collection ids); + + } + + interface RemoveByIdWithCollection extends TerminatingRemoveById { + + TerminatingRemoveById inCollection(String collection); + } + + interface RemoveByIdWithDurability extends RemoveByIdWithCollection { + + RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + + RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + + } + + interface ReactiveRemoveById extends RemoveByIdWithDurability {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java new file mode 100644 index 00000000..4d121e50 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByIdOperationSupport.java @@ -0,0 +1,108 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import org.springframework.util.Assert; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.RemoveOptions; +import com.couchbase.client.java.kv.ReplicateTo; + +public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOperation { + + private final ReactiveCouchbaseTemplate template; + + public ReactiveRemoveByIdOperationSupport(final ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveRemoveById removeById() { + return new ReactiveRemoveByIdSupport(template, null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE); + } + + static class ReactiveRemoveByIdSupport implements ReactiveRemoveById { + + private final ReactiveCouchbaseTemplate template; + private final String collection; + private final PersistTo persistTo; + private final ReplicateTo replicateTo; + private final DurabilityLevel durabilityLevel; + + ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final String collection, + final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) { + this.template = template; + this.collection = collection; + this.persistTo = persistTo; + this.replicateTo = replicateTo; + this.durabilityLevel = durabilityLevel; + } + + @Override + public Mono one(final String id) { + return Mono.just(id).flatMap(docId -> template.getCollection(collection).reactive() + .remove(id, buildRemoveOptions()).map(r -> RemoveResult.from(docId, r))).onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }); + } + + @Override + public Flux all(final Collection ids) { + return Flux.fromIterable(ids).flatMap(this::one); + } + + private RemoveOptions buildRemoveOptions() { + final RemoveOptions options = RemoveOptions.removeOptions(); + if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) { + options.durability(persistTo, replicateTo); + } else if (durabilityLevel != DurabilityLevel.NONE) { + options.durability(durabilityLevel); + } + return options; + } + + @Override + public TerminatingRemoveById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public RemoveByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + Assert.notNull(persistTo, "PersistTo must not be null."); + Assert.notNull(replicateTo, "ReplicateTo must not be null."); + return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java new file mode 100644 index 00000000..de837248 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperation.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; + +import org.springframework.data.couchbase.core.query.Query; + +import com.couchbase.client.java.query.QueryScanConsistency; + +public interface ReactiveRemoveByQueryOperation { + + ReactiveRemoveByQuery removeByQuery(Class domainType); + + interface TerminatingRemoveByQuery { + Flux all(); + } + + interface RemoveByQueryWithQuery extends TerminatingRemoveByQuery { + + TerminatingRemoveByQuery matching(Query query); + + } + + interface RemoveByQueryConsistentWith extends RemoveByQueryWithQuery { + + RemoveByQueryWithQuery consistentWith(QueryScanConsistency scanConsistency); + + } + + interface ReactiveRemoveByQuery extends RemoveByQueryConsistentWith {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java new file mode 100644 index 00000000..8dd08092 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveRemoveByQueryOperationSupport.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; + +import java.util.Optional; + +import org.springframework.data.couchbase.core.query.Query; + +import com.couchbase.client.java.query.QueryOptions; +import com.couchbase.client.java.query.QueryScanConsistency; +import com.couchbase.client.java.query.ReactiveQueryResult; + +public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQueryOperation { + + private static final Query ALL_QUERY = new Query(); + + private final ReactiveCouchbaseTemplate template; + + public ReactiveRemoveByQueryOperationSupport(final ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveRemoveByQuery removeByQuery(Class domainType) { + return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED); + } + + static class ReactiveRemoveByQuerySupport implements ReactiveRemoveByQuery { + + private final ReactiveCouchbaseTemplate template; + private final Class domainType; + private final Query query; + private final QueryScanConsistency scanConsistency; + + ReactiveRemoveByQuerySupport(final ReactiveCouchbaseTemplate template, final Class domainType, final Query query, + final QueryScanConsistency scanConsistency) { + this.template = template; + this.domainType = domainType; + this.query = query; + this.scanConsistency = scanConsistency; + } + + @Override + public Flux all() { + return Flux.defer(() -> { + String bucket = "`" + template.getBucketName() + "`"; + + String typeKey = template.getConverter().getTypeKey(); + String typeValue = template.support().getJavaNameForEntity(domainType); + String where = " WHERE `" + typeKey + "` = \"" + typeValue + "\""; + + String returning = " RETURNING meta().*"; + String statement = "DELETE FROM " + bucket + " " + where + returning; + + return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions()) + .onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }).flatMapMany(ReactiveQueryResult::rowsAsObject) + .map(row -> new RemoveResult(row.getString("id"), row.getLong("cas"), Optional.empty())); + }); + } + + private QueryOptions buildQueryOptions() { + final QueryOptions options = QueryOptions.queryOptions(); + if (scanConsistency != null) { + options.scanConsistency(scanConsistency); + } + return options; + } + + @Override + public TerminatingRemoveByQuery matching(final Query query) { + return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + } + + @Override + public RemoveByQueryWithQuery consistentWith(final QueryScanConsistency scanConsistency) { + return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java new file mode 100644 index 00000000..b6ea112a --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperation.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public interface ReactiveReplaceByIdOperation { + + ReactiveReplaceById replaceById(Class domainType); + + interface TerminatingReplaceById { + + Mono one(T object); + + Flux all(Collection objects); + + } + + interface ReplaceByIdWithCollection extends TerminatingReplaceById { + + TerminatingReplaceById inCollection(String collection); + } + + interface ReplaceByIdWithDurability extends ReplaceByIdWithCollection { + + ReplaceByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + + ReplaceByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + + } + + interface ReactiveReplaceById extends ReplaceByIdWithDurability {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java new file mode 100644 index 00000000..486bb270 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveReplaceByIdOperationSupport.java @@ -0,0 +1,123 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.util.Assert; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplaceOptions; +import com.couchbase.client.java.kv.ReplicateTo; + +public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation { + + private final ReactiveCouchbaseTemplate template; + + public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveReplaceById replaceById(final Class domainType) { + Assert.notNull(domainType, "DomainType must not be null!"); + return new ReactiveReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, + DurabilityLevel.NONE); + } + + static class ReactiveReplaceByIdSupport implements ReactiveReplaceById { + + private final ReactiveCouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final PersistTo persistTo; + private final ReplicateTo replicateTo; + private final DurabilityLevel durabilityLevel; + + ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, + final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, + final DurabilityLevel durabilityLevel) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.persistTo = persistTo; + this.replicateTo = replicateTo; + this.durabilityLevel = durabilityLevel; + } + + @Override + public Mono one(T object) { + return Mono.just(object).flatMap(o -> { + CouchbaseDocument converted = template.support().encodeEntity(o); + return template.getCollection(collection).reactive() + .replace(converted.getId(), converted.getPayload(), buildReplaceOptions()).map(result -> { + template.support().applyUpdatedCas(object, result.cas()); + return o; + }); + }).onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }); + } + + @Override + public Flux all(Collection objects) { + return Flux.fromIterable(objects).flatMap(this::one); + } + + private ReplaceOptions buildReplaceOptions() { + final ReplaceOptions options = ReplaceOptions.replaceOptions(); + if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) { + options.durability(persistTo, replicateTo); + } else if (durabilityLevel != DurabilityLevel.NONE) { + options.durability(durabilityLevel); + } + return options; + } + + @Override + public TerminatingReplaceById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + @Override + public ReplaceByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + @Override + public ReplaceByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + Assert.notNull(persistTo, "PersistTo must not be null."); + Assert.notNull(replicateTo, "ReplicateTo must not be null."); + return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo, + durabilityLevel); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperation.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperation.java new file mode 100644 index 00000000..7c1811be --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperation.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; + +public interface ReactiveUpsertByIdOperation { + + ReactiveUpsertById upsertById(Class domainType); + + interface TerminatingUpsertById { + + Mono one(T object); + + Flux all(Collection objects); + + } + + interface UpsertByIdWithCollection extends TerminatingUpsertById { + + TerminatingUpsertById inCollection(String collection); + } + + interface UpsertByIdWithDurability extends UpsertByIdWithCollection { + + UpsertByIdWithCollection withDurability(DurabilityLevel durabilityLevel); + + UpsertByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo); + + } + + interface ReactiveUpsertById extends UpsertByIdWithDurability {} + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java new file mode 100644 index 00000000..2e4c9ac5 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/ReactiveUpsertByIdOperationSupport.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.core; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Collection; + +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.util.Assert; + +import com.couchbase.client.core.msg.kv.DurabilityLevel; +import com.couchbase.client.java.kv.PersistTo; +import com.couchbase.client.java.kv.ReplicateTo; +import com.couchbase.client.java.kv.UpsertOptions; + +public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOperation { + + private final ReactiveCouchbaseTemplate template; + + public ReactiveUpsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) { + this.template = template; + } + + @Override + public ReactiveUpsertById upsertById(final Class domainType) { + Assert.notNull(domainType, "DomainType must not be null!"); + return new ReactiveUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE, + DurabilityLevel.NONE); + } + + static class ReactiveUpsertByIdSupport implements ReactiveUpsertById { + + private final ReactiveCouchbaseTemplate template; + private final Class domainType; + private final String collection; + private final PersistTo persistTo; + private final ReplicateTo replicateTo; + private final DurabilityLevel durabilityLevel; + + ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class domainType, + final String collection, final PersistTo persistTo, final ReplicateTo replicateTo, + final DurabilityLevel durabilityLevel) { + this.template = template; + this.domainType = domainType; + this.collection = collection; + this.persistTo = persistTo; + this.replicateTo = replicateTo; + this.durabilityLevel = durabilityLevel; + } + + @Override + public Mono one(T object) { + return Mono.just(object).flatMap(o -> { + CouchbaseDocument converted = template.support().encodeEntity(o); + return template.getCollection(collection).reactive() + .upsert(converted.getId(), converted.getPayload(), buildUpsertOptions()).map(result -> { + template.support().applyUpdatedCas(object, result.cas()); + return o; + }); + }).onErrorMap(throwable -> { + if (throwable instanceof RuntimeException) { + return template.potentiallyConvertRuntimeException((RuntimeException) throwable); + } else { + return throwable; + } + }); + } + + @Override + public Flux all(Collection objects) { + return Flux.fromIterable(objects).flatMap(this::one); + } + + private UpsertOptions buildUpsertOptions() { + final UpsertOptions options = UpsertOptions.upsertOptions(); + if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) { + options.durability(persistTo, replicateTo); + } else if (durabilityLevel != DurabilityLevel.NONE) { + options.durability(durabilityLevel); + } + return options; + } + + @Override + public TerminatingUpsertById inCollection(final String collection) { + Assert.hasText(collection, "Collection must not be null nor empty."); + return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public UpsertByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) { + Assert.notNull(durabilityLevel, "Durability Level must not be null."); + return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + @Override + public UpsertByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) { + Assert.notNull(persistTo, "PersistTo must not be null."); + Assert.notNull(replicateTo, "ReplicateTo must not be null."); + return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel); + } + + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/RemoveResult.java b/src/main/java/org/springframework/data/couchbase/core/RemoveResult.java new file mode 100644 index 00000000..322cac6d --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/RemoveResult.java @@ -0,0 +1,56 @@ +package org.springframework.data.couchbase.core; + +import java.util.Objects; +import java.util.Optional; + +import com.couchbase.client.core.msg.kv.MutationToken; +import com.couchbase.client.java.kv.MutationResult; + +public class RemoveResult { + + private final String id; + private final long cas; + private final Optional mutationToken; + + public RemoveResult(String id, long cas, Optional mutationToken) { + this.id = id; + this.cas = cas; + this.mutationToken = mutationToken; + } + + public static RemoveResult from(final String id, final MutationResult result) { + return new RemoveResult(id, result.cas(), result.mutationToken()); + } + + public String getId() { + return id; + } + + public long getCas() { + return cas; + } + + public Optional getMutationToken() { + return mutationToken; + } + + @Override + public String toString() { + return "RemoveResult{" + "id='" + id + '\'' + ", cas=" + cas + ", mutationToken=" + mutationToken + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + RemoveResult that = (RemoveResult) o; + return cas == that.cas && Objects.equals(id, that.id) && Objects.equals(mutationToken, that.mutationToken); + } + + @Override + public int hashCode() { + return Objects.hash(id, cas, mutationToken); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/core/RxJavaCouchbaseOperations.java b/src/main/java/org/springframework/data/couchbase/core/RxJavaCouchbaseOperations.java deleted file mode 100644 index 51910bcf..00000000 --- a/src/main/java/org/springframework/data/couchbase/core/RxJavaCouchbaseOperations.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.couchbase.core; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.PersistTo; -import com.couchbase.client.java.ReplicateTo; -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.query.AsyncN1qlQueryResult; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.view.AsyncSpatialViewResult; -import com.couchbase.client.java.view.AsyncViewResult; -import com.couchbase.client.java.view.SpatialViewQuery; -import com.couchbase.client.java.view.ViewQuery; -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.query.Consistency; -import rx.Observable; - -/** - * @author Subhashni Balakrishnan - * @author Alex Derkach - * @since 3.0 - */ -public interface RxJavaCouchbaseOperations { - - Observable save(T objectToSave); - - Observable save(Iterable batchToSave); - - Observable save(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo); - - Observable save(Iterable batchToSave, PersistTo persistTo, ReplicateTo replicateTo); - - Observable insert(T objectToSave); - - Observable insert(Iterable batchToSave); - - Observable insert(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo); - - Observable insert(Iterable batchToSave, PersistTo persistTo, ReplicateTo replicateTo); - - Observable update(T objectToSave); - - Observable update(Iterable batchToSave); - - Observable update(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo); - - Observable update(Iterable batchToSave, PersistTo persistTo, ReplicateTo replicateTo); - - Observable remove(T objectToRemove); - - Observable remove(T objectToRemove, PersistTo persistTo, ReplicateTo replicateTo); - - Observable remove(Iterable batchToRemove); - - Observable remove(Iterable batchToRemove, PersistTo persistTo, ReplicateTo replicateTo); - - Observable exists(String id); - - Observable findById(String id, Class entityClass); - - Observable queryN1QL(N1qlQuery n1ql); - - Observable queryView(ViewQuery query); - - Observable querySpatialView(SpatialViewQuery query); - - Observable findByView(ViewQuery query, Class entityClass); - - Observable findByN1QL(N1qlQuery n1ql, Class entityClass); - - Observable findBySpatialView(SpatialViewQuery query, Class entityClass); - - Observable findByN1QLProjection(N1qlQuery n1ql, Class fragmentClass); - - Consistency getDefaultConsistency(); - - /** - * Returns the linked {@link Bucket} to this template. - * - * @return the client used for the template. - */ - Bucket getCouchbaseBucket(); - - CouchbaseConverter getConverter(); - - ClusterInfo getCouchbaseClusterInfo(); - -} diff --git a/src/main/java/org/springframework/data/couchbase/core/RxJavaCouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/RxJavaCouchbaseTemplate.java deleted file mode 100644 index 9c144e43..00000000 --- a/src/main/java/org/springframework/data/couchbase/core/RxJavaCouchbaseTemplate.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import static org.springframework.data.couchbase.core.CouchbaseTemplate.ensureNotIterable; - -import com.couchbase.client.java.AsyncBucket; -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.PersistTo; -import com.couchbase.client.java.ReplicateTo; -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.document.Document; -import com.couchbase.client.java.document.RawJsonDocument; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.error.CASMismatchException; -import com.couchbase.client.java.error.DocumentAlreadyExistsException; -import com.couchbase.client.java.query.*; -import com.couchbase.client.java.view.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.dao.OptimisticLockingFailureException; -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; -import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService; -import org.springframework.data.couchbase.core.convert.translation.TranslationService; -import org.springframework.data.couchbase.core.mapping.*; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.core.support.TemplateUtils; -import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.mapping.model.ConvertingPropertyAccessor; -import rx.Observable; -import rx.functions.Func3; - -/** - * RxJavaCouchbaseTemplate implements operations using rxjava1 observables - * @author Subhashni Balakrishnan - * @author Mark Paluch - * @author Alex Derkach - * @since 3.0 - */ -public class RxJavaCouchbaseTemplate implements RxJavaCouchbaseOperations { - - private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE; - - protected final MappingContext, CouchbasePersistentProperty> mappingContext; - - private Bucket syncClient; - private AsyncBucket client; - private final ClusterInfo clusterInfo; - private final CouchbaseConverter converter; - private final TranslationService translationService; - private Consistency configuredConsistency = Consistency.DEFAULT_CONSISTENCY; - private WriteResultChecking writeResultChecking = DEFAULT_WRITE_RESULT_CHECKING; - - public Observable save(T objectToSave) { - return save(objectToSave, PersistTo.NONE, ReplicateTo.NONE); - } - - public Observable save(Iterable batchToSave) { - return Observable.from(batchToSave) - .flatMap(this::save); - } - - public Observable save(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) { - return doPersist(objectToSave, PersistType.SAVE, persistTo, replicateTo); - } - - public Observable save(Iterable batchToSave, PersistTo persistTo, ReplicateTo replicateTo) { - return Observable.from(batchToSave) - .flatMap(object -> save(object, persistTo, replicateTo)); - } - - @Override - public Observable insert(T objectToSave) { - return insert(objectToSave, PersistTo.NONE, ReplicateTo.NONE); - } - - @Override - public Observable insert(Iterable batchToSave) { - return Observable.from(batchToSave) - .flatMap(this::insert); - } - - @Override - public Observable insert(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) { - return doPersist(objectToSave, PersistType.INSERT, persistTo, replicateTo); - } - - @Override - public Observable insert(Iterable batchToSave, PersistTo persistTo, ReplicateTo replicateTo) { - return Observable.from(batchToSave) - .flatMap(objectToSave -> insert(objectToSave, persistTo, replicateTo)); - } - - @Override - public Observable update(T objectToSave) { - return update(objectToSave, PersistTo.NONE, ReplicateTo.NONE); - } - - @Override - public Observable update(Iterable batchToSave) { - return Observable.from(batchToSave) - .flatMap(this::update); - } - - @Override - public Observable update(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo) { - return doPersist(objectToSave, PersistType.UPDATE, persistTo, replicateTo); - } - - @Override - public Observable update(Iterable batchToSave, PersistTo persistTo, ReplicateTo replicateTo) { - return Observable.from(batchToSave) - .flatMap(objectToSave -> update(objectToSave, persistTo, replicateTo)); - } - - public Observable remove(T objectToRemove) { - return doRemove(objectToRemove, PersistTo.NONE, ReplicateTo.NONE); - } - - public Observable remove(Iterable batchToRemove) { - return Observable.from(batchToRemove) - .flatMap(this::remove); - } - - public Observable remove(T objectToRemove, PersistTo persistTo, ReplicateTo replicateTo) { - return doRemove(objectToRemove, persistTo, replicateTo); - } - - public Observable remove(Iterable batchToRemove, PersistTo persistTo, ReplicateTo replicateTo) { - return Observable.from(batchToRemove) - .flatMap(object -> remove(object, persistTo, replicateTo)); - } - - public RxJavaCouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client) { - this(clusterInfo, client, null, null); - } - - public RxJavaCouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client, final TranslationService translationService) { - this(clusterInfo, client, null, translationService); - } - - - public void setWriteResultChecking(WriteResultChecking writeResultChecking) { - this.writeResultChecking = writeResultChecking == null ? DEFAULT_WRITE_RESULT_CHECKING : writeResultChecking; - } - - public RxJavaCouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client, - final CouchbaseConverter converter, - final TranslationService translationService) { - this.syncClient = client; - this.clusterInfo = clusterInfo; - this.client = client.async(); - this.converter = converter == null ? getDefaultConverter() : converter; - this.translationService = translationService == null ? getDefaultTranslationService() : translationService; - this.mappingContext = this.converter.getMappingContext(); - } - - private RawJsonDocument encodeAndWrap(final CouchbaseDocument source, Long version) { - String encodedContent = translationService.encode(source); - if (version == null) { - return RawJsonDocument.create(source.getId(), source.getExpiration(), encodedContent); - } else { - return RawJsonDocument.create(source.getId(), source.getExpiration(), encodedContent, version); - } - } - - - private TranslationService getDefaultTranslationService() { - JacksonTranslationService t = new JacksonTranslationService(); - t.afterPropertiesSet(); - return t; - } - - - private CouchbaseConverter getDefaultConverter() { - MappingCouchbaseConverter c = new MappingCouchbaseConverter(new CouchbaseMappingContext()); - c.afterPropertiesSet(); - return c; - } - - private final ConvertingPropertyAccessor getPropertyAccessor(T source) { - - CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(source.getClass()); - PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); - - return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService()); - } - - private Observable doPersist(T objectToPersist, PersistType persistType, PersistTo persistTo, ReplicateTo replicateTo) { - // If version is not set - assumption that document is new, otherwise updating - Long version = getVersion(objectToPersist); - Func3> persistFunction; - switch (persistType) { - case SAVE: - if (version == null) { - //No version field - no cas - persistFunction = client::upsert; - } else if (version > 0) { - //Updating existing document with cas - persistFunction = client::replace; - } else { - //Creating new document - persistFunction = client::insert; - } - break; - case UPDATE: - persistFunction = client::replace; - break; - case INSERT: - default: - persistFunction = client::insert; - break; - } - return persistFunction.call(toJsonDocument(objectToPersist), persistTo, replicateTo) - .flatMap(storedDoc -> { - if (storedDoc != null) { - if (storedDoc.cas() != 0) { - setVersion(objectToPersist, storedDoc.cas()); - } - // Only set the id if the objectToPersist doesn't have it. That only - // happens when you have generated ids, and you are first persisting the - // document. - if (storedDoc.id() != null && getId(objectToPersist) == null) { - setId(objectToPersist, storedDoc.id()); - } - } - return Observable.just(objectToPersist); - }) - .onErrorResumeNext(e -> { - if (e instanceof DocumentAlreadyExistsException) { - throw new OptimisticLockingFailureException(persistType.springDataOperationName + - " document with version value failed: " + version, e); - } - if (e instanceof CASMismatchException) { - throw new OptimisticLockingFailureException(persistType.springDataOperationName + - " document with version value failed: " + version, e); - } - return TemplateUtils.translateError(e); - }); - } - - private RawJsonDocument toJsonDocument(T object) { - ensureNotIterable(object); - - final CouchbaseDocument converted = new CouchbaseDocument(); - converter.write(object, converted); - return encodeAndWrap(converted, getVersion(object)); - } - - private CouchbasePersistentProperty versionProperty(T object) { - return mappingContext.getRequiredPersistentEntity(object.getClass()).getVersionProperty(); - } - - private CouchbasePersistentProperty idProperty(T object) { - return mappingContext.getRequiredPersistentEntity(object.getClass()).getIdProperty(); - } - - private Long getVersion(T object) { - - CouchbasePersistentProperty versionProperty = versionProperty(object); - - return versionProperty == null // - ? null // - : getPropertyAccessor(object).getProperty(versionProperty, Long.class); - } - - private T setVersion(T object, long version) { - - CouchbasePersistentProperty versionProperty = versionProperty(object); - - if (versionProperty == null) { - return object; - } - - final ConvertingPropertyAccessor accessor = getPropertyAccessor(object); - accessor.setProperty(versionProperty, version); - return accessor.getBean(); - } - - private String getId(T object) { - CouchbasePersistentProperty idProperty = idProperty(object); - return idProperty == null - ? null // - : getPropertyAccessor(object).getProperty(idProperty, String.class); - } - - private T setId(T object, String id) { - - CouchbasePersistentProperty idProperty = idProperty(object); - if (idProperty == null) { - return object; - } - final ConvertingPropertyAccessor accessor = getPropertyAccessor(object); - accessor.setProperty(idProperty, id); - return accessor.getBean(); - } - - private Observable doRemove(T objectToRemove, final PersistTo persistTo, final ReplicateTo replicateTo) { - if(objectToRemove instanceof String) { - return client.remove((String) objectToRemove, persistTo, replicateTo) - .flatMap(rawJsonDocument -> Observable.just(objectToRemove)) - .doOnError(e -> TemplateUtils.translateError(e)); - } else { - RawJsonDocument doc = toJsonDocument(objectToRemove); - return client.remove(doc, persistTo, replicateTo) - .flatMap(rawJsonDocument -> Observable.just(objectToRemove)) - .doOnError(e -> TemplateUtils.translateError(e)); - } - } - - - @Override - public Observable exists(String id) { - return client.exists(id) - .doOnError(e -> TemplateUtils.translateError(e)); - } - - @Override - public Observable queryN1QL(N1qlQuery query) { - return client.query(query) - .doOnError(e -> TemplateUtils.translateError(e)); - } - - @Override - public Observable queryView(ViewQuery query) { - return client.query(query) - .doOnError(e -> TemplateUtils.translateError(e)); - } - - @Override - public Observable querySpatialView(SpatialViewQuery query){ - return client.query(query) - .doOnError(e -> TemplateUtils.translateError(e)); - } - - @Override - public Observable findById(String id, Class entityClass) { - final CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityClass); - if (entity.isTouchOnRead()) { - return client.getAndTouch(id, entity.getExpiry(), RawJsonDocument.class) - .switchIfEmpty(Observable.just(null)) - .map(doc -> mapToEntity(id, doc, entityClass)) - .doOnError(e -> TemplateUtils.translateError(e)); - } else { - return client.get(id, RawJsonDocument.class) - .switchIfEmpty(Observable.just(null)) - .map(doc -> mapToEntity(id, doc, entityClass)) - .doOnError(e -> TemplateUtils.translateError(e)); - } - } - - @Override - public Observable findByView(ViewQuery query, Class entityClass) { - if (!query.isIncludeDocs() || !query.includeDocsTarget().equals(RawJsonDocument.class)) { - if (query.isOrderRetained()) { - query.includeDocsOrdered(RawJsonDocument.class); - } else { - query.includeDocs(RawJsonDocument.class); - } - } - //we'll always map the document to the entity, hence reduce never makes sense. - query.reduce(false); - - return queryView(query) - .flatMap(asyncViewResult -> asyncViewResult.error() - .flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute view query due to error:" + error.toString()))) - .switchIfEmpty(asyncViewResult.rows())) - .map(row -> { - AsyncViewRow asyncViewRow = (AsyncViewRow) row; - return asyncViewRow.document(RawJsonDocument.class) - .map(doc -> mapToEntity(doc.id(), doc, entityClass)).toBlocking().single(); - }) - .doOnError(throwable -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute view query", throwable))); - } - - - @Override - public Observable findByN1QL(N1qlQuery query, Class entityClass) { - return queryN1QL(query) - .flatMap(asyncN1qlQueryResult -> asyncN1qlQueryResult.errors() - .flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query due to error:" + error.toString()))) - .switchIfEmpty(asyncN1qlQueryResult.rows())) - .map(row -> { - JsonObject json = ((AsyncN1qlQueryRow)row).value(); - String id = json.getString(TemplateUtils.SELECT_ID); - Long cas = json.getLong(TemplateUtils.SELECT_CAS); - if (id == null || cas == null) { - throw new CouchbaseQueryExecutionException("Unable to retrieve enough metadata for N1QL to entity mapping, " + - "have you selected " + TemplateUtils.SELECT_ID + " and " + TemplateUtils.SELECT_CAS + "?"); - } - json = json.removeKey(TemplateUtils.SELECT_ID).removeKey(TemplateUtils.SELECT_CAS); - RawJsonDocument entityDoc = RawJsonDocument.create(id, json.toString(), cas); - T decoded = mapToEntity(id, entityDoc, entityClass); - return decoded; - }) - .doOnError(throwable -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query", throwable))); - } - - @Override - public Observable findBySpatialView(SpatialViewQuery query, Class entityClass) { - return querySpatialView(query) - .flatMap(spatialViewResult -> spatialViewResult.error() - .flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute spatial view query due to error:" + error.toString()))) - .switchIfEmpty(spatialViewResult.rows())) - .map(row -> { - AsyncSpatialViewRow asyncSpatialViewRow = (AsyncSpatialViewRow) row; - return asyncSpatialViewRow.document(RawJsonDocument.class) - .map(doc -> mapToEntity(doc.id(), doc, entityClass)) - .toBlocking().single(); - }) - .doOnError(throwable -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute spatial view query", throwable))); - } - - @Override - public Observable findByN1QLProjection(N1qlQuery query, Class entityClass) { - return queryN1QL(query) - .flatMap(asyncN1qlQueryResult -> asyncN1qlQueryResult.errors() - .flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query due to error:" + error.toString()))) - .switchIfEmpty(asyncN1qlQueryResult.rows())) - .map(row -> { - JsonObject json = ((AsyncN1qlQueryRow)row).value(); - T decoded = translationService.decodeFragment(json.toString(), entityClass); - return decoded; - }) - .doOnError(throwable -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute n1ql query", throwable))); - } - - - @Override - public Consistency getDefaultConsistency() { - return configuredConsistency; - } - - - public void setDefaultConsistency(Consistency consistency) { - this.configuredConsistency = consistency; - } - - @Override - public CouchbaseConverter getConverter() { - return this.converter; - } - - - private T mapToEntity(String id, Document data, Class entityClass) { - if (data == null) { - return null; - } - - final CouchbaseDocument converted = new CouchbaseDocument(id); - Object readEntity = converter.read(entityClass, (CouchbaseDocument) decodeAndUnwrap(data, converted)); - - final ConvertingPropertyAccessor accessor = getPropertyAccessor(readEntity); - CouchbasePersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(readEntity.getClass()); - CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty(); - if (versionProperty != null) { - accessor.setProperty(versionProperty, data.cas()); - } - - return (T) readEntity; - } - - /** - * Decode a {@link Document Document<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); - } - - @Override - public Bucket getCouchbaseBucket() { - return this.syncClient; - } - - @Override - public ClusterInfo getCouchbaseClusterInfo() { - return this.clusterInfo; - } - - private enum PersistType { - SAVE("Save", "Upsert"), - INSERT("Insert", "Insert"), - UPDATE("Update", "Replace"); - - private final String sdkOperationName; - private final String springDataOperationName; - - PersistType(String sdkOperationName, String springDataOperationName) { - this.sdkOperationName = sdkOperationName; - this.springDataOperationName = springDataOperationName; - } - - } - -} diff --git a/src/main/java/org/springframework/data/couchbase/core/UnsupportedCouchbaseFeatureException.java b/src/main/java/org/springframework/data/couchbase/core/UnsupportedCouchbaseFeatureException.java index f290f944..653fcdda 100644 --- a/src/main/java/org/springframework/data/couchbase/core/UnsupportedCouchbaseFeatureException.java +++ b/src/main/java/org/springframework/data/couchbase/core/UnsupportedCouchbaseFeatureException.java @@ -16,34 +16,34 @@ package org.springframework.data.couchbase.core; -import com.couchbase.client.java.util.features.CouchbaseFeature; - import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.NonTransientDataAccessException; +import com.couchbase.client.core.service.ServiceType; + /** - * A {@link NonTransientDataAccessException} that denotes that a particular feature is expected - * on the server side but is not available. + * A {@link NonTransientDataAccessException} that denotes that a particular feature is expected on the server side but + * is not available. */ public class UnsupportedCouchbaseFeatureException extends InvalidDataAccessApiUsageException { - private final CouchbaseFeature feature; + private final ServiceType feature; - public UnsupportedCouchbaseFeatureException(String msg, CouchbaseFeature feature) { - super(msg); - this.feature = feature; - } + public UnsupportedCouchbaseFeatureException(String msg, ServiceType feature) { + super(msg); + this.feature = feature; + } - public UnsupportedCouchbaseFeatureException(String msg, CouchbaseFeature feature, Throwable cause) { - super(msg, cause); - this.feature = feature; - } + public UnsupportedCouchbaseFeatureException(String msg, ServiceType feature, Throwable cause) { + super(msg, cause); + this.feature = feature; + } - /** - * @return the {@link CouchbaseFeature} that was missing (could be null if not - * a registered CouchbaseFeature, in which case see {@link #getMessage()}). - */ - public CouchbaseFeature getFeature() { - return feature; - } + /** + * @return the {@link ServiceType} that was missing (could be null if not a registered CouchbaseFeature, in which case + * see {@link #getMessage()}). + */ + public ServiceType getFeature() { + return feature; + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java index 1517f786..cc55d050 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java @@ -16,13 +16,13 @@ package org.springframework.data.couchbase.core.convert; +import java.util.Collections; + import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.convert.CustomConversions; - -import java.util.Collections; +import org.springframework.data.convert.EntityInstantiators; /** * An abstract {@link CouchbaseConverter} that provides the basics for the {@link MappingCouchbaseConverter}. @@ -32,79 +32,79 @@ import java.util.Collections; */ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter, InitializingBean { - /** - * Contains the conversion service. - */ - protected final GenericConversionService conversionService; + /** + * Contains the conversion service. + */ + protected final GenericConversionService conversionService; - /** - * Contains the entity instantiators. - */ - protected EntityInstantiators instantiators = new EntityInstantiators(); + /** + * Contains the entity instantiators. + */ + protected EntityInstantiators instantiators = new EntityInstantiators(); - /** - * Holds the custom conversions. - */ - protected CustomConversions conversions = new CouchbaseCustomConversions(Collections.emptyList()); + /** + * Holds the custom conversions. + */ + protected CustomConversions conversions = new CouchbaseCustomConversions(Collections.emptyList()); - /** - * Create a new converter and hand it over the {@link ConversionService} - * - * @param conversionService the conversion service to use. - */ - protected AbstractCouchbaseConverter(final GenericConversionService conversionService) { - this.conversionService = conversionService; - } + /** + * Create a new converter and hand it over the {@link ConversionService} + * + * @param conversionService the conversion service to use. + */ + protected AbstractCouchbaseConverter(final GenericConversionService conversionService) { + this.conversionService = conversionService; + } - /** - * Return the conversion service. - * - * @return the conversion service. - */ - @Override - public ConversionService getConversionService() { - return conversionService; - } + /** + * Return the conversion service. + * + * @return the conversion service. + */ + @Override + public ConversionService getConversionService() { + return conversionService; + } - /** - * Set the custom conversions. - * - * @param conversions the conversions. - */ - public void setCustomConversions(final CustomConversions conversions) { - this.conversions = conversions; - } + /** + * Set the custom conversions. + * + * @param conversions the conversions. + */ + public void setCustomConversions(final CustomConversions conversions) { + this.conversions = conversions; + } - /** - * Set the entity instantiators. - * - * @param instantiators the instantiators. - */ - public void setInstantiators(final EntityInstantiators instantiators) { - this.instantiators = instantiators; - } + /** + * Set the entity instantiators. + * + * @param instantiators the instantiators. + */ + public void setInstantiators(final EntityInstantiators instantiators) { + this.instantiators = instantiators; + } - /** - * Do nothing after the properties set on the bean. - */ - @Override - public void afterPropertiesSet() { - conversions.registerConvertersIn(conversionService); - } + /** + * Do nothing after the properties set on the bean. + */ + @Override + public void afterPropertiesSet() { + conversions.registerConvertersIn(conversionService); + } - @Override - public Object convertForWriteIfNeeded(Object value) { - if (value == null) { - return null; - } + @Override + public Object convertForWriteIfNeeded(Object value) { + if (value == null) { + return null; + } - return this.conversions.getCustomWriteTarget(value.getClass()) // - .map(it -> (Object) this.conversionService.convert(value, it)) // - .orElse(value); - } + return this.conversions.getCustomWriteTarget(value.getClass()) // + .map(it -> (Object) this.conversionService.convert(value, it)) // + .orElse(value); + } - @Override - public Class getWriteClassFor(Class clazz) { - return this.conversions.getCustomWriteTarget(clazz).orElse(clazz); - } + @Override + public Class getWriteClassFor(Class clazz) { + return this.conversions.getCustomWriteTarget(clazz).orElse(clazz); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/ConverterRegistration.java b/src/main/java/org/springframework/data/couchbase/core/convert/ConverterRegistration.java index 4518d3fe..c218ce04 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/ConverterRegistration.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/ConverterRegistration.java @@ -28,89 +28,89 @@ import org.springframework.util.Assert; */ class ConverterRegistration { - private final ConvertiblePair convertiblePair; - private final boolean reading; - private final boolean writing; + private final ConvertiblePair convertiblePair; + private final boolean reading; + private final boolean writing; - /** - * Creates a new {@link ConverterRegistration}. - * - * @param convertiblePair must not be {@literal null}. - * @param isReading whether to force to consider the converter for reading. - * @param isWriting whether to force to consider the converter for reading. - */ - public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) { - Assert.notNull(convertiblePair, "ConvertiblePair must not be null!"); + /** + * Creates a new {@link ConverterRegistration}. + * + * @param convertiblePair must not be {@literal null}. + * @param isReading whether to force to consider the converter for reading. + * @param isWriting whether to force to consider the converter for reading. + */ + public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) { + Assert.notNull(convertiblePair, "ConvertiblePair must not be null!"); - this.convertiblePair = convertiblePair; - reading = isReading; - writing = isWriting; - } + this.convertiblePair = convertiblePair; + reading = isReading; + writing = isWriting; + } - /** - * Creates a new {@link ConverterRegistration} from the given source and target type and read/write flags. - * - * @param source the source type to be converted from, must not be {@literal null}. - * @param target the target type to be converted to, must not be {@literal null}. - * @param isReading whether to force to consider the converter for reading. - * @param isWriting whether to force to consider the converter for writing. - */ - public ConverterRegistration(Class source, Class target, boolean isReading, boolean isWriting) { - this(new ConvertiblePair(source, target), isReading, isWriting); - } + /** + * Creates a new {@link ConverterRegistration} from the given source and target type and read/write flags. + * + * @param source the source type to be converted from, must not be {@literal null}. + * @param target the target type to be converted to, must not be {@literal null}. + * @param isReading whether to force to consider the converter for reading. + * @param isWriting whether to force to consider the converter for writing. + */ + public ConverterRegistration(Class source, Class target, boolean isReading, boolean isWriting) { + this(new ConvertiblePair(source, target), isReading, isWriting); + } - /** - * Returns whether the converter shall be used for writing. - * - * @return - */ - public boolean isWriting() { - return writing == true || (!reading && isSimpleTargetType()); - } + /** + * Returns whether the given type is a type that Couchbase can handle basically. + * + * @param type + * @return + */ + private static boolean isCouchbaseBasicType(Class type) { + return CouchbaseSimpleTypes.JSON_TYPES.isSimpleType(type); + } - /** - * Returns whether the converter shall be used for reading. - * - * @return - */ - public boolean isReading() { - return reading == true || (!writing && isSimpleSourceType()); - } + /** + * Returns whether the converter shall be used for writing. + * + * @return + */ + public boolean isWriting() { + return writing == true || (!reading && isSimpleTargetType()); + } - /** - * Returns the actual conversion pair. - * - * @return - */ - public ConvertiblePair getConvertiblePair() { - return convertiblePair; - } + /** + * Returns whether the converter shall be used for reading. + * + * @return + */ + public boolean isReading() { + return reading == true || (!writing && isSimpleSourceType()); + } - /** - * Returns whether the source type is a Couchbase simple one. - * - * @return - */ - public boolean isSimpleSourceType() { - return isCouchbaseBasicType(convertiblePair.getSourceType()); - } + /** + * Returns the actual conversion pair. + * + * @return + */ + public ConvertiblePair getConvertiblePair() { + return convertiblePair; + } - /** - * Returns whether the target type is a Couchbase simple one. - * - * @return - */ - public boolean isSimpleTargetType() { - return isCouchbaseBasicType(convertiblePair.getTargetType()); - } + /** + * Returns whether the source type is a Couchbase simple one. + * + * @return + */ + public boolean isSimpleSourceType() { + return isCouchbaseBasicType(convertiblePair.getSourceType()); + } - /** - * Returns whether the given type is a type that Couchbase can handle basically. - * - * @param type - * @return - */ - private static boolean isCouchbaseBasicType(Class type) { - return CouchbaseSimpleTypes.JSON_TYPES.isSimpleType(type); - } + /** + * Returns whether the target type is a Couchbase simple one. + * + * @return + */ + public boolean isSimpleTargetType() { + return isCouchbaseBasicType(convertiblePair.getTargetType()); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseConverter.java index 34d93f99..ca452bff 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseConverter.java @@ -29,31 +29,29 @@ import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProper * @author Simon Baslé */ public interface CouchbaseConverter - extends EntityConverter, - CouchbasePersistentProperty, Object, CouchbaseDocument>, - CouchbaseWriter, - EntityReader { + extends EntityConverter, CouchbasePersistentProperty, Object, CouchbaseDocument>, + CouchbaseWriter, EntityReader { - /** - * Convert the value if necessary to the class that would actually be stored, - * or leave it as is if no conversion needed. - * - * @param value the value to be converted to the class that would actually be stored. - * @return the converted value (or the same value if no conversion necessary). - */ - Object convertForWriteIfNeeded(Object value); + /** + * Convert the value if necessary to the class that would actually be stored, or leave it as is if no conversion + * needed. + * + * @param value the value to be converted to the class that would actually be stored. + * @return the converted value (or the same value if no conversion necessary). + */ + Object convertForWriteIfNeeded(Object value); - /** - * Return the Class that would actually be stored for a given Class. - * - * @param clazz the source class. - * @return the target class that would actually be stored. - * @see #convertForWriteIfNeeded(Object) - */ - Class getWriteClassFor(Class clazz); + /** + * Return the Class that would actually be stored for a given Class. + * + * @param clazz the source class. + * @return the target class that would actually be stored. + * @see #convertForWriteIfNeeded(Object) + */ + Class getWriteClassFor(Class clazz); - /** - * @return the name of the field that will hold type information. - */ - String getTypeKey(); + /** + * @return the name of the field that will hold type information. + */ + String getTypeKey(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java index 9f1de201..9f84ffa9 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseCustomConversions.java @@ -16,17 +16,19 @@ package org.springframework.data.couchbase.core.convert; -import org.springframework.data.mapping.model.SimpleTypeHolder; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.springframework.data.mapping.model.SimpleTypeHolder; + /** * Value object to capture custom conversion. *

    - *

    Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper - * inspection nor nested conversion.

    + *

    + * Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper inspection + * nor nested conversion. + *

    * * @author Michael Nitschinger * @author Oliver Gierke @@ -38,27 +40,27 @@ import java.util.List; */ public class CouchbaseCustomConversions extends org.springframework.data.convert.CustomConversions { - private static final StoreConversions STORE_CONVERSIONS; + private static final StoreConversions STORE_CONVERSIONS; - private static final List STORE_CONVERTERS; + private static final List STORE_CONVERTERS; - static { + static { - List converters = new ArrayList<>(); + List converters = new ArrayList<>(); - converters.addAll(DateConverters.getConvertersToRegister()); - converters.addAll(CouchbaseJsr310Converters.getConvertersToRegister()); + converters.addAll(DateConverters.getConvertersToRegister()); + converters.addAll(CouchbaseJsr310Converters.getConvertersToRegister()); - STORE_CONVERTERS = Collections.unmodifiableList(converters); - STORE_CONVERSIONS = StoreConversions.of(SimpleTypeHolder.DEFAULT, STORE_CONVERTERS); - } + STORE_CONVERTERS = Collections.unmodifiableList(converters); + STORE_CONVERSIONS = StoreConversions.of(SimpleTypeHolder.DEFAULT, STORE_CONVERTERS); + } - /** - * Create a new instance with a given list of converters. - * - * @param converters the list of custom converters. - */ - public CouchbaseCustomConversions(final List converters) { - super(STORE_CONVERSIONS, converters); - } + /** + * Create a new instance with a given list of converters. + * + * @param converters the list of custom converters. + */ + public CouchbaseCustomConversions(final List converters) { + super(STORE_CONVERSIONS, converters); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseDocumentPropertyAccessor.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseDocumentPropertyAccessor.java index bf3f9309..7b207c4d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseDocumentPropertyAccessor.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseDocumentPropertyAccessor.java @@ -30,47 +30,47 @@ import org.springframework.expression.TypedValue; */ public class CouchbaseDocumentPropertyAccessor extends MapAccessor { - /** - * Contains the static instance of thi accessor. - */ - static final MapAccessor INSTANCE = new CouchbaseDocumentPropertyAccessor(); + /** + * Contains the static instance of thi accessor. + */ + static final MapAccessor INSTANCE = new CouchbaseDocumentPropertyAccessor(); - /** - * Returns the target classes of the properties. - * - * @return - */ - @Override - public Class[] getSpecificTargetClasses() { - return new Class[] {CouchbaseDocument.class}; - } + /** + * Returns the target classes of the properties. + * + * @return + */ + @Override + public Class[] getSpecificTargetClasses() { + return new Class[] { CouchbaseDocument.class }; + } - /** - * It can always read from those properties. - * - * @param context the evaluation context. - * @param target the target object. - * @param name the name of the property. - * @return always true. - */ - @Override - public boolean canRead(final EvaluationContext context, final Object target, final String name) { - return true; - } + /** + * It can always read from those properties. + * + * @param context the evaluation context. + * @param target the target object. + * @param name the name of the property. + * @return always true. + */ + @Override + public boolean canRead(final EvaluationContext context, final Object target, final String name) { + return true; + } - /** - * Read the value from the property. - * - * @param context the evaluation context. - * @param target the target object. - * @param name the name of the property. - * @return the typed value of the content to be read. - */ - @Override - public TypedValue read(final EvaluationContext context, final Object target, final String name) { - Map source = (Map) target; + /** + * Read the value from the property. + * + * @param context the evaluation context. + * @param target the target object. + * @param name the name of the property. + * @return the typed value of the content to be read. + */ + @Override + public TypedValue read(final EvaluationContext context, final Object target, final String name) { + Map source = (Map) target; - Object value = source.get(name); - return value == null ? TypedValue.NULL : new TypedValue(value); - } + Object value = source.get(name); + return value == null ? TypedValue.NULL : new TypedValue(value); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseJsr310Converters.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseJsr310Converters.java index 92752ffe..e001f33f 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseJsr310Converters.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseJsr310Converters.java @@ -16,13 +16,10 @@ package org.springframework.data.couchbase.core.convert; -import static java.time.Instant.ofEpochMilli; -import static java.time.LocalDateTime.ofInstant; -import static java.time.ZoneId.systemDefault; +import static java.time.Instant.*; +import static java.time.LocalDateTime.*; +import static java.time.ZoneId.*; -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.ReadingConverter; -import org.springframework.data.convert.WritingConverter; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; @@ -35,6 +32,10 @@ import java.util.Collection; import java.util.Date; import java.util.List; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.WritingConverter; + /** * Helper class to register JSR-310 specific {@link Converter} implementations. * @@ -45,195 +46,198 @@ import java.util.List; */ public final class CouchbaseJsr310Converters { - private CouchbaseJsr310Converters() { + private CouchbaseJsr310Converters() { - } + } - /** - * Returns the converters to be registered - * - * @return - */ - public static Collection> getConvertersToRegister() { - List> converters = new ArrayList<>(); - converters.add(NumberToLocalDateTimeConverter.INSTANCE); - converters.add(LocalDateTimeToLongConverter.INSTANCE); - converters.add(NumberToLocalDateConverter.INSTANCE); - converters.add(LocalDateToLongConverter.INSTANCE); - converters.add(NumberToLocalTimeConverter.INSTANCE); - converters.add(LocalTimeToLongConverter.INSTANCE); - converters.add(NumberToInstantConverter.INSTANCE); - converters.add(InstantToLongConverter.INSTANCE); - converters.add(ZoneIdToStringConverter.INSTANCE); - converters.add(StringToZoneIdConverter.INSTANCE); - converters.add(DurationToStringConverter.INSTANCE); - converters.add(StringToDurationConverter.INSTANCE); - converters.add(PeriodToStringConverter.INSTANCE); - converters.add(StringToPeriodConverter.INSTANCE); - return converters; - } + /** + * Returns the converters to be registered + * + * @return + */ + public static Collection> getConvertersToRegister() { + List> converters = new ArrayList<>(); + converters.add(NumberToLocalDateTimeConverter.INSTANCE); + converters.add(LocalDateTimeToLongConverter.INSTANCE); + converters.add(NumberToLocalDateConverter.INSTANCE); + converters.add(LocalDateToLongConverter.INSTANCE); + converters.add(NumberToLocalTimeConverter.INSTANCE); + converters.add(LocalTimeToLongConverter.INSTANCE); + converters.add(NumberToInstantConverter.INSTANCE); + converters.add(InstantToLongConverter.INSTANCE); + converters.add(ZoneIdToStringConverter.INSTANCE); + converters.add(StringToZoneIdConverter.INSTANCE); + converters.add(DurationToStringConverter.INSTANCE); + converters.add(StringToDurationConverter.INSTANCE); + converters.add(PeriodToStringConverter.INSTANCE); + converters.add(StringToPeriodConverter.INSTANCE); + return converters; + } - @ReadingConverter - public enum NumberToLocalDateTimeConverter implements Converter { + @ReadingConverter + public enum NumberToLocalDateTimeConverter implements Converter { - INSTANCE; + INSTANCE; - @Override - public LocalDateTime convert(Number source) { - return source == null ? null : ofInstant( - DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source) - .toInstant(), systemDefault()); - } - } + @Override + public LocalDateTime convert(Number source) { + return source == null ? null + : ofInstant(DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).toInstant(), + systemDefault()); + } + } + @WritingConverter + public enum LocalDateTimeToLongConverter implements Converter { - @WritingConverter - public enum LocalDateTimeToLongConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public Long convert(LocalDateTime source) { + return source == null ? null + : DateConverters.DateToLongConverter.INSTANCE.convert(Date.from(source.atZone(systemDefault()).toInstant())); + } + } - @Override - public Long convert(LocalDateTime source) { - return source == null ? null : DateConverters.DateToLongConverter.INSTANCE.convert( - Date.from(source.atZone(systemDefault()).toInstant())); - } - } + @ReadingConverter + public enum NumberToLocalDateConverter implements Converter { - @ReadingConverter - public enum NumberToLocalDateConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public LocalDate convert(Number source) { + return source == null ? null + : ofInstant(ofEpochMilli(DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).getTime()), + systemDefault()).toLocalDate(); + } + } - @Override - public LocalDate convert(Number source) { - return source == null ? null : ofInstant(ofEpochMilli( - DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).getTime()), - systemDefault()).toLocalDate(); - } - } + @WritingConverter + public enum LocalDateToLongConverter implements Converter { - @WritingConverter - public enum LocalDateToLongConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public Long convert(LocalDate source) { + return source == null ? null + : DateConverters.DateToLongConverter.INSTANCE + .convert(Date.from(source.atStartOfDay(systemDefault()).toInstant())); + } + } - @Override - public Long convert(LocalDate source) { - return source == null ? null : DateConverters.DateToLongConverter.INSTANCE.convert( - Date.from(source.atStartOfDay(systemDefault()).toInstant())); - } - } + @ReadingConverter + public enum NumberToLocalTimeConverter implements Converter { - @ReadingConverter - public enum NumberToLocalTimeConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public LocalTime convert(Number source) { + return source == null ? null + : ofInstant(ofEpochMilli(DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).getTime()), + systemDefault()).toLocalTime(); + } + } - @Override - public LocalTime convert(Number source) { - return source == null ? null : ofInstant(ofEpochMilli( - DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source) - .getTime()), systemDefault()).toLocalTime(); - } - } + @WritingConverter + public enum LocalTimeToLongConverter implements Converter { - @WritingConverter - public enum LocalTimeToLongConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public Long convert(LocalTime source) { + return source == null ? null + : DateConverters.DateToLongConverter.INSTANCE + .convert(Date.from(source.atDate(LocalDate.now()).atZone(systemDefault()).toInstant())); + } + } - @Override - public Long convert(LocalTime source) { - return source == null ? null : DateConverters.DateToLongConverter.INSTANCE.convert( - Date.from(source.atDate(LocalDate.now()).atZone(systemDefault()).toInstant())); - } - } + @ReadingConverter + public enum NumberToInstantConverter implements Converter { - @ReadingConverter - public enum NumberToInstantConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public Instant convert(Number source) { + return source == null ? null + : DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).toInstant(); + } + } - @Override - public Instant convert(Number source) { - return source == null ? null : DateConverters.SerializedObjectToDateConverter.INSTANCE.convert(source).toInstant(); - } - } + @WritingConverter + public enum InstantToLongConverter implements Converter { - @WritingConverter - public enum InstantToLongConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public Long convert(Instant source) { + return source == null ? null + : DateConverters.DateToLongConverter.INSTANCE.convert(Date.from(source.atZone(systemDefault()).toInstant())); + } + } - @Override - public Long convert(Instant source) { - return source == null ? null : DateConverters.DateToLongConverter.INSTANCE.convert(Date.from(source.atZone(systemDefault()).toInstant())); - } - } + @WritingConverter + public enum ZoneIdToStringConverter implements Converter { - @WritingConverter - public enum ZoneIdToStringConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public String convert(ZoneId source) { + return source.toString(); + } + } - @Override - public String convert(ZoneId source) { - return source.toString(); - } - } + @ReadingConverter + public enum StringToZoneIdConverter implements Converter { - @ReadingConverter - public enum StringToZoneIdConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public ZoneId convert(String source) { + return ZoneId.of(source); + } + } - @Override - public ZoneId convert(String source) { - return ZoneId.of(source); - } - } + @WritingConverter + public enum DurationToStringConverter implements Converter { - @WritingConverter - public enum DurationToStringConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public String convert(Duration duration) { + return duration.toString(); + } + } - @Override - public String convert(Duration duration) { - return duration.toString(); - } - } + @ReadingConverter + public enum StringToDurationConverter implements Converter { - @ReadingConverter - public enum StringToDurationConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public Duration convert(String s) { + return Duration.parse(s); + } + } - @Override - public Duration convert(String s) { - return Duration.parse(s); - } - } + @WritingConverter + public enum PeriodToStringConverter implements Converter { - @WritingConverter - public enum PeriodToStringConverter implements Converter { + INSTANCE; - INSTANCE; + @Override + public String convert(Period period) { + return period.toString(); + } + } - @Override - public String convert(Period period) { - return period.toString(); - } - } + @ReadingConverter + public enum StringToPeriodConverter implements Converter { - @ReadingConverter - public enum StringToPeriodConverter implements Converter { + INSTANCE; - INSTANCE; - - @Override - public Period convert(String s) { - return Period.parse(s); - } - } + @Override + public Period convert(String s) { + return Period.parse(s); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseTypeMapper.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseTypeMapper.java index 4fe6462b..31e8a059 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseTypeMapper.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseTypeMapper.java @@ -26,6 +26,6 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; */ public interface CouchbaseTypeMapper extends TypeMapper { - String getTypeKey(); + String getTypeKey(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseWriter.java b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseWriter.java index 59e3e069..31228024 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseWriter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseWriter.java @@ -23,6 +23,4 @@ import org.springframework.data.convert.EntityWriter; * * @author Michael Nitschinger */ -public interface CouchbaseWriter extends - EntityWriter { -} +public interface CouchbaseWriter extends EntityWriter {} diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/CustomConversions.java b/src/main/java/org/springframework/data/couchbase/core/convert/CustomConversions.java index 19fd5f46..b68dc4f9 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/CustomConversions.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/CustomConversions.java @@ -21,8 +21,10 @@ import java.util.List; /** * Value object to capture custom conversion. *

    - *

    Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper - * inspection nor nested conversion.

    + *

    + * Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper inspection + * nor nested conversion. + *

    * * @author Michael Nitschinger * @author Oliver Gierke @@ -33,12 +35,12 @@ import java.util.List; @Deprecated public class CustomConversions extends CouchbaseCustomConversions { - /** - * Create a new instance with a given list of conversers. - * - * @param converters the list of custom converters. - */ - public CustomConversions(final List converters) { - super(converters); - } + /** + * Create a new instance with a given list of conversers. + * + * @param converters the list of custom converters. + */ + public CustomConversions(final List converters) { + super(converters); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/DateConverters.java b/src/main/java/org/springframework/data/couchbase/core/convert/DateConverters.java index 9533444f..d1352944 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/DateConverters.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/DateConverters.java @@ -16,7 +16,7 @@ package org.springframework.data.couchbase.core.convert; -import static java.time.ZoneId.systemDefault; +import static java.time.ZoneId.*; import java.time.Instant; import java.util.ArrayList; @@ -28,7 +28,6 @@ import java.util.List; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; - import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; @@ -43,171 +42,170 @@ import org.springframework.util.ClassUtils; */ public final class DateConverters { - private DateConverters() { - } + private static final boolean JODA_TIME_IS_PRESENT = ClassUtils.isPresent("org.joda.time.LocalDate", null); - private static final boolean JODA_TIME_IS_PRESENT = ClassUtils.isPresent("org.joda.time.LocalDate", null); + private DateConverters() {} - /** - * Returns all converters by this class that can be registered. - * - * @return the list of converters to register. - */ - public static Collection> getConvertersToRegister() { - List> converters = new ArrayList>(); + /** + * Returns all converters by this class that can be registered. + * + * @return the list of converters to register. + */ + public static Collection> getConvertersToRegister() { + List> converters = new ArrayList>(); - boolean useISOStringConverterForDate = Boolean.parseBoolean( - System.getProperty("org.springframework.data.couchbase.useISOStringConverterForDate", "false")); + boolean useISOStringConverterForDate = Boolean + .parseBoolean(System.getProperty("org.springframework.data.couchbase.useISOStringConverterForDate", "false")); - if (useISOStringConverterForDate) { - converters.add(DateToStringConverter.INSTANCE); - } else { - converters.add(DateToLongConverter.INSTANCE); - } + if (useISOStringConverterForDate) { + converters.add(DateToStringConverter.INSTANCE); + } else { + converters.add(DateToLongConverter.INSTANCE); + } - converters.add(SerializedObjectToDateConverter.INSTANCE); + converters.add(SerializedObjectToDateConverter.INSTANCE); - converters.add(CalendarToLongConverter.INSTANCE); - converters.add(NumberToCalendarConverter.INSTANCE); + converters.add(CalendarToLongConverter.INSTANCE); + converters.add(NumberToCalendarConverter.INSTANCE); - if (JODA_TIME_IS_PRESENT) { - converters.add(LocalDateToLongConverter.INSTANCE); - converters.add(LocalDateTimeToLongConverter.INSTANCE); - converters.add(DateTimeToLongConverter.INSTANCE); - converters.add(NumberToLocalDateConverter.INSTANCE); - converters.add(NumberToLocalDateTimeConverter.INSTANCE); - converters.add(NumberToDateTimeConverter.INSTANCE); - } + if (JODA_TIME_IS_PRESENT) { + converters.add(LocalDateToLongConverter.INSTANCE); + converters.add(LocalDateTimeToLongConverter.INSTANCE); + converters.add(DateTimeToLongConverter.INSTANCE); + converters.add(NumberToLocalDateConverter.INSTANCE); + converters.add(NumberToLocalDateTimeConverter.INSTANCE); + converters.add(NumberToDateTimeConverter.INSTANCE); + } - return converters; - } + return converters; + } - @WritingConverter - public enum DateToStringConverter implements Converter { - INSTANCE; + @WritingConverter + public enum DateToStringConverter implements Converter { + INSTANCE; - @Override - public String convert(Date source) { - return source == null ? null : source.toInstant().toString(); - } - } + @Override + public String convert(Date source) { + return source == null ? null : source.toInstant().toString(); + } + } - @ReadingConverter - public enum SerializedObjectToDateConverter implements Converter { - INSTANCE; + @ReadingConverter + public enum SerializedObjectToDateConverter implements Converter { + INSTANCE; - @Override - public Date convert(Object source) { - if (source == null) { - return null; - } - if (source instanceof Number) { - Date date = new Date(); - date.setTime(((Number) source).longValue()); - return date; - } else if (source instanceof String) { - return Date.from(Instant.parse((String) source).atZone(systemDefault()).toInstant()); - } else { - //Unsupported serialized object - return null; - } - } - } + @Override + public Date convert(Object source) { + if (source == null) { + return null; + } + if (source instanceof Number) { + Date date = new Date(); + date.setTime(((Number) source).longValue()); + return date; + } else if (source instanceof String) { + return Date.from(Instant.parse((String) source).atZone(systemDefault()).toInstant()); + } else { + // Unsupported serialized object + return null; + } + } + } - @WritingConverter - public enum DateToLongConverter implements Converter { - INSTANCE; + @WritingConverter + public enum DateToLongConverter implements Converter { + INSTANCE; - @Override - public Long convert(Date source) { - return source == null ? null : source.getTime(); - } - } + @Override + public Long convert(Date source) { + return source == null ? null : source.getTime(); + } + } - @WritingConverter - public enum CalendarToLongConverter implements Converter { - INSTANCE; + @WritingConverter + public enum CalendarToLongConverter implements Converter { + INSTANCE; - @Override - public Long convert(Calendar source) { - return source == null ? null : source.getTimeInMillis() / 1000; - } - } + @Override + public Long convert(Calendar source) { + return source == null ? null : source.getTimeInMillis() / 1000; + } + } - @ReadingConverter - public enum NumberToCalendarConverter implements Converter { - INSTANCE; + @ReadingConverter + public enum NumberToCalendarConverter implements Converter { + INSTANCE; - @Override - public Calendar convert(Number source) { - if (source == null) { - return null; - } + @Override + public Calendar convert(Number source) { + if (source == null) { + return null; + } - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(source.longValue() * 1000); - return calendar; - } - } + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(source.longValue() * 1000); + return calendar; + } + } - @WritingConverter - public enum LocalDateToLongConverter implements Converter { - INSTANCE; + @WritingConverter + public enum LocalDateToLongConverter implements Converter { + INSTANCE; - @Override - public Long convert(LocalDate source) { - return source == null ? null : source.toDate().getTime(); - } - } + @Override + public Long convert(LocalDate source) { + return source == null ? null : source.toDate().getTime(); + } + } - @WritingConverter - public enum LocalDateTimeToLongConverter implements Converter { - INSTANCE; + @WritingConverter + public enum LocalDateTimeToLongConverter implements Converter { + INSTANCE; - @Override - public Long convert(LocalDateTime source) { - return source == null ? null : source.toDate().getTime(); - } - } + @Override + public Long convert(LocalDateTime source) { + return source == null ? null : source.toDate().getTime(); + } + } - @WritingConverter - public enum DateTimeToLongConverter implements Converter { - INSTANCE; + @WritingConverter + public enum DateTimeToLongConverter implements Converter { + INSTANCE; - @Override - public Long convert(DateTime source) { - return source == null ? null : source.toDate().getTime(); - } - } + @Override + public Long convert(DateTime source) { + return source == null ? null : source.toDate().getTime(); + } + } - @ReadingConverter - public enum NumberToLocalDateConverter implements Converter { - INSTANCE; + @ReadingConverter + public enum NumberToLocalDateConverter implements Converter { + INSTANCE; - @Override - public LocalDate convert(Number source) { - return source == null ? null : new LocalDate(source.longValue()); - } - } + @Override + public LocalDate convert(Number source) { + return source == null ? null : new LocalDate(source.longValue()); + } + } - @ReadingConverter - public enum NumberToLocalDateTimeConverter implements Converter { - INSTANCE; + @ReadingConverter + public enum NumberToLocalDateTimeConverter implements Converter { + INSTANCE; - @Override - public LocalDateTime convert(Number source) { - return source == null ? null : new LocalDateTime(source.longValue()); - } - } + @Override + public LocalDateTime convert(Number source) { + return source == null ? null : new LocalDateTime(source.longValue()); + } + } - @ReadingConverter - public enum NumberToDateTimeConverter implements Converter { - INSTANCE; + @ReadingConverter + public enum NumberToDateTimeConverter implements Converter { + INSTANCE; - @Override - public DateTime convert(Number source) { - return source == null ? null : new DateTime(source.longValue()); - } - } + @Override + public DateTime convert(Number source) { + return source == null ? null : new DateTime(source.longValue()); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java b/src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java index 9bafb04d..5a16f162 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/DefaultCouchbaseTypeMapper.java @@ -21,8 +21,6 @@ import org.springframework.data.convert.TypeAliasAccessor; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.mapping.Alias; -import java.util.Optional; - /** * The Couchbase Type Mapper. * @@ -31,47 +29,47 @@ import java.util.Optional; */ public class DefaultCouchbaseTypeMapper extends DefaultTypeMapper implements CouchbaseTypeMapper { - /** - * The type key to use if a complex type was identified. - */ - public static final String DEFAULT_TYPE_KEY = "_class"; + /** + * The type key to use if a complex type was identified. + */ + public static final String DEFAULT_TYPE_KEY = "_class"; - private final String typeKey; + private final String typeKey; - /** - * Create a new type mapper with the type key. - * - * @param typeKey the typeKey to use. - */ - public DefaultCouchbaseTypeMapper(final String typeKey) { - super(new CouchbaseDocumentTypeAliasAccessor(typeKey)); - this.typeKey = typeKey; - } + /** + * Create a new type mapper with the type key. + * + * @param typeKey the typeKey to use. + */ + public DefaultCouchbaseTypeMapper(final String typeKey) { + super(new CouchbaseDocumentTypeAliasAccessor(typeKey)); + this.typeKey = typeKey; + } - @Override - public String getTypeKey() { - return this.typeKey; - } + @Override + public String getTypeKey() { + return this.typeKey; + } - public static final class CouchbaseDocumentTypeAliasAccessor implements TypeAliasAccessor { + public static final class CouchbaseDocumentTypeAliasAccessor implements TypeAliasAccessor { - private final String typeKey; + private final String typeKey; - public CouchbaseDocumentTypeAliasAccessor(final String typeKey) { - this.typeKey = typeKey; - } + public CouchbaseDocumentTypeAliasAccessor(final String typeKey) { + this.typeKey = typeKey; + } - @Override - public Alias readAliasFrom(final CouchbaseDocument source) { - return Alias.ofNullable(source.get(typeKey)); - } + @Override + public Alias readAliasFrom(final CouchbaseDocument source) { + return Alias.ofNullable(source.get(typeKey)); + } - @Override - public void writeTypeTo(final CouchbaseDocument sink, final Object alias) { - if (typeKey != null) { - sink.put(typeKey, alias); - } - } - } + @Override + public void writeTypeTo(final CouchbaseDocument sink, final Object alias) { + if (typeKey != null) { + sink.put(typeKey, alias); + } + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java index f80121d0..2206d4b8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java @@ -17,6 +17,7 @@ package org.springframework.data.couchbase.core.convert; import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -27,8 +28,6 @@ import java.util.Optional; import java.util.TreeMap; import java.util.UUID; -import com.couchbase.client.java.repository.annotation.Field; - import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.CollectionFactory; @@ -37,6 +36,7 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.mapping.CouchbaseList; +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.id.GeneratedValue; @@ -65,880 +65,864 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** - * A mapping converter for Couchbase. - * - * The converter is responsible for reading from and writing to entities and converting it into a - * consumable database represenation. + * A mapping converter for Couchbase. The converter is responsible for reading from and writing to entities and + * converting it into a consumable database representation. * * @author Michael Nitschinger * @author Oliver Gierke * @author Geoffrey Mina * @author Mark Paluch */ -public class MappingCouchbaseConverter extends AbstractCouchbaseConverter - implements ApplicationContextAware { - - /** - * The default "type key", the name of the field that will hold type information. - * - * @see #TYPEKEY_SYNCGATEWAY_COMPATIBLE - */ - public static final String TYPEKEY_DEFAULT = DefaultCouchbaseTypeMapper.DEFAULT_TYPE_KEY; - - /** - * A "type key" (the name of the field that will hold type information) that is - * compatible with Sync Gateway (which doesn't allows underscores). - */ - public static final String TYPEKEY_SYNCGATEWAY_COMPATIBLE = "javaClass"; - - /** - * The overall application context. - */ - protected ApplicationContext applicationContext; - - /** - * The generic mapping context. - */ - protected final MappingContext, - CouchbasePersistentProperty> mappingContext; - - /** - * The Couchbase specific type mapper in use. - */ - protected CouchbaseTypeMapper typeMapper; - - /** - * Spring Expression Language context. - */ - private final SpELContext spELContext; - - /** - * Enable strict @Field checking on mapper - */ - private boolean enableStrictFieldChecking = false; - - /** - * Create a new {@link MappingCouchbaseConverter}. - * - * @param mappingContext the mapping context to use. - */ - public MappingCouchbaseConverter(final MappingContext, - CouchbasePersistentProperty> mappingContext) { - this(mappingContext, TYPEKEY_DEFAULT); - } - - /** - * Create a new {@link MappingCouchbaseConverter} that will store class name for - * complex types in the typeKey attribute. - * - * @param mappingContext the mapping context to use. - * @param typeKey the attribute name to use to store complex types class name. - */ - public MappingCouchbaseConverter(final MappingContext, - CouchbasePersistentProperty> mappingContext, final String typeKey) { - super(new DefaultConversionService()); - - this.mappingContext = mappingContext; - typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey : TYPEKEY_DEFAULT); - spELContext = new SpELContext(CouchbaseDocumentPropertyAccessor.INSTANCE); - } - - @Override - public MappingContext, CouchbasePersistentProperty> getMappingContext() { - return mappingContext; - } - - @Override - public String getTypeKey() { - return typeMapper.getTypeKey(); - } - - /** - * Toggles strict checking of the couchbase {@link Field} annotation. If enabled, - * strict checking will prevent non-annotated properties to be serialized. This only - * applies to the Couchbase datastore, allowing other Spring Data datastores to still - * deal with the property. - * - * @param enableStrictFieldChecking true to only consider Field-annotated properties for - * Couchbase serialization. - * @see DATACOUCH-226 - */ - public void setEnableStrictFieldChecking(boolean enableStrictFieldChecking){ - this.enableStrictFieldChecking = enableStrictFieldChecking; - } - - @Override - public R read(final Class clazz, final CouchbaseDocument source) { - return read(ClassTypeInformation.from(clazz), source, null); - } - - /** - * Read an incoming {@link CouchbaseDocument} into the target entity. - * - * @param type the type information of the target entity. - * @param source the document to convert. - * @param the entity type. - * @return the converted entity. - */ - protected R read(final TypeInformation type, final CouchbaseDocument source) { - return read(type, source, null); - } - - /** - * Read an incoming {@link CouchbaseDocument} into the target entity. - * - * @param type the type information of the target entity. - * @param source the document to convert. - * @param parent an optional parent object. - * @param the entity type. - * @return the converted entity. - */ - @SuppressWarnings("unchecked") - protected R read(final TypeInformation type, final CouchbaseDocument source, final Object parent) { - if (source == null) { - return null; - } - - TypeInformation typeToUse = typeMapper.readType(source, type); - Class rawType = typeToUse.getType(); - - if (conversions.hasCustomReadTarget(source.getClass(), rawType)) { - return conversionService.convert(source, rawType); - } - - if (typeToUse.isMap()) { - return (R) readMap(typeToUse, source, parent); - } - - CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext.getRequiredPersistentEntity(typeToUse); - return read(entity, source, parent); - } - - private boolean isIdConstructionProperty(final CouchbasePersistentProperty property) { - return property.isAnnotationPresent(IdPrefix.class) || property.isAnnotationPresent(IdSuffix.class); - } - +public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implements ApplicationContextAware { /** - * Read an incoming {@link CouchbaseDocument} into the target entity. - * - * @param entity the target entity. - * @param source the document to convert. - * @param parent an optional parent object. - * @param the entity type. - * @return the converted entity. - */ - protected R read(final CouchbasePersistentEntity entity, final CouchbaseDocument source, final Object parent) { - final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(source, spELContext); - ParameterValueProvider provider = - getParameterProvider(entity, source, evaluator, parent); - EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); - - final R instance = instantiator.createInstance(entity, provider); - final ConvertingPropertyAccessor accessor = getPropertyAccessor(instance); - - entity.doWithProperties(new PropertyHandler() { - @Override - public void doWithPersistentProperty(final CouchbasePersistentProperty prop) { - if (!doesPropertyExistInSource(prop) || entity.isConstructorArgument(prop) || isIdConstructionProperty(prop) || prop.isAnnotationPresent(N1qlJoin.class)) { - return; - } - Object obj = prop.isIdProperty() ? source.getId() : getValueInternal(prop, source, instance); - accessor.setProperty(prop, obj); - } - - private boolean doesPropertyExistInSource(final CouchbasePersistentProperty property) { - return property.isIdProperty() || source.containsKey(property.getFieldName()); - } - - private boolean isIdConstructionProperty(final CouchbasePersistentProperty property) { - return property.isAnnotationPresent(IdPrefix.class) || property.isAnnotationPresent(IdSuffix.class); - } - }); - - entity.doWithAssociations((AssociationHandler) association -> { - CouchbasePersistentProperty inverseProp = association.getInverse(); - Object obj = getValueInternal(inverseProp, source, instance); - accessor.setProperty(inverseProp, obj); - }); - - - return instance; - } - - /** - * Loads the property value through the value provider. - * - * @param property the source property. - * @param source the source document. - * @param parent the optional parent. - * @return the actual property value. - */ - protected Object getValueInternal(final CouchbasePersistentProperty property, final CouchbaseDocument source, - final Object parent) { - return new CouchbasePropertyValueProvider(source, spELContext, parent).getPropertyValue(property); - } - - /** - * Creates a new parameter provider. - * - * @param entity the persistent entity. - * @param source the source document. - * @param evaluator the SPEL expression evaluator. - * @param parent the optional parent. - * @return a new parameter value provider. - */ - private ParameterValueProvider getParameterProvider( - final CouchbasePersistentEntity entity, final CouchbaseDocument source, - final DefaultSpELExpressionEvaluator evaluator, final Object parent) { - CouchbasePropertyValueProvider provider = new CouchbasePropertyValueProvider(source, evaluator, parent); - PersistentEntityParameterValueProvider parameterProvider = - new PersistentEntityParameterValueProvider<>(entity, provider, parent); - - return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider, - parent); - } - - /** - * Recursively parses the a map from the source document. - * - * @param type the type information for the document. - * @param source the source document. - * @param parent the optional parent. - * @return the recursively parsed map. - */ - @SuppressWarnings("unchecked") - protected Map readMap(final TypeInformation type, final CouchbaseDocument source, - final Object parent) { - Assert.notNull(source, "CouchbaseDocument must not be null!"); - - Class mapType = typeMapper.readType(source, type).getType(); - Map map = CollectionFactory.createMap(mapType, source.export().keySet().size()); - Map sourceMap = source.getPayload(); - - for (Map.Entry entry : sourceMap.entrySet()) { - Object key = entry.getKey(); - Object value = entry.getValue(); - - TypeInformation keyTypeInformation = type.getComponentType(); - if (keyTypeInformation != null) { - Class keyType = keyTypeInformation.getType(); - key = conversionService.convert(key, keyType); - } - - TypeInformation valueType = type.getMapValueType(); - if (value instanceof CouchbaseDocument) { - map.put(key, read(valueType, (CouchbaseDocument) value, parent)); - } else if (value instanceof CouchbaseList) { - map.put(key, readCollection(valueType, (CouchbaseList) value, parent)); - } else { - Class valueClass = valueType == null ? null : valueType.getType(); - map.put(key, getPotentiallyConvertedSimpleRead(value, valueClass)); - } - } - - return map; - } - - /** - * Potentially convert simple values like ENUMs. - * - * @param value the value to convert. - * @param target the target object. - * @return the potentially converted object. - */ - @SuppressWarnings("unchecked") - private Object getPotentiallyConvertedSimpleRead(final Object value, final Class target) { - if (value == null || target == null) { - return value; - } - - if (conversions.hasCustomReadTarget(value.getClass(), target)) { - return conversionService.convert(value, target); - } - - if (Enum.class.isAssignableFrom(target)) { - return Enum.valueOf((Class) target, value.toString()); - } - - if (Class.class.isAssignableFrom(target)) { - try { - return Class.forName(value.toString()); - } catch (ClassNotFoundException e) { - throw new MappingException("Unable to create class from " + value.toString()); - } - } - - return target.isAssignableFrom(value.getClass()) ? value : conversionService.convert(value, target); - } - - @Override - public void write(final Object source, final CouchbaseDocument target) { - if (source == null) { - return; - } - - boolean isCustom = conversions.getCustomWriteTarget(source.getClass(), CouchbaseDocument.class).isPresent(); - TypeInformation type = ClassTypeInformation.from(source.getClass()); - - if (!isCustom) { - typeMapper.writeType(type, target); - } - - writeInternal(source, target, type); - if (target.getId() == null) { - throw new MappingException("An ID property is needed, but not found/could not be generated on this entity."); - } - } - - /** - * Convert a source object into a {@link CouchbaseDocument} target. - * - * @param source the source object. - * @param target the target document. - * @param typeHint the type information for the source. - */ - @SuppressWarnings("unchecked") - protected void writeInternal(final Object source, CouchbaseDocument target, final TypeInformation typeHint) { - if (source == null) { - return; - } - - Optional> customTarget = conversions.getCustomWriteTarget(source.getClass(), CouchbaseDocument.class); - if (customTarget.isPresent()) { - copyCouchbaseDocument(conversionService.convert(source, CouchbaseDocument.class), target); - return; - } - - if (Map.class.isAssignableFrom(source.getClass())) { - writeMapInternal((Map) source, target, ClassTypeInformation.MAP); - return; - } - - if (Collection.class.isAssignableFrom(source.getClass())) { - throw new IllegalArgumentException("Root Document must be either CouchbaseDocument or Map."); - } - - CouchbasePersistentEntity entity = mappingContext.getPersistentEntity(source.getClass()); - writeInternal(source, target, entity); - addCustomTypeKeyIfNecessary(typeHint, source, target); - } - - /** - * Helper method to copy the internals from a source document into a target document. - * - * @param source the source document. - * @param target the target document. - */ - protected void copyCouchbaseDocument(final CouchbaseDocument source, final CouchbaseDocument target) { - for (Map.Entry entry : source.export().entrySet()) { - target.put(entry.getKey(), entry.getValue()); - } - target.setId(source.getId()); - target.setExpiration(source.getExpiration()); - } - - private String convertToString(Object propertyObj) { - if (propertyObj instanceof String) { - return (String) propertyObj; - } else if (propertyObj instanceof Number) { - return new StringBuffer().append(propertyObj).toString(); - } else { - return propertyObj.toString(); - } - } - - /** - * Internal helper method to write the source object into the target document. - * - * @param source the source object. - * @param target the target document. - * @param entity the persistent entity to convert from. - */ - protected void writeInternal(final Object source, final CouchbaseDocument target, - final CouchbasePersistentEntity entity) { - if (source == null) { - return; - } - - if (entity == null) { - throw new MappingException("No mapping metadata found for entity of type " + source.getClass().getName()); - } - - final ConvertingPropertyAccessor accessor = getPropertyAccessor(source); - final CouchbasePersistentProperty idProperty = entity.getIdProperty(); - final CouchbasePersistentProperty versionProperty = entity.getVersionProperty(); - - GeneratedValue generatedValueInfo = null; - final TreeMap prefixes = new TreeMap<>(); - final TreeMap suffixes = new TreeMap<>(); - final TreeMap idAttributes = new TreeMap<>(); - - target.setExpiration(entity.getExpiry()); - - entity.doWithProperties(new PropertyHandler() { - @Override - public void doWithPersistentProperty(final CouchbasePersistentProperty prop) { - if (prop.equals(idProperty) || (versionProperty != null && prop.equals(versionProperty))) { - return; - } else if (enableStrictFieldChecking && !prop.isAnnotationPresent(Field.class)) { - return; - } else if (prop.isAnnotationPresent(N1qlJoin.class)) { - return; - } - - Object propertyObj = accessor.getProperty(prop, prop.getType()); - if (null != propertyObj) { - if (prop.isAnnotationPresent(IdPrefix.class)) { - IdPrefix prefix = prop.findAnnotation(IdPrefix.class); - int order = prefix.order(); - prefixes.put(order, convertToString(propertyObj)); - return; - } - - if (prop.isAnnotationPresent(IdSuffix.class)) { - IdSuffix suffix = prop.findAnnotation(IdSuffix.class); - int order = suffix.order(); - suffixes.put(order, convertToString(propertyObj)); - return; - } - - if (prop.isAnnotationPresent(IdAttribute.class)) { - IdAttribute idAttribute = prop.findAnnotation(IdAttribute.class); - int order = idAttribute.order(); - idAttributes.put(order, convertToString(propertyObj)); - } - - if (!conversions.isSimpleType(propertyObj.getClass())) { - writePropertyInternal(propertyObj, target, prop); - } else { - writeSimpleInternal(propertyObj, target, prop.getFieldName()); - } - } - } - }); - - if (idProperty != null && target.getId() == null) { - String id = accessor.getProperty(idProperty, String.class); - if(idProperty.isAnnotationPresent(GeneratedValue.class) && (id == null || id.equals(""))) { - generatedValueInfo = idProperty.findAnnotation(GeneratedValue.class); - target.setId(generateId(generatedValueInfo, prefixes, suffixes, idAttributes)); - } else { - target.setId(id); - } - } - - entity.doWithAssociations(new AssociationHandler() { - @Override - public void doWithAssociation(final Association association) { - CouchbasePersistentProperty inverseProp = association.getInverse(); - Class type = inverseProp.getType(); - Object propertyObj = accessor.getProperty(inverseProp, type); - if (null != propertyObj) { - writePropertyInternal(propertyObj, target, inverseProp); - } - } - }); - - } - - /** - * Helper method to write a property into the target document. - * - * @param source the source object. - * @param target the target document. - * @param prop the property information. - */ - @SuppressWarnings("unchecked") - private void writePropertyInternal(final Object source, final CouchbaseDocument target, - final CouchbasePersistentProperty prop) { - if (source == null) { - return; - } - - String name = prop.getFieldName(); - TypeInformation valueType = ClassTypeInformation.from(source.getClass()); - TypeInformation type = prop.getTypeInformation(); - - if (valueType.isCollectionLike()) { - CouchbaseList collectionDoc = createCollection(asCollection(source), prop); - target.put(name, collectionDoc); - return; - } - - if (valueType.isMap()) { - CouchbaseDocument mapDoc = createMap((Map) source, prop); - target.put(name, mapDoc); - return; - } - - Optional> basicTargetType = conversions.getCustomWriteTarget(source.getClass()); - if (basicTargetType.isPresent()) { - - basicTargetType.ifPresent(it -> { - target.put(name, conversionService.convert(source, it)); - }); - - return; - } - - CouchbaseDocument propertyDoc = new CouchbaseDocument(); - addCustomTypeKeyIfNecessary(type, source, propertyDoc); - - CouchbasePersistentEntity entity = isSubtype(prop.getType(), source.getClass()) ? mappingContext - .getRequiredPersistentEntity(source.getClass()) : mappingContext.getRequiredPersistentEntity(type); - writeInternal(source, propertyDoc, entity); - target.put(name, propertyDoc); - } - - /** - * Wrapper method to create the underlying map. - * - * @param map the source map. - * @param prop the persistent property. - * @return the written couchbase document. - */ - private CouchbaseDocument createMap(final Map map, final CouchbasePersistentProperty prop) { - Assert.notNull(map, "Given map must not be null!"); - Assert.notNull(prop, "PersistentProperty must not be null!"); - - return writeMapInternal(map, new CouchbaseDocument(), prop.getTypeInformation()); - } - - /** - * Helper method to write the map into the couchbase document. - * - * @param source the source object. - * @param target the target document. - * @param type the type information for the document. - * @return the written couchbase document. - */ - private CouchbaseDocument writeMapInternal(final Map source, final CouchbaseDocument target, - final TypeInformation type) { - for (Map.Entry entry : source.entrySet()) { - Object key = entry.getKey(); - Object val = entry.getValue(); - - if (conversions.isSimpleType(key.getClass())) { - String simpleKey = key.toString(); - - if (val == null || conversions.isSimpleType(val.getClass())) { - writeSimpleInternal(val, target, simpleKey); - } else if (val instanceof Collection || val.getClass().isArray()) { - target.put(simpleKey, writeCollectionInternal(asCollection(val), new CouchbaseList(conversions.getSimpleTypeHolder()), type.getMapValueType())); - } else { - CouchbaseDocument embeddedDoc = new CouchbaseDocument(); - TypeInformation valueTypeInfo = type.isMap() ? type.getMapValueType() : ClassTypeInformation.OBJECT; - writeInternal(val, embeddedDoc, valueTypeInfo); - target.put(simpleKey, embeddedDoc); - } - } else { - throw new MappingException("Cannot use a complex object as a key value."); - } - } - - return target; - } - - /** - * Helper method to create the underlying collection/list. - * - * @param collection the collection to write. - * @param prop the property information. - * @return the created couchbase list. - */ - private CouchbaseList createCollection(final Collection collection, final CouchbasePersistentProperty prop) { - return writeCollectionInternal(collection, new CouchbaseList(conversions.getSimpleTypeHolder()), prop.getTypeInformation()); - } - - /** - * Helper method to write the internal collection. - * - * @param source the source object. - * @param target the target document. - * @param type the type information for the document. - * @return the created couchbase list. - */ - private CouchbaseList writeCollectionInternal(final Collection source, final CouchbaseList target, - final TypeInformation type) { - TypeInformation componentType = type == null ? null : type.getComponentType(); - - for (Object element : source) { - Class elementType = element == null ? null : element.getClass(); - - if (elementType == null || conversions.isSimpleType(elementType)) { - target.put(getPotentiallyConvertedSimpleWrite(element)); - } else if (element instanceof Collection || elementType.isArray()) { - target.put(writeCollectionInternal(asCollection(element), new CouchbaseList(conversions.getSimpleTypeHolder()), componentType)); - } else { - - CouchbaseDocument embeddedDoc = new CouchbaseDocument(); - writeInternal(element, embeddedDoc, componentType); - target.put(embeddedDoc); - } - - } - - return target; - } - - /** - * Read a collection from the source object. - * - * @param targetType the target type. - * @param source the list as source. - * @param parent the optional parent. - * @return the instantiated collection. - */ - @SuppressWarnings("unchecked") - private Object readCollection(final TypeInformation targetType, final CouchbaseList source, final Object parent) { - Assert.notNull(targetType, "Target type must not be null!"); - - Class collectionType = targetType.getType(); - if (source.isEmpty()) { - return getPotentiallyConvertedSimpleRead(new HashSet(), collectionType); - } - - collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class; - Collection items = targetType.getType().isArray() ? new ArrayList() : CollectionFactory - .createCollection(collectionType, source.size(false)); - TypeInformation componentType = targetType.getComponentType(); - Class rawComponentType = componentType == null ? null : componentType.getType(); - - for (int i = 0; i < source.size(false); i++) { - - Object dbObjItem = source.get(i); - - if (dbObjItem instanceof CouchbaseDocument) { - items.add(read(componentType, (CouchbaseDocument) dbObjItem, parent)); - } else if (dbObjItem instanceof CouchbaseList) { - items.add(readCollection(componentType, (CouchbaseList) dbObjItem, parent)); - } else { - items.add(getPotentiallyConvertedSimpleRead(dbObjItem, rawComponentType)); - } - } - - return getPotentiallyConvertedSimpleRead(items, targetType.getType()); - } - - /** - * Returns a collection from the given source object. - * - * @param source the source object. - * @return the target collection. - */ - private static Collection asCollection(final Object source) { - if (source instanceof Collection) { - return (Collection) source; - } - - return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); - } - - /** - * Check if one class is a subtype of the other. - * - * @param left the first class. - * @param right the second class. - * @return true if it is a subtype, false otherwise. - */ - private static boolean isSubtype(final Class left, final Class right) { - return left.isAssignableFrom(right) && !left.equals(right); - } - - /** - * Write the given source into the couchbase document target. - * - * @param source the source object. - * @param target the target document. - * @param key the key of the object. - */ - private void writeSimpleInternal(final Object source, final CouchbaseDocument target, final String key) { - target.put(key, getPotentiallyConvertedSimpleWrite(source)); - } - - private Object getPotentiallyConvertedSimpleWrite(final Object value) { - if (value == null) { - return null; - } - - Optional> customTarget = conversions.getCustomWriteTarget(value.getClass()); - - return customTarget.map(it -> (Object) conversionService.convert(value, it)) - .orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value); - } - - /** - * Add a custom type key if needed. - * - * @param type the type information. - * @param source th the source object. - * @param target the target document. - */ - protected void addCustomTypeKeyIfNecessary(TypeInformation type, Object source, CouchbaseDocument target) { - TypeInformation actualType = type != null ? type.getActualType() : type; - Class reference = actualType == null ? Object.class : actualType.getType(); - - boolean notTheSameClass = !source.getClass().equals(reference); - if (notTheSameClass) { - typeMapper.writeType(source.getClass(), target); - } - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - - /** - * Helper method to read the value based on the value type. - * - * @param value the value to convert. - * @param type the type information. - * @param parent the optional parent. - * @param the target type. - * @return the converted object. - */ - @SuppressWarnings("unchecked") - private R readValue(Object value, TypeInformation type, Object parent) { - Class rawType = type.getType(); - - if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { - return (R) conversionService.convert(value, rawType); - } else if (value instanceof CouchbaseDocument) { - return (R) read(type, (CouchbaseDocument) value, parent); - } else if (value instanceof CouchbaseList) { - return (R) readCollection(type, (CouchbaseList) value, parent); - } else { - return (R) getPotentiallyConvertedSimpleRead(value, rawType); - } - } - - private ConvertingPropertyAccessor getPropertyAccessor(Object source) { - - CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(source.getClass()); - PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); - - return new ConvertingPropertyAccessor<>(accessor, conversionService); - } - - /** - * A property value provider for Couchbase documents. - */ - private class CouchbasePropertyValueProvider implements PropertyValueProvider { - - /** - * The source document. - */ - private final CouchbaseDocument source; - - /** - * The expression evaluator. - */ - private final SpELExpressionEvaluator evaluator; - - /** - * The optional parent object. - */ - private final Object parent; - - public CouchbasePropertyValueProvider(final CouchbaseDocument source, final SpELContext factory, - final Object parent) { - this(source, new DefaultSpELExpressionEvaluator(source, factory), parent); - } - - public CouchbasePropertyValueProvider(final CouchbaseDocument source, - final DefaultSpELExpressionEvaluator evaluator, final Object parent) { - Assert.notNull(source, "CouchbaseDocument must not be null!"); - Assert.notNull(evaluator, "DefaultSpELExpressionEvaluator must not be null!"); - - this.source = source; - this.evaluator = evaluator; - this.parent = parent; - } - - @Override - @SuppressWarnings("unchecked") - public R getPropertyValue(final CouchbasePersistentProperty property) { - String expression = property.getSpelExpression(); - Object value = expression != null ? evaluator.evaluate(expression) : source.get(property.getFieldName()); - - if (property.isIdProperty()) { - return (R) source.getId(); - } - if (value == null) { - return null; - } - - return readValue(value, property.getTypeInformation(), parent); - } - } - - /** - * A expression parameter value provider. - */ - private class ConverterAwareSpELExpressionParameterValueProvider extends - SpELExpressionParameterValueProvider { - - private final Object parent; - - public ConverterAwareSpELExpressionParameterValueProvider(final SpELExpressionEvaluator evaluator, - final ConversionService conversionService, final ParameterValueProvider delegate, - final Object parent) { - super(evaluator, conversionService, delegate); - this.parent = parent; - } - - @Override - protected T potentiallyConvertSpelValue(final Object object, - final Parameter parameter) { - return readValue(object, parameter.getType(), parent); - } - } - - private String generateId(GeneratedValue generatedValue, TreeMap prefixes, TreeMap suffixes, - TreeMap idAttributes) { - String delimiter = generatedValue.delimiter(); - StringBuilder sb = new StringBuilder(); - boolean isAppending = false; - if (prefixes.size() > 0) { - appendKeyParts(sb, prefixes.values(), delimiter); - isAppending = true; - } - - if (generatedValue.strategy() == USE_ATTRIBUTES && idAttributes.size() > 0) { - if(isAppending) { - sb.append(delimiter); - } - appendKeyParts(sb, idAttributes.values(), delimiter); - } - - if (generatedValue.strategy() == UNIQUE) { - if(isAppending) { - sb.append(delimiter); - } - sb.append(UUID.randomUUID()); - } - - if (suffixes.size() > 0) { - if(isAppending) { - sb.append(delimiter); - } - appendKeyParts(sb, suffixes.values(), delimiter); - } - return sb.toString(); - } - - private StringBuilder appendKeyParts(StringBuilder sb, Collection values, String delimiter) { - boolean isAppending = false; - for(String value : values) { - if (isAppending) { - sb.append(delimiter); - } else { - isAppending = true; - } - sb.append(value); - } - return sb; - } + * The default "type key", the name of the field that will hold type information. + * + * @see #TYPEKEY_SYNCGATEWAY_COMPATIBLE + */ + public static final String TYPEKEY_DEFAULT = DefaultCouchbaseTypeMapper.DEFAULT_TYPE_KEY; + + /** + * A "type key" (the name of the field that will hold type information) that is compatible with Sync Gateway (which + * doesn't allows underscores). + */ + public static final String TYPEKEY_SYNCGATEWAY_COMPATIBLE = "javaClass"; + /** + * The generic mapping context. + */ + protected final MappingContext, CouchbasePersistentProperty> mappingContext; + /** + * Spring Expression Language context. + */ + private final SpELContext spELContext; + /** + * The overall application context. + */ + protected ApplicationContext applicationContext; + /** + * The Couchbase specific type mapper in use. + */ + protected CouchbaseTypeMapper typeMapper; + + public MappingCouchbaseConverter() { + super(new DefaultConversionService()); + + this.typeMapper = new DefaultCouchbaseTypeMapper(TYPEKEY_DEFAULT); + this.mappingContext = new CouchbaseMappingContext(); + this.spELContext = new SpELContext(CouchbaseDocumentPropertyAccessor.INSTANCE); + } + + /** + * Create a new {@link MappingCouchbaseConverter}. + * + * @param mappingContext the mapping context to use. + */ + public MappingCouchbaseConverter( + final MappingContext, CouchbasePersistentProperty> mappingContext) { + this(mappingContext, TYPEKEY_DEFAULT); + } + + /** + * Create a new {@link MappingCouchbaseConverter} that will store class name for complex types in the typeKey + * attribute. + * + * @param mappingContext the mapping context to use. + * @param typeKey the attribute name to use to store complex types class name. + */ + public MappingCouchbaseConverter( + final MappingContext, CouchbasePersistentProperty> mappingContext, + final String typeKey) { + super(new DefaultConversionService()); + + this.mappingContext = mappingContext; + typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey : TYPEKEY_DEFAULT); + spELContext = new SpELContext(CouchbaseDocumentPropertyAccessor.INSTANCE); + } + + /** + * Returns a collection from the given source object. + * + * @param source the source object. + * @return the target collection. + */ + private static Collection asCollection(final Object source) { + if (source instanceof Collection) { + return (Collection) source; + } + + return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); + } + + /** + * Check if one class is a subtype of the other. + * + * @param left the first class. + * @param right the second class. + * @return true if it is a subtype, false otherwise. + */ + private static boolean isSubtype(final Class left, final Class right) { + return left.isAssignableFrom(right) && !left.equals(right); + } + + @Override + public MappingContext, CouchbasePersistentProperty> getMappingContext() { + return mappingContext; + } + + @Override + public String getTypeKey() { + return typeMapper.getTypeKey(); + } + + @Override + public R read(final Class clazz, final CouchbaseDocument source) { + return read(ClassTypeInformation.from(clazz), source, null); + } + + /** + * Read an incoming {@link CouchbaseDocument} into the target entity. + * + * @param type the type information of the target entity. + * @param source the document to convert. + * @param the entity type. + * @return the converted entity. + */ + protected R read(final TypeInformation type, final CouchbaseDocument source) { + return read(type, source, null); + } + + /** + * Read an incoming {@link CouchbaseDocument} into the target entity. + * + * @param type the type information of the target entity. + * @param source the document to convert. + * @param parent an optional parent object. + * @param the entity type. + * @return the converted entity. + */ + @SuppressWarnings("unchecked") + protected R read(final TypeInformation type, final CouchbaseDocument source, final Object parent) { + if (source == null) { + return null; + } + + TypeInformation typeToUse = typeMapper.readType(source, type); + Class rawType = typeToUse.getType(); + + if (conversions.hasCustomReadTarget(source.getClass(), rawType)) { + return conversionService.convert(source, rawType); + } + + if (typeToUse.isMap()) { + return (R) readMap(typeToUse, source, parent); + } + + CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext + .getRequiredPersistentEntity(typeToUse); + return read(entity, source, parent); + } + + private boolean isIdConstructionProperty(final CouchbasePersistentProperty property) { + return property.isAnnotationPresent(IdPrefix.class) || property.isAnnotationPresent(IdSuffix.class); + } + + /** + * Read an incoming {@link CouchbaseDocument} into the target entity. + * + * @param entity the target entity. + * @param source the document to convert. + * @param parent an optional parent object. + * @param the entity type. + * @return the converted entity. + */ + protected R read(final CouchbasePersistentEntity entity, final CouchbaseDocument source, final Object parent) { + final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(source, spELContext); + ParameterValueProvider provider = getParameterProvider(entity, source, evaluator, + parent); + EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); + + final R instance = instantiator.createInstance(entity, provider); + final ConvertingPropertyAccessor accessor = getPropertyAccessor(instance); + + entity.doWithProperties(new PropertyHandler() { + @Override + public void doWithPersistentProperty(final CouchbasePersistentProperty prop) { + if (!doesPropertyExistInSource(prop) || entity.isConstructorArgument(prop) || isIdConstructionProperty(prop) + || prop.isAnnotationPresent(N1qlJoin.class)) { + return; + } + Object obj = prop.isIdProperty() ? source.getId() : getValueInternal(prop, source, instance); + accessor.setProperty(prop, obj); + } + + private boolean doesPropertyExistInSource(final CouchbasePersistentProperty property) { + return property.isIdProperty() || source.containsKey(property.getFieldName()); + } + + private boolean isIdConstructionProperty(final CouchbasePersistentProperty property) { + return property.isAnnotationPresent(IdPrefix.class) || property.isAnnotationPresent(IdSuffix.class); + } + }); + + entity.doWithAssociations((AssociationHandler) association -> { + CouchbasePersistentProperty inverseProp = association.getInverse(); + Object obj = getValueInternal(inverseProp, source, instance); + accessor.setProperty(inverseProp, obj); + }); + + return instance; + } + + /** + * Loads the property value through the value provider. + * + * @param property the source property. + * @param source the source document. + * @param parent the optional parent. + * @return the actual property value. + */ + protected Object getValueInternal(final CouchbasePersistentProperty property, final CouchbaseDocument source, + final Object parent) { + return new CouchbasePropertyValueProvider(source, spELContext, parent).getPropertyValue(property); + } + + /** + * Creates a new parameter provider. + * + * @param entity the persistent entity. + * @param source the source document. + * @param evaluator the SPEL expression evaluator. + * @param parent the optional parent. + * @return a new parameter value provider. + */ + private ParameterValueProvider getParameterProvider( + final CouchbasePersistentEntity entity, final CouchbaseDocument source, + final DefaultSpELExpressionEvaluator evaluator, final Object parent) { + CouchbasePropertyValueProvider provider = new CouchbasePropertyValueProvider(source, evaluator, parent); + PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider<>( + entity, provider, parent); + + return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider, + parent); + } + + /** + * Recursively parses the a map from the source document. + * + * @param type the type information for the document. + * @param source the source document. + * @param parent the optional parent. + * @return the recursively parsed map. + */ + @SuppressWarnings("unchecked") + protected Map readMap(final TypeInformation type, final CouchbaseDocument source, + final Object parent) { + Assert.notNull(source, "CouchbaseDocument must not be null!"); + + Class mapType = typeMapper.readType(source, type).getType(); + Map map = CollectionFactory.createMap(mapType, source.export().keySet().size()); + Map sourceMap = source.getPayload(); + + for (Map.Entry entry : sourceMap.entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + + TypeInformation keyTypeInformation = type.getComponentType(); + if (keyTypeInformation != null) { + Class keyType = keyTypeInformation.getType(); + key = conversionService.convert(key, keyType); + } + + TypeInformation valueType = type.getMapValueType(); + if (value instanceof CouchbaseDocument) { + map.put(key, read(valueType, (CouchbaseDocument) value, parent)); + } else if (value instanceof CouchbaseList) { + map.put(key, readCollection(valueType, (CouchbaseList) value, parent)); + } else { + Class valueClass = valueType == null ? null : valueType.getType(); + map.put(key, getPotentiallyConvertedSimpleRead(value, valueClass)); + } + } + + return map; + } + + /** + * Potentially convert simple values like ENUMs. + * + * @param value the value to convert. + * @param target the target object. + * @return the potentially converted object. + */ + @SuppressWarnings("unchecked") + private Object getPotentiallyConvertedSimpleRead(final Object value, final Class target) { + if (value == null || target == null) { + return value; + } + + if (conversions.hasCustomReadTarget(value.getClass(), target)) { + return conversionService.convert(value, target); + } + + if (Enum.class.isAssignableFrom(target)) { + return Enum.valueOf((Class) target, value.toString()); + } + + if (Class.class.isAssignableFrom(target)) { + try { + return Class.forName(value.toString()); + } catch (ClassNotFoundException e) { + throw new MappingException("Unable to create class from " + value.toString()); + } + } + + return target.isAssignableFrom(value.getClass()) ? value : conversionService.convert(value, target); + } + + @Override + public void write(final Object source, final CouchbaseDocument target) { + if (source == null) { + return; + } + + boolean isCustom = conversions.getCustomWriteTarget(source.getClass(), CouchbaseDocument.class).isPresent(); + TypeInformation type = ClassTypeInformation.from(source.getClass()); + + if (!isCustom) { + typeMapper.writeType(type, target); + } + + writeInternal(source, target, type); + if (target.getId() == null) { + throw new MappingException("An ID property is needed, but not found/could not be generated on this entity."); + } + } + + /** + * Convert a source object into a {@link CouchbaseDocument} target. + * + * @param source the source object. + * @param target the target document. + * @param typeHint the type information for the source. + */ + @SuppressWarnings("unchecked") + protected void writeInternal(final Object source, CouchbaseDocument target, final TypeInformation typeHint) { + if (source == null) { + return; + } + + Optional> customTarget = conversions.getCustomWriteTarget(source.getClass(), CouchbaseDocument.class); + if (customTarget.isPresent()) { + copyCouchbaseDocument(conversionService.convert(source, CouchbaseDocument.class), target); + return; + } + + if (Map.class.isAssignableFrom(source.getClass())) { + writeMapInternal((Map) source, target, ClassTypeInformation.MAP); + return; + } + + if (Collection.class.isAssignableFrom(source.getClass())) { + throw new IllegalArgumentException("Root Document must be either CouchbaseDocument or Map."); + } + + CouchbasePersistentEntity entity = mappingContext.getPersistentEntity(source.getClass()); + writeInternal(source, target, entity); + addCustomTypeKeyIfNecessary(typeHint, source, target); + } + + /** + * Helper method to copy the internals from a source document into a target document. + * + * @param source the source document. + * @param target the target document. + */ + protected void copyCouchbaseDocument(final CouchbaseDocument source, final CouchbaseDocument target) { + for (Map.Entry entry : source.export().entrySet()) { + target.put(entry.getKey(), entry.getValue()); + } + target.setId(source.getId()); + target.setExpiration(source.getExpiration()); + } + + private String convertToString(Object propertyObj) { + if (propertyObj instanceof String) { + return (String) propertyObj; + } else if (propertyObj instanceof Number) { + return new StringBuffer().append(propertyObj).toString(); + } else { + return propertyObj.toString(); + } + } + + /** + * Internal helper method to write the source object into the target document. + * + * @param source the source object. + * @param target the target document. + * @param entity the persistent entity to convert from. + */ + protected void writeInternal(final Object source, final CouchbaseDocument target, + final CouchbasePersistentEntity entity) { + if (source == null) { + return; + } + + if (entity == null) { + throw new MappingException("No mapping metadata found for entity of type " + source.getClass().getName()); + } + + final ConvertingPropertyAccessor accessor = getPropertyAccessor(source); + final CouchbasePersistentProperty idProperty = entity.getIdProperty(); + final CouchbasePersistentProperty versionProperty = entity.getVersionProperty(); + + GeneratedValue generatedValueInfo = null; + final TreeMap prefixes = new TreeMap<>(); + final TreeMap suffixes = new TreeMap<>(); + final TreeMap idAttributes = new TreeMap<>(); + + target.setExpiration(entity.getExpiry()); + + entity.doWithProperties(new PropertyHandler() { + @Override + public void doWithPersistentProperty(final CouchbasePersistentProperty prop) { + if (prop.equals(idProperty) || (versionProperty != null && prop.equals(versionProperty))) { + return; + } else if (prop.isAnnotationPresent(N1qlJoin.class)) { + return; + } + + Object propertyObj = accessor.getProperty(prop, prop.getType()); + if (null != propertyObj) { + if (prop.isAnnotationPresent(IdPrefix.class)) { + IdPrefix prefix = prop.findAnnotation(IdPrefix.class); + int order = prefix.order(); + prefixes.put(order, convertToString(propertyObj)); + return; + } + + if (prop.isAnnotationPresent(IdSuffix.class)) { + IdSuffix suffix = prop.findAnnotation(IdSuffix.class); + int order = suffix.order(); + suffixes.put(order, convertToString(propertyObj)); + return; + } + + if (prop.isAnnotationPresent(IdAttribute.class)) { + IdAttribute idAttribute = prop.findAnnotation(IdAttribute.class); + int order = idAttribute.order(); + idAttributes.put(order, convertToString(propertyObj)); + } + + if (!conversions.isSimpleType(propertyObj.getClass())) { + writePropertyInternal(propertyObj, target, prop); + } else { + writeSimpleInternal(propertyObj, target, prop.getFieldName()); + } + } + } + }); + + if (idProperty != null && target.getId() == null) { + String id = accessor.getProperty(idProperty, String.class); + if (idProperty.isAnnotationPresent(GeneratedValue.class) && (id == null || id.equals(""))) { + generatedValueInfo = idProperty.findAnnotation(GeneratedValue.class); + target.setId(generateId(generatedValueInfo, prefixes, suffixes, idAttributes)); + } else { + target.setId(id); + } + } + + entity.doWithAssociations(new AssociationHandler() { + @Override + public void doWithAssociation(final Association association) { + CouchbasePersistentProperty inverseProp = association.getInverse(); + Class type = inverseProp.getType(); + Object propertyObj = accessor.getProperty(inverseProp, type); + if (null != propertyObj) { + writePropertyInternal(propertyObj, target, inverseProp); + } + } + }); + + } + + /** + * Helper method to write a property into the target document. + * + * @param source the source object. + * @param target the target document. + * @param prop the property information. + */ + @SuppressWarnings("unchecked") + private void writePropertyInternal(final Object source, final CouchbaseDocument target, + final CouchbasePersistentProperty prop) { + if (source == null) { + return; + } + + String name = prop.getFieldName(); + TypeInformation valueType = ClassTypeInformation.from(source.getClass()); + TypeInformation type = prop.getTypeInformation(); + + if (valueType.isCollectionLike()) { + CouchbaseList collectionDoc = createCollection(asCollection(source), prop); + target.put(name, collectionDoc); + return; + } + + if (valueType.isMap()) { + CouchbaseDocument mapDoc = createMap((Map) source, prop); + target.put(name, mapDoc); + return; + } + + Optional> basicTargetType = conversions.getCustomWriteTarget(source.getClass()); + if (basicTargetType.isPresent()) { + + basicTargetType.ifPresent(it -> { + target.put(name, conversionService.convert(source, it)); + }); + + return; + } + + CouchbaseDocument propertyDoc = new CouchbaseDocument(); + addCustomTypeKeyIfNecessary(type, source, propertyDoc); + + CouchbasePersistentEntity entity = isSubtype(prop.getType(), source.getClass()) + ? mappingContext.getRequiredPersistentEntity(source.getClass()) + : mappingContext.getRequiredPersistentEntity(type); + writeInternal(source, propertyDoc, entity); + target.put(name, propertyDoc); + } + + /** + * Wrapper method to create the underlying map. + * + * @param map the source map. + * @param prop the persistent property. + * @return the written couchbase document. + */ + private CouchbaseDocument createMap(final Map map, final CouchbasePersistentProperty prop) { + Assert.notNull(map, "Given map must not be null!"); + Assert.notNull(prop, "PersistentProperty must not be null!"); + + return writeMapInternal(map, new CouchbaseDocument(), prop.getTypeInformation()); + } + + /** + * Helper method to write the map into the couchbase document. + * + * @param source the source object. + * @param target the target document. + * @param type the type information for the document. + * @return the written couchbase document. + */ + private CouchbaseDocument writeMapInternal(final Map source, final CouchbaseDocument target, + final TypeInformation type) { + for (Map.Entry entry : source.entrySet()) { + Object key = entry.getKey(); + Object val = entry.getValue(); + + if (conversions.isSimpleType(key.getClass())) { + String simpleKey = key.toString(); + + if (val == null || conversions.isSimpleType(val.getClass())) { + writeSimpleInternal(val, target, simpleKey); + } else if (val instanceof Collection || val.getClass().isArray()) { + target.put(simpleKey, writeCollectionInternal(asCollection(val), + new CouchbaseList(conversions.getSimpleTypeHolder()), type.getMapValueType())); + } else { + CouchbaseDocument embeddedDoc = new CouchbaseDocument(); + TypeInformation valueTypeInfo = type.isMap() ? type.getMapValueType() : ClassTypeInformation.OBJECT; + writeInternal(val, embeddedDoc, valueTypeInfo); + target.put(simpleKey, embeddedDoc); + } + } else { + throw new MappingException("Cannot use a complex object as a key value."); + } + } + + return target; + } + + /** + * Helper method to create the underlying collection/list. + * + * @param collection the collection to write. + * @param prop the property information. + * @return the created couchbase list. + */ + private CouchbaseList createCollection(final Collection collection, final CouchbasePersistentProperty prop) { + return writeCollectionInternal(collection, new CouchbaseList(conversions.getSimpleTypeHolder()), + prop.getTypeInformation()); + } + + /** + * Helper method to write the internal collection. + * + * @param source the source object. + * @param target the target document. + * @param type the type information for the document. + * @return the created couchbase list. + */ + private CouchbaseList writeCollectionInternal(final Collection source, final CouchbaseList target, + final TypeInformation type) { + TypeInformation componentType = type == null ? null : type.getComponentType(); + + for (Object element : source) { + Class elementType = element == null ? null : element.getClass(); + + if (elementType == null || conversions.isSimpleType(elementType)) { + target.put(getPotentiallyConvertedSimpleWrite(element)); + } else if (element instanceof Collection || elementType.isArray()) { + target.put(writeCollectionInternal(asCollection(element), new CouchbaseList(conversions.getSimpleTypeHolder()), + componentType)); + } else { + + CouchbaseDocument embeddedDoc = new CouchbaseDocument(); + writeInternal(element, embeddedDoc, componentType); + target.put(embeddedDoc); + } + + } + + return target; + } + + /** + * Read a collection from the source object. + * + * @param targetType the target type. + * @param source the list as source. + * @param parent the optional parent. + * @return the instantiated collection. + */ + @SuppressWarnings("unchecked") + private Object readCollection(final TypeInformation targetType, final CouchbaseList source, final Object parent) { + Assert.notNull(targetType, "Target type must not be null!"); + + Class collectionType = targetType.getType(); + if (source.isEmpty()) { + return getPotentiallyConvertedSimpleRead(new HashSet(), collectionType); + } + + collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class; + Collection items = targetType.getType().isArray() ? new ArrayList() + : CollectionFactory.createCollection(collectionType, source.size(false)); + TypeInformation componentType = targetType.getComponentType(); + Class rawComponentType = componentType == null ? null : componentType.getType(); + + for (int i = 0; i < source.size(false); i++) { + + Object dbObjItem = source.get(i); + + if (dbObjItem instanceof CouchbaseDocument) { + items.add(read(componentType, (CouchbaseDocument) dbObjItem, parent)); + } else if (dbObjItem instanceof CouchbaseList) { + items.add(readCollection(componentType, (CouchbaseList) dbObjItem, parent)); + } else { + items.add(getPotentiallyConvertedSimpleRead(dbObjItem, rawComponentType)); + } + } + + return getPotentiallyConvertedSimpleRead(items, targetType.getType()); + } + + /** + * Write the given source into the couchbase document target. + * + * @param source the source object. + * @param target the target document. + * @param key the key of the object. + */ + private void writeSimpleInternal(final Object source, final CouchbaseDocument target, final String key) { + target.put(key, getPotentiallyConvertedSimpleWrite(source)); + } + + private Object getPotentiallyConvertedSimpleWrite(final Object value) { + if (value == null) { + return null; + } + + Optional> customTarget = conversions.getCustomWriteTarget(value.getClass()); + + return customTarget.map(it -> (Object) conversionService.convert(value, it)) + .orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value); + } + + /** + * Add a custom type key if needed. + * + * @param type the type information. + * @param source th the source object. + * @param target the target document. + */ + protected void addCustomTypeKeyIfNecessary(TypeInformation type, Object source, CouchbaseDocument target) { + TypeInformation actualType = type != null ? type.getActualType() : type; + Class reference = actualType == null ? Object.class : actualType.getType(); + + boolean notTheSameClass = !source.getClass().equals(reference); + if (notTheSameClass) { + typeMapper.writeType(source.getClass(), target); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * Helper method to read the value based on the value type. + * + * @param value the value to convert. + * @param type the type information. + * @param parent the optional parent. + * @param the target type. + * @return the converted object. + */ + @SuppressWarnings("unchecked") + private R readValue(Object value, TypeInformation type, Object parent) { + Class rawType = type.getType(); + + if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { + return (R) conversionService.convert(value, rawType); + } else if (value instanceof CouchbaseDocument) { + return (R) read(type, (CouchbaseDocument) value, parent); + } else if (value instanceof CouchbaseList) { + return (R) readCollection(type, (CouchbaseList) value, parent); + } else { + return (R) getPotentiallyConvertedSimpleRead(value, rawType); + } + } + + private ConvertingPropertyAccessor getPropertyAccessor(Object source) { + + CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(source.getClass()); + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); + + return new ConvertingPropertyAccessor<>(accessor, conversionService); + } + + private String generateId(GeneratedValue generatedValue, TreeMap prefixes, + TreeMap suffixes, TreeMap idAttributes) { + String delimiter = generatedValue.delimiter(); + StringBuilder sb = new StringBuilder(); + boolean isAppending = false; + if (prefixes.size() > 0) { + appendKeyParts(sb, prefixes.values(), delimiter); + isAppending = true; + } + + if (generatedValue.strategy() == USE_ATTRIBUTES && idAttributes.size() > 0) { + if (isAppending) { + sb.append(delimiter); + } + appendKeyParts(sb, idAttributes.values(), delimiter); + } + + if (generatedValue.strategy() == UNIQUE) { + if (isAppending) { + sb.append(delimiter); + } + sb.append(UUID.randomUUID()); + } + + if (suffixes.size() > 0) { + if (isAppending) { + sb.append(delimiter); + } + appendKeyParts(sb, suffixes.values(), delimiter); + } + return sb.toString(); + } + + private StringBuilder appendKeyParts(StringBuilder sb, Collection values, String delimiter) { + boolean isAppending = false; + for (String value : values) { + if (isAppending) { + sb.append(delimiter); + } else { + isAppending = true; + } + sb.append(value); + } + return sb; + } + + /** + * A property value provider for Couchbase documents. + */ + private class CouchbasePropertyValueProvider implements PropertyValueProvider { + + /** + * The source document. + */ + private final CouchbaseDocument source; + + /** + * The expression evaluator. + */ + private final SpELExpressionEvaluator evaluator; + + /** + * The optional parent object. + */ + private final Object parent; + + public CouchbasePropertyValueProvider(final CouchbaseDocument source, final SpELContext factory, + final Object parent) { + this(source, new DefaultSpELExpressionEvaluator(source, factory), parent); + } + + public CouchbasePropertyValueProvider(final CouchbaseDocument source, + final DefaultSpELExpressionEvaluator evaluator, final Object parent) { + Assert.notNull(source, "CouchbaseDocument must not be null!"); + Assert.notNull(evaluator, "DefaultSpELExpressionEvaluator must not be null!"); + + this.source = source; + this.evaluator = evaluator; + this.parent = parent; + } + + @Override + @SuppressWarnings("unchecked") + public R getPropertyValue(final CouchbasePersistentProperty property) { + String expression = property.getSpelExpression(); + Object value = expression != null ? evaluator.evaluate(expression) : source.get(property.getFieldName()); + + if (property.isIdProperty()) { + return (R) source.getId(); + } + if (value == null) { + return null; + } + + return readValue(value, property.getTypeInformation(), parent); + } + } + + /** + * A expression parameter value provider. + */ + private class ConverterAwareSpELExpressionParameterValueProvider + extends SpELExpressionParameterValueProvider { + + private final Object parent; + + public ConverterAwareSpELExpressionParameterValueProvider(final SpELExpressionEvaluator evaluator, + final ConversionService conversionService, final ParameterValueProvider delegate, + final Object parent) { + super(evaluator, conversionService, delegate); + this.parent = parent; + } + + @Override + protected T potentiallyConvertSpelValue(final Object object, + final Parameter parameter) { + return readValue(object, parameter.getType(), parent); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/join/N1qlJoinResolver.java b/src/main/java/org/springframework/data/couchbase/core/convert/join/N1qlJoinResolver.java index 999dace3..fdecb7af 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/join/N1qlJoinResolver.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/join/N1qlJoinResolver.java @@ -22,7 +22,6 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.List; -import com.couchbase.client.java.query.N1qlQuery; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.CouchbaseTemplate; @@ -33,134 +32,137 @@ import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; /** - * N1qlJoinResolver resolves by converting the join definition to query statement - * and executing using CouchbaseTemplate + * N1qlJoinResolver resolves by converting the join definition to query statement and executing using CouchbaseTemplate * * @author Subhashni Balakrishnan */ public class N1qlJoinResolver { - private static final Logger LOGGER = LoggerFactory.getLogger(N1qlJoinResolver.class); + private static final Logger LOGGER = LoggerFactory.getLogger(N1qlJoinResolver.class); - public static String buildQuery(CouchbaseTemplate template, N1qlJoinResolverParameters parameters) { - String joinType = "JOIN"; - String selectEntity = "SELECT META(rks).id AS " + SELECT_ID + - ", META(rks).cas AS " + SELECT_CAS + ", (rks).* "; + public static String buildQuery(CouchbaseTemplate template, N1qlJoinResolverParameters parameters) { + String joinType = "JOIN"; + String selectEntity = "SELECT META(rks).id AS " + SELECT_ID + ", META(rks).cas AS " + SELECT_CAS + ", (rks).* "; - StringBuilder useLKSBuilder = new StringBuilder(); - if (parameters.getJoinDefinition().index().length() > 0) { - useLKSBuilder.append("INDEX(" + parameters.getJoinDefinition().index() + ")"); - } - String useLKS = useLKSBuilder.length() > 0 ? "USE " + useLKSBuilder.toString() + " " : ""; + StringBuilder useLKSBuilder = new StringBuilder(); + if (parameters.getJoinDefinition().index().length() > 0) { + useLKSBuilder.append("INDEX(" + parameters.getJoinDefinition().index() + ")"); + } + String useLKS = useLKSBuilder.length() > 0 ? "USE " + useLKSBuilder.toString() + " " : ""; - String from = "FROM `" + template.getCouchbaseBucket().name() + "` lks " + useLKS + joinType + " " + template.getCouchbaseBucket().name() + " rks"; - String onLks = "lks." + template.getConverter().getTypeKey() + " = \""+ parameters.getEntityTypeInfo().getType().getName() + "\""; - String onRks = "rks." + template.getConverter().getTypeKey() + " = \"" + parameters.getAssociatedEntityTypeInfo().getType().getName() + "\""; + String from = "FROM `" + template.getBucketName() + "` lks " + useLKS + joinType + " " + template.getBucketName() + + " rks"; + String onLks = "lks." + template.getConverter().getTypeKey() + " = \"" + + parameters.getEntityTypeInfo().getType().getName() + "\""; + String onRks = "rks." + template.getConverter().getTypeKey() + " = \"" + + parameters.getAssociatedEntityTypeInfo().getType().getName() + "\""; + StringBuilder useRKSBuilder = new StringBuilder(); + if (parameters.getJoinDefinition().rightIndex().length() > 0) { + useRKSBuilder.append("INDEX(" + parameters.getJoinDefinition().rightIndex() + ")"); + } + if (!parameters.getJoinDefinition().hashside().equals(HashSide.NONE)) { + if (useRKSBuilder.length() > 0) + useRKSBuilder.append(" "); + useRKSBuilder.append("HASH(" + parameters.getJoinDefinition().hashside().getValue() + ")"); + } + if (parameters.getJoinDefinition().keys().length > 0) { + if (useRKSBuilder.length() > 0) + useRKSBuilder.append(" "); + useRKSBuilder.append("KEYS ["); + String[] keys = parameters.getJoinDefinition().keys(); - StringBuilder useRKSBuilder = new StringBuilder(); - if (parameters.getJoinDefinition().rightIndex().length() > 0) { - useRKSBuilder.append("INDEX(" + parameters.getJoinDefinition().rightIndex() + ")"); - } - if (!parameters.getJoinDefinition().hashside().equals(HashSide.NONE)) { - if (useRKSBuilder.length() > 0) useRKSBuilder.append(" "); - useRKSBuilder.append("HASH(" + parameters.getJoinDefinition().hashside().getValue() +")"); - } - if (parameters.getJoinDefinition().keys().length > 0) { - if (useRKSBuilder.length() > 0) useRKSBuilder.append(" "); - useRKSBuilder.append("KEYS ["); - String[] keys = parameters.getJoinDefinition().keys(); + for (int i = 0; i < keys.length; i++) { + if (i != 0) + useRKSBuilder.append(","); + useRKSBuilder.append("\"" + keys[i] + "\""); + } + useRKSBuilder.append("]"); + } - for(int i=0; i < keys.length;i++) { - if(i != 0) useRKSBuilder.append(","); - useRKSBuilder.append("\"" + keys[i] +"\""); - } - useRKSBuilder.append("]"); - } + String on = "ON " + parameters.getJoinDefinition().on().concat(" AND " + onLks).concat(" AND " + onRks); - String on = "ON " + parameters.getJoinDefinition().on().concat(" AND " + onLks).concat(" AND " + onRks); + String where = "WHERE META(lks).id=\"" + parameters.getLksId() + "\""; + where += ((parameters.getJoinDefinition().where().length() > 0) ? " AND " + parameters.getJoinDefinition().where() + : ""); - String where = "WHERE META(lks).id=\"" + parameters.getLksId() + "\""; - where += ((parameters.getJoinDefinition().where().length() > 0) ? " AND " + parameters.getJoinDefinition().where() : ""); + StringBuilder statementSb = new StringBuilder(); + statementSb.append(selectEntity); + statementSb.append(" " + from); + statementSb.append((useRKSBuilder.length() > 0 ? " USE " + useRKSBuilder.toString() : "")); + statementSb.append(" " + on); + statementSb.append(" " + where); + return statementSb.toString(); + } - StringBuilder statementSb = new StringBuilder(); - statementSb.append(selectEntity); - statementSb.append(" " + from); - statementSb.append((useRKSBuilder.length() > 0? " USE "+ useRKSBuilder.toString() : "")); - statementSb.append(" " + on); - statementSb.append(" " + where); - return statementSb.toString(); - } + public static List doResolve(CouchbaseTemplate template, N1qlJoinResolverParameters parameters, + Class associatedEntityClass) { + throw new UnsupportedOperationException(); + /* + String statement = buildQuery(template, parameters); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Join query executed " + statement); + } + + N1QLQuery query = new N1QLQuery(N1QLExpression.x(statement), QueryOptions.queryOptions()); + return template.findByN1QL(query, associatedEntityClass);*/ + } - public static List doResolve(CouchbaseTemplate template, - N1qlJoinResolverParameters parameters, - Class associatedEntityClass) { - String statement = buildQuery(template, parameters); + public static boolean isLazyJoin(N1qlJoin joinDefinition) { + return joinDefinition.fetchType().equals(FetchType.LAZY); + } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Join query executed " + statement); - } + static public class N1qlJoinProxy implements InvocationHandler { + private final CouchbaseTemplate template; + private final N1qlJoinResolverParameters params; + private List resolved = null; - N1qlQuery query = N1qlQuery.simple(statement); - return template.findByN1QL(query, associatedEntityClass); - } + public N1qlJoinProxy(CouchbaseTemplate template, N1qlJoinResolverParameters params) { + this.template = template; + this.params = params; + } - public static boolean isLazyJoin(N1qlJoin joinDefinition) { - return joinDefinition.fetchType().equals(FetchType.LAZY); - } + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (this.resolved == null) { + this.resolved = doResolve(this.template, this.params, this.params.associatedEntityTypeInfo.getType()); + } + return method.invoke(this.resolved, args); + } + } - static public class N1qlJoinProxy implements InvocationHandler { - private final CouchbaseTemplate template; - private final N1qlJoinResolverParameters params; - private List resolved = null; + static public class N1qlJoinResolverParameters { + private N1qlJoin joinDefinition; + private String lksId; + private TypeInformation entityTypeInfo; + private TypeInformation associatedEntityTypeInfo; - public N1qlJoinProxy(CouchbaseTemplate template, N1qlJoinResolverParameters params) { - this.template = template; - this.params = params; - } + public N1qlJoinResolverParameters(N1qlJoin joinDefinition, String lksId, TypeInformation entityTypeInfo, + TypeInformation associatedEntityTypeInfo) { + Assert.notNull(joinDefinition, "The join definition is required"); + Assert.notNull(entityTypeInfo, "The entity type information is required"); + Assert.notNull(associatedEntityTypeInfo, "The associated entity type information is required"); - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if(this.resolved == null) { - this.resolved = doResolve(this.template, this.params, this.params.associatedEntityTypeInfo.getType()); - } - return method.invoke(this.resolved, args); - } - } + this.joinDefinition = joinDefinition; + this.lksId = lksId; + this.entityTypeInfo = entityTypeInfo; + this.associatedEntityTypeInfo = associatedEntityTypeInfo; + } - static public class N1qlJoinResolverParameters { - private N1qlJoin joinDefinition; - private String lksId; - private TypeInformation entityTypeInfo; - private TypeInformation associatedEntityTypeInfo; + public N1qlJoin getJoinDefinition() { + return joinDefinition; + } - public N1qlJoinResolverParameters(N1qlJoin joinDefinition, - String lksId, - TypeInformation entityTypeInfo, - TypeInformation associatedEntityTypeInfo) { - Assert.notNull(joinDefinition, "The join definition is required"); - Assert.notNull(entityTypeInfo, "The entity type information is required"); - Assert.notNull(associatedEntityTypeInfo, "The associated entity type information is required"); + public String getLksId() { + return lksId; + } - this.joinDefinition = joinDefinition; - this.lksId = lksId; - this.entityTypeInfo = entityTypeInfo; - this.associatedEntityTypeInfo = associatedEntityTypeInfo; - } + public TypeInformation getEntityTypeInfo() { + return entityTypeInfo; + } - public N1qlJoin getJoinDefinition() { - return joinDefinition; - } - - public String getLksId() { - return lksId; - } - - public TypeInformation getEntityTypeInfo() { - return entityTypeInfo; - } - - public TypeInformation getAssociatedEntityTypeInfo() { - return associatedEntityTypeInfo; - } - } + public TypeInformation getAssociatedEntityTypeInfo() { + return associatedEntityTypeInfo; + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/package-info.java b/src/main/java/org/springframework/data/couchbase/core/convert/package-info.java index fdc2aeea..531f23db 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/package-info.java @@ -1,5 +1,4 @@ /** - * This package contains classes used for entity-to-JSON conversions, type mapping - * and writing. + * This package contains classes used for entity-to-JSON conversions, type mapping and writing. */ package org.springframework.data.couchbase.core.convert; diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationService.java b/src/main/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationService.java index 647e9875..9ccfb03c 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationService.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationService.java @@ -21,12 +21,6 @@ import java.io.StringWriter; import java.io.Writer; import java.util.Map; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.beans.factory.InitializingBean; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.mapping.CouchbaseList; @@ -34,6 +28,13 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseStorable; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.model.SimpleTypeHolder; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * A Jackson JSON Translator that implements the {@link TranslationService} contract. * @@ -44,218 +45,208 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; */ public class JacksonTranslationService implements TranslationService, InitializingBean { - /** - * Jackson Object Mapper; - */ - private ObjectMapper objectMapper; + /** + * Jackson Object Mapper; + */ + private ObjectMapper objectMapper; - /** - * Type holder to help easily identify simple types. - */ - private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT; + /** + * Type holder to help easily identify simple types. + */ + private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT; - /** - * JSON factory for Jackson. - */ - private JsonFactory factory = new JsonFactory(); + /** + * JSON factory for Jackson. + */ + private JsonFactory factory = new JsonFactory(); - /** - * Encode a {@link CouchbaseStorable} to a JSON string. - * - * @param source the source document to encode. - * @return the encoded JSON String. - */ - @Override - public final String encode(final CouchbaseStorable source) { - Writer writer = new StringWriter(); + /** + * Encode a {@link CouchbaseStorable} to a JSON string. + * + * @param source the source document to encode. + * @return the encoded JSON String. + */ + @Override + public final String encode(final CouchbaseStorable source) { + Writer writer = new StringWriter(); - try { - JsonGenerator generator = factory.createGenerator(writer); - encodeRecursive(source, generator); - generator.close(); - writer.close(); - } - catch (IOException ex) { - throw new RuntimeException("Could not encode JSON", ex); - } + try { + JsonGenerator generator = factory.createGenerator(writer); + encodeRecursive(source, generator); + generator.close(); + writer.close(); + } catch (IOException ex) { + throw new RuntimeException("Could not encode JSON", ex); + } - return writer.toString(); - } + return writer.toString(); + } - /** - * Recursively iterates through the sources and adds it to the JSON generator. - * - * @param source the source document - * @param generator the JSON generator. - * @throws IOException - */ - private void encodeRecursive(final CouchbaseStorable source, final JsonGenerator generator) throws IOException { - generator.writeStartObject(); + /** + * Recursively iterates through the sources and adds it to the JSON generator. + * + * @param source the source document + * @param generator the JSON generator. + * @throws IOException + */ + private void encodeRecursive(final CouchbaseStorable source, final JsonGenerator generator) throws IOException { + generator.writeStartObject(); - for (Map.Entry entry : ((CouchbaseDocument) source).export().entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - generator.writeFieldName(key); - if (value instanceof CouchbaseDocument) { - encodeRecursive((CouchbaseDocument) value, generator); - continue; - } + for (Map.Entry entry : ((CouchbaseDocument) source).export().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + generator.writeFieldName(key); + if (value instanceof CouchbaseDocument) { + encodeRecursive((CouchbaseDocument) value, generator); + continue; + } - final Class clazz = value.getClass(); + final Class clazz = value.getClass(); - if (simpleTypeHolder.isSimpleType(clazz) && !isEnumOrClass(clazz)) { - generator.writeObject(value); - } - else { - objectMapper.writeValue(generator, value); - } + if (simpleTypeHolder.isSimpleType(clazz) && !isEnumOrClass(clazz)) { + generator.writeObject(value); + } else { + objectMapper.writeValue(generator, value); + } - } + } - generator.writeEndObject(); - } + generator.writeEndObject(); + } - private boolean isEnumOrClass(final Class clazz) { - return Enum.class.isAssignableFrom(clazz) || Class.class.isAssignableFrom(clazz); - } + private boolean isEnumOrClass(final Class clazz) { + return Enum.class.isAssignableFrom(clazz) || Class.class.isAssignableFrom(clazz); + } - /** - * Decode a JSON string into the {@link CouchbaseStorable} structure. - * - * @param source the source formatted document. - * @param target the target of the populated data. - * @return the decoded structure. - */ - @Override - public final CouchbaseStorable decode(final String source, final CouchbaseStorable target) { - try { - JsonParser parser = factory.createParser((String) source); - while (parser.nextToken() != null) { - JsonToken currentToken = parser.getCurrentToken(); + /** + * Decode a JSON string into the {@link CouchbaseStorable} structure. + * + * @param source the source formatted document. + * @param target the target of the populated data. + * @return the decoded structure. + */ + @Override + public final CouchbaseStorable decode(final String source, final CouchbaseStorable target) { + try { + JsonParser parser = factory.createParser((String) source); + while (parser.nextToken() != null) { + JsonToken currentToken = parser.getCurrentToken(); - if (currentToken == JsonToken.START_OBJECT) { - return decodeObject(parser, (CouchbaseDocument) target); - } - else if (currentToken == JsonToken.START_ARRAY) { - return decodeArray(parser, new CouchbaseList()); - } - else { - throw new MappingException("JSON to decode needs to start as array or object!"); - } - } - parser.close(); - } - catch (IOException ex) { - throw new RuntimeException("Could not decode JSON", ex); - } - return target; - } + if (currentToken == JsonToken.START_OBJECT) { + return decodeObject(parser, (CouchbaseDocument) target); + } else if (currentToken == JsonToken.START_ARRAY) { + return decodeArray(parser, new CouchbaseList()); + } else { + throw new MappingException("JSON to decode needs to start as array or object!"); + } + } + parser.close(); + } catch (IOException ex) { + throw new RuntimeException("Could not decode JSON", ex); + } + return target; + } - /** - * Helper method to decode an object recursively. - * - * @param parser the JSON parser with the content. - * @param target the target where the content should be stored. - * @throws IOException - * @returns the decoded object. - */ - private CouchbaseDocument decodeObject(final JsonParser parser, final CouchbaseDocument target) throws IOException { - JsonToken currentToken = parser.nextToken(); + /** + * Helper method to decode an object recursively. + * + * @param parser the JSON parser with the content. + * @param target the target where the content should be stored. + * @throws IOException + * @returns the decoded object. + */ + private CouchbaseDocument decodeObject(final JsonParser parser, final CouchbaseDocument target) throws IOException { + JsonToken currentToken = parser.nextToken(); - String fieldName = ""; - while (currentToken != null && currentToken != JsonToken.END_OBJECT) { - if (currentToken == JsonToken.START_OBJECT) { - target.put(fieldName, decodeObject(parser, new CouchbaseDocument())); - } - else if (currentToken == JsonToken.START_ARRAY) { - target.put(fieldName, decodeArray(parser, new CouchbaseList())); - } - else if (currentToken == JsonToken.FIELD_NAME) { - fieldName = parser.getCurrentName(); - } - else { - target.put(fieldName, decodePrimitive(currentToken, parser)); - } + String fieldName = ""; + while (currentToken != null && currentToken != JsonToken.END_OBJECT) { + if (currentToken == JsonToken.START_OBJECT) { + target.put(fieldName, decodeObject(parser, new CouchbaseDocument())); + } else if (currentToken == JsonToken.START_ARRAY) { + target.put(fieldName, decodeArray(parser, new CouchbaseList())); + } else if (currentToken == JsonToken.FIELD_NAME) { + fieldName = parser.getCurrentName(); + } else { + target.put(fieldName, decodePrimitive(currentToken, parser)); + } - currentToken = parser.nextToken(); - } + currentToken = parser.nextToken(); + } - return target; - } + return target; + } - /** - * Helper method to decode an array recusrively. - * - * @param parser the JSON parser with the content. - * @param target the target where the content should be stored. - * @throws IOException - * @returns the decoded list. - */ - private CouchbaseList decodeArray(final JsonParser parser, final CouchbaseList target) throws IOException { - JsonToken currentToken = parser.nextToken(); + /** + * Helper method to decode an array recusrively. + * + * @param parser the JSON parser with the content. + * @param target the target where the content should be stored. + * @throws IOException + * @returns the decoded list. + */ + private CouchbaseList decodeArray(final JsonParser parser, final CouchbaseList target) throws IOException { + JsonToken currentToken = parser.nextToken(); - while (currentToken != null && currentToken != JsonToken.END_ARRAY) { - if (currentToken == JsonToken.START_OBJECT) { - target.put(decodeObject(parser, new CouchbaseDocument())); - } - else if (currentToken == JsonToken.START_ARRAY) { - target.put(decodeArray(parser, new CouchbaseList())); - } - else { - target.put(decodePrimitive(currentToken, parser)); - } + while (currentToken != null && currentToken != JsonToken.END_ARRAY) { + if (currentToken == JsonToken.START_OBJECT) { + target.put(decodeObject(parser, new CouchbaseDocument())); + } else if (currentToken == JsonToken.START_ARRAY) { + target.put(decodeArray(parser, new CouchbaseList())); + } else { + target.put(decodePrimitive(currentToken, parser)); + } - currentToken = parser.nextToken(); - } + currentToken = parser.nextToken(); + } - return target; - } + return target; + } - /** - * Helper method to decode and assign a primitive. - * - * @param token the type of token. - * @param parser the parser with the content. - * @return the decoded primitve. - * @throws IOException - */ - private Object decodePrimitive(final JsonToken token, final JsonParser parser) throws IOException { - switch (token) { - case VALUE_TRUE: - case VALUE_FALSE: - return parser.getBooleanValue(); - case VALUE_STRING: - return parser.getValueAsString(); - case VALUE_NUMBER_INT: - return parser.getNumberValue(); - case VALUE_NUMBER_FLOAT: - return parser.getDoubleValue(); - case VALUE_NULL: - return null; - default: - throw new MappingException("Could not decode primitive value " + token); - } - } + /** + * Helper method to decode and assign a primitive. + * + * @param token the type of token. + * @param parser the parser with the content. + * @return the decoded primitve. + * @throws IOException + */ + private Object decodePrimitive(final JsonToken token, final JsonParser parser) throws IOException { + switch (token) { + case VALUE_TRUE: + case VALUE_FALSE: + return parser.getBooleanValue(); + case VALUE_STRING: + return parser.getValueAsString(); + case VALUE_NUMBER_INT: + return parser.getNumberValue(); + case VALUE_NUMBER_FLOAT: + return parser.getDoubleValue(); + case VALUE_NULL: + return null; + default: + throw new MappingException("Could not decode primitive value " + token); + } + } - @Override - public T decodeFragment(String source, Class target) { - try { - return objectMapper.readValue(source, target); - } - catch (IOException e) { - throw new RuntimeException("Cannot decode ad-hoc JSON", e); - } - } + @Override + public T decodeFragment(String source, Class target) { + try { + return objectMapper.readValue(source, target); + } catch (IOException e) { + throw new RuntimeException("Cannot decode ad-hoc JSON", e); + } + } - public void setObjectMapper(final ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } + public void setObjectMapper(final ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } - @Override - public void afterPropertiesSet() { - if (objectMapper == null) { - objectMapper = new ObjectMapper(); - } - } + @Override + public void afterPropertiesSet() { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + } + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/translation/TranslationService.java b/src/main/java/org/springframework/data/couchbase/core/convert/translation/TranslationService.java index c29e53c7..768e7ac6 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/translation/TranslationService.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/translation/TranslationService.java @@ -16,8 +16,6 @@ package org.springframework.data.couchbase.core.convert.translation; -import com.couchbase.client.java.query.N1qlQueryRow; - import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.data.couchbase.core.mapping.CouchbaseStorable; @@ -28,30 +26,30 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseStorable; */ public interface TranslationService { - /** - * Encodes a JSON String into the target format. - * - * @param source the source contents to encode. - * @return the encoded document representation. - */ - String encode(CouchbaseStorable source); + /** + * Encodes a JSON String into the target format. + * + * @param source the source contents to encode. + * @return the encoded document representation. + */ + String encode(CouchbaseStorable source); - /** - * Decodes the target format into a {@link CouchbaseDocument} - * - * @param source the source formatted document. - * @param target the target of the populated data. - * @return a properly populated document to work with. - */ - CouchbaseStorable decode(String source, CouchbaseStorable target); + /** + * Decodes the target format into a {@link CouchbaseDocument} + * + * @param source the source formatted document. + * @param target the target of the populated data. + * @return a properly populated document to work with. + */ + CouchbaseStorable decode(String source, CouchbaseStorable target); - /** - * Decodes an ad-hoc JSON object into a corresponding "case" class. - * - * @param source the JSON for the ad-hoc JSON object (from a N1QL {@link N1qlQueryRow} for instance). - * @param target the target class information. - * @param the target class. - * @return an ad-hoc instance of the decoded JSON into the corresponding "case" class. - */ - T decodeFragment(String source, Class target); + /** + * Decodes an ad-hoc JSON object into a corresponding "case" class. + * + * @param source the JSON for the ad-hoc JSON object (from a N1QL query for instance). + * @param target the target class information. + * @param the target class. + * @return an ad-hoc instance of the decoded JSON into the corresponding "case" class. + */ + T decodeFragment(String source, Class target); } diff --git a/src/main/java/org/springframework/data/couchbase/core/convert/translation/package-info.java b/src/main/java/org/springframework/data/couchbase/core/convert/translation/package-info.java index 9c9f7d02..3ac28f0e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/convert/translation/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/core/convert/translation/package-info.java @@ -1,5 +1,5 @@ /** - * This package contains a service interface to translate entities to - * a Couchbase storable format, and its implementations. + * This package contains a service interface to translate entities to a Couchbase storable format, and its + * implementations. */ package org.springframework.data.couchbase.core.convert.translation; diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntity.java b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntity.java index 7e88ac8e..78e4f3a6 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntity.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntity.java @@ -16,18 +16,18 @@ package org.springframework.data.couchbase.core.mapping; -import com.couchbase.client.java.repository.annotation.Id; +import java.util.Calendar; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; +import org.springframework.data.annotation.Id; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import java.util.Calendar; -import java.util.TimeZone; -import java.util.concurrent.TimeUnit; - /** * The representation of a persistent entity. * @@ -35,111 +35,113 @@ import java.util.concurrent.TimeUnit; * @author Mark Paluch */ public class BasicCouchbasePersistentEntity extends BasicPersistentEntity - implements CouchbasePersistentEntity, EnvironmentAware { + implements CouchbasePersistentEntity, EnvironmentAware { - private Environment environment; + private Environment environment; - /** - * Create a new entity. - * - * @param typeInformation the type information of the entity. - */ - public BasicCouchbasePersistentEntity(final TypeInformation typeInformation) { - super(typeInformation); - validateExpirationConfiguration(); - } + /** + * Create a new entity. + * + * @param typeInformation the type information of the entity. + */ + public BasicCouchbasePersistentEntity(final TypeInformation typeInformation) { + super(typeInformation); + validateExpirationConfiguration(); + } - private void validateExpirationConfiguration() { - Document annotation = getType().getAnnotation(Document.class); - if (annotation != null && annotation.expiry() > 0 && StringUtils.hasLength(annotation.expiryExpression())) { - String msg = String.format("Incorrect expiry configuration on class %s using %s. " + - "You cannot use 'expiry' and 'expiryExpression' at the same time", getType().getName(), annotation); - throw new IllegalArgumentException(msg); - } - } + private void validateExpirationConfiguration() { + Document annotation = getType().getAnnotation(Document.class); + if (annotation != null && annotation.expiry() > 0 && StringUtils.hasLength(annotation.expiryExpression())) { + String msg = String.format("Incorrect expiry configuration on class %s using %s. " + + "You cannot use 'expiry' and 'expiryExpression' at the same time", getType().getName(), annotation); + throw new IllegalArgumentException(msg); + } + } - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } - // DATACOUCH-145: allows SDK's @Id annotation to be used - @Override - protected CouchbasePersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull(CouchbasePersistentProperty property) { - if (!property.isIdProperty()) { - return null; - } + // DATACOUCH-145: allows SDK's @Id annotation to be used + @Override + protected CouchbasePersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull( + CouchbasePersistentProperty property) { + if (!property.isIdProperty()) { + return null; + } - if (!this.hasIdProperty()) { - return property; - } + if (!this.hasIdProperty()) { + return property; + } - //check existing ID vs new candidate - boolean currentCbId = this.getIdProperty().isAnnotationPresent(Id.class); - boolean currentSpringId = this.getIdProperty().isAnnotationPresent(org.springframework.data.annotation.Id.class); - boolean candidateCbId = property.isAnnotationPresent(Id.class); - boolean candidateSpringId = property.isAnnotationPresent(org.springframework.data.annotation.Id.class); + // check existing ID vs new candidate + boolean currentCbId = this.getIdProperty().isAnnotationPresent(Id.class); + boolean currentSpringId = this.getIdProperty().isAnnotationPresent(org.springframework.data.annotation.Id.class); + boolean candidateCbId = property.isAnnotationPresent(Id.class); + boolean candidateSpringId = property.isAnnotationPresent(org.springframework.data.annotation.Id.class); - if (currentCbId && candidateSpringId) { - //spring IDs will have priority over SDK IDs - return property; - } else if (currentSpringId && candidateCbId) { - //ignore SDK's IDs if current is a Spring ID - return null; - } - /* any of the following will throw: - - current is a spring ID and the candidate bears another spring ID - - current is a SDK ID and the candidate bears another SDK ID - - any other combination involving something else than a SDK or Spring ID - */ - return super.returnPropertyIfBetterIdPropertyCandidateOrNull(property); - } + if (currentCbId && candidateSpringId) { + // spring IDs will have priority over SDK IDs + return property; + } else if (currentSpringId && candidateCbId) { + // ignore SDK's IDs if current is a Spring ID + return null; + } + /* any of the following will throw: + - current is a spring ID and the candidate bears another spring ID + - current is a SDK ID and the candidate bears another SDK ID + - any other combination involving something else than a SDK or Spring ID + */ + return super.returnPropertyIfBetterIdPropertyCandidateOrNull(property); + } - @Override - public int getExpiry() { - Document annotation = getType().getAnnotation(Document.class); - if (annotation == null) - return 0; + @Override + public int getExpiry() { + Document annotation = getType().getAnnotation(Document.class); + if (annotation == null) + return 0; - int expiryValue = getExpiryValue(annotation); + int expiryValue = getExpiryValue(annotation); - long secondsShift = annotation.expiryUnit().toSeconds(expiryValue); - if (secondsShift > TTL_IN_SECONDS_INCLUSIVE_END) { - //we want it to be represented as a UNIX timestamp style, seconds since Epoch in UTC - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - if (annotation.expiryUnit() == TimeUnit.DAYS) { - //makes sure we won't lose resolution - cal.add(Calendar.DAY_OF_MONTH, expiryValue); - } else { - //use the shift in seconds since resolution should be smaller - cal.add(Calendar.SECOND, (int) secondsShift); - } - return (int) (cal.getTimeInMillis() / 1000); //note: Unix UTC time representation in int is okay until year 2038 - } else { - return (int) secondsShift; - } - } + long secondsShift = annotation.expiryUnit().toSeconds(expiryValue); + if (secondsShift > TTL_IN_SECONDS_INCLUSIVE_END) { + // we want it to be represented as a UNIX timestamp style, seconds since Epoch in UTC + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + if (annotation.expiryUnit() == TimeUnit.DAYS) { + // makes sure we won't lose resolution + cal.add(Calendar.DAY_OF_MONTH, expiryValue); + } else { + // use the shift in seconds since resolution should be smaller + cal.add(Calendar.SECOND, (int) secondsShift); + } + return (int) (cal.getTimeInMillis() / 1000); // note: Unix UTC time representation in int is okay until year 2038 + } else { + return (int) secondsShift; + } + } - private int getExpiryValue(Document annotation) { - int expiryValue = annotation.expiry(); - String expiryExpressionString = annotation.expiryExpression(); - if (StringUtils.hasLength(expiryExpressionString)) { - Assert.notNull(environment, "Environment must be set to use 'expiryExpression'"); - String expiryWithReplacedPlaceholders = environment.resolveRequiredPlaceholders(expiryExpressionString); - try { - expiryValue = Integer.parseInt(expiryWithReplacedPlaceholders); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid Integer value for expiry expression: " + expiryWithReplacedPlaceholders); - } - } - return expiryValue; - } + private int getExpiryValue(Document annotation) { + int expiryValue = annotation.expiry(); + String expiryExpressionString = annotation.expiryExpression(); + if (StringUtils.hasLength(expiryExpressionString)) { + Assert.notNull(environment, "Environment must be set to use 'expiryExpression'"); + String expiryWithReplacedPlaceholders = environment.resolveRequiredPlaceholders(expiryExpressionString); + try { + expiryValue = Integer.parseInt(expiryWithReplacedPlaceholders); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid Integer value for expiry expression: " + expiryWithReplacedPlaceholders); + } + } + return expiryValue; + } - @Override - public boolean isTouchOnRead() { - org.springframework.data.couchbase.core.mapping.Document annotation = getType().getAnnotation( - org.springframework.data.couchbase.core.mapping.Document.class); - return annotation == null ? false : annotation.touchOnRead() && getExpiry() > 0; - } + @Override + public boolean isTouchOnRead() { + org.springframework.data.couchbase.core.mapping.Document annotation = getType() + .getAnnotation(org.springframework.data.couchbase.core.mapping.Document.class); + return annotation == null ? false : annotation.touchOnRead() && getExpiry() > 0; + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java index 5998ce3a..73cb0c55 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java @@ -16,8 +16,7 @@ package org.springframework.data.couchbase.core.mapping; -import com.couchbase.client.java.repository.annotation.Field; -import com.couchbase.client.java.repository.annotation.Id; +import org.springframework.data.annotation.Id; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; @@ -27,74 +26,72 @@ import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.util.StringUtils; -import java.util.Optional; +import com.couchbase.client.core.deps.com.fasterxml.jackson.annotation.JsonProperty; /** - * Implements annotated property representations of a given {@link com.couchbase.client.java.repository.annotation.Field} instance. + * Implements annotated property representations of a given {@link Field} instance. *

    - *

    This object is used to gather information out of properties on objects that need to be persisted. For example, it - * supports overriding of the actual property name by providing custom annotations.

    + *

    + * This object is used to gather information out of properties on objects that need to be persisted. For example, it + * supports overriding of the actual property name by providing custom annotations. + *

    * * @author Michael Nitschinger * @author Mark Paluch */ -public class BasicCouchbasePersistentProperty - extends AnnotationBasedPersistentProperty - implements CouchbasePersistentProperty { +public class BasicCouchbasePersistentProperty extends AnnotationBasedPersistentProperty + implements CouchbasePersistentProperty { - private final FieldNamingStrategy fieldNamingStrategy; + private final FieldNamingStrategy fieldNamingStrategy; - /** - * Create a new instance of the BasicCouchbasePersistentProperty class. - * - * @param property the PropertyDescriptor. - * @param owner the original owner of the property. - * @param simpleTypeHolder the type holder. - */ - public BasicCouchbasePersistentProperty(Property property, - final CouchbasePersistentEntity owner, final SimpleTypeHolder simpleTypeHolder, - final FieldNamingStrategy fieldNamingStrategy) { - super(property, owner, simpleTypeHolder); - this.fieldNamingStrategy = fieldNamingStrategy == null ? PropertyNameFieldNamingStrategy.INSTANCE - : fieldNamingStrategy; - } + /** + * Create a new instance of the BasicCouchbasePersistentProperty class. + * + * @param property the PropertyDescriptor. + * @param owner the original owner of the property. + * @param simpleTypeHolder the type holder. + */ + public BasicCouchbasePersistentProperty(Property property, final CouchbasePersistentEntity owner, + final SimpleTypeHolder simpleTypeHolder, final FieldNamingStrategy fieldNamingStrategy) { + super(property, owner, simpleTypeHolder); + this.fieldNamingStrategy = fieldNamingStrategy == null ? PropertyNameFieldNamingStrategy.INSTANCE + : fieldNamingStrategy; + } - /** - * Creates a new Association. - */ - @Override - protected Association createAssociation() { - return new Association(this, null); - } + /** + * Creates a new Association. + */ + @Override + protected Association createAssociation() { + return new Association(this, null); + } - /** - * Returns the field name of the property. - *

    - * The field name can be different from the actual property name by using a - * custom annotation. - */ - @Override - public String getFieldName() { - com.couchbase.client.java.repository.annotation.Field annotation = getField(). - getAnnotation(com.couchbase.client.java.repository.annotation.Field.class); + /** + * Returns the field name of the property. + *

    + * The field name can be different from the actual property name by using a custom annotation. + */ + @Override + public String getFieldName() { + JsonProperty annotation = getField().getAnnotation(JsonProperty.class); - if (annotation != null && StringUtils.hasText(annotation.value())) { - return annotation.value(); - } + if (annotation != null && StringUtils.hasText(annotation.value())) { + return annotation.value(); + } - String fieldName = fieldNamingStrategy.getFieldName(this); + String fieldName = fieldNamingStrategy.getFieldName(this); - if (!StringUtils.hasText(fieldName)) { - throw new MappingException(String.format("Invalid (null or empty) field name returned for property %s by %s!", - this, fieldNamingStrategy.getClass())); - } + if (!StringUtils.hasText(fieldName)) { + throw new MappingException(String.format("Invalid (null or empty) field name returned for property %s by %s!", + this, fieldNamingStrategy.getClass())); + } - return fieldName; - } + return fieldName; + } - // DATACOUCH-145: allows SDK's @Id annotation to be used - @Override - public boolean isIdProperty() { - return isAnnotationPresent(Id.class) || super.isIdProperty(); - } + // DATACOUCH-145: allows SDK's @Id annotation to be used + @Override + public boolean isIdProperty() { + return isAnnotationPresent(Id.class) || super.isIdProperty(); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java index f5466de8..b4a39c58 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java @@ -22,258 +22,255 @@ import java.util.Map; /** * A {@link CouchbaseDocument} is an abstract representation of a document stored inside Couchbase Server. *

    - *

    It acts like a {@link HashMap}, but only allows those types to be written that are supported by the underlying + *

    + * It acts like a {@link HashMap}, but only allows those types to be written that are supported by the underlying * storage format, which is currently JSON. Note that JSON conversion is not happening here, but performed at a - * different stage based on the payload stored in the {@link CouchbaseDocument}.

    + * different stage based on the payload stored in the {@link CouchbaseDocument}. + *

    *

    - *

    In addition to the actual content, meta data is also stored. This especially refers to the document ID and its + *

    + * In addition to the actual content, meta data is also stored. This especially refers to the document ID and its * expiration time. Note that this information is not mandatory, since documents can be nested and therefore only the - * topmost document most likely has an ID.

    + * topmost document most likely has an ID. + *

    * * @author Michael Nitschinger */ public class CouchbaseDocument implements CouchbaseStorable { - /** - * Defnes the default expiration time for the document. - */ - public static final int DEFAULT_EXPIRATION_TIME = 0; + /** + * Defnes the default expiration time for the document. + */ + public static final int DEFAULT_EXPIRATION_TIME = 0; - /** - * Contains the actual data to be stored. - */ - private HashMap payload; + /** + * Contains the actual data to be stored. + */ + private HashMap payload; - /** - * Represents the document ID used to identify the document in the bucket. - */ - private String id; + /** + * Represents the document ID used to identify the document in the bucket. + */ + private String id; - /** - * Contains the expiration time of the document. - */ - private int expiration; + /** + * Contains the expiration time of the document. + */ + private int expiration; - /** - * Creates a completely empty {@link CouchbaseDocument}. - */ - public CouchbaseDocument() { - this(null); - } + /** + * Creates a completely empty {@link CouchbaseDocument}. + */ + public CouchbaseDocument() { + this(null); + } - /** - * Creates a empty {@link CouchbaseDocument}, and set the ID immediately. - * - * @param id the document ID. - */ - public CouchbaseDocument(final String id) { - this(id, DEFAULT_EXPIRATION_TIME); - } + /** + * Creates a empty {@link CouchbaseDocument}, and set the ID immediately. + * + * @param id the document ID. + */ + public CouchbaseDocument(final String id) { + this(id, DEFAULT_EXPIRATION_TIME); + } - /** - * Creates a empty {@link CouchbaseDocument} with ID and expiration time. - * - * @param id the document ID. - * @param expiration the expiration time of the document. - */ - public CouchbaseDocument(final String id, final int expiration) { - this.id = id; - this.expiration = expiration; - payload = new HashMap(); - } + /** + * Creates a empty {@link CouchbaseDocument} with ID and expiration time. + * + * @param id the document ID. + * @param expiration the expiration time of the document. + */ + public CouchbaseDocument(final String id, final int expiration) { + this.id = id; + this.expiration = expiration; + payload = new HashMap(); + } - /** - * Store a value with the given key for later retreival. - * - * @param key the key of the attribute. - * @param value the actual content to be stored. - * @return the {@link CouchbaseDocument} for chaining. - */ - public final CouchbaseDocument put(final String key, final Object value) { - verifyValueType(value); + /** + * Store a value with the given key for later retreival. + * + * @param key the key of the attribute. + * @param value the actual content to be stored. + * @return the {@link CouchbaseDocument} for chaining. + */ + public final CouchbaseDocument put(final String key, final Object value) { + verifyValueType(value); - payload.put(key, value); - return this; - } + payload.put(key, value); + return this; + } - /** - * Potentially get a value from the payload with the given key. - * - * @param key the key of the attribute. - * @return the value to which the specified key is mapped, or - * null if does not contain a mapping for the key. - */ - public final Object get(final String key) { - return payload.get(key); - } + /** + * Potentially get a value from the payload with the given key. + * + * @param key the key of the attribute. + * @return the value to which the specified key is mapped, or null if does not contain a mapping for the key. + */ + public final Object get(final String key) { + return payload.get(key); + } - /** - * Returns the current payload, including all recursive elements. - *

    - * It either returns the raw results or makes sure that the recusrive elements - * are also exported properly. - * - * @return - */ - public final HashMap export() { - HashMap toExport = new HashMap(payload); - for (Map.Entry entry : payload.entrySet()) { - if (entry.getValue() instanceof CouchbaseDocument) { - toExport.put(entry.getKey(), ((CouchbaseDocument) entry.getValue()).export()); - } - else if (entry.getValue() instanceof CouchbaseList) { - toExport.put(entry.getKey(), ((CouchbaseList) entry.getValue()).export()); - } - } - return toExport; - } + /** + * Returns the current payload, including all recursive elements. + *

    + * It either returns the raw results or makes sure that the recusrive elements are also exported properly. + * + * @return + */ + public final HashMap export() { + HashMap toExport = new HashMap(payload); + for (Map.Entry entry : payload.entrySet()) { + if (entry.getValue() instanceof CouchbaseDocument) { + toExport.put(entry.getKey(), ((CouchbaseDocument) entry.getValue()).export()); + } else if (entry.getValue() instanceof CouchbaseList) { + toExport.put(entry.getKey(), ((CouchbaseList) entry.getValue()).export()); + } + } + return toExport; + } - /** - * Returns true if it contains a payload for the specified key. - * - * @param key the key of the attribute. - * @return true if it contains a payload for the specified key. - */ - public final boolean containsKey(final String key) { - return payload.containsKey(key); - } + /** + * Returns true if it contains a payload for the specified key. + * + * @param key the key of the attribute. + * @return true if it contains a payload for the specified key. + */ + public final boolean containsKey(final String key) { + return payload.containsKey(key); + } - /** - * Returns true if it contains the given value. - * - * @param value the value to check for. - * @return true if it contains the specified value. - */ - public final boolean containsValue(final Object value) { - return payload.containsValue(value); - } + /** + * Returns true if it contains the given value. + * + * @param value the value to check for. + * @return true if it contains the specified value. + */ + public final boolean containsValue(final Object value) { + return payload.containsValue(value); + } - /** - * Returns the size of the attributes in this document (not nested). - * - * @return the size of the attributes in this document (not nested). - */ - public final int size() { - return size(false); - } + /** + * Returns the size of the attributes in this document (not nested). + * + * @return the size of the attributes in this document (not nested). + */ + public final int size() { + return size(false); + } - /** - * Retruns the size of the attributes in this and recursive documents. - * - * @param recursive wheter nested attributes should be taken into account. - * @return the size of the attributes in this and recursive documents. - */ - public final int size(final boolean recursive) { - int thisSize = payload.size(); + /** + * Retruns the size of the attributes in this and recursive documents. + * + * @param recursive wheter nested attributes should be taken into account. + * @return the size of the attributes in this and recursive documents. + */ + public final int size(final boolean recursive) { + int thisSize = payload.size(); - if (!recursive || thisSize == 0) { - return thisSize; - } + if (!recursive || thisSize == 0) { + return thisSize; + } - int totalSize = thisSize; - for (Object value : payload.values()) { - if (value instanceof CouchbaseDocument) { - totalSize += ((CouchbaseDocument) value).size(true); - } - else if (value instanceof CouchbaseList) { - totalSize += ((CouchbaseList) value).size(true); - } - } + int totalSize = thisSize; + for (Object value : payload.values()) { + if (value instanceof CouchbaseDocument) { + totalSize += ((CouchbaseDocument) value).size(true); + } else if (value instanceof CouchbaseList) { + totalSize += ((CouchbaseList) value).size(true); + } + } - return totalSize; - } + return totalSize; + } - /** - * Returns the underlying payload. - *

    - *

    Note that unlike {@link #export()}, the nested objects are not converted, so the "raw" map is returned.

    - * - * @return the underlying payload. - */ - public HashMap getPayload() { - return payload; - } + /** + * Returns the underlying payload. + *

    + *

    + * Note that unlike {@link #export()}, the nested objects are not converted, so the "raw" map is returned. + *

    + * + * @return the underlying payload. + */ + public HashMap getPayload() { + return payload; + } - /** - * Returns the expiration time of the document. - *

    - * If the expiration time is 0, then the document will be persisted until - * deleted manually ("forever"). - * - * @return the expiration time of the document. - */ - public int getExpiration() { - return expiration; - } + /** + * Returns the expiration time of the document. + *

    + * If the expiration time is 0, then the document will be persisted until deleted manually ("forever"). + * + * @return the expiration time of the document. + */ + public int getExpiration() { + return expiration; + } - /** - * Set the expiration time of the document. - *

    - * If the expiration time is 0, then the document will be persisted until - * deleted manually ("forever"). - *

    - * Expiration should be expressed as seconds if <= 30 days (30 x 24 x 60 x 60 seconds), - * or as an expiry date (UTC, UNIX time ie. seconds form the Epoch) if > 30 days. - * - * @param expiration - * @return the {@link CouchbaseDocument} for chaining. - */ - public CouchbaseDocument setExpiration(int expiration) { - this.expiration = expiration; - return this; - } + /** + * Set the expiration time of the document. + *

    + * If the expiration time is 0, then the document will be persisted until deleted manually ("forever"). + *

    + * Expiration should be expressed as seconds if <= 30 days (30 x 24 x 60 x 60 seconds), or as an expiry date (UTC, + * UNIX time ie. seconds form the Epoch) if > 30 days. + * + * @param expiration + * @return the {@link CouchbaseDocument} for chaining. + */ + public CouchbaseDocument setExpiration(int expiration) { + this.expiration = expiration; + return this; + } - /** - * Returns the ID of the document. - * - * @return the ID of the document. - */ - public String getId() { - return id; - } + /** + * Returns the ID of the document. + * + * @return the ID of the document. + */ + public String getId() { + return id; + } - /** - * Sets the unique ID of the document per bucket. - * - * @param id the ID of the document. - * @return the {@link CouchbaseDocument} for chaining. - */ - public CouchbaseDocument setId(String id) { - this.id = id; - return this; - } + /** + * Sets the unique ID of the document per bucket. + * + * @param id the ID of the document. + * @return the {@link CouchbaseDocument} for chaining. + */ + public CouchbaseDocument setId(String id) { + this.id = id; + return this; + } - /** - * Verifies that only values of a certain and supported type - * can be stored. - *

    - *

    If this is not the case, a {@link IllegalArgumentException} is - * thrown.

    - * - * @param value the object to verify its type. - */ - private void verifyValueType(final Object value) { - if (value == null) { - return; - } - final Class clazz = value.getClass(); - if (CouchbaseSimpleTypes.DOCUMENT_TYPES.isSimpleType(clazz)) { - return; - } - throw new IllegalArgumentException("Attribute of type " + clazz.getCanonicalName() + " cannot be stored and must be converted."); - } + /** + * Verifies that only values of a certain and supported type can be stored. + *

    + *

    + * If this is not the case, a {@link IllegalArgumentException} is thrown. + *

    + * + * @param value the object to verify its type. + */ + private void verifyValueType(final Object value) { + if (value == null) { + return; + } + final Class clazz = value.getClass(); + if (CouchbaseSimpleTypes.DOCUMENT_TYPES.isSimpleType(clazz)) { + return; + } + throw new IllegalArgumentException( + "Attribute of type " + clazz.getCanonicalName() + " cannot be stored and must be converted."); + } - /** - * A string representation of expiration, id and payload. - * - * @return the string representation of the object. - */ - @Override - public String toString() { - return "CouchbaseDocument{" + - "id=" + id + - ", exp=" + expiration + - ", payload=" + payload + - '}'; - } + /** + * A string representation of expiration, id and payload. + * + * @return the string representation of the object. + */ + @Override + public String toString() { + return "CouchbaseDocument{" + "id=" + id + ", exp=" + expiration + ", payload=" + payload + '}'; + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseList.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseList.java index 768ef419..4a1b90e9 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseList.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseList.java @@ -26,199 +26,196 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; /** * A {@link CouchbaseList} is an abstract list that represents an array stored in a (most of the times JSON) document. *

    - *

    This {@link CouchbaseList} is part of the potentially nested structure inside one or more - * {@link CouchbaseDocument}s. It can also contain them recursively, depending on how the document is modeled.

    + *

    + * This {@link CouchbaseList} is part of the potentially nested structure inside one or more {@link CouchbaseDocument}s. + * It can also contain them recursively, depending on how the document is modeled. + *

    */ public class CouchbaseList implements CouchbaseStorable { - /** - * Contains the actual data to be stored. - */ - private List payload; + /** + * Contains the actual data to be stored. + */ + private List payload; - /** - * Holds types considered simple and allowed to be stored. - */ - private SimpleTypeHolder simpleTypeHolder; + /** + * Holds types considered simple and allowed to be stored. + */ + private SimpleTypeHolder simpleTypeHolder; - /** - * Create a new (empty) list. - */ - public CouchbaseList() { - this(new ArrayList()); - } + /** + * Create a new (empty) list. + */ + public CouchbaseList() { + this(new ArrayList()); + } - /** - * Create a new list with a given payload on construction. - * - * @param initialPayload the initial data to store. - */ - public CouchbaseList(final List initialPayload) { - this(initialPayload, null); - } + /** + * Create a new list with a given payload on construction. + * + * @param initialPayload the initial data to store. + */ + public CouchbaseList(final List initialPayload) { + this(initialPayload, null); + } - /** - * Create a new (empty) list with an existing {@link SimpleTypeHolder}. - * - * @param simpleTypeHolder context instance. - */ - public CouchbaseList(final SimpleTypeHolder simpleTypeHolder) { - this(new ArrayList(), simpleTypeHolder); - } + /** + * Create a new (empty) list with an existing {@link SimpleTypeHolder}. + * + * @param simpleTypeHolder context instance. + */ + public CouchbaseList(final SimpleTypeHolder simpleTypeHolder) { + this(new ArrayList(), simpleTypeHolder); + } - /** - * Create a new list with a given payload on construction and an existing {@link SimpleTypeHolder}. - * - * @param initialPayload the initial data to store. - * @param simpleTypeHolder context instance. - */ - public CouchbaseList(final List initialPayload, final SimpleTypeHolder simpleTypeHolder) { - this.payload = initialPayload; - if (simpleTypeHolder != null) { - Set> additionalTypes = new HashSet>(); - additionalTypes.add(CouchbaseDocument.class); - additionalTypes.add(CouchbaseList.class); - this.simpleTypeHolder = new SimpleTypeHolder(additionalTypes, simpleTypeHolder); - } else { - this.simpleTypeHolder = CouchbaseSimpleTypes.DOCUMENT_TYPES; - } - } + /** + * Create a new list with a given payload on construction and an existing {@link SimpleTypeHolder}. + * + * @param initialPayload the initial data to store. + * @param simpleTypeHolder context instance. + */ + public CouchbaseList(final List initialPayload, final SimpleTypeHolder simpleTypeHolder) { + this.payload = initialPayload; + if (simpleTypeHolder != null) { + Set> additionalTypes = new HashSet>(); + additionalTypes.add(CouchbaseDocument.class); + additionalTypes.add(CouchbaseList.class); + this.simpleTypeHolder = new SimpleTypeHolder(additionalTypes, simpleTypeHolder); + } else { + this.simpleTypeHolder = CouchbaseSimpleTypes.DOCUMENT_TYPES; + } + } - /** - * Add content to the underlying list. - * - * @param value the value to be added. - * @return the {@link CouchbaseList} object for chaining purposes. - */ - public final CouchbaseList put(final Object value) { - verifyValueType(value); + /** + * Add content to the underlying list. + * + * @param value the value to be added. + * @return the {@link CouchbaseList} object for chaining purposes. + */ + public final CouchbaseList put(final Object value) { + verifyValueType(value); - payload.add(value); - return this; - } + payload.add(value); + return this; + } - /** - * Return the stored element at the given index. - * - * @param index the index where the document is located. - * @return the found object (or null if nothing found). - */ - public final Object get(final int index) { - return payload.get(index); - } + /** + * Return the stored element at the given index. + * + * @param index the index where the document is located. + * @return the found object (or null if nothing found). + */ + public final Object get(final int index) { + return payload.get(index); + } - /** - * Returns the size of the attributes in this document (not nested). - * - * @return the size of the attributes in this document (not nested). - */ - public final int size() { - return size(false); - } + /** + * Returns the size of the attributes in this document (not nested). + * + * @return the size of the attributes in this document (not nested). + */ + public final int size() { + return size(false); + } - /** - * Retruns the size of the attributes in this and recursive documents. - * - * @param recursive wheter nested attributes should be taken into account. - * @return the size of the attributes in this and recursive documents. - */ - public final int size(final boolean recursive) { - int thisSize = payload.size(); + /** + * Retruns the size of the attributes in this and recursive documents. + * + * @param recursive wheter nested attributes should be taken into account. + * @return the size of the attributes in this and recursive documents. + */ + public final int size(final boolean recursive) { + int thisSize = payload.size(); - if (!recursive || thisSize == 0) { - return thisSize; - } + if (!recursive || thisSize == 0) { + return thisSize; + } - int totalSize = thisSize; - for (Object value : payload) { - if (value instanceof CouchbaseDocument) { - totalSize += ((CouchbaseDocument) value).size(true); - } - else if (value instanceof CouchbaseList) { - totalSize += ((CouchbaseList) value).size(true); - } - } + int totalSize = thisSize; + for (Object value : payload) { + if (value instanceof CouchbaseDocument) { + totalSize += ((CouchbaseDocument) value).size(true); + } else if (value instanceof CouchbaseList) { + totalSize += ((CouchbaseList) value).size(true); + } + } - return totalSize; - } + return totalSize; + } - /** - * Returns the current payload, including all recursive elements. - *

    - * It either returns the raw results or makes sure that the recusrive elements - * are also exported properly. - * - * @return - */ - public final List export() { - List toExport = new ArrayList(payload); + /** + * Returns the current payload, including all recursive elements. + *

    + * It either returns the raw results or makes sure that the recusrive elements are also exported properly. + * + * @return + */ + public final List export() { + List toExport = new ArrayList(payload); - int elem = 0; - for (Object entry : payload) { - if (entry instanceof CouchbaseDocument) { - toExport.remove(elem); - toExport.add(elem, ((CouchbaseDocument) entry).export()); - } - else if (entry instanceof CouchbaseList) { - toExport.remove(elem); - toExport.add(elem, ((CouchbaseList) entry).export()); - } - elem++; - } - return toExport; - } + int elem = 0; + for (Object entry : payload) { + if (entry instanceof CouchbaseDocument) { + toExport.remove(elem); + toExport.add(elem, ((CouchbaseDocument) entry).export()); + } else if (entry instanceof CouchbaseList) { + toExport.remove(elem); + toExport.add(elem, ((CouchbaseList) entry).export()); + } + elem++; + } + return toExport; + } - /** - * Returns true if it contains the given value. - * - * @param value the value to check for. - * @return true if it contains the specified value. - */ - public final boolean containsValue(final Object value) { - return payload.contains(value); - } + /** + * Returns true if it contains the given value. + * + * @param value the value to check for. + * @return true if it contains the specified value. + */ + public final boolean containsValue(final Object value) { + return payload.contains(value); + } - /** - * Checks if the underlying payload is empty or not. - * - * @return whether the underlying payload is empty or not. - */ - public final boolean isEmpty() { - return payload.isEmpty(); - } + /** + * Checks if the underlying payload is empty or not. + * + * @return whether the underlying payload is empty or not. + */ + public final boolean isEmpty() { + return payload.isEmpty(); + } - /** - * Verifies that only values of a certain and supported type - * can be stored. - *

    - *

    If this is not the case, a {@link IllegalArgumentException} is - * thrown.

    - * - * @param value the object to verify its type. - */ - private void verifyValueType(final Object value) { - if (value == null) { - return; - } + /** + * Verifies that only values of a certain and supported type can be stored. + *

    + *

    + * If this is not the case, a {@link IllegalArgumentException} is thrown. + *

    + * + * @param value the object to verify its type. + */ + private void verifyValueType(final Object value) { + if (value == null) { + return; + } - final Class clazz = value.getClass(); - if (simpleTypeHolder.isSimpleType(clazz)) { - return; - } + final Class clazz = value.getClass(); + if (simpleTypeHolder.isSimpleType(clazz)) { + return; + } - throw new IllegalArgumentException("Attribute of type " - + clazz.getCanonicalName() + "can not be stored and must be converted."); - } + throw new IllegalArgumentException( + "Attribute of type " + clazz.getCanonicalName() + "can not be stored and must be converted."); + } - /** - * A string reprensation of the payload. - * - * @return the underlying payload as a string representation for easier debugging. - */ - @Override - public String toString() { - return "CouchbaseList{" + - "payload=" + payload + - '}'; - } + /** + * A string reprensation of the payload. + * + * @return the underlying payload as a string representation for easier debugging. + */ + @Override + public String toString() { + return "CouchbaseList{" + "payload=" + payload + '}'; + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseMappingContext.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseMappingContext.java index 6477773a..800cbb50 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseMappingContext.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseMappingContext.java @@ -33,74 +33,72 @@ import org.springframework.data.util.TypeInformation; * @author Michael Nitschinger */ public class CouchbaseMappingContext - extends AbstractMappingContext, CouchbasePersistentProperty> - implements ApplicationContextAware { + extends AbstractMappingContext, CouchbasePersistentProperty> + implements ApplicationContextAware { - /** - * Contains the application context to configure the application. - */ - private ApplicationContext context; + /** + * The default field naming strategy. + */ + private static final FieldNamingStrategy DEFAULT_NAMING_STRATEGY = PropertyNameFieldNamingStrategy.INSTANCE; + /** + * Contains the application context to configure the application. + */ + private ApplicationContext context; + /** + * The field naming strategy to use. + */ + private FieldNamingStrategy fieldNamingStrategy = DEFAULT_NAMING_STRATEGY; - /** - * The default field naming strategy. - */ - private static final FieldNamingStrategy DEFAULT_NAMING_STRATEGY = PropertyNameFieldNamingStrategy.INSTANCE; + /** + * Configures the {@link FieldNamingStrategy} to be used to determine the field name if no manual mapping is applied. + * Defaults to a strategy using the plain property name. + * + * @param fieldNamingStrategy the {@link FieldNamingStrategy} to be used to determine the field name if no manual + * mapping is applied. + */ + public void setFieldNamingStrategy(final FieldNamingStrategy fieldNamingStrategy) { + this.fieldNamingStrategy = fieldNamingStrategy == null ? DEFAULT_NAMING_STRATEGY : fieldNamingStrategy; + } - /** - * The field naming strategy to use. - */ - private FieldNamingStrategy fieldNamingStrategy = DEFAULT_NAMING_STRATEGY; + /** + * Creates a concrete entity based out of the type information passed. + * + * @param typeInformation type information of the entity to create. + * @param the type for the corresponding type information. + * @return the constructed entity. + */ + @Override + protected BasicCouchbasePersistentEntity createPersistentEntity(final TypeInformation typeInformation) { + BasicCouchbasePersistentEntity entity = new BasicCouchbasePersistentEntity(typeInformation); + if (context != null) { + entity.setEnvironment(context.getEnvironment()); + } + return entity; + } - /** - * Configures the {@link FieldNamingStrategy} to be used to determine the field name if no manual mapping is applied. - * Defaults to a strategy using the plain property name. - * - * @param fieldNamingStrategy the {@link FieldNamingStrategy} to be used to determine the field name if no manual - * mapping is applied. - */ - public void setFieldNamingStrategy(final FieldNamingStrategy fieldNamingStrategy) { - this.fieldNamingStrategy = fieldNamingStrategy == null ? DEFAULT_NAMING_STRATEGY : fieldNamingStrategy; - } + /** + * Creates a concrete property based on the field information and entity. + * + * @param property the property descriptor. + * @param owner the entity which owns the property. + * @param simpleTypeHolder the type holder. + * @return the constructed property. + */ + @Override + protected CouchbasePersistentProperty createPersistentProperty(Property property, + final BasicCouchbasePersistentEntity owner, final SimpleTypeHolder simpleTypeHolder) { + return new BasicCouchbasePersistentProperty(property, owner, simpleTypeHolder, fieldNamingStrategy); + } - /** - * Creates a concrete entity based out of the type information passed. - * - * @param typeInformation type information of the entity to create. - * @param the type for the corresponding type information. - * @return the constructed entity. - */ - @Override - protected BasicCouchbasePersistentEntity createPersistentEntity(final TypeInformation typeInformation) { - BasicCouchbasePersistentEntity entity = new BasicCouchbasePersistentEntity(typeInformation); - if (context != null) { - entity.setEnvironment(context.getEnvironment()); - } - return entity; - } - - /** - * Creates a concrete property based on the field information and entity. - * - * @param property the property descriptor. - * @param owner the entity which owns the property. - * @param simpleTypeHolder the type holder. - * @return the constructed property. - */ - @Override - protected CouchbasePersistentProperty createPersistentProperty(Property property, - final BasicCouchbasePersistentEntity owner, final SimpleTypeHolder simpleTypeHolder) { - return new BasicCouchbasePersistentProperty(property, owner, simpleTypeHolder, fieldNamingStrategy); - } - - /** - * Sets (or overrides) the current application context. - * - * @param applicationContext the application context to be assigned. - * @throws BeansException if the context can not be set properly. - */ - @Override - public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { - context = applicationContext; - } + /** + * Sets (or overrides) the current application context. + * + * @param applicationContext the application context to be assigned. + * @throws BeansException if the context can not be set properly. + */ + @Override + public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java index 1fb2fe18..2088fb41 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentEntity.java @@ -23,31 +23,29 @@ import org.springframework.data.mapping.PersistentEntity; * * @author Michael Nitschinger */ -public interface CouchbasePersistentEntity extends - PersistentEntity { +public interface CouchbasePersistentEntity extends PersistentEntity { - /** - * The threshold (inclusive) after which expiry should be expressed as a Unix UTC time. - */ - long TTL_IN_SECONDS_INCLUSIVE_END = 30 * 24 * 60 * 60; + /** + * The threshold (inclusive) after which expiry should be expressed as a Unix UTC time. + */ + long TTL_IN_SECONDS_INCLUSIVE_END = 30 * 24 * 60 * 60; - /** - * Returns the expiration time of the entity. - *

    - * The Couchbase format for expiration time is: - * - for TTL < 31 days (<= 30 * 24 * 60 * 60): expressed as a TTL in seconds - * - for TTL > 30 days: expressed as Unix UTC time of expiry (number of SECONDS since the Epoch) - * - * @return the expiration time in correct Couchbase format. - */ - int getExpiry(); + /** + * Returns the expiration time of the entity. + *

    + * The Couchbase format for expiration time is: - for TTL < 31 days (<= 30 * 24 * 60 * 60): expressed as a TTL in + * seconds - for TTL > 30 days: expressed as Unix UTC time of expiry (number of SECONDS since the Epoch) + * + * @return the expiration time in correct Couchbase format. + */ + int getExpiry(); - /** - * Flag for using getAndTouch operations for reads, resetting the expiration (if one was set) when the - * entity is directly read (eg. findOne, findById). - * - * @return true if a direct read of the document should trigger a touch, resetting its expiration timer. - */ - boolean isTouchOnRead(); + /** + * Flag for using getAndTouch operations for reads, resetting the expiration (if one was set) when the entity is + * directly read (eg. findOne, findById). + * + * @return true if a direct read of the document should trigger a touch, resetting its expiration timer. + */ + boolean isTouchOnRead(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java index 82dbd0bd..e5350b58 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbasePersistentProperty.java @@ -25,10 +25,10 @@ import org.springframework.data.mapping.PersistentProperty; */ public interface CouchbasePersistentProperty extends PersistentProperty { - /** - * Returns the field name of the property. - *

    - * The field name can be different from the actual property name by using a custom annotation. - */ - String getFieldName(); + /** + * Returns the field name of the property. + *

    + * The field name can be different from the actual property name by using a custom annotation. + */ + String getFieldName(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseSimpleTypes.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseSimpleTypes.java index 6f042488..cdd47d28 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseSimpleTypes.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseSimpleTypes.java @@ -16,30 +16,23 @@ package org.springframework.data.couchbase.core.mapping; - -import static java.util.stream.Collectors.toSet; +import static java.util.stream.Collectors.*; import java.util.stream.Stream; -import com.couchbase.client.java.document.RawJsonDocument; -import com.couchbase.client.java.document.json.JsonArray; - import org.springframework.data.mapping.model.SimpleTypeHolder; +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; + public abstract class CouchbaseSimpleTypes { - public static final SimpleTypeHolder JSON_TYPES = new SimpleTypeHolder( - Stream.of(RawJsonDocument.class, - JsonArray.class, - Number.class).collect(toSet()), - true); + public static final SimpleTypeHolder JSON_TYPES = new SimpleTypeHolder( + Stream.of(JsonObject.class, JsonArray.class, Number.class).collect(toSet()), true); - public static final SimpleTypeHolder DOCUMENT_TYPES = new SimpleTypeHolder( - Stream.of(CouchbaseDocument.class, - CouchbaseList.class).collect(toSet()), - true); + public static final SimpleTypeHolder DOCUMENT_TYPES = new SimpleTypeHolder( + Stream.of(CouchbaseDocument.class, CouchbaseList.class).collect(toSet()), true); - private CouchbaseSimpleTypes() { - } + private CouchbaseSimpleTypes() {} } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseStorable.java b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseStorable.java index 2185e42d..965acbbb 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseStorable.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseStorable.java @@ -23,5 +23,4 @@ package org.springframework.data.couchbase.core.mapping; * * @author Michael Nitschinger */ -public interface CouchbaseStorable { -} +public interface CouchbaseStorable {} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/Document.java b/src/main/java/org/springframework/data/couchbase/core/mapping/Document.java index 9982b986..87e93e03 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/Document.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/Document.java @@ -34,36 +34,38 @@ import org.springframework.data.annotation.Persistent; @Persistent @Inherited @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ ElementType.TYPE }) public @interface Document { - /** - * An optional expiry time for the document. Default is no expiry. - * Only one of two might might be set at the same time: either {@link #expiry()} or {@link #expiryExpression()} - */ - int expiry() default 0; + /** + * An optional expiry time for the document. Default is no expiry. Only one of two might might be set at the same + * time: either {@link #expiry()} or {@link #expiryExpression()} + */ + int expiry() default 0; - /** - * Same as {@link #expiry} but allows the actual value to be set using standard Spring property sources mechanism. - * Only one might be set at the same time: either {@link #expiry()} or {@link #expiryExpression()}.
    - * Syntax is the same as for {@link org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)}. - *

    - * The value will be recalculated for every {@link org.springframework.data.couchbase.core.CouchbaseTemplate} save/insert/update call, - * thus allowing actual expiration to reflect changes on-the-fly as soon as property sources change. - *

    - * SpEL is NOT supported. - */ - String expiryExpression() default ""; + /** + * Same as {@link #expiry} but allows the actual value to be set using standard Spring property sources mechanism. + * Only one might be set at the same time: either {@link #expiry()} or {@link #expiryExpression()}.
    + * Syntax is the same as for {@link org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)}. + *
    + *
    + * The value will be recalculated for every {@link org.springframework.data.couchbase.core.CouchbaseTemplate} + * save/insert/update call, thus allowing actual expiration to reflect changes on-the-fly as soon as property sources + * change.
    + *
    + * SpEL is NOT supported. + */ + String expiryExpression() default ""; - /** - * An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}. - */ - TimeUnit expiryUnit() default TimeUnit.SECONDS; + /** + * An optional time unit for the document's {@link #expiry()}, if set. Default is {@link TimeUnit#SECONDS}. + */ + TimeUnit expiryUnit() default TimeUnit.SECONDS; - /** - * An optional flag associated with {@link #expiry()} indicating whether the expiry timer should - * be reset whenever the document is directly read (eg. findByOne, findById). - */ - boolean touchOnRead() default false; + /** + * An optional flag associated with {@link #expiry()} indicating whether the expiry timer should be reset whenever the + * document is directly read (eg. findByOne, findById). + */ + boolean touchOnRead() default false; } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/Field.java b/src/main/java/org/springframework/data/couchbase/core/mapping/Field.java new file mode 100644 index 00000000..8a7327b0 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/Field.java @@ -0,0 +1,45 @@ +package org.springframework.data.couchbase.core.mapping; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to define custom metadata for document fields. + * + * @author Oliver Gierke + * @author Christoph Strobl + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +public @interface Field { + + /** + * The key to be used to store the field inside the document. Alias for {@link #name()}. + * + * @return an empty {@link String} by default. + */ + @AliasFor("name") + String value() default ""; + + /** + * The key to be used to store the field inside the document. Alias for {@link #value()}. + * + * @return an empty {@link String} by default. + */ + @AliasFor("value") + String name() default ""; + + /** + * The order in which various fields shall be stored. Has to be a positive integer. + * + * @return the order the field shall have in the document or -1 if undefined. + */ + int order() default Integer.MAX_VALUE; + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/KeySettings.java b/src/main/java/org/springframework/data/couchbase/core/mapping/KeySettings.java index fc741258..7fb0c2b5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/KeySettings.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/KeySettings.java @@ -17,10 +17,7 @@ package org.springframework.data.couchbase.core.mapping; /** - * Common settings for Couchbase key - * - prefix - * - suffix - * - delimiter + * Common settings for Couchbase key - prefix - suffix - delimiter * * @author Subhashni Balakrishnan */ @@ -34,14 +31,14 @@ public class KeySettings { private String delimiter; - public static KeySettings build() { - return new KeySettings(); - } - protected KeySettings() { this.delimiter = DEFAULT_DELIMITER; } + public static KeySettings build() { + return new KeySettings(); + } + /** * Set common prefix */ diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListener.java index dcf2a4e8..87f8bcd9 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListener.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListener.java @@ -18,7 +18,6 @@ package org.springframework.data.couchbase.core.mapping.event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.context.ApplicationListener; import org.springframework.core.GenericTypeResolver; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; @@ -32,71 +31,68 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; * @author Michael Nitschinger */ public class AbstractCouchbaseEventListener implements ApplicationListener> { - private static final Logger LOG = LoggerFactory.getLogger(AbstractCouchbaseEventListener.class); - private final Class domainClass; + private static final Logger LOG = LoggerFactory.getLogger(AbstractCouchbaseEventListener.class); + private final Class domainClass; - public AbstractCouchbaseEventListener() { - Class typeArgument = GenericTypeResolver.resolveTypeArgument(getClass(), AbstractCouchbaseEventListener.class); - domainClass = typeArgument == null ? Object.class : typeArgument; - } + public AbstractCouchbaseEventListener() { + Class typeArgument = GenericTypeResolver.resolveTypeArgument(getClass(), AbstractCouchbaseEventListener.class); + domainClass = typeArgument == null ? Object.class : typeArgument; + } - @SuppressWarnings("rawtypes") - public void onApplicationEvent(CouchbaseMappingEvent event) { + @SuppressWarnings("rawtypes") + public void onApplicationEvent(CouchbaseMappingEvent event) { - E source = (E) event.getSource(); - // Check for matching domain type and invoke callbacks - if (source != null && !domainClass.isAssignableFrom(source.getClass())) { - return; - } + E source = (E) event.getSource(); + // Check for matching domain type and invoke callbacks + if (source != null && !domainClass.isAssignableFrom(source.getClass())) { + return; + } - if (event instanceof BeforeDeleteEvent) { - onBeforeDelete(event.getSource(), event.getDocument()); - return; - } - else if (event instanceof AfterDeleteEvent) { - onAfterDelete(event.getSource(), event.getDocument()); - return; - } + if (event instanceof BeforeDeleteEvent) { + onBeforeDelete(event.getSource(), event.getDocument()); + return; + } else if (event instanceof AfterDeleteEvent) { + onAfterDelete(event.getSource(), event.getDocument()); + return; + } - if (event instanceof BeforeConvertEvent) { - onBeforeConvert(source); - } - else if (event instanceof BeforeSaveEvent) { - onBeforeSave(source, event.getDocument()); - } - else if (event instanceof AfterSaveEvent) { - onAfterSave(source, event.getDocument()); - } - } + if (event instanceof BeforeConvertEvent) { + onBeforeConvert(source); + } else if (event instanceof BeforeSaveEvent) { + onBeforeSave(source, event.getDocument()); + } else if (event instanceof AfterSaveEvent) { + onAfterSave(source, event.getDocument()); + } + } - public void onBeforeConvert(E source) { - if (LOG.isDebugEnabled()) { - LOG.debug("onBeforeConvert({})", source); - } - } + public void onBeforeConvert(E source) { + if (LOG.isDebugEnabled()) { + LOG.debug("onBeforeConvert({})", source); + } + } - public void onBeforeSave(E source, CouchbaseDocument doc) { - if (LOG.isDebugEnabled()) { - LOG.debug("onBeforeSave({}, {})", source, doc); - } - } + public void onBeforeSave(E source, CouchbaseDocument doc) { + if (LOG.isDebugEnabled()) { + LOG.debug("onBeforeSave({}, {})", source, doc); + } + } - public void onAfterSave(E source, CouchbaseDocument doc) { - if (LOG.isDebugEnabled()) { - LOG.debug("onAfterSave({}, {})", source, doc); - } - } + public void onAfterSave(E source, CouchbaseDocument doc) { + if (LOG.isDebugEnabled()) { + LOG.debug("onAfterSave({}, {})", source, doc); + } + } - public void onAfterDelete(Object source, CouchbaseDocument doc) { - if (LOG.isDebugEnabled()) { - LOG.debug("onAfterConvert({})", doc); - } - } + public void onAfterDelete(Object source, CouchbaseDocument doc) { + if (LOG.isDebugEnabled()) { + LOG.debug("onAfterConvert({})", doc); + } + } - public void onBeforeDelete(Object source, CouchbaseDocument doc) { - if (LOG.isDebugEnabled()) { - LOG.debug("onAfterConvert({})", doc); - } - } + public void onBeforeDelete(Object source, CouchbaseDocument doc) { + if (LOG.isDebugEnabled()) { + LOG.debug("onAfterConvert({})", doc); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterDeleteEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterDeleteEvent.java index aae85908..4ae7be38 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterDeleteEvent.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterDeleteEvent.java @@ -21,8 +21,8 @@ package org.springframework.data.couchbase.core.mapping.event; */ public class AfterDeleteEvent extends CouchbaseMappingEvent { - public AfterDeleteEvent(E source) { - super(source, null); - } + public AfterDeleteEvent(E source) { + super(source, null); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveEvent.java index a9146b5c..2716a1d8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveEvent.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AfterSaveEvent.java @@ -23,8 +23,8 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; */ public class AfterSaveEvent extends CouchbaseMappingEvent { - public AfterSaveEvent(E source, CouchbaseDocument document) { - super(source, document); - } + public AfterSaveEvent(E source, CouchbaseDocument document) { + super(source, document); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEventListener.java index bd03ec8c..33025720 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEventListener.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/AuditingEventListener.java @@ -16,6 +16,8 @@ package org.springframework.data.couchbase.core.mapping.event; +import java.util.Optional; + import org.springframework.beans.factory.ObjectFactory; import org.springframework.context.ApplicationListener; import org.springframework.data.auditing.AuditingHandler; @@ -23,8 +25,6 @@ import org.springframework.data.auditing.IsNewAwareAuditingHandler; import org.springframework.data.mapping.context.MappingContext; import org.springframework.util.Assert; -import java.util.Optional; - /** * Event listener to populate auditing related fields on an entity about to be saved. * @@ -34,26 +34,26 @@ import java.util.Optional; */ public class AuditingEventListener implements ApplicationListener> { - private final ObjectFactory auditingHandlerFactory; + private final ObjectFactory auditingHandlerFactory; - /** - * Creates a new {@link AuditingEventListener} using the given {@link MappingContext} and {@link AuditingHandler} - * provided by the given {@link ObjectFactory}. - * - * @param auditingHandlerFactory must not be {@literal null}. - */ - public AuditingEventListener(ObjectFactory auditingHandlerFactory) { - Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); - this.auditingHandlerFactory = auditingHandlerFactory; - } + /** + * Creates a new {@link AuditingEventListener} using the given {@link MappingContext} and {@link AuditingHandler} + * provided by the given {@link ObjectFactory}. + * + * @param auditingHandlerFactory must not be {@literal null}. + */ + public AuditingEventListener(ObjectFactory auditingHandlerFactory) { + Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!"); + this.auditingHandlerFactory = auditingHandlerFactory; + } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) - */ - public void onApplicationEvent(BeforeConvertEvent event) { + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(BeforeConvertEvent event) { - Optional.ofNullable(event.getSource())// - .ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it)); - } + Optional.ofNullable(event.getSource())// + .ifPresent(it -> auditingHandlerFactory.getObject().markAudited(it)); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertEvent.java index cfb5b468..a5241f98 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertEvent.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeConvertEvent.java @@ -21,8 +21,8 @@ package org.springframework.data.couchbase.core.mapping.event; */ public class BeforeConvertEvent extends CouchbaseMappingEvent { - public BeforeConvertEvent(E source) { - super(source, null); - } + public BeforeConvertEvent(E source) { + super(source, null); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeDeleteEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeDeleteEvent.java index 48902e70..0510d4cb 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeDeleteEvent.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeDeleteEvent.java @@ -21,8 +21,8 @@ package org.springframework.data.couchbase.core.mapping.event; */ public class BeforeDeleteEvent extends CouchbaseMappingEvent { - public BeforeDeleteEvent(E source) { - super(source, null); - } + public BeforeDeleteEvent(E source) { + super(source, null); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveEvent.java index a70b19a9..fa300e90 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveEvent.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/BeforeSaveEvent.java @@ -23,8 +23,8 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; */ public class BeforeSaveEvent extends CouchbaseMappingEvent { - public BeforeSaveEvent(E source, CouchbaseDocument document) { - super(source, document); - } + public BeforeSaveEvent(E source, CouchbaseDocument document) { + super(source, document); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/CouchbaseMappingEvent.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/CouchbaseMappingEvent.java index 5575bfd0..c5f0e3a5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/CouchbaseMappingEvent.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/CouchbaseMappingEvent.java @@ -26,20 +26,20 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; */ public class CouchbaseMappingEvent extends ApplicationEvent { - private final CouchbaseDocument document; + private final CouchbaseDocument document; - public CouchbaseMappingEvent(T source, CouchbaseDocument document) { - super(source); - this.document = document; - } + public CouchbaseMappingEvent(T source, CouchbaseDocument document) { + super(source); + this.document = document; + } - public CouchbaseDocument getDocument() { - return document; - } + public CouchbaseDocument getDocument() { + return document; + } - @SuppressWarnings("unchecked") - @Override - public T getSource() { - return (T) super.getSource(); - } + @SuppressWarnings("unchecked") + @Override + public T getSource() { + return (T) super.getSource(); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/LoggingEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/LoggingEventListener.java index b07dd9ba..af016897 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/LoggingEventListener.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/LoggingEventListener.java @@ -18,7 +18,6 @@ package org.springframework.data.couchbase.core.mapping.event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.context.ApplicationListener; import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; @@ -29,31 +28,31 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; */ public class LoggingEventListener extends AbstractCouchbaseEventListener { - private static final Logger LOGGER = LoggerFactory.getLogger(LoggingEventListener.class); + private static final Logger LOGGER = LoggerFactory.getLogger(LoggingEventListener.class); - @Override - public void onBeforeDelete(Object source, CouchbaseDocument doc) { - LOGGER.info("onBeforeDelete: {}, {}", source, doc); - } + @Override + public void onBeforeDelete(Object source, CouchbaseDocument doc) { + LOGGER.info("onBeforeDelete: {}, {}", source, doc); + } - @Override - public void onAfterDelete(Object source, CouchbaseDocument doc) { - LOGGER.info("onAfterDelete: {} {}", source, doc); - } + @Override + public void onAfterDelete(Object source, CouchbaseDocument doc) { + LOGGER.info("onAfterDelete: {} {}", source, doc); + } - @Override - public void onAfterSave(Object source, CouchbaseDocument doc) { - LOGGER.info("onAfterSave: {}, {}", source, doc); - } + @Override + public void onAfterSave(Object source, CouchbaseDocument doc) { + LOGGER.info("onAfterSave: {}, {}", source, doc); + } - @Override - public void onBeforeSave(Object source, CouchbaseDocument doc) { - LOGGER.info("onBeforeSave: {}, {}", source, doc); - } + @Override + public void onBeforeSave(Object source, CouchbaseDocument doc) { + LOGGER.info("onBeforeSave: {}, {}", source, doc); + } - @Override - public void onBeforeConvert(Object source) { - LOGGER.info("onBeforeConvert: {}, {}", source); - } + @Override + public void onBeforeConvert(Object source) { + LOGGER.info("onBeforeConvert: {}, {}", source); + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java index 2951ee92..7d08f35e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListener.java @@ -23,7 +23,6 @@ import javax.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; import org.springframework.util.Assert; @@ -37,34 +36,34 @@ import org.springframework.util.Assert; */ public class ValidatingCouchbaseEventListener extends AbstractCouchbaseEventListener { - private static final Logger LOG = LoggerFactory.getLogger(ValidatingCouchbaseEventListener.class); + private static final Logger LOG = LoggerFactory.getLogger(ValidatingCouchbaseEventListener.class); - private final Validator validator; + private final Validator validator; - /** - * Creates a new {@link ValidatingCouchbaseEventListener} using the given {@link Validator}. - * - * @param validator must not be {@literal null}. - */ - public ValidatingCouchbaseEventListener(Validator validator) { - Assert.notNull(validator, "Validator must not be null!"); - this.validator = validator; - } + /** + * Creates a new {@link ValidatingCouchbaseEventListener} using the given {@link Validator}. + * + * @param validator must not be {@literal null}. + */ + public ValidatingCouchbaseEventListener(Validator validator) { + Assert.notNull(validator, "Validator must not be null!"); + this.validator = validator; + } - @Override - @SuppressWarnings({"rawtypes", "unchecked"}) - public void onBeforeSave(Object source, CouchbaseDocument dbo) { + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void onBeforeSave(Object source, CouchbaseDocument dbo) { - if (LOG.isDebugEnabled()) { - LOG.debug("Validating object: {}", source); - } - Set violations = validator.validate(source); + if (LOG.isDebugEnabled()) { + LOG.debug("Validating object: {}", source); + } + Set violations = validator.validate(source); - if (!violations.isEmpty()) { + if (!violations.isEmpty()) { - LOG.info("During object: {} validation violations found: {}", source, violations); - throw new ConstraintViolationException(violations); - } - } + LOG.info("During object: {} validation violations found: {}", source, violations); + throw new ConstraintViolationException(violations); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/event/package-info.java b/src/main/java/org/springframework/data/couchbase/core/mapping/event/package-info.java index 9cde06e5..ebeccabc 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/event/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/event/package-info.java @@ -1,5 +1,4 @@ /** - * This package contains various events that are emitted during the lifecycle - * of a Spring Data entity. + * This package contains various events that are emitted during the lifecycle of a Spring Data entity. */ package org.springframework.data.couchbase.core.mapping.event; diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/id/GeneratedValue.java b/src/main/java/org/springframework/data/couchbase/core/mapping/id/GeneratedValue.java index 4aa3bca2..b88fe4c0 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/id/GeneratedValue.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/id/GeneratedValue.java @@ -15,21 +15,23 @@ */ package org.springframework.data.couchbase.core.mapping.id; + import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; + import org.springframework.data.couchbase.core.mapping.Document; /** - * This annotation is targeted at {@link Document} interfaces, indicating that - * the framework should generate the document key value using the specified generator + * This annotation is targeted at {@link Document} interfaces, indicating that the framework should generate the + * document key value using the specified generator * * @author Subhashni Balakrishnan */ -@Target({ElementType.FIELD}) +@Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface GeneratedValue { diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdAttribute.java b/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdAttribute.java index ace24732..82ef13ae 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdAttribute.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdAttribute.java @@ -17,10 +17,10 @@ package org.springframework.data.couchbase.core.mapping.id; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.annotation.Inherited; /** * This annotation is targeted at building the document id using the attribute value. @@ -28,7 +28,7 @@ import java.lang.annotation.Inherited; * @author Subhashni Balakrishnan */ @Inherited -@Target({ElementType.FIELD}) +@Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface IdAttribute { int order() default 0; diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdPrefix.java b/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdPrefix.java index 00bf4ebd..d3ba4d85 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdPrefix.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdPrefix.java @@ -17,20 +17,19 @@ package org.springframework.data.couchbase.core.mapping.id; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.annotation.Inherited; /** - * This annotation is targeted at building the document id using the value - * as a part of a prefix build. The order determines in way which the specified - * prefix value is used in building the prefix. + * This annotation is targeted at building the document id using the value as a part of a prefix build. The order + * determines in way which the specified prefix value is used in building the prefix. * * @author Subhashni Balakrishnan */ @Inherited -@Target({ElementType.FIELD}) +@Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface IdPrefix { int order() default 0; diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdSuffix.java b/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdSuffix.java index 5e8cf576..32b9b3f1 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdSuffix.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/id/IdSuffix.java @@ -16,22 +16,20 @@ package org.springframework.data.couchbase.core.mapping.id; - import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.annotation.Inherited; /** - * This annotation is targeted at building the document id using the value - * as a part of a suffix build. The order determines in way which the specified - * suffix value is used in building the suffix. + * This annotation is targeted at building the document id using the value as a part of a suffix build. The order + * determines in way which the specified suffix value is used in building the suffix. * * @author Subhashni Balakrishnan */ @Inherited -@Target({ElementType.FIELD}) +@Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface IdSuffix { int order() default 0; diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/package-info.java b/src/main/java/org/springframework/data/couchbase/core/mapping/package-info.java index 53fb3512..e5245287 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/package-info.java @@ -1,5 +1,5 @@ /** - * This package contains interfaces and annotations relative to object-json mapping - * and the notion of a Couchbase Storable. + * This package contains interfaces and annotations relative to object-json mapping and the notion of a Couchbase + * Storable. */ package org.springframework.data.couchbase.core.mapping; diff --git a/src/main/java/org/springframework/data/couchbase/core/package-info.java b/src/main/java/org/springframework/data/couchbase/core/package-info.java index 02afcbd4..8628fac7 100644 --- a/src/main/java/org/springframework/data/couchbase/core/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/core/package-info.java @@ -1,8 +1,6 @@ /** - * This package contains the specific implementations and core classes for - * Spring Data Couchbase internals. It also contains Couchbase implementation - * to support the Spring Data template abstraction. - *
    + * This package contains the specific implementations and core classes for Spring Data Couchbase internals. It also + * contains Couchbase implementation to support the Spring Data template abstraction.
    * 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. diff --git a/src/main/java/org/springframework/data/couchbase/core/query/AnalyticsQuery.java b/src/main/java/org/springframework/data/couchbase/core/query/AnalyticsQuery.java new file mode 100644 index 00000000..797c92ab --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/query/AnalyticsQuery.java @@ -0,0 +1,93 @@ +package org.springframework.data.couchbase.core.query; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.util.Assert; + +public class AnalyticsQuery { + + private long skip; + private int limit; + private Sort sort = Sort.unsorted(); + + public AnalyticsQuery() {} + + /** + * Set number of documents to skip before returning results. + * + * @param skip + * @return + */ + public AnalyticsQuery skip(long skip) { + this.skip = skip; + return this; + } + + /** + * Limit the number of returned documents to {@code limit}. + * + * @param limit + * @return + */ + public AnalyticsQuery limit(int limit) { + this.limit = limit; + return this; + } + + /** + * Sets the given pagination information on the {@link AnalyticsQuery} instance. Will transparently set {@code skip} + * and {@code limit} as well as applying the {@link Sort} instance defined with the {@link Pageable}. + * + * @param pageable + * @return + */ + public AnalyticsQuery with(final Pageable pageable) { + if (pageable.isUnpaged()) { + return this; + } + this.limit = pageable.getPageSize(); + this.skip = pageable.getOffset(); + return with(pageable.getSort()); + } + + /** + * Adds a {@link Sort} to the {@link AnalyticsQuery} instance. + * + * @param sort + * @return + */ + public AnalyticsQuery with(final Sort sort) { + Assert.notNull(sort, "Sort must not be null!"); + if (sort.isUnsorted()) { + return this; + } + this.sort = this.sort.and(sort); + return this; + } + + public void appendSkipAndLimit(final StringBuilder sb) { + if (limit > 0) { + sb.append(" LIMIT ").append(limit); + } + if (skip > 0) { + sb.append(" OFFSET ").append(skip); + } + } + + public void appendSort(final StringBuilder sb) { + if (sort.isUnsorted()) { + return; + } + + sb.append(" ORDER BY "); + sort.stream().forEach(order -> { + if (order.isIgnoreCase()) { + throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! " + + "Couchbase N1QL does not support sorting ignoring case currently!", order.getProperty())); + } + sb.append(order.getProperty()).append(" ").append(order.isAscending() ? "ASC," : "DESC,"); + }); + sb.deleteCharAt(sb.length() - 1); + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Consistency.java b/src/main/java/org/springframework/data/couchbase/core/query/Consistency.java index 26d550d4..13b107cb 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Consistency.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Consistency.java @@ -16,57 +16,56 @@ package org.springframework.data.couchbase.core.query; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import com.couchbase.client.java.view.Stale; +import com.couchbase.client.java.query.QueryScanConsistency; +import com.couchbase.client.java.view.ViewScanConsistency; /** - * Enumeration of different consistency configurations to be used by the queries generated by the - * framework. - * - * Each consistency can be translated to a {@link Stale} (for the {@link #viewConsistency() views}) - * and {@link ScanConsistency} (for the {@link #n1qlConsistency() N1QL queries}). + * Enumeration of different consistency configurations to be used by the queries generated by the framework. Each + * consistency can be translated to a {@link ViewScanConsistency} (for the {@link #viewConsistency() views}) and + * {@link QueryScanConsistency} (for the {@link #n1qlConsistency() N1QL queries}). * * @author Simon Baslé */ public enum Consistency { + // TODO: Needs to be reviewed carefully.. - /** READ_YOUR_OWN_WRITES is {@link Stale#FALSE} and {@link ScanConsistency#STATEMENT_PLUS} */ - READ_YOUR_OWN_WRITES(Stale.FALSE, ScanConsistency.STATEMENT_PLUS), - /** STRONGLY_CONSISTENT is {@link Stale#FALSE} and {@link ScanConsistency#REQUEST_PLUS} */ - STRONGLY_CONSISTENT(Stale.FALSE, ScanConsistency.REQUEST_PLUS), - /** UPDATE_AFTER is {@link Stale#UPDATE_AFTER} and {@link ScanConsistency#NOT_BOUNDED} */ - UPDATE_AFTER(Stale.UPDATE_AFTER, ScanConsistency.NOT_BOUNDED), - /** EVENTUALLY_CONSISTENT is {@link Stale#TRUE} and {@link ScanConsistency#NOT_BOUNDED} */ - EVENTUALLY_CONSISTENT(Stale.TRUE, ScanConsistency.NOT_BOUNDED); + /** READ_YOUR_OWN_WRITES is {@link ViewScanConsistency#REQUEST_PLUS} and {@link QueryScanConsistency#REQUEST_PLUS} */ + READ_YOUR_OWN_WRITES(ViewScanConsistency.REQUEST_PLUS, QueryScanConsistency.REQUEST_PLUS), + /** STRONGLY_CONSISTENT is {@link ViewScanConsistency#REQUEST_PLUS} and {@link QueryScanConsistency#REQUEST_PLUS} */ + STRONGLY_CONSISTENT(ViewScanConsistency.REQUEST_PLUS, QueryScanConsistency.REQUEST_PLUS), + /** UPDATE_AFTER is {@link ViewScanConsistency#UPDATE_AFTER} and {@link QueryScanConsistency#NOT_BOUNDED} */ + UPDATE_AFTER(ViewScanConsistency.UPDATE_AFTER, QueryScanConsistency.NOT_BOUNDED), + /** EVENTUALLY_CONSISTENT is {@link ViewScanConsistency#UPDATE_AFTER} and {@link QueryScanConsistency#NOT_BOUNDED} */ + EVENTUALLY_CONSISTENT(ViewScanConsistency.UPDATE_AFTER, QueryScanConsistency.NOT_BOUNDED); - /** - * The static default Consistency ({@link #READ_YOUR_OWN_WRITES}). - */ - public static final Consistency DEFAULT_CONSISTENCY = READ_YOUR_OWN_WRITES; + /** + * The static default Consistency ({@link #READ_YOUR_OWN_WRITES}). + */ + public static final Consistency DEFAULT_CONSISTENCY = READ_YOUR_OWN_WRITES; - private final Stale viewConsistency; - private final ScanConsistency n1qlConsistency; + private final ViewScanConsistency viewConsistency; + private final QueryScanConsistency n1qlConsistency; - Consistency(Stale viewConsistency, ScanConsistency n1qlConsistency) { - this.viewConsistency = viewConsistency; - this.n1qlConsistency = n1qlConsistency; - } + Consistency(ViewScanConsistency viewConsistency, QueryScanConsistency n1qlConsistency) { + this.viewConsistency = viewConsistency; + this.n1qlConsistency = n1qlConsistency; + } - /** - * Returns the {@link Stale view consistency} corresponding to this {@link Consistency}. - * - * @return the view consistency. - */ - public Stale viewConsistency() { - return viewConsistency; - } + /** + * Returns the {@link ViewScanConsistency view consistency} corresponding to this {@link Consistency}. + * + * @return the view consistency. + */ + public ViewScanConsistency viewConsistency() { + return viewConsistency; + } - /** - * Returns the {@link ScanConsistency N1QL consistency} corresponding to this {@link Consistency}. - * - * @return the N1QL consistency. - */ - public ScanConsistency n1qlConsistency() { - return n1qlConsistency; - } + /** + * Returns the {@link QueryScanConsistency consistency} corresponding to this {@link Consistency}. + * + * @return the N1QL consistency. + */ + public QueryScanConsistency n1qlConsistency() { + return n1qlConsistency; + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Dimensional.java b/src/main/java/org/springframework/data/couchbase/core/query/Dimensional.java index eadaac79..58223340 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Dimensional.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Dimensional.java @@ -25,19 +25,21 @@ import java.lang.annotation.Target; import org.springframework.data.annotation.QueryAnnotation; /** - * An annotation to mark a repository method as querying using a Couchbase Spatial View. - * The attributes will be used to resolve the actual Spatial View to be used and to determine - * the expected number of dimensions in the Spatial View keys. It can also be used as meta-annotation. + * An annotation to mark a repository method as querying using a Couchbase Spatial View. The attributes will be used to + * resolve the actual Spatial View to be used and to determine the expected number of dimensions in the Spatial View + * keys. It can also be used as meta-annotation. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @QueryAnnotation public @interface Dimensional { - /** The name of the design document to be queried */ - String designDocument(); - /** The name of the spatial view to be queried */ - String spatialViewName(); - /** The number of dimensions in the spatial view, defaults to 2 */ - int dimensions() default 2; + /** The name of the design document to be queried */ + String designDocument(); + + /** The name of the spatial view to be queried */ + String spatialViewName(); + + /** The number of dimensions in the spatial view, defaults to 2 */ + int dimensions() default 2; } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/FetchType.java b/src/main/java/org/springframework/data/couchbase/core/query/FetchType.java index ca116a7c..ccf3fb2e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/FetchType.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/FetchType.java @@ -18,6 +18,7 @@ package org.springframework.data.couchbase.core.query; /** * Setting for specify when to fetch the associated entities + * * @author Subhashni Balakrishnan */ public enum FetchType { @@ -27,8 +28,7 @@ public enum FetchType { IMMEDIATE, /** - * Lazily fetch the associated entities on access, the - * fetch happens only once + * Lazily fetch the associated entities on access, the fetch happens only once */ LAZY } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/HashSide.java b/src/main/java/org/springframework/data/couchbase/core/query/HashSide.java index 004942a0..91b1685a 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/HashSide.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/HashSide.java @@ -17,9 +17,8 @@ package org.springframework.data.couchbase.core.query; /** - * Hash side to specify hash join. Here based on probe or build, the - * entity will be used to query or build the hash table. - * The smaller data set side should be used to build to fit in memory. + * Hash side to specify hash join. Here based on probe or build, the entity will be used to query or build the hash + * table. The smaller data set side should be used to build to fit in memory. * * @author Subhashni Balakrishnan */ @@ -42,7 +41,7 @@ public enum HashSide { private final String value; HashSide(String value) { - this.value=value; + this.value = value; } public String getValue() { diff --git a/src/main/java/org/springframework/data/couchbase/core/query/N1QLExpression.java b/src/main/java/org/springframework/data/couchbase/core/query/N1QLExpression.java new file mode 100644 index 00000000..b08588ba --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/query/N1QLExpression.java @@ -0,0 +1,450 @@ +package org.springframework.data.couchbase.core.query; + +import java.io.Serializable; +import java.util.Iterator; + +public class N1QLExpression { + private static final N1QLExpression NULL_INSTANCE = new N1QLExpression("NULL"); + private static final N1QLExpression TRUE_INSTANCE = new N1QLExpression("TRUE"); + private static final N1QLExpression FALSE_INSTANCE = new N1QLExpression("FALSE"); + private static final N1QLExpression MISSING_INSTANCE = new N1QLExpression("MISSING"); + private static final N1QLExpression EMPTY_INSTANCE = new N1QLExpression(""); + + private Object value; + + private N1QLExpression(final Object value) { + this.value = value; + } + + /** + * Creates an arbitrary expression from the given string value. No quoting or escaping will be done on the input. In + * addition, it is not checked if the given value is an actual valid (N1QL syntax wise) expression. + * + * @param value the value to create the expression from. + * @return a new {@link N1QLExpression} representing the value. + */ + public static N1QLExpression x(final String value) { + return new N1QLExpression(value); + } + + /** + * An identifier or list of identifiers escaped using backquotes `. Useful for example for identifiers that contains a + * dash like "beer-sample". Multiple identifiers are returned as a list of escaped identifiers separated by ", ". + * + * @param identifiers the identifier(s) to escape. + * @return an {@link N1QLExpression} representing the escaped identifier. + */ + public static N1QLExpression i(final String... identifiers) { + return new N1QLExpression(wrapWith('`', identifiers)); + } + + /** + * An identifier or list of identifiers which will be quoted as strings (with ""). + * + * @param strings the list of strings to quote. + * @return an {@link N1QLExpression} representing the quoted strings. + */ + public static N1QLExpression s(final String... strings) { + return new N1QLExpression(wrapWith('"', strings)); + } + + /** + * Returns an expression representing boolean TRUE. + * + * @return an expression representing TRUE. + */ + public static N1QLExpression TRUE() { + return TRUE_INSTANCE; + } + + /** + * Returns an expression representing boolean FALSE. + * + * @return an expression representing FALSE. + */ + public static N1QLExpression FALSE() { + return FALSE_INSTANCE; + } + + /** + * Returns an expression representing NULL. + * + * @return an expression representing NULL. + */ + public static N1QLExpression NULL() { + return NULL_INSTANCE; + } + + /** + * Returns an expression representing MISSING. + * + * @return an expression representing MISSING. + */ + public static N1QLExpression MISSING() { + return MISSING_INSTANCE; + } + + /** + * Helper method to prefix a string. + * + * @param prefix the prefix. + * @param right the right side of the expression. + * @return a prefixed expression. + */ + private static N1QLExpression prefix(String prefix, String right) { + return new N1QLExpression(prefix + " " + right); + } + + /** + * Helper method to infix a string. + * + * @param infix the infix. + * @param left the left side of the expression. + * @param right the right side of the expression. + * @return a infixed expression. + */ + private static N1QLExpression infix(String infix, String left, String right) { + return new N1QLExpression(left + " " + infix + " " + right); + } + + /** + * Helper method to postfix a string. + * + * @param postfix the postfix. + * @param left the left side of the expression. + * @return a prefixed expression. + */ + private static N1QLExpression postfix(String postfix, String left) { + return new N1QLExpression(left + " " + postfix); + } + + /** + * Construct a path ("a.b.c") from Expressions or values. Strings are considered identifiers (so they won't be + * quoted). + * + * @param pathComponents the elements of the path, joined together by a dot. + * @return the path created from the given components. + */ + public static N1QLExpression path(Object... pathComponents) { + if (pathComponents == null || pathComponents.length == 0) { + return EMPTY_INSTANCE; + } + StringBuilder path = new StringBuilder(); + for (Object p : pathComponents) { + path.append('.'); + if (p instanceof N1QLExpression) { + path.append(((N1QLExpression) p).toString()); + } else { + path.append(String.valueOf(p)); + } + } + path.deleteCharAt(0); + return x(path.toString()); + } + + /** + * @return metadata for the document expression + */ + public static N1QLExpression meta(N1QLExpression expression) { + return x("META(" + expression.toString() + ")"); + } + + /** + * Prepends a SELECT to the given expression + */ + public static N1QLExpression select(N1QLExpression... expressions) { + StringBuilder sb = new StringBuilder(); + sb.append("SELECT "); + + for (int i = 0; i < expressions.length; i++) { + sb.append(expressions[i].toString()); + if (i < expressions.length - 1) { + sb.append(", "); + } + } + return x(sb.toString()); + } + + /** + * Begins a delete statement + */ + public static N1QLExpression delete() { + return x("DELETE"); + } + + /** + * Returned expression results in count of all the non-NULL and non-MISSING values in the group. + */ + public static N1QLExpression count(N1QLExpression expression) { + return x("COUNT(" + expression.toString() + ")"); + } + + /** + * Helper method to wrap varargs with the given character. + * + * @param wrapper the wrapper character. + * @param input the input fields to wrap. + * @return a concatenated string with characters wrapped. + */ + private static String wrapWith(char wrapper, String... input) { + StringBuilder escaped = new StringBuilder(); + for (String i : input) { + escaped.append(", "); + escaped.append(wrapper).append(i).append(wrapper); + } + if (escaped.length() > 2) { + escaped.delete(0, 2); + } + return escaped.toString(); + } + + /** + * AND-combines two expressions. + * + * @param right the expression to combine with the current one. + * @return a combined expression. + */ + public N1QLExpression and(N1QLExpression right) { + return infix("AND", toString(), right.toString()); + } + + /** + * OR-combines two expressions. + * + * @param right the expression to combine with the current one. + * @return a combined expression. + */ + public N1QLExpression or(N1QLExpression right) { + return infix("OR", toString(), right.toString()); + } + + /** + * Adds a AS clause between the current and the given expression. Often used to alias an identifier. + * + * @param alias the right hand side expression. + * @return a new expression with the clause applied. + */ + public N1QLExpression as(N1QLExpression alias) { + return infix("AS", toString(), alias.toString()); + } + + public N1QLExpression from(N1QLExpression bucketName) { + return infix("FROM", toString(), bucketName.toString()); + } + + public N1QLExpression from(String bucketName) { + return from(i(bucketName)); + } + + public N1QLExpression where(N1QLExpression right) { + return infix("WHERE", toString(), right.toString()); + } + + public N1QLExpression returning(N1QLExpression right) { + return infix("RETURNING", toString(), right.toString()); + } + + public N1QLExpression keys(Iterable ids) { + StringBuilder sb = new StringBuilder(); + Iterator it = ids.iterator(); + // TODO: really? Lets do better. + while (it.hasNext()) { + sb.append(i(it.next().toString())); + if (it.hasNext()) { + sb.append(","); + } + } + return infix("USE KEYS", toString(), sb.toString()); + } + + /** + * Returned expression results in the given expression in lowercase. + */ + public N1QLExpression lower() { + return x("LOWER(" + toString() + ")"); + } + + /** + * Returned expression will be converted to a string + */ + + public N1QLExpression convertToString() { + return x("TOSTRING(" + toString() + ")"); + } + + /** + * Combines two expressions with the equals operator ("="). + * + * @param right the expression to combine. + * @return the combined expressions. + */ + public N1QLExpression eq(N1QLExpression right) { + return infix("=", toString(), right.toString()); + } + + public N1QLExpression eq(final boolean value) { + return value ? eq(TRUE_INSTANCE) : eq(FALSE_INSTANCE); + } + + public N1QLExpression asc() { + return postfix("ASC", toString()); + } + + public N1QLExpression desc() { + return postfix("DESC", toString()); + } + + public N1QLExpression limit(int limit) { + return infix("LIMIT", toString(), String.valueOf(limit)); + } + + public N1QLExpression offset(int offset) { + return infix("OFFSET", toString(), String.valueOf(offset)); + } + + /** + * Adds a BETWEEN clause between the current and the given expression. + * + * @param right the right hand side expression. + * @return a new expression with the clause applied. + */ + public N1QLExpression between(N1QLExpression right) { + return infix("BETWEEN", toString(), right.toString()); + } + + /** + * Combines two expressions with the greater than operator (">"). + * + * @param right the expression to combine. + * @return the combined expressions. + */ + public N1QLExpression gt(N1QLExpression right) { + return infix(">", toString(), right.toString()); + } + + /** + * Appends a "IS NULL" to the expression. + * + * @return the postfixed expression. + */ + public N1QLExpression isNull() { + return postfix("IS NULL", toString()); + } + + /** + * Appends a "IS NOT NULL" to the expression. + * + * @return the postfixed expression. + */ + public N1QLExpression isNotNull() { + return postfix("IS NOT NULL", toString()); + } + + /** + * Combines two expressions with the not equals operator ("!="). + * + * @param right the expression to combine. + * @return the combined expressions. + */ + public N1QLExpression ne(N1QLExpression right) { + return infix("!=", toString(), right.toString()); + } + + // ===== HELPERS ===== + + /** + * Combines two expressions with the greater or equals than operator (">="). + * + * @param right the expression to combine. + * @return the combined expressions. + */ + public N1QLExpression gte(N1QLExpression right) { + return infix(">=", toString(), right.toString()); + } + + /** + * Combines two expressions with the less or equals than operator ("<="). + * + * @param right the expression to combine. + * @return the combined expressions. + */ + public N1QLExpression lte(N1QLExpression right) { + return infix("<=", toString(), right.toString()); + } + + /** + * Adds a LIKE clause between the current and the given expression. + * + * @param right the right hand side expression. + * @return a new expression with the clause applied. + */ + public N1QLExpression like(N1QLExpression right) { + return infix("LIKE", toString(), right.toString()); + } + + /** + * Adds a NOT LIKE clause between the current and the given expression. + * + * @param right the right hand side expression. + * @return a new expression with the clause applied. + */ + public N1QLExpression notLike(N1QLExpression right) { + return infix("NOT LIKE", toString(), right.toString()); + } + + /** + * Adds a IN clause between the current and the given expression. + * + * @param right the right hand side expression. + * @return a new expression with the clause applied. + */ + public N1QLExpression in(N1QLExpression right) { + return infix("IN", toString(), right.toString()); + } + + /** + * Adds a NOT IN clause between the current and the given expression. + * + * @param right the right hand side expression. + * @return a new expression with the clause applied. + */ + public N1QLExpression notIn(N1QLExpression right) { + return infix("NOT IN", toString(), right.toString()); + } + + /** + * Appends a "IS NOT MISSING" to the expression. + * + * @return the postfixed expression. + */ + public N1QLExpression isNotMissing() { + return postfix("IS NOT MISSING", toString()); + } + + /** + * Combines two expressions with the less than operator ("<"). + * + * @param right the expression to combine. + * @return the combined expressions. + */ + public N1QLExpression lt(N1QLExpression right) { + return infix("<", toString(), right.toString()); + } + + public N1QLExpression orderBy(N1QLExpression... expressions) { + // all the expressions just need to be separated by commas... + StringBuilder sbOrderClause = new StringBuilder(); + for (int i = 0; i < expressions.length; i++) { + sbOrderClause.append(expressions[i].toString()); + if (expressions.length - 1 > i) { + sbOrderClause.append(", "); + } + } + return infix("ORDER BY", toString(), sbOrderClause.toString()); + } + + @Override + public String toString() { + return value.toString(); + } +} diff --git a/src/main/java/org/springframework/data/couchbase/core/query/N1QLQuery.java b/src/main/java/org/springframework/data/couchbase/core/query/N1QLQuery.java new file mode 100644 index 00000000..f32f8c28 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/query/N1QLQuery.java @@ -0,0 +1,33 @@ +package org.springframework.data.couchbase.core.query; + +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.query.QueryOptions; + +public class N1QLQuery { + private N1QLExpression expression; + private QueryOptions options; + + public N1QLQuery(N1QLExpression expression, QueryOptions options) { + this.expression = expression; + this.options = options; + } + + public N1QLQuery(N1QLExpression expression) { + this(expression, QueryOptions.queryOptions()); + } + + public String getExpression() { + return expression.toString(); + } + + public QueryOptions getOptions() { + return options; + } + + public JsonObject n1ql() { + JsonObject query = JsonObject.create().put("statement", expression.toString()); + options.build().injectParams(query); + return query; + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/core/query/N1qlJoin.java b/src/main/java/org/springframework/data/couchbase/core/query/N1qlJoin.java index c0c3d4d2..d73c87f8 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/N1qlJoin.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/N1qlJoin.java @@ -15,61 +15,56 @@ */ package org.springframework.data.couchbase.core.query; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * This annotation is targeted for entity field which is a list of the - * associated entities fetched by ANSI Join across the entities available - * from Couchbase Server 5.5 + * This annotation is targeted for entity field which is a list of the associated entities fetched by ANSI Join across + * the entities available from Couchbase Server 5.5 * * @author Subhashni Balakrishnan */ -@Target({ElementType.FIELD}) +@Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface N1qlJoin { - /** - * Join Criteria can be a simple equi join or multiple conditions - * combined using AND or OR. Array based equi joins with unnest is - * also possible. To reference fields in entity use prefix "lks." - * (left key space) and for referencing fields in associated entities - * use "rks." (right key space) - */ - String on(); + /** + * Join Criteria can be a simple equi join or multiple conditions combined using AND or OR. Array based equi joins + * with unnest is also possible. To reference fields in entity use prefix "lks." (left key space) and for referencing + * fields in associated entities use "rks." (right key space) + */ + String on(); - /** - * Fetch type specifies how the associated entities are fetched - * {@link FetchType} - */ - FetchType fetchType() default FetchType.IMMEDIATE; + /** + * Fetch type specifies how the associated entities are fetched {@link FetchType} + */ + FetchType fetchType() default FetchType.IMMEDIATE; - /** - * Where clause for the join. To reference fields in entity use - * prefix "lks." and for referencing fields in associated entities - * use "rks." - */ - String where() default ""; + /** + * Where clause for the join. To reference fields in entity use prefix "lks." and for referencing fields in associated + * entities use "rks." + */ + String where() default ""; - /** - * Hint index for entity for indexed nested loop join - */ - String index() default ""; + /** + * Hint index for entity for indexed nested loop join + */ + String index() default ""; - /** - * Hint index for associated entity for indexed nested loop join - */ - String rightIndex() default ""; + /** + * Hint index for associated entity for indexed nested loop join + */ + String rightIndex() default ""; - /** - * Hash side specification for the associated entity for hash join - * Note: Supported on enterprise edition only - */ - HashSide hashside() default HashSide.NONE; + /** + * Hash side specification for the associated entity for hash join Note: Supported on enterprise edition only + */ + HashSide hashside() default HashSide.NONE; - /** - * Use keys query hint - */ - String[] keys() default {}; + /** + * Use keys query hint + */ + String[] keys() default {}; } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/Query.java b/src/main/java/org/springframework/data/couchbase/core/query/Query.java index 56df9561..5061012e 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/Query.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/Query.java @@ -1,73 +1,130 @@ -/* - * Copyright 2012-2020 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.springframework.data.couchbase.core.query; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.util.Assert; -import org.springframework.data.annotation.QueryAnnotation; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; -/** - * Annotation to support the use of N1QL queries with Couchbase. - *

    - * Using it without parameter will resolve the query from the method name. Providing a value - * (an inline N1QL statement) will execute that statement instead. - *

    - * In this case, one can use a placeholder notation of {@code ?0}, {@code ?1} and so on. - *

    - * Also, SpEL in the form #{spelExpression} is supported, including the - * following N1QL variables that will be replaced by the underlying {@link CouchbaseTemplate} - * associated information: - *

      - *
    • - * {@value StringN1qlBasedQuery#SPEL_SELECT_FROM_CLAUSE} - * (see {@link StringN1qlBasedQuery#SPEL_SELECT_FROM_CLAUSE}) - *
    • - *
    • - * {@value StringN1qlBasedQuery#SPEL_BUCKET} - * (see {@link StringN1qlBasedQuery#SPEL_BUCKET}) - *
    • - *
    • - * {@value StringN1qlBasedQuery#SPEL_ENTITY} - * (see {@link StringN1qlBasedQuery#SPEL_ENTITY}) - *
    • - *
    • - * {@value StringN1qlBasedQuery#SPEL_FILTER} - * (see {@link StringN1qlBasedQuery#SPEL_FILTER}) - *
    • - *
    - * - * @author Simon Baslé. - */ -@Documented -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@QueryAnnotation -public @interface Query { +public class Query { + + private long skip; + private int limit; + private Sort sort = Sort.unsorted(); + private final List criteria = new ArrayList<>(); + + public Query() {} + + public Query(final QueryCriteria criteriaDefinition) { + addCriteria(criteriaDefinition); + } + + public Query addCriteria(QueryCriteria criteriaDefinition) { + this.criteria.add(criteriaDefinition); + return this; + } + + /** + * Set number of documents to skip before returning results. + * + * @param skip + * @return + */ + public Query skip(long skip) { + this.skip = skip; + return this; + } + + /** + * Limit the number of returned documents to {@code limit}. + * + * @param limit + * @return + */ + public Query limit(int limit) { + this.limit = limit; + return this; + } + + /** + * Sets the given pagination information on the {@link Query} instance. Will transparently set {@code skip} and + * {@code limit} as well as applying the {@link Sort} instance defined with the {@link Pageable}. + * + * @param pageable + * @return + */ + public Query with(final Pageable pageable) { + if (pageable.isUnpaged()) { + return this; + } + this.limit = pageable.getPageSize(); + this.skip = pageable.getOffset(); + return with(pageable.getSort()); + } + + /** + * Adds a {@link Sort} to the {@link Query} instance. + * + * @param sort + * @return + */ + public Query with(final Sort sort) { + Assert.notNull(sort, "Sort must not be null!"); + if (sort.isUnsorted()) { + return this; + } + this.sort = this.sort.and(sort); + return this; + } + + public void appendSkipAndLimit(final StringBuilder sb) { + if (limit > 0) { + sb.append(" LIMIT ").append(limit); + } + if (skip > 0) { + sb.append(" OFFSET ").append(skip); + } + } + + public void appendSort(final StringBuilder sb) { + if (sort.isUnsorted()) { + return; + } + + sb.append(" ORDER BY "); + sort.stream().forEach(order -> { + if (order.isIgnoreCase()) { + throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! " + + "Couchbase N1QL does not support sorting ignoring case currently!", order.getProperty())); + } + sb.append(order.getProperty()).append(" ").append(order.isAscending() ? "ASC," : "DESC,"); + }); + sb.deleteCharAt(sb.length() - 1); + } + + public void appendWhere(final StringBuilder sb) { + sb.append(" WHERE "); + boolean first = true; + for (QueryCriteria c : criteria) { + if (first) { + first = false; + } else { + sb.append(" AND "); + } + sb.append(c.export()); + } + } + + public String export() { + StringBuilder sb = new StringBuilder(); + appendWhere(sb); + appendSort(sb); + appendSkipAndLimit(sb); + return sb.toString(); + } - /** - * Takes a N1QL statement string to define the actual query to be executed. This one will take precedence over the - * method name then. - */ - String value() default ""; } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java new file mode 100644 index 00000000..9b30e70d --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java @@ -0,0 +1,153 @@ +package org.springframework.data.couchbase.core.query; + +import org.springframework.lang.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class QueryCriteria implements QueryCriteriaDefinition { + + /** + * Holds the chain itself, the current operator being always the last one. + */ + private final List criteriaChain; + + /** + * Represents how the chain is hung together, null only for the first element. + */ + private ChainOperator chainOperator; + + private final String key; + private String operator; + private Object value; + + QueryCriteria(List chain, String key, Object value, ChainOperator chainOperator) { + this.criteriaChain = chain; + criteriaChain.add(this); + this.key = key; + this.value = value; + this.chainOperator = chainOperator; + } + + /** + * Static factory method to create a Criteria using the provided key. + */ + public static QueryCriteria where(String key) { + return new QueryCriteria(new ArrayList<>(), key, null, null); + } + + public QueryCriteria and(String key) { + return new QueryCriteria(this.criteriaChain, key, null, ChainOperator.AND); + } + + public QueryCriteria and(QueryCriteria criteria) { + return new QueryCriteria(this.criteriaChain, null, criteria, ChainOperator.AND); + } + + public QueryCriteria or(QueryCriteria criteria) { + return new QueryCriteria(this.criteriaChain, null, criteria, ChainOperator.OR); + } + + public QueryCriteria or(String key) { + return new QueryCriteria(this.criteriaChain, key, null, ChainOperator.OR); + } + + public QueryCriteria eq(@Nullable Object o) { + return is(o); + } + + public QueryCriteria is(@Nullable Object o) { + operator = "="; + value = o; + return this; + } + + public QueryCriteria ne(@Nullable Object o) { + operator = "!="; + value = o; + return this; + } + + public QueryCriteria lt(@Nullable Object o) { + operator = "<"; + value = o; + return this; + } + + public QueryCriteria lte(@Nullable Object o) { + operator = "<="; + value = o; + return this; + } + + public QueryCriteria gt(@Nullable Object o) { + operator = ">"; + value = o; + return this; + } + + public QueryCriteria gte(@Nullable Object o) { + operator = ">="; + value = o; + return this; + } + + @Override + public String export() { + StringBuilder output = new StringBuilder(); + + boolean first = true; + for (QueryCriteria c : this.criteriaChain) { + if (!first) { + if (c.chainOperator == null) { + throw new IllegalStateException("A chain operator must be present when chaining!"); + } + + output.append(" ").append(c.chainOperator.representation).append(" "); + } + if (first) { + first = false; + } + c.exportSingle(output); + } + + return output.toString(); + } + + protected void exportSingle(final StringBuilder sb) { + if (value instanceof QueryCriteria) { + sb.append("(").append(((QueryCriteria) value).export()).append(")"); + } else { + String fieldName = "`" + key + "`"; + sb.append(fieldName).append(" ").append(operator).append(" ").append(maybeWrapValue(value)); + } + } + + private String maybeWrapValue(Object value) { + if (value instanceof String) { + return "\"" + value + "\""; + } else if (value == null) { + return "null"; + } else { + return value.toString(); + } + } + + enum ChainOperator { + AND("and"), + OR("or"), + NOT("not"); + + private final String representation; + + ChainOperator(String representation) { + this.representation = representation; + } + + public String getRepresentation() { + return representation; + } + } + +} + diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonFragment.java b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteriaDefinition.java similarity index 68% rename from src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonFragment.java rename to src/main/java/org/springframework/data/couchbase/core/query/QueryCriteriaDefinition.java index a8f90fa7..f29b6732 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonFragment.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/QueryCriteriaDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2010-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. @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.couchbase.repository.cdi; +package org.springframework.data.couchbase.core.query; + +import org.springframework.lang.Nullable; /** - * @author Mark Paluch + * @author Oliver Gierke + * @author Christoph Strobl */ -public interface CdiPersonFragment { - - int returnTwo(); +public interface QueryCriteriaDefinition { + String export(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/View.java b/src/main/java/org/springframework/data/couchbase/core/query/View.java index e0748d4f..6de0d9c5 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/View.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/View.java @@ -31,25 +31,26 @@ import java.lang.annotation.Target; @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) -//don't set @QueryAnnotation, as it causes problems with reduce and replacing count() method, the reduce detection needs to be improved +// don't set @QueryAnnotation, as it causes problems with reduce and replacing count() method, the reduce detection +// needs to be improved public @interface View { - /** - * The name of the Design Document to use. If omitted, defaults to one derived from the entity class name. - * - * @return name of the Design Document. - */ - String designDocument() default ""; + /** + * The name of the Design Document to use. If omitted, defaults to one derived from the entity class name. + * + * @return name of the Design Document. + */ + String designDocument() default ""; - /** - * The name of the View to use. If omitted, defaults to one derived from the method name (stripped of prefix "find" or - * "count"). This is mandatory to trigger a query derivation from the method name (ie. a View query with parameters - * like limit, startkey, etc...). - * - * @return name of the View - */ - String viewName() default ""; + /** + * The name of the View to use. If omitted, defaults to one derived from the method name (stripped of prefix "find" or + * "count"). This is mandatory to trigger a query derivation from the method name (ie. a View query with parameters + * like limit, startkey, etc...). + * + * @return name of the View + */ + String viewName() default ""; - boolean reduce() default false; + boolean reduce() default false; } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/ViewIndexed.java b/src/main/java/org/springframework/data/couchbase/core/query/ViewIndexed.java index a990ccd9..0cdc419b 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/ViewIndexed.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/ViewIndexed.java @@ -15,48 +15,49 @@ */ package org.springframework.data.couchbase.core.query; - import org.springframework.data.couchbase.repository.CouchbaseRepository; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.data.couchbase.repository.CouchbaseRepository; + /** - * This annotation is targeted at {@link CouchbaseRepository Repository} interfaces, indicating that - * the framework should ensure a View is present when the repository is instantiated. + * This annotation is targeted at {@link CouchbaseRepository Repository} interfaces, indicating that the framework + * should ensure a View is present when the repository is instantiated. *

    - * The view must at least be described as a design document name and view name. Default map function - * will filter documents on the type associated to the repository, and default reduce function is "_count". + * The view must at least be described as a design document name and view name. Default map function will filter + * documents on the type associated to the repository, and default reduce function is "_count". *

    * One can specify a custom reduce function as well as a non-default map function. * * @author Simon Baslé */ -@Target({ElementType.TYPE}) +@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface ViewIndexed { - /** - * The design document in which to create/look for the view. Usually to create the backing view for CRUD - * methods, the expected designDoc value is the entity's simple class name, with a lowercase first letter - * (eg. a UserInfo repository would expect a design document named "userInfo"). - */ - String designDoc(); + /** + * The design document in which to create/look for the view. Usually to create the backing view for CRUD methods, the + * expected designDoc value is the entity's simple class name, with a lowercase first letter (eg. a UserInfo + * repository would expect a design document named "userInfo"). + */ + String designDoc(); - /** - * The name of the view, defaults to "all" (which is what CRUD methods expect by default). - */ - String viewName() default "all"; + /** + * The name of the view, defaults to "all" (which is what CRUD methods expect by default). + */ + String viewName() default "all"; - /** - * The map function to use (default is empty, which will trigger a default map function filtering on the - * repository's associated entity type). - */ - String mapFunction() default ""; + /** + * The map function to use (default is empty, which will trigger a default map function filtering on the repository's + * associated entity type). + */ + String mapFunction() default ""; - /** - * The reduce function to use (default is built in "_count" reduce function). - */ - String reduceFunction() default "_count"; + /** + * The reduce function to use (default is built in "_count" reduce function). + */ + String reduceFunction() default "_count"; } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/WithConsistency.java b/src/main/java/org/springframework/data/couchbase/core/query/WithConsistency.java index b498e26b..e44bc787 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/WithConsistency.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/WithConsistency.java @@ -16,19 +16,20 @@ package org.springframework.data.couchbase.core.query; -import com.couchbase.client.java.query.consistency.ScanConsistency; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; + import org.springframework.data.annotation.QueryAnnotation; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import com.couchbase.client.java.query.QueryScanConsistency; + /** - * Annotation to set the scan consistency of N1QL queries with Couchbase. - * This controls whether couchbase waits for all changes to be processed by an index - * or whether stale results are acceptable. + * Annotation to set the scan consistency of N1QL queries with Couchbase. This controls whether couchbase waits for all + * changes to be processed by an index or whether stale results are acceptable. *

    * If not set, the default consistency set in {@link AbstractCouchbaseConfiguration#getDefaultConsistency()} is used. * @@ -40,6 +41,6 @@ import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; @QueryAnnotation public @interface WithConsistency { - ScanConsistency value(); + QueryScanConsistency value(); } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/package-info.java b/src/main/java/org/springframework/data/couchbase/core/query/package-info.java index 2d38c5e4..05969697 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/core/query/package-info.java @@ -1,5 +1,5 @@ /** - * This package contains annotations and classes relative to querying with Couchbase - * (whether through views or N1QL) and the associated indexes. + * This package contains annotations and classes relative to querying with Couchbase (whether through views or N1QL) and + * the associated indexes. */ package org.springframework.data.couchbase.core.query; diff --git a/src/main/java/org/springframework/data/couchbase/core/support/TemplateUtils.java b/src/main/java/org/springframework/data/couchbase/core/support/TemplateUtils.java index 68330b64..0aadd6da 100644 --- a/src/main/java/org/springframework/data/couchbase/core/support/TemplateUtils.java +++ b/src/main/java/org/springframework/data/couchbase/core/support/TemplateUtils.java @@ -17,11 +17,11 @@ package org.springframework.data.couchbase.core.support; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; + import org.springframework.dao.QueryTimeoutException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator; import org.springframework.data.couchbase.core.OperationInterruptedException; -import rx.Observable; /** * @author Subhashni Balakrishnan @@ -32,21 +32,17 @@ public class TemplateUtils { public static final String SELECT_CAS = "_CAS"; private static PersistenceExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator(); - - public static Observable translateError(Throwable e) { + public static Throwable translateError(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)); + return exceptionTranslator.translateExceptionIfPossible((RuntimeException) e); + } else if (e instanceof TimeoutException) { + return new QueryTimeoutException(e.getMessage(), e); + } else if (e instanceof InterruptedException) { + return new OperationInterruptedException(e.getMessage(), e); + } else if (e instanceof ExecutionException) { + return new OperationInterruptedException(e.getMessage(), e); } else { - return Observable.error(e); + return e; } } } diff --git a/src/main/java/org/springframework/data/couchbase/monitor/ClientInfo.java b/src/main/java/org/springframework/data/couchbase/monitor/ClientInfo.java deleted file mode 100644 index c0175db7..00000000 --- a/src/main/java/org/springframework/data/couchbase/monitor/ClientInfo.java +++ /dev/null @@ -1,60 +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.monitor; - -import java.net.InetAddress; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.bucket.BucketInfo; - -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.jmx.export.annotation.ManagedResource; - -/** - * Exposes basic client information. - * - * @author Michael Nitschinger - * @author Simon Baslé - */ -@ManagedResource(description = "Client Information") -public class ClientInfo { - - private final Bucket bucket; - private final BucketInfo info; - - public ClientInfo(final Bucket bucket) { - this.bucket = bucket; - this.info = bucket.bucketManager().info(); - } - - @ManagedAttribute(description = "Hostnames of connected nodes") - public String getHostNames() { - StringBuilder result = new StringBuilder(); - for (InetAddress node : info.nodeList()) { - result.append(node.toString()).append(","); - } - return result.toString(); - } - - @ManagedAttribute(description = "Number of connected nodes") - public int getNumberOfNodes() { - return info.nodeCount(); - } - - //TODO obtain count of available nodes vs unavailable ones and expose it - -} diff --git a/src/main/java/org/springframework/data/couchbase/monitor/ClusterInfo.java b/src/main/java/org/springframework/data/couchbase/monitor/ClusterInfo.java deleted file mode 100644 index 3387721e..00000000 --- a/src/main/java/org/springframework/data/couchbase/monitor/ClusterInfo.java +++ /dev/null @@ -1,126 +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.monitor; - -import java.net.InetAddress; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.bucket.BucketInfo; - -import org.springframework.jmx.export.annotation.ManagedAttribute; -import org.springframework.jmx.export.annotation.ManagedMetric; -import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.web.client.RestTemplate; - -/** - * Exposes basic cluster information. - * - * @author Michael Nitschinger - * @author Simon Baslé - */ -@ManagedResource(description = "Cluster Information") -public class ClusterInfo { - - private final RestTemplate template; - private final Bucket bucket; - private final BucketInfo info; - - public ClusterInfo(final Bucket bucket) { - this.template = new RestTemplate(); - this.bucket = bucket; - this.info = bucket.bucketManager().info(); - } - - @ManagedMetric(description = "Total RAM assigned") - public long getTotalRAMAssigned() { - return convertPotentialLong(parseStorageTotals().get("ram").get("total")); - } - - @ManagedMetric(description = "Total RAM used") - public long getTotalRAMUsed() { - return convertPotentialLong(parseStorageTotals().get("ram").get("used")); - } - - @ManagedMetric(description = "Total Disk Space assigned") - public long getTotalDiskAssigned() { - return convertPotentialLong(parseStorageTotals().get("hdd").get("total")); - } - - @ManagedMetric(description = "Total Disk Space used") - public long getTotalDiskUsed() { - return convertPotentialLong(parseStorageTotals().get("hdd").get("used")); - } - - @ManagedMetric(description = "Total Disk Space free") - public long getTotalDiskFree() { - return convertPotentialLong(parseStorageTotals().get("hdd").get("free")); - } - - @ManagedAttribute(description = "Cluster is Balanced") - public boolean getIsBalanced() { - return (Boolean) fetchPoolInfo().get("balanced"); - } - - @ManagedAttribute(description = "Rebalance Status") - public String getRebalanceStatus() { - return (String) fetchPoolInfo().get("rebalanceStatus"); - } - - @ManagedAttribute(description = "Maximum Available Buckets") - public int getMaxBuckets() { - return (Integer) fetchPoolInfo().get("maxBucketCount"); - } - - /** - * Depending on the value size, either int or long can be passed in and get - * converted to long. - * - * @param value the value to convert. - * @return the converted value. - */ - private long convertPotentialLong(Object value) { - if (value instanceof Integer) { - return new Long((Integer) value); - } - else if (value instanceof Long) { - return (Long) value; - } - else { - throw new IllegalStateException("Cannot convert value to long: " + value); - } - } - - protected String randomAvailableHostname() { - List available = info.nodeList(); - Collections.shuffle(available); - return available.get(0).getHostName(); - } - - private HashMap fetchPoolInfo() { - return template.getForObject("http://" - + randomAvailableHostname() + ":8091/pools/default", HashMap.class); - } - - private HashMap parseStorageTotals() { - HashMap stats = fetchPoolInfo(); - return (HashMap) stats.get("storageTotals"); - } - -} diff --git a/src/main/java/org/springframework/data/couchbase/monitor/package-info.java b/src/main/java/org/springframework/data/couchbase/monitor/package-info.java deleted file mode 100644 index be1786d4..00000000 --- a/src/main/java/org/springframework/data/couchbase/monitor/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains all classes related to monitoring the Couchbase cluster, - * statistics that will be exposed as JMX beans. - */ -package org.springframework.data.couchbase.monitor; diff --git a/src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java index 6e258d4e..10e795b9 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java @@ -16,21 +16,33 @@ package org.springframework.data.couchbase.repository; -import java.io.Serializable; +import java.util.List; import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.PagingAndSortingRepository; /** * Couchbase specific {@link org.springframework.data.repository.Repository} interface. * * @author Michael Nitschinger */ -public interface CouchbaseRepository extends CrudRepository { +@NoRepositoryBean +public interface CouchbaseRepository extends PagingAndSortingRepository { - /** - * @return a reference to the underlying {@link CouchbaseOperations operation template}. - */ - CouchbaseOperations getCouchbaseOperations(); + /** + * @return a reference to the underlying {@link CouchbaseOperations operation template}. + */ + CouchbaseOperations getCouchbaseOperations(); + + @Override + List findAll(Sort sort); + + @Override + List findAll(); + + @Override + List findAllById(Iterable iterable); } diff --git a/src/main/java/org/springframework/data/couchbase/repository/Query.java b/src/main/java/org/springframework/data/couchbase/repository/Query.java new file mode 100644 index 00000000..72c333d1 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/Query.java @@ -0,0 +1,45 @@ +package org.springframework.data.couchbase.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.annotation.QueryAnnotation; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery; + +/** + * Annotation to support the use of N1QL queries with Couchbase. + *

    + * Using it without parameter will resolve the query from the method name. Providing a value (an inline N1QL statement) + * will execute that statement instead. + *

    + * In this case, one can use a placeholder notation of {@code ?0}, {@code ?1} and so on. + *

    + * Also, SpEL in the form #{spelExpression} is supported, including the following N1QL variables that will + * be replaced by the underlying {@link CouchbaseTemplate} associated information: + *

      + *
    • {@value StringN1qlBasedQuery#SPEL_SELECT_FROM_CLAUSE} (see {@link StringN1qlBasedQuery#SPEL_SELECT_FROM_CLAUSE}) + *
    • + *
    • {@value StringN1qlBasedQuery#SPEL_BUCKET} (see {@link StringN1qlBasedQuery#SPEL_BUCKET})
    • + *
    • {@value StringN1qlBasedQuery#SPEL_ENTITY} (see {@link StringN1qlBasedQuery#SPEL_ENTITY})
    • + *
    • {@value StringN1qlBasedQuery#SPEL_FILTER} (see {@link StringN1qlBasedQuery#SPEL_FILTER})
    • + *
    + * + * @author Simon Baslé. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@QueryAnnotation +public @interface Query { + + /** + * Takes a N1QL statement string to define the actual query to be executed. This one will take precedence over the + * method name. + */ + String value() default ""; + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepository.java index 546dbba4..4bba8ff1 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/ReactiveCouchbaseRepository.java @@ -15,18 +15,19 @@ */ package org.springframework.data.couchbase.repository; -import java.io.Serializable; - -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.reactive.ReactiveSortingRepository; /** * @author Subhashni Balakrishnan * @since 3.0 */ -public interface ReactiveCouchbaseRepository extends ReactiveCrudRepository { - /** - * @return a reference to the underlying {@link RxJavaCouchbaseOperations operation template}. - */ - RxJavaCouchbaseOperations getCouchbaseOperations(); +@NoRepositoryBean +public interface ReactiveCouchbaseRepository extends ReactiveSortingRepository { + /** + * @return a reference to the underlying {@link CouchbaseOperations operation template}. + */ + ReactiveCouchbaseOperations getReactiveCouchbaseOperations(); } diff --git a/src/main/java/org/springframework/data/couchbase/repository/ScanConsistency.java b/src/main/java/org/springframework/data/couchbase/repository/ScanConsistency.java new file mode 100644 index 00000000..a65458f1 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/ScanConsistency.java @@ -0,0 +1,31 @@ +package org.springframework.data.couchbase.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.couchbase.client.java.analytics.AnalyticsScanConsistency; +import com.couchbase.client.java.query.QueryScanConsistency; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +public @interface ScanConsistency { + + /** + * Specifies a custom scan consistency for N1QL queries. + * + * @return the scan consistency configured, defaults to not bounded. + */ + QueryScanConsistency query() default QueryScanConsistency.NOT_BOUNDED; + + /** + * Specifies a custom scan consistency for analytics queries. + * + * @return the scan consistency configured, defaults to not bounded. + */ + AnalyticsScanConsistency analytics() default AnalyticsScanConsistency.NOT_BOUNDED; + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java b/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java deleted file mode 100644 index 1c5c424b..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/auditing/CouchbaseAuditingRegistrar.java +++ /dev/null @@ -1,98 +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.repository.auditing; - -import java.lang.annotation.Annotation; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.data.auditing.AuditingHandler; -import org.springframework.data.auditing.IsNewAwareAuditingHandler; -import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport; -import org.springframework.data.auditing.config.AuditingConfiguration; -import org.springframework.data.config.ParsingUtils; -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; -import org.springframework.data.couchbase.core.mapping.event.AuditingEventListener; -import org.springframework.util.Assert; - -/** - * A support registrar that allows to set up auditing for Couchbase ({@link AuditingHandler} set up). - * See {@link EnableCouchbaseAuditing} for the associated annotation. - * - * @author Thomas Darimont - * @author Oliver Gierke - * @author Simon Baslé - */ -public class CouchbaseAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { - - @Override - protected Class getAnnotation() { - return EnableCouchbaseAuditing.class; - } - - @Override - protected String getAuditingHandlerBeanName() { - return BeanNames.COUCHBASE_AUDITING_HANDLER; - } - - @Override - public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { - Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!"); - Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); - - ensureMappingContext(registry, annotationMetadata); - super.registerBeanDefinitions(annotationMetadata, registry); - } - - @Override - protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) { - Assert.notNull(configuration, "AuditingConfiguration must not be null!"); - - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class); - builder.addConstructorArgReference(BeanNames.COUCHBASE_MAPPING_CONTEXT); - return configureDefaultAuditHandlerAttributes(configuration, builder); - } - - @Override - protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition, - BeanDefinitionRegistry registry) { - Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!"); - Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); - - BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder - .rootBeanDefinition(AuditingEventListener.class); - listenerBeanDefinitionBuilder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition( - getAuditingHandlerBeanName(), registry)); - - registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(), - AuditingEventListener.class.getName(), registry); - } - - private void ensureMappingContext(BeanDefinitionRegistry registry, Object source) { - if (!registry.containsBeanDefinition(BeanNames.COUCHBASE_MAPPING_CONTEXT)) { - RootBeanDefinition definition = new RootBeanDefinition(CouchbaseMappingContext.class); - definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - definition.setSource(source); - - registry.registerBeanDefinition(BeanNames.COUCHBASE_MAPPING_CONTEXT, definition); - } - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/auditing/EnableCouchbaseAuditing.java b/src/main/java/org/springframework/data/couchbase/repository/auditing/EnableCouchbaseAuditing.java deleted file mode 100644 index 37ef7558..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/auditing/EnableCouchbaseAuditing.java +++ /dev/null @@ -1,63 +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.repository.auditing; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.context.annotation.Import; -import org.springframework.data.auditing.DateTimeProvider; -import org.springframework.data.domain.AuditorAware; - -/** - * Annotation to enable auditing in Couchbase via annotation configuration. - * - * @author Thomas Darimont - * @author Oliver Gierke - * @author Simon Baslé - */ -@Inherited -@Documented -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Import(CouchbaseAuditingRegistrar.class) -public @interface EnableCouchbaseAuditing { - /** - * Configures the {@link AuditorAware} bean to be used to lookup the current principal. - */ - String auditorAwareRef() default ""; - - /** - * Configures whether the creation and modification dates are set. Defaults to {@literal true}. - */ - boolean setDates() default true; - - /** - * Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}. - */ - boolean modifyOnCreate() default true; - - /** - * Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be - * used for setting creation and modification dates. - */ - String dateTimeProviderRef() default ""; -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java deleted file mode 100644 index 560efa48..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryBean.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import java.lang.annotation.Annotation; -import java.util.Optional; -import java.util.Set; - -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.BeanManager; - -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.cdi.CdiRepositoryBean; -import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; -import org.springframework.util.Assert; - -/** - * A bean which represents a Couchbase repository. - * - * @author Mark Paluch - */ -public class CouchbaseRepositoryBean extends CdiRepositoryBean { - - private final Bean couchbaseOperationsBean; - - /** - * Creates a new {@link CouchbaseRepositoryBean}. - * - * @param operations must not be {@literal null}. - * @param qualifiers must not be {@literal null}. - * @param repositoryType must not be {@literal null}. - * @param beanManager must not be {@literal null}. - * @param detector detector for the custom {@link org.springframework.data.repository.Repository} implementations - * {@link org.springframework.data.repository.config.CustomRepositoryImplementationDetector}, can be - * {@literal null}. - */ - public CouchbaseRepositoryBean(Bean operations, Set qualifiers, - Class repositoryType, BeanManager beanManager, CustomRepositoryImplementationDetector detector) { - super(qualifiers, repositoryType, beanManager, Optional.of(detector)); - - Assert.notNull(operations, "Cannot create repository with 'null' for CouchbaseOperations."); - this.couchbaseOperationsBean = operations; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class) - */ - @Override - protected T create(CreationalContext creationalContext, Class repositoryType) { - - CouchbaseOperations couchbaseOperations = getDependencyInstance(couchbaseOperationsBean, CouchbaseOperations.class); - RepositoryOperationsMapping couchbaseOperationsMapping = new RepositoryOperationsMapping(couchbaseOperations); - IndexManager indexManager = new IndexManager(); - - return create(() -> new CouchbaseRepositoryFactory(couchbaseOperationsMapping, indexManager), repositoryType); - } - - @Override - public Class getScope() { - return couchbaseOperationsBean.getScope(); - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java deleted file mode 100644 index 1ab5d8b1..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/cdi/CouchbaseRepositoryExtension.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import javax.enterprise.event.Observes; -import javax.enterprise.inject.UnsatisfiedResolutionException; -import javax.enterprise.inject.spi.AfterBeanDiscovery; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.ProcessBean; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.repository.cdi.CdiRepositoryBean; -import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport; - -/** - * A portable CDI extension which registers beans for Spring Data Couchbase repositories. - * @author Mark Paluch - */ -public class CouchbaseRepositoryExtension extends CdiRepositoryExtensionSupport{ - - private final Map, Bean> couchbaseOperationsMap = new HashMap, Bean>(); - - /** - * Implementation of a an observer which checks for CouchbaseOperations beans and stores them in {@link #couchbaseOperationsMap} for - * later association with corresponding repository beans. - * - * @param The type. - * @param processBean The annotated type as defined by CDI. - */ - @SuppressWarnings("unchecked") - void processBean(@Observes ProcessBean processBean) { - Bean bean = processBean.getBean(); - for (Type type : bean.getTypes()) { - if (type instanceof Class && CouchbaseOperations.class.isAssignableFrom((Class) type)) { - couchbaseOperationsMap.put(bean.getQualifiers(), ((Bean) bean)); - } - } - } - - /** - * Implementation of a an observer which registers beans to the CDI container for the detected Spring Data - * repositories. - *

    - * The repository beans are associated to the CouchbaseOperations using their qualifiers. - * - * @param beanManager The BeanManager instance. - */ - void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { - for (Map.Entry, Set> entry : getRepositoryTypes()) { - - Class repositoryType = entry.getKey(); - Set qualifiers = entry.getValue(); - - CdiRepositoryBean repositoryBean = createRepositoryBean(repositoryType, qualifiers, beanManager); - afterBeanDiscovery.addBean(repositoryBean); - registerBean(repositoryBean); - } - } - - /** - * Creates a {@link Bean}. - * - * @param The type of the repository. - * @param repositoryType The class representing the repository. - * @param beanManager The BeanManager instance. - * @return The bean. - */ - private CdiRepositoryBean createRepositoryBean(Class repositoryType, Set qualifiers, BeanManager beanManager) { - - Bean couchbaseOperationsBean = this.couchbaseOperationsMap.get(qualifiers); - - if (couchbaseOperationsBean == null) { - throw new UnsatisfiedResolutionException(String.format("Unable to resolve a bean for '%s' with qualifiers %s.", - CouchbaseOperations.class.getName(), qualifiers)); - } - - return new CouchbaseRepositoryBean(couchbaseOperationsBean, qualifiers, repositoryType, beanManager, getCustomImplementationDetector()); - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/ReactiveCouchbaseRepositoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/ReactiveCouchbaseRepositoryBean.java deleted file mode 100644 index 64290cc1..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/cdi/ReactiveCouchbaseRepositoryBean.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.BeanManager; -import java.lang.annotation.Annotation; -import java.util.Optional; -import java.util.Set; - -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory; -import org.springframework.data.repository.cdi.CdiRepositoryBean; -import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; -import org.springframework.util.Assert; - -/** - * A bean which represents a Couchbase repository. - * @author Subhashni Balakrishnan - * @author Mark Paluch - */ -public class ReactiveCouchbaseRepositoryBean extends CdiRepositoryBean { - - private final Bean reactiveCouchbaseOperationsBean; - - /** - * Creates a new {@link ReactiveCouchbaseRepositoryBean}. - * - * @param reactiveOperations must not be {@literal null}. - * @param qualifiers must not be {@literal null}. - * @param repositoryType must not be {@literal null}. - * @param beanManager must not be {@literal null}. - * @param detector detector for the custom {@link org.springframework.data.repository.Repository} implementations - * {@link org.springframework.data.repository.config.CustomRepositoryImplementationDetector}, can be {@literal null}. - */ - public ReactiveCouchbaseRepositoryBean(Bean reactiveOperations, Set qualifiers, Class repositoryType, - BeanManager beanManager, CustomRepositoryImplementationDetector detector) { - super(qualifiers, repositoryType, beanManager, Optional.of(detector)); - - Assert.notNull(reactiveOperations, "Cannot create repository with 'null' for ReactiveCouchbaseOperations."); - this.reactiveCouchbaseOperationsBean = reactiveOperations; - } - - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class, java.lang.Object) - */ - @Override - protected T create(CreationalContext creationalContext, Class repositoryType, Optional customImplementation) { - RxJavaCouchbaseOperations reactiveCouchbaseOperations = getDependencyInstance(reactiveCouchbaseOperationsBean, RxJavaCouchbaseOperations.class); - ReactiveRepositoryOperationsMapping reactiveCouchbaseOperationsMapping = new ReactiveRepositoryOperationsMapping(reactiveCouchbaseOperations); - IndexManager indexManager = new IndexManager(); - - ReactiveCouchbaseRepositoryFactory factory = new ReactiveCouchbaseRepositoryFactory(reactiveCouchbaseOperationsMapping, indexManager); - - return customImplementation.map(o -> factory.getRepository(repositoryType, o)) - .orElseGet(() -> factory.getRepository(repositoryType)); - } - - @Override - public Class getScope() { - return reactiveCouchbaseOperationsBean.getScope(); - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/ReactiveCouchbaseRepositoryExtension.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/ReactiveCouchbaseRepositoryExtension.java deleted file mode 100644 index 30afa709..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/cdi/ReactiveCouchbaseRepositoryExtension.java +++ /dev/null @@ -1,100 +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.repository.cdi; - -import javax.enterprise.event.Observes; -import javax.enterprise.inject.UnsatisfiedResolutionException; -import javax.enterprise.inject.spi.AfterBeanDiscovery; -import javax.enterprise.inject.spi.Bean; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.ProcessBean; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.repository.cdi.CdiRepositoryBean; -import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport; - -/** - * A portable CDI extension which registers beans for Spring Data Couchbase repositories. - * @author Mark Paluch - */ -public class ReactiveCouchbaseRepositoryExtension extends CdiRepositoryExtensionSupport{ - - private final Map, Bean> reactiveCouchbaseOperationsMap = new HashMap, Bean>(); - - /** - * Implementation of a an observer which checks for CouchbaseOperations beans and stores them in {@link #reactiveCouchbaseOperationsMap} for - * later association with corresponding repository beans. - * - * @param The type. - * @param processBean The annotated type as defined by CDI. - */ - @SuppressWarnings("unchecked") - void processBean(@Observes ProcessBean processBean) { - Bean bean = processBean.getBean(); - for (Type type : bean.getTypes()) { - if (type instanceof Class && CouchbaseOperations.class.isAssignableFrom((Class) type)) { - reactiveCouchbaseOperationsMap.put(bean.getQualifiers(), ((Bean) bean)); - } - } - } - - /** - * Implementation of a an observer which registers beans to the CDI container for the detected Spring Data - * repositories. - *

    - * The repository beans are associated to the CouchbaseOperations using their qualifiers. - * - * @param beanManager The BeanManager instance. - */ - void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { - for (Map.Entry, Set> entry : getRepositoryTypes()) { - - Class repositoryType = entry.getKey(); - Set qualifiers = entry.getValue(); - - CdiRepositoryBean repositoryBean = createRepositoryBean(repositoryType, qualifiers, beanManager); - afterBeanDiscovery.addBean(repositoryBean); - registerBean(repositoryBean); - } - } - - /** - * Creates a {@link Bean}. - * - * @param The type of the repository. - * @param repositoryType The class representing the repository. - * @param beanManager The BeanManager instance. - * @return The bean. - */ - private CdiRepositoryBean createRepositoryBean(Class repositoryType, Set qualifiers, BeanManager beanManager) { - - Bean reactiveCouchbaseOperationsBean = this.reactiveCouchbaseOperationsMap.get(qualifiers); - - if (reactiveCouchbaseOperationsBean == null) { - throw new UnsatisfiedResolutionException(String.format("Unable to resolve a bean for '%s' with qualifiers %s.", - CouchbaseOperations.class.getName(), qualifiers)); - } - - return new ReactiveCouchbaseRepositoryBean(reactiveCouchbaseOperationsBean, qualifiers, repositoryType, beanManager, getCustomImplementationDetector()); - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/cdi/package-info.java b/src/main/java/org/springframework/data/couchbase/repository/cdi/package-info.java deleted file mode 100644 index f3cb87b1..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/cdi/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This package contains the Couchbase implementation to integrate the Spring Data repository abstraction with CDI. - */ -package org.springframework.data.couchbase.repository.cdi; diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoriesRegistrar.java b/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoriesRegistrar.java index fbf1ae94..e86c5140 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoriesRegistrar.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoriesRegistrar.java @@ -16,24 +16,24 @@ package org.springframework.data.couchbase.repository.config; +import java.lang.annotation.Annotation; + import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; import org.springframework.data.repository.config.RepositoryConfigurationExtension; -import java.lang.annotation.Annotation; - /** * @author Michael Nitschinger */ public class CouchbaseRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { - @Override - protected Class getAnnotation() { - return EnableCouchbaseRepositories.class; - } + @Override + protected Class getAnnotation() { + return EnableCouchbaseRepositories.class; + } - @Override - protected RepositoryConfigurationExtension getExtension() { - return new CouchbaseRepositoryConfigurationExtension(); - } + @Override + protected RepositoryConfigurationExtension getExtension() { + return new CouchbaseRepositoryConfigurationExtension(); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtension.java index a81e5a4b..450b3457 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtension.java @@ -39,87 +39,85 @@ import org.w3c.dom.Element; */ public class CouchbaseRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { - /** The reference property to use in xml configuration to specify the template to use with a repository. */ - private static final String COUCHBASE_TEMPLATE_REF = "couchbase-template-ref"; + /** The reference property to use in xml configuration to specify the template to use with a repository. */ + private static final String COUCHBASE_TEMPLATE_REF = "couchbase-template-ref"; - /** The reference property to use in xml configuration to specify the index manager bean to use with a repository. */ - private static final String COUCHBASE_INDEX_MANAGER_REF = "couchbase-index-manager-ref"; + /** The reference property to use in xml configuration to specify the index manager bean to use with a repository. */ + private static final String COUCHBASE_INDEX_MANAGER_REF = "couchbase-index-manager-ref"; - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModuleName() - */ - @Override - public String getModuleName() { - return "Couchbase"; -} - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix() - */ - @Override - protected String getModulePrefix() { - return "couchbase"; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryFactoryBeanClassName() - */ - public String getRepositoryFactoryBeanClassName() { - return CouchbaseRepositoryFactoryBean.class.getName(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingAnnotations() - */ - @Override - protected Collection> getIdentifyingAnnotations() { - return Collections.singleton(Document.class); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingTypes() - */ - @Override - protected Collection> getIdentifyingTypes() { - return Collections.singleton(CouchbaseRepository.class); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.XmlRepositoryConfigurationSource) - */ - @Override - public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) { - - Element element = config.getElement(); - - ParsingUtils.setPropertyReference(builder, element, COUCHBASE_TEMPLATE_REF, "couchbaseOperations"); - ParsingUtils.setPropertyReference(builder, element, COUCHBASE_INDEX_MANAGER_REF, "indexManager"); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource) - */ - @Override - public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) { - - builder.addDependsOn(BeanNames.COUCHBASE_OPERATIONS_MAPPING); - builder.addDependsOn(BeanNames.COUCHBASE_INDEX_MANAGER); - builder.addPropertyReference("couchbaseOperationsMapping", BeanNames.COUCHBASE_OPERATIONS_MAPPING); - builder.addPropertyReference("indexManager", BeanNames.COUCHBASE_INDEX_MANAGER); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#useRepositoryConfiguration(org.springframework.data.repository.core.RepositoryMetadata) - */ - protected boolean useRepositoryConfiguration(RepositoryMetadata metadata) { - return !metadata.isReactiveRepository(); - } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModuleName() + */ + @Override + public String getModuleName() { + return "Couchbase"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix() + */ + @Override + protected String getModulePrefix() { + return "couchbase"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryFactoryBeanClassName() + */ + public String getRepositoryFactoryBeanClassName() { + return CouchbaseRepositoryFactoryBean.class.getName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingAnnotations() + */ + @Override + protected Collection> getIdentifyingAnnotations() { + return Collections.singleton(Document.class); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingTypes() + */ + @Override + protected Collection> getIdentifyingTypes() { + return Collections.singleton(CouchbaseRepository.class); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.XmlRepositoryConfigurationSource) + */ + @Override + public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) { + + Element element = config.getElement(); + + ParsingUtils.setPropertyReference(builder, element, COUCHBASE_TEMPLATE_REF, "couchbaseOperations"); + ParsingUtils.setPropertyReference(builder, element, COUCHBASE_INDEX_MANAGER_REF, "indexManager"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource) + */ + @Override + public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) { + + builder.addDependsOn(BeanNames.COUCHBASE_OPERATIONS_MAPPING); + builder.addPropertyReference("couchbaseOperationsMapping", BeanNames.COUCHBASE_OPERATIONS_MAPPING); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#useRepositoryConfiguration(org.springframework.data.repository.core.RepositoryMetadata) + */ + protected boolean useRepositoryConfiguration(RepositoryMetadata metadata) { + return !metadata.isReactiveRepository(); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/EnableCouchbaseRepositories.java b/src/main/java/org/springframework/data/couchbase/repository/config/EnableCouchbaseRepositories.java index c566129e..9ed5eab2 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/EnableCouchbaseRepositories.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/EnableCouchbaseRepositories.java @@ -16,6 +16,13 @@ package org.springframework.data.couchbase.repository.config; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + import org.springframework.beans.factory.FactoryBean; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Import; @@ -24,8 +31,6 @@ import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactoryBean; import org.springframework.data.repository.config.DefaultRepositoryBaseClass; -import java.lang.annotation.*; - /** * Annotation to activate Couchbase repositories. If no base package is configured through either {@link #value()}, * {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class. @@ -39,80 +44,80 @@ import java.lang.annotation.*; @Import(CouchbaseRepositoriesRegistrar.class) public @interface EnableCouchbaseRepositories { - /** - * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: - * {@code @EnableCouchbaseRepositories("org.my.pkg")} instead of {@code @EnableCouchbaseRepositories(basePackages="org.my.pkg")}. - */ - String[] value() default {}; + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: + * {@code @EnableCouchbaseRepositories("org.my.pkg")} instead of + * {@code @EnableCouchbaseRepositories(basePackages="org.my.pkg")}. + */ + String[] value() default {}; - /** - * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this - * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. - */ - String[] basePackages() default {}; + /** + * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this + * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. + */ + String[] basePackages() default {}; - /** - * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The - * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in - * each package that serves no purpose other than being referenced by this attribute. - */ - Class[] basePackageClasses() default {}; + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The + * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in + * each package that serves no purpose other than being referenced by this attribute. + */ + Class[] basePackageClasses() default {}; - /** - * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from - * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. - */ - Filter[] includeFilters() default {}; + /** + * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from + * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. + */ + Filter[] includeFilters() default {}; - /** - * Specifies which types are not eligible for component scanning. - */ - Filter[] excludeFilters() default {}; + /** + * Specifies which types are not eligible for component scanning. + */ + Filter[] excludeFilters() default {}; - /** - * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So - * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning - * for {@code PersonRepositoryImpl}. - * - * @return - */ - String repositoryImplementationPostfix() default ""; + /** + * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So + * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning + * for {@code PersonRepositoryImpl}. + * + * @return + */ + String repositoryImplementationPostfix() default ""; - /** - * Configures the location of where to find the Spring Data named queries properties file. Will default to - * {@code META-INFO/couchbase-named-queries.properties}. - * - * @return - */ - String namedQueriesLocation() default ""; + /** + * Configures the location of where to find the Spring Data named queries properties file. Will default to + * {@code META-INFO/couchbase-named-queries.properties}. + * + * @return + */ + String namedQueriesLocation() default ""; - /** - * Configure the repository base class to be used to create repository proxies for this particular configuration. - * - * @return - */ - Class repositoryBaseClass() default DefaultRepositoryBaseClass.class; + /** + * Configure the repository base class to be used to create repository proxies for this particular configuration. + * + * @return + */ + Class repositoryBaseClass() default DefaultRepositoryBaseClass.class; + /** + * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to + * {@link CouchbaseRepositoryFactoryBean}. + * + * @return + */ + Class repositoryFactoryBeanClass() default CouchbaseRepositoryFactoryBean.class; - /** - * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to - * {@link CouchbaseRepositoryFactoryBean}. - * - * @return - */ - Class repositoryFactoryBeanClass() default CouchbaseRepositoryFactoryBean.class; + /** + * Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the + * repositories infrastructure. + */ + boolean considerNestedRepositories() default false; - /** - * Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the - * repositories infrastructure. - */ - boolean considerNestedRepositories() default false; - - /** - * Configures the name of the {@link CouchbaseTemplate} bean to be used by default with the repositories detected. - * - * @return - */ - String couchbaseTemplateRef() default BeanNames.COUCHBASE_TEMPLATE; + /** + * Configures the name of the {@link CouchbaseTemplate} bean to be used by default with the repositories detected. + * + * @return + */ + String couchbaseTemplateRef() default BeanNames.COUCHBASE_TEMPLATE; } diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/EnableReactiveCouchbaseRepositories.java b/src/main/java/org/springframework/data/couchbase/repository/config/EnableReactiveCouchbaseRepositories.java index 6c0508c2..aeaab0fd 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/EnableReactiveCouchbaseRepositories.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/EnableReactiveCouchbaseRepositories.java @@ -15,18 +15,25 @@ */ package org.springframework.data.couchbase.repository.config; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.springframework.beans.factory.FactoryBean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; import org.springframework.data.couchbase.config.BeanNames; +import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactoryBean; import org.springframework.data.repository.config.DefaultRepositoryBaseClass; /** - * Annotation to activate reactive couchbase repositories. If no base package is configured through either {@link #value()}, - * {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class. + * Annotation to activate reactive couchbase repositories. If no base package is configured through either + * {@link #value()}, {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of + * annotated class. * * @author Subhashni Balakrishnan * @since 3.0 @@ -36,11 +43,12 @@ import org.springframework.data.repository.config.DefaultRepositoryBaseClass; @Documented @Inherited @Import(ReactiveCouchbaseRepositoriesRegistrar.class) -public @interface EnableReactiveCouchbaseRepositories { +public @interface EnableReactiveCouchbaseRepositories { /** * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: - * {@code @EnableCouchbaseRepositories("org.my.pkg")} instead of {@code @EnableCouchbaseRepositories(basePackages="org.my.pkg")}. + * {@code @EnableCouchbaseRepositories("org.my.pkg")} instead of + * {@code @EnableCouchbaseRepositories(basePackages="org.my.pkg")}. */ String[] value() default {}; @@ -92,7 +100,6 @@ public @interface EnableReactiveCouchbaseRepositories { */ Class repositoryBaseClass() default DefaultRepositoryBaseClass.class; - /** * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to * {@link ReactiveCouchbaseRepositoryFactoryBean}. @@ -108,10 +115,10 @@ public @interface EnableReactiveCouchbaseRepositories { boolean considerNestedRepositories() default false; /** - * Configures the name of the {@link ReactiveCouchbaseTemplate} bean to be used by default with the repositories detected. + * Configures the name of the {@link CouchbaseTemplate} bean to be used by default with the repositories detected. * * @return */ - String couchbaseTemplateRef() default BeanNames.REACTIVE_COUCHBASE_TEMPLATE; + String couchbaseTemplateRef() default BeanNames.COUCHBASE_TEMPLATE; } diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/ReactiveCouchbaseRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/couchbase/repository/config/ReactiveCouchbaseRepositoryConfigurationExtension.java index 90e26eb2..42ececcc 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/ReactiveCouchbaseRepositoryConfigurationExtension.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/ReactiveCouchbaseRepositoryConfigurationExtension.java @@ -109,12 +109,9 @@ public class ReactiveCouchbaseRepositoryConfigurationExtension extends Repositor public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) { builder.addDependsOn(BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING); - builder.addDependsOn(BeanNames.COUCHBASE_INDEX_MANAGER); builder.addPropertyReference("couchbaseOperationsMapping", BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING); - builder.addPropertyReference("indexManager", BeanNames.COUCHBASE_INDEX_MANAGER); } - /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#useRepositoryConfiguration(org.springframework.data.repository.core.RepositoryMetadata) diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/ReactiveRepositoryOperationsMapping.java b/src/main/java/org/springframework/data/couchbase/repository/config/ReactiveRepositoryOperationsMapping.java index c906b87a..8db8dd6b 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/ReactiveRepositoryOperationsMapping.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/ReactiveRepositoryOperationsMapping.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.Map; import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.mapping.context.MappingContext; @@ -31,41 +30,29 @@ import org.springframework.util.Assert; * @since 3.0 */ public class ReactiveRepositoryOperationsMapping { - private RxJavaCouchbaseOperations defaultOperations; - private Map byRepository = new HashMap(); - private Map byEntity = new HashMap(); + private CouchbaseOperations defaultOperations; + private Map byRepository = new HashMap<>(); + private Map byEntity = new HashMap<>(); /** * Creates a new mapping, setting the default fallback to use by otherwise non mapped repositories. * * @param defaultOperations the default fallback reactive couchbase operations. */ - public ReactiveRepositoryOperationsMapping(RxJavaCouchbaseOperations defaultOperations) { + public ReactiveRepositoryOperationsMapping(CouchbaseOperations defaultOperations) { Assert.notNull(defaultOperations); this.defaultOperations = defaultOperations; } - /** - * Change the default reactive couchbase operations in an existing mapping. - * - * @param aDefault the new default couchbase operations. - * @return the mapping, for chaining. - */ - public ReactiveRepositoryOperationsMapping setDefault(RxJavaCouchbaseOperations aDefault) { - Assert.notNull(aDefault); - this.defaultOperations = aDefault; - return this; - } - /** * Add a highest priority mapping that will associate a specific repository interface with a given - * {@link RxJavaCouchbaseOperations}. + * {@link CouchbaseOperations}. * * @param repositoryInterface the repository interface {@link Class}. * @param operations the ReactiveCouchbaseOperations to use. * @return the mapping, for chaining. */ - public ReactiveRepositoryOperationsMapping map(Class repositoryInterface, RxJavaCouchbaseOperations operations) { + public ReactiveRepositoryOperationsMapping map(Class repositoryInterface, CouchbaseOperations operations) { byRepository.put(repositoryInterface.getName(), operations); return this; } @@ -78,39 +65,51 @@ public class ReactiveRepositoryOperationsMapping { * @param operations the CouchbaseOperations to use. * @return the mapping, for chaining. */ - public ReactiveRepositoryOperationsMapping mapEntity(Class entityClass, RxJavaCouchbaseOperations operations) { + public ReactiveRepositoryOperationsMapping mapEntity(Class entityClass, CouchbaseOperations operations) { byEntity.put(entityClass.getName(), operations); return this; } /** - * @return the configured default {@link RxJavaCouchbaseOperations}. + * @return the configured default {@link CouchbaseOperations}. */ - public RxJavaCouchbaseOperations getDefault() { + public CouchbaseOperations getDefault() { return defaultOperations; } /** - * Get the {@link MappingContext} to use in repositories. It is extracted from the default {@link RxJavaCouchbaseOperations}. + * Change the default reactive couchbase operations in an existing mapping. * - * @return the mapping context. + * @param aDefault the new default couchbase operations. + * @return the mapping, for chaining. + */ + public ReactiveRepositoryOperationsMapping setDefault(CouchbaseOperations aDefault) { + Assert.notNull(aDefault); + this.defaultOperations = aDefault; + return this; + } + + /** + * Get the {@link MappingContext} to use in repositories. It is extracted from the default + * {@link CouchbaseOperations}. + * + * @return the mapping context. */ public MappingContext, CouchbasePersistentProperty> getMappingContext() { return defaultOperations.getConverter().getMappingContext(); } /** - * Given a repository interface and its domain type, resolves which {@link RxJavaCouchbaseOperations} it should be backed with. - * - * Starts by looking for a direct mapping to the interface, then a common mapping for the domain type, then falls back - * to the default CouchbaseOperations. + * Given a repository interface and its domain type, resolves which {@link CouchbaseOperations} it should be backed + * with. Starts by looking for a direct mapping to the interface, then a common mapping for the domain type, then + * falls back to the default CouchbaseOperations. * * @param repositoryInterface the repository's interface. * @param domainType the repository's domain type / entity. * @return the CouchbaseOperations to back the repository. */ - public RxJavaCouchbaseOperations resolve(Class repositoryInterface, Class domainType) { - RxJavaCouchbaseOperations result = byRepository.get(repositoryInterface.getName()); + public CouchbaseOperations resolve(Class repositoryInterface, Class domainType) { + CouchbaseOperations result = byRepository.get(repositoryInterface.getName()); if (result != null) { return result; } else { @@ -123,4 +122,3 @@ public class ReactiveRepositoryOperationsMapping { } } } - diff --git a/src/main/java/org/springframework/data/couchbase/repository/config/RepositoryOperationsMapping.java b/src/main/java/org/springframework/data/couchbase/repository/config/RepositoryOperationsMapping.java index ad40c70f..9a362525 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/config/RepositoryOperationsMapping.java +++ b/src/main/java/org/springframework/data/couchbase/repository/config/RepositoryOperationsMapping.java @@ -26,103 +26,103 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.util.Assert; /** - * A utility class for configuration allowing to tell which {@link CouchbaseOperations} should be backing - * repositories. Its fluent API allows to set that (in order of precedence) for specific repository interfaces, - * by repository domain type or as a default fallback. + * A utility class for configuration allowing to tell which {@link CouchbaseOperations} should be backing repositories. + * Its fluent API allows to set that (in order of precedence) for specific repository interfaces, by repository domain + * type or as a default fallback. * * @author Simon Baslé * @author Mark Paluch */ public class RepositoryOperationsMapping { - private CouchbaseOperations defaultOperations; - private Map byRepository = new HashMap(); - private Map byEntity = new HashMap(); + private CouchbaseOperations defaultOperations; + private Map byRepository = new HashMap<>(); + private Map byEntity = new HashMap<>(); + /** + * Creates a new mapping, setting the default fallback to use by otherwise non mapped repositories. + * + * @param defaultOperations the default fallback couchbase operations. + */ + public RepositoryOperationsMapping(CouchbaseOperations defaultOperations) { + Assert.notNull(defaultOperations, "CouchbaseOperations must not be null!"); + this.defaultOperations = defaultOperations; + } - /** - * Creates a new mapping, setting the default fallback to use by otherwise non mapped repositories. - * - * @param defaultOperations the default fallback couchbase operations. - */ - public RepositoryOperationsMapping(CouchbaseOperations defaultOperations) { - Assert.notNull(defaultOperations, "CouchbaseOperations must not be null!"); - this.defaultOperations = defaultOperations; - } + /** + * Add a highest priority mapping that will associate a specific repository interface with a given + * {@link CouchbaseOperations}. + * + * @param repositoryInterface the repository interface {@link Class}. + * @param couchbaseOperations the CouchbaseOperations to use. + * @return the mapping, for chaining. + */ + public RepositoryOperationsMapping map(Class repositoryInterface, CouchbaseOperations couchbaseOperations) { + byRepository.put(repositoryInterface.getName(), couchbaseOperations); + return this; + } - /** - * Change the default couchbase operations in an existing mapping. - * - * @param aDefault the new default couchbase operations. - * @return the mapping, for chaining. - */ - public RepositoryOperationsMapping setDefault(CouchbaseOperations aDefault) { - Assert.notNull(aDefault, "CouchbaseOperations must not be null!"); - this.defaultOperations = aDefault; - return this; - } + /** + * Add a middle priority mapping that will associate any un-mapped repository that deals with the given domain type + * Class with a given {@link CouchbaseOperations}. + * + * @param entityClass the domain type's {@link Class}. + * @param couchbaseOperations the CouchbaseOperations to use. + * @return the mapping, for chaining. + */ + public RepositoryOperationsMapping mapEntity(Class entityClass, CouchbaseOperations couchbaseOperations) { + byEntity.put(entityClass.getName(), couchbaseOperations); + return this; + } - /** - * Add a highest priority mapping that will associate a specific repository interface with a given {@link CouchbaseOperations}. - * - * @param repositoryInterface the repository interface {@link Class}. - * @param couchbaseOperations the CouchbaseOperations to use. - * @return the mapping, for chaining. - */ - public RepositoryOperationsMapping map(Class repositoryInterface, CouchbaseOperations couchbaseOperations) { - byRepository.put(repositoryInterface.getName(), couchbaseOperations); - return this; - } + /** + * @return the configured default {@link CouchbaseOperations}. + */ + public CouchbaseOperations getDefault() { + return defaultOperations; + } - /** - * Add a middle priority mapping that will associate any un-mapped repository that deals with the given domain type - * Class with a given {@link CouchbaseOperations}. - * - * @param entityClass the domain type's {@link Class}. - * @param couchbaseOperations the CouchbaseOperations to use. - * @return the mapping, for chaining. - */ - public RepositoryOperationsMapping mapEntity(Class entityClass, CouchbaseOperations couchbaseOperations) { - byEntity.put(entityClass.getName(), couchbaseOperations); - return this; - } + /** + * Change the default couchbase operations in an existing mapping. + * + * @param aDefault the new default couchbase operations. + * @return the mapping, for chaining. + */ + public RepositoryOperationsMapping setDefault(CouchbaseOperations aDefault) { + Assert.notNull(aDefault, "CouchbaseOperations must not be null!"); + this.defaultOperations = aDefault; + return this; + } - /** - * @return the configured default {@link CouchbaseOperations}. - */ - public CouchbaseOperations getDefault() { - return defaultOperations; - } + /** + * Get the {@link MappingContext} to use in repositories. It is extracted from the default + * {@link CouchbaseOperations}. + * + * @return the mapping context. + */ + public MappingContext, CouchbasePersistentProperty> getMappingContext() { + return defaultOperations.getConverter().getMappingContext(); + } - /** - * Get the {@link MappingContext} to use in repositories. It is extracted from the default {@link CouchbaseOperations}. - * - * @return the mapping context. - */ - public MappingContext, CouchbasePersistentProperty> getMappingContext() { - return defaultOperations.getConverter().getMappingContext(); - } - - /** - * Given a repository interface and its domain type, resolves which {@link CouchbaseOperations} it should be backed with. - * - * Starts by looking for a direct mapping to the interface, then a common mapping for the domain type, then falls back - * to the default CouchbaseOperations. - * - * @param repositoryInterface the repository's interface. - * @param domainType the repository's domain type / entity. - * @return the CouchbaseOperations to back the repository. - */ - public CouchbaseOperations resolve(Class repositoryInterface, Class domainType) { - CouchbaseOperations result = byRepository.get(repositoryInterface.getName()); - if (result != null) { - return result; - } else { - result = byEntity.get(domainType.getName()); - if (result != null) { - return result; - } else { - return defaultOperations; - } - } - } + /** + * Given a repository interface and its domain type, resolves which {@link CouchbaseOperations} it should be backed + * with. Starts by looking for a direct mapping to the interface, then a common mapping for the domain type, then + * falls back to the default CouchbaseOperations. + * + * @param repositoryInterface the repository's interface. + * @param domainType the repository's domain type / entity. + * @return the CouchbaseOperations to back the repository. + */ + public CouchbaseOperations resolve(Class repositoryInterface, Class domainType) { + CouchbaseOperations result = byRepository.get(repositoryInterface.getName()); + if (result != null) { + return result; + } else { + result = byEntity.get(domainType.getName()); + if (result != null) { + return result; + } else { + return defaultOperations; + } + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/package-info.java b/src/main/java/org/springframework/data/couchbase/repository/package-info.java index 35ccdbc7..af5a6b56 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/repository/package-info.java @@ -1,6 +1,5 @@ /** - * This package contains the Couchbase interfaces to support the Spring Data repository abstraction. - *
    + * This package contains the Couchbase interfaces to support the Spring Data repository abstraction.
    * The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to * implement data access layers for various persistence stores. */ diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQuery.java index d270e1e7..e36288f8 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQuery.java @@ -16,23 +16,14 @@ package org.springframework.data.couchbase.repository.query; -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import java.util.Collection; import java.util.List; -import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.domain.PageImpl; +import org.springframework.data.couchbase.core.query.N1QLExpression; +import org.springframework.data.couchbase.core.query.N1QLQuery; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.SliceImpl; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryMethod; @@ -40,11 +31,16 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.util.StreamUtils; -import org.springframework.util.Assert; + +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.json.JsonValue; +import com.couchbase.client.java.query.QueryOptions; +import com.couchbase.client.java.query.QueryScanConsistency; /** - * Abstract base for all Couchbase {@link RepositoryQuery}. It is in charge of inspecting the parameters - * and choosing the correct {@link N1qlQuery} implementation to use. + * Abstract base for all Couchbase {@link RepositoryQuery}. It is in charge of inspecting the parameters and choosing + * the correct {@link N1QLQuery} implementation to use. * * @author Simon Baslé * @author Subhashni Balakrishnan @@ -53,182 +49,196 @@ import org.springframework.util.Assert; */ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery { - private static final Logger LOG = LoggerFactory.getLogger(AbstractN1qlBasedQuery.class); + private static final Logger LOG = LoggerFactory.getLogger(AbstractN1qlBasedQuery.class); - protected final CouchbaseQueryMethod queryMethod; - private final CouchbaseOperations couchbaseOperations; + protected final CouchbaseQueryMethod queryMethod; + private final CouchbaseOperations couchbaseOperations; - protected AbstractN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) { - this.queryMethod = queryMethod; - this.couchbaseOperations = couchbaseOperations; - } + protected AbstractN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) { + this.queryMethod = queryMethod; + this.couchbaseOperations = couchbaseOperations; + } - /** - * The statement for a count() query. This must aggregate using count with the alias {@link CountFragment#COUNT_ALIAS}. - * - * @see CountFragment - */ - protected abstract Statement getCount(ParameterAccessor accessor, Object[] runtimeParameters); + protected static N1QLQuery buildQuery(N1QLExpression expression, JsonValue queryPlaceholderValues, + QueryScanConsistency scanConsistency) { + QueryOptions opts = QueryOptions.queryOptions().scanConsistency(scanConsistency); - /** - * @return true if the {@link #getCount(ParameterAccessor, Object[]) count statement} should also be used when - * the return type of the QueryMethod is a primitive type. - */ - protected abstract boolean useGeneratedCountQuery(); + if (queryPlaceholderValues instanceof JsonObject && !((JsonObject) queryPlaceholderValues).isEmpty()) { + opts.parameters((JsonObject) queryPlaceholderValues); + } else if (queryPlaceholderValues instanceof JsonArray && !((JsonArray) queryPlaceholderValues).isEmpty()) { + opts.parameters((JsonArray) queryPlaceholderValues); + } - protected abstract Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType); + return new N1QLQuery(expression, opts); + } - protected abstract JsonValue getPlaceholderValues(ParameterAccessor accessor); + /** + * The statement for a count() query. This must aggregate using count with the alias + * {@link CountFragment#COUNT_ALIAS}. + * + * @see CountFragment + */ + protected abstract N1QLExpression getCount(ParameterAccessor accessor, Object[] runtimeParameters); - protected ScanConsistency getScanConsistency() { + /** + * @return true if the {@link #getCount(ParameterAccessor, Object[]) count statement} should also be used when the + * return type of the QueryMethod is a primitive type. + */ + protected abstract boolean useGeneratedCountQuery(); - if (queryMethod.hasConsistencyAnnotation()) { - return queryMethod.getConsistencyAnnotation().value(); - } + protected abstract N1QLExpression getExpression(ParameterAccessor accessor, Object[] runtimeParameters, + ReturnedType returnedType); - return getCouchbaseOperations().getDefaultConsistency().n1qlConsistency(); - } + protected abstract JsonValue getPlaceholderValues(ParameterAccessor accessor); - @Override - public Object execute(Object[] parameters) { - ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters); + protected QueryScanConsistency getScanConsistency() { + return QueryScanConsistency.REQUEST_PLUS; + /* + if (queryMethod.hasConsistencyAnnotation()) { + return queryMethod.getConsistencyAnnotation().value(); + } - ResultProcessor processor = this.queryMethod.getResultProcessor().withDynamicProjection(accessor); - ReturnedType returnedType = processor.getReturnedType(); + return getCouchbaseOperations().getDefaultConsistency().n1qlConsistency();*/ + } - Class typeToRead = returnedType.getTypeToRead(); - typeToRead = typeToRead == null ? returnedType.getDomainType() : typeToRead; + @Override + public Object execute(Object[] parameters) { + ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters); + ResultProcessor processor = this.queryMethod.getResultProcessor().withDynamicProjection(accessor); + ReturnedType returnedType = processor.getReturnedType(); - Statement statement = getStatement(accessor, parameters, returnedType); - JsonValue queryPlaceholderValues = getPlaceholderValues(accessor); + // TODO: review this - I just hacked it to work, basically... + // This was what was here in sdk2, but seem to end up being always Object. Forcing + // it to be the same as the object type for the repo. + // Class typeToRead = returnedType.getTypeToRead(); + // typeToRead = typeToRead == null ? returnedType.getDomainType() : typeToRead; - //prepare the final query - N1qlQuery query = buildQuery(statement, queryPlaceholderValues, getScanConsistency()); + Class typeToRead = queryMethod.getEntityInformation().getJavaType(); - //prepare a count query - Statement countStatement = getCount(accessor, parameters); - //the place holder values are the same for the count query as well - N1qlQuery countQuery = buildQuery(countStatement, queryPlaceholderValues, getScanConsistency()); - return processor.processResult(executeDependingOnType(query, countQuery, queryMethod, accessor.getPageable(), typeToRead)); - } + N1QLExpression statement = getExpression(accessor, parameters, returnedType); + JsonValue queryPlaceholderValues = getPlaceholderValues(accessor); - protected static N1qlQuery buildQuery(Statement statement, JsonValue queryPlaceholderValues, ScanConsistency scanConsistency) { - N1qlParams n1qlParams = N1qlParams.build().consistency(scanConsistency); - N1qlQuery query; + // prepare the final query + N1QLQuery query = buildQuery(statement, queryPlaceholderValues, getScanConsistency()); - if (queryPlaceholderValues instanceof JsonObject && !((JsonObject) queryPlaceholderValues).isEmpty()) { - query = N1qlQuery.parameterized(statement, (JsonObject) queryPlaceholderValues, n1qlParams); - } else if (queryPlaceholderValues instanceof JsonArray && !((JsonArray) queryPlaceholderValues).isEmpty()) { - query = N1qlQuery.parameterized(statement, (JsonArray) queryPlaceholderValues, n1qlParams); - } else { - query = N1qlQuery.simple(statement, n1qlParams); - } - return query; - } + // prepare a count query + N1QLExpression countStatement = getCount(accessor, parameters); + // the place holder values are the same for the count query as well + N1QLQuery countQuery = buildQuery(countStatement, queryPlaceholderValues, getScanConsistency()); + return processor + .processResult(executeDependingOnType(query, countQuery, queryMethod, accessor.getPageable(), typeToRead)); + } - protected Object executeDependingOnType(N1qlQuery query, N1qlQuery countQuery, QueryMethod queryMethod, - Pageable pageable, Class typeToRead) { + protected Object executeDependingOnType(N1QLQuery query, N1QLQuery countQuery, QueryMethod queryMethod, + Pageable pageable, Class typeToRead) { - if (queryMethod.isPageQuery()) { - return executePaged(query, countQuery, pageable, typeToRead); - } else if (queryMethod.isSliceQuery()) { - return executeSliced(query, countQuery, pageable, typeToRead); - } else if (queryMethod.isCollectionQuery()) { - return executeCollection(query, typeToRead); - } else if (queryMethod.isStreamQuery()){ - return executeStream(query, typeToRead); - } else if (queryMethod.isQueryForEntity()) { - return executeEntity(query, typeToRead); - } else if (queryMethod.getReturnedObjectType().isPrimitive() - && useGeneratedCountQuery()) { - //attempt to execute the created COUNT query - return executeSingleProjection(countQuery); - } else { - //attempt a single projection on a simple type - // (ie, a single row with a single k->v entry where v is the desired value) - return executeSingleProjection(query); - } - //more complex projections could be added in the future, like DTO direct mapping with a SELECT a,b,c FROM something - } + if (queryMethod.isPageQuery()) { + return executePaged(query, countQuery, pageable, typeToRead); + } else if (queryMethod.isSliceQuery()) { + return executeSliced(query, countQuery, pageable, typeToRead); + } else if (queryMethod.isCollectionQuery()) { + return executeCollection(query, typeToRead); + } else if (queryMethod.isStreamQuery()) { + return executeStream(query, typeToRead); + } else if (queryMethod.isQueryForEntity()) { + return executeEntity(query, typeToRead); + } else if (queryMethod.getReturnedObjectType().isPrimitive() && useGeneratedCountQuery()) { + // attempt to execute the created COUNT query + return executeSingleProjection(countQuery); + } else { + // attempt a single projection on a simple type + // (ie, a single row with a single k->v entry where v is the desired value) + return executeSingleProjection(query); + } + // more complex projections could be added in the future, like DTO direct mapping with a SELECT a,b,c FROM something + } - private void logIfNecessary(N1qlQuery query) { - if (LOG.isDebugEnabled()) { - LOG.debug("Executing N1QL query: " + query.n1ql()); - } - } + private void logIfNecessary(N1QLQuery query) { + if (LOG.isDebugEnabled()) { + LOG.debug("Executing N1QL query: " + query.n1ql()); + } + } - protected List executeCollection(N1qlQuery query, Class typeToRead) { - logIfNecessary(query); - List result = couchbaseOperations.findByN1QL(query, typeToRead); - return result; - } + protected List executeCollection(N1QLQuery query, Class typeToRead) { + throw new UnsupportedOperationException("TODO"); - protected Object executeEntity(N1qlQuery query, Class typeToRead) { - logIfNecessary(query); - List result = executeCollection(query, typeToRead); - return result.isEmpty() ? null : result.get(0); - } + /* logIfNecessary(query); + List result = couchbaseOperations.findByN1QL(query, typeToRead); + return result;*/ + } - protected Object executeStream(N1qlQuery query, Class typeToRead) { - logIfNecessary(query); - return StreamUtils.createStreamFromIterator(executeCollection(query, typeToRead).iterator()); - } + protected Object executeEntity(N1QLQuery query, Class typeToRead) { + logIfNecessary(query); + List result = executeCollection(query, typeToRead); + return result.isEmpty() ? null : result.get(0); + } - protected Object executePaged(N1qlQuery query, N1qlQuery countQuery, Pageable pageable, Class typeToRead) { - Assert.notNull(pageable, "Pageable must not be null!"); + protected Object executeStream(N1QLQuery query, Class typeToRead) { + logIfNecessary(query); + return StreamUtils.createStreamFromIterator(executeCollection(query, typeToRead).iterator()); + } - long total = 0L; - logIfNecessary(countQuery); - List countResult = couchbaseOperations.findByN1QLProjection(countQuery, CountFragment.class); - if (countResult != null && !countResult.isEmpty()) { - total = countResult.get(0).count; - } + protected Object executePaged(N1QLQuery query, N1QLQuery countQuery, Pageable pageable, Class typeToRead) { + throw new UnsupportedOperationException("TODO"); + /* + Assert.notNull(pageable, "Pageable must not be null!"); + + long total = 0L; + logIfNecessary(countQuery); + List countResult = couchbaseOperations.findByN1QLProjection(countQuery, CountFragment.class); + if (countResult != null && !countResult.isEmpty()) { + total = countResult.get(0).count; + } + + logIfNecessary(query); + List result = couchbaseOperations.findByN1QL(query, typeToRead); + return new PageImpl(result, pageable, total);*/ + } - logIfNecessary(query); - List result = couchbaseOperations.findByN1QL(query, typeToRead); - return new PageImpl(result, pageable, total); - } + protected Object executeSliced(N1QLQuery query, N1QLQuery countQuery, Pageable pageable, Class typeToRead) { + throw new UnsupportedOperationException("TODO"); - protected Object executeSliced(N1qlQuery query, N1qlQuery countQuery, Pageable pageable, Class typeToRead) { - Assert.notNull(pageable, "Pageable must not be null!"); - logIfNecessary(query); - List result = couchbaseOperations.findByN1QL(query, typeToRead); - int pageSize = pageable.getPageSize(); - boolean hasNext = result.size() > pageSize; + /* Assert.notNull(pageable, "Pageable must not be null!"); + logIfNecessary(query); + List result = couchbaseOperations.findByN1QL(query, typeToRead); + int pageSize = pageable.getPageSize(); + boolean hasNext = result.size() > pageSize; + + return new SliceImpl(hasNext ? result.subList(0, pageSize) : result, pageable, hasNext);*/ + } - return new SliceImpl(hasNext ? result.subList(0, pageSize) : result, pageable, hasNext); - } + protected Object executeSingleProjection(N1QLQuery query) { + throw new UnsupportedOperationException("TODO"); - protected Object executeSingleProjection(N1qlQuery query) { - logIfNecessary(query); - //the structure of the response from N1QL gives us a JSON object even when selecting a single aggregation - List resultAsMap = couchbaseOperations.findByN1QLProjection(query, Map.class); + /* logIfNecessary(query); + //the structure of the response from N1QL gives us a JSON object even when selecting a single aggregation + List resultAsMap = couchbaseOperations.findByN1QLProjection(query, Map.class); + + if (resultAsMap.size() != 1) { + throw new CouchbaseQueryExecutionException("Query returning a primitive type are expected to return " + + "exactly 1 result, got " + resultAsMap.size()); + } + + Map singleRow = (Map) resultAsMap.get(0); + if (singleRow.size() != 1) { + throw new CouchbaseQueryExecutionException("Query returning a simple type are expected to return " + + "a unique value, got " + singleRow.size()); + } + Collection rowValues = singleRow.values(); + if (rowValues.size() != 1) { + throw new CouchbaseQueryExecutionException("Query returning a simple type are expected to use a " + + "single aggregation/projection, got " + rowValues.size()); + } + + return rowValues.iterator().next();*/ + } - if (resultAsMap.size() != 1) { - throw new CouchbaseQueryExecutionException("Query returning a primitive type are expected to return " + - "exactly 1 result, got " + resultAsMap.size()); - } + @Override + public CouchbaseQueryMethod getQueryMethod() { + return this.queryMethod; + } - Map singleRow = (Map) resultAsMap.get(0); - if (singleRow.size() != 1) { - throw new CouchbaseQueryExecutionException("Query returning a simple type are expected to return " + - "a unique value, got " + singleRow.size()); - } - Collection rowValues = singleRow.values(); - if (rowValues.size() != 1) { - throw new CouchbaseQueryExecutionException("Query returning a simple type are expected to use a " + - "single aggregation/projection, got " + rowValues.size()); - } - - return rowValues.iterator().next(); - } - - @Override - public CouchbaseQueryMethod getQueryMethod() { - return this.queryMethod; - } - - protected CouchbaseOperations getCouchbaseOperations() { - return this.couchbaseOperations; - } + protected CouchbaseOperations getCouchbaseOperations() { + return this.couchbaseOperations; + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ConvertingIterator.java b/src/main/java/org/springframework/data/couchbase/repository/query/ConvertingIterator.java index 4624811d..78bcf1ac 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ConvertingIterator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ConvertingIterator.java @@ -28,27 +28,27 @@ import org.springframework.data.couchbase.core.convert.CouchbaseConverter; * @author Subhashni Balakrishnan */ public class ConvertingIterator implements Iterator { - private final Iterator delegate; - private final CouchbaseConverter converter; + private final Iterator delegate; + private final CouchbaseConverter converter; - public ConvertingIterator(Iterator delegate, CouchbaseConverter converter) { - this.delegate = delegate; - this.converter = converter; - } + public ConvertingIterator(Iterator delegate, CouchbaseConverter converter) { + this.delegate = delegate; + this.converter = converter; + } - @Override - public boolean hasNext() { - return delegate.hasNext(); - } + @Override + public boolean hasNext() { + return delegate.hasNext(); + } - @Override - public void remove() { - delegate.remove(); - } + @Override + public void remove() { + delegate.remove(); + } - @Override - public Object next() { - Object next = delegate.next(); - return converter.convertForWriteIfNeeded(next); - } + @Override + public Object next() { + Object next = delegate.next(); + return converter.convertForWriteIfNeeded(next); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java index 9f40da92..734a711d 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseQueryMethod.java @@ -22,9 +22,9 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; import org.springframework.data.couchbase.core.query.Dimensional; -import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.core.query.View; import org.springframework.data.couchbase.core.query.WithConsistency; +import org.springframework.data.couchbase.repository.Query; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; @@ -32,8 +32,8 @@ import org.springframework.data.repository.query.QueryMethod; import org.springframework.util.StringUtils; /** - * Represents a query method with couchbase extensions, allowing to discover - * if View-based query or N1QL-based query must be used. + * Represents a query method with couchbase extensions, allowing to discover if View-based query or N1QL-based query + * must be used. * * @author Michael Nitschinger * @author Simon Baslé @@ -41,131 +41,130 @@ import org.springframework.util.StringUtils; */ public class CouchbaseQueryMethod extends QueryMethod { - private final Method method; + private final Method method; - public CouchbaseQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory, - MappingContext, CouchbasePersistentProperty> mappingContext) { - super(method, metadata, factory); + public CouchbaseQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory, + MappingContext, CouchbasePersistentProperty> mappingContext) { + super(method, metadata, factory); - this.method = method; - } + this.method = method; + } - /** - * If the method has a @View annotation. - * - * @return true if it has the annotation, false otherwise. - */ - public boolean hasViewAnnotation() { - return getViewAnnotation() != null; - } + /** + * If the method has a @View annotation. + * + * @return true if it has the annotation, false otherwise. + */ + public boolean hasViewAnnotation() { + return getViewAnnotation() != null; + } - /** - * If the method has a @View annotation with the designDocument and viewName specified. - * - * @return true if it has the annotation and full view specified. - */ - public boolean hasViewSpecification() { - return hasDesignDoc() && hasViewName(); - } + /** + * If the method has a @View annotation with the designDocument and viewName specified. + * + * @return true if it has the annotation and full view specified. + */ + public boolean hasViewSpecification() { + return hasDesignDoc() && hasViewName(); + } - /** - * If the method has a @View annotation with the designDocument specified. - * - * @return true if it has the design document specified. - */ - public boolean hasDesignDoc() { - View annotation = getViewAnnotation(); - if (annotation == null) { - return false; - } - return StringUtils.hasText(annotation.designDocument()); - } + /** + * If the method has a @View annotation with the designDocument specified. + * + * @return true if it has the design document specified. + */ + public boolean hasDesignDoc() { + View annotation = getViewAnnotation(); + if (annotation == null) { + return false; + } + return StringUtils.hasText(annotation.designDocument()); + } - /** - * If the method has a @View annotation with the viewName specified. - * - * @return true if it has the view name specified. - */ - public boolean hasViewName() { - View annotation = getViewAnnotation(); - if (annotation == null) { - return false; - } - return StringUtils.hasText(annotation.viewName()); - } + /** + * If the method has a @View annotation with the viewName specified. + * + * @return true if it has the view name specified. + */ + public boolean hasViewName() { + View annotation = getViewAnnotation(); + if (annotation == null) { + return false; + } + return StringUtils.hasText(annotation.viewName()); + } - /** - * Returns the @View annotation if set, null otherwise. - * - * @return the view annotation of present. - */ - public View getViewAnnotation() { - return method.getAnnotation(View.class); - } + /** + * Returns the @View annotation if set, null otherwise. + * + * @return the view annotation of present. + */ + public View getViewAnnotation() { + return method.getAnnotation(View.class); + } + /** + * @return true if the method has a @Dimensional annotation, false otherwise. + */ + public boolean hasDimensionalAnnotation() { + return getDimensionalAnnotation() != null; + } - /** - * @return true if the method has a @Dimensional annotation, false otherwise. - */ - public boolean hasDimensionalAnnotation() { - return getDimensionalAnnotation() != null; - } + /** + * @return the @Dimensional annotation if set, null otherwise. + */ + public Dimensional getDimensionalAnnotation() { + return AnnotationUtils.findAnnotation(method, Dimensional.class); + } - /** - * @return the @Dimensional annotation if set, null otherwise. - */ - public Dimensional getDimensionalAnnotation() { - return AnnotationUtils.findAnnotation(method, Dimensional.class); - } + /** + * If the method has a @Query annotation. + * + * @return true if it has the annotation, false otherwise. + */ + public boolean hasN1qlAnnotation() { + return getN1qlAnnotation() != null; + } - /** - * If the method has a @Query annotation. - * - * @return true if it has the annotation, false otherwise. - */ - public boolean hasN1qlAnnotation() { - return getN1qlAnnotation() != null; - } + /** + * Returns the @Query annotation if set, null otherwise. + * + * @return the n1ql annotation if present. + */ + public Query getN1qlAnnotation() { + return method.getAnnotation(Query.class); + } - /** - * Returns the @Query annotation if set, null otherwise. - * - * @return the n1ql annotation if present. - */ - public Query getN1qlAnnotation() { - return method.getAnnotation(Query.class); - } + /** + * If the method has a @Query annotation with an inline Query statement inside. + * + * @return true if this has the annotation and N1QL inline statement, false otherwise. + */ + public boolean hasInlineN1qlQuery() { + return getInlineN1qlQuery() != null; + } - /** - * If the method has a @Query annotation with an inline Query statement inside. - * - * @return true if this has the annotation and N1QL inline statement, false otherwise. - */ - public boolean hasInlineN1qlQuery() { - return getInlineN1qlQuery() != null; - } + public boolean hasConsistencyAnnotation() { + return getConsistencyAnnotation() != null; + } - public boolean hasConsistencyAnnotation() { - return getConsistencyAnnotation() != null; - } + public WithConsistency getConsistencyAnnotation() { + return method.getAnnotation(WithConsistency.class); + } - public WithConsistency getConsistencyAnnotation() { - return method.getAnnotation(WithConsistency.class); - } + /** + * Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation + * found nor the attribute was specified. + * + * @return the query statement if present. + */ + public String getInlineN1qlQuery() { + String query = (String) AnnotationUtils.getValue(getN1qlAnnotation()); + return StringUtils.hasText(query) ? query : null; + } - /** - * Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found - * nor the attribute was specified. - * - * @return the query statement if present. - */ - public String getInlineN1qlQuery() { - String query = (String) AnnotationUtils.getValue(getN1qlAnnotation()); - return StringUtils.hasText(query) ? query : null; - } - - @Override - public String toString() { - return super.toString(); - } + @Override + public String toString() { + return super.toString(); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuery.java new file mode 100644 index 00000000..48c2a5c0 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CouchbaseRepositoryQuery.java @@ -0,0 +1,27 @@ +package org.springframework.data.couchbase.repository.query; + +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; + +public class CouchbaseRepositoryQuery implements RepositoryQuery { + + private final CouchbaseOperations operations; + private final QueryMethod queryMethod; + + public CouchbaseRepositoryQuery(final CouchbaseOperations operations, final QueryMethod queryMethod) { + this.operations = operations; + this.queryMethod = queryMethod; + } + + @Override + public Object execute(final Object[] parameters) { + return new N1qlRepositoryQueryExecutor(operations, queryMethod).execute(parameters); + } + + @Override + public QueryMethod getQueryMethod() { + return queryMethod; + } + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/CountFragment.java b/src/main/java/org/springframework/data/couchbase/repository/query/CountFragment.java index 45819195..918192c9 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/CountFragment.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/CountFragment.java @@ -21,20 +21,20 @@ package org.springframework.data.couchbase.repository.query; *

    * The query should use the COUNT_ALIAS, eg.: SELECT COUNT(*) AS count FROM default; *

    - * This ensures that the framework will be able to map the JSON result to this {@link CountFragment} class - * so that it can be used. + * This ensures that the framework will be able to map the JSON result to this {@link CountFragment} class so that it + * can be used. */ public class CountFragment { - /** - * Use this alias for the COUNT part of a N1QL query so that the framework can extract the count result. - * Eg.: "SELECT A.COUNT(*) AS " + COUNT_ALIAS + " FROM A"; - */ - public static final String COUNT_ALIAS = "count"; + /** + * Use this alias for the COUNT part of a N1QL query so that the framework can extract the count result. Eg.: "SELECT + * A.COUNT(*) AS " + COUNT_ALIAS + " FROM A"; + */ + public static final String COUNT_ALIAS = "count"; - /** - * The value for a COUNT that used {@link #COUNT_ALIAS} as an alias. - */ - public long count; + /** + * The value for a COUNT that used {@link #COUNT_ALIAS} as an alias. + */ + public long count; } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlCountQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlCountQueryCreator.java index 83d2f6c7..d78bd340 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlCountQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlCountQueryCreator.java @@ -19,29 +19,25 @@ import java.util.Iterator; import java.util.Optional; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.parser.PartTree; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.path.LimitPath; -import com.couchbase.client.java.query.dsl.path.WherePath; - /** - * * @author Mark Ramach * @author Mark Paluch */ -public class N1qlCountQueryCreator extends N1qlQueryCreator { +public class N1qlCountQueryCreator extends OldN1qlQueryCreator { - public N1qlCountQueryCreator(PartTree tree, ParameterAccessor parameters, WherePath selectFrom, + public N1qlCountQueryCreator(PartTree tree, ParameterAccessor parameters, N1QLExpression selectFrom, CouchbaseConverter converter, CouchbaseQueryMethod queryMethod) { super(tree, new CountParameterAccessor(parameters), selectFrom, converter, queryMethod); } @Override - protected LimitPath complete(Expression criteria, Sort sort) { + protected N1QLExpression complete(N1QLExpression criteria, Sort sort) { // Sorting is not allowed on aggregate count queries. return super.complete(criteria, Sort.unsorted()); } @@ -106,7 +102,7 @@ public class N1qlCountQueryCreator extends N1qlQueryCreator { } public Sort getSort() { - // Sorting is not allowed on aggregate count queries. + // Sorting is not allowed on aggregate count queries. return Sort.unsorted(); } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java index 79aa8001..2df68ade 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlMutateQueryCreator.java @@ -19,11 +19,8 @@ package org.springframework.data.couchbase.repository.query; import java.util.Iterator; import java.util.concurrent.atomic.AtomicInteger; -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.path.*; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.couchbase.repository.query.support.N1qlQueryCreatorUtils; import org.springframework.data.couchbase.repository.query.support.N1qlUtils; import org.springframework.data.domain.Sort; @@ -32,59 +29,64 @@ import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.PartTree; +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonValue; + /** - * N1qlMutateQueryCreator allows to create queries for delete operations. - * - * See {@link N1qlQueryCreator} for part types supported + * N1qlMutateQueryCreator allows to create queries for delete operations. See {@link OldN1qlQueryCreator} for part types + * supported * * @author Subhashni Balakrishnan */ -public class N1qlMutateQueryCreator extends AbstractQueryCreator implements PartTreeN1qlQueryCreator { - private final MutateWherePath mutateFrom; - private final CouchbaseConverter converter; - private final CouchbaseQueryMethod queryMethod; - private final ParameterAccessor accessor; - private final JsonArray placeHolderValues; - private final AtomicInteger position; +public class N1qlMutateQueryCreator extends AbstractQueryCreator + implements PartTreeN1qlQueryCreator { + private final N1QLExpression mutateFrom; + private final CouchbaseConverter converter; + private final CouchbaseQueryMethod queryMethod; + private final ParameterAccessor accessor; + private final JsonArray placeHolderValues; + private final AtomicInteger position; - public N1qlMutateQueryCreator(PartTree tree, ParameterAccessor parameters, MutateWherePath mutateFrom, - CouchbaseConverter converter, CouchbaseQueryMethod queryMethod) { - super(tree, parameters); - this.mutateFrom = mutateFrom; - this.converter = converter; - this.queryMethod = queryMethod; - this.accessor = parameters; - this.placeHolderValues = JsonArray.create(); - this.position = new AtomicInteger(1); - } + public N1qlMutateQueryCreator(PartTree tree, ParameterAccessor parameters, N1QLExpression mutateFrom, + CouchbaseConverter converter, CouchbaseQueryMethod queryMethod) { + super(tree, parameters); + this.mutateFrom = mutateFrom; + this.converter = converter; + this.queryMethod = queryMethod; + this.accessor = parameters; + this.placeHolderValues = JsonArray.create(); + this.position = new AtomicInteger(1); + } - @Override - protected Expression create(Part part, Iterator iterator) { - return N1qlQueryCreatorUtils.prepareExpression(this.converter, part, iterator, this.position, this.placeHolderValues); - } + @Override + protected N1QLExpression create(Part part, Iterator iterator) { + return N1qlQueryCreatorUtils.prepareExpression(this.converter, part, iterator, this.position, + this.placeHolderValues); + } - @Override - protected Expression and(Part part, Expression base, Iterator iterator) { - if (base == null) { - return create(part, iterator); - } + @Override + protected N1QLExpression and(Part part, N1QLExpression base, Iterator iterator) { + if (base == null) { + return create(part, iterator); + } - return base.and(create(part, iterator)); - } + return base.and(create(part, iterator)); + } - @Override - protected Expression or(Expression base, Expression criteria) { - return base.or(criteria); - } + @Override + protected N1QLExpression or(N1QLExpression base, N1QLExpression criteria) { + return base.or(criteria); + } - @Override - protected MutateLimitPath complete(Expression criteria, Sort sort) { - Expression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter, this.queryMethod.getEntityInformation()); - return mutateFrom.where(whereCriteria); - } + @Override + protected N1QLExpression complete(N1QLExpression criteria, Sort sort) { + N1QLExpression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter, + this.queryMethod.getEntityInformation()); + return mutateFrom.where(whereCriteria); + } - @Override - public JsonValue getPlaceHolderValues() { - return this.placeHolderValues; - } + @Override + public JsonValue getPlaceHolderValues() { + return this.placeHolderValues; + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java index 0f8630d9..53cfca1f 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java @@ -1,149 +1,80 @@ -/* - * 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.repository.query; -import java.util.Iterator; -import java.util.concurrent.atomic.AtomicInteger; - -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.path.LimitPath; -import com.couchbase.client.java.query.dsl.path.OrderByPath; -import com.couchbase.client.java.query.dsl.path.WherePath; - -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.repository.query.support.N1qlQueryCreatorUtils; -import org.springframework.data.couchbase.repository.query.support.N1qlUtils; -import org.springframework.data.domain.Pageable; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.couchbase.core.query.QueryCriteria; +import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.PersistentPropertyPath; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.PartTree; -/** - * This {@link AbstractQueryCreator Query Creator} is responsible for parsing a {@link PartTree} (representing - * a method name) into the WHERE clause of a N1QL query. - *

    - * In the following, "field" represents the path in JSON deduced from the part of the method name. "a" and "b" represent - * the values of next consumed method parameters. "array" represent a {@link JsonArray} constructed from the next method - * parameter value (if a collection or array, contained values are used to fill the array, otherwise it's a single item - * array). - *
    - * Here are the {@link Part.Type} supported (field: - *

      - *
    • BETWEEN: field BETWEEN a AND b
    • - *
    • IS_NOT_NULL: field IS NOT NULL
    • - *
    • IS_NULL: field IS NULL
    • - *
    • NEGATING_SIMPLE_PROPERTY: - field != a
    • - *
    • SIMPLE_PROPERTY: - field = a
    • - *
    • LESS_THAN: field < a
    • - *
    • LESS_THAN_EQUAL: field <= a
    • - *
    • GREATER_THAN_EQUAL: field >= a
    • - *
    • GREATER_THAN: field > a
    • - *
    • BEFORE: field < a
    • - *
    • AFTER: field > a
    • - *
    • NOT_LIKE: field NOT LIKE "a" - a should be a String containing % and _ (matching n and 1 characters)
    • - *
    • LIKE: field LIKE "a" - a should be a String containing % and _ (matching n and 1 characters)
    • - *
    • STARTING_WITH: field LIKE "a%" - a should be a String prefix
    • - *
    • ENDING_WITH: field LIKE "%a" - a should be a String suffix
    • - *
    • NOT_CONTAINING: field NOT LIKE "%a%" - a should be a String
    • - *
    • CONTAINING: field LIKE "%a%" - a should be a String
    • - *
    • NOT_IN: field NOT IN array - note that the next parameter value (or its children if a collection/array) - * should be compatible for storage in a {@link JsonArray})
    • - *
    • IN: field IN array - note that the next parameter value (or its children if a collection/array) should - * be compatible for storage in a {@link JsonArray})
    • - *
    • TRUE: field = TRUE
    • - *
    • FALSE: field = FALSE
    • - *
    • REGEX: REGEXP_LIKE(field, "a") - note that the ignoreCase is ignored here, a is a regular expression - * in String form
    • - *
    • EXISTS: field IS NOT MISSING - used to verify that the JSON contains this attribute
    • - *
    - *
    - * The following are not supported and will throw an {@link IllegalArgumentException} if encountered: - *
      - *
    • NEAR, WITHIN: geospatial is not supported in N1QL as of now
    • - *
    - *

    - * - * @author Simon Baslé - * @author Subhashni Balakrishnan - * @author Mark Paluch - */ -public class N1qlQueryCreator extends AbstractQueryCreator implements PartTreeN1qlQueryCreator { - private final WherePath selectFrom; - private final CouchbaseConverter converter; - private final CouchbaseQueryMethod queryMethod; - private final ParameterAccessor accessor; - private final JsonArray placeHolderValues; - private final AtomicInteger position; +import java.util.Iterator; - public N1qlQueryCreator(PartTree tree, ParameterAccessor parameters, WherePath selectFrom, - CouchbaseConverter converter, CouchbaseQueryMethod queryMethod) { - super(tree, parameters); - this.selectFrom = selectFrom; - this.converter = converter; - this.queryMethod = queryMethod; - this.accessor = parameters; - this.placeHolderValues = JsonArray.create(); - this.position = new AtomicInteger(1); - } +import static org.springframework.data.couchbase.core.query.QueryCriteria.*; - @Override - protected Expression create(Part part, Iterator iterator) { - return N1qlQueryCreatorUtils.prepareExpression(converter, part, iterator, this.position, this.placeHolderValues); - } +public class N1qlQueryCreator extends AbstractQueryCreator { - @Override - protected Expression and(Part part, Expression base, Iterator iterator) { - if (base == null) { - return create(part, iterator); - } + private final ParameterAccessor accessor; + private final MappingContext context; - return base.and(create(part, iterator)); - } + public N1qlQueryCreator(final PartTree tree, final ParameterAccessor accessor, + final MappingContext context) { + super(tree, accessor); + this.accessor = accessor; + this.context = context; + } - @Override - protected Expression or(Expression base, Expression criteria) { - return base.or(criteria); - } + @Override + protected QueryCriteria create(final Part part, final Iterator iterator) { + PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); + CouchbasePersistentProperty property = path.getLeafProperty(); + return from(part, property, where(path.toDotPath()), iterator); + } - @Override - protected LimitPath complete(Expression criteria, Sort sort) { - Expression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter, this.queryMethod.getEntityInformation()); + @Override + protected QueryCriteria and(final Part part, final QueryCriteria base, final Iterator iterator) { + if (base == null) { + return create(part, iterator); + } - OrderByPath selectFromWhere = selectFrom.where(whereCriteria); + PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty()); + CouchbasePersistentProperty property = path.getLeafProperty(); - //sort of the Pageable takes precedence over the sort in the query name - if ((queryMethod.isPageQuery() || queryMethod.isSliceQuery()) && accessor.getPageable().isPaged()) { - Pageable pageable = accessor.getPageable(); - sort = pageable.getSort(); - } + return from(part, property, base.and(path.toDotPath()), iterator); + } - if (sort.isSorted()) { - com.couchbase.client.java.query.dsl.Sort[] cbSorts = N1qlUtils.createSort(sort, converter); - return selectFromWhere.orderBy(cbSorts); - } - return selectFromWhere; - } + @Override + protected QueryCriteria or(QueryCriteria base, QueryCriteria criteria) { + return base.or(criteria); + } + + @Override + protected Query complete(QueryCriteria criteria, Sort sort) { + return (criteria == null ? new Query() : new Query().addCriteria(criteria)).with(sort); + } + + private QueryCriteria from(final Part part, final CouchbasePersistentProperty property, final QueryCriteria criteria, + final Iterator parameters) { + + final Part.Type type = part.getType(); + + switch (type) { + case GREATER_THAN: + return criteria.gt(parameters.next()); + case GREATER_THAN_EQUAL: + return criteria.gte(parameters.next()); + case LESS_THAN: + return criteria.lt(parameters.next()); + case LESS_THAN_EQUAL: + return criteria.lte(parameters.next()); + case SIMPLE_PROPERTY: + return criteria.eq(parameters.next()); + default: + throw new IllegalArgumentException("Unsupported keyword!"); + } + } - @Override - public JsonValue getPlaceHolderValues() { - return this.placeHolderValues; - } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/N1qlRepositoryQueryExecutor.java b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlRepositoryQueryExecutor.java new file mode 100644 index 00000000..1bffa9bd --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/N1qlRepositoryQueryExecutor.java @@ -0,0 +1,32 @@ +package org.springframework.data.couchbase.repository.query; + +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.parser.PartTree; + +import java.util.List; + +public class N1qlRepositoryQueryExecutor { + + private final CouchbaseOperations operations; + private final QueryMethod queryMethod; + + public N1qlRepositoryQueryExecutor(final CouchbaseOperations operations, final QueryMethod queryMethod) { + this.operations = operations; + this.queryMethod = queryMethod; + } + + public Object execute(final Object[] parameters) { + final Class domainClass = queryMethod.getResultProcessor().getReturnedType().getDomainType(); + final ParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters); + + final PartTree tree = new PartTree(queryMethod.getName(), domainClass); + Query query = new N1qlQueryCreator(tree, accessor, operations.getConverter().getMappingContext()).createQuery(); + + List all = operations.findByQuery(domainClass).matching(query).all(); + return all; + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/OldN1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/OldN1qlQueryCreator.java new file mode 100644 index 00000000..73102b06 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/query/OldN1qlQueryCreator.java @@ -0,0 +1,147 @@ +/* + * Copyright 2012-2019 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.repository.query; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.query.N1QLExpression; +import org.springframework.data.couchbase.repository.query.support.N1qlQueryCreatorUtils; +import org.springframework.data.couchbase.repository.query.support.N1qlUtils; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.PartTree; + +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonValue; + +/** + * This {@link AbstractQueryCreator Query Creator} is responsible for parsing a {@link PartTree} (representing a method + * name) into the WHERE clause of a N1QL query. + *

    + * In the following, "field" represents the path in JSON deduced from the part of the method name. "a" and "b" represent + * the values of next consumed method parameters. "array" represent a {@link JsonArray} constructed from the next method + * parameter value (if a collection or array, contained values are used to fill the array, otherwise it's a single item + * array).
    + * Here are the {@link Part.Type} supported (field: + *

      + *
    • BETWEEN: field BETWEEN a AND b
    • + *
    • IS_NOT_NULL: field IS NOT NULL
    • + *
    • IS_NULL: field IS NULL
    • + *
    • NEGATING_SIMPLE_PROPERTY: - field != a
    • + *
    • SIMPLE_PROPERTY: - field = a
    • + *
    • LESS_THAN: field < a
    • + *
    • LESS_THAN_EQUAL: field <= a
    • + *
    • GREATER_THAN_EQUAL: field >= a
    • + *
    • GREATER_THAN: field > a
    • + *
    • BEFORE: field < a
    • + *
    • AFTER: field > a
    • + *
    • NOT_LIKE: field NOT LIKE "a" - a should be a String containing % and _ (matching n and 1 characters)
    • + *
    • LIKE: field LIKE "a" - a should be a String containing % and _ (matching n and 1 characters)
    • + *
    • STARTING_WITH: field LIKE "a%" - a should be a String prefix
    • + *
    • ENDING_WITH: field LIKE "%a" - a should be a String suffix
    • + *
    • NOT_CONTAINING: field NOT LIKE "%a%" - a should be a String
    • + *
    • CONTAINING: field LIKE "%a%" - a should be a String
    • + *
    • NOT_IN: field NOT IN array - note that the next parameter value (or its children if a collection/array) + * should be compatible for storage in a {@link JsonArray})
    • + *
    • IN: field IN array - note that the next parameter value (or its children if a collection/array) should be + * compatible for storage in a {@link JsonArray})
    • + *
    • TRUE: field = TRUE
    • + *
    • FALSE: field = FALSE
    • + *
    • REGEX: REGEXP_LIKE(field, "a") - note that the ignoreCase is ignored here, a is a regular expression in + * String form
    • + *
    • EXISTS: field IS NOT MISSING - used to verify that the JSON contains this attribute
    • + *
    + *
    + * The following are not supported and will throw an {@link IllegalArgumentException} if encountered: + *
      + *
    • NEAR, WITHIN: geospatial is not supported in N1QL as of now
    • + *
    + *

    + * + * @author Simon Baslé + * @author Subhashni Balakrishnan + * @author Mark Paluch + */ +public class OldN1qlQueryCreator extends AbstractQueryCreator + implements PartTreeN1qlQueryCreator { + private final N1QLExpression selectFrom; + private final CouchbaseConverter converter; + private final CouchbaseQueryMethod queryMethod; + private final ParameterAccessor accessor; + private final JsonArray placeHolderValues; + private final AtomicInteger position; + + public OldN1qlQueryCreator(PartTree tree, ParameterAccessor parameters, N1QLExpression selectFrom, + CouchbaseConverter converter, CouchbaseQueryMethod queryMethod) { + super(tree, parameters); + this.selectFrom = selectFrom; + this.converter = converter; + this.queryMethod = queryMethod; + this.accessor = parameters; + this.placeHolderValues = JsonArray.create(); + this.position = new AtomicInteger(1); + } + + @Override + protected N1QLExpression create(Part part, Iterator iterator) { + return N1qlQueryCreatorUtils.prepareExpression(converter, part, iterator, this.position, this.placeHolderValues); + } + + @Override + protected N1QLExpression and(Part part, N1QLExpression base, Iterator iterator) { + if (base == null) { + return create(part, iterator); + } + + return base.and(create(part, iterator)); + } + + @Override + protected N1QLExpression or(N1QLExpression base, N1QLExpression criteria) { + return base.or(criteria); + } + + @Override + protected N1QLExpression complete(N1QLExpression criteria, Sort sort) { + N1QLExpression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter, + this.queryMethod.getEntityInformation()); + + N1QLExpression selectFromWhere = selectFrom.where(whereCriteria); + + // sort of the Pageable takes precedence over the sort in the query name + if ((queryMethod.isPageQuery() || queryMethod.isSliceQuery()) && accessor.getPageable().isPaged()) { + Pageable pageable = accessor.getPageable(); + sort = pageable.getSort(); + } + + if (sort.isSorted()) { + N1QLExpression[] cbSorts = N1qlUtils.createSort(sort); + return selectFromWhere.orderBy(cbSorts); + } + return selectFromWhere; + } + + @Override + public JsonValue getPlaceHolderValues() { + return this.placeHolderValues; + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java index 77d8fe8d..e3b524cd 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java @@ -16,22 +16,11 @@ package org.springframework.data.couchbase.repository.query; -import static com.couchbase.client.java.query.Delete.deleteFrom; -import static com.couchbase.client.java.query.Select.select; -import static com.couchbase.client.java.query.dsl.Expression.i; -import static com.couchbase.client.java.query.dsl.functions.AggregateFunctions.count; -import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.createReturningExpressionForDelete; -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.path.FromPath; -import com.couchbase.client.java.query.dsl.path.LimitPath; -import com.couchbase.client.java.query.dsl.path.WherePath; -import com.couchbase.client.java.query.dsl.path.MutateLimitPath; -import com.couchbase.client.java.query.dsl.path.DeleteUsePath; +import static org.springframework.data.couchbase.core.query.N1QLExpression.*; +import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.*; + import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.couchbase.repository.query.support.N1qlUtils; import org.springframework.data.domain.Pageable; import org.springframework.data.repository.query.ParameterAccessor; @@ -40,6 +29,8 @@ import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.util.Assert; +import com.couchbase.client.java.json.JsonValue; + /** * A {@link RepositoryQuery} for Couchbase, based on query derivation * @@ -49,83 +40,81 @@ import org.springframework.util.Assert; */ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery { - private final PartTree partTree; - private ThreadLocal placeHolderValues; + private final PartTree partTree; + private JsonValue placeHolderValues; - public PartTreeN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) { - super(queryMethod, couchbaseOperations); - this.partTree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType()); - this.placeHolderValues = new ThreadLocal() { - @Override public JsonValue initialValue() { - return JsonArray.create(); - } - }; - } + public PartTreeN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) { + super(queryMethod, couchbaseOperations); + this.partTree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType()); + } - @Override - protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { - return this.placeHolderValues.get(); - } + @Override + protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { + return this.placeHolderValues; + } - @Override - protected Statement getCount(ParameterAccessor accessor, Object[] runtimeParameters) { - Expression bucket = i(getCouchbaseOperations().getCouchbaseBucket().name()); - WherePath countFrom = select(count("*").as(CountFragment.COUNT_ALIAS)).from(bucket); + @Override + protected N1QLExpression getCount(ParameterAccessor accessor, Object[] runtimeParameters) { + N1QLExpression bucket = i(getCouchbaseOperations().getBucketName()); + N1QLExpression countFrom = select(count(x("*")).as(x(CountFragment.COUNT_ALIAS))).from(bucket); - N1qlCountQueryCreator queryCountCreator = new N1qlCountQueryCreator(partTree, accessor, countFrom, - getCouchbaseOperations().getConverter(), getQueryMethod()); - Statement statement = queryCountCreator.createQuery(); - this.placeHolderValues.set(queryCountCreator.getPlaceHolderValues()); - return statement; - } + N1qlCountQueryCreator queryCountCreator = new N1qlCountQueryCreator(partTree, accessor, countFrom, + getCouchbaseOperations().getConverter(), getQueryMethod()); + N1QLExpression statement = queryCountCreator.createQuery(); + this.placeHolderValues = queryCountCreator.getPlaceHolderValues(); + return statement; + } - @Override - protected Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) { - String bucketName = getCouchbaseOperations().getCouchbaseBucket().name(); - Expression bucket = N1qlUtils.escapedBucket(bucketName); + @Override + protected N1QLExpression getExpression(ParameterAccessor accessor, Object[] runtimeParameters, + ReturnedType returnedType) { + String bucketName = getCouchbaseOperations().getBucketName(); + N1QLExpression bucket = N1qlUtils.escapedBucket(bucketName); - if (partTree.isDelete()) { - DeleteUsePath deleteUsePath = deleteFrom(bucket); - N1qlMutateQueryCreator mutateQueryCreator = new N1qlMutateQueryCreator(partTree, accessor, deleteUsePath, getCouchbaseOperations().getConverter(), getQueryMethod()); - MutateLimitPath mutateFromWhereOrderBy = mutateQueryCreator.createQuery(); - this.placeHolderValues.set(mutateQueryCreator.getPlaceHolderValues()); + if (partTree.isDelete()) { + N1QLExpression deleteUsePath = delete().from(bucket); + N1qlMutateQueryCreator mutateQueryCreator = new N1qlMutateQueryCreator(partTree, accessor, deleteUsePath, + getCouchbaseOperations().getConverter(), getQueryMethod()); + N1QLExpression mutateFromWhereOrderBy = mutateQueryCreator.createQuery(); + this.placeHolderValues = mutateQueryCreator.getPlaceHolderValues(); - if (partTree.isLimiting()) { - return mutateFromWhereOrderBy.limit(partTree.getMaxResults()); - } else { - return mutateFromWhereOrderBy.returning(createReturningExpressionForDelete(bucketName)); - } - } else { - FromPath select; - if (partTree.isCountProjection()) { - select = select(count("*")); - } else { - select = N1qlUtils.createSelectClauseForEntity(bucketName, returnedType, this.getCouchbaseOperations().getConverter()); - } - WherePath selectFrom = select.from(bucket); - N1qlQueryCreator queryCreator = new N1qlQueryCreator(partTree, accessor, selectFrom, - getCouchbaseOperations().getConverter(), getQueryMethod()); - LimitPath selectFromWhereOrderBy = queryCreator.createQuery(); - this.placeHolderValues.set(queryCreator.getPlaceHolderValues()); + if (partTree.isLimiting()) { + return mutateFromWhereOrderBy.limit(partTree.getMaxResults()); + } else { + return mutateFromWhereOrderBy.returning(createReturningExpressionForDelete(bucketName)); + } + } else { + N1QLExpression select; + if (partTree.isCountProjection()) { + select = select(count(x("*"))); + } else { + select = N1qlUtils.createSelectClauseForEntity(bucketName, returnedType, + this.getCouchbaseOperations().getConverter()); + } + N1QLExpression selectFrom = select.from(bucket); + OldN1qlQueryCreator queryCreator = new OldN1qlQueryCreator(partTree, accessor, selectFrom, + getCouchbaseOperations().getConverter(), getQueryMethod()); + N1QLExpression selectFromWhereOrderBy = queryCreator.createQuery(); + this.placeHolderValues = queryCreator.getPlaceHolderValues(); - if (queryMethod.isPageQuery()) { - Pageable pageable = accessor.getPageable(); - Assert.notNull(pageable, "Pageable must not be null!"); - return selectFromWhereOrderBy.limit(pageable.getPageSize()).offset(Math.toIntExact(pageable.getOffset())); - } else if (queryMethod.isSliceQuery() && accessor.getPageable().isPaged()) { - Pageable pageable = accessor.getPageable(); - Assert.notNull(pageable, "Pageable must not be null!"); - return selectFromWhereOrderBy.limit(pageable.getPageSize() + 1).offset(Math.toIntExact(pageable.getOffset())); - } else if (partTree.isLimiting()) { - return selectFromWhereOrderBy.limit(partTree.getMaxResults()); - } else { - return selectFromWhereOrderBy; - } - } - } + if (queryMethod.isPageQuery()) { + Pageable pageable = accessor.getPageable(); + Assert.notNull(pageable, "Pageable must not be null!"); + return selectFromWhereOrderBy.limit(pageable.getPageSize()).offset(Math.toIntExact(pageable.getOffset())); + } else if (queryMethod.isSliceQuery() && accessor.getPageable().isPaged()) { + Pageable pageable = accessor.getPageable(); + Assert.notNull(pageable, "Pageable must not be null!"); + return selectFromWhereOrderBy.limit(pageable.getPageSize() + 1).offset(Math.toIntExact(pageable.getOffset())); + } else if (partTree.isLimiting()) { + return selectFromWhereOrderBy.limit(partTree.getMaxResults()); + } else { + return selectFromWhereOrderBy; + } + } + } - @Override - protected boolean useGeneratedCountQuery() { - return false; //generated count query is just for Page/Slice, not projections - } + @Override + protected boolean useGeneratedCountQuery() { + return false; // generated count query is just for Page/Slice, not projections + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlQueryCreator.java index 02e9bd2a..6a5337db 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlQueryCreator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlQueryCreator.java @@ -16,7 +16,7 @@ package org.springframework.data.couchbase.repository.query; -import com.couchbase.client.java.document.json.JsonValue; +import com.couchbase.client.java.json.JsonValue; /** * A Part Tree Query creator for Couchbase @@ -25,6 +25,6 @@ import com.couchbase.client.java.document.json.JsonValue; */ public interface PartTreeN1qlQueryCreator { - /** Get the named placeholder values */ - JsonValue getPlaceHolderValues(); + /** Get the named placeholder values */ + JsonValue getPlaceHolderValues(); } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveAbstractN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveAbstractN1qlBasedQuery.java index 19f015d2..3dc5e589 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveAbstractN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveAbstractN1qlBasedQuery.java @@ -15,19 +15,23 @@ */ package org.springframework.data.couchbase.repository.query; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import java.util.Map; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.Statement; +import reactor.core.publisher.Flux; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.query.N1QLExpression; +import org.springframework.data.couchbase.core.query.N1QLQuery; import org.springframework.data.couchbase.repository.query.support.N1qlUtils; -import org.springframework.data.repository.query.*; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.util.ReactiveWrapperConverters; -import reactor.core.publisher.Flux; + +import com.couchbase.client.java.json.JsonValue; +import com.couchbase.client.java.query.QueryScanConsistency; /** * @author Subhashni Balakrishnan @@ -36,93 +40,97 @@ import reactor.core.publisher.Flux; * @since 3.0 */ public abstract class ReactiveAbstractN1qlBasedQuery implements RepositoryQuery { - private static final Logger LOG = LoggerFactory.getLogger(ReactiveAbstractN1qlBasedQuery.class); + private static final Logger LOG = LoggerFactory.getLogger(ReactiveAbstractN1qlBasedQuery.class); - protected final CouchbaseQueryMethod queryMethod; - private final RxJavaCouchbaseOperations couchbaseOperations; + protected final CouchbaseQueryMethod queryMethod; + private final CouchbaseOperations couchbaseOperations; - protected ReactiveAbstractN1qlBasedQuery(CouchbaseQueryMethod method, RxJavaCouchbaseOperations operations) { - this.queryMethod = method; - this.couchbaseOperations = operations; - } + protected ReactiveAbstractN1qlBasedQuery(CouchbaseQueryMethod method, CouchbaseOperations operations) { + this.queryMethod = method; + this.couchbaseOperations = operations; + } - protected abstract Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType); + protected abstract N1QLExpression getExpression(ParameterAccessor accessor, Object[] runtimeParameters, + ReturnedType returnedType); - protected abstract JsonValue getPlaceholderValues(ParameterAccessor accessor); + protected abstract JsonValue getPlaceholderValues(ParameterAccessor accessor); - @Override - public Object execute(Object[] parameters) { - ReactiveCouchbaseParameterAccessor accessor = new ReactiveCouchbaseParameterAccessor(queryMethod, parameters); - ResultProcessor processor = this.queryMethod.getResultProcessor().withDynamicProjection(accessor); - ReturnedType returnedType = processor.getReturnedType(); + @Override + public Object execute(Object[] parameters) { + ReactiveCouchbaseParameterAccessor accessor = new ReactiveCouchbaseParameterAccessor(queryMethod, parameters); + ResultProcessor processor = this.queryMethod.getResultProcessor().withDynamicProjection(accessor); + ReturnedType returnedType = processor.getReturnedType(); - Class typeToRead = returnedType.getTypeToRead(); - typeToRead = typeToRead == null ? returnedType.getDomainType() : typeToRead; + Class typeToRead = returnedType.getTypeToRead(); + typeToRead = typeToRead == null ? returnedType.getDomainType() : typeToRead; - Statement statement = getStatement(accessor, parameters, returnedType); - JsonValue queryPlaceholderValues = getPlaceholderValues(accessor); + N1QLExpression expression = getExpression(accessor, parameters, returnedType); + JsonValue queryPlaceholderValues = getPlaceholderValues(accessor); - //prepare the final query - N1qlQuery query = N1qlUtils.buildQuery(statement, queryPlaceholderValues, getScanConsistency()); - return ReactiveWrapperConverters.toWrapper( - processor.processResult(executeDependingOnType(query, queryMethod, typeToRead)), Flux.class); - } + // prepare the final query + N1QLQuery query = N1qlUtils.buildQuery(expression, queryPlaceholderValues, getScanConsistency()); + return ReactiveWrapperConverters + .toWrapper(processor.processResult(executeDependingOnType(query, queryMethod, typeToRead)), Flux.class); + } + protected Object executeDependingOnType(N1QLQuery query, QueryMethod queryMethod, Class typeToRead) { - protected Object executeDependingOnType(N1qlQuery query, - QueryMethod queryMethod, - Class typeToRead) { + if (queryMethod.isModifyingQuery()) { + throw new UnsupportedOperationException("Modifying queries not yet supported"); + } - if (queryMethod.isModifyingQuery()) { - throw new UnsupportedOperationException("Modifying queries not yet supported"); - } + if (queryMethod.isQueryForEntity()) { + return execute(query, typeToRead); + } else { + return executeSingleProjection(query, typeToRead); + } + } - if (queryMethod.isQueryForEntity()) { - return execute(query, typeToRead); - } else { - return executeSingleProjection(query, typeToRead); - } - } + private void logIfNecessary(N1QLQuery query) { + if (LOG.isDebugEnabled()) { + LOG.debug("Executing N1QL query: " + query.n1ql()); + } + } - private void logIfNecessary(N1qlQuery query) { - if (LOG.isDebugEnabled()) { - LOG.debug("Executing N1QL query: " + query.n1ql()); - } - } + protected Object execute(N1QLQuery query, Class typeToRead) { + throw new UnsupportedOperationException(); + /* + logIfNecessary(query); + return couchbaseOperations.findByN1QL(query, typeToRead);*/ + } - protected Object execute(N1qlQuery query, Class typeToRead) { - logIfNecessary(query); - return couchbaseOperations.findByN1QL(query, typeToRead); - } + protected Object executeSingleProjection(N1QLQuery query, final Class typeToRead) { + throw new UnsupportedOperationException(); - protected Object executeSingleProjection(N1qlQuery query, final Class typeToRead) { - logIfNecessary(query); - return couchbaseOperations.findByN1QLProjection(query, Map.class) - .map(m -> { - if (m.size() > 1) { - throw new CouchbaseQueryExecutionException("Query returning primitive got more values than expected: " - + m.size()); - } - Object v = m.values().iterator().next(); - return this.couchbaseOperations.getConverter().getConversionService().convert(v, typeToRead); - }); - } + /* logIfNecessary(query); + return couchbaseOperations.findByN1QLProjection(query, Map.class) + .map(m -> { + if (m.size() > 1) { + throw new CouchbaseQueryExecutionException("Query returning primitive got more values than expected: " + + m.size()); + } + Object v = m.values().iterator().next(); + return this.couchbaseOperations.getConverter().getConversionService().convert(v, typeToRead); + });*/ + } - @Override - public CouchbaseQueryMethod getQueryMethod() { - return this.queryMethod; - } + @Override + public CouchbaseQueryMethod getQueryMethod() { + return this.queryMethod; + } - protected RxJavaCouchbaseOperations getCouchbaseOperations() { - return this.couchbaseOperations; - } + protected CouchbaseOperations getCouchbaseOperations() { + return this.couchbaseOperations; + } - protected ScanConsistency getScanConsistency() { + protected QueryScanConsistency getScanConsistency() { + throw new UnsupportedOperationException(); - if (queryMethod.hasConsistencyAnnotation()) { - return queryMethod.getConsistencyAnnotation().value(); - } - - return getCouchbaseOperations().getDefaultConsistency().n1qlConsistency(); - } + /* + if (queryMethod.hasConsistencyAnnotation()) { + return queryMethod.getConsistencyAnnotation().value(); + } + + return getCouchbaseOperations().getDefaultConsistency().n1qlConsistency();*/ + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseParameterAccessor.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseParameterAccessor.java index e67badae..c5c98a3c 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseParameterAccessor.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveCouchbaseParameterAccessor.java @@ -15,6 +15,9 @@ */ package org.springframework.data.couchbase.repository.query; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; import java.util.ArrayList; import java.util.List; @@ -22,9 +25,6 @@ import java.util.List; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.repository.util.ReactiveWrappers; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoProcessor; /** * Reactive {@link org.springframework.data.repository.query.ParametersParameterAccessor} implementation that subscribes diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java index d642d5a1..a952f4bc 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactivePartTreeN1qlBasedQuery.java @@ -15,24 +15,18 @@ */ package org.springframework.data.couchbase.repository.query; -import static com.couchbase.client.java.query.Select.select; -import static com.couchbase.client.java.query.dsl.functions.AggregateFunctions.count; +import static org.springframework.data.couchbase.core.query.N1QLExpression.*; -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.path.FromPath; -import com.couchbase.client.java.query.dsl.path.LimitPath; -import com.couchbase.client.java.query.dsl.path.WherePath; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.couchbase.repository.query.support.N1qlUtils; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.PartTree; +import com.couchbase.client.java.json.JsonValue; + /** * A reactive {@link RepositoryQuery} for Couchbase, based on query derivation * @@ -41,40 +35,42 @@ import org.springframework.data.repository.query.parser.PartTree; */ public class ReactivePartTreeN1qlBasedQuery extends ReactiveAbstractN1qlBasedQuery { - private final PartTree partTree; - private JsonValue placeHolderValues; + private final PartTree partTree; + private JsonValue placeHolderValues; - public ReactivePartTreeN1qlBasedQuery(CouchbaseQueryMethod queryMethod, RxJavaCouchbaseOperations operations) { - super(queryMethod, operations); - this.partTree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType()); - } + public ReactivePartTreeN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations operations) { + super(queryMethod, operations); + this.partTree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType()); + } - @Override - protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { - return this.placeHolderValues; - } + @Override + protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { + return this.placeHolderValues; + } - @Override - protected Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) { - String bucketName = getCouchbaseOperations().getCouchbaseBucket().name(); - Expression bucket = N1qlUtils.escapedBucket(bucketName); + @Override + protected N1QLExpression getExpression(ParameterAccessor accessor, Object[] runtimeParameters, + ReturnedType returnedType) { + String bucketName = getCouchbaseOperations().getBucketName(); + N1QLExpression bucket = N1qlUtils.escapedBucket(bucketName); - FromPath select; - if (partTree.isCountProjection()) { - select = select(count("*")); - } else { - select = N1qlUtils.createSelectClauseForEntity(bucketName, returnedType, this.getCouchbaseOperations().getConverter()); - } - WherePath selectFrom = select.from(bucket); + N1QLExpression select; + if (partTree.isCountProjection()) { + select = select(count(x("*"))); + } else { + select = N1qlUtils.createSelectClauseForEntity(bucketName, returnedType, + this.getCouchbaseOperations().getConverter()); + } + N1QLExpression selectFrom = select.from(bucket); - N1qlQueryCreator queryCreator = new N1qlQueryCreator(partTree, accessor, selectFrom, - getCouchbaseOperations().getConverter(), getQueryMethod()); - LimitPath selectFromWhereOrderBy = queryCreator.createQuery(); - this.placeHolderValues = queryCreator.getPlaceHolderValues(); - if (partTree.isLimiting()) { - return selectFromWhereOrderBy.limit(partTree.getMaxResults()); - } else { - return selectFromWhereOrderBy; - } - } + OldN1qlQueryCreator queryCreator = new OldN1qlQueryCreator(partTree, accessor, selectFrom, + getCouchbaseOperations().getConverter(), getQueryMethod()); + N1QLExpression selectFromWhereOrderBy = queryCreator.createQuery(); + this.placeHolderValues = queryCreator.getPlaceHolderValues(); + if (partTree.isLimiting()) { + return selectFromWhereOrderBy.limit(partTree.getMaxResults()); + } else { + return selectFromWhereOrderBy; + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveSpatialViewBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveSpatialViewBasedQuery.java deleted file mode 100644 index 42a874ba..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveSpatialViewBasedQuery.java +++ /dev/null @@ -1,79 +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.repository.query; - -import com.couchbase.client.java.view.SpatialViewQuery; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.data.repository.util.ReactiveWrapperConverters; -import reactor.core.publisher.Flux; - -/** - * A reactive {@link RepositoryQuery} for Couchbase, for spatial queries - * - * @author Subhashni Balakrishnan - * @since 3.0 - */ -public class ReactiveSpatialViewBasedQuery implements RepositoryQuery { - private static final Logger LOG = LoggerFactory.getLogger(ReactiveSpatialViewBasedQuery.class); - - private final CouchbaseQueryMethod method; - private final RxJavaCouchbaseOperations operations; - - public ReactiveSpatialViewBasedQuery(CouchbaseQueryMethod method, RxJavaCouchbaseOperations operations) { - this.method = method; - this.operations = operations; - } - - @Override - public Object execute(Object[] runtimeParams) { - String designDoc = method.getDimensionalAnnotation().designDocument(); - String viewName = method.getDimensionalAnnotation().spatialViewName(); - int dimensions = method.getDimensionalAnnotation().dimensions(); - PartTree tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType()); - - //prepare a spatial view query to be used as a base for the query creator - SpatialViewQuery baseSpatialQuery = SpatialViewQuery.from(designDoc, viewName) - .stale(operations.getDefaultConsistency().viewConsistency()); - - //use the SpatialViewQueryCreator to complete it - SpatialViewQueryCreator creator = new SpatialViewQueryCreator(dimensions, - tree, new ReactiveCouchbaseParameterAccessor(method, runtimeParams), - baseSpatialQuery, operations.getConverter()); - SpatialViewQueryCreator.SpatialViewQueryWrapper finalQuery = creator.createQuery(); - - //execute the spatial query - return execute(finalQuery); - } - - protected Object execute(SpatialViewQueryCreator.SpatialViewQueryWrapper query) { - if (LOG.isDebugEnabled()) { - LOG.debug("Executing spatial view query: " + query.getQuery().toString()); - } - - //TODO: eliminate false positives in geo query - return ReactiveWrapperConverters.toWrapper(operations.findBySpatialView(query.getQuery(), - method.getEntityInformation().getJavaType()), Flux.class); - } - - @Override - public CouchbaseQueryMethod getQueryMethod() { - return method; - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringN1qlBasedQuery.java index 52d06cf7..8e2ceb11 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveStringN1qlBasedQuery.java @@ -15,7 +15,10 @@ */ package org.springframework.data.couchbase.repository.query; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; +import static org.springframework.data.couchbase.core.query.N1QLExpression.*; + +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; @@ -23,16 +26,13 @@ import org.springframework.data.repository.query.ReturnedType; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.Statement; - +import com.couchbase.client.java.json.JsonValue; /** * A reactive StringN1qlBasedQuery {@link RepositoryQuery} for Couchbase, based on N1QL and a String statement. *

    - * The statement can contain positional placeholders (eg. name = $1) that will map to the - * method's parameters, in the same order. + * The statement can contain positional placeholders (eg. name = $1) that will map to the method's + * parameters, in the same order. *

    * The statement can also contain SpEL expressions enclosed in #{ and }. *

    @@ -42,41 +42,40 @@ import com.couchbase.client.java.query.Statement; */ public class ReactiveStringN1qlBasedQuery extends ReactiveAbstractN1qlBasedQuery { - private final StringBasedN1qlQueryParser queryParser; - private final SpelExpressionParser parser; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; + private final StringBasedN1qlQueryParser queryParser; + private final SpelExpressionParser parser; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; - protected String getTypeField() { - return getCouchbaseOperations().getConverter().getTypeKey(); - } + public ReactiveStringN1qlBasedQuery(String statement, CouchbaseQueryMethod queryMethod, + CouchbaseOperations couchbaseOperations, SpelExpressionParser spelParser, + QueryMethodEvaluationContextProvider evaluationContextProvider) { + super(queryMethod, couchbaseOperations); - protected Class getTypeValue() { - return getQueryMethod().getEntityInformation().getJavaType(); - } + this.queryParser = new StringBasedN1qlQueryParser(statement, queryMethod, getCouchbaseOperations().getBucketName(), + getCouchbaseOperations().getConverter(), getTypeField(), getTypeValue()); + this.parser = spelParser; + this.evaluationContextProvider = evaluationContextProvider; + } - public ReactiveStringN1qlBasedQuery(String statement, - CouchbaseQueryMethod queryMethod, - RxJavaCouchbaseOperations couchbaseOperations, - SpelExpressionParser spelParser, - QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(queryMethod, couchbaseOperations); + protected String getTypeField() { + return getCouchbaseOperations().getConverter().getTypeKey(); + } - this.queryParser = new StringBasedN1qlQueryParser(statement, queryMethod, - getCouchbaseOperations().getCouchbaseBucket().name(), getCouchbaseOperations().getConverter(), getTypeField(), getTypeValue()); - this.parser = spelParser; - this.evaluationContextProvider = evaluationContextProvider; - } + protected Class getTypeValue() { + return getQueryMethod().getEntityInformation().getJavaType(); + } - @Override - protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { - return this.queryParser.getPlaceholderValues(accessor); - } + @Override + protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { + return this.queryParser.getPlaceholderValues(accessor); + } - @Override - public Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) { - EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), runtimeParameters); - String parsedStatement = queryParser.doParse(parser, evaluationContext, false); - return N1qlQuery.simple(parsedStatement).statement(); - } + @Override + public N1QLExpression getExpression(ParameterAccessor accessor, Object[] runtimeParameters, + ReturnedType returnedType) { + EvaluationContext evaluationContext = evaluationContextProvider + .getEvaluationContext(getQueryMethod().getParameters(), runtimeParameters); + return x(queryParser.doParse(parser, evaluationContext, false)); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveViewBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveViewBasedCouchbaseQuery.java deleted file mode 100644 index e89e7c30..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ReactiveViewBasedCouchbaseQuery.java +++ /dev/null @@ -1,158 +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.repository.query; - -import com.couchbase.client.java.view.AsyncViewRow; -import com.couchbase.client.java.view.ViewQuery; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.mapping.PropertyReferenceException; -import org.springframework.data.repository.query.QueryMethod; -import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.data.repository.util.ReactiveWrapperConverters; -import org.springframework.util.StringUtils; -import reactor.core.publisher.Flux; -import rx.Observable; - -/** - * Execute a reactive repository query through the View mechanism. - * - * @author Subhashni Balakrishnan - * @since 3.0 - */ -public class ReactiveViewBasedCouchbaseQuery implements RepositoryQuery { - - private static final Logger LOG = LoggerFactory.getLogger(ReactiveViewBasedCouchbaseQuery.class); - - private final CouchbaseQueryMethod method; - private final RxJavaCouchbaseOperations operations; - - public ReactiveViewBasedCouchbaseQuery(CouchbaseQueryMethod method, RxJavaCouchbaseOperations operations) { - this.method = method; - this.operations = operations; - } - - @Override - public Object execute(Object[] runtimeParams) { - if (method.hasViewName()) { //only allow derivation on @View explicitly defining a viewName - return deriveAndExecute(runtimeParams); - } else { - return guessViewAndExecute(); - } - } - - protected Object guessViewAndExecute() { - String designDoc = designDocName(method); - String methodName = method.getName(); - boolean isExplicitReduce = method.hasViewAnnotation() && method.getViewAnnotation().reduce(); - boolean isReduce = methodName.startsWith("count") || isExplicitReduce; - String viewName = StringUtils.uncapitalize(methodName.replaceFirst("find|count", "")); - - ViewQuery simpleQuery = ViewQuery.from(designDoc, viewName) - .stale(operations.getDefaultConsistency().viewConsistency()); - if (isReduce) { - simpleQuery.reduce(); - return executeReduce(simpleQuery, designDoc, viewName); - } else { - return execute(simpleQuery); - } - } - - protected Object deriveAndExecute(Object[] runtimeParams) { - String designDoc = designDocName(method); - String viewName = method.getViewAnnotation().viewName(); - - //prepare a ViewQuery to be used as a base for the ViewQueryCreator - ViewQuery baseQuery = ViewQuery.from(designDoc, viewName) - .stale(operations.getDefaultConsistency().viewConsistency()); - - try { - PartTree tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType()); - - //use a ViewQueryCreator to complete the base query - ViewQueryCreator creator = new ViewQueryCreator(tree, new ReactiveCouchbaseParameterAccessor(method, runtimeParams), - method.getViewAnnotation(), baseQuery, operations.getConverter()); - ViewQueryCreator.DerivedViewQuery result = creator.createQuery(); - - if (result.isReduce) { - return executeReduce(result.builtQuery, designDoc, viewName); - } else { - //otherwise just execute the query - return execute(result.builtQuery); - } - } catch (PropertyReferenceException e) { - /* - For views, not including an attribute name in the method will result in returning - the whole set of results from the view. - This is detected by looking for PropertyReferenceExceptions that seem to complain - about a missing property that corresponds to the method name - */ - if (e.getPropertyName().equals(method.getName())) { - return execute(baseQuery); - } - throw e; - } - } - - protected Object execute(ViewQuery query) { - if (LOG.isDebugEnabled()) { - LOG.debug("Executing view query: " + query.toString()); - } - return ReactiveWrapperConverters.toWrapper(operations.findByView(query, method.getEntityInformation().getJavaType()), - Flux.class); - } - - protected Object executeReduce(ViewQuery query, String designDoc, String viewName) { - if (LOG.isDebugEnabled()) { - LOG.debug("Executing view reduced query: " + query.toString()); - } - return ReactiveWrapperConverters.toWrapper(operations.queryView(query) - .flatMap(asyncViewResult -> asyncViewResult.error() - .flatMap(error -> Observable.error(new CouchbaseQueryExecutionException("Unable to execute reducing view " - + viewName +" in design document " + designDoc + - "due to error:" + error.toString()))) - .switchIfEmpty(asyncViewResult.rows())) - .map(row -> { - AsyncViewRow asyncViewRow = (AsyncViewRow) row; - return asyncViewRow.value(); - }).take(1), Flux.class); - } - - @Override - public QueryMethod getQueryMethod() { - return method; - } - - /** - * Returns the best-guess design document name. - * - * @return the design document name. - */ - private static String designDocName(CouchbaseQueryMethod method) { - if (method.hasViewSpecification()) { - return method.getViewAnnotation().designDocument(); - } else if (method.hasViewAnnotation()) { - return StringUtils.uncapitalize(method.getEntityInformation().getJavaType().getSimpleName()); - } else { - throw new IllegalStateException("View-based query should only happen on a method with @View annotation"); - } - } - -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewBasedQuery.java deleted file mode 100644 index e05ac1a3..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewBasedQuery.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2013-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.repository.query; - -import com.couchbase.client.java.view.SpatialViewQuery; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.query.Dimensional; -import org.springframework.data.repository.query.ParametersParameterAccessor; -import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.query.parser.PartTree; - -/** - * Execute a {@link Dimensional} repository query through the Spatial View mechanism. - * - * @author Simon Baslé - */ -public class SpatialViewBasedQuery implements RepositoryQuery { - - private static final Logger LOG = LoggerFactory.getLogger(SpatialViewBasedQuery.class); - - private final CouchbaseQueryMethod method; - private final CouchbaseOperations operations; - - public SpatialViewBasedQuery(CouchbaseQueryMethod method, CouchbaseOperations operations) { - this.method = method; - this.operations = operations; - } - - @Override - public Object execute(Object[] runtimeParams) { - String designDoc = method.getDimensionalAnnotation().designDocument(); - String viewName = method.getDimensionalAnnotation().spatialViewName(); - int dimensions = method.getDimensionalAnnotation().dimensions(); - - /* - here contrary to the classical view query we don't support not including an attribute of - the entity in the method name, those are mandatory and will result in a PropertyReferenceException - if not used... - */ - PartTree tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType()); - - //prepare a spatial view query to be used as a base for the query creator - SpatialViewQuery baseSpatialQuery = SpatialViewQuery.from(designDoc, viewName) - .stale(operations.getDefaultConsistency().viewConsistency()); - - //use the SpatialViewQueryCreator to complete it - SpatialViewQueryCreator creator = new SpatialViewQueryCreator(dimensions, - tree, new ParametersParameterAccessor(method.getParameters(), runtimeParams), - baseSpatialQuery, operations.getConverter()); - SpatialViewQueryCreator.SpatialViewQueryWrapper finalQuery = creator.createQuery(); - - //execute the spatial query - return execute(finalQuery); - } - - protected Object execute(SpatialViewQueryCreator.SpatialViewQueryWrapper query) { - if (LOG.isDebugEnabled()) { - LOG.debug("Executing spatial view query: " + query.getQuery().toString()); - } - - return query.eliminate( - operations.findBySpatialView(query.getQuery(), method.getEntityInformation().getJavaType()) - ); - } - - @Override - public CouchbaseQueryMethod getQueryMethod() { - return method; - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewQueryCreator.java deleted file mode 100644 index 71ad9b99..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewQueryCreator.java +++ /dev/null @@ -1,363 +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.repository.query; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.view.SpatialViewQuery; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.query.Dimensional; -import org.springframework.data.couchbase.repository.query.support.AwtPointInShapeEvaluator; -import org.springframework.data.couchbase.repository.query.support.GeoUtils; -import org.springframework.data.couchbase.repository.query.support.PointInShapeEvaluator; -import org.springframework.data.domain.Sort; -import org.springframework.data.geo.Box; -import org.springframework.data.geo.Circle; -import org.springframework.data.geo.Distance; -import org.springframework.data.geo.Point; -import org.springframework.data.geo.Polygon; -import org.springframework.data.geo.Shape; -import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.repository.query.ParameterAccessor; -import org.springframework.data.repository.query.parser.AbstractQueryCreator; -import org.springframework.data.repository.query.parser.Part; -import org.springframework.data.repository.query.parser.PartTree; - -/** - * A QueryCreator that will enrich a {@link SpatialViewQuery} using query derivation mechanisms - * and the parsed {@link PartTree}. - *

    - * Support for query derivation keywords is limited, it is triggered by having a {@link Dimensional} annotation - * on the query method. - *
    - * Here are the {@link Part.Type} supported: - *

      - *
    • WITHIN: finds elements contained in the provided {@link Shape}, {@link Point Point[] polygon}, - * pair of {@link Point}s bounding box (lower left+upper right) or pair of raw {@link JsonArray} (discouraged as it - * leaks Couchbase specific class in your method signature, needs to be numerical data)
    • - *
    • NEAR: finds elements near a provided {@link Point}, within the provided {@link Distance}
    • - *
    • GREATER_THAN, AFTER, GREATER_THAN_EQUALS: adds a numerical element to the startRange and null to the endRange - * (useful for non geographic additional dimensions)
    • - *
    • LESS_THAN, BEFORE, LESS_THAN_EQUALS: adds null to the startRange and a numerical element to the endRange - * (useful for non geographic additional dimensions)
    • - *
    • SIMPLE_PROPERTY (Is, Equals): adds a numerical element to both the startRange and the endRange - * (useful for non geographic additional dimensions)
    • - *
    • BETWEEN: adds a numerical element to the startRange and a second numerical element to the endRange - * (useful for non geographic additional dimensions)
    • - *
    - *

    - * Additionally, {@link PartTree#isLimiting()} will trigger usage of {@link SpatialViewQuery#limit(int) limit}. - * - * @author Mark Paluch - */ -public class SpatialViewQueryCreator extends AbstractQueryCreator { - - private static final Logger LOGGER = LoggerFactory.getLogger(SpatialViewQueryCreator.class); - - private final SpatialViewQuery query; - private final PartTree tree; - private final CouchbaseConverter converter; - - private final int dimensions; - private final JsonArray startRange; - private final JsonArray endRange; - private final List evaluators; - - public SpatialViewQueryCreator(int dimensions, PartTree tree, ParameterAccessor parameters, SpatialViewQuery query, - CouchbaseConverter converter) { - super(tree, parameters); - this.query = query; - this.tree = tree; - this.converter = converter; - this.dimensions = dimensions; - this.startRange = JsonArray.create(); - this.endRange = JsonArray.create(); - this.evaluators = new ArrayList(); - } - - @Override - protected SpatialViewQuery create(Part part, Iterator objectIterator) { - ConvertingIterator iterator = new ConvertingIterator(objectIterator, converter); - - switch (part.getType()) { - case WITHIN: - applyWithin(startRange, endRange, iterator, evaluators, part.getProperty()); - break; - case NEAR: - applyNear(startRange, endRange, iterator, evaluators, part.getProperty()); - break; - case GREATER_THAN: - case GREATER_THAN_EQUAL: - case AFTER: - startRange.add(checkedNext(iterator, Object.class, null)); - endRange.addNull(); - break; - case LESS_THAN: - case LESS_THAN_EQUAL: - case BEFORE: - startRange.addNull(); - endRange.add(checkedNext(iterator, Object.class, null)); - break; - case SIMPLE_PROPERTY: - Object equals = checkedNext(iterator, Object.class, null); - startRange.add(equals); - endRange.add(equals); - break; - case BETWEEN: - startRange.add(checkedNext(iterator, Object.class, null)); - endRange.add(checkedNext(iterator, Object.class, null)); - break; - default: - throw new IllegalArgumentException("Unsupported keyword in Spatial View query derivation: " + part.toString()); - } - - //will complete the ranges in complete step (if not enough elements for the ranges to match dimension count) - return query; - } - - private static void completeRangeIfNeeded(JsonArray range, int dimensions) { - for (int i = range.size(); i < dimensions; i++) { - range.addNull(); - } - } - - private static void applyWithin(JsonArray startRange, JsonArray endRange, Iterator iterator, - List evaluators, PropertyPath path) { - if (!iterator.hasNext()) { - throw new IllegalArgumentException("Not enough parameters for within"); - } - - Object next = iterator.next(); - if (next instanceof Circle) { - evaluators.add(new CircleFalsePositiveEvaluator(path, (Circle) next)); - GeoUtils.convertShapeTo2DRanges(startRange, endRange, (Circle) next); - } else if (next instanceof Polygon) { - evaluators.add(new PolygonFalsePositiveEvaluator(path, (Polygon) next)); - GeoUtils.convertShapeTo2DRanges(startRange, endRange, (Polygon) next); - } else if (next instanceof Box) { - GeoUtils.convertShapeTo2DRanges(startRange, endRange, (Box) next); - } else if (next instanceof Point) { - //expect another point for the other corner of the bounding box - Point northwest = (Point) next; - Point southeast = checkedNext(iterator, Point.class, "Cannot compute a bounding box for within, 2 Point needed"); - GeoUtils.convertPointsTo2DRanges(startRange, endRange, true, northwest, southeast); - } else if (next instanceof Point[]) { - evaluators.add(new PointArrayFalsePositiveEvaluator(path, (Point[]) next)); - GeoUtils.convertPointsTo2DRanges(startRange, endRange, false, (Point[]) next); - } else if (next instanceof JsonArray) { //discouraged, leaks Couchbase classes into signatures - JsonArray first = (JsonArray) next; - for (Object o : first) { - startRange.add(o); - } - - JsonArray second = checkedNext(iterator, JsonArray.class, "2 JsonArray required for within: startRange and endRange"); - for (Object o : second) { - endRange.add(o); - } - } else { - throw new IllegalArgumentException("Unsupported parameter type for within: " + next.getClass()); - } - } - - private static void applyNear(JsonArray startRange, JsonArray endRange, Iterator iterator, - List evaluators, PropertyPath path) { - if (!iterator.hasNext()) { - throw new IllegalArgumentException("Not enough parameters for near"); - } - - Point near = checkedNext(iterator, Point.class, "Near queries need a Point as first argument"); - Distance distance = checkedNext(iterator, Distance.class, "Near queries need a maximum Distance as second argument"); - - evaluators.add(new CircleFalsePositiveEvaluator(path, new Circle(near, distance))); - - double[] boundingBox = GeoUtils.getBoundingBoxForNear(near, distance); - - startRange.add(boundingBox[0]).add(boundingBox[1]); - endRange.add(boundingBox[2]).add(boundingBox[3]); - } - - /** - * Retrieve next value in the iterator assuming it is of the specified type (otherwise throw - * an {@link IllegalArgumentException}. - * - * @param iterator the iterator to peek into. - * @param clazz the expected type of the next value in the iterator. - * @param errorMsg the error message prefix to use in the exception (will append a short message - * describing if the iterator has no value or if the type found was different than expected). - * @param the desired return type. - * @return the next value as a T. - * @throws IllegalArgumentException if there is no next value or it doesn't conform to the desired type. - */ - private static T checkedNext(Iterator iterator, Class clazz, String errorMsg) { - if (errorMsg == null) { - errorMsg = "Expected an additional parameter of type " + clazz.getName(); - } - - if (!iterator.hasNext()) { - throw new IllegalArgumentException(errorMsg + ", missing parameter"); - } - - Object next = iterator.next(); - if (clazz.isInstance(next)) { - return (T) next; - } else if (next == null) { - throw new IllegalArgumentException(errorMsg + ", got null"); - } else { - throw new IllegalArgumentException(errorMsg + ", got a " + next.getClass().getName()); - } - } - - @Override - protected SpatialViewQuery and(Part part, SpatialViewQuery base, Iterator iterator) { - return create(part, iterator);//and not really supported, all query derivation mutate the original ViewQuery - } - - @Override - protected SpatialViewQuery or(SpatialViewQuery base, SpatialViewQuery criteria) { - //this won't be called unless there's a Or keyword in the method - throw new UnsupportedOperationException("Or is not supported for View-based queries"); - } - - @Override - protected SpatialViewQueryWrapper complete(SpatialViewQuery criteria, Sort sort) { - if (sort.isSorted()) { - throw new IllegalArgumentException("Sort is not supported on Spatial View queries"); - } - - if (tree.isLimiting()) { - query.limit(tree.getMaxResults()); - } - - if (startRange.isEmpty() && endRange.isEmpty()) { - return new SpatialViewQueryWrapper(query, evaluators); - } - - completeRangeIfNeeded(startRange, dimensions); - completeRangeIfNeeded(endRange, dimensions); - return new SpatialViewQueryWrapper(query.range(startRange, endRange), evaluators); - } - - - public static abstract class AbstractFalsePositiveEvaluator { - protected static final PointInShapeEvaluator POINT_IN_SHAPE = new AwtPointInShapeEvaluator(); - protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractFalsePositiveEvaluator.class); - - protected final PropertyPath propertyPath; - - protected AbstractFalsePositiveEvaluator(PropertyPath path) { - this.propertyPath = path; - } - - public boolean evaluate(Object original, BeanWrapper bean) { - Object value = bean.getPropertyValue(propertyPath.toDotPath());//TODO use the aliases? - if (value instanceof Point) { - return evaluateCriteria((Point) value); - } else if (value == null) { - LOGGER.trace("Cannot find a Point (was null) for attribute {}, object is {}", propertyPath.toDotPath(), original); - return false; - } else { - LOGGER.trace("Cannot find a Point (was {}) for attribute {}, object is {}", value.getClass().getName(), - propertyPath.toDotPath(), original); - return false; - } - } - - protected abstract boolean evaluateCriteria(Point p); - } - - public static class CircleFalsePositiveEvaluator extends AbstractFalsePositiveEvaluator { - private final Circle criteria; - - public CircleFalsePositiveEvaluator(PropertyPath path, Circle criteria) { - super(path); - this.criteria = criteria; - } - - protected boolean evaluateCriteria(Point p) { - return POINT_IN_SHAPE.pointInCircle(p, criteria); - } - } - - public static class PolygonFalsePositiveEvaluator extends AbstractFalsePositiveEvaluator { - private final Polygon criteria; - - public PolygonFalsePositiveEvaluator(PropertyPath path, Polygon criteria) { - super(path); - this.criteria = criteria; - } - - @Override - protected boolean evaluateCriteria(Point p) { - return POINT_IN_SHAPE.pointInPolygon(p, criteria); - } - } - - public static final class PointArrayFalsePositiveEvaluator extends AbstractFalsePositiveEvaluator { - private final Point[] criteria; - - public PointArrayFalsePositiveEvaluator(PropertyPath path, Point[] criteria) { - super(path); - this.criteria = criteria; - } - - @Override - protected boolean evaluateCriteria(Point p) { - return POINT_IN_SHAPE.pointInPolygon(p, criteria); - } - } - - - public static class SpatialViewQueryWrapper { - private SpatialViewQuery query; - private List eliminators; - - public SpatialViewQueryWrapper(SpatialViewQuery query, List eliminators) { - this.query = query; - this.eliminators = eliminators; - } - - public SpatialViewQuery getQuery() { - return query; - } - - public List eliminate(List objects) { - List result = new ArrayList(objects.size()); - for (T object : objects) { - BeanWrapper bean = new BeanWrapperImpl(object); - boolean pass = true; - for (AbstractFalsePositiveEvaluator eliminator : eliminators) { - pass = pass && eliminator.evaluate(object, bean); - } - if (pass) { - result.add(object); - } else { - LOGGER.trace("Object {} was a false positive in geo query", object); - } - } - return result; - } - } - -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java index 6a52ca59..247625a0 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java @@ -15,21 +15,19 @@ */ package org.springframework.data.couchbase.repository.query; -import static com.couchbase.client.java.query.Delete.deleteFrom; -import static com.couchbase.client.java.query.dsl.Expression.i; +import static org.springframework.data.couchbase.core.query.N1QLExpression.*; import static org.springframework.data.couchbase.core.support.TemplateUtils.*; import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.document.json.JsonValue; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.query.N1QLExpression; +import org.springframework.data.couchbase.repository.Query; import org.springframework.data.couchbase.repository.query.support.N1qlUtils; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.ParameterAccessor; @@ -38,87 +36,69 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.json.JsonValue; + /** * @author Subhashni Balakrishnan */ public class StringBasedN1qlQueryParser { - private static final Logger LOGGER = LoggerFactory.getLogger(StringBasedN1qlQueryParser.class); - - public static final String SPEL_PREFIX = "n1ql"; /** - * Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query} - * annotation's inline statement. This will be replaced by the correct SELECT x FROM y clause needed - * for entity mapping. Eg. "#{{@value SPEL_SELECT_FROM_CLAUSE}} WHERE test = true". - * Note this only makes sense once, as the beginning of the statement. + * Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement. This will be + * replaced by the correct SELECT x FROM y clause needed for entity mapping. Eg. + * "#{{@value SPEL_SELECT_FROM_CLAUSE}} WHERE test = true". Note this only makes sense once, as the + * beginning of the statement. */ public static final String SPEL_SELECT_FROM_CLAUSE = "#" + SPEL_PREFIX + ".selectEntity"; - /** - * Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query} - * annotation's inline statement. This will be replaced by the (escaped) bucket name corresponding to the repository's - * entity. Eg. "SELECT * FROM #{{@value SPEL_BUCKET}} LIMIT 3". + * Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement. This will be + * replaced by the (escaped) bucket name corresponding to the repository's entity. Eg. + * "SELECT * FROM #{{@value SPEL_BUCKET}} LIMIT 3". */ public static final String SPEL_BUCKET = "#" + SPEL_PREFIX + ".bucket"; - /** - * Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query} - * annotation's inline statement. This will be replaced by the fields allowing to construct the repository's entity - * (SELECT clause). Eg. "SELECT #{{@value SPEL_ENTITY}} FROM test". + * Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement. This will be + * replaced by the fields allowing to construct the repository's entity (SELECT clause). Eg. + * "SELECT #{{@value SPEL_ENTITY}} FROM test". */ public static final String SPEL_ENTITY = "#" + SPEL_PREFIX + ".fields"; - /** - * Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query} - * annotation's inline statement WHERE clause. This will be replaced by the expression allowing to only select - * documents matching the entity's class. Eg. "SELECT * FROM test WHERE test = true AND #{{@value SPEL_FILTER}}". + * Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement WHERE clause. + * This will be replaced by the expression allowing to only select documents matching the entity's class. Eg. + * "SELECT * FROM test WHERE test = true AND #{{@value SPEL_FILTER}}". */ public static final String SPEL_FILTER = "#" + SPEL_PREFIX + ".filter"; - /** - * Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query} - * annotation's inline statement. This will be replaced by the correct delete expression needed - * Eg. "#{{@value SPEL_DELETE}} WHERE test = true". - * Note this only makes sense once, as the beginning of the statement. + * Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement. This will be + * replaced by the correct delete expression needed Eg. + * "#{{@value SPEL_DELETE}} WHERE test = true". Note this only makes sense once, as the beginning of the + * statement. */ public static final String SPEL_DELETE = "#" + SPEL_PREFIX + ".delete"; - /** - * Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query} - * annotation's inline statement. This will be replaced by the correct returning clause needed - * for entity mapping. Eg. "#{{@value SPEL_RETURNING}} WHERE test = true". - * Note this only makes sense once, as the beginning of the statement. + * Use this variable in a SpEL expression in a {@link Query @Query} annotation's inline statement. This will be + * replaced by the correct returning clause needed for entity mapping. Eg. + * "#{{@value SPEL_RETURNING}} WHERE test = true". Note this only makes sense once, as the beginning of + * the statement. */ public static final String SPEL_RETURNING = "#" + SPEL_PREFIX + ".returning"; - /** regexp that detect $named placeholder (starts with a letter, then alphanum chars) */ public static final Pattern NAMED_PLACEHOLDER_PATTERN = Pattern.compile("\\W(\\$\\p{Alpha}\\p{Alnum}*)\\b"); - /** regexp that detect positional placeholder ($ followed by digits only) */ public static final Pattern POSITIONAL_PLACEHOLDER_PATTERN = Pattern.compile("\\W(\\$\\p{Digit}+)\\b"); - /** regexp that detects " and ' quote boundaries, ignoring escaped quotes */ public static final Pattern QUOTE_DETECTION_PATTERN = Pattern.compile("[\"'](?:[^\"'\\\\]*(?:\\\\.)?)*[\"']"); - - - /** enumeration of all the combinations of placeholder types that could be found in a N1QL statement */ - private enum PlaceholderType { - NAMED, POSITIONAL, NONE - } - + private static final Logger LOGGER = LoggerFactory.getLogger(StringBasedN1qlQueryParser.class); private final String statement; private final QueryMethod queryMethod; private final PlaceholderType placeHolderType; private final N1qlSpelValues statementContext; private final N1qlSpelValues countContext; private final CouchbaseConverter couchbaseConverter; - - public StringBasedN1qlQueryParser(String statement, - QueryMethod queryMethod, - String bucketName, - CouchbaseConverter couchbaseConverter, - String typeField, - Class typeValue) { + public StringBasedN1qlQueryParser(String statement, QueryMethod queryMethod, String bucketName, + CouchbaseConverter couchbaseConverter, String typeField, Class typeValue) { this.statement = statement; this.queryMethod = queryMethod; this.placeHolderType = checkPlaceholders(statement); @@ -127,10 +107,10 @@ public class StringBasedN1qlQueryParser { this.couchbaseConverter = couchbaseConverter; } - public static N1qlSpelValues createN1qlSpelValues(String bucketName, String typeField, Class typeValue, boolean isCount) { + public static N1qlSpelValues createN1qlSpelValues(String bucketName, String typeField, Class typeValue, + boolean isCount) { String b = "`" + bucketName + "`"; - String entity = "META(" + b + ").id AS " + SELECT_ID + - ", META(" + b + ").cas AS " + SELECT_CAS; + String entity = "META(" + b + ").id AS " + SELECT_ID + ", META(" + b + ").cas AS " + SELECT_CAS; String count = "COUNT(*) AS " + CountFragment.COUNT_ALIAS; String selectEntity; if (isCount) { @@ -140,16 +120,17 @@ public class StringBasedN1qlQueryParser { } String typeSelection = "`" + typeField + "` = \"" + typeValue.getName() + "\""; - String delete = deleteFrom(i(bucketName)).toString(); + String delete = N1QLExpression.delete().from(i(bucketName)).toString(); String returning = " returning " + N1qlUtils.createReturningExpressionForDelete(bucketName).toString(); return new N1qlSpelValues(selectEntity, entity, b, typeSelection, delete, returning); } - //this static method can be used to test the parsing behavior for Couchbase specific spel variables - //in isolation from the rest of the spel parser initialization chain. + // this static method can be used to test the parsing behavior for Couchbase specific spel variables + // in isolation from the rest of the spel parser initialization chain. public String doParse(SpelExpressionParser parser, EvaluationContext evaluationContext, boolean isCountQuery) { - org.springframework.expression.Expression parsedExpression = parser.parseExpression(this.getStatement(), new TemplateParserContext()); + org.springframework.expression.Expression parsedExpression = parser.parseExpression(this.getStatement(), + new TemplateParserContext()); if (isCountQuery) { evaluationContext.setVariable(SPEL_PREFIX, this.getCountContext()); } else { @@ -160,29 +141,29 @@ public class StringBasedN1qlQueryParser { private PlaceholderType checkPlaceholders(String statement) { Matcher quoteMatcher = QUOTE_DETECTION_PATTERN.matcher(statement); - Matcher positionMatcher = POSITIONAL_PLACEHOLDER_PATTERN.matcher(statement); - Matcher namedMatcher = NAMED_PLACEHOLDER_PATTERN.matcher(statement); + Matcher positionMatcher = POSITIONAL_PLACEHOLDER_PATTERN.matcher(statement); + Matcher namedMatcher = NAMED_PLACEHOLDER_PATTERN.matcher(statement); List quotes = new ArrayList(); - while(quoteMatcher.find()) { + while (quoteMatcher.find()) { quotes.add(new int[] { quoteMatcher.start(), quoteMatcher.end() }); } int posCount = 0; int namedCount = 0; - while(positionMatcher.find()) { + while (positionMatcher.find()) { String placeholder = positionMatcher.group(1); - //check not in quoted + // check not in quoted if (checkNotQuoted(placeholder, positionMatcher.start(), positionMatcher.end(), quotes)) { LOGGER.trace("{}: Found positional placeholder {}", this.queryMethod.getName(), placeholder); posCount++; } } - while(namedMatcher.find()) { + while (namedMatcher.find()) { String placeholder = namedMatcher.group(1); - //check not in quoted + // check not in quoted if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes)) { LOGGER.trace("{}: Found named placeholder {}", this.queryMethod.getName(), placeholder); namedCount++; @@ -190,8 +171,8 @@ public class StringBasedN1qlQueryParser { } if (posCount > 0 && namedCount > 0) { - throw new IllegalArgumentException("Using both named (" + namedCount + ") and positional (" + posCount + - ") placeholders is not supported, please choose one over the other in " + this.queryMethod.getName()); + throw new IllegalArgumentException("Using both named (" + namedCount + ") and positional (" + posCount + + ") placeholders is not supported, please choose one over the other in " + this.queryMethod.getName()); } else if (posCount > 0) { return PlaceholderType.POSITIONAL; } else if (namedCount > 0) { @@ -244,7 +225,7 @@ public class StringBasedN1qlQueryParser { return getPositionalPlaceholderValues(accessor); case NONE: default: - return JsonArray.empty(); + return JsonArray.create(); } } @@ -264,15 +245,21 @@ public class StringBasedN1qlQueryParser { return this.statement; } + /** enumeration of all the combinations of placeholder types that could be found in a N1QL statement */ + private enum PlaceholderType { + NAMED, POSITIONAL, NONE + } + /** - * This class is exposed to SpEL parsing through the variable #{@value SPEL_PREFIX}. - * Use the attributes in your SpEL expressions: {@link #selectEntity}, {@link #fields}, {@link #bucket} and {@link #filter}. + * This class is exposed to SpEL parsing through the variable #{@value SPEL_PREFIX}. Use the attributes + * in your SpEL expressions: {@link #selectEntity}, {@link #fields}, {@link #bucket} and {@link #filter}. */ public static final class N1qlSpelValues { /** * #{{@value SPEL_SELECT_FROM_CLAUSE}. - * selectEntity will be replaced by the SELECT-FROM clause corresponding to the entity. Use once at the beginning. + * selectEntity will be replaced by the SELECT-FROM clause corresponding to the entity. Use once at the + * beginning. */ public final String selectEntity; @@ -290,7 +277,8 @@ public class StringBasedN1qlQueryParser { /** * #{{@value SPEL_FILTER}}. - * filter will be replaced by an expression allowing to select only entries matching the entity in a WHERE clause. + * filter will be replaced by an expression allowing to select only entries matching the entity in a WHERE + * clause. */ public final String filter; @@ -302,11 +290,13 @@ public class StringBasedN1qlQueryParser { /** * #{{@value SPEL_RETURNING}}. - * returning will be replaced by a returning expression allowing to return the entity and meta information on deletes. + * returning will be replaced by a returning expression allowing to return the entity and meta information on + * deletes. */ public final String returning; - public N1qlSpelValues(String selectClause, String entityFields, String bucket, String filter, String delete, String returning) { + public N1qlSpelValues(String selectClause, String entityFields, String bucket, String filter, String delete, + String returning) { this.selectEntity = selectClause; this.fields = entityFields; this.bucket = bucket; diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlBasedQuery.java index 2d15f46b..14e7793f 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringN1qlBasedQuery.java @@ -16,7 +16,10 @@ package org.springframework.data.couchbase.repository.query; +import static org.springframework.data.couchbase.core.query.N1QLExpression.*; + import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.couchbase.repository.query.support.N1qlUtils; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -28,92 +31,87 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.dsl.path.DefaultLimitPath; -import com.couchbase.client.java.query.dsl.path.DefaultOrderByPath; +import com.couchbase.client.java.json.JsonValue; /** * A {@link RepositoryQuery} for Couchbase, based on N1QL and a String statement. *

    - * The statement can contain positional placeholders (eg. name = $1) that will map to the - * method's parameters, in the same order. + * The statement can contain positional placeholders (eg. name = $1) that will map to the method's + * parameters, in the same order. *

    * The statement can also contain SpEL expressions enclosed in #{ and }. *

    - * There are couchbase-provided variables included for the {@link StringBasedN1qlQueryParser#SPEL_BUCKET bucket namespace}, - * the {@link StringBasedN1qlQueryParser#SPEL_ENTITY ID and CAS fields} necessary for entity reconstruction - * or a shortcut that covers {@link StringBasedN1qlQueryParser#SPEL_SELECT_FROM_CLAUSE SELECT AND FROM clauses}, - * along with a variable for {@link StringBasedN1qlQueryParser#SPEL_FILTER WHERE clause filtering} of the correct entity. + * There are couchbase-provided variables included for the {@link StringBasedN1qlQueryParser#SPEL_BUCKET bucket + * namespace}, the {@link StringBasedN1qlQueryParser#SPEL_ENTITY ID and CAS fields} necessary for entity reconstruction + * or a shortcut that covers {@link StringBasedN1qlQueryParser#SPEL_SELECT_FROM_CLAUSE SELECT AND FROM clauses}, along + * with a variable for {@link StringBasedN1qlQueryParser#SPEL_FILTER WHERE clause filtering} of the correct entity. * * @author Simon Baslé * @author Subhashni Balakrishnan * @author Mark Paluch */ public class StringN1qlBasedQuery extends AbstractN1qlBasedQuery { - private final SpelExpressionParser parser; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; - private final StringBasedN1qlQueryParser queryParser; + private final SpelExpressionParser parser; + private final QueryMethodEvaluationContextProvider evaluationContextProvider; + private final StringBasedN1qlQueryParser queryParser; - protected String getTypeField() { - return getCouchbaseOperations().getConverter().getTypeKey(); - } + public StringN1qlBasedQuery(String statement, CouchbaseQueryMethod queryMethod, + CouchbaseOperations couchbaseOperations, SpelExpressionParser spelParser, + QueryMethodEvaluationContextProvider evaluationContextProvider) { + super(queryMethod, couchbaseOperations); + this.queryParser = new StringBasedN1qlQueryParser(statement, queryMethod, getCouchbaseOperations().getBucketName(), + getCouchbaseOperations().getConverter(), getTypeField(), getTypeValue()); + this.parser = spelParser; + this.evaluationContextProvider = evaluationContextProvider; + } - protected Class getTypeValue() { - return getQueryMethod().getEntityInformation().getJavaType(); - } + protected String getTypeField() { + return getCouchbaseOperations().getConverter().getTypeKey(); + } - public StringN1qlBasedQuery(String statement, CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations, - SpelExpressionParser spelParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(queryMethod, couchbaseOperations); - this.queryParser = new StringBasedN1qlQueryParser(statement, queryMethod, - getCouchbaseOperations().getCouchbaseBucket().name(), getCouchbaseOperations().getConverter(), getTypeField(), getTypeValue()); - this.parser = spelParser; - this.evaluationContextProvider = evaluationContextProvider; - } + protected Class getTypeValue() { + return getQueryMethod().getEntityInformation().getJavaType(); + } - @Override - protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { - return this.queryParser.getPlaceholderValues(accessor); - } + @Override + protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { + return this.queryParser.getPlaceholderValues(accessor); + } - @Override - public Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) { - EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), runtimeParameters); - String parsedStatement = this.queryParser.doParse(parser, evaluationContext, false); - String orderByPart = ""; - String limitByPart = ""; + @Override + public N1QLExpression getExpression(ParameterAccessor accessor, Object[] runtimeParameters, + ReturnedType returnedType) { + EvaluationContext evaluationContext = evaluationContextProvider + .getEvaluationContext(getQueryMethod().getParameters(), runtimeParameters); + N1QLExpression parsedStatement = x(this.queryParser.doParse(parser, evaluationContext, false)); - Sort sort = accessor.getSort(); - if (sort.isSorted()) { - com.couchbase.client.java.query.dsl.Sort[] cbSorts = N1qlUtils.createSort(sort, getCouchbaseOperations().getConverter()); - orderByPart = " " + new DefaultOrderByPath(null).orderBy(cbSorts).toString(); - } - if (queryMethod.isPageQuery()) { - Pageable pageable = accessor.getPageable(); - Assert.notNull(pageable, "Pageable must not be null!"); - limitByPart = " " + new DefaultLimitPath(null).limit(pageable.getPageSize()) - .offset(Math.toIntExact(pageable.getOffset())).toString(); - } else if (queryMethod.isSliceQuery()) { - Pageable pageable = accessor.getPageable(); - Assert.notNull(pageable, "Pageable must not be null!"); - limitByPart = " " + new DefaultLimitPath(null).limit(pageable.getPageSize() + 1) - .offset(Math.toIntExact(pageable.getOffset())).toString(); - } - return N1qlQuery.simple(parsedStatement + orderByPart + limitByPart).statement(); - } + Sort sort = accessor.getSort(); + if (sort.isSorted()) { + N1QLExpression[] cbSorts = N1qlUtils.createSort(sort); + parsedStatement = parsedStatement.orderBy(cbSorts); + } + if (queryMethod.isPageQuery()) { + Pageable pageable = accessor.getPageable(); + Assert.notNull(pageable, "Pageable must not be null!"); + parsedStatement = parsedStatement.limit(pageable.getPageSize()).offset(Math.toIntExact(pageable.getOffset())); + } else if (queryMethod.isSliceQuery()) { + Pageable pageable = accessor.getPageable(); + Assert.notNull(pageable, "Pageable must not be null!"); + parsedStatement = parsedStatement.limit(pageable.getPageSize() + 1).offset(Math.toIntExact(pageable.getOffset())); + } + return parsedStatement; + } - @Override - protected Statement getCount(ParameterAccessor accessor, Object[] runtimeParameters) { - EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), runtimeParameters); - String parsedStatement = this.queryParser.doParse(parser, evaluationContext, true); - return N1qlQuery.simple(parsedStatement).statement(); - } + @Override + protected N1QLExpression getCount(ParameterAccessor accessor, Object[] runtimeParameters) { + EvaluationContext evaluationContext = evaluationContextProvider + .getEvaluationContext(getQueryMethod().getParameters(), runtimeParameters); + return x(this.queryParser.doParse(parser, evaluationContext, true)); + } - @Override - protected boolean useGeneratedCountQuery() { - return this.queryParser.useGeneratedCountQuery(); - } + @Override + protected boolean useGeneratedCountQuery() { + return this.queryParser.useGeneratedCountQuery(); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java deleted file mode 100644 index 62abc0e5..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2013-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.repository.query; - -import java.util.List; - -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.view.SpatialViewQuery; -import com.couchbase.client.java.view.Stale; -import com.couchbase.client.java.view.ViewQuery; -import com.couchbase.client.java.view.ViewResult; -import com.couchbase.client.java.view.ViewRow; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.geo.Distance; -import org.springframework.data.geo.Shape; -import org.springframework.data.mapping.PropertyReferenceException; -import org.springframework.data.repository.query.ParametersParameterAccessor; -import org.springframework.data.repository.query.QueryMethod; -import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.util.StringUtils; - -/** - * Execute a repository query through the View mechanism. - * - * @author Michael Nitschinger - * @author Simon Baslé - */ -public class ViewBasedCouchbaseQuery implements RepositoryQuery { - - private static final Logger LOG = LoggerFactory.getLogger(ViewBasedCouchbaseQuery.class); - - private final CouchbaseQueryMethod method; - private final CouchbaseOperations operations; - - public ViewBasedCouchbaseQuery(CouchbaseQueryMethod method, CouchbaseOperations operations) { - this.method = method; - this.operations = operations; - } - - @Override - public Object execute(Object[] runtimeParams) { - if (method.hasViewName()) { //only allow derivation on @View explicitly defining a viewName - return deriveAndExecute(runtimeParams); - } else { - return guessViewAndExecute(); - } - } - - protected Object guessViewAndExecute() { - String designDoc = designDocName(method); - String methodName = method.getName(); - boolean isExplicitReduce = method.hasViewAnnotation() && method.getViewAnnotation().reduce(); - boolean isReduce = methodName.startsWith("count") || isExplicitReduce; - String viewName = StringUtils.uncapitalize(methodName.replaceFirst("find|count", "")); - - ViewQuery simpleQuery = ViewQuery.from(designDoc, viewName) - .stale(operations.getDefaultConsistency().viewConsistency()); - if (isReduce) { - simpleQuery.reduce(); - return executeReduce(simpleQuery, designDoc, viewName); - } else { - return execute(simpleQuery); - } - } - - protected Object deriveAndExecute(Object[] runtimeParams) { - String designDoc = designDocName(method); - String viewName = method.getViewAnnotation().viewName(); - - //prepare a ViewQuery to be used as a base for the ViewQueryCreator - ViewQuery baseQuery = ViewQuery.from(designDoc, viewName) - .stale(operations.getDefaultConsistency().viewConsistency()); - - try { - PartTree tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType()); - - //use a ViewQueryCreator to complete the base query - ViewQueryCreator creator = new ViewQueryCreator(tree, new ParametersParameterAccessor(method.getParameters(), runtimeParams), - method.getViewAnnotation(), baseQuery, operations.getConverter()); - ViewQueryCreator.DerivedViewQuery result = creator.createQuery(); - - if (result.isReduce) { - return executeReduce(result.builtQuery, designDoc, viewName); - } else { - //otherwise just execute the query - return execute(result.builtQuery); - } - } catch (PropertyReferenceException e) { - /* - For views, not including an attribute name in the method will result in returning - the whole set of results from the view. - This is detected by looking for PropertyReferenceExceptions that seem to complain - about a missing property that corresponds to the method name - */ - if (e.getPropertyName().equals(method.getName())) { - return execute(baseQuery); - } - throw e; - } - } - - protected Object execute(ViewQuery query) { - if (LOG.isDebugEnabled()) { - LOG.debug("Executing view query: " + query.toString()); - } - return operations.findByView(query, method.getEntityInformation().getJavaType()); - } - - protected Object executeReduce(ViewQuery query, String designDoc, String viewName) { - if (LOG.isDebugEnabled()) { - LOG.debug("Executing view reduced query: " + query.toString()); - } - ViewResult viewResult = operations.queryView(query); - List allRows = viewResult.allRows(); - JsonObject error = viewResult.error(); - if (error != null) { - throw new CouchbaseQueryExecutionException("Error while reducing on view " + designDoc + "/" + viewName + - ": " + error); - } - if (allRows == null || allRows.isEmpty()) { - return null; - } else{ - return allRows.get(0).value(); - } - } - - @Override - public QueryMethod getQueryMethod() { - return method; - } - - /** - * Returns the best-guess design document name. - * - * @return the design document name. - */ - private static String designDocName(CouchbaseQueryMethod method) { - if (method.hasViewSpecification()) { - return method.getViewAnnotation().designDocument(); - } else if (method.hasViewAnnotation()) { - return StringUtils.uncapitalize(method.getEntityInformation().getJavaType().getSimpleName()); - } else { - throw new IllegalStateException("View-based query should only happen on a method with @View annotation"); - } - } - -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ViewQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/ViewQueryCreator.java deleted file mode 100644 index 80b17d9c..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ViewQueryCreator.java +++ /dev/null @@ -1,320 +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.repository.query; - -import java.util.ArrayList; -import java.util.Arrays; -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 com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.view.ViewQuery; - -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.query.View; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.ParameterAccessor; -import org.springframework.data.repository.query.parser.AbstractQueryCreator; -import org.springframework.data.repository.query.parser.Part; -import org.springframework.data.repository.query.parser.PartTree; - -/** - * A QueryCreator that will enrich a {@link ViewQuery} using query derivation mechanisms - * and the parsed {@link PartTree}. - *

    - * Support for query derivation keywords is very limited (and especially you have to use one valid entity property name - * in your query naming, and compound key views are not supported). - *
    - * Here are the {@link Part.Type} supported: - *

      - *
    • GREATER_THAN_EQUAL: uses {@link ViewQuery#startKey(String) startkey}
    • - *
    • LESS_THAN_EQUAL: uses {@link ViewQuery#endKey(String) endkey} {@link ViewQuery#inclusiveEnd() inclusive}
    • - *
    • LESS_THAN / BEFORE: uses {@link ViewQuery#endKey(String) endkey}
    • - *
    • BETWEEN: uses {@link ViewQuery#startKey(String) startkey}, - * {@link ViewQuery#endKey(String) endkey(exclusive)}
    • - *
    • STARTING_WITH: (only with String key). uses {@link ViewQuery#startKey(String) startkey}, - * {@link ViewQuery#endKey(String) endkey(exclusive)}. Will append special unicode char \uefff.
    • - *
    • SIMPLE_PROPERTY: (aka "Is", "Equals"). This one can have no argument if used alone - * (eg. "findAllByUsername"), otherwise uses {@link ViewQuery#key(String) key}
    • - *
    • IN: uses {@link ViewQuery#keys(JsonArray) keys} (provide a collection or array)
    • - *
    - *

    - * Additionally, {@link PartTree#isLimiting()} will use {@link ViewQuery#limit(int) limit} - * and either {@link View#reduce()} or {@link PartTree#isCountProjection()} will trigger a {@link ViewQuery#reduce() reduce}. - */ -public class ViewQueryCreator extends AbstractQueryCreator { - - private ViewQuery query; - private final View viewAnnotation; - private final PartTree tree; - private final int treeCount; - private final CouchbaseConverter converter; - - public ViewQueryCreator(PartTree tree, ParameterAccessor parameters, View viewAnnotation, - ViewQuery query, CouchbaseConverter converter) { - super(tree, parameters); - this.query = query; - this.viewAnnotation = viewAnnotation; - this.tree = tree; - this.converter = converter; - - //sanity check the partTree since we have strong restrictions on what's supported: - int i = 0; - Set properties = new HashSet(); - for (PartTree.OrPart parts : tree) { - for (Part part : parts) { - i++; - properties.add(part.getProperty().toDotPath()); - } - } - this.treeCount = i; - if (properties.size() > 1) { - throw new IllegalArgumentException("View-based queries do not support compound keys"); - } - } - - @Override - protected ViewQuery create(Part part, Iterator objectIterator) { - ConvertingIterator iterator = new ConvertingIterator(objectIterator, converter); - - switch (part.getType()) { - case GREATER_THAN_EQUAL: - startKey(iterator); - break; - case LESS_THAN_EQUAL: - query.inclusiveEnd(true); - case BEFORE: - case LESS_THAN://fall-through on purpose here - endKey(iterator); - break; - case BETWEEN: - startKey(iterator); - endKey(iterator); - break; - case STARTING_WITH: //starting_with only supports String keys - String nameStart = nextString(iterator); - query.startKey(nameStart).endKey(nameStart + "\uefff"); - query.inclusiveEnd(false); - break; - case SIMPLE_PROPERTY: - key(iterator); - break; - case IN: - query.keys(in(iterator)); - break; - default: - throw new IllegalArgumentException("Unsupported keyword in View query derivation: " + part.toString()); - } - return query; - } - - private void startKey(Iterator iterator) { - if (!iterator.hasNext()) { - throw new IllegalArgumentException("Not enough parameters for startKey"); - } - - Object next = iterator.next(); - if (next instanceof String) { - query.startKey((String) next); - } else if (next instanceof Boolean) { - query.startKey((Boolean) next); - } else if (next instanceof Double) { - query.startKey((Double) next); - } else if (next instanceof Integer) { - query.startKey((Integer) next); - } else if (next instanceof Long) { - query.startKey((Long) next); - } else if (next instanceof Collection) { - //when creating a JsonArray, the from(List) method is preferred because it will convert internal - //Lists and Maps to JsonObject and JsonArray respectively - List arrayContent = new ArrayList((Collection) next); - query.startKey(JsonArray.from(arrayContent)); - } else if (next.getClass().isArray()) { - List arrayContent = Arrays.asList((Object[]) next); - query.startKey(JsonArray.from(arrayContent)); - } else if (next instanceof JsonArray) { //discouraged, since it's leaking store-specifics in the method signature - query.startKey((JsonArray) next); - } else if (next instanceof JsonObject) { //discouraged, since it's leaking store-specifics in the method signature - query.startKey((JsonObject) next); - } else { - throw new IllegalArgumentException("Unsupported parameter type for startKey: " + next.getClass()); - } - } - private void endKey(Iterator iterator) { - if (!iterator.hasNext()) { - throw new IllegalArgumentException("Not enough parameters for endKey"); - } - - Object next = iterator.next(); - if (next instanceof String) { - query.endKey((String) next); - } else if (next instanceof Boolean) { - query.endKey((Boolean) next); - } else if (next instanceof Double) { - query.endKey((Double) next); - } else if (next instanceof Integer) { - query.endKey((Integer) next); - } else if (next instanceof Long) { - query.endKey((Long) next); - } else if (next instanceof Collection) { - //when creating a JsonArray, the from(List) method is preferred because it will convert internal - //Lists and Maps to JsonObject and JsonArray respectively - List arrayContent = new ArrayList((Collection) next); - query.endKey(JsonArray.from(arrayContent)); - } else if (next.getClass().isArray()) { - List arrayContent = Arrays.asList((Object[]) next); - query.endKey(JsonArray.from(arrayContent)); - } else if (next instanceof JsonArray) { //discouraged, since it's leaking store-specifics in the method signature - query.endKey((JsonArray) next); - } else if (next instanceof JsonObject) { //discouraged, since it's leaking store-specifics in the method signature - query.endKey((JsonObject) next); - } else { - throw new IllegalArgumentException("Unsupported parameter type for endKey: " + next.getClass()); - } - } - - private void key(Iterator iterator) { - if (!iterator.hasNext() && treeCount > 1) { - throw new IllegalArgumentException("Not enough parameters for key"); - } else if (!iterator.hasNext()) { - //probably pattern like findAllByUsername(), just apply query without parameters - return; - } - - Object next = iterator.next(); - if (next instanceof String) { - query.key((String) next); - } else if (next instanceof Boolean) { - query.key((Boolean) next); - } else if (next instanceof Double) { - query.key((Double) next); - } else if (next instanceof Integer) { - query.key((Integer) next); - } else if (next instanceof Long) { - query.key((Long) next); - } else if (next instanceof Collection) { - //when creating a JsonArray, the from(List) method is preferred because it will convert internal - //Lists and Maps to JsonObject and JsonArray respectively - List arrayContent = new ArrayList((Collection) next); - query.key(JsonArray.from(arrayContent)); - } else if (next.getClass().isArray()) { - List arrayContent = Arrays.asList((Object[]) next); - query.key(JsonArray.from(arrayContent)); - } else if (next instanceof JsonArray) { //discouraged, since it's leaking store-specifics in the method signature - query.key((JsonArray) next); - } else if (next instanceof JsonObject) { //discouraged, since it's leaking store-specifics in the method signature - query.key((JsonObject) next); - } else { - throw new IllegalArgumentException("Unsupported parameter type for key: " + next.getClass()); - } - } - - - private String nextString(Iterator iterator) { - if (!iterator.hasNext()) { - throw new IllegalStateException("Not enough parameters"); - } - - Object next = iterator.next(); - if (!(next instanceof String)) { - throw new IllegalArgumentException("Expected String, got " + next.getClass().getName()); - } - - return (String) next; - } - - private JsonArray in(Iterator iterator) { - if (!iterator.hasNext()) { - throw new IllegalArgumentException("Not enough parameters for in"); - } - Object next = iterator.next(); - - List values; - if (next instanceof Collection) { - values = new ArrayList((Collection) next); - } else if (next.getClass().isArray()) { - values = Arrays.asList((Object[]) next); - } else { - values = Collections.singletonList(next); - } - //using the from(List) method ensure that contained Lists and Maps will be converted to JsonArrays and JsonObjects - return JsonArray.from(values); - } - - @Override - protected ViewQuery and(Part part, ViewQuery base, Iterator iterator) { - return create(part, iterator);//and not really supported, all query derivation mutate the original ViewQuery - } - - @Override - protected ViewQuery or(ViewQuery base, ViewQuery criteria) { - //this won't be called unless there's a Or keyword in the method - throw new UnsupportedOperationException("Or is not supported for View-based queries"); - } - - @Override - protected DerivedViewQuery complete(ViewQuery criteria, Sort sort) { - boolean descending = false; - - if (sort.isSorted()) { - int sortCount = 0; - Iterator it = sort.iterator(); - while(it.hasNext()) { - sortCount++; - if (!it.next().isAscending()) { - descending = true; - } - } - if (sortCount > 1) { - throw new IllegalArgumentException("Detected " + sortCount + " sort instructions, maximum one supported"); - } - query.descending(descending); - } - - if (tree.isLimiting()) { - query.limit(tree.getMaxResults()); - } - - boolean isCount = tree.isCountProjection() == Boolean.TRUE; - boolean isExplicitReduce = viewAnnotation != null && viewAnnotation.reduce(); - if (isCount || isExplicitReduce) { - query.reduce(); - } - - return new DerivedViewQuery(query, tree.isLimiting(), isCount || isExplicitReduce); - } - - /** - * Wrapper class allowing to see downstream if the built query was built with options like reduce and limit. - */ - protected static class DerivedViewQuery { - public final ViewQuery builtQuery; - public final boolean isLimited; - public final boolean isReduce; - - public DerivedViewQuery(ViewQuery builtQuery, boolean isLimited, boolean isReduce) { - this.builtQuery = builtQuery; - this.isLimited = isLimited; - this.isReduce = isReduce; - } - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/package-info.java b/src/main/java/org/springframework/data/couchbase/repository/query/package-info.java index ad1c3220..44da2fed 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/package-info.java @@ -1,5 +1,4 @@ /** - * This package contains classes related to query derivation and concrete - * ways of querying couchbase. + * This package contains classes related to query derivation and concrete ways of querying couchbase. */ package org.springframework.data.couchbase.repository.query; diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/support/AwtPointInShapeEvaluator.java b/src/main/java/org/springframework/data/couchbase/repository/query/support/AwtPointInShapeEvaluator.java index bb08b21c..44f6a0c4 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/support/AwtPointInShapeEvaluator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/support/AwtPointInShapeEvaluator.java @@ -32,58 +32,60 @@ import org.springframework.data.geo.Polygon; */ public class AwtPointInShapeEvaluator extends PointInShapeEvaluator { - @Override - public boolean pointInPolygon(Point p, Polygon polygon) { - final List points = polygon.getPoints(); - Path2D awtPolygon = new Path2D.Double(Path2D.WIND_EVEN_ODD, points.size()); - boolean first = true; - for (Point point : points) { - if (first) { - first = false; - awtPolygon.moveTo(point.getX(), point.getY()); - } else { - awtPolygon.lineTo(point.getX(), point.getY()); - } - } - awtPolygon.closePath(); - return pointInPolygon(p, awtPolygon); - } + @Override + public boolean pointInPolygon(Point p, Polygon polygon) { + final List points = polygon.getPoints(); + Path2D awtPolygon = new Path2D.Double(Path2D.WIND_EVEN_ODD, points.size()); + boolean first = true; + for (Point point : points) { + if (first) { + first = false; + awtPolygon.moveTo(point.getX(), point.getY()); + } else { + awtPolygon.lineTo(point.getX(), point.getY()); + } + } + awtPolygon.closePath(); + return pointInPolygon(p, awtPolygon); + } - @Override - public boolean pointInPolygon(Point p, Point... points) { - if (points == null) throw new NullPointerException("Polygon must at least contain 3 points"); - if (points.length < 3) throw new IllegalArgumentException("Polygon must at least contain 3 points"); - Path2D awtPolygon = new Path2D.Double(Path2D.WIND_EVEN_ODD, points.length); - boolean first = true; - for (Point point : points) { - if (first) { - first = false; - awtPolygon.moveTo(point.getX(), point.getY()); - } else { - awtPolygon.lineTo(point.getX(), point.getY()); - } - } - awtPolygon.closePath(); - return pointInPolygon(p, awtPolygon); - } + @Override + public boolean pointInPolygon(Point p, Point... points) { + if (points == null) + throw new NullPointerException("Polygon must at least contain 3 points"); + if (points.length < 3) + throw new IllegalArgumentException("Polygon must at least contain 3 points"); + Path2D awtPolygon = new Path2D.Double(Path2D.WIND_EVEN_ODD, points.length); + boolean first = true; + for (Point point : points) { + if (first) { + first = false; + awtPolygon.moveTo(point.getX(), point.getY()); + } else { + awtPolygon.lineTo(point.getX(), point.getY()); + } + } + awtPolygon.closePath(); + return pointInPolygon(p, awtPolygon); + } - @Override - public boolean pointInCircle(Point p, Circle c) { - Point2D center = new Point2D.Double(c.getCenter().getX(), c.getCenter().getY()); - return pointNear(p, center, c.getRadius().getNormalizedValue()); - } + @Override + public boolean pointInCircle(Point p, Circle c) { + Point2D center = new Point2D.Double(c.getCenter().getX(), c.getCenter().getY()); + return pointNear(p, center, c.getRadius().getNormalizedValue()); + } - @Override - public boolean pointInCircle(Point p, Point center, Distance radiusDistance) { - double radius = radiusDistance.getNormalizedValue(); - return pointNear(p, new Point2D.Double(center.getX(), center.getY()), radius); - } + @Override + public boolean pointInCircle(Point p, Point center, Distance radiusDistance) { + double radius = radiusDistance.getNormalizedValue(); + return pointNear(p, new Point2D.Double(center.getX(), center.getY()), radius); + } - private boolean pointInPolygon(Point p, Path2D awtPolygon) { - return awtPolygon.contains(p.getX(), p.getY()); - } + private boolean pointInPolygon(Point p, Path2D awtPolygon) { + return awtPolygon.contains(p.getX(), p.getY()); + } - private boolean pointNear(Point p, Point2D center, double distance) { - return center.distance(p.getX(), p.getY()) <= distance; - } + private boolean pointNear(Point p, Point2D center, double distance) { + return center.distance(p.getX(), p.getY()) <= distance; + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/support/GeoUtils.java b/src/main/java/org/springframework/data/couchbase/repository/query/support/GeoUtils.java index 5fc8a38e..47e22974 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/support/GeoUtils.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/support/GeoUtils.java @@ -16,10 +16,6 @@ package org.springframework.data.couchbase.repository.query.support; -import com.couchbase.client.java.document.json.JsonArray; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import org.springframework.data.geo.Box; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; @@ -27,6 +23,8 @@ import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; import org.springframework.data.geo.Shape; +import com.couchbase.client.java.json.JsonArray; + /** * Utility class to deal with geo/dimensional indexed data and queries. * @@ -34,127 +32,124 @@ import org.springframework.data.geo.Shape; */ public class GeoUtils { - /** - * Computes the bounding box approximation for a "near" query (max distance from a point of origin). - * - * @param origin the point of origin, center for the query. - * @param distance the max distance to search within (negative distances will be multiplied by -1). - * @return the bounding box approximation for this query ([xMin, yMin, xMax, yMax]). - * @throws NullPointerException if any of the origin and distance are null - */ - public static double[] getBoundingBoxForNear(Point origin, Distance distance) { - if (origin == null || distance == null) throw new NullPointerException("Origin and distance required"); + /** + * Computes the bounding box approximation for a "near" query (max distance from a point of origin). + * + * @param origin the point of origin, center for the query. + * @param distance the max distance to search within (negative distances will be multiplied by -1). + * @return the bounding box approximation for this query ([xMin, yMin, xMax, yMax]). + * @throws NullPointerException if any of the origin and distance are null + */ + public static double[] getBoundingBoxForNear(Point origin, Distance distance) { + if (origin == null || distance == null) + throw new NullPointerException("Origin and distance required"); - //since maxDistance COULD be negative, we have to make sure we have correct min/max - double maxDistance = Math.abs(distance.getNormalizedValue()); - double xMin = origin.getX() - maxDistance; - double yMin = origin.getY() - maxDistance; - double xMax = origin.getX() + maxDistance; - double yMax = origin.getY() + maxDistance; + // since maxDistance COULD be negative, we have to make sure we have correct min/max + double maxDistance = Math.abs(distance.getNormalizedValue()); + double xMin = origin.getX() - maxDistance; + double yMin = origin.getY() - maxDistance; + double xMax = origin.getX() + maxDistance; + double yMax = origin.getY() + maxDistance; - return new double[] { xMin, yMin, xMax, yMax }; - } + return new double[] { xMin, yMin, xMax, yMax }; + } - /** - * Convert a sequence of {@link Point Points} describing a polygon to a pair of - * {@link JsonArray} ranges corresponding to that polygon's bounding box, - * and inject the coordinates into startRange and endRange. - * If it is already equivalent to a Box (upper-left Point + lower-right Point), set - * isBoundingBox to true. - * Otherwise, this method will attempt to find the bounding box by finding the lowest - * and highest X and Y coordinates. - * - * @param startRange the startRange to populate with this shape's data. - * @param endRange the endRange to populate with this shape's data. - * @param isBoundingBox true to skip finding min/max X and Y coordinates and use 2 Points as a {@link Box}. - * @param points the sequence of Points. - * @throws IllegalArgumentException if no points are provided, or in the case of isBoundingBox true - * if more or less than 2 points are provided or the 2 points are not ordered (a.x <= b.x && a.y <= b.y). - */ - public static void convertPointsTo2DRanges(JsonArray startRange, JsonArray endRange, boolean isBoundingBox, Point... points) { - if (points == null || points.length == 0) { - throw new IllegalArgumentException("Needs points to convert"); - } + /** + * Convert a sequence of {@link Point Points} describing a polygon to a pair of {@link JsonArray} ranges corresponding + * to that polygon's bounding box, and inject the coordinates into startRange and endRange. If it is already + * equivalent to a Box (upper-left Point + lower-right Point), set isBoundingBox to true. Otherwise, this + * method will attempt to find the bounding box by finding the lowest and highest X and Y coordinates. + * + * @param startRange the startRange to populate with this shape's data. + * @param endRange the endRange to populate with this shape's data. + * @param isBoundingBox true to skip finding min/max X and Y coordinates and use 2 Points as a {@link Box}. + * @param points the sequence of Points. + * @throws IllegalArgumentException if no points are provided, or in the case of isBoundingBox true if more or less + * than 2 points are provided or the 2 points are not ordered (a.x <= b.x && a.y <= b.y). + */ + public static void convertPointsTo2DRanges(JsonArray startRange, JsonArray endRange, boolean isBoundingBox, + Point... points) { + if (points == null || points.length == 0) { + throw new IllegalArgumentException("Needs points to convert"); + } - if (isBoundingBox) { - if (points.length != 2) { - throw new IllegalArgumentException("Bounding box must be made of 2 points"); - } - if (points[0].getX() > points[1].getX() || points[0].getY() > points[1].getY()) { - throw new IllegalArgumentException("Bounding box must have point A on the lower left of point B"); - } - startRange.add(points[0].getX()).add(points[0].getY()); - endRange.add(points[1].getX()).add(points[1].getY()); - } else { - //this is like a polygon, find the bounding box - //find the lowest and highest X and Y to get the bounding box - double xMin = Double.POSITIVE_INFINITY; - double yMin = Double.POSITIVE_INFINITY; - double xMax = Double.NEGATIVE_INFINITY; - double yMax = Double.NEGATIVE_INFINITY; - for (Point point : points) { - xMin = point.getX() < xMin ? point.getX() : xMin; - xMax = point.getX() > xMax ? point.getX() : xMax; + if (isBoundingBox) { + if (points.length != 2) { + throw new IllegalArgumentException("Bounding box must be made of 2 points"); + } + if (points[0].getX() > points[1].getX() || points[0].getY() > points[1].getY()) { + throw new IllegalArgumentException("Bounding box must have point A on the lower left of point B"); + } + startRange.add(points[0].getX()).add(points[0].getY()); + endRange.add(points[1].getX()).add(points[1].getY()); + } else { + // this is like a polygon, find the bounding box + // find the lowest and highest X and Y to get the bounding box + double xMin = Double.POSITIVE_INFINITY; + double yMin = Double.POSITIVE_INFINITY; + double xMax = Double.NEGATIVE_INFINITY; + double yMax = Double.NEGATIVE_INFINITY; + for (Point point : points) { + xMin = point.getX() < xMin ? point.getX() : xMin; + xMax = point.getX() > xMax ? point.getX() : xMax; - yMin = point.getY() < yMin ? point.getY() : yMin; - yMax = point.getY() > yMax ? point.getY() : yMax; - } + yMin = point.getY() < yMin ? point.getY() : yMin; + yMax = point.getY() > yMax ? point.getY() : yMax; + } - //once we have the coordinates of lower left and upper right points, use them - startRange.add(xMin).add(yMin); - endRange.add(xMax).add(yMax); - } - } + // once we have the coordinates of lower left and upper right points, use them + startRange.add(xMin).add(yMin); + endRange.add(xMax).add(yMax); + } + } - /** - * Convert a {@link Shape} to a pair of {@link JsonArray} ranges, injected into startRange and endRange. - * - * @param startRange the startRange to populate with this shape's data. - * @param endRange the endRange to populate with this shape's data. - * @param shape the shape to extract ranges from. - * @throws IllegalArgumentException if the {@link Shape} is unsupported. - */ - public static void convertShapeTo2DRanges(JsonArray startRange, JsonArray endRange, Shape shape) { - if (shape instanceof Box) { - Box box = (Box) shape; - startRange //add minimum coordinates for x and y - .add(box.getFirst().getX()) - .add(box.getFirst().getY()); - endRange //add maximum coordinates for x and y - .add(box.getSecond().getX()) - .add(box.getSecond().getY()); - } else if (shape instanceof Polygon) { - //find the lowest and highest X and Y to get the bounding box - double xMin = Double.POSITIVE_INFINITY; - double yMin = Double.POSITIVE_INFINITY; - double xMax = Double.NEGATIVE_INFINITY; - double yMax = Double.NEGATIVE_INFINITY; - for (Point point : (Polygon) shape) { - xMin = point.getX() < xMin ? point.getX() : xMin; - xMax = point.getX() > xMax ? point.getX() : xMax; + /** + * Convert a {@link Shape} to a pair of {@link JsonArray} ranges, injected into startRange and endRange. + * + * @param startRange the startRange to populate with this shape's data. + * @param endRange the endRange to populate with this shape's data. + * @param shape the shape to extract ranges from. + * @throws IllegalArgumentException if the {@link Shape} is unsupported. + */ + public static void convertShapeTo2DRanges(JsonArray startRange, JsonArray endRange, Shape shape) { + if (shape instanceof Box) { + Box box = (Box) shape; + startRange // add minimum coordinates for x and y + .add(box.getFirst().getX()).add(box.getFirst().getY()); + endRange // add maximum coordinates for x and y + .add(box.getSecond().getX()).add(box.getSecond().getY()); + } else if (shape instanceof Polygon) { + // find the lowest and highest X and Y to get the bounding box + double xMin = Double.POSITIVE_INFINITY; + double yMin = Double.POSITIVE_INFINITY; + double xMax = Double.NEGATIVE_INFINITY; + double yMax = Double.NEGATIVE_INFINITY; + for (Point point : (Polygon) shape) { + xMin = point.getX() < xMin ? point.getX() : xMin; + xMax = point.getX() > xMax ? point.getX() : xMax; - yMin = point.getY() < yMin ? point.getY() : yMin; - yMax = point.getY() > yMax ? point.getY() : yMax; - } + yMin = point.getY() < yMin ? point.getY() : yMin; + yMax = point.getY() > yMax ? point.getY() : yMax; + } - //once we have the coordinates of lower left and upper right points, use them - startRange.add(xMin).add(yMin); - endRange.add(xMax).add(yMax); - } else if (shape instanceof Circle) { - //here the bounding box is the box that contains the circle - Circle circle = (Circle) shape; - Point center = circle.getCenter(); - double radius = circle.getRadius().getNormalizedValue(); + // once we have the coordinates of lower left and upper right points, use them + startRange.add(xMin).add(yMin); + endRange.add(xMax).add(yMax); + } else if (shape instanceof Circle) { + // here the bounding box is the box that contains the circle + Circle circle = (Circle) shape; + Point center = circle.getCenter(); + double radius = circle.getRadius().getNormalizedValue(); - double xMin = center.getX() - radius; - double xMax = center.getX() + radius; - double yMin = center.getY() - radius; - double yMax = center.getY() + radius; + double xMin = center.getX() - radius; + double xMax = center.getX() + radius; + double yMin = center.getY() - radius; + double yMax = center.getY() + radius; - startRange.add(xMin).add(yMin); - endRange.add(xMax).add(yMax); - } else { - throw new IllegalArgumentException("Unsupported shape " + shape.getClass().getName()); - } - } + startRange.add(xMin).add(yMin); + endRange.add(xMax).add(yMax); + } else { + throw new IllegalArgumentException("Unsupported shape " + shape.getClass().getName()); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlQueryCreatorUtils.java b/src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlQueryCreatorUtils.java index 4924a136..aa94c292 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlQueryCreatorUtils.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlQueryCreatorUtils.java @@ -16,25 +16,20 @@ package org.springframework.data.couchbase.repository.query.support; -import static com.couchbase.client.java.query.dsl.Expression.*; +import static org.springframework.data.couchbase.core.query.N1QLExpression.*; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; -import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import com.couchbase.client.java.document.json.JsonObject; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.couchbase.core.query.N1QLExpression; import org.springframework.data.couchbase.repository.query.ConvertingIterator; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.repository.query.parser.Part; -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.functions.PatternMatchingFunctions; -import com.couchbase.client.java.query.dsl.functions.StringFunctions; +import com.couchbase.client.java.json.JsonArray; /** * Utils for creating part tree expressions @@ -42,195 +37,196 @@ import com.couchbase.client.java.query.dsl.functions.StringFunctions; * @author Subhashni Balakrishnan */ public class N1qlQueryCreatorUtils { - public static Expression prepareExpression(CouchbaseConverter converter, Part part, Iterator iterator, AtomicInteger position, JsonArray placeHolderValues) { - PersistentPropertyPath path = N1qlUtils.getPathWithAlternativeFieldNames( - converter, part.getProperty()); - ConvertingIterator parameterValues = new ConvertingIterator(iterator, converter); + public static N1QLExpression prepareExpression(CouchbaseConverter converter, Part part, Iterator iterator, + AtomicInteger position, JsonArray placeHolderValues) { + PersistentPropertyPath path = N1qlUtils.getPathWithAlternativeFieldNames(converter, + part.getProperty()); + ConvertingIterator parameterValues = new ConvertingIterator(iterator, converter); - //get the whole doted path with fieldNames instead of potentially wrong propNames - String fieldNamePath = N1qlUtils.getDottedPathWithAlternativeFieldNames(path); + // get the whole doted path with fieldNames instead of potentially wrong propNames + String fieldNamePath = N1qlUtils.getDottedPathWithAlternativeFieldNames(path); - //deal with ignore case - boolean ignoreCase = false; - Class leafType = converter.getWriteClassFor(path.getLeafProperty().getType()); - boolean isString = leafType == String.class; - if (part.shouldIgnoreCase() == Part.IgnoreCaseType.WHEN_POSSIBLE) { - ignoreCase = isString; - } else if (part.shouldIgnoreCase() == Part.IgnoreCaseType.ALWAYS) { - if (!isString) { - throw new IllegalArgumentException(String.format("Part %s must be of type String but was %s", fieldNamePath, leafType)); - } - ignoreCase = true; - } + // deal with ignore case + boolean ignoreCase = false; + Class leafType = converter.getWriteClassFor(path.getLeafProperty().getType()); + boolean isString = leafType == String.class; + if (part.shouldIgnoreCase() == Part.IgnoreCaseType.WHEN_POSSIBLE) { + ignoreCase = isString; + } else if (part.shouldIgnoreCase() == Part.IgnoreCaseType.ALWAYS) { + if (!isString) { + throw new IllegalArgumentException( + String.format("Part %s must be of type String but was %s", fieldNamePath, leafType)); + } + ignoreCase = true; + } - return createExpression(part.getType(), fieldNamePath, ignoreCase, parameterValues, position, placeHolderValues); - } + return createExpression(part.getType(), fieldNamePath, ignoreCase, parameterValues, position, placeHolderValues); + } + public static N1QLExpression createExpression(Part.Type partType, String fieldNamePath, boolean ignoreCase, + Iterator parameterValues, AtomicInteger position, JsonArray placeHolderValues) { + // create the left hand side of the expression, taking ignoreCase into account - public static Expression createExpression(Part.Type partType, String fieldNamePath, boolean ignoreCase, - Iterator parameterValues, AtomicInteger position, JsonArray placeHolderValues) { - //create the left hand side of the expression, taking ignoreCase into account + N1QLExpression left = ignoreCase ? (x(fieldNamePath).lower()) : x(fieldNamePath); + N1QLExpression exp; + switch (partType) { + case BETWEEN: + exp = left.between(getPlaceHolder(position, ignoreCase).and(getPlaceHolder(position, ignoreCase))); + placeHolderValues.add(getValue(parameterValues)); + placeHolderValues.add(getValue(parameterValues)); + break; + case IS_NOT_NULL: + exp = left.isNotNull(); + break; + case IS_NULL: + exp = left.isNull(); + break; + case NEGATING_SIMPLE_PROPERTY: + exp = left.ne(getPlaceHolder(position, ignoreCase)); + placeHolderValues.add(getValue(parameterValues)); + break; + case SIMPLE_PROPERTY: + exp = left.eq(getPlaceHolder(position, ignoreCase)); + placeHolderValues.add(getValue(parameterValues)); + break; + case BEFORE: + case LESS_THAN: + exp = left.lt(getPlaceHolder(position, ignoreCase)); + placeHolderValues.add(getValue(parameterValues)); + break; + case LESS_THAN_EQUAL: + exp = left.lte(getPlaceHolder(position, ignoreCase)); + placeHolderValues.add(getValue(parameterValues)); + break; + case GREATER_THAN_EQUAL: + exp = left.gte(getPlaceHolder(position, ignoreCase)); + placeHolderValues.add(getValue(parameterValues)); + break; + case AFTER: + case GREATER_THAN: + exp = left.gt(getPlaceHolder(position, ignoreCase)); + placeHolderValues.add(getValue(parameterValues)); + break; + case NOT_LIKE: + exp = left.notLike(getPlaceHolder(position, ignoreCase)); + placeHolderValues.add(getValue(parameterValues)); + break; + case LIKE: + exp = left.like(getPlaceHolder(position, ignoreCase)); + placeHolderValues.add(getValue(parameterValues)); + break; + case STARTING_WITH: + exp = left.like(x(getPlaceHolder(position, ignoreCase) + " || '%'")); + placeHolderValues.add(getValue(parameterValues)); + break; + case ENDING_WITH: + exp = left.like(x("'%' || " + getPlaceHolder(position, ignoreCase))); + placeHolderValues.add(getValue(parameterValues)); + break; + case NOT_CONTAINING: + exp = left.notLike(x("'%' || " + getPlaceHolder(position, ignoreCase) + " || '%'")); + placeHolderValues.add(getValue(parameterValues)); + break; + case CONTAINING: + exp = left.like(x("'%' || " + getPlaceHolder(position, ignoreCase) + " || '%'")); + placeHolderValues.add(getValue(parameterValues)); + break; + case NOT_IN: + exp = left.notIn(getPlaceHolder(position, false)); + placeHolderValues.add(getArray(parameterValues)); + break; + case IN: + exp = left.in(getPlaceHolder(position, false)); + placeHolderValues.add(getArray(parameterValues)); + break; + case TRUE: + exp = left.eq(true); + break; + case FALSE: + exp = left.eq(false); + break; + case REGEX: + exp = x("REGEXP_LIKE(" + left.toString() + ", " + getPlaceHolder(position, false) + ")"); + placeHolderValues.add(getValueAsString(parameterValues)); + break; + case EXISTS: + exp = left.isNotMissing(); + break; + case WITHIN: + case NEAR: + default: + throw new IllegalArgumentException("Unsupported keyword in N1QL query derivation"); + } + return exp; + } - Expression left = ignoreCase ? StringFunctions.lower(x(fieldNamePath)) : x(fieldNamePath); - Expression exp; - switch (partType) { - case BETWEEN: - exp = left.between(x(getPlaceHolder(position, ignoreCase)).and(x(getPlaceHolder(position, ignoreCase)))); - placeHolderValues.add(getValue(parameterValues)); - placeHolderValues.add(getValue(parameterValues)); - break; - case IS_NOT_NULL: - exp = left.isNotNull(); - break; - case IS_NULL: - exp = left.isNull(); - break; - case NEGATING_SIMPLE_PROPERTY: - exp = left.ne(getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case SIMPLE_PROPERTY: - exp = left.eq(getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case BEFORE: - case LESS_THAN: - exp = left.lt(getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case LESS_THAN_EQUAL: - exp = left.lte(getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case GREATER_THAN_EQUAL: - exp = left.gte(getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case AFTER: - case GREATER_THAN: - exp = left.gt(getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case NOT_LIKE: - exp = left.notLike(getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case LIKE: - exp = left.like(getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case STARTING_WITH: - exp = left.like(getPlaceHolder(position, ignoreCase) + " || '%'"); - placeHolderValues.add(getValue(parameterValues)); - break; - case ENDING_WITH: - exp = left.like("'%' || " + getPlaceHolder(position, ignoreCase)); - placeHolderValues.add(getValue(parameterValues)); - break; - case NOT_CONTAINING: - exp = left.notLike("'%' || " + getPlaceHolder(position, ignoreCase) + " || '%'"); - placeHolderValues.add(getValue(parameterValues)); - break; - case CONTAINING: - exp = left.like("'%' || " + getPlaceHolder(position, ignoreCase) + " || '%'"); - placeHolderValues.add(getValue(parameterValues)); - break; - case NOT_IN: - exp = left.notIn(getPlaceHolder(position, false)); - placeHolderValues.add(getArray(parameterValues)); - break; - case IN: - exp = left.in(getPlaceHolder(position, false)); - placeHolderValues.add(getArray(parameterValues)); - break; - case TRUE: - exp = left.eq(true); - break; - case FALSE: - exp = left.eq(false); - break; - case REGEX: - exp = x("REGEXP_LIKE(" + left.toString() + ", " + getPlaceHolder(position, false) + ")"); - placeHolderValues.add(getValueAsString(parameterValues)); - break; - case EXISTS: - exp = left.isNotMissing(); - break; - case WITHIN: - case NEAR: - default: - throw new IllegalArgumentException("Unsupported keyword in N1QL query derivation"); - } - return exp; - } + protected static N1QLExpression getPlaceHolder(AtomicInteger position, boolean ignoreCase) { + N1QLExpression placeHolder = x("$" + position.getAndIncrement()); + if (ignoreCase) { + placeHolder = placeHolder.lower(); + } + return placeHolder; + } - protected static String getPlaceHolder(AtomicInteger position, boolean ignoreCase) { - String placeHolder = "$" + position.getAndIncrement(); - if (ignoreCase) { - placeHolder = StringFunctions.lower(x(placeHolder)).toString(); - } - return placeHolder; - } + protected static String getValueAsString(Iterator parameterValues) { + Object next = parameterValues.next(); - protected static String getValueAsString(Iterator parameterValues) { - Object next = parameterValues.next(); + String pattern; + if (next == null) { + pattern = ""; + } else { + pattern = String.valueOf(next); + } + return pattern; + } - String pattern; - if (next == null) { - pattern = ""; - } else { - pattern = String.valueOf(next); - } - return pattern; - } + protected static N1QLExpression like(Iterator parameterValues, boolean ignoreCase, boolean anyPrefix, + boolean anySuffix) { + Object next = parameterValues.next(); + if (next == null) { + return N1QLExpression.NULL(); + } - protected static Expression like(Iterator parameterValues, boolean ignoreCase, - boolean anyPrefix, boolean anySuffix) { - Object next = parameterValues.next(); - if (next == null) { - return Expression.NULL(); - } + N1QLExpression converted; + if (next instanceof String) { + String pattern = (String) next; + if (anyPrefix) { + pattern = "%" + pattern; + } + if (anySuffix) { + pattern = pattern + "%"; + } + converted = s(pattern); - Expression converted; - if (next instanceof String) { - String pattern = (String) next; - if (anyPrefix) { - pattern = "%" + pattern; - } - if (anySuffix) { - pattern = pattern + "%"; - } - converted = s(pattern); + } else { + converted = x(String.valueOf(next)); + } - } else { - converted = x(String.valueOf(next)); - } + if (ignoreCase) { + return converted.lower(); + } + return converted; + } - if (ignoreCase) { - return StringFunctions.lower(converted); - } - return converted; - } + protected static Object getValue(Iterator parameterValues) { + Object next = parameterValues.next(); + if (next instanceof Enum) { + next = String.valueOf(next); + } + return next; + } - protected static Object getValue(Iterator parameterValues) { - Object next = parameterValues.next(); - if (next instanceof Enum) { - next = String.valueOf(next); - } - return next; - } + protected static JsonArray getArray(Iterator parameterValues) { + Object next = parameterValues.next(); - protected static JsonArray getArray(Iterator parameterValues) { - Object next = parameterValues.next(); - - Object[] values; - if (next instanceof Collection) { - values = ((Collection) next).toArray(); - } else if (next.getClass().isArray()) { - values = (Object[]) next; - } else { - values = new Object[] {next}; - } - return JsonArray.from(values); - } + Object[] values; + if (next instanceof Collection) { + values = ((Collection) next).toArray(); + } else if (next.getClass().isArray()) { + values = (Object[]) next; + } else { + values = new Object[] { next }; + } + return JsonArray.from(values); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlUtils.java b/src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlUtils.java index 4495521a..522b018e 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlUtils.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/support/N1qlUtils.java @@ -16,11 +16,7 @@ package org.springframework.data.couchbase.repository.query.support; -import static com.couchbase.client.java.query.Select.*; -import static com.couchbase.client.java.query.dsl.Expression.*; -import static com.couchbase.client.java.query.dsl.functions.AggregateFunctions.*; -import static com.couchbase.client.java.query.dsl.functions.MetaFunctions.*; -import static com.couchbase.client.java.query.dsl.functions.StringFunctions.*; +import static org.springframework.data.couchbase.core.query.N1QLExpression.*; import static org.springframework.data.couchbase.core.support.TemplateUtils.*; import java.util.ArrayList; @@ -30,6 +26,8 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.couchbase.core.query.N1QLExpression; +import org.springframework.data.couchbase.core.query.N1QLQuery; import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; import org.springframework.data.couchbase.repository.query.CountFragment; import org.springframework.data.domain.Sort; @@ -38,22 +36,15 @@ import org.springframework.data.mapping.PropertyPath; import org.springframework.data.repository.core.EntityMetadata; import org.springframework.data.repository.query.ReturnedType; -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.functions.TypeFunctions; -import com.couchbase.client.java.query.dsl.path.FromPath; -import com.couchbase.client.java.query.dsl.path.WherePath; -import com.couchbase.client.java.repository.annotation.Field; +import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.json.JsonValue; +import com.couchbase.client.java.query.QueryOptions; +import com.couchbase.client.java.query.QueryScanConsistency; /** - * Utility class to deal with constructing well formed N1QL queries around Spring Data entities, so that - * the framework can use N1QL to find such entities (eg. restrict the bucket search to a particular type). + * Utility class to deal with constructing well formed N1QL queries around Spring Data entities, so that the framework + * can use N1QL to find such entities (eg. restrict the bucket search to a particular type). * * @author Simon Baslé * @author Subhashni Balakrishnan @@ -61,215 +52,217 @@ import com.couchbase.client.java.repository.annotation.Field; */ public class N1qlUtils { - /** - * A converter that can be used to extract the {@link CouchbasePersistentProperty#getFieldName() fieldName}, - * eg. when one wants a path from {@link PersistentPropertyPath#toDotPath(Converter)} made of escaped field names. - */ - public static final Converter FIELD_NAME_ESCAPED = - new Converter() { - @Override - public String convert(CouchbasePersistentProperty source) { - return "`" + source.getFieldName() + "`"; - } - }; + /** + * A converter that can be used to extract the {@link CouchbasePersistentProperty#getFieldName() fieldName}, eg. when + * one wants a path from {@link PersistentPropertyPath#toDotPath(Converter)} made of escaped field names. + */ + public static final Converter FIELD_NAME_ESCAPED = new Converter() { + @Override + public String convert(CouchbasePersistentProperty source) { + return "`" + source.getFieldName() + "`"; + } + }; - /** - * Escape the given bucketName and produce an {@link Expression}. - */ - public static Expression escapedBucket(String bucketName) { - return i(bucketName); - } + /** + * Escape the given bucketName and produce an {@link N1QLExpression}. + */ + public static N1QLExpression escapedBucket(String bucketName) { + return i(bucketName); + } - /** - * Produce a {@link Statement} that corresponds to the SELECT clause for looking for Spring Data entities - * stored in Couchbase. Notably it will select the content of the document AND its id and cas and use custom - * construction of query if required. - * - * @param bucketName the bucket that stores the entity documents (will be escaped). - * @param returnedType Returned type projection information from result processor. - * @param converter couchbase converter - * @return the needed SELECT clause of the statement. - */ - public static FromPath createSelectClauseForEntity(String bucketName, ReturnedType returnedType, CouchbaseConverter converter) { - Expression bucket = escapedBucket(bucketName); - Expression metaId = path(meta(bucket), "id").as(SELECT_ID); - Expression metaCas = path(meta(bucket), "cas").as(SELECT_CAS); - List expList = new ArrayList(); - expList.add(metaId); - expList.add(metaCas); + /** + * Produce a {@link N1QLExpression} that corresponds to the SELECT clause for looking for Spring Data entities stored + * in Couchbase. Notably it will select the content of the document AND its id and cas and use custom construction of + * query if required. + * + * @param bucketName the bucket that stores the entity documents (will be escaped). + * @param returnedType Returned type projection information from result processor. + * @param converter couchbase converter + * @return the needed SELECT clause of the statement. + */ + public static N1QLExpression createSelectClauseForEntity(String bucketName, ReturnedType returnedType, + CouchbaseConverter converter) { + N1QLExpression bucket = escapedBucket(bucketName); + N1QLExpression metaId = path(meta(bucket), "id").as(x(SELECT_ID)); + N1QLExpression metaCas = path(meta(bucket), "cas").as(x(SELECT_CAS)); + List expList = new ArrayList<>(); + expList.add(metaId); + expList.add(metaCas); - if (returnedType != null && returnedType.needsCustomConstruction()) { - List properties = returnedType.getInputProperties(); - CouchbasePersistentEntity entity = converter.getMappingContext().getRequiredPersistentEntity(returnedType.getDomainType()); + if (returnedType != null && returnedType.needsCustomConstruction()) { + List properties = returnedType.getInputProperties(); + CouchbasePersistentEntity entity = converter.getMappingContext() + .getRequiredPersistentEntity(returnedType.getDomainType()); - for (String property : properties) { - expList.add(path(bucket, i(entity.getRequiredPersistentProperty(property).getFieldName()))); - } - } else { - expList.add(path(bucket, "*")); - } + for (String property : properties) { + expList.add(path(bucket, i(entity.getRequiredPersistentProperty(property).getFieldName()))); + } + } else { + expList.add(path(bucket, "*")); + } - Expression[] propertiesExp = new Expression[expList.size()]; - propertiesExp = expList.toArray(propertiesExp); + N1QLExpression[] propertiesExp = new N1QLExpression[expList.size()]; + propertiesExp = expList.toArray(propertiesExp); - return select(propertiesExp); - } + return select(propertiesExp); + } - /** - * Creates the returning clause for N1ql deletes with all attributes of the entity and meta information - * - * @param bucketName the bucket that stores the entity documents (will be escaped). - * @return the needed returning clause of the statement. - */ - public static Expression createReturningExpressionForDelete(String bucketName) { - Expression fullEntity = path(i(bucketName), "*"); - Expression metaId = path(meta(i(bucketName)), "id").as(SELECT_ID); - Expression metaCas = path(meta(i(bucketName)), "cas").as(SELECT_CAS); - List expList = new ArrayList(); - expList.add(fullEntity); - expList.add(metaId); - expList.add(metaCas); + /** + * Creates the returning clause for N1ql deletes with all attributes of the entity and meta information + * + * @param bucketName the bucket that stores the entity documents (will be escaped). + * @return the needed returning clause of the statement. + */ + public static N1QLExpression createReturningExpressionForDelete(String bucketName) { + N1QLExpression fullEntity = path(i(bucketName), "*"); + N1QLExpression metaId = path(meta(i(bucketName)), "id").as(x(SELECT_ID)); + N1QLExpression metaCas = path(meta(i(bucketName)), "cas").as(x(SELECT_CAS)); + List expList = new ArrayList<>(); + expList.add(fullEntity); + expList.add(metaId); + expList.add(metaCas); - StringBuilder sb = new StringBuilder(); - for (Expression exp: expList) { - if (sb.length() != 0) { - sb.append(", "); - } - sb.append(exp.toString()); - } + StringBuilder sb = new StringBuilder(); + for (N1QLExpression exp : expList) { + if (sb.length() != 0) { + sb.append(", "); + } + sb.append(exp.toString()); + } - return x(sb.toString()); - } + return x(sb.toString()); + } - /** - * Produce a {@link Statement} that corresponds to the SELECT clause for looking for Spring Data entities - * stored in Couchbase. Notably it will select the content of the document AND its id and cas. - * - * @param bucketName the bucket that stores the entity documents (will be escaped). - * @return the needed SELECT clause of the statement. - */ - public static FromPath createSelectClauseForEntity(String bucketName) { - return createSelectClauseForEntity(bucketName, null, null); - } + /** + * Produce a {@link N1QLExpression} that corresponds to the SELECT clause for looking for Spring Data entities stored + * in Couchbase. Notably it will select the content of the document AND its id and cas. + * + * @param bucketName the bucket that stores the entity documents (will be escaped). + * @return the needed SELECT clause of the statement. + */ + public static N1QLExpression createSelectClauseForEntity(String bucketName) { + return createSelectClauseForEntity(bucketName, null, null); + } - /** - * Produce a {@link Statement} that corresponds to the SELECT...FROM clauses for looking for Spring Data entities - * stored in Couchbase. Notably it will select the content of the document AND its id and cas FROM the given bucket. - * - * @param bucketName the bucket that stores the entity documents (will be escaped). - * @return the needed SELECT...FROM clauses of the statement. - */ - public static WherePath createSelectFromForEntity(String bucketName) { - return createSelectClauseForEntity(bucketName).from(escapedBucket(bucketName)); - } + /** + * Produce a {@link N1QLExpression} that corresponds to the SELECT...FROM clauses for looking for Spring Data entities + * stored in Couchbase. Notably it will select the content of the document AND its id and cas FROM the given bucket. + * + * @param bucketName the bucket that stores the entity documents (will be escaped). + * @return the needed SELECT...FROM clauses of the statement. + */ + public static N1QLExpression createSelectFromForEntity(String bucketName) { + return createSelectClauseForEntity(bucketName).from(bucketName); + } - /** - * Produces an {@link Expression} that can serve as a WHERE clause criteria to only select documents in a bucket - * that matches a particular Spring Data entity (as given by the {@link EntityMetadata} parameter). - * - * @param baseWhereCriteria the other criteria of the WHERE clause, or null if none. - * @param converter the {@link CouchbaseConverter} giving the attribute storing the type information can be extracted. - * @param entityInformation the expected type information. - * @return an {@link Expression} to be used as a WHERE clause, that additionally restricts on the given type. - */ - public static Expression createWhereFilterForEntity(Expression baseWhereCriteria, CouchbaseConverter converter, - EntityMetadata entityInformation) { - //add part that filters on type key - String typeKey = converter.getTypeKey(); - String typeValue = entityInformation.getJavaType().getName(); - Expression typeSelector = i(typeKey).eq(s(typeValue)); - if (baseWhereCriteria == null) { - baseWhereCriteria = typeSelector; - } else { - baseWhereCriteria = x("(" + baseWhereCriteria.toString() + ")").and(typeSelector); - } - return baseWhereCriteria; - } + /** + * Produces an {@link N1QLExpression} that can serve as a WHERE clause criteria to only select documents in a bucket + * that matches a particular Spring Data entity (as given by the {@link EntityMetadata} parameter). + * + * @param baseWhereCriteria the other criteria of the WHERE clause, or null if none. + * @param converter the {@link CouchbaseConverter} giving the attribute storing the type information can be extracted. + * @param entityInformation the expected type information. + * @return an {@link N1QLExpression} to be used as a WHERE clause, that additionally restricts on the given type. + */ + public static N1QLExpression createWhereFilterForEntity(N1QLExpression baseWhereCriteria, + CouchbaseConverter converter, EntityMetadata entityInformation) { + // add part that filters on type key + String typeKey = converter.getTypeKey(); + String typeValue = entityInformation.getJavaType().getName(); + N1QLExpression typeSelector = i(typeKey).eq(s(typeValue)); + if (baseWhereCriteria == null) { + baseWhereCriteria = typeSelector; + } else { + baseWhereCriteria = x("(" + baseWhereCriteria.toString() + ")").and(typeSelector); + } + return baseWhereCriteria; + } - /** - * Given a common {@link PropertyPath}, returns the corresponding {@link PersistentPropertyPath} - * of {@link CouchbasePersistentProperty} which will allow to discover alternative naming for fields. - */ - public static PersistentPropertyPath getPathWithAlternativeFieldNames( - CouchbaseConverter converter, PropertyPath property) { - PersistentPropertyPath path = converter.getMappingContext() - .getPersistentPropertyPath(property); - return path; - } + /** + * Given a common {@link PropertyPath}, returns the corresponding {@link PersistentPropertyPath} of + * {@link CouchbasePersistentProperty} which will allow to discover alternative naming for fields. + */ + public static PersistentPropertyPath getPathWithAlternativeFieldNames( + CouchbaseConverter converter, PropertyPath property) { + PersistentPropertyPath path = converter.getMappingContext() + .getPersistentPropertyPath(property); + return path; + } - /** - * Given a {@link PersistentPropertyPath} of {@link CouchbasePersistentProperty} - * (see {@link #getPathWithAlternativeFieldNames(CouchbaseConverter, PropertyPath)}), - * obtain a String representation of the path, separated with dots and using alternative field names. - */ - public static String getDottedPathWithAlternativeFieldNames(PersistentPropertyPath path) { - return path.toDotPath(FIELD_NAME_ESCAPED); - } + /** + * Given a {@link PersistentPropertyPath} of {@link CouchbasePersistentProperty} (see + * {@link #getPathWithAlternativeFieldNames(CouchbaseConverter, PropertyPath)}), obtain a String representation of the + * path, separated with dots and using alternative field names. + */ + public static String getDottedPathWithAlternativeFieldNames( + PersistentPropertyPath path) { + return path.toDotPath(FIELD_NAME_ESCAPED); + } - /** - * Create a N1QL {@link com.couchbase.client.java.query.dsl.Sort} out of a Spring Data {@link Sort}. Note that the later - * must use alternative field names as declared by the {@link Field} annotation on the entity, if any. - */ - public static com.couchbase.client.java.query.dsl.Sort[] createSort(Sort sort, CouchbaseConverter converter) { - List cbSortList = new ArrayList(); - for (Sort.Order order : sort) { - String orderProperty = order.getProperty(); - //FIXME the order property should be converted to its corresponding fieldName - String[] orderPropertyParts = orderProperty.split("\\."); + /** + * Create a N1QL {@link N1QLExpression} out of a Spring Data {@link Sort}. Note that the later must use alternative + * field names as declared by the {@link Field} annotation on the entity, if any. + */ + public static N1QLExpression[] createSort(Sort sort) { + List cbSortList = new ArrayList<>(); + for (Sort.Order order : sort) { + String orderProperty = order.getProperty(); + // FIXME the order property should be converted to its corresponding fieldName + String[] orderPropertyParts = orderProperty.split("\\."); - StringBuilder sb = new StringBuilder(); - for (String part:orderPropertyParts) { - if (sb.length() != 0) { - sb.append("."); - } - sb.append(i(part).toString()); - } - Expression orderFieldName = x(sb.toString()); - if (order.isIgnoreCase()) { - orderFieldName = lower(TypeFunctions.toString(orderFieldName)); - } - if (order.isAscending()) { - cbSortList.add(com.couchbase.client.java.query.dsl.Sort.asc(orderFieldName)); - } else { - cbSortList.add(com.couchbase.client.java.query.dsl.Sort.desc(orderFieldName)); - } - } - return cbSortList.toArray(new com.couchbase.client.java.query.dsl.Sort[cbSortList.size()]); - } + StringBuilder sb = new StringBuilder(); + for (String part : orderPropertyParts) { + if (sb.length() != 0) { + sb.append("."); + } + sb.append(i(part).toString()); + } + N1QLExpression orderFieldName = x(sb.toString()); + if (order.isIgnoreCase()) { + orderFieldName = orderFieldName.convertToString().lower(); + } + if (order.isAscending()) { + cbSortList.add(orderFieldName.asc()); + } else { + cbSortList.add(orderFieldName.desc()); + } + } + return cbSortList.toArray(new N1QLExpression[cbSortList.size()]); + } - /** - * Creates a full N1QL query that counts total number of the given entity in the bucket. - * - * @param bucketName the name of the bucket where data is stored (will be escaped). - * @param converter the {@link CouchbaseConverter} giving the attribute storing the type information can be extracted. - * @param entityInformation the counted entity type. - * @return the N1QL query that counts number of documents matching this entity type. - */ - public static Statement createCountQueryForEntity(String bucketName, CouchbaseConverter converter, CouchbaseEntityInformation entityInformation) { - return select(count("*").as(CountFragment.COUNT_ALIAS)).from(escapedBucket(bucketName)).where(createWhereFilterForEntity(null, converter, entityInformation)); - } + /** + * Creates a full N1QL query that counts total number of the given entity in the bucket. + * + * @param bucketName the name of the bucket where data is stored (will be escaped). + * @param converter the {@link CouchbaseConverter} giving the attribute storing the type information can be extracted. + * @param entityInformation the counted entity type. + * @return the N1QL query that counts number of documents matching this entity type. + */ + public static N1QLExpression createCountQueryForEntity(String bucketName, CouchbaseConverter converter, + CouchbaseEntityInformation entityInformation) { + return select(count(x("*")).as(x(CountFragment.COUNT_ALIAS))).from(escapedBucket(bucketName)) + .where(createWhereFilterForEntity(null, converter, entityInformation)); + } - /** - * Creates N1QLQuery object from the statement, query placeholder values and scan consistency - * - * @param statement - * @param queryPlaceholderValues - * @param scanConsistency - * @return - */ - public static N1qlQuery buildQuery(Statement statement, JsonValue queryPlaceholderValues, ScanConsistency scanConsistency) { - N1qlParams n1qlParams = N1qlParams.build().consistency(scanConsistency); - N1qlQuery query; - - if (queryPlaceholderValues instanceof JsonObject && !((JsonObject) queryPlaceholderValues).isEmpty()) { - query = N1qlQuery.parameterized(statement, (JsonObject) queryPlaceholderValues, n1qlParams); - } else if (queryPlaceholderValues instanceof JsonArray && !((JsonArray) queryPlaceholderValues).isEmpty()) { - query = N1qlQuery.parameterized(statement, (JsonArray) queryPlaceholderValues, n1qlParams); - } else { - query = N1qlQuery.simple(statement, n1qlParams); - } - return query; - } + /** + * Creates N1QLQuery object from the statement, query placeholder values and scan consistency + * + * @param expression A {@link N1QLExpression} representing the query to execute + * @param queryPlaceholderValues The positional or named parameters needed for the query + * @param scanConsistency The {@link QueryScanConsistency} to be used. + * @return A {@link N1QLQuery} to be executed. + */ + public static N1QLQuery buildQuery(N1QLExpression expression, JsonValue queryPlaceholderValues, + QueryScanConsistency scanConsistency) { + QueryOptions opts = QueryOptions.queryOptions().scanConsistency(scanConsistency); + // put the placeholders in the options + if (queryPlaceholderValues instanceof JsonObject && !((JsonObject) queryPlaceholderValues).isEmpty()) { + opts.parameters((JsonObject) queryPlaceholderValues); + } else if (queryPlaceholderValues instanceof JsonArray && !((JsonArray) queryPlaceholderValues).isEmpty()) { + opts.parameters((JsonArray) queryPlaceholderValues); + } + return new N1QLQuery(expression, opts); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/support/PointInShapeEvaluator.java b/src/main/java/org/springframework/data/couchbase/repository/query/support/PointInShapeEvaluator.java index 2856d186..aca86186 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/support/PointInShapeEvaluator.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/support/PointInShapeEvaluator.java @@ -28,167 +28,156 @@ import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; /** - * PointInShapeEvaluator can be used to tell if a particular {@link Point} is contained by a - * {@link Polygon} or {@link Circle}. It is most useful to eliminate false positive when a - * geo query has been made using a {@link Box bounding box} approximation. - * - * For the purpose of this class, a point on the edge of a polygon isn't considered within it. - * On the contrary a point on the edge of a circle is considered in it (distance from center <= radius). - * - * To that end, {@link #removeFalsePositives(Collection, Converter, Circle) additional methods} - * that return a {@link List} of objects with false positives removed are also provided. However, - * these need a {@link Converter Converter<T, Point>} to extract the location attribute that - * should be tested against the polygon/circle. + * PointInShapeEvaluator can be used to tell if a particular {@link Point} is contained by a {@link Polygon} or + * {@link Circle}. It is most useful to eliminate false positive when a geo query has been made using a {@link Box + * bounding box} approximation. For the purpose of this class, a point on the edge of a polygon isn't considered within + * it. On the contrary a point on the edge of a circle is considered in it (distance from center <= radius). To that + * end, {@link #removeFalsePositives(Collection, Converter, Circle) additional methods} that return a {@link List} of + * objects with false positives removed are also provided. However, these need a {@link Converter Converter<T, + * Point>} to extract the location attribute that should be tested against the polygon/circle. * * @author Simon Baslé * @see AwtPointInShapeEvaluator for a simple implementation based on AWT standard library. */ public abstract class PointInShapeEvaluator { - /** - * Determine if a {@link Point} is contained by a {@link Polygon}. - * - * @param p the point to test. - * @param polygon the polygon we want the point to be in. - * @return true if the polygon contains the point, false otherwise. - */ - public abstract boolean pointInPolygon(Point p, Polygon polygon); + /** + * Determine if a {@link Point} is contained by a {@link Polygon}. + * + * @param p the point to test. + * @param polygon the polygon we want the point to be in. + * @return true if the polygon contains the point, false otherwise. + */ + public abstract boolean pointInPolygon(Point p, Polygon polygon); - /** - * Determine if a {@link Point} is contained by a polygon represented - * as an array of {@link Point points}. - * - * The points are not required to form a closed shape, but can (by having the - * first and last points be the same). - * - * @param p the point to test. - * @param points the Point[] representation of the polygon we want the point to be in. - * @return true if the polygon contains the point, false otherwise. - */ - public abstract boolean pointInPolygon(Point p, Point... points); + /** + * Determine if a {@link Point} is contained by a polygon represented as an array of {@link Point points}. The points + * are not required to form a closed shape, but can (by having the first and last points be the same). + * + * @param p the point to test. + * @param points the Point[] representation of the polygon we want the point to be in. + * @return true if the polygon contains the point, false otherwise. + */ + public abstract boolean pointInPolygon(Point p, Point... points); - /** - * Determine if a {@link Point} is contained by a {@link Circle}. - * - * @param p the point to test. - * @param c the Circle we want the point to be in. - * @return true if the circle contains the point, false otherwise. - */ - public abstract boolean pointInCircle(Point p, Circle c); + /** + * Determine if a {@link Point} is contained by a {@link Circle}. + * + * @param p the point to test. + * @param c the Circle we want the point to be in. + * @return true if the circle contains the point, false otherwise. + */ + public abstract boolean pointInCircle(Point p, Circle c); - /** - * Determine if a {@link Point} is contained by a {@link Circle} represented - * by its {@link Point center Point} and {@link Distance Distance radius}. - * - * @param p the point to test. - * @param center the center Point of the Circle we want the point to be in. - * @param radius the Distance radius of the Circle we want the point to be in. - * @return true if the circle contains the point, false otherwise. - */ - public abstract boolean pointInCircle(Point p, Point center, Distance radius); + /** + * Determine if a {@link Point} is contained by a {@link Circle} represented by its {@link Point center Point} and + * {@link Distance Distance radius}. + * + * @param p the point to test. + * @param center the center Point of the Circle we want the point to be in. + * @param radius the Distance radius of the Circle we want the point to be in. + * @return true if the circle contains the point, false otherwise. + */ + public abstract boolean pointInCircle(Point p, Point center, Distance radius); - /** - * Utility method to remove false positives from a collection of objects that have a - * notion of location, where we want to only include values that are located within a polygon. - * - * The collection should usually be already contained in the bounding box approximation of - * the polygon for maximum efficiency. - * - * @param boundingBoxResults the collections of located objects approximately inside the target polygon. - * @param locationExtractor a {@link Converter} to extract the location of the value objects. - * @param polygon the target polygon. - * @param the type of located value objects in the collection. - * @return a {@link List} of the value objects which location has been verified to actually be contained within the polygon. - */ - public List removeFalsePositives(Collection boundingBoxResults, - Converter locationExtractor, Polygon polygon) { - ArrayList result = new ArrayList(boundingBoxResults.size()); - for (T boxResult : boundingBoxResults) { - Point p = locationExtractor.convert(boxResult); - if (pointInPolygon(p, polygon)) { - result.add(boxResult); - } - } - result.trimToSize(); - return result; - } + /** + * Utility method to remove false positives from a collection of objects that have a notion of location, where we want + * to only include values that are located within a polygon. The collection should usually be already contained in the + * bounding box approximation of the polygon for maximum efficiency. + * + * @param boundingBoxResults the collections of located objects approximately inside the target polygon. + * @param locationExtractor a {@link Converter} to extract the location of the value objects. + * @param polygon the target polygon. + * @param the type of located value objects in the collection. + * @return a {@link List} of the value objects which location has been verified to actually be contained within the + * polygon. + */ + public List removeFalsePositives(Collection boundingBoxResults, + Converter locationExtractor, Polygon polygon) { + ArrayList result = new ArrayList(boundingBoxResults.size()); + for (T boxResult : boundingBoxResults) { + Point p = locationExtractor.convert(boxResult); + if (pointInPolygon(p, polygon)) { + result.add(boxResult); + } + } + result.trimToSize(); + return result; + } - /** - * Utility method to remove false positives from a collection of objects that have a - * notion of location, where we want to only include values that are located within a circle. - * - * The collection should usually be already contained in the bounding box approximation of - * the circle for maximum efficiency. - * - * @param boundingBoxResults the collections of located objects approximately inside the target circle. - * @param locationExtractor a {@link Converter} to extract the location of the value objects. - * @param circle the target circle. - * @param the type of located value objects in the collection. - * @return a {@link List} of the value objects which location has been verified to actually be contained within the circle. - */ - public List removeFalsePositives(Collection boundingBoxResults, - Converter locationExtractor, Circle circle) { - ArrayList result = new ArrayList(boundingBoxResults.size()); - for (T boxResult : boundingBoxResults) { - Point p = locationExtractor.convert(boxResult); - if (pointInCircle(p, circle)) { - result.add(boxResult); - } - } - result.trimToSize(); - return result; - } + /** + * Utility method to remove false positives from a collection of objects that have a notion of location, where we want + * to only include values that are located within a circle. The collection should usually be already contained in the + * bounding box approximation of the circle for maximum efficiency. + * + * @param boundingBoxResults the collections of located objects approximately inside the target circle. + * @param locationExtractor a {@link Converter} to extract the location of the value objects. + * @param circle the target circle. + * @param the type of located value objects in the collection. + * @return a {@link List} of the value objects which location has been verified to actually be contained within the + * circle. + */ + public List removeFalsePositives(Collection boundingBoxResults, + Converter locationExtractor, Circle circle) { + ArrayList result = new ArrayList(boundingBoxResults.size()); + for (T boxResult : boundingBoxResults) { + Point p = locationExtractor.convert(boxResult); + if (pointInCircle(p, circle)) { + result.add(boxResult); + } + } + result.trimToSize(); + return result; + } - /** - * Utility method to remove false positives from a collection of objects that have a - * notion of location, where we want to only include values that are located within a polygon. - * - * The collection should usually be already contained in the bounding box approximation of - * the polygon for maximum efficiency. - * - * @param boundingBoxResults the collections of located objects approximately inside the target polygon. - * @param locationExtractor a {@link Converter} to extract the location of the value objects. - * @param polygon the target polygon, as an array of {@link Point} (not necessarily closed). - * @param the type of located value objects in the collection. - * @return a {@link List} of the value objects which location has been verified to actually be contained within the polygon. - */ - public List removeFalsePositives(Collection boundingBoxResults, - Converter locationExtractor, Point... polygon) { - ArrayList result = new ArrayList(boundingBoxResults.size()); - for (T boxResult : boundingBoxResults) { - Point p = locationExtractor.convert(boxResult); - if (pointInPolygon(p, polygon)) { - result.add(boxResult); - } - } - result.trimToSize(); - return result; - } + /** + * Utility method to remove false positives from a collection of objects that have a notion of location, where we want + * to only include values that are located within a polygon. The collection should usually be already contained in the + * bounding box approximation of the polygon for maximum efficiency. + * + * @param boundingBoxResults the collections of located objects approximately inside the target polygon. + * @param locationExtractor a {@link Converter} to extract the location of the value objects. + * @param polygon the target polygon, as an array of {@link Point} (not necessarily closed). + * @param the type of located value objects in the collection. + * @return a {@link List} of the value objects which location has been verified to actually be contained within the + * polygon. + */ + public List removeFalsePositives(Collection boundingBoxResults, + Converter locationExtractor, Point... polygon) { + ArrayList result = new ArrayList(boundingBoxResults.size()); + for (T boxResult : boundingBoxResults) { + Point p = locationExtractor.convert(boxResult); + if (pointInPolygon(p, polygon)) { + result.add(boxResult); + } + } + result.trimToSize(); + return result; + } - /** - * Utility method to remove false positives from a collection of objects that have a - * notion of location, where we want to only include values that are located within a circle. - * - * The collection should usually be already contained in the bounding box approximation of - * the circle for maximum efficiency. - * - * @param boundingBoxResults the collections of located objects approximately inside the target circle. - * @param locationExtractor a {@link Converter} to extract the location of the value objects. - * @param center the center of the target circle. - * @param radius the radius of the target circle. - * @param the type of located value objects in the collection. - * @return a {@link List} of the value objects which location has been verified to actually be contained within the circle. - */ - public List removeFalsePositives(Collection boundingBoxResults, - Converter locationExtractor, Point center, Distance radius) { - ArrayList result = new ArrayList(boundingBoxResults.size()); - for (T boxResult : boundingBoxResults) { - Point p = locationExtractor.convert(boxResult); - if (pointInCircle(p, center, radius)) { - result.add(boxResult); - } - } - result.trimToSize(); - return result; - } + /** + * Utility method to remove false positives from a collection of objects that have a notion of location, where we want + * to only include values that are located within a circle. The collection should usually be already contained in the + * bounding box approximation of the circle for maximum efficiency. + * + * @param boundingBoxResults the collections of located objects approximately inside the target circle. + * @param locationExtractor a {@link Converter} to extract the location of the value objects. + * @param center the center of the target circle. + * @param radius the radius of the target circle. + * @param the type of located value objects in the collection. + * @return a {@link List} of the value objects which location has been verified to actually be contained within the + * circle. + */ + public List removeFalsePositives(Collection boundingBoxResults, + Converter locationExtractor, Point center, Distance radius) { + ArrayList result = new ArrayList(boundingBoxResults.size()); + for (T boxResult : boundingBoxResults) { + Point p = locationExtractor.convert(boxResult); + if (pointInCircle(p, center, radius)) { + result.add(boxResult); + } + } + result.trimToSize(); + return result; + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/support/package-info.java b/src/main/java/org/springframework/data/couchbase/repository/query/support/package-info.java index f6888bbd..009b7c44 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/support/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/support/package-info.java @@ -1,5 +1,4 @@ /** - * This package contains support classes for query derivation and other - * ways of querying couchbase (helper classes). + * This package contains support classes for query derivation and other ways of querying couchbase (helper classes). */ package org.springframework.data.couchbase.repository.query.support; diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java index 787f2b94..9f940339 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java @@ -20,23 +20,15 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.Optional; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.UnsupportedCouchbaseFeatureException; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.core.query.View; -import org.springframework.data.couchbase.core.query.ViewIndexed; import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod; +import org.springframework.data.couchbase.repository.query.CouchbaseRepositoryQuery; import org.springframework.data.couchbase.repository.query.PartTreeN1qlBasedQuery; -import org.springframework.data.couchbase.repository.query.SpatialViewBasedQuery; import org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery; -import org.springframework.data.couchbase.repository.query.ViewBasedCouchbaseQuery; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; @@ -44,13 +36,12 @@ import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; -import com.couchbase.client.java.util.features.CouchbaseFeature; - /** * Factory to create {@link SimpleCouchbaseRepository} instances. * @@ -61,186 +52,131 @@ import com.couchbase.client.java.util.features.CouchbaseFeature; */ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { - private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); + private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); - /** - * Holds the reference to the template. - */ - private final RepositoryOperationsMapping couchbaseOperationsMapping; + /** + * Holds the reference to the template. + */ + private final RepositoryOperationsMapping couchbaseOperationsMapping; - /** - * Holds the reference to the {@link IndexManager}. - */ - private final IndexManager indexManager; + /** + * Holds the mapping context. + */ + private final MappingContext, CouchbasePersistentProperty> mappingContext; - /** - * Holds the mapping context. - */ - private final MappingContext, CouchbasePersistentProperty> mappingContext; + private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor; - /** - * Holds a custom ViewPostProcessor.. - */ - private final ViewPostProcessor viewPostProcessor; + /** + * Create a new factory. + * + * @param couchbaseOperationsMapping the template for the underlying actions. + */ + public CouchbaseRepositoryFactory(final RepositoryOperationsMapping couchbaseOperationsMapping) { + Assert.notNull(couchbaseOperationsMapping, "RepositoryOperationsMapping must not be null!"); - /** - * Create a new factory. - * - * @param couchbaseOperationsMapping the template for the underlying actions. - */ - public CouchbaseRepositoryFactory(final RepositoryOperationsMapping couchbaseOperationsMapping, final IndexManager indexManager) { - Assert.notNull(couchbaseOperationsMapping, "RepositoryOperationsMapping must not be null!"); - Assert.notNull(indexManager, "IndexManager must not be null!"); + this.couchbaseOperationsMapping = couchbaseOperationsMapping; + this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(); + mappingContext = this.couchbaseOperationsMapping.getMappingContext(); - this.couchbaseOperationsMapping = couchbaseOperationsMapping; - this.indexManager = indexManager; - mappingContext = this.couchbaseOperationsMapping.getMappingContext(); - viewPostProcessor = ViewPostProcessor.INSTANCE; + addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor); + } - addRepositoryProxyPostProcessor(viewPostProcessor); - } + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + super.setBeanClassLoader(classLoader); + this.crudMethodMetadataPostProcessor.setBeanClassLoader(classLoader); + } - /** - * Returns entity information based on the domain class. - * - * @param domainClass the class for the entity. - * @param the value type - * @param the id type. - * - * @return entity information for that domain class. - */ - @Override - public CouchbaseEntityInformation getEntityInformation(Class domainClass) { + /** + * Returns entity information based on the domain class. + * + * @param domainClass the class for the entity. + * @param the value type + * @param the id type. + * @return entity information for that domain class. + */ + @Override + public CouchbaseEntityInformation getEntityInformation(Class domainClass) { + CouchbasePersistentEntity entity = (CouchbasePersistentEntity) mappingContext + .getRequiredPersistentEntity(domainClass); + return new MappingCouchbaseEntityInformation<>(entity); + } - CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(domainClass); - return new MappingCouchbaseEntityInformation((CouchbasePersistentEntity) entity); - } + /** + * Returns a new Repository based on the metadata. Two categories of repositories can be instantiated: + * {@link SimpleCouchbaseRepository}. This method performs feature checks to decide which of the two categories can be + * instantiated (eg. is N1QL available?). Instantiation is done via reflection, see + * {@link #getRepositoryBaseClass(RepositoryMetadata)}. + * + * @param metadata the repository metadata. + * @return a new created repository. + */ + @Override + protected final Object getTargetRepository(final RepositoryInformation metadata) { + CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), + metadata.getDomainType()); + CouchbaseEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); + SimpleCouchbaseRepository repository = getTargetRepositoryViaReflection(metadata, entityInformation, + couchbaseOperations); + repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata()); + return repository; + } - /** - * Returns a new Repository based on the metadata. Two categories of repositories can be instantiated: - * {@link SimpleCouchbaseRepository} and {@link N1qlCouchbaseRepository}. - * - * This method performs feature checks to decide which of the two categories can be instantiated (eg. is N1QL available?). - * Instantiation is done via reflection, see {@link #getRepositoryBaseClass(RepositoryMetadata)}. - * - * @param metadata the repository metadata. - * - * @return a new created repository. - */ - @Override - protected final Object getTargetRepository(final RepositoryInformation metadata) { - CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); - boolean isN1qlAvailable = couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL); + /** + * Returns the base class for the repository being constructed. We always return Override these methods if you want to + * change the base class for all your repositories. + * + * @param repositoryMetadata metadata for the repository. + * @return the base class. + */ + @Override + protected final Class getRepositoryBaseClass(final RepositoryMetadata repositoryMetadata) { + return SimpleCouchbaseRepository.class; + } - ViewIndexed viewIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), ViewIndexed.class); - N1qlPrimaryIndexed n1qlPrimaryIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), N1qlPrimaryIndexed.class); - N1qlSecondaryIndexed n1qlSecondaryIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), N1qlSecondaryIndexed.class); + @Override + protected Optional getQueryLookupStrategy(QueryLookupStrategy.Key key, + QueryMethodEvaluationContextProvider contextProvider) { + return Optional.of(new CouchbaseQueryLookupStrategy(contextProvider)); + } - checkFeatures(metadata, isN1qlAvailable, n1qlPrimaryIndexed, n1qlSecondaryIndexed); + /** + * Strategy to lookup Couchbase queries implementation to be used. + */ + private class CouchbaseQueryLookupStrategy implements QueryLookupStrategy { - indexManager.buildIndexes(metadata, viewIndexed, n1qlPrimaryIndexed, n1qlSecondaryIndexed, couchbaseOperations); + private final QueryMethodEvaluationContextProvider evaluationContextProvider; - CouchbaseEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); - SimpleCouchbaseRepository repo = getTargetRepositoryViaReflection(metadata, entityInformation, couchbaseOperations); - repo.setViewMetadataProvider(viewPostProcessor.getViewMetadataProvider()); - return repo; - } + public CouchbaseQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluationContextProvider) { + this.evaluationContextProvider = evaluationContextProvider; + } - private void checkFeatures(RepositoryInformation metadata, boolean isN1qlAvailable, - N1qlPrimaryIndexed n1qlPrimaryIndexed, N1qlSecondaryIndexed n1qlSecondaryIndexed) { - //paging repo will always need N1QL, also check if the repository requires a N1QL index - boolean needsN1ql = metadata.isPagingRepository() || n1qlPrimaryIndexed != null || n1qlSecondaryIndexed != null; + @Override + public RepositoryQuery resolveQuery(final Method method, final RepositoryMetadata metadata, + final ProjectionFactory factory, final NamedQueries namedQueries) { + final CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping + .resolve(metadata.getRepositoryInterface(), metadata.getDomainType()); - //for other repos, they might also need N1QL if they don't have only @View methods - if (!needsN1ql) { - for (Method method : metadata.getQueryMethods()) { + return new CouchbaseRepositoryQuery(couchbaseOperations, new QueryMethod(method, metadata, factory)); - boolean hasN1ql = AnnotationUtils.findAnnotation(method, Query.class) != null; - boolean hasView = AnnotationUtils.findAnnotation(method, View.class) != null; + /*CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); + String namedQueryName = queryMethod.getNamedQueryName(); - if (hasN1ql || !hasView) { - needsN1ql = true; - break; - } - } - } + if (queryMethod.hasN1qlAnnotation()) { - if (needsN1ql && !isN1qlAvailable) { - throw new UnsupportedCouchbaseFeatureException("Repository uses N1QL", CouchbaseFeature.N1QL); - } - } + if (queryMethod.hasInlineN1qlQuery()) { + return new StringN1qlBasedQuery(queryMethod.getInlineN1qlQuery(), queryMethod, couchbaseOperations, + SPEL_PARSER, evaluationContextProvider); + } else if (namedQueries.hasQuery(namedQueryName)) { + String namedQuery = namedQueries.getQuery(namedQueryName); + return new StringN1qlBasedQuery(namedQuery, queryMethod, couchbaseOperations, SPEL_PARSER, + evaluationContextProvider); + } - /** - * Returns the base class for the repository being constructed. Two categories of repositories can be produced by - * this factory: {@link SimpleCouchbaseRepository} and {@link N1qlCouchbaseRepository}. This method checks if N1QL - * is available to choose between the two, but the actual concrete class is determined respectively by - * {@link #getSimpleBaseClass(RepositoryMetadata)} and {@link #getN1qlBaseClass(RepositoryMetadata)}. - * - * Override these methods if you want to change the base class for all your repositories. - * - * @param repositoryMetadata metadata for the repository. - * - * @return the base class. - */ - @Override - protected final Class getRepositoryBaseClass(final RepositoryMetadata repositoryMetadata) { - CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(repositoryMetadata.getRepositoryInterface(), - repositoryMetadata.getDomainType()); - boolean isN1qlAvailable = couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL); - if (isN1qlAvailable) { - return getN1qlBaseClass(repositoryMetadata); - } - return getSimpleBaseClass(repositoryMetadata); - } + } - protected Class getN1qlBaseClass(final RepositoryMetadata repositoryMetadata) { - return N1qlCouchbaseRepository.class; - } - - protected Class getSimpleBaseClass(final RepositoryMetadata repositoryMetadata) { - return SimpleCouchbaseRepository.class; - } - - @Override - protected Optional getQueryLookupStrategy(QueryLookupStrategy.Key key, QueryMethodEvaluationContextProvider contextProvider) { - return Optional.of(new CouchbaseQueryLookupStrategy(contextProvider)); - } - - /** - * Strategy to lookup Couchbase queries implementation to be used. - */ - private class CouchbaseQueryLookupStrategy implements QueryLookupStrategy { - - private final QueryMethodEvaluationContextProvider evaluationContextProvider; - - public CouchbaseQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluationContextProvider) { - this.evaluationContextProvider = evaluationContextProvider; - } - - @Override - public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { - CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), - metadata.getDomainType()); - - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); - String namedQueryName = queryMethod.getNamedQueryName(); - - if (queryMethod.hasDimensionalAnnotation()) { - return new SpatialViewBasedQuery(queryMethod, couchbaseOperations); - } else if (queryMethod.hasViewAnnotation()) { - return new ViewBasedCouchbaseQuery(queryMethod, couchbaseOperations); - } else if (queryMethod.hasN1qlAnnotation()) { - if (queryMethod.hasInlineN1qlQuery()) { - return new StringN1qlBasedQuery(queryMethod.getInlineN1qlQuery(), queryMethod, couchbaseOperations, - SPEL_PARSER, evaluationContextProvider); - } else if (namedQueries.hasQuery(namedQueryName)) { - String namedQuery = namedQueries.getQuery(namedQueryName); - return new StringN1qlBasedQuery(namedQuery, queryMethod, couchbaseOperations, - SPEL_PARSER, evaluationContextProvider); - } //otherwise will do default, queryDerivation - } - return new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); - } - } + return new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations);*/ + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java index ab280054..3056b31d 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactoryBean.java @@ -30,80 +30,63 @@ import org.springframework.util.Assert; * * @author Michael Nitschinger */ -public class CouchbaseRepositoryFactoryBean, S, ID extends Serializable> extends - RepositoryFactoryBeanSupport { +public class CouchbaseRepositoryFactoryBean, S, ID extends Serializable> + extends RepositoryFactoryBeanSupport { - /** - * Contains the reference to the template. - */ - private RepositoryOperationsMapping operationsMapping; + /** + * Contains the reference to the template. + */ + private RepositoryOperationsMapping operationsMapping; - /** - * Contains the reference to the IndexManager. - */ - private IndexManager indexManager; - - /** - * Creates a new {@link CouchbaseRepositoryFactoryBean} for the given repository interface. - * - * @param repositoryInterface must not be {@literal null}. - */ - public CouchbaseRepositoryFactoryBean(Class repositoryInterface) { - super(repositoryInterface); - } + /** + * Creates a new {@link CouchbaseRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public CouchbaseRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } - /** - * Set the template reference. - * - * @param operations the reference to the operations template. - */ - public void setCouchbaseOperations(final CouchbaseOperations operations) { - setCouchbaseOperationsMapping(new RepositoryOperationsMapping(operations)); - } + /** + * Set the template reference. + * + * @param operations the reference to the operations template. + */ + public void setCouchbaseOperations(final CouchbaseOperations operations) { + setCouchbaseOperationsMapping(new RepositoryOperationsMapping(operations)); + } - public void setCouchbaseOperationsMapping(final RepositoryOperationsMapping mapping) { - this.operationsMapping = mapping; - setMappingContext(operationsMapping.getMappingContext()); - } + public void setCouchbaseOperationsMapping(final RepositoryOperationsMapping mapping) { + this.operationsMapping = mapping; + setMappingContext(operationsMapping.getMappingContext()); + } - /** - * Set the IndexManager reference. - * - * @param indexManager the IndexManager to use. - */ - public void setIndexManager(final IndexManager indexManager) { - this.indexManager = indexManager; - } + /** + * Returns a factory instance. + * + * @return the factory instance. + */ + @Override + protected RepositoryFactorySupport createRepositoryFactory() { + return getFactoryInstance(operationsMapping); + } - /** - * Returns a factory instance. - * - * @return the factory instance. - */ - @Override - protected RepositoryFactorySupport createRepositoryFactory() { - return getFactoryInstance(operationsMapping, indexManager); - } + /** + * Get the factory instance for the operations. + * + * @param operationsMapping the reference to the template. + * @return the factory instance. + */ + protected CouchbaseRepositoryFactory getFactoryInstance(final RepositoryOperationsMapping operationsMapping) { + return new CouchbaseRepositoryFactory(operationsMapping); + } - /** - * Get the factory instance for the operations. - * - * @param operationsMapping the reference to the template. - * @param indexManager the reference to the {@link IndexManager}. - * @return the factory instance. - */ - protected CouchbaseRepositoryFactory getFactoryInstance(final RepositoryOperationsMapping operationsMapping, - IndexManager indexManager) { - return new CouchbaseRepositoryFactory(operationsMapping, indexManager); - } - - /** - * Make sure that the dependencies are set and not null. - */ - @Override - public void afterPropertiesSet() { - super.afterPropertiesSet(); - Assert.notNull(operationsMapping, "operationsMapping must not be null!"); - Assert.notNull(indexManager, "indexManager must not be null!"); - } + /** + * Make sure that the dependencies are set and not null. + */ + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + Assert.notNull(operationsMapping, "operationsMapping must not be null!"); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CrudMethodMetadata.java b/src/main/java/org/springframework/data/couchbase/repository/support/CrudMethodMetadata.java new file mode 100644 index 00000000..2ac11d21 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CrudMethodMetadata.java @@ -0,0 +1,19 @@ +package org.springframework.data.couchbase.repository.support; + +import java.lang.reflect.Method; + +import org.springframework.data.couchbase.repository.ScanConsistency; + +public interface CrudMethodMetadata { + + /** + * Returns the {@link Method} to be used. + */ + Method getMethod(); + + /** + * If present holds the scan consistency annotation (null otherwise). + */ + ScanConsistency getScanConsistency(); + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CrudMethodMetadataPostProcessor.java b/src/main/java/org/springframework/data/couchbase/repository/support/CrudMethodMetadataPostProcessor.java new file mode 100644 index 00000000..d6b3b092 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CrudMethodMetadataPostProcessor.java @@ -0,0 +1,239 @@ +package org.springframework.data.couchbase.repository.support; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.core.NamedThreadLocal; +import org.springframework.data.couchbase.repository.ScanConsistency; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; +import org.springframework.lang.Nullable; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * {@link RepositoryProxyPostProcessor} that sets up interceptors to read metadata information from the invoked method. + * This is necessary to allow redeclaration of CRUD methods in repository interfaces and configure locking information + * or query hints on them. + * + * @author Oliver Gierke + * @author Thomas Darimont + * @author Christoph Strobl + * @author Mark Paluch + * @author Jens Schauder + */ +class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, BeanClassLoaderAware { + + private @Nullable ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader) + */ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.RepositoryProxyPostProcessor#postProcess(org.springframework.aop.framework.ProxyFactory, org.springframework.data.repository.core.RepositoryInformation) + */ + @Override + public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { + factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(repositoryInformation)); + } + + /** + * Returns a {@link CrudMethodMetadata} proxy that will lookup the actual target object by obtaining a thread bound + * instance from the {@link TransactionSynchronizationManager} later. + */ + CrudMethodMetadata getCrudMethodMetadata() { + ProxyFactory factory = new ProxyFactory(); + factory.addInterface(CrudMethodMetadata.class); + factory.setTargetSource(new ThreadBoundTargetSource()); + return (CrudMethodMetadata) factory.getProxy(this.classLoader); + } + + /** + * {@link MethodInterceptor} to build and cache {@link DefaultCrudMethodMetadata} instances for the invoked methods. + * Will bind the found information to a {@link TransactionSynchronizationManager} for later lookup. + * + * @see DefaultCrudMethodMetadata + * @author Oliver Gierke + * @author Thomas Darimont + */ + static class CrudMethodMetadataPopulatingMethodInterceptor implements MethodInterceptor { + + private static final ThreadLocal currentInvocation = new NamedThreadLocal<>( + "Current AOP method invocation"); + + private final ConcurrentMap metadataCache = new ConcurrentHashMap<>(); + private final Set implementations = new HashSet<>(); + + CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation) { + ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), implementations::add, + method -> !repositoryInformation.isQueryMethod(method)); + } + + /** + * Return the AOP Alliance {@link MethodInvocation} object associated with the current invocation. + * + * @return the invocation object associated with the current invocation. + * @throws IllegalStateException if there is no AOP invocation in progress, or if the + * {@link CrudMethodMetadataPopulatingMethodInterceptor} was not added to this interceptor chain. + */ + static MethodInvocation currentInvocation() throws IllegalStateException { + + MethodInvocation mi = currentInvocation.get(); + + if (mi == null) + throw new IllegalStateException( + "No MethodInvocation found: Check that an AOP invocation is in progress, and that the " + + "CrudMethodMetadataPopulatingMethodInterceptor is upfront in the interceptor chain."); + return mi; + } + + /* + * (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + + Method method = invocation.getMethod(); + + if (!implementations.contains(method)) { + return invocation.proceed(); + } + + MethodInvocation oldInvocation = currentInvocation.get(); + currentInvocation.set(invocation); + + try { + + CrudMethodMetadata metadata = (CrudMethodMetadata) TransactionSynchronizationManager.getResource(method); + + if (metadata != null) { + return invocation.proceed(); + } + + CrudMethodMetadata methodMetadata = metadataCache.get(method); + + if (methodMetadata == null) { + + methodMetadata = new DefaultCrudMethodMetadata(method); + CrudMethodMetadata tmp = metadataCache.putIfAbsent(method, methodMetadata); + + if (tmp != null) { + methodMetadata = tmp; + } + } + + TransactionSynchronizationManager.bindResource(method, methodMetadata); + + try { + return invocation.proceed(); + } finally { + TransactionSynchronizationManager.unbindResource(method); + } + } finally { + currentInvocation.set(oldInvocation); + } + } + } + + /** + * Default implementation of {@link CrudMethodMetadata} that will inspect the backing method for annotations. + * + * @author Oliver Gierke + * @author Thomas Darimont + */ + private static class DefaultCrudMethodMetadata implements CrudMethodMetadata { + + private final Method method; + private final ScanConsistency scanConsistency; + + /** + * Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}. + * + * @param method must not be {@literal null}. + */ + DefaultCrudMethodMetadata(Method method) { + Assert.notNull(method, "Method must not be null!"); + this.method = method; + + ScanConsistency scanConsistency = null; + for (Annotation ann : method.getAnnotations()) { + if (ann instanceof ScanConsistency) { + scanConsistency = ((ScanConsistency) ann); + } + } + this.scanConsistency = scanConsistency; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getMethod() + */ + @Override + public Method getMethod() { + return method; + } + + @Override + public ScanConsistency getScanConsistency() { + return scanConsistency; + } + } + + private static class ThreadBoundTargetSource implements TargetSource { + + /* + * (non-Javadoc) + * @see org.springframework.aop.TargetSource#getTargetClass() + */ + @Override + public Class getTargetClass() { + return CrudMethodMetadata.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.TargetSource#isStatic() + */ + @Override + public boolean isStatic() { + return false; + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.TargetSource#getTarget() + */ + @Override + public Object getTarget() { + + MethodInvocation invocation = CrudMethodMetadataPopulatingMethodInterceptor.currentInvocation(); + return TransactionSynchronizationManager.getResource(invocation.getMethod()); + } + + /* + * (non-Javadoc) + * @see org.springframework.aop.TargetSource#releaseTarget(java.lang.Object) + */ + @Override + public void releaseTarget(Object target) {} + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/IndexManager.java b/src/main/java/org/springframework/data/couchbase/repository/support/IndexManager.java deleted file mode 100644 index a46eda55..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/support/IndexManager.java +++ /dev/null @@ -1,317 +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.repository.support; - -import static com.couchbase.client.java.query.dsl.Expression.s; -import static com.couchbase.client.java.query.dsl.Expression.x; - -import java.util.Collections; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.bucket.BucketManager; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.error.DesignDocumentDoesNotExistException; -import com.couchbase.client.java.query.AsyncN1qlQueryResult; -import com.couchbase.client.java.query.Index; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.dsl.path.index.IndexType; -import com.couchbase.client.java.view.DefaultView; -import com.couchbase.client.java.view.DesignDocument; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import rx.Observable; -import rx.exceptions.CompositeException; -import rx.functions.Action1; -import rx.functions.Func1; - -import org.springframework.context.annotation.Profile; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -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.repository.core.RepositoryInformation; - -/** - * {@link IndexManager} is responsible for automatic index creation according to the provided metadata and - * various index annotations (if not null). - *

    - * Index creation will be attempted in parallel using the asynchronous APIs, but the overall process is still blocking. - * - * @author Simon Baslé - * @author Subhashni Balakrishnan - */ -public class IndexManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(IndexManager.class); - - private static final String TEMPLATE_MAP_FUNCTION = "function (doc, meta) { if(doc.%s == \"%s\") { emit(meta.id, null); } }"; - - private static final JsonObject SUCCESS_MARKER = JsonObject.empty(); - - /** True if this index manager should ignore view creation annotations */ - private boolean ignoreViews; - /** True if this index manager should ignore N1QL PRIMARY creation annotations */ - private boolean ignoreN1qlPrimary; - /** True if this index manager should ignore N1QL SECONDARY creation annotations */ - private boolean ignoreN1qlSecondary; - - - /** - * Construct an IndexManager that can be used as a Bean in a {@link Profile @Profile} annotated configuration - * in order to activate only all or part of automatic index creations in some contexts (like activating it in Dev but - * not in Prod). - * - * @param processViews true to process, false to ignore {@link ViewIndexed} annotations. - * @param processN1qlPrimary true to process, false to ignore {@link N1qlPrimaryIndexed} annotations. - * @param processN1qlSecondary true to process, false to ignore {@link N1qlSecondaryIndexed} annotations. - */ - public IndexManager(boolean processViews, boolean processN1qlPrimary, boolean processN1qlSecondary) { - this.ignoreViews = !processViews; - this.ignoreN1qlPrimary = !processN1qlPrimary; - this.ignoreN1qlSecondary = !processN1qlSecondary; - } - - /** - * Construct a default IndexManager that process all three types of automatic index creations. - */ - public IndexManager() { - this(true, true, true); - } - - /** - * @return true if this IndexManager ignores {@link ViewIndexed} annotations. - */ - public boolean isIgnoreViews() { - return ignoreViews; - } - - /** - * @return true if this IndexManager ignores {@link N1qlPrimaryIndexed} annotations. - */ - public boolean isIgnoreN1qlPrimary() { - return ignoreN1qlPrimary; - } - - /** - * @return true if this IndexManager ignores {@link N1qlSecondaryIndexed} annotations. - */ - public boolean isIgnoreN1qlSecondary() { - return ignoreN1qlSecondary; - } - - /** - * Build the relevant indexes according to the provided annotation and repository metadata, in parallel but blocking - * until all relevant indexes are created. Existing indexes will be detected and skipped. - *

    - * Note that this IndexManager could be configured to ignore some of the annotation types. - * In case of multiple errors, a {@link CompositeException} can be raised with up to 3 causes (one per type of index). - * - * @param metadata the repository's metadata (allowing to find out the type of entity stored, the key under which type - * information is stored, etc...). - * @param viewIndexed the annotation for creation of a View-based index. - * @param n1qlPrimaryIndexed the annotation for creation of a N1QL-based primary index (generic). - * @param n1qlSecondaryIndexed the annotation for creation of a N1QL-based secondary index (specific to the repository - * stored entity). - * @param couchbaseOperations the template to use for index creation. - * @throws CompositeException when several errors (for multiple index types) have been raised. - */ - public void buildIndexes(RepositoryInformation metadata, ViewIndexed viewIndexed, N1qlPrimaryIndexed n1qlPrimaryIndexed, - N1qlSecondaryIndexed n1qlSecondaryIndexed, CouchbaseOperations couchbaseOperations) { - Observable viewAsync = Observable.empty(); - Observable n1qlPrimaryAsync = Observable.empty(); - Observable n1qlSecondaryAsync = Observable.empty(); - - if (viewIndexed != null && !ignoreViews) { - viewAsync = buildAllView(viewIndexed, metadata, couchbaseOperations.getCouchbaseBucket(), couchbaseOperations.getConverter().getTypeKey()); - } - - if (n1qlPrimaryIndexed != null && !ignoreN1qlPrimary) { - n1qlPrimaryAsync = buildN1qlPrimary(metadata, couchbaseOperations.getCouchbaseBucket()); - } - - if (n1qlSecondaryIndexed != null && !ignoreN1qlSecondary) { - n1qlSecondaryAsync = buildN1qlSecondary(n1qlSecondaryIndexed, metadata, couchbaseOperations.getCouchbaseBucket(), couchbaseOperations.getConverter().getTypeKey()); - } - - //trigger the builds, wait for the last one, throw CompositeException if errors - Observable.mergeDelayError(viewAsync, n1qlPrimaryAsync, n1qlSecondaryAsync) - .toBlocking() - .lastOrDefault(null); - } - - /** - * Build the relevant indexes according to the provided annotation and repository metadata, in parallel but blocking - * until all relevant indexes are created. Existing indexes will be detected and skipped. - *

    - * Note that this IndexManager could be configured to ignore some of the annotation types. - * In case of multiple errors, a {@link CompositeException} can be raised with up to 3 causes (one per type of index). - * - * @param metadata the repository's metadata (allowing to find out the type of entity stored, the key under which type - * information is stored, etc...). - * @param viewIndexed the annotation for creation of a View-based index. - * @param n1qlPrimaryIndexed the annotation for creation of a N1QL-based primary index (generic). - * @param n1qlSecondaryIndexed the annotation for creation of a N1QL-based secondary index (specific to the repository - * stored entity). - * @param rxjava1CouchbaseOperations the template to use for index creation. - * @throws CompositeException when several errors (for multiple index types) have been raised. - */ - public void buildIndexes(RepositoryInformation metadata, ViewIndexed viewIndexed, N1qlPrimaryIndexed n1qlPrimaryIndexed, - N1qlSecondaryIndexed n1qlSecondaryIndexed, RxJavaCouchbaseOperations rxjava1CouchbaseOperations) { - Observable viewAsync = Observable.empty(); - Observable n1qlPrimaryAsync = Observable.empty(); - Observable n1qlSecondaryAsync = Observable.empty(); - - if (viewIndexed != null && !ignoreViews) { - viewAsync = buildAllView(viewIndexed, metadata, rxjava1CouchbaseOperations.getCouchbaseBucket(), rxjava1CouchbaseOperations.getConverter().getTypeKey()); - } - - if (n1qlPrimaryIndexed != null && !ignoreN1qlPrimary) { - n1qlPrimaryAsync = buildN1qlPrimary(metadata, rxjava1CouchbaseOperations.getCouchbaseBucket()); - } - - if (n1qlSecondaryIndexed != null && !ignoreN1qlSecondary) { - n1qlSecondaryAsync = buildN1qlSecondary(n1qlSecondaryIndexed, metadata, rxjava1CouchbaseOperations.getCouchbaseBucket(), rxjava1CouchbaseOperations.getConverter().getTypeKey()); - } - - //trigger the builds, wait for the last one, throw CompositeException if errors - Observable.mergeDelayError(viewAsync, n1qlPrimaryAsync, n1qlSecondaryAsync) - .toBlocking() - .lastOrDefault(null); - } - - private Observable buildN1qlPrimary(final RepositoryInformation metadata, Bucket bucket) { - final String bucketName = bucket.name(); - Statement createPrimary = Index.createPrimaryIndex() - .on(bucketName) - .using(IndexType.GSI); - - LOGGER.debug("Creating N1QL primary index for repository {}", metadata.getRepositoryInterface().getSimpleName()); - return bucket.async().query(createPrimary) - .flatMap(new Func1>() { - @Override - public Observable call(AsyncN1qlQueryResult asyncN1qlQueryResult) { - return asyncN1qlQueryResult.errors(); - } - }) - .defaultIfEmpty(SUCCESS_MARKER) - .flatMap(new Func1>() { - @Override - public Observable call(JsonObject json) { - if (json == SUCCESS_MARKER) { - LOGGER.debug("N1QL primary index created for repository {}", metadata.getRepositoryInterface().getSimpleName()); - return Observable.empty(); - } else if (json.getString("msg").contains("Index #primary already exist") || - (json.containsKey("code") && json.getLong("code") == 4300L)) { - LOGGER.debug("Primary index already exist, skipping"); - return Observable.empty(); //ignore, the index already exist - } else { - return Observable.error(new CouchbaseQueryExecutionException( - "Cannot create N1QL primary index on " + bucketName + ": " + json)); - } - } - }); - } - - private Observable buildN1qlSecondary(N1qlSecondaryIndexed config, final RepositoryInformation metadata, Bucket bucket, String typeKey) { - final String bucketName = bucket.name(); - final String indexName = config.indexName(); - final String type = metadata.getDomainType().getName(); - - Statement createIndex = Index.createIndex(indexName) - .on(bucketName, x(typeKey)) - .where(x(typeKey).eq(s(type))) - .using(IndexType.GSI); - - LOGGER.debug("Creating N1QL secondary index for repository {}", metadata.getRepositoryInterface().getSimpleName()); - return bucket.async().query(createIndex) - .flatMap(new Func1>() { - @Override - public Observable call(AsyncN1qlQueryResult asyncN1qlQueryResult) { - return asyncN1qlQueryResult.errors(); - } - }) - .defaultIfEmpty(SUCCESS_MARKER) - .flatMap(new Func1>() { - @Override - public Observable call(JsonObject json) { - if (json == SUCCESS_MARKER) { - LOGGER.debug("N1QL secondary index created for repository {}", metadata.getRepositoryInterface().getSimpleName()); - return Observable.empty(); - } else if (json.getString("msg").contains("Index " + indexName + " already exist") || - (json.containsKey("code") && json.getLong("code") == 4300L)) { - LOGGER.debug("Secondary index already exist, skipping"); - return Observable.empty(); //ignore, the index already exist - } else { - return Observable.error(new CouchbaseQueryExecutionException( - "Cannot create N1QL secondary index " + bucketName + "." + indexName + " for " + type + ": " + json)); - } - } - }); - } - - private Observable buildAllView(ViewIndexed config, final RepositoryInformation metadata, Bucket bucket, String typeKey) { - if (config == null) return Observable.empty(); - LOGGER.debug("Creating View index index for repository {}", metadata.getRepositoryInterface().getSimpleName()); - - BucketManager manager = bucket.bucketManager(); - String viewName = config.viewName(); - String mapFunction = config.mapFunction(); - if (mapFunction.isEmpty()) { - String type = metadata.getDomainType().getName(); - - mapFunction = String.format(TEMPLATE_MAP_FUNCTION, typeKey, type); - } - String reduceFunction = config.reduceFunction(); - if ("".equals(reduceFunction)) { - reduceFunction = null; - } - - com.couchbase.client.java.view.View view = DefaultView.create(viewName, mapFunction, reduceFunction); - DesignDocument doc = null; - try { - doc = manager.getDesignDocument(config.designDoc()); - } catch(DesignDocumentDoesNotExistException ex) { - //ignore - } - if (doc != null) { - for (com.couchbase.client.java.view.View existingView : doc.views()) { - if (existingView.name().equals(viewName)) { - LOGGER.debug("View index {}/{} already exist, skipping", config.designDoc(), viewName); - return Observable.empty(); //abort, the view already exist - } - } - doc.views().add(view); - } else { - doc = DesignDocument.create(config.designDoc(), Collections.singletonList(view)); - } - return manager.async().upsertDesignDocument(doc) - .map(new Func1() { - @Override - public Void call(DesignDocument designDocument) { - return null; - } - }) - .doOnNext(new Action1() { - @Override - public void call(Void aVoid) { - LOGGER.debug("View index created for repository {}", metadata.getRepositoryInterface().getSimpleName()); - } - }); - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/MappingCouchbaseEntityInformation.java b/src/main/java/org/springframework/data/couchbase/repository/support/MappingCouchbaseEntityInformation.java index 0d07f14a..51427a6b 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/MappingCouchbaseEntityInformation.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/MappingCouchbaseEntityInformation.java @@ -26,14 +26,14 @@ import org.springframework.data.repository.core.support.PersistentEntityInformat * @author Oliver Gierke */ public class MappingCouchbaseEntityInformation extends PersistentEntityInformation - implements CouchbaseEntityInformation { + implements CouchbaseEntityInformation { - /** - * Create a new Information container. - * - * @param entity the entity of the container. - */ - public MappingCouchbaseEntityInformation(CouchbasePersistentEntity entity) { - super(entity); - } + /** + * Create a new Information container. + * + * @param entity the entity of the container. + */ + public MappingCouchbaseEntityInformation(CouchbasePersistentEntity entity) { + super(entity); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/N1qlCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/N1qlCouchbaseRepository.java deleted file mode 100644 index bc4168cb..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/support/N1qlCouchbaseRepository.java +++ /dev/null @@ -1,124 +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.repository.support; - -import java.io.Serializable; -import java.util.List; - -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.SimpleN1qlQuery; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.path.GroupByPath; -import com.couchbase.client.java.query.dsl.path.LimitPath; -import com.couchbase.client.java.query.dsl.path.WherePath; - -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.repository.CouchbasePagingAndSortingRepository; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.couchbase.repository.query.CountFragment; -import org.springframework.data.couchbase.repository.query.support.N1qlUtils; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.util.Assert; - -/** - * A {@link CouchbasePagingAndSortingRepository} implementation. It uses N1QL for its {@link PagingAndSortingRepository} - * method implementation. - * - * @author Mark Paluch - */ -public class N1qlCouchbaseRepository - extends SimpleCouchbaseRepository - implements CouchbasePagingAndSortingRepository { - - /** - * Create a new Repository. - * - * @param metadata the Metadata for the entity. - * @param couchbaseOperations the reference to the template used. - */ - public N1qlCouchbaseRepository(CouchbaseEntityInformation metadata, CouchbaseOperations couchbaseOperations) { - super(metadata, couchbaseOperations); - } - - @Override - public Iterable findAll(Sort sort) { - Assert.notNull(sort, "Sort must not be null!"); - - //prepare elements of the query - WherePath selectFrom = N1qlUtils.createSelectFromForEntity(getCouchbaseOperations().getCouchbaseBucket().name()); - Expression whereCriteria = N1qlUtils.createWhereFilterForEntity(null, getCouchbaseOperations().getConverter(), - getEntityInformation()); - - //apply the sort - com.couchbase.client.java.query.dsl.Sort[] orderings = N1qlUtils.createSort(sort, getCouchbaseOperations().getConverter()); - Statement st = selectFrom.where(whereCriteria).orderBy(orderings); - - //fire the query - ScanConsistency consistency = getCouchbaseOperations().getDefaultConsistency().n1qlConsistency(); - N1qlQuery query = N1qlQuery.simple(st, N1qlParams.build().consistency(consistency)); - return getCouchbaseOperations().findByN1QL(query, getEntityInformation().getJavaType()); - } - - @Override - public Page findAll(Pageable pageable) { - Assert.notNull(pageable, "Pageable must not be null"); - ScanConsistency consistency = getCouchbaseOperations().getDefaultConsistency().n1qlConsistency(); - - //prepare the count total query - Statement countStatement = N1qlUtils.createCountQueryForEntity(getCouchbaseOperations().getCouchbaseBucket().name(), - getCouchbaseOperations().getConverter(), getEntityInformation()); - SimpleN1qlQuery countQuery = N1qlQuery.simple(countStatement, N1qlParams.build().consistency(consistency)); - - //TODO how to avoid to do that more than once? - //fire the count query and get total count - List < CountFragment > countResult = getCouchbaseOperations().findByN1QLProjection(countQuery, CountFragment.class); - long totalCount = countResult == null || countResult.isEmpty() ? 0 : countResult.get(0).count; - - //prepare elements of the data query - WherePath selectFrom = N1qlUtils.createSelectFromForEntity(getCouchbaseOperations().getCouchbaseBucket().name()); - - //add where criteria - Expression whereCriteria = N1qlUtils.createWhereFilterForEntity(null, getCouchbaseOperations().getConverter(), - getEntityInformation()); - GroupByPath groupBy = selectFrom.where(whereCriteria); - - //apply the sort if available - LimitPath limitPath = groupBy; - if (pageable.getSort().isSorted()) { - com.couchbase.client.java.query.dsl.Sort[] orderings = N1qlUtils.createSort(pageable.getSort(), - getCouchbaseOperations().getConverter()); - limitPath = groupBy.orderBy(orderings); - } - - //apply the paging - Statement pageStatement = limitPath.limit(pageable.getPageSize()).offset(Math.toIntExact(pageable.getOffset())); - - //fire the query - N1qlQuery query = N1qlQuery.simple(pageStatement, N1qlParams.build().consistency(consistency)); - List pageContent = getCouchbaseOperations().findByN1QL(query, getEntityInformation().getJavaType()); - - //return the list as a Page - return new PageImpl(pageContent, pageable, totalCount); - } -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java index a346fc73..1494c298 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactory.java @@ -19,23 +19,14 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.Optional; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.couchbase.core.UnsupportedCouchbaseFeatureException; +import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.core.query.View; -import org.springframework.data.couchbase.core.query.ViewIndexed; import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod; import org.springframework.data.couchbase.repository.query.ReactivePartTreeN1qlBasedQuery; -import org.springframework.data.couchbase.repository.query.ReactiveSpatialViewBasedQuery; import org.springframework.data.couchbase.repository.query.ReactiveStringN1qlBasedQuery; -import org.springframework.data.couchbase.repository.query.ReactiveViewBasedCouchbaseQuery; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; @@ -48,197 +39,129 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; -import com.couchbase.client.java.util.features.CouchbaseFeature; - /** * @author Subhashni Balakrishnan * @author Mark Paluch * @since 3.0 */ public class ReactiveCouchbaseRepositoryFactory extends ReactiveRepositoryFactorySupport { - private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); + private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); - /** - * Holds the reference to the template. - */ - private final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping; + /** + * Holds the reference to the template. + */ + private final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping; - /** - * Holds the reference to the {@link IndexManager}. - */ - private final IndexManager indexManager; + /** + * Holds the mapping context. + */ + private final MappingContext, CouchbasePersistentProperty> mappingContext; - /** - * Holds the mapping context. - */ - private final MappingContext, CouchbasePersistentProperty> mappingContext; + /** + * Create a new factory. + * + * @param couchbaseOperationsMapping the template for the underlying actions. + */ + public ReactiveCouchbaseRepositoryFactory(final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping) { + Assert.notNull(couchbaseOperationsMapping); - /** - * Holds a custom ViewPostProcessor.. - */ - private final ViewPostProcessor viewPostProcessor; + this.couchbaseOperationsMapping = couchbaseOperationsMapping; + mappingContext = this.couchbaseOperationsMapping.getMappingContext(); - /** - * Create a new factory. - * - * @param couchbaseOperationsMapping the template for the underlying actions. - */ - public ReactiveCouchbaseRepositoryFactory(final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping, - final IndexManager indexManager) { - Assert.notNull(couchbaseOperationsMapping); - Assert.notNull(indexManager); + } - this.couchbaseOperationsMapping = couchbaseOperationsMapping; - this.indexManager = indexManager; - mappingContext = this.couchbaseOperationsMapping.getMappingContext(); - viewPostProcessor = ViewPostProcessor.INSTANCE; + /** + * Returns entity information based on the domain class. + * + * @param domainClass the class for the entity. + * @param the value type + * @param the id type. + * @return entity information for that domain class. + */ + @Override + public CouchbaseEntityInformation getEntityInformation(Class domainClass) { + CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(domainClass); - addRepositoryProxyPostProcessor(viewPostProcessor); - } + return new MappingCouchbaseEntityInformation<>((CouchbasePersistentEntity) entity); + } - /** - * Returns entity information based on the domain class. - * - * @param domainClass the class for the entity. - * @param the value type - * @param the id type. - * - * @return entity information for that domain class. - */ - @Override - public CouchbaseEntityInformation getEntityInformation(Class domainClass) { - CouchbasePersistentEntity entity = mappingContext.getRequiredPersistentEntity(domainClass); + /** + * Returns a new Repository based on the metadata. Two categories of repositories can be instantiated: + * {@link SimpleReactiveCouchbaseRepository}. This method performs feature checks to decide which of the two + * categories can be instantiated (eg. is N1QL available?). Instantiation is done via reflection, see + * {@link #getRepositoryBaseClass(RepositoryMetadata)}. + * + * @param metadata the repository metadata. + * @return a new created repository. + */ + @Override + protected final Object getTargetRepository(final RepositoryInformation metadata) { + CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), + metadata.getDomainType()); + // boolean isN1qlAvailable = + // couchbaseOperations.getCouchbaseClusterConfig().clusterCapabilities().containsKey(ServiceType.QUERY); - return new MappingCouchbaseEntityInformation((CouchbasePersistentEntity) entity); - } + CouchbaseEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); + SimpleReactiveCouchbaseRepository repo = getTargetRepositoryViaReflection(metadata, entityInformation, + couchbaseOperations); + // repo.setViewMetadataProvider(viewPostProcessor.getViewMetadataProvider()); + return repo; + } - /** - * Returns a new Repository based on the metadata. Two categories of repositories can be instantiated: - * {@link SimpleReactiveCouchbaseRepository} and {@link ReactiveN1qlCouchbaseRepository}. - * - * This method performs feature checks to decide which of the two categories can be instantiated (eg. is N1QL available?). - * Instantiation is done via reflection, see {@link #getRepositoryBaseClass(RepositoryMetadata)}. - * - * @param metadata the repository metadata. - * - * @return a new created repository. - */ - @Override - protected final Object getTargetRepository(final RepositoryInformation metadata) { - RxJavaCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), - metadata.getDomainType()); - boolean isN1qlAvailable = couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL); + /** + * Returns the base class for the repository being constructed. Two categories of repositories can be produced by this + * factory: {@link SimpleReactiveCouchbaseRepository} and. This method checks if N1QL is available to choose between + * the two, but the actual concrete class is determined respectively by. Override these methods if you want to change + * the base class for all your repositories. + * + * @param repositoryMetadata metadata for the repository. + * @return the base class. + */ + @Override + protected final Class getRepositoryBaseClass(final RepositoryMetadata repositoryMetadata) { + // Since we always need n1ql (we eliminated use of views for findAll, etc...), lets just + // always return the n1ql repo + return SimpleReactiveCouchbaseRepository.class; + } - ViewIndexed viewIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), ViewIndexed.class); - N1qlPrimaryIndexed n1qlPrimaryIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), N1qlPrimaryIndexed.class); - N1qlSecondaryIndexed n1qlSecondaryIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), N1qlSecondaryIndexed.class); + @Override + protected Optional getQueryLookupStrategy(QueryLookupStrategy.Key key, + QueryMethodEvaluationContextProvider contextProvider) { + return Optional.of(new ReactiveCouchbaseRepositoryFactory.CouchbaseQueryLookupStrategy(contextProvider)); + } - checkFeatures(metadata, isN1qlAvailable, n1qlPrimaryIndexed, n1qlSecondaryIndexed); + /** + * Strategy to lookup Couchbase queries implementation to be used. + */ + private class CouchbaseQueryLookupStrategy implements QueryLookupStrategy { - indexManager.buildIndexes(metadata, viewIndexed, n1qlPrimaryIndexed, n1qlSecondaryIndexed, couchbaseOperations); + private final QueryMethodEvaluationContextProvider evaluationContextProvider; - CouchbaseEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); - SimpleReactiveCouchbaseRepository repo = getTargetRepositoryViaReflection(metadata, entityInformation, couchbaseOperations); - repo.setViewMetadataProvider(viewPostProcessor.getViewMetadataProvider()); - return repo; - } + public CouchbaseQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluationContextProvider) { + this.evaluationContextProvider = evaluationContextProvider; + } - private void checkFeatures(RepositoryInformation metadata, boolean isN1qlAvailable, - N1qlPrimaryIndexed n1qlPrimaryIndexed, N1qlSecondaryIndexed n1qlSecondaryIndexed) { - //paging repo will always need N1QL, also check if the repository requires a N1QL index - boolean needsN1ql = metadata.isPagingRepository() || n1qlPrimaryIndexed != null || n1qlSecondaryIndexed != null; + @Override + public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, + NamedQueries namedQueries) { + CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), + metadata.getDomainType()); - //for other repos, they might also need N1QL if they don't have only @View methods - if (!needsN1ql) { - for (Method method : metadata.getQueryMethods()) { - - boolean hasN1ql = AnnotationUtils.findAnnotation(method, Query.class) != null; - boolean hasView = AnnotationUtils.findAnnotation(method, View.class) != null; - - if (hasN1ql || !hasView) { - needsN1ql = true; - break; - } - } - } - - if (needsN1ql && !isN1qlAvailable) { - throw new UnsupportedCouchbaseFeatureException("Repository uses N1QL", CouchbaseFeature.N1QL); - } - } - - /** - * Returns the base class for the repository being constructed. Two categories of repositories can be produced by - * this factory: {@link SimpleReactiveCouchbaseRepository} and {@link ReactiveN1qlCouchbaseRepository}. This method checks if N1QL - * is available to choose between the two, but the actual concrete class is determined respectively by - * {@link #getSimpleBaseClass(RepositoryMetadata)} and {@link #getN1qlBaseClass(RepositoryMetadata)}. - * - * Override these methods if you want to change the base class for all your repositories. - * - * @param repositoryMetadata metadata for the repository. - * - * @return the base class. - */ - @Override - protected final Class getRepositoryBaseClass(final RepositoryMetadata repositoryMetadata) { - RxJavaCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(repositoryMetadata.getRepositoryInterface(), - repositoryMetadata.getDomainType()); - boolean isN1qlAvailable = couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL); - if (isN1qlAvailable) { - return getN1qlBaseClass(repositoryMetadata); - } - return getSimpleBaseClass(repositoryMetadata); - } - - protected Class getN1qlBaseClass(final RepositoryMetadata repositoryMetadata) { - return ReactiveN1qlCouchbaseRepository.class; - } - - protected Class getSimpleBaseClass(final RepositoryMetadata repositoryMetadata) { - return SimpleReactiveCouchbaseRepository.class; - } - - @Override - protected Optional getQueryLookupStrategy(QueryLookupStrategy.Key key, QueryMethodEvaluationContextProvider contextProvider) { - return Optional.of(new ReactiveCouchbaseRepositoryFactory.CouchbaseQueryLookupStrategy(contextProvider)); - } - - /** - * Strategy to lookup Couchbase queries implementation to be used. - */ - private class CouchbaseQueryLookupStrategy implements QueryLookupStrategy { - - private final QueryMethodEvaluationContextProvider evaluationContextProvider; - - public CouchbaseQueryLookupStrategy(QueryMethodEvaluationContextProvider evaluationContextProvider) { - this.evaluationContextProvider = evaluationContextProvider; - } - - @Override - public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { - RxJavaCouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), - metadata.getDomainType()); - - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); - String namedQueryName = queryMethod.getNamedQueryName(); - - if (queryMethod.hasDimensionalAnnotation()) { - return new ReactiveSpatialViewBasedQuery(queryMethod, couchbaseOperations); - } else if (queryMethod.hasViewAnnotation()) { - return new ReactiveViewBasedCouchbaseQuery(queryMethod, couchbaseOperations); - } else if (queryMethod.hasN1qlAnnotation()) { - if (queryMethod.hasInlineN1qlQuery()) { - return new ReactiveStringN1qlBasedQuery(queryMethod.getInlineN1qlQuery(), queryMethod, couchbaseOperations, - SPEL_PARSER, evaluationContextProvider); - } else if (namedQueries.hasQuery(namedQueryName)) { - String namedQuery = namedQueries.getQuery(namedQueryName); - return new ReactiveStringN1qlBasedQuery(namedQuery, queryMethod, couchbaseOperations, - SPEL_PARSER, evaluationContextProvider); - } //otherwise will do default, queryDerivation - } - return new ReactivePartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); - } - } + CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); + String namedQueryName = queryMethod.getNamedQueryName(); + if (queryMethod.hasN1qlAnnotation()) { + if (queryMethod.hasInlineN1qlQuery()) { + return new ReactiveStringN1qlBasedQuery(queryMethod.getInlineN1qlQuery(), queryMethod, couchbaseOperations, + SPEL_PARSER, evaluationContextProvider); + } else if (namedQueries.hasQuery(namedQueryName)) { + String namedQuery = namedQueries.getQuery(namedQueryName); + return new ReactiveStringN1qlBasedQuery(namedQuery, queryMethod, couchbaseOperations, SPEL_PARSER, + evaluationContextProvider); + } // otherwise will do default, queryDerivation + } + return new ReactivePartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); + } + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactoryBean.java b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactoryBean.java index e43482c0..459772db 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveCouchbaseRepositoryFactoryBean.java @@ -17,7 +17,7 @@ package org.springframework.data.couchbase.repository.support; import java.io.Serializable; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; +import org.springframework.data.couchbase.core.CouchbaseOperations; import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; @@ -29,79 +29,63 @@ import org.springframework.util.Assert; * @since 3.0 */ public class ReactiveCouchbaseRepositoryFactoryBean, S, ID extends Serializable> - extends RepositoryFactoryBeanSupport { + extends RepositoryFactoryBeanSupport { - /** - * Contains the reference to the template. - */ - private ReactiveRepositoryOperationsMapping couchbaseOperationsMapping; + /** + * Contains the reference to the template. + */ + private ReactiveRepositoryOperationsMapping couchbaseOperationsMapping; - /** - * Contains the reference to the IndexManager. - */ - private IndexManager indexManager; + /** + * Creates a new {@link CouchbaseRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public ReactiveCouchbaseRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } - /** - * Creates a new {@link CouchbaseRepositoryFactoryBean} for the given repository interface. - * - * @param repositoryInterface must not be {@literal null}. - */ - public ReactiveCouchbaseRepositoryFactoryBean(Class repositoryInterface) { - super(repositoryInterface); - } + /** + * Set the template reference. + * + * @param couchbaseOperationsMapping the reference to the operations template. + */ + public void setCouchbaseOperations(final CouchbaseOperations couchbaseOperationsMapping) { + setCouchbaseOperationsMapping(new ReactiveRepositoryOperationsMapping(couchbaseOperationsMapping)); + } - /** - * Set the template reference. - * - * @param couchbaseOperationsMapping the reference to the operations template. - */ - public void setCouchbaseOperations(final RxJavaCouchbaseOperations couchbaseOperationsMapping) { - setCouchbaseOperationsMapping(new ReactiveRepositoryOperationsMapping(couchbaseOperationsMapping)); - } + public void setCouchbaseOperationsMapping(final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping) { + this.couchbaseOperationsMapping = couchbaseOperationsMapping; + setMappingContext(couchbaseOperationsMapping.getMappingContext()); + } - public void setCouchbaseOperationsMapping(final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping) { - this.couchbaseOperationsMapping = couchbaseOperationsMapping; - setMappingContext(couchbaseOperationsMapping.getMappingContext()); - } + /** + * Returns a factory instance. + * + * @return the factory instance. + */ + @Override + protected RepositoryFactorySupport createRepositoryFactory() { + return getFactoryInstance(couchbaseOperationsMapping); + } - /** - * Set the IndexManager reference. - * - * @param indexManager the IndexManager to use. - */ - public void setIndexManager(final IndexManager indexManager) { - this.indexManager = indexManager; - } + /** + * Get the factory instance for the operations. + * + * @param couchbaseOperationsMapping the reference to the template. + * @return the factory instance. + */ + protected ReactiveCouchbaseRepositoryFactory getFactoryInstance( + final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping) { + return new ReactiveCouchbaseRepositoryFactory(couchbaseOperationsMapping); + } - /** - * Returns a factory instance. - * - * @return the factory instance. - */ - @Override - protected RepositoryFactorySupport createRepositoryFactory() { - return getFactoryInstance(couchbaseOperationsMapping, indexManager); - } - - /** - * Get the factory instance for the operations. - * - * @param couchbaseOperationsMapping the reference to the template. - * @param indexManager the reference to the {@link IndexManager}. - * @return the factory instance. - */ - protected ReactiveCouchbaseRepositoryFactory getFactoryInstance(final ReactiveRepositoryOperationsMapping couchbaseOperationsMapping, - IndexManager indexManager) { - return new ReactiveCouchbaseRepositoryFactory(couchbaseOperationsMapping, indexManager); - } - - /** - * Make sure that the dependencies are set and not null. - */ - @Override - public void afterPropertiesSet() { - super.afterPropertiesSet(); - Assert.notNull(couchbaseOperationsMapping, "operationsMapping must not be null!"); - Assert.notNull(indexManager, "indexManager must not be null!"); - } + /** + * Make sure that the dependencies are set and not null. + */ + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + Assert.notNull(couchbaseOperationsMapping, "operationsMapping must not be null!"); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveN1qlCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveN1qlCouchbaseRepository.java deleted file mode 100644 index 15b0cebc..00000000 --- a/src/main/java/org/springframework/data/couchbase/repository/support/ReactiveN1qlCouchbaseRepository.java +++ /dev/null @@ -1,67 +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.repository.support; - -import java.io.Serializable; - -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import com.couchbase.client.java.query.dsl.Expression; -import com.couchbase.client.java.query.dsl.path.WherePath; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.couchbase.repository.ReactiveCouchbaseSortingRepository; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.couchbase.repository.query.support.N1qlUtils; -import org.springframework.data.domain.Sort; -import org.springframework.util.Assert; -import reactor.core.publisher.Flux; - -/** - * @author Subhashni Balakrishnan - * @since 3.0 - */ -public class ReactiveN1qlCouchbaseRepository - extends SimpleReactiveCouchbaseRepository - implements ReactiveCouchbaseSortingRepository { - - public ReactiveN1qlCouchbaseRepository(CouchbaseEntityInformation metadata, RxJavaCouchbaseOperations operations) { - super(metadata, operations); - } - - @SuppressWarnings("unchecked") - @Override - public Flux findAll(Sort sort) { - Assert.notNull(sort); - - //prepare elements of the query - WherePath selectFrom = N1qlUtils.createSelectFromForEntity(getCouchbaseOperations().getCouchbaseBucket().name()); - Expression whereCriteria = N1qlUtils.createWhereFilterForEntity(null, getCouchbaseOperations().getConverter(), - getEntityInformation()); - - //apply the sort - com.couchbase.client.java.query.dsl.Sort[] orderings = N1qlUtils.createSort(sort, getCouchbaseOperations().getConverter()); - Statement st = selectFrom.where(whereCriteria).orderBy(orderings); - - //fire the query - ScanConsistency consistency = getCouchbaseOperations().getDefaultConsistency().n1qlConsistency(); - N1qlQuery query = N1qlQuery.simple(st, N1qlParams.build().consistency(consistency)); - return mapFlux(getCouchbaseOperations().findByN1QL(query, getEntityInformation().getJavaType())); - } - - -} diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java index d6b3136b..6d15ee07 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java @@ -16,24 +16,25 @@ package org.springframework.data.couchbase.repository.support; -import java.io.Serializable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.error.DocumentDoesNotExistException; -import com.couchbase.client.java.view.ViewQuery; -import com.couchbase.client.java.view.ViewResult; -import com.couchbase.client.java.view.ViewRow; - -import org.springframework.dao.DataRetrievalFailureException; import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.query.View; +import org.springframework.data.couchbase.core.query.Query; import org.springframework.data.couchbase.repository.CouchbaseRepository; import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.util.StreamUtils; +import org.springframework.data.util.Streamable; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; + +import com.couchbase.client.java.query.QueryScanConsistency; /** * Repository base implementation for Couchbase. @@ -41,213 +42,157 @@ import org.springframework.util.StringUtils; * @author Michael Nitschinger * @author Mark Paluch */ -public class SimpleCouchbaseRepository implements CouchbaseRepository { +public class SimpleCouchbaseRepository implements CouchbaseRepository { - /** - * Holds the reference to the {@link org.springframework.data.couchbase.core.CouchbaseTemplate}. - */ - private final CouchbaseOperations couchbaseOperations; + /** + * Holds the reference to the {@link org.springframework.data.couchbase.core.CouchbaseTemplate}. + */ + private final CouchbaseOperations couchbaseOperations; - /** - * Contains information about the entity being used in this repository. - */ - private final CouchbaseEntityInformation entityInformation; + /** + * Contains information about the entity being used in this repository. + */ + private final CouchbaseEntityInformation entityInformation; - /** - * Custom ViewMetadataProvider. - */ - private ViewMetadataProvider viewMetadataProvider; + private CrudMethodMetadata crudMethodMetadata; - /** - * Create a new Repository. - * - * @param metadata the Metadata for the entity. - * @param couchbaseOperations the reference to the template used. - */ - public SimpleCouchbaseRepository(final CouchbaseEntityInformation metadata, final CouchbaseOperations couchbaseOperations) { - Assert.notNull(metadata, "CouchbaseEntityInformation must not be null!"); - Assert.notNull(couchbaseOperations, "CouchbaseOperations must not be null!"); + /** + * Create a new Repository. + * + * @param entityInformation the Metadata for the entity. + * @param couchbaseOperations the reference to the template used. + */ + public SimpleCouchbaseRepository(final CouchbaseEntityInformation entityInformation, + final CouchbaseOperations couchbaseOperations) { + Assert.notNull(entityInformation, "CouchbaseEntityInformation must not be null!"); + Assert.notNull(couchbaseOperations, "CouchbaseOperations must not be null!"); - entityInformation = metadata; - this.couchbaseOperations = couchbaseOperations; - } + this.entityInformation = entityInformation; + this.couchbaseOperations = couchbaseOperations; + } - /** - * Configures a custom {@link ViewMetadataProvider} to be used to detect {@link View}s to be applied to queries. - * - * @param viewMetadataProvider that is used to lookup any annotated View on a query method. - */ - public void setViewMetadataProvider(final ViewMetadataProvider viewMetadataProvider) { - this.viewMetadataProvider = viewMetadataProvider; - } + @Override + @SuppressWarnings("unchecked") + public S save(final S entity) { + Assert.notNull(entity, "Entity must not be null!"); + return (S) couchbaseOperations.upsertById(entityInformation.getJavaType()).one(entity); + } - @Override - public S save(S entity) { - Assert.notNull(entity, "Entity must not be null!"); - couchbaseOperations.save(entity); - return entity; - } + @Override + @SuppressWarnings("unchecked") + public Iterable saveAll(final Iterable entities) { + Assert.notNull(entities, "The given Iterable of entities must not be null!"); + return (Iterable) couchbaseOperations.upsertById(entityInformation.getJavaType()) + .all(Streamable.of(entities).toList()); + } - @Override - public Iterable saveAll(Iterable entities) { - Assert.notNull(entities, "The given Iterable of entities must not be null!"); + @Override + public Optional findById(final ID id) { + Assert.notNull(id, "The given id must not be null!"); + return Optional.ofNullable(couchbaseOperations.findById(entityInformation.getJavaType()).one(id.toString())); + } - List result = new ArrayList(); - for (S entity : entities) { - save(entity); - result.add(entity); - } - return result; - } + @Override + @SuppressWarnings("unchecked") + public List findAllById(final Iterable ids) { + Assert.notNull(ids, "The given Iterable of ids must not be null!"); + List convertedIds = Streamable.of(ids).stream().map(Objects::toString).collect(Collectors.toList()); + Collection all = couchbaseOperations.findById(entityInformation.getJavaType()).all(convertedIds); + return Streamable.of(all).stream().collect(StreamUtils.toUnmodifiableList()); + } - @Override - public Optional findById(ID id) { - Assert.notNull(id, "The given id must not be null!"); - return Optional.ofNullable(couchbaseOperations.findById(couchbaseOperations.getConverter().convertForWriteIfNeeded(id).toString(), entityInformation.getJavaType())); - } + @Override + public boolean existsById(final ID id) { + Assert.notNull(id, "The given id must not be null!"); + return couchbaseOperations.existsById().one(id.toString()); + } - @Override - public boolean existsById(ID id) { - Assert.notNull(id, "The given id must not be null!"); - return couchbaseOperations.exists(couchbaseOperations.getConverter().convertForWriteIfNeeded(id).toString()); - } + @Override + public void deleteById(final ID id) { + Assert.notNull(id, "The given id must not be null!"); + couchbaseOperations.removeById().one(id.toString()); + } - @Override - public void deleteById(ID id) { - Assert.notNull(id, "The given id must not be null!"); - couchbaseOperations.remove(couchbaseOperations.getConverter().convertForWriteIfNeeded(id).toString()); - } + @Override + public void delete(final T entity) { + Assert.notNull(entity, "Entity must not be null!"); + couchbaseOperations.removeById().one(entityInformation.getId(entity)); + } - @Override - public void delete(T entity) { - Assert.notNull(entity, "The given id must not be null!"); - couchbaseOperations.remove(entity); - } + @Override + public void deleteAll(final Iterable entities) { + Assert.notNull(entities, "The given Iterable of entities must not be null!"); + couchbaseOperations.removeById().all(Streamable.of(entities).map(entityInformation::getId).toList()); + } - @Override - public void deleteAll(Iterable entities) { - Assert.notNull(entities, "The given Iterable of entities must not be null!"); - for (T entity : entities) { - couchbaseOperations.remove(entity); - } - } + @Override + public long count() { + return couchbaseOperations.findByQuery(entityInformation.getJavaType()).consistentWith(buildQueryScanConsistency()) + .count(); + } - @Override - public Iterable findAll() { - final ResolvedView resolvedView = determineView(); - ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName()); - query.reduce(false); - query.stale(getCouchbaseOperations().getDefaultConsistency().viewConsistency()); - return couchbaseOperations.findByView(query, entityInformation.getJavaType()); - } + @Override + public void deleteAll() { + couchbaseOperations.removeByQuery(entityInformation.getJavaType()).consistentWith(buildQueryScanConsistency()) + .all(); + } - @Override - public Iterable findAllById(final Iterable ids) { - final ResolvedView resolvedView = determineView(); - ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName()); - query.reduce(false); - query.stale(getCouchbaseOperations().getDefaultConsistency().viewConsistency()); - JsonArray keys = JsonArray.create(); - for (ID id : ids) { - keys.add(couchbaseOperations.getConverter().convertForWriteIfNeeded(id)); - } - query.keys(keys); + @Override + public List findAll() { + return findAll(new Query()); + } - return couchbaseOperations.findByView(query, entityInformation.getJavaType()); - } + @Override + public List findAll(final Sort sort) { + return findAll(new Query().with(sort)); + } - @Override - public long count() { - final ResolvedView resolvedView = determineView(); - ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName()); - query.reduce(true); - query.stale(getCouchbaseOperations().getDefaultConsistency().viewConsistency()); + @Override + public Page findAll(final Pageable pageable) { + List results = findAll(new Query().with(pageable)); + return new PageImpl<>(results, pageable, count()); + } - ViewResult response = couchbaseOperations.queryView(query); + @Override + public CouchbaseOperations getCouchbaseOperations() { + return couchbaseOperations; + } - long count = 0; - for (ViewRow row : response) { - count += Long.parseLong(String.valueOf(row.value())); - } + /** + * Returns the information for the underlying template. + * + * @return the underlying entity information. + */ + protected CouchbaseEntityInformation getEntityInformation() { + return entityInformation; + } - return count; - } + /** + * Helper method to assemble a n1ql find all query, taking annotations into acocunt. + * + * @param query the originating query. + * @return the list of found entities, already executed. + */ + private List findAll(final Query query) { + return couchbaseOperations.findByQuery(entityInformation.getJavaType()).consistentWith(buildQueryScanConsistency()) + .matching(query).all(); + } - @Override - public void deleteAll() { - final ResolvedView resolvedView = determineView(); - ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName()); - query.reduce(false); - query.stale(getCouchbaseOperations().getDefaultConsistency().viewConsistency()); + private QueryScanConsistency buildQueryScanConsistency() { + QueryScanConsistency scanConsistency = QueryScanConsistency.NOT_BOUNDED; + if (crudMethodMetadata.getScanConsistency() != null) { + scanConsistency = crudMethodMetadata.getScanConsistency().query(); + } + return scanConsistency; + } - - ViewResult response = couchbaseOperations.queryView(query); - for (ViewRow row : response) { - try { - couchbaseOperations.remove(row.id()); - } catch (DataRetrievalFailureException e) { - //ignore stale deletions - if (!(e.getCause() instanceof DocumentDoesNotExistException)) throw e; - } - } - } - - @Override - public CouchbaseOperations getCouchbaseOperations() { - return couchbaseOperations; - } - - /** - * Returns the information for the underlying template. - * - * @return the underlying entity information. - */ - protected CouchbaseEntityInformation getEntityInformation() { - return entityInformation; - } - - /** - * Resolve a View based upon: - *

    - * 1. Any @View annotation that is present - * 2. If none are found, default designDocument to be the entity name (lowercase) and viewName to be "all". - * - * @return ResolvedView containing the designDocument and viewName. - */ - private ResolvedView determineView() { - String designDocument = StringUtils.uncapitalize(entityInformation.getJavaType().getSimpleName()); - String viewName = "all"; - - final View view = viewMetadataProvider.getView(); - - if (view != null) { - designDocument = view.designDocument(); - viewName = view.viewName(); - } - - return new ResolvedView(designDocument, viewName); - } - - /** - * Simple holder to allow an easier exchange of information. - */ - private final class ResolvedView { - - private final String designDocument; - private final String viewName; - - public ResolvedView(final String designDocument, final String viewName) { - this.designDocument = designDocument; - this.viewName = viewName; - } - - private String getDesignDocument() { - return designDocument; - } - - private String getViewName() { - return viewName; - } - } + /** + * Setter for the repository metadata, contains annotations on the overidden methods. + * + * @param crudMethodMetadata the injected repository metadata. + */ + void setRepositoryMethodMetadata(final CrudMethodMetadata crudMethodMetadata) { + this.crudMethodMetadata = crudMethodMetadata; + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java index 826666b3..52a9cab5 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleReactiveCouchbaseRepository.java @@ -16,26 +16,23 @@ package org.springframework.data.couchbase.repository.support; -import java.io.Serializable; - -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.error.DocumentDoesNotExistException; -import com.couchbase.client.java.view.AsyncViewResult; -import com.couchbase.client.java.view.ViewQuery; - -import org.reactivestreams.Publisher; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.couchbase.core.query.View; -import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.repository.util.ReactiveWrapperConverters; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import rx.Single; -import rx.Observable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.reactivestreams.Publisher; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.ReactiveCouchbaseOperations; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository; +import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; +import org.springframework.data.domain.Sort; +import org.springframework.data.util.Streamable; +import org.springframework.util.Assert; + /** * Reactive repository base implementation for Couchbase. * @@ -46,282 +43,165 @@ import reactor.core.publisher.Mono; * @author Douglas Six * @since 3.0 */ -public class SimpleReactiveCouchbaseRepository implements ReactiveCouchbaseRepository { +public class SimpleReactiveCouchbaseRepository implements ReactiveCouchbaseRepository { - /** - * Holds the reference to the {@link org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate}. - */ - private final RxJavaCouchbaseOperations operations; + /** + * Holds the reference to the {@link CouchbaseOperations}. + */ + private final ReactiveCouchbaseOperations operations; - /** - * Contains information about the entity being used in this repository. - */ - private final CouchbaseEntityInformation entityInformation; + /** + * Contains information about the entity being used in this repository. + */ + private final CouchbaseEntityInformation entityInformation; - /** - * Custom ViewMetadataProvider. - */ - private ViewMetadataProvider viewMetadataProvider; + /** + * Create a new Repository. + * + * @param metadata the Metadata for the entity. + * @param operations the reference to the reactive template used. + */ + public SimpleReactiveCouchbaseRepository(final CouchbaseEntityInformation metadata, + final ReactiveCouchbaseOperations operations) { + Assert.notNull(operations, "RxJavaCouchbaseOperations must not be null!"); + Assert.notNull(metadata, "CouchbaseEntityInformation must not be null!"); - /** - * Create a new Repository. - * - * @param metadata the Metadata for the entity. - * @param operations the reference to the reactive template used. - */ - public SimpleReactiveCouchbaseRepository(final CouchbaseEntityInformation metadata, - final RxJavaCouchbaseOperations operations) { - Assert.notNull(operations, "RxJavaCouchbaseOperations must not be null!"); - Assert.notNull(metadata, "CouchbaseEntityInformation must not be null!"); + this.entityInformation = metadata; + this.operations = operations; + } - this.entityInformation = metadata; - this.operations = operations; - } + @SuppressWarnings("unchecked") + public Mono save(final S entity) { + Assert.notNull(entity, "Entity must not be null!"); + return (Mono) operations.upsertById(entityInformation.getJavaType()).one(entity); + } - /** - * Configures a custom {@link ViewMetadataProvider} to be used to detect {@link View}s to be applied to queries. - * - * @param viewMetadataProvider that is used to lookup any annotated View on a query method. - */ - public void setViewMetadataProvider(final ViewMetadataProvider viewMetadataProvider) { - this.viewMetadataProvider = viewMetadataProvider; - } + @Override + public Flux findAll(final Sort sort) { + return findAll(new Query().with(sort)); + } - protected Mono mapMono(Single single) { - return ReactiveWrapperConverters.toWrapper(single , Mono.class); - } + @SuppressWarnings("unchecked") + @Override + public Flux saveAll(final Iterable entities) { + Assert.notNull(entities, "The given Iterable of entities must not be null!"); + return (Flux) operations.upsertById(entityInformation.getJavaType()).all(Streamable.of(entities).toList()); + } - protected Flux mapFlux(Observable observable) { - return ReactiveWrapperConverters.toWrapper(observable, Flux.class); - } + @SuppressWarnings("unchecked") + @Override + public Flux saveAll(final Publisher entityStream) { + Assert.notNull(entityStream, "The given Iterable of entities must not be null!"); + return Flux.from(entityStream).flatMap(this::save); + } - @SuppressWarnings("unchecked") - public Mono save(S entity) { - Assert.notNull(entity, "Entity must not be null!"); - return mapMono(operations.save(entity).toSingle()); - } + @SuppressWarnings("unchecked") + @Override + public Mono findById(final ID id) { + return operations.findById(entityInformation.getJavaType()).one(id.toString()); + } - @SuppressWarnings("unchecked") - @Override - public Flux saveAll(Iterable entities) { - Assert.notNull(entities, "The given Iterable of entities must not be null!"); - return mapFlux(operations.save(entities)); - } + @SuppressWarnings("unchecked") + @Override + public Mono findById(final Publisher publisher) { + Assert.notNull(publisher, "The given Publisher must not be null!"); + return Mono.from(publisher).flatMap(this::findById); + } - @SuppressWarnings("unchecked") - @Override - public Flux saveAll(Publisher entityStream) { - Assert.notNull(entityStream, "The given Iterable of entities must not be null!"); - return Flux.from(entityStream) - .flatMap(object -> save(object)); - } + @SuppressWarnings("unchecked") + @Override + public Mono existsById(final ID id) { + Assert.notNull(id, "The given id must not be null!"); + return operations.existsById().one(id.toString()); + } - @SuppressWarnings("unchecked") - @Override - public Mono findById(ID id) { - Assert.notNull(id, "The given id must not be null!"); - return mapMono(operations.findById(id.toString(), entityInformation.getJavaType()).toSingle()) - .onErrorResume(throwable -> { - //reactive streams adapter doesn't work with null - if(throwable instanceof NullPointerException) { - return Mono.empty(); - } - return Mono.just(throwable); - }); - } + @SuppressWarnings("unchecked") + @Override + public Mono existsById(final Publisher publisher) { + Assert.notNull(publisher, "The given Publisher must not be null!"); + return Mono.from(publisher).flatMap(this::existsById); + } - @SuppressWarnings("unchecked") - @Override - public Mono findById(Publisher publisher) { - Assert.notNull(publisher, "The given Publisher must not be null!"); - return Mono.from(publisher).flatMap( - this::findById); - } + @SuppressWarnings("unchecked") + @Override + public Flux findAll() { + return findAll(new Query()); + } - @SuppressWarnings("unchecked") - @Override - public Mono existsById(ID id) { - Assert.notNull(id, "The given id must not be null!"); - return mapMono(operations.exists(id.toString()).toSingle()); - } + @SuppressWarnings("unchecked") + @Override + public Flux findAllById(final Iterable ids) { + Assert.notNull(ids, "The given Iterable of ids must not be null!"); + List convertedIds = Streamable.of(ids).stream().map(Objects::toString).collect(Collectors.toList()); + return (Flux) operations.findById(entityInformation.getJavaType()).all(convertedIds); + } - @SuppressWarnings("unchecked") - @Override - public Mono existsById(Publisher publisher) { - Assert.notNull(publisher, "The given Publisher must not be null!"); - return Mono.from(publisher).flatMap( - this::existsById); - } + @SuppressWarnings("unchecked") + @Override + public Flux findAllById(final Publisher entityStream) { + Assert.notNull(entityStream, "The given entityStream must not be null!"); + return Flux.from(entityStream).flatMap(this::findById); + } - @SuppressWarnings("unchecked") - @Override - public Flux findAll() { - final ResolvedView resolvedView = determineView(); - ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName()); - query.reduce(false); - query.stale(operations.getDefaultConsistency().viewConsistency()); - return mapFlux(operations.findByView(query, entityInformation.getJavaType())); - } + @SuppressWarnings("unchecked") + @Override + public Mono deleteById(final ID id) { + return operations.removeById().one(id.toString()).then(); + } - @SuppressWarnings("unchecked") - @Override - public Flux findAllById(final Iterable ids) { - final ResolvedView resolvedView = determineView(); - ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName()); - query.reduce(false); - query.stale(operations.getDefaultConsistency().viewConsistency()); - JsonArray keys = JsonArray.create(); - for (ID id : ids) { - keys.add(id); - } - query.keys(keys); - return mapFlux(operations.findByView(query, entityInformation.getJavaType())); - } + @Override + public Mono deleteById(final Publisher publisher) { + Assert.notNull(publisher, "The given id must not be null!"); + return Mono.from(publisher).flatMap(this::deleteById); + } - @SuppressWarnings("unchecked") - @Override - public Flux findAllById(Publisher entityStream) { - Assert.notNull(entityStream, "The given entityStream must not be null!"); - return Flux.from(entityStream) - .flatMap(this::findById); - } + @SuppressWarnings("unchecked") + @Override + public Mono delete(final T entity) { + Assert.notNull(entity, "Entity must not be null!"); + return operations.removeById().one(entityInformation.getId(entity)).then(); + } - @SuppressWarnings("unchecked") - @Override - public Mono deleteById(ID id) { - Assert.notNull(id, "The given id must not be null!"); - return mapMono(operations.remove(id.toString()).map(res -> Observable.empty()).toSingle()); - } + @SuppressWarnings("unchecked") + @Override + public Mono deleteAll(final Iterable entities) { + return operations.removeById().all(Streamable.of(entities).map(entityInformation::getId).toList()).then(); + } - @Override - public Mono deleteById(Publisher publisher) { - Assert.notNull(publisher, "The given id must not be null!"); - return Mono.from(publisher).flatMap( - this::deleteById); - } + @Override + public Mono deleteAll(final Publisher entityStream) { + Assert.notNull(entityStream, "The given publisher of entities must not be null!"); + return Flux.from(entityStream).flatMap(this::delete).single(); + } - @SuppressWarnings("unchecked") - @Override - public Mono delete(T entity) { - Assert.notNull(entity, "The given id must not be null!"); - return mapMono(operations.remove(entity).map(res -> Observable.empty()).toSingle()); - } + @SuppressWarnings("unchecked") + @Override + public Mono count() { + return operations.findByQuery(entityInformation.getJavaType()).count(); + } - @SuppressWarnings("unchecked") - @Override - public Mono deleteAll(Iterable entities) { - Assert.notNull(entities, "The given Iterable of entities must not be null!"); - return mapMono(operations - .remove(entities) - .last() - .map(res -> Observable.empty()).toSingle()); - } + @SuppressWarnings("unchecked") + @Override + public Mono deleteAll() { + return operations.removeByQuery(entityInformation.getJavaType()).all().then(); + } + /** + * Returns the information for the underlying template. + * + * @return the underlying entity information. + */ + protected CouchbaseEntityInformation getEntityInformation() { + return entityInformation; + } - @Override - public Mono deleteAll(Publisher entityStream) { - Assert.notNull(entityStream, "The given publisher of entities must not be null!"); - return Flux.from(entityStream) - .flatMap(entity -> delete(entity)).single(); - } + @Override + public ReactiveCouchbaseOperations getReactiveCouchbaseOperations() { + return operations; + } - @SuppressWarnings("unchecked") - @Override - public Mono count() { - final ResolvedView resolvedView = determineView(); - ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName()); - query.reduce(true); - query.stale(operations.getDefaultConsistency().viewConsistency()); - - return mapMono(operations - .queryView(query) - .flatMap(AsyncViewResult::rows) - .map(asyncViewRow -> - Long.valueOf(asyncViewRow.value().toString())) - .switchIfEmpty(Observable.just(0L)).toSingle()); - } - - @SuppressWarnings("unchecked") - @Override - public Mono deleteAll() { - final ResolvedView resolvedView = determineView(); - ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName()); - query.reduce(false); - query.stale(operations.getDefaultConsistency().viewConsistency()); - - - return mapMono(operations.queryView(query) - .flatMap(AsyncViewResult::rows) - .flatMap(row -> { - return operations.remove(row.id()) - .onErrorResumeNext(throwable -> { - if (throwable instanceof DocumentDoesNotExistException) { - return Observable.empty(); - } - return Observable.error(throwable); - }); - }) - .toList() - .map(list -> Observable.empty()) - .toSingle()); - } - - /** - * Returns the information for the underlying template. - * - * @return the underlying entity information. - */ - protected CouchbaseEntityInformation getEntityInformation() { - return entityInformation; - } - - /** - * Resolve a View based upon: - *

    - * 1. Any @View annotation that is present - * 2. If none are found, default designDocument to be the entity name (lowercase) and viewName to be "all". - * - * @return ResolvedView containing the designDocument and viewName. - */ - private ResolvedView determineView() { - String designDocument = StringUtils.uncapitalize(entityInformation.getJavaType().getSimpleName()); - String viewName = "all"; - - final View view = viewMetadataProvider.getView(); - - if (view != null) { - designDocument = view.designDocument(); - viewName = view.viewName(); - } - - return new ResolvedView(designDocument, viewName); - } - - @Override - public RxJavaCouchbaseOperations getCouchbaseOperations(){ - return operations; - } - - /** - * Simple holder to allow an easier exchange of information. - */ - private final class ResolvedView { - - private final String designDocument; - private final String viewName; - - public ResolvedView(final String designDocument, final String viewName) { - this.designDocument = designDocument; - this.viewName = viewName; - } - - private String getDesignDocument() { - return designDocument; - } - - private String getViewName() { - return viewName; - } - } + private Flux findAll(final Query query) { + return operations.findByQuery(entityInformation.getJavaType()).matching(query).all(); + } } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/package-info.java b/src/main/java/org/springframework/data/couchbase/repository/support/package-info.java index d856fa1e..933ff11d 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/package-info.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/package-info.java @@ -1,7 +1,22 @@ +/* + * 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. + */ + /** - * This package contains the Couchbase implementations to support the Spring Data repository abstraction. - *
    - * This includes repository factories and factory beans, concrete base repository classes, metadata providers - * and a class in charge of managing various indexes (views, N1QL). + * This package contains the Couchbase implementations to support the Spring Data repository abstraction.
    + * This includes repository factories and factory beans, concrete base repository classes, metadata providers and a + * class in charge of managing various indexes (views, N1QL). */ package org.springframework.data.couchbase.repository.support; diff --git a/src/main/kotlin/org/springframework/data/couchbase/core/CouchbaseOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/couchbase/core/CouchbaseOperationsExtensions.kt index c6efd1b8..630387c9 100644 --- a/src/main/kotlin/org/springframework/data/couchbase/core/CouchbaseOperationsExtensions.kt +++ b/src/main/kotlin/org/springframework/data/couchbase/core/CouchbaseOperationsExtensions.kt @@ -16,52 +16,8 @@ package org.springframework.data.couchbase.core -import com.couchbase.client.java.query.N1qlQuery -import com.couchbase.client.java.view.SpatialViewQuery -import com.couchbase.client.java.view.ViewQuery - /** * Kotlin extensions for [CouchbaseOperations] * * @author Subhashni Balakrishnan - */ - -/** - * Extension for [CouchbaseOperations.findById] leveraging reified type - * @param id the identifier of the document. - * @return the document fetched from couchbase. - */ -inline fun CouchbaseOperations.findById(id: String) : T = - findById(id, T::class.java) - -/** - * Extension for [CouchbaseOperations.findByView] leveraging reified type. - * @param query a [ViewQuery] instance that defines the query. - * @return list of entities satisfying the view query. - */ -inline fun CouchbaseOperations.findByView(query: ViewQuery) : List = - findByView(query, T::class.java) - -/** - * Extension for [CouchbaseOperations.findBySpatialView] leveraging reified type. - * @param query a [SpatialViewQuery] instance that defines the query. - * @return list of entities satisfying the spatial view query. - */ -inline fun CouchbaseOperations.findBySpatialView(query: SpatialViewQuery) : List = - findBySpatialView(query, T::class.java) - -/** - * Extension for [CouchbaseOperations.findByN1QL] leveraging reified type. - * @param query a [N1qlQuery] instance that defines the query. - * @return list of entities satisfying the N1ql query. - */ -inline fun CouchbaseOperations.findByN1QL(query: N1qlQuery) : List = - findByN1QL(query, T::class.java) - -/** - * Extension for [CouchbaseOperations.findByN1QLProjection] leveraging reified type. - * @param query a [N1qlQuery] instance that defines the query. - * @return list of entities satisfying the N1ql query projection. - */ -inline fun CouchbaseOperations.findByN1QLProjection(query: N1qlQuery) : List = - findByN1QLProjection(query, T::class.java) + */ \ No newline at end of file diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonFragmentImpl.java b/src/main/kotlin/org/springframework/data/couchbase/core/ReactiveJavaCouchbaseOperationsExtensions.kt similarity index 62% rename from src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonFragmentImpl.java rename to src/main/kotlin/org/springframework/data/couchbase/core/ReactiveJavaCouchbaseOperationsExtensions.kt index 4890df46..0221abe5 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonFragmentImpl.java +++ b/src/main/kotlin/org/springframework/data/couchbase/core/ReactiveJavaCouchbaseOperationsExtensions.kt @@ -1,11 +1,11 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2018-2019 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 + * 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, @@ -13,15 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.couchbase.repository.cdi; + +package org.springframework.data.couchbase.core /** - * @author Mark Paluch + * Kotlin extensions for [ReactiveJavaCouchbaseOperations] + * + * @author Subhashni Balakrishnan */ -public class CdiPersonFragmentImpl implements CdiPersonFragment { - - @Override - public int returnTwo() { - return 2; - } -} diff --git a/src/main/kotlin/org/springframework/data/couchbase/core/RxJavaCouchbaseOperationsExtensions.kt b/src/main/kotlin/org/springframework/data/couchbase/core/RxJavaCouchbaseOperationsExtensions.kt deleted file mode 100644 index a2686ff8..00000000 --- a/src/main/kotlin/org/springframework/data/couchbase/core/RxJavaCouchbaseOperationsExtensions.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2018-2019 the original author or authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core - -import com.couchbase.client.java.query.N1qlQuery -import com.couchbase.client.java.view.SpatialViewQuery -import com.couchbase.client.java.view.ViewQuery -import rx.Observable - -/** - * Kotlin extensions for [RxJavaCouchbaseOperations] - * - * @author Subhashni Balakrishnan - */ - -/** - * Extension for [RxJavaCouchbaseOperations.findById] leveraging reified type - * @param id the identifier of the document. - * @return the document fetched from couchbase. - */ -inline fun RxJavaCouchbaseOperations.findById(id: String) : Observable = - findById(id, T::class.java) - -/** - * Extension for [RxJavaCouchbaseOperations.findByView] leveraging reified type. - * @param query a [ViewQuery] instance that defines the query. - * @return list of entities satisfying the view query. - */ -inline fun RxJavaCouchbaseOperations.findByView(query: ViewQuery) : Observable = - findByView(query, T::class.java) - -/** - * Extension for [RxJavaCouchbaseOperations.findBySpatialView] leveraging reified type. - * @param query a [SpatialViewQuery] instance that defines the query. - * @return list of entities satisfying the spatial view query. - */ -inline fun RxJavaCouchbaseOperations.findBySpatialView(query: SpatialViewQuery) : Observable = - findBySpatialView(query, T::class.java) - -/** - * Extension for [RxJavaCouchbaseOperations.findByN1QL] leveraging reified type. - * @param query a [N1qlQuery] instance that defines the query. - * @return list of entities satisfying the n1ql query. - */ -inline fun RxJavaCouchbaseOperations.findByN1QL(query: N1qlQuery) : Observable = - findByN1QL(query, T::class.java) - -/** - * Extension for [RxJavaCouchbaseOperations.findByN1QLProjection] leveraging reified type. - * @param query a [N1qlQuery] instance that defines the query. - * @return list of entities satisfying the n1ql query projection. - */ -inline fun RxJavaCouchbaseOperations.findByN1QLProjection(query: N1qlQuery) : Observable = - findByN1QLProjection(query, T::class.java) diff --git a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-1.0.xsd b/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-1.0.xsd deleted file mode 100644 index ac8f004d..00000000 --- a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-1.0.xsd +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The reference to a CouchbaseClient object. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The reference to a CouchbaseTemplate. Will default to 'couchbaseTemplate'. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd b/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd deleted file mode 100644 index 523987e3..00000000 --- a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The reference to a ClusterInfo object giving information about the cluster this template connects to. - - - - - - - - - - - - The reference to a Bucket object. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The reference to a CouchbaseTemplate. Will default to 'couchbaseTemplate'. - - - - - - - The reference to an IndexManager. Will default to 'couchbaseIndexManager'. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-env-2.0.xsd b/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-env-2.0.xsd deleted file mode 100644 index 9752a144..00000000 --- a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-env-2.0.xsd +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/java/org/springframework/data/couchbase/ContainerResourceRunner.java b/src/test/java/org/springframework/data/couchbase/ContainerResourceRunner.java deleted file mode 100644 index 4e58f23d..00000000 --- a/src/test/java/org/springframework/data/couchbase/ContainerResourceRunner.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.springframework.data.couchbase; - -import org.junit.ClassRule; -import org.junit.runners.model.InitializationError; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -/** - * This runner initializes container for the container based testing. - * - * @author Subhashni Balakrishnan - */ -public class ContainerResourceRunner extends SpringJUnit4ClassRunner { - - @ClassRule - public static final TestContainerResource resource = TestContainerResource.getResource(); - - public ContainerResourceRunner(Class clazz) throws InitializationError { - super(clazz); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/CouchbaseHttpPortListeningCheck.java b/src/test/java/org/springframework/data/couchbase/CouchbaseHttpPortListeningCheck.java deleted file mode 100644 index dd82b6a2..00000000 --- a/src/test/java/org/springframework/data/couchbase/CouchbaseHttpPortListeningCheck.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.springframework.data.couchbase; - -import java.util.concurrent.Callable; - -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.HttpClientBuilder; - -/** - * Helper to check if the Couchbase http endpoints are up. - */ -public class CouchbaseHttpPortListeningCheck implements Callable { - - private final int port; - private final String path; - - public CouchbaseHttpPortListeningCheck(int port, String path) { - this.port = port; - this.path = path; - } - - private Boolean executeRequest(URIBuilder builder) throws Exception { - try { - HttpGet request = new HttpGet(builder.build()); - HttpClient client = HttpClientBuilder.create().build(); - HttpResponse response = client.execute(request); - int status = response.getStatusLine().getStatusCode(); - if (status < 200 || status >= 300) { - return false; - } - return true; - } catch (Exception ex) { - Thread.sleep(1000); - throw ex; - } - } - - @Override - public Boolean call() throws Exception { - URIBuilder builder = new URIBuilder(); - builder.setScheme("http").setHost("localhost").setPort(this.port).setPath(this.path); - return executeRequest(builder); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/CouchbaseTestHelper.java b/src/test/java/org/springframework/data/couchbase/CouchbaseTestHelper.java deleted file mode 100644 index 34d34c1f..00000000 --- a/src/test/java/org/springframework/data/couchbase/CouchbaseTestHelper.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2019 Couchbase, Inc. - * - * 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 org.rnorth.ducttape.unreliables.Unreliables; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; - -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - -public class CouchbaseTestHelper { - private CouchbaseTestHelper() { - throw new AssertionError("not instantiable"); - } - - /** - * Returns a repository instance for the given interface. - *

    - * Retry because concurrent index creation throws exception. - * See https://issues.couchbase.com/browse/MB-32238 - */ - public static T getRepositoryWithRetry(RepositoryFactorySupport factory, Class repositoryInterface) { - return retryUntilSuccess(() -> factory.getRepository(repositoryInterface)); - } - - private static T retryUntilSuccess(final Callable lambda) { - return Unreliables.retryUntilSuccess(10, TimeUnit.SECONDS, lambda); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/CouchbaseWaitStrategy.java b/src/test/java/org/springframework/data/couchbase/CouchbaseWaitStrategy.java deleted file mode 100644 index 158d274d..00000000 --- a/src/test/java/org/springframework/data/couchbase/CouchbaseWaitStrategy.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.springframework.data.couchbase; - -import static java.time.temporal.ChronoUnit.SECONDS; - -import java.time.Duration; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - -import com.couchbase.client.java.util.features.Version; -import org.rnorth.ducttape.unreliables.Unreliables; -import org.testcontainers.containers.Container; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.WaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; - -/** - * WaitStrategy for Couchbase containers which makes the Server node is - * initialized, RBAC user and default bucket is created. - */ -public class CouchbaseWaitStrategy implements WaitStrategy { - - private Duration startupTimeout = Duration.of(60, SECONDS); - private final Boolean rbacEnabled; - private final Container container; - - public CouchbaseWaitStrategy(String serverVersion, GenericContainer container) { - this.container = container; - Version version = Version.parseVersion(serverVersion); - rbacEnabled = version.major() >= 5; - } - - private void checkResult(Container.ExecResult result, String command) throws Exception { - if (!result.getStdout().contains("SUCCESS")) { - throw new Exception(command + " command failed"); - } - } - - private void checkService(int port, String path) { - Callable externalCheck = new CouchbaseHttpPortListeningCheck(port, path); - Unreliables.retryUntilSuccess((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> - externalCheck.call()); - } - - @Override - public void waitUntilReady(WaitStrategyTarget target) { - try { - checkService(8091, "/pools"); - Container.ExecResult result; - if (rbacEnabled) { - result = container.execInContainer("/opt/couchbase/bin/couchbase-cli", - "cluster-init", - "--cluster=127.0.0.1:8091", - "--services=data,index,query", - "--cluster-name=localcontainer", - "--cluster-username=Administrator", - "--cluster-password=password", - "--cluster-ramsize=512", - "--cluster-index-ramsize=512", - "--index-storage-setting=default"); - checkResult(result, "Cluster init"); - result = container.execInContainer("/opt/couchbase/bin/couchbase-cli", - "user-manage", - "--cluster=127.0.0.1:8091", - "--username=Administrator", - "--password=password", - "--set", - "--rbac-username=protected", - "--rbac-password=password", - "--rbac-name=default", - "--roles=admin", - "--auth-domain=local"); - checkResult(result, "User manage"); - result = container.execInContainer("/opt/couchbase/bin/couchbase-cli", - "bucket-create", - "--cluster=127.0.0.1:8091", - "--username=Administrator", - "--password=password", - "--bucket=protected", - "--bucket-type=couchbase", - "--bucket-ramsize=200", - "--enable-flush=1", - "--wait"); - } else { - result = container.execInContainer("/opt/couchbase/bin/couchbase-cli", - "cluster-init", - "--cluster=127.0.0.1:8091", - "--services=data,index,query", - "-u", - "Administrator", - "-p", - "password", - "--cluster-ramsize=512", - "--cluster-index-ramsize=512", - "--index-storage-setting=default"); - checkResult(result, "Cluster init"); - result = container.execInContainer("/opt/couchbase/bin/couchbase-cli", - "bucket-create", - "--cluster=127.0.0.1:8091", - "-u", - "Administrator", - "-p", - "password", - "--bucket=protected", - "--bucket-password=password", - "--bucket-type=couchbase", - "--bucket-ramsize=200", - "--enable-flush=1", - "--wait"); - } - - checkResult(result, "Bucket create"); - checkService(8093, "/query/ping"); - } catch (Exception ex) { - ex.printStackTrace(); - System.exit(1); - } - } - - @Override - public WaitStrategy withStartupTimeout(Duration startupTimeout) { - this.startupTimeout = startupTimeout; - return this; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java deleted file mode 100644 index 4867530a..00000000 --- a/src/test/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.springframework.data.couchbase; - -import java.util.Collections; -import java.util.List; - -import com.couchbase.client.java.env.CouchbaseEnvironment; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.config.CouchbaseConfigurer; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.WriteResultChecking; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.repository.support.IndexManager; - -@Configuration -public class IntegrationTestApplicationConfig extends AbstractCouchbaseConfiguration { - - @Bean - public String couchbaseAdminUser() { - return "Administrator"; - } - - @Bean - public String couchbaseAdminPassword() { - return "password"; - } - - @Override - protected List getBootstrapHosts() { - return Collections.singletonList("127.0.0.1"); - } - - @Override - protected String getBucketName() { - return "protected"; - } - - @Override - protected String getBucketPassword() { - return "password"; - } - - //TODO maybe create the bucket if doesn't exist - - @Override - protected CouchbaseEnvironment getEnvironment() { - return DefaultCouchbaseEnvironment.builder() - .connectTimeout(10000) - .kvTimeout(10000) - .queryTimeout(20000) - .viewTimeout(20000) - .build(); - } - - @Override - public CouchbaseTemplate couchbaseTemplate() throws Exception { - CouchbaseTemplate template = super.couchbaseTemplate(); - template.setWriteResultChecking(WriteResultChecking.LOG); - return template; - } - - //this is for dev so it is ok to auto-create indexes - @Override - public IndexManager indexManager() { - return new IndexManager(); - } - - @Override - protected Consistency getDefaultConsistency() { - return Consistency.READ_YOUR_OWN_WRITES; - } - - - @Override - protected CouchbaseConfigurer couchbaseConfigurer() { - return this; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/IntegrationTestCustomKeySettings.java b/src/test/java/org/springframework/data/couchbase/IntegrationTestCustomKeySettings.java deleted file mode 100644 index 9ee5d5df..00000000 --- a/src/test/java/org/springframework/data/couchbase/IntegrationTestCustomKeySettings.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.springframework.data.couchbase; - -import org.springframework.context.annotation.Configuration; - -@Configuration -public class IntegrationTestCustomKeySettings extends IntegrationTestApplicationConfig { -} diff --git a/src/test/java/org/springframework/data/couchbase/IntegrationTestCustomTypeKeyConfig.java b/src/test/java/org/springframework/data/couchbase/IntegrationTestCustomTypeKeyConfig.java deleted file mode 100644 index 1ae3206e..00000000 --- a/src/test/java/org/springframework/data/couchbase/IntegrationTestCustomTypeKeyConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.springframework.data.couchbase; - -import org.springframework.context.annotation.Configuration; - -@Configuration -public class IntegrationTestCustomTypeKeyConfig extends IntegrationTestApplicationConfig { - - //change the name of the field that will hold type information - @Override - public String typeKey() { - return "javaClass"; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/IntegrationTestNoShutdownApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/IntegrationTestNoShutdownApplicationConfig.java deleted file mode 100644 index 7f8d0b30..00000000 --- a/src/test/java/org/springframework/data/couchbase/IntegrationTestNoShutdownApplicationConfig.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.springframework.data.couchbase; - -import java.util.Collections; -import java.util.List; -import org.springframework.context.annotation.Bean; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.config.CouchbaseConfigurer; - -/** - * Configuration for testing no shutdown - * - * @author Subhashni Balakrishnan - */ -public class IntegrationTestNoShutdownApplicationConfig extends AbstractCouchbaseConfiguration { - - @Bean - public String couchbaseAdminUser() { - return "Administrator"; - } - - @Bean - public String couchbaseAdminPassword() { - return "password"; - } - - @Override - protected List getBootstrapHosts() { - return Collections.singletonList("127.0.0.1"); - } - - @Override - protected String getBucketName() { - return "protected"; - } - - @Override - protected String getBucketPassword() { - return "password"; - } - - @Override - protected boolean isEnvironmentManagedBySpring() { - return false; - } - - @Override - protected CouchbaseConfigurer couchbaseConfigurer() { - return this; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/ReactiveIntegrationTestApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/ReactiveIntegrationTestApplicationConfig.java deleted file mode 100644 index 3a75f7f1..00000000 --- a/src/test/java/org/springframework/data/couchbase/ReactiveIntegrationTestApplicationConfig.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.springframework.data.couchbase; - -import java.util.Collections; -import java.util.List; - -import com.couchbase.client.java.env.CouchbaseEnvironment; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.config.AbstractReactiveCouchbaseConfiguration; -import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate; -import org.springframework.data.couchbase.core.WriteResultChecking; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.repository.support.IndexManager; - -@Configuration -public class ReactiveIntegrationTestApplicationConfig extends AbstractReactiveCouchbaseConfiguration { - - @Bean - public String couchbaseAdminUser() { - return "Administrator"; - } - - @Bean - public String couchbaseAdminPassword() { - return "password"; - } - - @Override - protected List getBootstrapHosts() { - return Collections.singletonList("127.0.0.1"); - } - - @Override - protected String getBucketName() { - return "protected"; - } - - @Override - protected String getBucketPassword() { - return "password"; - } - - @Override - protected CouchbaseEnvironment getEnvironment() { - return DefaultCouchbaseEnvironment.builder() - .connectTimeout(10000) - .kvTimeout(10000) - .queryTimeout(10000) - .viewTimeout(10000) - .build(); - } - - @Override - public RxJavaCouchbaseTemplate reactiveCouchbaseTemplate() throws Exception { - RxJavaCouchbaseTemplate template = super.reactiveCouchbaseTemplate(); - template.setWriteResultChecking(WriteResultChecking.LOG); - return template; - } - - //this is for dev so it is ok to auto-create indexes - @Override - public IndexManager indexManager() { - return new IndexManager(); - } - - @Override - protected Consistency getDefaultConsistency() { - return Consistency.READ_YOUR_OWN_WRITES; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/TestContainerResource.java b/src/test/java/org/springframework/data/couchbase/TestContainerResource.java deleted file mode 100644 index f589c285..00000000 --- a/src/test/java/org/springframework/data/couchbase/TestContainerResource.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.springframework.data.couchbase; - -import java.io.IOException; -import java.util.Properties; -import java.util.concurrent.atomic.AtomicInteger; -import org.junit.rules.ExternalResource; -import org.testcontainers.containers.FixedHostPortGenericContainer; - -/** - * Testcontainers as external resource. It is recommended to use it as ClassRule. - * It also does the internal reference counting, in case if the getResource is called again. - * - */ -public class TestContainerResource extends ExternalResource { - - private static FixedHostPortGenericContainer couchbaseContainer = null; - private static final AtomicInteger referenceCount = new AtomicInteger(); - private static TestContainerResource currentInstance; - private static String serverVersion; - - - public static TestContainerResource getResource() { - if (currentInstance == null) { - currentInstance = new TestContainerResource(); - try { - Properties properties = new Properties(); - properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("server.properties")); - serverVersion = properties.getProperty("server.version"); - if(!"container".equals(properties.getProperty("server.resource"))) { - return null; - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - couchbaseContainer = new FixedHostPortGenericContainer("couchbase:" + serverVersion) - .withFixedExposedPort(8091, 8091) - .withFixedExposedPort(18091, 18091) - .withFixedExposedPort(8092, 8092) - .withFixedExposedPort(18092, 18092) - .withFixedExposedPort(8093, 8093) - .withFixedExposedPort(18093, 18093) - .withFixedExposedPort(8094, 8094) - .withFixedExposedPort(18094, 18094) - .withFixedExposedPort(11210, 11210) - .withFixedExposedPort(11211, 11211) - .withFixedExposedPort(11207, 11207); - couchbaseContainer.waitingFor(new CouchbaseWaitStrategy(serverVersion, couchbaseContainer)); - couchbaseContainer.start(); - } - - return currentInstance; - } - - @Override - protected void before() { - referenceCount.incrementAndGet(); - } - - @Override - protected void after() { - if (referenceCount.decrementAndGet() == 0 && couchbaseContainer != null) { - if(couchbaseContainer.isRunning()) { - couchbaseContainer.close(); - } - currentInstance = null; - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java deleted file mode 100644 index 50f66958..00000000 --- a/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.springframework.data.couchbase; - -import static org.mockito.Mockito.*; - -import java.util.Collections; -import java.util.List; - -import org.mockito.Mockito; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.WriteResultChecking; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.repository.support.IndexManager; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.Cluster; -import com.couchbase.client.java.CouchbaseBucket; -import com.couchbase.client.java.CouchbaseCluster; -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.cluster.DefaultClusterInfo; -import com.couchbase.client.java.util.features.CouchbaseFeature; -import com.couchbase.client.java.util.features.Version; - -@Configuration -public class UnitTestApplicationConfig extends AbstractCouchbaseConfiguration { - - @Bean - public String couchbaseAdminUser() { - return "someLogin"; - } - - @Bean - public String couchbaseAdminPassword() { - return "somePassword"; - } - - @Override - protected List getBootstrapHosts() { - return Collections.singletonList("192.1.2.3"); - } - - @Override - protected String getBucketName() { - return "someBucket"; - } - - @Override - protected String getBucketPassword() { - return "someBucketPassword"; - } - - @Override - public Cluster couchbaseCluster() throws Exception { - return Mockito.mock(CouchbaseCluster.class); - } - - @Override - public ClusterInfo couchbaseClusterInfo() { - DefaultClusterInfo mock = Mockito.mock(DefaultClusterInfo.class); - when(mock.checkAvailable(CouchbaseFeature.N1QL)).thenReturn(true); - when(mock.getMinVersion()).thenReturn(new Version(4, 0, 0)); - return mock; - } - - @Override - public Bucket couchbaseClient() throws Exception { - return Mockito.mock(CouchbaseBucket.class); - } - - @Override - public CouchbaseTemplate couchbaseTemplate() throws Exception { - CouchbaseTemplate template = super.couchbaseTemplate(); - template.setWriteResultChecking(WriteResultChecking.LOG); - return template; - } - - //this is for dev so it is ok to auto-create indexes - @Override - public IndexManager indexManager() { - return new IndexManager(); - } - - @Override - protected Consistency getDefaultConsistency() { - return Consistency.READ_YOUR_OWN_WRITES; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfigurationIntegrationTests.java deleted file mode 100644 index db86c5bb..00000000 --- a/src/test/java/org/springframework/data/couchbase/config/AbstractCouchbaseDataConfigurationIntegrationTests.java +++ /dev/null @@ -1,164 +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.CouchbaseCluster; -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.document.JsonDocument; -import com.couchbase.client.java.env.CouchbaseEnvironment; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.annotation.Id; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.repository.CouchbaseRepository; -import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; -import org.springframework.stereotype.Repository; -import org.springframework.test.context.ContextConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This test case demonstrates that the {@link AbstractCouchbaseDataConfiguration} can take its SDK beans - * from a sibling {@link Configuration}. - * - * Tests DATACOUCH-279 - * - * @author Simon Baslé - */ -@SuppressWarnings("SpringJavaAutowiringInspection") -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration -public class AbstractCouchbaseDataConfigurationIntegrationTests { - - @Autowired - ItemRepository repository; - - @Autowired - Bucket client; - - @Configuration - static class SdkConfig { - - private static final String IP = "127.0.0.1"; - private static final String BUCKET_NAME = "protected"; - private static final String BUCKET_PASSWORD = "password"; - - public static Bucket bucket; - - @Bean - public Cluster couchbaseCluster() { - return CouchbaseCluster.create(couchbaseEnv(), IP); - } - - @Bean - public ClusterInfo couchbaseClusterInfo() { - return couchbaseCluster().clusterManager(BUCKET_NAME, BUCKET_PASSWORD).info(); - } - - @Bean - public Bucket couchbaseBucket() { - Bucket b = couchbaseCluster().openBucket(BUCKET_NAME, BUCKET_PASSWORD); - bucket = b; - return b; - } - - @Bean - public CouchbaseEnvironment couchbaseEnv() { - return DefaultCouchbaseEnvironment.create(); - } - } - - @Configuration - @EnableCouchbaseRepositories(basePackageClasses = AbstractCouchbaseDataConfigurationIntegrationTests.class, considerNestedRepositories = true) - static class Config extends AbstractCouchbaseDataConfiguration { - - @Autowired - Cluster c; - - @Autowired - ClusterInfo ci; - - @Autowired - Bucket b; - - @Autowired - CouchbaseEnvironment e; - - @Override - protected CouchbaseConfigurer couchbaseConfigurer() { - return new TestCouchbaseConfigurer(e, c, ci, b); - } - } - - @Test - public void testInjectedBucketIsFromAdditionalConfig() { - assertThat(SdkConfig.bucket).isSameAs(client); - } - - @Test - public void testTemplateIsUsable() { - String key = "simpleConfigTest"; - assertThat(repository).isNotNull(); - - Item item = new Item(); - item.id = key; - item.value = "Test if the SimpleCouchbaseConfiguration can correctly get Bucket/Cluster/etc... beans injected"; - - repository.save(item); - JsonDocument testDoc = client.get(key); - - assertThat(testDoc).isNotNull(); - assertThat(testDoc.content()).isNotNull(); - assertThat(testDoc.content().getString("value")).isEqualTo(item.value); - } - - private static class Item { - @Id - public String id; - - public String value; - } - - private static class TestCouchbaseConfigurer implements CouchbaseConfigurer { - - private CouchbaseEnvironment env; - private Cluster cluster; - private ClusterInfo info; - private Bucket bucket; - - public TestCouchbaseConfigurer(CouchbaseEnvironment env, Cluster cluster, ClusterInfo info, Bucket bucket) { - this.env = env; - this.cluster = cluster; - this.info = info; - this.bucket = bucket; - } - - @Override - public CouchbaseEnvironment couchbaseEnvironment() throws Exception { - return this.env; - } - - @Override - public Cluster couchbaseCluster() throws Exception { - return this.cluster; - } - - @Override - public ClusterInfo couchbaseClusterInfo() throws Exception { - return this.info; - } - - @Override - public Bucket couchbaseClient() throws Exception { - return this.bucket; - } - } - - @Repository - interface ItemRepository extends CouchbaseRepository {} -} diff --git a/src/test/java/org/springframework/data/couchbase/config/CouchbaseBucketParserTest.java b/src/test/java/org/springframework/data/couchbase/config/CouchbaseBucketParserTest.java deleted file mode 100644 index 82b9f36f..00000000 --- a/src/test/java/org/springframework/data/couchbase/config/CouchbaseBucketParserTest.java +++ /dev/null @@ -1,135 +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.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.core.io.ClassPathResource; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CouchbaseBucketParserTest { - - private static DefaultListableBeanFactory factory; - - @BeforeClass - public static void setUp() { - factory = new DefaultListableBeanFactory(); - BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); - int n = reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbaseBucket-bean.xml")); - System.out.println(n); - } - - @AfterClass - public static void tearDown() { - } - - @Test - public void testDefaultBucketNoCluster() { - BeanDefinition def = factory.getBeanDefinition("bucketDefaultNoCluster"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(1); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - - ConstructorArgumentValues.ValueHolder holder = def.getConstructorArgumentValues() - .getArgumentValue(0, Object.class); - assertThat(holder.getValue()).isInstanceOf(RuntimeBeanReference.class); - - RuntimeBeanReference clusterRef = (RuntimeBeanReference) holder.getValue(); - - assertThat(clusterRef.getBeanName()).isEqualTo(BeanNames.COUCHBASE_CLUSTER); - } - - @Test - public void testDefaultBucket() throws Exception { - BeanDefinition def = factory.getBeanDefinition("bucketDefault"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(1); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - - ConstructorArgumentValues.ValueHolder holder = def.getConstructorArgumentValues() - .getArgumentValue(0, Object.class); - assertThat(holder.getValue()).isInstanceOf(RuntimeBeanReference.class); - - RuntimeBeanReference clusterRef = (RuntimeBeanReference) holder.getValue(); - - assertThat(clusterRef.getBeanName()).isEqualTo("clusterDefault"); - } - - @Test - public void testBucketWithName() throws Exception { - BeanDefinition def = factory.getBeanDefinition("bucketWithName"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(2); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - - ConstructorArgumentValues.ValueHolder holder = def.getConstructorArgumentValues() - .getArgumentValue(0, Object.class); - assertThat(holder.getValue()).isInstanceOf(RuntimeBeanReference.class); - - RuntimeBeanReference clusterRef = (RuntimeBeanReference) holder.getValue(); - assertThat(clusterRef.getBeanName()).isEqualTo("clusterDefault"); - - ConstructorArgumentValues.ValueHolder nameHolder = def.getConstructorArgumentValues() - .getArgumentValue(1, Object.class); - assertThat(nameHolder.getValue()).isInstanceOf(String.class); - assertThat(nameHolder.getValue()).hasToString("toto"); - } - - @Test - public void testBucketWithNameAndPassword() throws Exception { - BeanDefinition def = factory.getBeanDefinition("bucketWithNameAndPassword"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(4); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - - ConstructorArgumentValues.ValueHolder holder = def.getConstructorArgumentValues() - .getArgumentValue(0, Object.class); - assertThat(holder.getValue()).isInstanceOf(RuntimeBeanReference.class); - - RuntimeBeanReference clusterRef = (RuntimeBeanReference) holder.getValue(); - assertThat(clusterRef.getBeanName()).isEqualTo("clusterDefault"); - - ConstructorArgumentValues.ValueHolder nameHolder = def.getConstructorArgumentValues() - .getArgumentValue(1, Object.class); - assertThat(nameHolder.getValue()).isInstanceOf(String.class); - assertThat(nameHolder.getValue()).hasToString("test"); - - - ConstructorArgumentValues.ValueHolder usernameHolder = def.getConstructorArgumentValues() - .getArgumentValue(2, Object.class); - assertThat(usernameHolder.getValue()).isInstanceOf(String.class); - assertThat(usernameHolder.getValue()).hasToString("testuser"); - - - ConstructorArgumentValues.ValueHolder passwordHolder = def.getConstructorArgumentValues() - .getArgumentValue(3, Object.class); - assertThat(passwordHolder.getValue()).isInstanceOf(String.class); - assertThat(passwordHolder.getValue()).hasToString("123"); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/config/CouchbaseClusterParserTest.java b/src/test/java/org/springframework/data/couchbase/config/CouchbaseClusterParserTest.java deleted file mode 100644 index a3416731..00000000 --- a/src/test/java/org/springframework/data/couchbase/config/CouchbaseClusterParserTest.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.List; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.core.io.ClassPathResource; - -import com.couchbase.client.java.env.CouchbaseEnvironment; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CouchbaseClusterParserTest { - - - private static DefaultListableBeanFactory factory; - - @BeforeClass - public static void setUp() { - factory = new DefaultListableBeanFactory(); - BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); - int n = reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbaseCluster-bean.xml")); - System.out.println(n); - } - - @AfterClass - public static void tearDown() { - } - - @Test - public void testClusterWithoutSpecificEnv() { - BeanDefinition def = factory.getBeanDefinition("clusterDefault"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(1); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - assertThat(def.getFactoryMethodName()).isEqualTo("create"); - - ConstructorArgumentValues.ValueHolder holder = def.getConstructorArgumentValues() - .getArgumentValue(0, CouchbaseEnvironment.class); - - assertThat(holder.getValue()).isInstanceOf(RuntimeBeanReference.class); - RuntimeBeanReference envRef = (RuntimeBeanReference) holder.getValue(); - - assertThat(envRef.getBeanName()).isEqualTo("couchbaseEnv"); - } - - @Test - public void testClusterWithNodes() { - BeanDefinition def = factory.getBeanDefinition("clusterWithNodes"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(2); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - assertThat(def.getFactoryMethodName()).isEqualTo("create"); - - ConstructorArgumentValues.ValueHolder holder = def.getConstructorArgumentValues() - .getArgumentValue(1, List.class); - assertThat(holder.getValue()).isInstanceOf(List.class); - List nodes = (List) holder.getValue(); - - assertThat(nodes.size()).isEqualTo(2); - assertThat((String) nodes.get(0)).isEqualTo("192.1.2.3"); - assertThat((String) nodes.get(1)).isEqualTo("192.4.5.6"); - } - - @Test - public void testClusterWithEnvInline() { - BeanDefinition def = factory.getBeanDefinition("clusterWithEnvInline"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(1); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - - ConstructorArgumentValues.ValueHolder holder = def.getConstructorArgumentValues() - .getArgumentValue(0, CouchbaseEnvironment.class); - GenericBeanDefinition envDef = (GenericBeanDefinition) holder.getValue(); - - assertThat(envDef.getBeanClassName()) - .isEqualTo(CouchbaseEnvironmentFactoryBean.class.getName()); - assertThat(envDef.getPropertyValues().contains("managementTimeout")) - .as("unexpected attribute").isTrue(); - } - - @Test - public void testClusterWithEnvRef() { - BeanDefinition def = factory.getBeanDefinition("clusterWithEnvRef"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(1); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - - ConstructorArgumentValues.ValueHolder holder = def.getConstructorArgumentValues() - .getArgumentValue(0, CouchbaseEnvironment.class); - - assertThat(holder.getValue()).isInstanceOf(RuntimeBeanReference.class); - RuntimeBeanReference envRef = (RuntimeBeanReference) holder.getValue(); - - assertThat(envRef.getBeanName()).isEqualTo("someEnv"); - } - @Test - public void testClusterConfigurationPrecedence() { - BeanDefinition def = factory.getBeanDefinition("clusterWithAll"); - - assertThat(def).isNotNull(); - assertThat(def.getConstructorArgumentValues().getArgumentCount()).isEqualTo(2); - assertThat(def.getPropertyValues().size()).isEqualTo(0); - assertThat(def.getFactoryMethodName()).isEqualTo("create"); - - assertThat(def.getConstructorArgumentValues().getIndexedArgumentValues().get(0) - .getValue()).isInstanceOf(GenericBeanDefinition.class); - assertThat(def.getConstructorArgumentValues().getIndexedArgumentValues().get(1) - .getValue()).isInstanceOf(List.class); - - ConstructorArgumentValues.ValueHolder holderEnv = def.getConstructorArgumentValues() - .getArgumentValue(0, CouchbaseEnvironment.class); - GenericBeanDefinition envDef = (GenericBeanDefinition) holderEnv.getValue(); - - assertThat(envDef.getBeanClassName()) - .isEqualTo(CouchbaseEnvironmentFactoryBean.class.getName()); - assertThat(envDef.getPropertyValues().contains("autoreleaseAfter")) - .as("unexpected attribute").isTrue(); - - ConstructorArgumentValues.ValueHolder holderNodes = def.getConstructorArgumentValues() - .getArgumentValue(1, List.class); - List nodes = (List) holderNodes.getValue(); - - assertThat(nodes.size()).isEqualTo(2); - assertThat((String) nodes.get(0)).isEqualTo("2.2.2.2"); - assertThat((String) nodes.get(1)).isEqualTo("4.4.4.4"); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownProxyIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownProxyIntegrationTests.java deleted file mode 100644 index 3b24cdf8..00000000 --- a/src/test/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownProxyIntegrationTests.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.springframework.data.couchbase.config; - -import com.couchbase.client.java.env.CouchbaseEnvironment; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestNoShutdownApplicationConfig; -import org.springframework.test.context.ContextConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Simple test to make sure that environment is not shutdown if not life cycle managed by Spring. - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestNoShutdownApplicationConfig.class) -public class CouchbaseEnvironmentNoShutdownProxyIntegrationTests { - - @Autowired - public CouchbaseEnvironment environment; - - @Test - public void testEnvironmentShutDown() { - assertThat(environment.shutdown()).as("Should return false").isEqualTo(false); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentParserTest.java b/src/test/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentParserTest.java deleted file mode 100644 index b84ef328..00000000 --- a/src/test/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentParserTest.java +++ /dev/null @@ -1,122 +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.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.io.ClassPathResource; - -import com.couchbase.client.core.retry.BestEffortRetryStrategy; -import com.couchbase.client.core.retry.FailFastRetryStrategy; -import com.couchbase.client.java.env.CouchbaseEnvironment; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CouchbaseEnvironmentParserTest { - - private static GenericApplicationContext context; - - @BeforeClass - public static void setUp() { - DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); - BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbaseEnv-bean.xml")); - context = new GenericApplicationContext(factory); - context.refresh(); - } - - @Test - public void testParsingRetryStrategyFailFast() throws Exception { - CouchbaseEnvironment env = context.getBean("envWithFailFast", CouchbaseEnvironment.class); - - assertThat(env.retryStrategy()).isInstanceOf(FailFastRetryStrategy.class); - } - - @Test - public void testParsingRetryStrategyBestEffort() throws Exception { - CouchbaseEnvironment env = context.getBean("envWithBestEffort", CouchbaseEnvironment.class); - - assertThat(env.retryStrategy()).isInstanceOf(BestEffortRetryStrategy.class); - } - - @Test - public void testAllDefaultsOverridden() { - CouchbaseEnvironment env = context.getBean("envWithNoDefault", CouchbaseEnvironment.class); - CouchbaseEnvironment defaultEnv = DefaultCouchbaseEnvironment.create(); - - assertThat(env).isInstanceOf(DefaultCouchbaseEnvironment.class); - - assertThat(env.managementTimeout()).isEqualTo(1L); - assertThat(env.queryTimeout()).isEqualTo(2L); - assertThat(env.viewTimeout()).isEqualTo(3L); - assertThat(env.kvTimeout()).isEqualTo(4L); - assertThat(env.connectTimeout()).isEqualTo(5L); - assertThat(env.disconnectTimeout()).isEqualTo(6L); - assertThat(env.dnsSrvEnabled()).isTrue().isNotEqualTo(defaultEnv.dnsSrvEnabled()); - - assertThat(env.sslEnabled()).isTrue().isNotEqualTo(defaultEnv.sslEnabled()); - assertThat(env.sslKeystoreFile()).isEqualTo("test"); - assertThat(env.sslKeystorePassword()).isEqualTo("test"); - assertThat(env.bootstrapHttpEnabled()).isFalse() - .isNotEqualTo(defaultEnv.bootstrapHttpEnabled()); - assertThat(env.bootstrapCarrierEnabled()).isFalse() - .isNotEqualTo(defaultEnv.bootstrapCarrierEnabled()); - assertThat(env.bootstrapHttpDirectPort()).isEqualTo(8); - assertThat(env.bootstrapHttpSslPort()).isEqualTo(9); - assertThat(env.bootstrapCarrierDirectPort()).isEqualTo(10); - assertThat(env.bootstrapCarrierSslPort()).isEqualTo(11); - assertThat(env.ioPoolSize()).isEqualTo(12); - assertThat(env.computationPoolSize()).isEqualTo(13); - assertThat(env.responseBufferSize()).isEqualTo(14); - assertThat(env.requestBufferSize()).isEqualTo(15); - assertThat(env.kvEndpoints()).isEqualTo(16); - assertThat(env.viewEndpoints()).isEqualTo(17); - assertThat(env.queryEndpoints()).isEqualTo(18); - assertThat(env.retryStrategy()).isInstanceOf(FailFastRetryStrategy.class); - assertThat(env.maxRequestLifetime()).isEqualTo(19L); - assertThat(env.keepAliveInterval()).isEqualTo(20L); - assertThat(env.autoreleaseAfter()).isEqualTo(21L); - assertThat(env.bufferPoolingEnabled()).isFalse() - .isNotEqualTo(defaultEnv.bufferPoolingEnabled()); - assertThat(env.tcpNodelayEnabled()).isFalse() - .isNotEqualTo(defaultEnv.tcpNodelayEnabled()); - assertThat(env.mutationTokensEnabled()).isTrue() - .isNotEqualTo(defaultEnv.mutationTokensEnabled()); - assertThat(env.analyticsTimeout()).isEqualTo(30L); - assertThat(env.configPollInterval()).isEqualTo(50L); - assertThat(env.configPollFloorInterval()).isEqualTo(30L); - assertThat(env.operationTracingEnabled()).isFalse() - .isNotEqualTo(defaultEnv.operationTracingEnabled()); - assertThat(env.operationTracingServerDurationEnabled()).isFalse() - .isNotEqualTo(defaultEnv.operationTracingServerDurationEnabled()); - assertThat(env.orphanResponseReportingEnabled()).isFalse() - .isNotEqualTo(defaultEnv.orphanResponseReportingEnabled()); - assertThat(env.compressionMinSize()).isEqualTo(100); - assertThat(env.compressionMinRatio()).isEqualTo(0.90); - } - - @AfterClass - public static void tearDown() { - context.close(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/config/CouchbaseSingleEnvironmentParserTest.java b/src/test/java/org/springframework/data/couchbase/config/CouchbaseSingleEnvironmentParserTest.java deleted file mode 100644 index 3f904b6c..00000000 --- a/src/test/java/org/springframework/data/couchbase/config/CouchbaseSingleEnvironmentParserTest.java +++ /dev/null @@ -1,58 +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.junit.Test; -import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.io.ClassPathResource; - -import com.couchbase.client.core.env.DefaultCoreEnvironment; -import com.couchbase.client.java.env.CouchbaseEnvironment; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Simon Bland - */ -public class CouchbaseSingleEnvironmentParserTest { - - /** - * See DATACOUCH-235 - */ - @Test - public void testSingleCouchbaseEnvironment() throws Exception { - - int instanceCounterBefore = DefaultCoreEnvironment.instanceCounter(); - - DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); - BeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbaseSingleEnv-bean.xml")); - GenericApplicationContext context = new GenericApplicationContext(factory); - context.refresh(); - CouchbaseEnvironment env = context.getBean("singleEnv", CouchbaseEnvironment.class); - context.close(); - - int instanceCounterAfter = DefaultCoreEnvironment.instanceCounter(); - - assertThat(env).isInstanceOf(DefaultCouchbaseEnvironment.class); - assertThat(instanceCounterAfter).isEqualTo(instanceCounterBefore + 1); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java deleted file mode 100644 index 069f78d1..00000000 --- a/src/test/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2013-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.document.JsonDocument; -import com.couchbase.client.java.document.json.JsonObject; -import org.junit.Before; -import org.junit.Test; - -import org.junit.runner.RunWith; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.core.io.ClassPathResource; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.repository.User; -import org.springframework.test.context.ContextConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Michael Nitschinger - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = CouchbaseTemplateParserIntegrationTests.class) -public class CouchbaseTemplateParserIntegrationTests { - - DefaultListableBeanFactory factory; - BeanDefinitionReader reader; - - @Before - public void setUp() { - factory = new DefaultListableBeanFactory(); - reader = new XmlBeanDefinitionReader(factory); - } - - @Test - public void readsCouchbaseTemplateAttributesCorrectly() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-bean.xml")); - - BeanDefinition definition = factory.getBeanDefinition(BeanNames.COUCHBASE_TEMPLATE); - assertThat(definition.getConstructorArgumentValues().getArgumentCount()).isEqualTo(2); - - factory.getBean(BeanNames.COUCHBASE_TEMPLATE); - } - - @Test - public void readsCouchbaseTemplateWithTranslationServiceAttributesCorrectly() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-with-translation-service-bean.xml")); - - BeanDefinition definition = factory.getBeanDefinition(BeanNames.COUCHBASE_TEMPLATE); - assertThat(definition.getConstructorArgumentValues().getArgumentCount()).isEqualTo(3); - - factory.getBean(BeanNames.COUCHBASE_TEMPLATE); - } - - /** - * Test case for DATACOUCH-47. - */ - @Test - public void allowsMultipleBuckets() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-multi-bucket-bean.xml")); - - factory.getBean("cb-template-first"); - factory.getBean("cb-template-second"); - } - - /** - * Test case for DATACOUCH-134 in xml: field for storing type information can be renamed. - */ - @Test - public void testTypeFieldCanBeChosen() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-typekey.xml")); - CouchbaseTemplate template = factory.getBean(BeanNames.COUCHBASE_TEMPLATE, CouchbaseTemplate.class); - - assertThat(template.getConverter() instanceof MappingCouchbaseConverter).isTrue(); - MappingCouchbaseConverter converter = ((MappingCouchbaseConverter) template.getConverter()); - - assertThat(converter.getTypeKey()).isEqualTo("javaXmlClass"); - - User u = new User("specialSaveUser", "John Locke", 46); - template.save(u); - JsonDocument uJsonDoc = template.getCouchbaseBucket().get("specialSaveUser"); - template.getCouchbaseBucket().remove("specialSaveUser"); - assertThat(uJsonDoc).isNotNull(); - JsonObject uJson = uJsonDoc.content(); - assertThat(uJson.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT)).isNull(); - assertThat(uJson.getString("javaXmlClass")) - .isEqualTo("org.springframework.data.couchbase.repository.User"); - assertThat(uJson.getString("username")).isEqualTo("John Locke"); - } - - /** - * Test case for DATACOUCH-148, choosing an alternative default for view/N1QL staleness. - */ - @Test - public void shouldParseCustomStaleness() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-consistency.xml")); - CouchbaseTemplate template = factory.getBean("template", CouchbaseTemplate.class); - - assertThat(template.getDefaultConsistency()) - .isEqualTo(Consistency.EVENTUALLY_CONSISTENT); - assertThat(template.getDefaultConsistency()) - .isNotEqualTo(Consistency.DEFAULT_CONSISTENCY); - } - - /** - * Test case for DATACOUCH-148, choosing an unknown value for view/N1QL staleness. - */ - @Test - public void shouldIgnoreBadCustomStaleness() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-consistency.xml")); - CouchbaseTemplate template = factory.getBean("templateBad", CouchbaseTemplate.class); - - assertThat(template.getDefaultConsistency()) - .isEqualTo(Consistency.DEFAULT_CONSISTENCY); - } - - @Test - public void shouldHaveDefaultsForStaleness() { - //use another resource where staleness isn't customized - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-with-translation-service-bean.xml")); - CouchbaseTemplate template = factory.getBean(BeanNames.COUCHBASE_TEMPLATE, CouchbaseTemplate.class); - - assertThat(template.getDefaultConsistency()) - .isEqualTo(Consistency.DEFAULT_CONSISTENCY); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/AsyncUtils.java b/src/test/java/org/springframework/data/couchbase/core/AsyncUtils.java deleted file mode 100644 index 7d4a25d4..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/AsyncUtils.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.springframework.data.couchbase.core; - -import rx.observers.TestSubscriber; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.*; - -public class AsyncUtils { - - public static void executeConcurrently(int numThreads, Callable task) throws Exception { - ExecutorService pool = Executors.newFixedThreadPool(numThreads); - - Collection> tasks = Collections.nCopies(numThreads, task); - - List> futures = pool.invokeAll(tasks); - for (Future future : futures) { - future.get(numThreads, TimeUnit.SECONDS); - } - } - - public static void awaitCompleted(TestSubscriber testSubscriber) { - testSubscriber.awaitTerminalEvent(); - testSubscriber.assertNoErrors(); - testSubscriber.assertNoValues(); - testSubscriber.assertCompleted(); - } - - public static void awaitCompletedWithAnyValue(TestSubscriber testSubscriber) { - testSubscriber.awaitTerminalEvent(); - testSubscriber.assertNoErrors(); - testSubscriber.assertCompleted(); - } - - public static void awaitCompletedWithValueCount(TestSubscriber testSubscriber, int count) { - testSubscriber.awaitTerminalEvent(); - testSubscriber.assertNoErrors(); - testSubscriber.assertValueCount(count); - testSubscriber.assertCompleted(); - } - - public static void awaitError(TestSubscriber testSubscriber, Class throwableClazz) { - testSubscriber.awaitTerminalEvent(); - testSubscriber.assertError(throwableClazz); - testSubscriber.assertNoValues(); - } - - public static void awaitValue(TestSubscriber testSubscriber, T value) { - testSubscriber.awaitTerminalEvent(); - testSubscriber.assertNoErrors(); - testSubscriber.assertValue(value); - testSubscriber.assertCompleted(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/Beer.java b/src/test/java/org/springframework/data/couchbase/core/Beer.java deleted file mode 100644 index 27ffa99d..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/Beer.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import org.springframework.data.annotation.Id; - -import com.couchbase.client.java.repository.annotation.Field; - - -/** - * Test class for persisting and loading from {@link CouchbaseTemplate}. - * - * @author Michael Nitschinger - * @author Subhashni Balakrishnan - */ -public class Beer { - - @Id - private final String id; - - private String name; - - @Field("is_active") - private boolean active = true; - - @Field("desc") - private String description; - - public Beer(String id, String name, Boolean active, String description) { - this.id = id; - this.name = name; - this.active = active; - this.description = description; - } - - @Override - public String toString() { - return "Beer [id=" + id + ", name=" + name + ", active=" + active + ", description=" + description + "]"; - } - - public Beer setName(String name) { - this.name = name; - return this; - } - - public String getName() { - return name; - } - - public Beer setActive(boolean active) { - this.active = active; - return this; - } - - public boolean getActive() { - return active; - } - - public Beer setDescription(String description) { - this.description = description; - return this; - } - - public String getDescription() { - return description; - } - - public String getId() { - return id; - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/core/BeerDTO.java b/src/test/java/org/springframework/data/couchbase/core/BeerDTO.java deleted file mode 100644 index 3abfcb67..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/BeerDTO.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.springframework.data.couchbase.core; - -import com.couchbase.client.java.repository.annotation.Field; - -/** - * Test DTO for projecting from {@link CouchbaseTemplate}. - * - * @author Subhashni Balakrishnan - */ -public class BeerDTO{ - private String name; - - @Field("desc") - private String description; - - public BeerDTO(String name, String description) { - this.name = name; - this.description = description; - } - - public BeerDTO setName(String name) { - this.name = name; - return this; - } - - public String getName() { return name; } - - public BeerDTO setDescription(String description) { - this.description = description; - return this; - } - - public String getDescription() { return description; } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/BeerProjection.java b/src/test/java/org/springframework/data/couchbase/core/BeerProjection.java deleted file mode 100644 index a241282f..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/BeerProjection.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.springframework.data.couchbase.core; - -/** - * Test interface for projecting data from {@link CouchbaseTemplate}. - * - * @author Subhashni Balakrishnan - */ -public interface BeerProjection { - String getDescription(); -} diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateIdGenerationIntegrationTests.java deleted file mode 100644 index becaea58..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateIdGenerationIntegrationTests.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.springframework.data.couchbase.core; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.error.DocumentDoesNotExistException; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.annotation.Id; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.core.mapping.id.GeneratedValue; -import org.springframework.data.couchbase.core.mapping.id.IdAttribute; -import org.springframework.data.couchbase.core.mapping.id.IdPrefix; -import org.springframework.data.couchbase.core.mapping.id.IdSuffix; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Subhashni Balakrishnan - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -public class CouchbaseTemplateIdGenerationIntegrationTests { - - @Rule - public TestName testName = new TestName(); - - @Autowired - private Bucket client; - - @Autowired - private CouchbaseTemplate template; - - - private void removeIfExist(String key) { - try { - client.remove(key); - } - catch (DocumentDoesNotExistException e) { - //ignore - } - } - - @Test - public void shouldGenerateIdUsingAtrributes() throws Exception { - SimpleClassWithGeneratedIdValueUsingAttributes simpleClass = new SimpleClassWithGeneratedIdValueUsingAttributes(); - String generatedId = template.getGeneratedId(simpleClass); - - removeIfExist(generatedId); - assertThat("prefix1::prefix2::0::1::2.0::3.0::4::Simple::Nested{value:simple}::suffix1::suffix2") - .as("Id generation should be correct").isEqualTo(generatedId); - template.insert(simpleClass); - assertThat(template.exists(generatedId)).as("Exists after insert") - .isEqualTo(true); - simpleClass.value = "modified"; - template.save(simpleClass); - SimpleClassWithGeneratedIdValueUsingAttributes modifiedClass = template.findById(generatedId, - SimpleClassWithGeneratedIdValueUsingAttributes.class); - assertThat(modifiedClass.id).as("Get after save id should be correct") - .isEqualTo(generatedId); - template.update(simpleClass); - SimpleClassWithGeneratedIdValueUsingAttributes updatedClass = template.findById(generatedId, - SimpleClassWithGeneratedIdValueUsingAttributes.class); - assertThat(updatedClass.id).as("Get after update id should be correct") - .isEqualTo(generatedId); - template.remove(generatedId); - assertThat(template.exists(generatedId)).as("Exists after remove") - .isEqualTo(false); - } - - @Test - public void shouldGenerateIdUsingUUID() throws Exception { - SimpleClassWithGeneratedIdValueUsingUUID simpleClass = new SimpleClassWithGeneratedIdValueUsingUUID(); - String generatedId = template.getGeneratedId(simpleClass); - simpleClass.id = generatedId; - template.insert(simpleClass); - assertThat(simpleClass.id).as("Should not regenerate id").isEqualTo(generatedId); - template.remove(generatedId); - assertThat(template.exists(generatedId)).as("Exists after remove") - .isEqualTo(false); - } - - - @Document - static class SimpleClassWithGeneratedIdValueUsingAttributes { - - @Id @GeneratedValue(strategy = USE_ATTRIBUTES, delimiter = "::") - public String id; - - @IdAttribute(order = 6) - public Nested nested = new Nested("simple"); - - @IdAttribute(order = 5) - public String type = "Simple"; - - @IdAttribute(order = 4) - public int intNum = 4; - - @IdAttribute(order = 2) - public float floatNum = 2F; - - @IdAttribute(order = 3) - public double doubleNum = 3; - - @IdAttribute - public long longNum = 0L; - - @IdAttribute(order = 1) - public short shortNum = 1; - - @IdPrefix(order = 1) - public String prefix2 = "prefix2"; - - @IdPrefix - public String prefix1 = "prefix1"; - - @IdSuffix(order = 1) - public String suffix2 = "suffix2"; - - @IdSuffix - public String suffix1 = "suffix1"; - - public String value = "new"; - - } - - static class Nested { - private String value; - - public Nested(String value) { - this.value = value; - } - - @Override - public String toString() { - return "Nested{value:" + value + "}"; - } - } - - @Document - static class SimpleClassWithGeneratedIdValueUsingUUID { - @Id @GeneratedValue(strategy = UNIQUE) - public String id; - - public String value = "new"; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateIntegrationTests.java deleted file mode 100644 index c7c82804..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateIntegrationTests.java +++ /dev/null @@ -1,812 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import static com.couchbase.client.java.query.Select.select; -import static com.couchbase.client.java.query.dsl.Expression.*; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicLong; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.document.RawJsonDocument; -import com.couchbase.client.java.error.DocumentDoesNotExistException; -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.N1qlQueryResult; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import com.couchbase.client.java.repository.annotation.Field; -import com.couchbase.client.java.view.Stale; -import com.couchbase.client.java.view.ViewQuery; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.OptimisticLockingFailureException; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.Version; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -/** - * @author Michael Nitschinger - * @author Simon Baslé - * @author Anastasiia Smirnova - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(CouchbaseTemplateQueryListener.class) -public class CouchbaseTemplateIntegrationTests { - @Rule - public TestName testName = new TestName(); - - @Autowired - private Bucket client; - - @Autowired - private CouchbaseTemplate template; - - private static final ObjectMapper MAPPER = new ObjectMapper(); - - private void removeIfExist(String key) { - try { - template.remove(key); - } - catch (DocumentDoesNotExistException e) { - //ignore - } - } - - @Test - public void saveSimpleEntityCorrectly() throws Exception { - String id = "beers:awesome-stout"; - removeIfExist(id); - - String name = "The Awesome Stout"; - boolean active = false; - Beer beer = new Beer(id, name, active, ""); - - template.save(beer); - RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class); - assertThat(resultDoc).isNotNull(); - String result = resultDoc.content(); - assertThat(result).isNotNull(); - Map resultConv = MAPPER.readValue(result, new TypeReference>() {}); - - assertThat(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT)).isNotNull(); - assertThat(resultConv.get("javaClass")).isNull(); - assertThat(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT)) - .isEqualTo("org.springframework.data.couchbase.core.Beer"); - assertThat(resultConv.get("is_active")).isEqualTo(false); - assertThat(resultConv.get("name")).isEqualTo("The Awesome Stout"); - } - - @Test - public void saveDocumentWithExpiry() throws Exception { - String id = "simple-doc-with-expiry"; - DocumentWithExpiry doc = new DocumentWithExpiry(id); - template.save(doc); - assertThat(client.get(id)).isNotNull(); - Thread.sleep(3000); - assertThat(client.get(id)).isNull(); - } - - @Test - public void insertDoesNotOverride() throws Exception { - String id = "double-insert-test"; - removeIfExist(id); - - SimplePerson doc = new SimplePerson(id, "Mr. A"); - template.insert(doc); - RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class); - assertThat(resultDoc).isNotNull(); - String result = resultDoc.content(); - - Map resultConv = MAPPER.readValue(result, new TypeReference>() {}); - assertThat(resultConv.get("name")).isEqualTo("Mr. A"); - - doc = new SimplePerson(id, "Mr. B"); - try { - template.insert(doc); - } catch (OptimisticLockingFailureException e) { - //ignore, since this insert should fail - } - - resultDoc = client.get(id, RawJsonDocument.class); - assertThat(resultDoc).isNotNull(); - result = resultDoc.content(); - - resultConv = MAPPER.readValue(result, new TypeReference>() {}); - assertThat(resultConv.get("name")).isEqualTo("Mr. A"); - } - - - @Test - public void updateDoesNotInsert() { - String id = "update-does-not-insert"; - SimplePerson doc = new SimplePerson(id, "Nice Guy"); - template.update(doc); - assertThat(client.get(id)).isNull(); - } - - - @Test - public void removeDocument() { - String id = "beers:to-delete-stout"; - Beer beer = new Beer(id, "", false, ""); - - template.save(beer); - Object result = client.get(id); - assertThat(result).isNotNull(); - - template.remove(beer); - result = client.get(id); - assertThat(result).isNull(); - } - - - @Test - public void storeListsAndMaps() { - String id = "persons:lots-of-names"; - List names = new ArrayList(); - names.add("Michael"); - names.add("Thomas"); - names.add(null); - List votes = new LinkedList(); - Map info1 = new HashMap(); - info1.put("foo", true); - info1.put("bar", false); - info1.put("nullValue", null); - Map info2 = new HashMap(); - - ComplexPerson complex = new ComplexPerson(id, names, votes, info1, info2); - - template.save(complex); - assertThat(client.get(id)).isNotNull(); - - ComplexPerson response = template.findById(id, ComplexPerson.class); - assertThat(response.getFirstnames()).isEqualTo(names); - assertThat(response.getVotes()).isEqualTo(votes); - assertThat(response.getId()).isEqualTo(id); - assertThat(response.getInfo1()).isEqualTo(info1); - assertThat(response.getInfo2()).isEqualTo(info2); - } - - - @Test - public void validFindById() { - String id = "beers:findme-stout"; - String name = "The Findme Stout"; - boolean active = true; - Beer beer = new Beer(id, name, active, ""); - template.save(beer); - - Beer found = template.findById(id, Beer.class); - - assertThat(found).isNotNull(); - assertThat(found.getId()).isEqualTo(id); - assertThat(found.getName()).isEqualTo(name); - assertThat(found.getActive()).isEqualTo(active); - } - - @Test - public void shouldLoadAndMapViewDocs() { - ViewQuery query = ViewQuery.from("test_beers", "by_name"); - query.stale(Stale.FALSE); - - final List beers = template.findByView(query, Beer.class); - assertThat(beers.size() > 0).isTrue(); - - for (Beer beer : beers) { - assertThat(beer.getId()).isNotNull(); - assertThat(beer.getName()).isNotNull(); - assertThat(beer.getActive()).isNotNull(); - } - } - - @Test - public void shouldQueryRaw() { - N1qlQuery query = N1qlQuery.simple(select("name").from(i(client.name())) - .where(x("name").isNotMissing())); - - N1qlQueryResult queryResult = template.queryN1QL(query); - assertThat(queryResult).isNotNull(); - assertThat(queryResult.finalSuccess()).as(queryResult.errors().toString()) - .isTrue(); - assertThat(queryResult.allRows().isEmpty()).isFalse(); - } - - @Test - public void shouldQueryWithMapping() { - FullFragment ff1 = new FullFragment("fullFragment1", 1, "fullFragment", "test1"); - FullFragment ff2 = new FullFragment("fullFragment2", 2, "fullFragment", "test2"); - template.save(Arrays.asList(ff1, ff2)); - - N1qlQuery query = N1qlQuery.simple(select(i("value")) //"value" is a n1ql keyword apparently - .from(i(client.name())) - .where(x("type").eq(s("fullFragment")) - .and(x("criteria").gt(1))), - - N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS)); - - List fragments = template.findByN1QLProjection(query, Fragment.class); - assertThat(fragments).isNotNull(); - assertThat(fragments.isEmpty()).isFalse(); - assertThat(fragments.size()).isEqualTo(1); - assertThat(fragments.get(0).value).isEqualTo("test2"); - } - - /** - * @see DATACOUCH-159 - */ - @Test - public void shouldDeserialiseLongsAndInts() { - final long longValue = new Date().getTime(); - final int intValue = new Random().nextInt(); - - template.save(new SimpleWithLongAndInt("simpleWithLong:simple", longValue, intValue)); - SimpleWithLongAndInt document = template.findById("simpleWithLong:simple", SimpleWithLongAndInt.class); - assertThat(document).isNotNull(); - assertThat(document.getLongValue()).isEqualTo(longValue); - assertThat(document.getIntValue()).isEqualTo(intValue); - - template.save(new SimpleWithLongAndInt("simpleWithLong:simple:other", intValue, intValue)); - document = template.findById("simpleWithLong:simple:other", SimpleWithLongAndInt.class); - assertThat(document).isNotNull(); - assertThat(document.getLongValue()).isEqualTo(intValue); - assertThat(document.getIntValue()).isEqualTo(intValue); - } - - @Test - public void shouldDeserialiseEnums() { - SimpleWithEnum simpleWithEnum = new SimpleWithEnum("simpleWithEnum:enum", SimpleWithEnum.Type.BIG); - template.save(simpleWithEnum); - simpleWithEnum = template.findById("simpleWithEnum:enum", SimpleWithEnum.class); - assertThat(simpleWithEnum).isNotNull(); - assertThat(SimpleWithEnum.Type.BIG).isEqualTo(simpleWithEnum.getType()); - } - - @Test - public void shouldDeserialiseClass() { - SimpleWithClass simpleWithClass = new SimpleWithClass("simpleWithClass:class", Integer.class); - simpleWithClass.setValue("The dish ran away with the spoon."); - template.save(simpleWithClass); - simpleWithClass = template.findById("simpleWithClass:class", SimpleWithClass.class); - assertThat(simpleWithClass).isNotNull(); - assertThat(simpleWithClass.getValue()) - .isEqualTo("The dish ran away with the spoon."); - } - - @Test - public void shouldHandleCASVersionOnInsert() throws Exception { - removeIfExist("versionedClass:1"); - - VersionedClass versionedClass = new VersionedClass("versionedClass:1", "foobar"); - assertThat(versionedClass.getVersion()).isEqualTo(0); - template.insert(versionedClass); - RawJsonDocument rawStored = client.get("versionedClass:1", RawJsonDocument.class); - assertThat(versionedClass.getVersion()).isEqualTo(rawStored.cas()); - } - - @Test - public void versionShouldNotUpdateOnSecondInsert() throws Exception { - removeIfExist("versionedClass:2"); - - VersionedClass versionedClass = new VersionedClass("versionedClass:2", "foobar"); - template.insert(versionedClass); - long version1 = versionedClass.getVersion(); - try { - template.insert(versionedClass); - } catch (OptimisticLockingFailureException e) { - //ignore, since this insert should fail - } - long version2 = versionedClass.getVersion(); - - assertThat(version1 > 0).isTrue(); - assertThat(version2 > 0).isTrue(); - assertThat(version2).isEqualTo(version1); - } - - @Test - public void shouldSaveDocumentOnMatchingVersion() throws Exception { - removeIfExist("versionedClass:3"); - - VersionedClass versionedClass = new VersionedClass("versionedClass:3", "foobar"); - template.insert(versionedClass); - long version1 = versionedClass.getVersion(); - - versionedClass.setField("foobar2"); - template.save(versionedClass); - long version2 = versionedClass.getVersion(); - - assertThat(version1 > 0).isTrue(); - assertThat(version2 > 0).isTrue(); - assertThat(version2).isNotEqualTo(version1); - - assertThat(template.findById("versionedClass:3", VersionedClass.class).getField()) - .isEqualTo("foobar2"); - } - - @Test(expected = OptimisticLockingFailureException.class) - public void shouldNotSaveDocumentOnNotMatchingVersion() throws Exception { - removeIfExist("versionedClass:4"); - - VersionedClass versionedClass = new VersionedClass("versionedClass:4", "foobar"); - template.insert(versionedClass); - - RawJsonDocument toCompare = RawJsonDocument.create("versionedClass:4", "different"); - assertThat(client.upsert(toCompare)).isNotNull(); - - versionedClass.setField("foobar2"); - //save (aka upsert) won't error in case of CAS mismatch anymore - template.update(versionedClass); - } - - @Test - public void shouldUpdateDocumentOnMatchingVersion() throws Exception { - removeIfExist("versionedClass:5"); - - VersionedClass versionedClass = new VersionedClass("versionedClass:5", "foobar"); - template.insert(versionedClass); - long version1 = versionedClass.getVersion(); - - versionedClass.setField("foobar2"); - template.update(versionedClass); - long version2 = versionedClass.getVersion(); - - assertThat(version1 > 0).isTrue(); - assertThat(version2 > 0).isTrue(); - assertThat(version2).isNotEqualTo(version1); - - assertThat(template.findById("versionedClass:5", VersionedClass.class).getField()) - .isEqualTo("foobar2"); - } - - @Test(expected = OptimisticLockingFailureException.class) - public void shouldNotUpdateDocumentOnNotMatchingVersion() throws Exception { - removeIfExist("versionedClass:6"); - - VersionedClass versionedClass = new VersionedClass("versionedClass:6", "foobar"); - template.insert(versionedClass); - - RawJsonDocument toCompare = RawJsonDocument.create("versionedClass:6", "different"); - assertThat(client.upsert(toCompare)).isNotNull(); - - versionedClass.setField("foobar2"); - template.update(versionedClass); - } - - @Test - public void shouldLoadVersionPropertyOnFind() throws Exception { - removeIfExist("versionedClass:7"); - - VersionedClass versionedClass = new VersionedClass("versionedClass:7", "foobar"); - template.insert(versionedClass); - assertThat(versionedClass.getVersion() > 0).isTrue(); - - VersionedClass foundClass = template.findById("versionedClass:7", VersionedClass.class); - assertThat(foundClass.getVersion()).isEqualTo(versionedClass.getVersion()); - } - - @Test - public void shouldUpdateAlreadyExistingDocument() throws Exception { - final String key = testName.getMethodName(); - removeIfExist(key); - - final AtomicLong counter = new AtomicLong(); - - VersionedClass initial = new VersionedClass(key, "value-0"); - template.save(initial); - - AsyncUtils.executeConcurrently(3, new Callable() { - @Override - public Void call() throws Exception { - boolean saved = false; - while(!saved) { - long counterValue = counter.incrementAndGet(); - VersionedClass messageData = template.findById(key, VersionedClass.class); - messageData.field = "value-" + counterValue; - try { - template.save(messageData); - saved = true; - } catch (OptimisticLockingFailureException e) { - } - } - return null; - } - }); - - VersionedClass actual = template.findById(key, VersionedClass.class); - - assertThat(actual.field).isNotEqualTo(initial.field); - assertThat(actual.version).isNotEqualTo(initial.version); - } - - @Test - public void shouldInsertOnlyFirstDocumentAndNextAttemptsShouldFailWithOptimisticLockingException() throws Exception { - final String key = testName.getMethodName(); - removeIfExist(key); - - final AtomicLong counter = new AtomicLong(); - final AtomicLong optimisticLockCounter = new AtomicLong(); - AsyncUtils.executeConcurrently(5, new Callable() { - @Override - public Void call() throws Exception { - long counterValue = counter.incrementAndGet(); - String data = "value-" + counterValue; - VersionedClass messageData = new VersionedClass(key, data); - try { - template.insert(messageData); - } catch (OptimisticLockingFailureException e) { - optimisticLockCounter.incrementAndGet(); - } - //should save operation throw OptimisticLockingFailureException on next attempts to save? - return null; - } - }); - - - assertThat(optimisticLockCounter.intValue()).isEqualTo(4); - } - - /** - * @see DATACOUCH-59 - */ - @Test - public void expiryWhenTouchOnReadDocument() throws InterruptedException { - String id = "simple-doc-with-update-expiry-for-read"; - DocumentWithTouchOnRead doc = new DocumentWithTouchOnRead(id); - template.save(doc); - Thread.sleep(1000); - assertThat(template.findById(id, DocumentWithTouchOnRead.class)).isNotNull(); - Thread.sleep(1000); - assertThat(template.findById(id, DocumentWithTouchOnRead.class)).isNotNull(); - Thread.sleep(3000); - assertThat(template.findById(id, DocumentWithTouchOnRead.class)).isNull(); - } - - /** - * @see DATACOUCH-227 - */ - @Test - public void shouldRetainOrderWhenQueryingViewOrdered() { - ViewQuery q = ViewQuery.from("test_beers", "by_name"); - q.descending().includeDocsOrdered(true); - - String prev = null; - List beers = template.findByView(q, Beer.class); - assertThat(q.isIncludeDocs()).isTrue(); - assertThat(q.isOrderRetained()).isTrue(); - assertThat(q.includeDocsTarget()).isEqualTo(RawJsonDocument.class); - for (Beer beer : beers) { - if (prev != null) { - assertThat(beer.getName().compareTo(prev) < 0).describedAs(beer.getName() + " not alphabetically < to " + prev).isTrue(); - } - prev = beer.getName(); - } - } - - /** - * A sample document with just an id and property. - */ - @Document - static class SimplePerson { - - @Id - private final String id; - @Field - private final String name; - - public SimplePerson(String id, String name) { - this.id = id; - this.name = name; - } - } - - /** - * A sample document that expires in 2 seconds. - */ - @Document(expiry = 2) - static class DocumentWithExpiry { - - @Id - private final String id; - - public DocumentWithExpiry(String id) { - this.id = id; - } - } - - /** - * A sample document that expires in 2 seconds and touchOnRead set. - */ - @Document(expiry = 2, touchOnRead = true) - static class DocumentWithTouchOnRead { - - @Id - private final String id; - - public DocumentWithTouchOnRead(String id) { - this.id = id; - } - } - - @Document - static class ComplexPerson { - - @Id - private final String id; - @Field - private final List firstnames; - @Field - private final List votes; - - @Field - private final Map info1; - @Field - private final Map info2; - - public ComplexPerson(String id, List firstnames, - List votes, Map info1, - Map info2) { - this.id = id; - this.firstnames = firstnames; - this.votes = votes; - this.info1 = info1; - this.info2 = info2; - } - - List getFirstnames() { - return firstnames; - } - - List getVotes() { - return votes; - } - - Map getInfo1() { - return info1; - } - - Map getInfo2() { - return info2; - } - - String getId() { - return id; - } - } - - @Document - static class SimpleWithLongAndInt { - - @Id - private String id; - - private long longValue; - private int intValue; - - SimpleWithLongAndInt(final String id, final long longValue, int intValue) { - this.id = id; - this.longValue = longValue; - this.intValue = intValue; - } - - String getId() { - return id; - } - - long getLongValue() { - return longValue; - } - - void setLongValue(final long value) { - this.longValue = value; - } - - public int getIntValue() { - return intValue; - } - - public void setIntValue(int intValue) { - this.intValue = intValue; - } - } - - static class SimpleWithEnum { - - @Id - private String id; - - private enum Type { - BIG - } - - private Type type; - - SimpleWithEnum(final String id, final Type type) { - this.id = id; - this.type = type; - } - - String getId() { - return id; - } - - void setId(final String id) { - this.id = id; - } - - Type getType() { - return type; - } - - void setType(final Type type) { - this.type = type; - } - } - - static class SimpleWithClass { - - @Id - private String id; - - private Class integerClass; - - private String value; - - SimpleWithClass(final String id, final Class integerClass) { - this.id = id; - this.integerClass = integerClass; - } - - String getId() { - return id; - } - - void setId(final String id) { - this.id = id; - } - - Class getIntegerClass() { - return integerClass; - } - - void setIntegerClass(final Class integerClass) { - this.integerClass = integerClass; - } - - String getValue() { - return value; - } - - void setValue(final String value) { - this.value = value; - } - } - - static class VersionedClass { - - @Id - private String id; - - @Version - private long version; - - private String field; - - VersionedClass(String id, String field) { - this.id = id; - this.field = field; - } - - public String getId() { - return id; - } - - public long getVersion() { - return version; - } - - public String getField() { - return field; - } - - public void setField(String field) { - this.field = field; - } - - @Override - public String toString() { - return "VersionedClass{" + - "id='" + id + '\'' + - ", version=" + version + - ", field='" + field + '\'' + - '}'; - } - } - - @Document - static class FullFragment { - - @Id - private String id; - - private long criteria; - - private String type; - - private String value; - - public FullFragment(String id, long criteria, String type, String value) { - this.id = id; - this.criteria = criteria; - this.type = type; - this.value = value; - } - - public String getId() { - return id; - } - - public long getCriteria() { - return criteria; - } - - public String getType() { - return type; - } - - public String getValue() { - return value; - } - - public void setCriteria(long criteria) { - this.criteria = criteria; - } - - public void setType(String type) { - this.type = type; - } - - public void setValue(String value) { - this.value = value; - } - } - - static class Fragment { - public String value; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeySettingsIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeySettingsIntegrationTests.java deleted file mode 100644 index c4a004e6..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeySettingsIntegrationTests.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.springframework.data.couchbase.core; - -import com.couchbase.client.java.repository.annotation.Id; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; - -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestCustomKeySettings; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.core.mapping.KeySettings; -import org.springframework.test.context.ContextConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Subhashni Balakrishnan - */ - -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestCustomKeySettings.class) -public class CouchbaseTemplateKeySettingsIntegrationTests { - - @Rule - public TestName testName = new TestName(); - - @Autowired - private CouchbaseTemplate template; - - @Before - public void setup() { - if (template.keySettings() == null) { - template.keySettings(KeySettings.build().prefix("MyAppPrefix").suffix("MyAppSuffix").delimiter("::")); - } - } - - @Test - public void shouldAddCustomKeySettings() throws Exception { - SimpleClass simpleClass = new SimpleClass(); - String generatedId = template.getGeneratedId(simpleClass); - assertThat(generatedId).as("Id generated should include custom key settings") - .isEqualTo("MyAppPrefix::myId::MyAppSuffix"); - } - - @Test - public void shouldNotAllowKeySettingsToBeChanged() { - try { - template.keySettings(KeySettings.build().prefix("MyAppPrefix").suffix("MyAppSuffix").delimiter("::")); - fail("excepted unsupportedOperationException"); - } catch(Exception ex) { - - } - } - - @Document - static class SimpleClass { - @Id - public String id = "myId"; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java new file mode 100644 index 00000000..4f30cab5 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateKeyValueIntegrationTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.UUID; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.dao.DataRetrievalFailureException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.SimpleCouchbaseClientFactory; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.domain.User; +import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; +import org.springframework.data.couchbase.util.ClusterType; +import org.springframework.data.couchbase.util.IgnoreWhen; + +class CouchbaseTemplateKeyValueIntegrationTests extends ClusterAwareIntegrationTests { + + private static CouchbaseClientFactory couchbaseClientFactory; + private CouchbaseTemplate couchbaseTemplate; + + @BeforeAll + static void beforeAll() { + couchbaseClientFactory = new SimpleCouchbaseClientFactory(connectionString(), authenticator(), bucketName()); + } + + @AfterAll + static void afterAll() throws IOException { + couchbaseClientFactory.close(); + } + + @BeforeEach + void beforeEach() { + CouchbaseConverter couchbaseConverter = new MappingCouchbaseConverter(); + couchbaseTemplate = new CouchbaseTemplate(couchbaseClientFactory, couchbaseConverter); + } + + @Test + void upsertAndFindById() { + User user = new User(UUID.randomUUID().toString(), "firstname", "lastname"); + User modified = couchbaseTemplate.upsertById(User.class).one(user); + assertEquals(user, modified); + + User found = couchbaseTemplate.findById(User.class).one(user.getId()); + assertEquals(user, found); + } + + @Test + void findDocWhichDoesNotExist() { + assertThrows(DataRetrievalFailureException.class, + () -> couchbaseTemplate.findById(User.class).one(UUID.randomUUID().toString())); + } + + @Test + void upsertAndReplaceById() { + User user = new User(UUID.randomUUID().toString(), "firstname", "lastname"); + User modified = couchbaseTemplate.upsertById(User.class).one(user); + assertEquals(user, modified); + + User toReplace = new User(modified.getId(), "some other", "lastname"); + couchbaseTemplate.replaceById(User.class).one(toReplace); + + User loaded = couchbaseTemplate.findById(User.class).one(toReplace.getId()); + assertEquals("some other", loaded.getFirstname()); + } + + @Test + void upsertAndRemoveById() { + User user = new User(UUID.randomUUID().toString(), "firstname", "lastname"); + User modified = couchbaseTemplate.upsertById(User.class).one(user); + assertEquals(user, modified); + + RemoveResult removeResult = couchbaseTemplate.removeById().one(user.getId()); + assertEquals(user.getId(), removeResult.getId()); + assertTrue(removeResult.getCas() != 0); + assertTrue(removeResult.getMutationToken().isPresent()); + + assertThrows(DataRetrievalFailureException.class, () -> couchbaseTemplate.findById(User.class).one(user.getId())); + } + + @Test + void insertById() { + User user = new User(UUID.randomUUID().toString(), "firstname", "lastname"); + User inserted = couchbaseTemplate.insertById(User.class).one(user); + assertEquals(user, inserted); + + assertThrows(DuplicateKeyException.class, () -> couchbaseTemplate.insertById(User.class).one(user)); + } + + @Test + @IgnoreWhen(clusterTypes = ClusterType.MOCKED) + void existsById() { + String id = UUID.randomUUID().toString(); + assertFalse(couchbaseTemplate.existsById().one(id)); + + User user = new User(id, "firstname", "lastname"); + User inserted = couchbaseTemplate.insertById(User.class).one(user); + assertEquals(user, inserted); + + assertTrue(couchbaseTemplate.existsById().one(id)); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java new file mode 100644 index 00000000..4ae8e834 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryIntegrationTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.dao.DataRetrievalFailureException; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.SimpleCouchbaseClientFactory; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.domain.User; +import org.springframework.data.couchbase.util.Capabilities; +import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; +import org.springframework.data.couchbase.util.ClusterType; +import org.springframework.data.couchbase.util.IgnoreWhen; + +import com.couchbase.client.core.error.IndexExistsException; +import com.couchbase.client.java.query.QueryScanConsistency; + +@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) +class CouchbaseTemplateQueryIntegrationTests extends ClusterAwareIntegrationTests { + + private static CouchbaseClientFactory couchbaseClientFactory; + private CouchbaseTemplate couchbaseTemplate; + + @BeforeAll + static void beforeAll() { + couchbaseClientFactory = new SimpleCouchbaseClientFactory(connectionString(), authenticator(), bucketName()); + + try { + couchbaseClientFactory.getCluster().queryIndexes().createPrimaryIndex(bucketName()); + } catch (IndexExistsException ex) { + // ignore, all good. + } + } + + @AfterAll + static void afterAll() throws IOException { + couchbaseClientFactory.close(); + } + + @BeforeEach + void beforeEach() { + CouchbaseConverter couchbaseConverter = new MappingCouchbaseConverter(); + couchbaseTemplate = new CouchbaseTemplate(couchbaseClientFactory, couchbaseConverter); + } + + @Test + void findByQuery() { + User user1 = new User(UUID.randomUUID().toString(), "user1", "user1"); + User user2 = new User(UUID.randomUUID().toString(), "user2", "user2"); + + couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2)); + + final List foundUsers = couchbaseTemplate.findByQuery(User.class) + .consistentWith(QueryScanConsistency.REQUEST_PLUS).all(); + + assertEquals(2, foundUsers.size()); + for (User u : foundUsers) { + assertTrue(u.equals(user1) || u.equals(user2)); + } + } + + @Test + void removeByQuery() { + User user1 = new User(UUID.randomUUID().toString(), "user1", "user1"); + User user2 = new User(UUID.randomUUID().toString(), "user2", "user2"); + + couchbaseTemplate.upsertById(User.class).all(Arrays.asList(user1, user2)); + + assertTrue(couchbaseTemplate.existsById().one(user1.getId())); + assertTrue(couchbaseTemplate.existsById().one(user2.getId())); + + couchbaseTemplate.removeByQuery(User.class).consistentWith(QueryScanConsistency.REQUEST_PLUS).all(); + + assertThrows(DataRetrievalFailureException.class, () -> couchbaseTemplate.findById(User.class).one(user1.getId())); + assertThrows(DataRetrievalFailureException.class, () -> couchbaseTemplate.findById(User.class).one(user2.getId())); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryListener.java b/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryListener.java deleted file mode 100644 index 25529871..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/CouchbaseTemplateQueryListener.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import java.util.Collections; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.query.Index; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.view.DefaultView; -import com.couchbase.client.java.view.DesignDocument; -import com.couchbase.client.java.view.View; - -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -/** - * @author Michael Nitschinger - */ -public class CouchbaseTemplateQueryListener extends DependencyInjectionTestExecutionListener { - - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - populateTestData(client, clusterInfo); - createAndWaitForDesignDocs(client); - client.query(N1qlQuery.simple(Index.createPrimaryIndex().on(client.name()))); - } - - private void populateTestData(Bucket client, ClusterInfo clusterInfo) { - CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); - - for (int i = 0; i < 100; i++) { - Beer b = new Beer("testbeer-" + i, "MyBeer" + i, true, ""); - template.save(b); - } - } - - private void createAndWaitForDesignDocs(Bucket client) { - String mapFunction = "function (doc, meta) { if(doc._class == " - + "\"org.springframework.data.couchbase.core.Beer\") { emit(doc.name, null); } }"; - View view = DefaultView.create("by_name", mapFunction); - DesignDocument designDoc = DesignDocument.create("test_beers", Collections.singletonList(view)); - client.bucketManager().upsertDesignDocument(designDoc); - } - - @Override - public void afterTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); - - for (int i = 0; i < 100; i++) { - Beer b = new Beer("testbeer-" + i, "MyBeer" + i, true, ""); - template.remove(b); - } - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/core/CustomTypeKeyIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CustomTypeKeyIntegrationTests.java new file mode 100644 index 00000000..20890543 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/CustomTypeKeyIntegrationTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.core.convert.DefaultCouchbaseTypeMapper; +import org.springframework.data.couchbase.domain.User; +import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import com.couchbase.client.java.kv.GetResult; + +@SpringJUnitConfig(CustomTypeKeyIntegrationTests.Config.class) +public class CustomTypeKeyIntegrationTests extends ClusterAwareIntegrationTests { + + private static final String CUSTOM_TYPE_KEY = "javaClass"; + + @Autowired private CouchbaseOperations operations; + + @Autowired private CouchbaseClientFactory clientFactory; + + @Test + void saveSimpleEntityCorrectlyWithDifferentTypeKey() { + User user = new User(UUID.randomUUID().toString(), "firstname", "lastname"); + User modified = operations.upsertById(User.class).one(user); + assertEquals(user, modified); + + GetResult getResult = clientFactory.getCollection(null).get(user.getId()); + assertEquals("org.springframework.data.couchbase.domain.User", + getResult.contentAsObject().getString(CUSTOM_TYPE_KEY)); + assertFalse(getResult.contentAsObject().containsKey(DefaultCouchbaseTypeMapper.DEFAULT_TYPE_KEY)); + } + + @Configuration + @EnableCouchbaseRepositories("org.springframework.data.couchbase") + static class Config extends AbstractCouchbaseConfiguration { + + @Override + public String getConnectionString() { + return connectionString(); + } + + @Override + public String getUserName() { + return config().adminUsername(); + } + + @Override + public String getPassword() { + return config().adminPassword(); + } + + @Override + public String getBucketName() { + return bucketName(); + } + + @Override + public String typeKey() { + return CUSTOM_TYPE_KEY; + } + + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/core/ReactiveBeer.java b/src/test/java/org/springframework/data/couchbase/core/ReactiveBeer.java deleted file mode 100644 index a9061ac2..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/ReactiveBeer.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import lombok.EqualsAndHashCode; -import org.springframework.data.annotation.Id; - -import com.couchbase.client.java.repository.annotation.Field; - - -/** - * Test class for persisting and loading from {@link RxJavaCouchbaseTemplate}. - * - * @author Subhashni Balakrishnan - * @author Alex Derkach - */ -@EqualsAndHashCode -public class ReactiveBeer { - - @Id - private final String id; - - private String name; - - @Field("is_active") - private boolean active = true; - - @Field("desc") - private String description; - - public ReactiveBeer(String id, String name, Boolean active, String description) { - this.id = id; - this.name = name; - this.active = active; - this.description = description; - } - - @Override - public String toString() { - return "Beer [id=" + id + ", name=" + name + ", active=" + active + ", description=" + description + "]"; - } - - public ReactiveBeer setName(String name) { - this.name = name; - return this; - } - - public String getName() { - return name; - } - - public ReactiveBeer setActive(boolean active) { - this.active = active; - return this; - } - - public boolean getActive() { - return active; - } - - public ReactiveBeer setDescription(String description) { - this.description = description; - return this; - } - - public String getDescription() { - return description; - } - - public String getId() { - return id; - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/core/RxCouchbaseTemplateQueryListener.java b/src/test/java/org/springframework/data/couchbase/core/RxCouchbaseTemplateQueryListener.java deleted file mode 100644 index be21aee8..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/RxCouchbaseTemplateQueryListener.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import java.util.Collections; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.error.DocumentDoesNotExistException; -import com.couchbase.client.java.query.Index; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.view.*; - -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import rx.Observable; - -/** - * @author Subhashni Balakrishnan - */ -public class RxCouchbaseTemplateQueryListener extends DependencyInjectionTestExecutionListener { - - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - populateTestData(client, clusterInfo); - createAndWaitForDesignDocs(client); - client.query(N1qlQuery.simple(Index.createPrimaryIndex().on(client.name()))); - } - - private void populateTestData(Bucket client, ClusterInfo clusterInfo) { - RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client); - for (int i = 0; i < 100; i++) { - ReactiveBeer b = new ReactiveBeer("testbeer-" + i, "MyBeer" + i, true, ""); - template.save(b).subscribe(); - } - } - - private void createAndWaitForDesignDocs(Bucket client) { - String mapFunction = "function (doc, meta) { if(doc._class == " - + "\"org.springframework.data.couchbase.core.ReactiveBeer\") { emit(doc.name, null); } }"; - View view = DefaultView.create("by_name", mapFunction); - DesignDocument designDoc = DesignDocument.create("reactive_test_beers", Collections.singletonList(view)); - client.bucketManager().upsertDesignDocument(designDoc); - } - - @Override - public void afterTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client); - - for (int i = 0; i < 100; i++) { - ReactiveBeer b = new ReactiveBeer("testbeer-" + i, "MyBeer" + i, true, ""); - template.remove(b).subscribe(); - } - - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/RxJavaCouchbaseTemplateIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/RxJavaCouchbaseTemplateIntegrationTests.java deleted file mode 100644 index 2f222a7c..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/RxJavaCouchbaseTemplateIntegrationTests.java +++ /dev/null @@ -1,735 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import static com.couchbase.client.java.query.Select.select; -import static com.couchbase.client.java.query.dsl.Expression.*; -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.util.*; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.PersistTo; -import com.couchbase.client.java.ReplicateTo; -import com.couchbase.client.java.document.RawJsonDocument; -import com.couchbase.client.java.query.AsyncN1qlQueryResult; -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import com.couchbase.client.java.repository.annotation.Field; -import com.couchbase.client.java.view.Stale; -import com.couchbase.client.java.view.ViewQuery; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.dao.OptimisticLockingFailureException; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.Version; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; -import rx.observers.TestSubscriber; - -/** - * @author Subhashni Balakrishnan - * @author Alex Derkach - **/ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class) -@TestExecutionListeners(RxCouchbaseTemplateQueryListener.class) -public class RxJavaCouchbaseTemplateIntegrationTests { - - @Rule - public TestName testName = new TestName(); - - @Autowired - private Bucket client; - - @Autowired - private RxJavaCouchbaseOperations template; - - private static final ObjectMapper MAPPER = new ObjectMapper(); - private static final String DEFAULT_ID = "reactivebeers:awesome-stout"; - private static final String DEFAULT_NAME = "The Awesome Stout"; - private static final boolean DEFAULT_ACTIVE = false; - private static final String DEFAULT_DESCRIPTION = ""; - - @Before - public void setUp() throws Exception { - removeIfExist(DEFAULT_ID); - } - - private void removeIfExist(String key) { - TestSubscriber subscriber = TestSubscriber.create(); - template.remove(key) - .subscribe(subscriber); - subscriber.awaitTerminalEvent(); - } - - private void removeCollectionIfExist(Collection beers) { - TestSubscriber subscriber = TestSubscriber.create(); - template.remove(beers, PersistTo.MASTER, ReplicateTo.NONE) - .subscribe(subscriber); - subscriber.awaitTerminalEvent(); - } - - @Test - public void upsertNonVersionedEntityCorrectlyWhenSaveIsCalled() throws Exception { - String newName = DEFAULT_NAME + "Second"; - ReactiveBeer firstBeer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - ReactiveBeer secondBeer = new ReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - TestSubscriber firstSaveSubscriber = TestSubscriber.create(); - TestSubscriber secondSaveSubscriber = TestSubscriber.create(); - - template.save(firstBeer).subscribe(firstSaveSubscriber); - template.save(secondBeer).subscribe(secondSaveSubscriber); - - AsyncUtils.awaitCompletedWithAnyValue(firstSaveSubscriber); - AsyncUtils.awaitCompletedWithAnyValue(secondSaveSubscriber); - validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, ReactiveBeer.class); - } - - @Test - public void replaceVersionedEntityCorrectlyWhenSaveIsCalledAndCasIsNotZero() throws Exception { - String newName = DEFAULT_NAME + "Second"; - VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - - long version = template.save(firstBeer).toBlocking().single().getVersion(); - assertThat(version > 0).isTrue(); - secondBeer.setVersion(version); - long newVersion = template.save(secondBeer).toBlocking().single().getVersion(); - assertThat(newVersion > 0).isTrue(); - assertThat(newVersion).isNotEqualTo(version); - - validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class); - } - - @Test - public void throwExceptionWhenSaveIsCalledAndCasIsMissmatched() throws Exception { - String newName = DEFAULT_NAME + "Second"; - VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - TestSubscriber secondSaveSubscriber = TestSubscriber.create(); - - long version = template.save(firstBeer).toBlocking().single().getVersion(); - assertThat(version > 0).isTrue(); - secondBeer.setVersion(version + 1234); - template.save(secondBeer).subscribe(secondSaveSubscriber); - AsyncUtils.awaitError(secondSaveSubscriber, OptimisticLockingFailureException.class); - - validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class); - } - - @Test - public void throwExceptionWhenSaveIsCalledAndCasIsZeroAndEntityAlreadyExists() throws Exception { - String newName = DEFAULT_NAME + "Second"; - VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - TestSubscriber secondSaveSubscriber = TestSubscriber.create(); - - long version = template.save(firstBeer).toBlocking().single().getVersion(); - assertThat(version > 0).isTrue(); - template.save(secondBeer).subscribe(secondSaveSubscriber); - AsyncUtils.awaitError(secondSaveSubscriber, OptimisticLockingFailureException.class); - - validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class); - } - - @Test - public void saveCollectionCorrectly() throws Exception { - Collection beers = new ArrayList<>(); - TestSubscriber testSubscriber = TestSubscriber.create(); - String name = DEFAULT_NAME; - int collectionSize = 10000; - for (int i = 0; i < collectionSize; i++) { - beers.add(new ReactiveBeer("beerCollItem" + i, name + i, false, "")); - } - removeCollectionIfExist(beers); - - template.save(beers).subscribe(testSubscriber); - - AsyncUtils.awaitCompletedWithValueCount(testSubscriber, collectionSize); - } - - @Test - public void insertSimpleEntityCorrectly() throws Exception { - TestSubscriber testSubscriber = TestSubscriber.create(); - ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - - template.insert(beer).subscribe(testSubscriber); - - AsyncUtils.awaitCompletedWithAnyValue(testSubscriber); - validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, ReactiveBeer.class); - } - - @Test - public void expectErrorWhenDocumentExistsAndInsertIsCalled() throws Exception { - TestSubscriber firstInsertSubscriber = TestSubscriber.create(); - TestSubscriber secondInsertSubscriber = TestSubscriber.create(); - ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - - template.insert(beer).subscribe(firstInsertSubscriber); - AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber); - - template.insert(beer).subscribe(secondInsertSubscriber); - AsyncUtils.awaitError(secondInsertSubscriber, OptimisticLockingFailureException.class); - } - - @Test - public void insertCollectionCorrectly() throws Exception { - TestSubscriber testSubscriber = TestSubscriber.create(); - Collection beers = new ArrayList<>(); - String name = DEFAULT_NAME; - int collectionSize = 10000; - - for (int i = 0; i < collectionSize; i++) { - beers.add(new ReactiveBeer("beerCollItem" + i, name + i, false, "")); - } - removeCollectionIfExist(beers); - - template.insert(beers).subscribe(testSubscriber); - - AsyncUtils.awaitCompletedWithValueCount(testSubscriber, collectionSize); - } - - @Test - public void replaceSimpleEntityCorrectly() throws Exception { - TestSubscriber firstInsertSubscriber = TestSubscriber.create(); - TestSubscriber replaceTestSubscriber = TestSubscriber.create(); - String newName = DEFAULT_NAME + " New"; - VersionedReactiveBeer beer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - - template.insert(beer).subscribe(firstInsertSubscriber); - AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber); - - template.findById(DEFAULT_ID, VersionedReactiveBeer.class) - .doOnNext(v -> v.setName(newName)) - .flatMap(v -> template.update(v)) - .subscribe(replaceTestSubscriber); - - AsyncUtils.awaitCompletedWithAnyValue(replaceTestSubscriber); - validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class); - } - - @Test - public void expectErrorWhenReplacingSimpleEntityWhichDoesNotExist() throws Exception { - TestSubscriber testSubscriber = TestSubscriber.create(); - VersionedReactiveBeer beer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - - template.update(beer).subscribe(testSubscriber); - AsyncUtils.awaitError(testSubscriber, DataRetrievalFailureException.class); - } - - @Test - public void removeDocument() { - TestSubscriber firstInsertSubscriber = TestSubscriber.create(); - TestSubscriber firstFindSubscriber = TestSubscriber.create(); - TestSubscriber removalTestSubscriber = TestSubscriber.create(); - TestSubscriber secondFindSubscriber = TestSubscriber.create(); - ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - - template.save(beer).subscribe(firstInsertSubscriber); - AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber); - - template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(firstFindSubscriber); - AsyncUtils.awaitCompletedWithValueCount(firstFindSubscriber, 1); - - template.remove(beer).subscribe(removalTestSubscriber); - AsyncUtils.awaitCompletedWithValueCount(removalTestSubscriber, 1); - - template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(secondFindSubscriber); - AsyncUtils.awaitValue(secondFindSubscriber, null); - } - - @Test - public void storeListsAndMaps() { - String id = "persons:lots-of-names"; - List names = new ArrayList(); - names.add("Michael"); - names.add("Thomas"); - names.add(null); - List votes = new LinkedList(); - Map info1 = new HashMap(); - info1.put("foo", true); - info1.put("bar", false); - info1.put("nullValue", null); - Map info2 = new HashMap(); - - ComplexPerson complex = new ComplexPerson(id, names, votes, info1, info2); - - template.save(complex).subscribe(); - assertThat(client.get(id)).isNotNull(); - - ComplexPerson response = template.findById(id, ComplexPerson.class).toBlocking().single(); - assertThat(response.getFirstnames()).isEqualTo(names); - assertThat(response.getVotes()).isEqualTo(votes); - assertThat(response.getId()).isEqualTo(id); - assertThat(response.getInfo1()).isEqualTo(info1); - assertThat(response.getInfo2()).isEqualTo(info2); - } - - - @Test - public void validFindById() { - TestSubscriber saveSubscriber = TestSubscriber.create(); - TestSubscriber findSubscriber = TestSubscriber.create(); - - ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION); - - template.save(beer).subscribe(saveSubscriber); - AsyncUtils.awaitCompletedWithAnyValue(saveSubscriber); - - template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(findSubscriber); - AsyncUtils.awaitValue(findSubscriber, beer); - } - - @Test - public void shouldLoadAndMapViewDocs() { - ViewQuery query = ViewQuery.from("reactive_test_beers", "by_name"); - query.stale(Stale.FALSE); - - final List beers = template.findByView(query, ReactiveBeer.class).toList().toBlocking().single(); - assertThat(beers.size() > 0).isTrue(); - - for (ReactiveBeer beer : beers) { - assertThat(beer.getId()).isNotNull(); - assertThat(beer.getName()).isNotNull(); - assertThat(beer.getActive()).isNotNull(); - } - } - - @Test - public void shouldQueryRaw() { - N1qlQuery query = N1qlQuery.simple(select("name").from(i(client.name())).limit(1)); - - AsyncN1qlQueryResult queryResult = template.queryN1QL(query).toBlocking().single(); - assertThat(queryResult.finalSuccess().toBlocking().single()).isTrue(); - assertThat(queryResult.rows().toList().toBlocking().single().isEmpty()).isFalse(); - } - - @Test - public void shouldQueryWithMapping() { - FullFragment ff1 = new FullFragment("fullFragment1", 1, "fullFragment", "test1"); - FullFragment ff2 = new FullFragment("fullFragment2", 2, "fullFragment", "test2"); - template.save(Arrays.asList(ff1, ff2)).subscribe(); - - N1qlQuery query = N1qlQuery.simple(select(i("value")) //"value" is a n1ql keyword apparently - .from(i(client.name())) - .where(x("type").eq(s("fullFragment")) - .and(x("criteria").gt(1))), - - N1qlParams.build().consistency(ScanConsistency.REQUEST_PLUS)); - - List fragments = template.findByN1QLProjection(query, Fragment.class).toList().toBlocking().single(); - assertThat(fragments).isNotNull(); - assertThat(fragments.isEmpty()).isFalse(); - assertThat(fragments.size()).isEqualTo(1); - assertThat(fragments.get(0).value).isEqualTo("test2"); - } - - @Test - public void shouldDeserialiseLongsAndInts() { - final long longValue = new Date().getTime(); - final int intValue = new Random().nextInt(); - - template.save(new SimpleWithLongAndInt("simpleWithLong:simple", longValue, intValue)).toBlocking().single(); - SimpleWithLongAndInt document = template.findById("simpleWithLong:simple", SimpleWithLongAndInt.class).toBlocking().single(); - assertThat(document).isNotNull(); - assertThat(document.getLongValue()).isEqualTo(longValue); - assertThat(document.getIntValue()).isEqualTo(intValue); - - template.save(new SimpleWithLongAndInt("simpleWithLong:simple:other", intValue, intValue)).toBlocking().single(); - document = template.findById("simpleWithLong:simple:other", SimpleWithLongAndInt.class).toBlocking().single(); - assertThat(document).isNotNull(); - assertThat(document.getLongValue()).isEqualTo(intValue); - assertThat(document.getIntValue()).isEqualTo(intValue); - } - - @Test - public void shouldDeserialiseEnums() { - SimpleWithEnum simpleWithEnum = new SimpleWithEnum("simpleWithEnum:enum", SimpleWithEnum.Type.BIG); - template.save(simpleWithEnum).toBlocking().single(); - simpleWithEnum = template.findById("simpleWithEnum:enum", SimpleWithEnum.class).toBlocking().single(); - assertThat(simpleWithEnum).isNotNull(); - assertThat(SimpleWithEnum.Type.BIG).isEqualTo(simpleWithEnum.getType()); - } - - @Test - public void shouldDeserialiseClass() { - SimpleWithClass simpleWithClass = new SimpleWithClass("simpleWithClass:class", Integer.class); - simpleWithClass.setValue("The dish ran away with the spoon."); - template.save(simpleWithClass).toBlocking().single(); - simpleWithClass = template.findById("simpleWithClass:class", SimpleWithClass.class).toBlocking().single(); - assertThat(simpleWithClass).isNotNull(); - assertThat(simpleWithClass.getValue()) - .isEqualTo("The dish ran away with the spoon."); - } - - @Test - public void expiryWhenTouchOnReadDocument() throws InterruptedException { - String id = "simple-doc-with-update-expiry-for-read"; - DocumentWithTouchOnRead doc = new DocumentWithTouchOnRead(id); - template.save(doc).subscribe(); - Thread.sleep(1000); - assertThat(template.findById(id, DocumentWithTouchOnRead.class).toBlocking() - .single()).isNotNull(); - Thread.sleep(1000); - assertThat(template.findById(id, DocumentWithTouchOnRead.class).toBlocking() - .single()).isNotNull(); - Thread.sleep(3000); - assertThat(template.findById(id, DocumentWithTouchOnRead.class).toBlocking() - .single()).isNull(); - } - - @Test - public void shouldRetainOrderWhenQueryingViewOrdered() { - ViewQuery q = ViewQuery.from("reactive_test_beers", "by_name"); - q.descending().includeDocsOrdered(true); - - String prev = null; - List beers = template.findByView(q, ReactiveBeer.class).toList().toBlocking().single(); - assertThat(q.isIncludeDocs()).isTrue(); - assertThat(q.isOrderRetained()).isTrue(); - assertThat(q.includeDocsTarget()).isEqualTo(RawJsonDocument.class); - for (ReactiveBeer beer : beers) { - if (prev != null) { - assertThat(beer.getName().compareTo(prev) < 0).describedAs(beer.getName() + " not alphabetically < to " + prev).isTrue(); - } - prev = beer.getName(); - } - } - - private void validateBeer(String id, String name, boolean active, String description, Class clazz) throws IOException { - RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class); - assertThat(resultDoc).isNotNull(); - String result = resultDoc.content(); - assertThat(result).isNotNull(); - Map resultConv = MAPPER.readValue(result, new TypeReference>() {}); - - assertThat(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT)).isNotNull(); - assertThat(resultConv.get("javaClass")).isNull(); - assertThat(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT)) - .isEqualTo(clazz.getCanonicalName()); - assertThat(resultConv.get("is_active")).isEqualTo(active); - assertThat(resultConv.get("name")).isEqualTo(name); - assertThat(resultConv.get("desc")).isEqualTo(description); - } - - /** - * A sample document with just an id and property. - */ - @Document - static class SimplePerson { - - @Id - private final String id; - @Field - private final String name; - - public SimplePerson(String id, String name) { - this.id = id; - this.name = name; - } - } - - /** - * A sample document that expires in 2 seconds. - */ - @Document(expiry = 2) - static class DocumentWithExpiry { - - @Id - private final String id; - - public DocumentWithExpiry(String id) { - this.id = id; - } - } - - /** - * A sample document that expires in 2 seconds and touchOnRead set. - */ - @Document(expiry = 2, touchOnRead = true) - static class DocumentWithTouchOnRead { - - @Id - private final String id; - - public DocumentWithTouchOnRead(String id) { - this.id = id; - } - } - - @Document - static class ComplexPerson { - - @Id - private final String id; - @Field - private final List firstnames; - @Field - private final List votes; - - @Field - private final Map info1; - @Field - private final Map info2; - - public ComplexPerson(String id, List firstnames, - List votes, Map info1, - Map info2) { - this.id = id; - this.firstnames = firstnames; - this.votes = votes; - this.info1 = info1; - this.info2 = info2; - } - - List getFirstnames() { - return firstnames; - } - - List getVotes() { - return votes; - } - - Map getInfo1() { - return info1; - } - - Map getInfo2() { - return info2; - } - - String getId() { - return id; - } - } - - @Document - static class SimpleWithLongAndInt { - - @Id - private String id; - - private long longValue; - private int intValue; - - SimpleWithLongAndInt(final String id, final long longValue, int intValue) { - this.id = id; - this.longValue = longValue; - this.intValue = intValue; - } - - String getId() { - return id; - } - - long getLongValue() { - return longValue; - } - - void setLongValue(final long value) { - this.longValue = value; - } - - public int getIntValue() { - return intValue; - } - - public void setIntValue(int intValue) { - this.intValue = intValue; - } - } - - static class SimpleWithEnum { - - @Id - private String id; - - private enum Type { - BIG - } - - Type type; - - SimpleWithEnum(final String id, final Type type) { - this.id = id; - this.type = type; - } - - String getId() { - return id; - } - - void setId(final String id) { - this.id = id; - } - - Type getType() { - return type; - } - - void setType(final Type type) { - this.type = type; - } - } - - static class SimpleWithClass { - - @Id - private String id; - - private Class integerClass; - - private String value; - - SimpleWithClass(final String id, final Class integerClass) { - this.id = id; - this.integerClass = integerClass; - } - - String getId() { - return id; - } - - void setId(final String id) { - this.id = id; - } - - Class getIntegerClass() { - return integerClass; - } - - void setIntegerClass(final Class integerClass) { - this.integerClass = integerClass; - } - - String getValue() { - return value; - } - - void setValue(final String value) { - this.value = value; - } - } - - static class VersionedClass { - - @Id - private String id; - - @Version - private long version; - - private String field; - - VersionedClass(String id, String field) { - this.id = id; - this.field = field; - } - - public String getId() { - return id; - } - - public long getVersion() { - return version; - } - - public String getField() { - return field; - } - - public void setField(String field) { - this.field = field; - } - - @Override - public String toString() { - return "VersionedClass{" + - "id='" + id + '\'' + - ", version=" + version + - ", field='" + field + '\'' + - '}'; - } - } - - @Document - static class FullFragment { - - @Id - private String id; - - private long criteria; - - private String type; - - private String value; - - public FullFragment(String id, long criteria, String type, String value) { - this.id = id; - this.criteria = criteria; - this.type = type; - this.value = value; - } - - public String getId() { - return id; - } - - public long getCriteria() { - return criteria; - } - - public String getType() { - return type; - } - - public String getValue() { - return value; - } - - public void setCriteria(long criteria) { - this.criteria = criteria; - } - - public void setType(String type) { - this.type = type; - } - - public void setValue(String value) { - this.value = value; - } - } - - static class Fragment { - public String value; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/TypeKeyIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/TypeKeyIntegrationTests.java deleted file mode 100644 index e37a4003..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/TypeKeyIntegrationTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import java.util.Map; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.document.RawJsonDocument; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestCustomTypeKeyConfig; -import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; -import org.springframework.test.context.ContextConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests the Java Config template around type key modification (DATACOUCH-134) - * - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestCustomTypeKeyConfig.class) -public class TypeKeyIntegrationTests { - - @Autowired - private Bucket client; - - @Autowired - private CouchbaseTemplate template; - - private static final ObjectMapper MAPPER = new ObjectMapper(); - - /** - * see DATACOUCH-134 - */ - @Test - public void saveSimpleEntityCorrectlyWithDifferentTypeKey() throws Exception { - String id = "beers:awesome-stout"; - String name = "The Awesome Stout"; - boolean active = false; - Beer beer = new Beer(id, name, active, ""); - - template.save(beer); - RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class); - assertThat(resultDoc).isNotNull(); - String result = resultDoc.content(); - assertThat(result).isNotNull(); - Map resultConv = MAPPER.readValue(result, new TypeReference>() {}); - - assertThat(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT)).isNull(); - assertThat(resultConv.get("javaClass")).isNotNull(); - assertThat(resultConv.get("javaClass")) - .isEqualTo("org.springframework.data.couchbase.core.Beer"); - assertThat(resultConv.get("is_active")).isEqualTo(false); - assertThat(resultConv.get("name")).isEqualTo("The Awesome Stout"); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/VersionedReactiveBeer.java b/src/test/java/org/springframework/data/couchbase/core/VersionedReactiveBeer.java deleted file mode 100644 index 6239b823..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/VersionedReactiveBeer.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core; - -import com.couchbase.client.java.repository.annotation.Field; -import lombok.EqualsAndHashCode; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.Version; - - -/** - * Test class for persisting and loading from {@link RxJavaCouchbaseTemplate}. - * - * @author Alex Derkach - */ -@EqualsAndHashCode -public class VersionedReactiveBeer { - - @Id - private final String id; - - private String name; - - @Field("is_active") - private boolean active = true; - - @Field("desc") - private String description; - - @Version - private long version; - - public VersionedReactiveBeer(String id, String name, Boolean active, String description) { - this.id = id; - this.name = name; - this.active = active; - this.description = description; - } - - @Override - public String toString() { - return "Beer [id=" + id + ", name=" + name + ", active=" + active + ", description=" + description + "]"; - } - - public VersionedReactiveBeer setName(String name) { - this.name = name; - return this; - } - - public String getName() { - return name; - } - - public VersionedReactiveBeer setActive(boolean active) { - this.active = active; - return this; - } - - public boolean getActive() { - return active; - } - - public VersionedReactiveBeer setDescription(String description) { - this.description = description; - return this; - } - - public String getDescription() { - return description; - } - - public String getId() { - return id; - } - - public void setVersion(long version) { - this.version = version; - } - - public long getVersion() { - return version; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/convert/join/N1qlJoinResolverTest.java b/src/test/java/org/springframework/data/couchbase/core/convert/join/N1qlJoinResolverTest.java deleted file mode 100644 index fa84c5e7..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/convert/join/N1qlJoinResolverTest.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.springframework.data.couchbase.core.convert.join; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -import java.lang.annotation.Annotation; - -import com.couchbase.client.java.CouchbaseBucket; -import org.junit.BeforeClass; -import org.junit.Test; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.query.FetchType; -import org.springframework.data.couchbase.core.query.HashSide; -import org.springframework.data.couchbase.core.query.N1qlJoin; -import org.springframework.data.util.TypeInformation; -import org.springframework.data.couchbase.core.convert.join.N1qlJoinResolver.N1qlJoinResolverParameters; - -/** - * Unit tests for {@link N1qlJoinResolver} - */ -public class N1qlJoinResolverTest { - static CouchbaseTemplate template; - static TypeInformation entity; - static TypeInformation associatedEntity; - static String entityClassName; - - @BeforeClass - public static void setup() { - template = mock(CouchbaseTemplate.class); - CouchbaseBucket bucket = mock(CouchbaseBucket.class); - when(bucket.name()).thenReturn("B"); - when(template.getCouchbaseBucket()).thenReturn(bucket); - CouchbaseConverter converter = mock(CouchbaseConverter.class); - when(converter.getTypeKey()).thenReturn("_class"); - when(template.getConverter()).thenReturn(converter); - entity = mock(TypeInformation.class); - doReturn(Entity.class).when(entity).getType(); - associatedEntity = mock(TypeInformation.class); - doReturn(Entity.class).when(associatedEntity).getType(); - entityClassName = Entity.class.getName(); - } - - private static N1qlJoin createAnnotation(String on, String where, String index, String rightIndex, HashSide hashSide, String[] keys) { - N1qlJoin joinDefinition = new N1qlJoin() { - - @Override - public Class annotationType() { - return N1qlJoin.class; - } - - @Override - public String on() { - return on; - } - - @Override - public FetchType fetchType() { - return FetchType.IMMEDIATE; - } - - @Override - public String where() { - return where; - } - - @Override - public String index() { - return index; - } - - @Override - public String rightIndex() { - return rightIndex; - } - - @Override - public HashSide hashside() { - return hashSide; - } - - @Override - public String[] keys() { - return keys; - } - }; - return joinDefinition; - } - - static public class Entity { - } - - @Test - public void shouldBuildQueryWithIndex() { - N1qlJoin joinDefinition = createAnnotation("A=B", "", "leftIndex", "", HashSide.NONE, new String[0]); - N1qlJoinResolverParameters parameters = new N1qlJoinResolverParameters(joinDefinition, "mydoc", entity, associatedEntity); - String statement = N1qlJoinResolver.buildQuery(template, parameters); - String expected = "SELECT META(rks).id AS _ID, META(rks).cas AS _CAS, (rks).* FROM `B` lks USE INDEX(leftIndex) JOIN B rks ON A=B" + - " AND lks._class = \"" + entityClassName + "\"" + " AND " + - "rks._class = \"" + entityClassName + "\" WHERE META(lks).id=\"mydoc\""; - assertThat(expected).isEqualTo(statement); - } - - @Test - public void shouldBuildQueryWithRightIndex() { - N1qlJoin joinDefinition = createAnnotation("A=B", "", "", "rightIndex", HashSide.NONE, new String[0]); - N1qlJoinResolverParameters parameters = new N1qlJoinResolverParameters(joinDefinition, "mydoc", entity, associatedEntity); - String statement = N1qlJoinResolver.buildQuery(template, parameters); - String expected = "SELECT META(rks).id AS _ID, META(rks).cas AS _CAS, (rks).* FROM `B` lks JOIN B rks USE INDEX(rightIndex) ON A=B" + - " AND lks._class = \"" + entityClassName + "\"" + " AND " + - "rks._class = \"" + entityClassName + "\" WHERE META(lks).id=\"mydoc\""; - assertThat(expected).isEqualTo(statement); - } - - @Test - public void shouldBuildQueryWithHashProbe() { - N1qlJoin joinDefinition = createAnnotation("A=B", "", "", "", HashSide.PROBE, new String[0]); - N1qlJoinResolverParameters parameters = new N1qlJoinResolverParameters(joinDefinition, "mydoc", entity, associatedEntity); - String statement = N1qlJoinResolver.buildQuery(template, parameters); - String expected = "SELECT META(rks).id AS _ID, META(rks).cas AS _CAS, (rks).* FROM `B` lks JOIN B rks USE HASH(probe) ON A=B" + - " AND lks._class = \"" + entityClassName + "\"" + " AND " + - "rks._class = \"" + entityClassName + "\" WHERE META(lks).id=\"mydoc\""; - assertThat(expected).isEqualTo(statement); - } - - @Test - public void shouldBuildQueryWithHashBuild() { - N1qlJoin joinDefinition = createAnnotation("A=B", "", "", "", HashSide.BUILD, new String[0]); - N1qlJoinResolverParameters parameters = new N1qlJoinResolverParameters(joinDefinition, "mydoc", entity, associatedEntity); - String statement = N1qlJoinResolver.buildQuery(template, parameters); - String expected = "SELECT META(rks).id AS _ID, META(rks).cas AS _CAS, (rks).* FROM `B` lks JOIN B rks USE HASH(build) ON A=B" + - " AND lks._class = \"" + entityClassName + "\"" + " AND " + - "rks._class = \"" + entityClassName + "\" WHERE META(lks).id=\"mydoc\""; - assertThat(expected).isEqualTo(statement); - } - - @Test - public void shouldBuildQueryWithKeys() { - N1qlJoin joinDefinition = createAnnotation("A=B", "", "", "", HashSide.NONE, new String[]{"x", "y"}); - N1qlJoinResolverParameters parameters = new N1qlJoinResolverParameters(joinDefinition, "mydoc", entity, associatedEntity); - String statement = N1qlJoinResolver.buildQuery(template, parameters); - String expected = "SELECT META(rks).id AS _ID, META(rks).cas AS _CAS, (rks).* FROM `B` lks JOIN B rks USE KEYS [\"x\",\"y\"] ON A=B" + - " AND lks._class = \"" + entityClassName + "\"" + " AND " + - "rks._class = \"" + entityClassName + "\" WHERE META(lks).id=\"mydoc\""; - assertThat(expected).isEqualTo(statement); - } - - @Test - public void shouldBuildQueryWithWhere() { - N1qlJoin joinDefinition = createAnnotation("A=B", "C=D", "", "", HashSide.NONE, new String[0]); - N1qlJoinResolverParameters parameters = new N1qlJoinResolverParameters(joinDefinition, "mydoc", entity, associatedEntity); - String statement = N1qlJoinResolver.buildQuery(template, parameters); - String expected = "SELECT META(rks).id AS _ID, META(rks).cas AS _CAS, (rks).* FROM `B` lks JOIN B rks ON A=B" + - " AND lks._class = \"" + entityClassName + "\"" + " AND " + - "rks._class = \"" + entityClassName + "\" WHERE META(lks).id=\"mydoc\" AND C=D"; - assertThat(expected).isEqualTo(statement); - } - - @Test - public void shouldBuildQueryWithMultipleHints() { - N1qlJoin joinDefinition = createAnnotation("A=B", "", "leftIndex", "rightIndex", HashSide.BUILD, new String[]{"x"}); - N1qlJoinResolverParameters parameters = new N1qlJoinResolverParameters(joinDefinition, "mydoc", entity, associatedEntity); - String statement = N1qlJoinResolver.buildQuery(template, parameters); - String expected = "SELECT META(rks).id AS _ID, META(rks).cas AS _CAS, (rks).* FROM `B` lks USE INDEX(leftIndex) JOIN B rks USE INDEX(rightIndex)" + - " HASH(build) KEYS [\"x\"] ON A=B AND lks._class = \"" + entityClassName + "\"" + " AND " + - "rks._class = \"" + entityClassName + "\" WHERE META(lks).id=\"mydoc\""; - assertThat(expected).isEqualTo(statement); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationServiceTests.java b/src/test/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationServiceTests.java index 93d54ca0..7fc3d40c 100644 --- a/src/test/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationServiceTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/convert/translation/JacksonTranslationServiceTests.java @@ -1,11 +1,11 @@ /* - * Copyright 2013-2020 the original author or authors. + * 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 + * 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, @@ -16,11 +16,11 @@ package org.springframework.data.couchbase.core.convert.translation; -import org.junit.Before; -import org.junit.Test; -import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import static org.junit.jupiter.api.Assertions.*; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; /** * Verifies the functionality of a {@link JacksonTranslationService}. @@ -29,39 +29,40 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class JacksonTranslationServiceTests { - private TranslationService service; + private static TranslationService service; - @Before - public void setup() { - service = new JacksonTranslationService(); - ((JacksonTranslationService) service).afterPropertiesSet(); - } + @BeforeAll + static void beforeAll() { + service = new JacksonTranslationService(); + ((JacksonTranslationService) service).afterPropertiesSet(); + } - @Test - public void shouldEncodeNonASCII() { - CouchbaseDocument doc = new CouchbaseDocument("key"); - doc.put("language", "русский"); - String expected = "{\"language\":\"русский\"}"; - assertThat(service.encode(doc)).isEqualTo(expected); - } + @Test + void shouldEncodeNonASCII() { + CouchbaseDocument doc = new CouchbaseDocument("key"); + doc.put("language", "русский"); + String expected = "{\"language\":\"русский\"}"; + assertEquals(expected, service.encode(doc)); + } - @Test - public void shouldDecodeNonASCII() { - String source = "{\"language\":\"русский\"}"; - CouchbaseDocument target = new CouchbaseDocument(); - service.decode(source, target); - assertThat(target.get("language")).isEqualTo("русский"); - } + @Test + void shouldDecodeNonASCII() { + String source = "{\"language\":\"русский\"}"; + CouchbaseDocument target = new CouchbaseDocument(); + service.decode(source, target); + assertEquals("русский", target.get("language")); + } - @Test - public void shouldDecodeAdHocFragment() { - String source = "{\"language\":\"french\"}"; - LanguageFragment f = service.decodeFragment(source, LanguageFragment.class); - assertThat(f).isNotNull(); - assertThat(f.language).isEqualTo("french"); - } + @Test + void shouldDecodeAdHocFragment() { + String source = "{\"language\":\"french\"}"; + LanguageFragment f = service.decodeFragment(source, LanguageFragment.class); + assertNotNull(f); + assertEquals("french", f.language); + } + + static class LanguageFragment { + public String language; + } - private static class LanguageFragment { - public String language; - } } diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java index 1cfc4712..f3f7d5cc 100644 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentEntityTests.java @@ -16,305 +16,252 @@ package org.springframework.data.couchbase.core.mapping; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + import java.util.Calendar; -import java.util.Date; import java.util.TimeZone; import java.util.concurrent.TimeUnit; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.data.util.ClassTypeInformation; import org.springframework.mock.env.MockPropertySource; -import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Verifies the correct behavior of annotation at the class level on persistable objects. - * - * @author Simon Baslé - */ -@RunWith(SpringJUnit4ClassRunner.class) -@TestPropertySource(properties = { - "valid.document.expiry = 10", - "invalid.document.expiry = abc" -}) -@ContextConfiguration(classes = BasicCouchbasePersistentEntityTests.class) +@SpringJUnitConfig +@TestPropertySource(properties = { "valid.document.expiry = 10", "invalid.document.expiry = abc" }) public class BasicCouchbasePersistentEntityTests { - @Rule - public ExpectedException expectedException = ExpectedException.none(); + @Autowired ConfigurableEnvironment environment; - @Autowired - ConfigurableEnvironment environment; + @Test + void testNoExpiryByDefault() { + CouchbasePersistentEntity entity = new BasicCouchbasePersistentEntity<>( + ClassTypeInformation.from(DefaultExpiry.class)); - @Test - public void testNoExpiryByDefault() { - CouchbasePersistentEntity entity = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(DefaultExpiry.class)); + assertThat(entity.getExpiry()).isEqualTo(0); + } - assertThat(entity.getExpiry()).isEqualTo(0); - } + @Test + void testDefaultExpiryUnitIsSeconds() { + CouchbasePersistentEntity entity = new BasicCouchbasePersistentEntity<>( + ClassTypeInformation.from(DefaultExpiryUnit.class)); - @Test - public void testDefaultExpiryUnitIsSeconds() { - CouchbasePersistentEntity entity = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(DefaultExpiryUnit.class)); + assertThat(entity.getExpiry()).isEqualTo(78); + } - assertThat(entity.getExpiry()).isEqualTo(78); - } + @Test + void testLargeExpiry30DaysStillInSeconds() { + CouchbasePersistentEntity entityUnder = new BasicCouchbasePersistentEntity<>( + ClassTypeInformation.from(LimitDaysExpiry.class)); + assertThat(entityUnder.getExpiry()).isEqualTo(30 * 24 * 60 * 60); + } - @Test - public void testLargeExpiry30DaysStillInSeconds() { - CouchbasePersistentEntity entityUnder = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(LimitDaysExpiry.class)); - assertThat(entityUnder.getExpiry()).isEqualTo(30 * 24 * 60 * 60); - } + @Test + void testLargeExpiry31DaysIsConvertedToUnixUtcTime() { + CouchbasePersistentEntity entityOver = new BasicCouchbasePersistentEntity<>( + ClassTypeInformation.from(OverLimitDaysExpiry.class)); - @Test - public void testLargeExpiry31DaysIsConvertedToUnixUtcTime() { - CouchbasePersistentEntity entityOver = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(OverLimitDaysExpiry.class)); + int expiryOver = entityOver.getExpiry(); + Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expected.add(Calendar.DAY_OF_YEAR, 31); - int expiryOver = entityOver.getExpiry(); - Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - expected.add(Calendar.DAY_OF_YEAR, 31); + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.add(Calendar.SECOND, expiryOver); + assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR)); + assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH)); + assertThat(calendar.get(Calendar.DAY_OF_MONTH)).isEqualTo(expected.get(Calendar.DAY_OF_MONTH)); + assertThat(calendar.get(Calendar.HOUR_OF_DAY)).isEqualTo(expected.get(Calendar.HOUR_OF_DAY)); + assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE)); + assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND)); + } - Date dateOver = new Date(expiryOver * 1000L); - System.out.println(entityOver + " => " + dateOver); + @Test + void testLargeExpiryExpression31DaysIsConvertedToUnixUtcTime() { + BasicCouchbasePersistentEntity entityOver = new BasicCouchbasePersistentEntity<>( + ClassTypeInformation.from(OverLimitDaysExpiryExpression.class)); + entityOver.setEnvironment(environment); - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.clear(); - calendar.add(Calendar.SECOND, expiryOver); - assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR)); - assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH)); - assertThat(calendar.get(Calendar.DAY_OF_MONTH)) - .isEqualTo(expected.get(Calendar.DAY_OF_MONTH)); - assertThat(calendar.get(Calendar.HOUR_OF_DAY)) - .isEqualTo(expected.get(Calendar.HOUR_OF_DAY)); - assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE)); - assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND)); - } + int expiryOver = entityOver.getExpiry(); + Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expected.add(Calendar.DAY_OF_YEAR, 31); - @Test - public void testLargeExpiryExpression31DaysIsConvertedToUnixUtcTime() { - BasicCouchbasePersistentEntity entityOver = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(OverLimitDaysExpiryExpression.class)); - entityOver.setEnvironment(environment); + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.add(Calendar.SECOND, expiryOver); + assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR)); + assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH)); + assertThat(calendar.get(Calendar.DAY_OF_MONTH)).isEqualTo(expected.get(Calendar.DAY_OF_MONTH)); + assertThat(calendar.get(Calendar.HOUR_OF_DAY)).isEqualTo(expected.get(Calendar.HOUR_OF_DAY)); + assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE)); + assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND)); + } - int expiryOver = entityOver.getExpiry(); - Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - expected.add(Calendar.DAY_OF_YEAR, 31); + @Test + void testLargeExpiry31DaysInSecondsIsConvertedToUnixUtcTime() { + CouchbasePersistentEntity entityOver = new BasicCouchbasePersistentEntity<>( + ClassTypeInformation.from(OverLimitSecondsExpiry.class)); - Date dateOver = new Date(expiryOver * 1000L); - System.out.println(entityOver + " => " + dateOver); + int expiryOver = entityOver.getExpiry(); + Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expected.add(Calendar.DAY_OF_YEAR, 31); - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.clear(); - calendar.add(Calendar.SECOND, expiryOver); - assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR)); - assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH)); - assertThat(calendar.get(Calendar.DAY_OF_MONTH)) - .isEqualTo(expected.get(Calendar.DAY_OF_MONTH)); - assertThat(calendar.get(Calendar.HOUR_OF_DAY)) - .isEqualTo(expected.get(Calendar.HOUR_OF_DAY)); - assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE)); - assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND)); - } + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + calendar.add(Calendar.SECOND, expiryOver); + assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR)); + assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH)); + assertThat(calendar.get(Calendar.DAY_OF_MONTH)).isEqualTo(expected.get(Calendar.DAY_OF_MONTH)); + assertThat(calendar.get(Calendar.HOUR_OF_DAY)).isEqualTo(expected.get(Calendar.HOUR_OF_DAY)); + assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE)); + assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND)); + } - @Test - public void testLargeExpiry31DaysInSecondsIsConvertedToUnixUtcTime() { - CouchbasePersistentEntity entityOver = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(OverLimitSecondsExpiry.class)); + @Test + void doesNotUseGetExpiry() { + assertThat(getBasicCouchbasePersistentEntity(SimpleDocument.class).getExpiry()).isEqualTo(0); + } - int expiryOver = entityOver.getExpiry(); - Calendar expected = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - expected.add(Calendar.DAY_OF_YEAR, 31); + @Test + void usesGetExpiry() { + assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithExpiry.class).getExpiry()).isEqualTo(10); + } - Date dateOver = new Date(expiryOver * 1000L); - System.out.println(entityOver + " => " + dateOver); + @Test + void doesNotUseIsUpdateExpiryForRead() { + assertThat(getBasicCouchbasePersistentEntity(SimpleDocument.class).isTouchOnRead()).isFalse(); + assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithExpiry.class).isTouchOnRead()).isFalse(); + } - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.clear(); - calendar.add(Calendar.SECOND, expiryOver); - assertThat(calendar.get(Calendar.YEAR)).isEqualTo(expected.get(Calendar.YEAR)); - assertThat(calendar.get(Calendar.MONTH)).isEqualTo(expected.get(Calendar.MONTH)); - assertThat(calendar.get(Calendar.DAY_OF_MONTH)) - .isEqualTo(expected.get(Calendar.DAY_OF_MONTH)); - assertThat(calendar.get(Calendar.HOUR_OF_DAY)) - .isEqualTo(expected.get(Calendar.HOUR_OF_DAY)); - assertThat(calendar.get(Calendar.MINUTE)).isEqualTo(expected.get(Calendar.MINUTE)); - assertThat(calendar.get(Calendar.SECOND)).isEqualTo(expected.get(Calendar.SECOND)); - } + @Test + void usesTouchOnRead() { + assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithTouchOnRead.class).isTouchOnRead()).isTrue(); + } - @Test - public void doesNotUseGetExpiry() throws Exception { - assertThat(getBasicCouchbasePersistentEntity(SimpleDocument.class).getExpiry()) - .isEqualTo(0); - } + @Test + void usesGetExpiryExpression() { + assertThat(getBasicCouchbasePersistentEntity(ConstantExpiryExpression.class).getExpiry()).isEqualTo(10); + } - @Test - public void usesGetExpiry() throws Exception { - assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithExpiry.class) - .getExpiry()).isEqualTo(10); - } + @Test + void usesGetExpiryFromValidExpression() { + assertThat(getBasicCouchbasePersistentEntity(ExpiryWithValidExpression.class).getExpiry()).isEqualTo(10); + } - @Test - public void doesNotUseIsUpdateExpiryForRead() throws Exception { - assertThat(getBasicCouchbasePersistentEntity(SimpleDocument.class).isTouchOnRead()) - .isFalse(); - assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithExpiry.class) - .isTouchOnRead()).isFalse(); - } + @Test + void doesNotAllowUseExpiryFromInvalidExpression() { + assertThrows(IllegalArgumentException.class, + () -> getBasicCouchbasePersistentEntity(ExpiryWithInvalidExpression.class).getExpiry()); + } - @Test - public void usesTouchOnRead() throws Exception { - assertThat(getBasicCouchbasePersistentEntity(SimpleDocumentWithTouchOnRead.class) - .isTouchOnRead()).isTrue(); - } + @Test + void usesGetExpiryExpressionAndRespectsPropertyUpdates() { + BasicCouchbasePersistentEntity entity = getBasicCouchbasePersistentEntity(ExpiryWithValidExpression.class); + assertThat(entity.getExpiry()).isEqualTo(10); - @Test - public void usesGetExpiryExpression() throws Exception { - assertThat(getBasicCouchbasePersistentEntity(ConstantExpiryExpression.class) - .getExpiry()).isEqualTo(10); - } + environment.getPropertySources().addFirst(new MockPropertySource().withProperty("valid.document.expiry", "20")); + assertThat(entity.getExpiry()).isEqualTo(20); + } - @Test - public void usesGetExpiryFromValidExpression() throws Exception { - assertThat(getBasicCouchbasePersistentEntity(ExpiryWithValidExpression.class) - .getExpiry()).isEqualTo(10); - } + @Test + void failsIfExpiryExpressionMissesRequiredProperty() { + assertThrows(IllegalArgumentException.class, + () -> getBasicCouchbasePersistentEntity(ExpiryWithMissingProperty.class).getExpiry()); + } - @Test - public void doesNotAllowUseExpiryFromInvalidExpression() throws Exception { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Invalid Integer value for expiry expression: abc"); - assertThat(getBasicCouchbasePersistentEntity(ExpiryWithInvalidExpression.class) - .getExpiry()).isEqualTo(10); - } + @Test + void doesNotAllowUseExpiryAndExpressionSimultaneously() { + assertThrows(IllegalArgumentException.class, + () -> getBasicCouchbasePersistentEntity(ExpiryAndExpression.class).getExpiry()); + } - @Test - public void usesGetExpiryExpressionAndRespectsPropertyUpdates() throws Exception { - BasicCouchbasePersistentEntity entity = getBasicCouchbasePersistentEntity(ExpiryWithValidExpression.class); - assertThat(entity.getExpiry()).isEqualTo(10); + private BasicCouchbasePersistentEntity getBasicCouchbasePersistentEntity(Class clazz) { + BasicCouchbasePersistentEntity basicCouchbasePersistentEntity = new BasicCouchbasePersistentEntity( + ClassTypeInformation.from(clazz)); + basicCouchbasePersistentEntity.setEnvironment(environment); + return basicCouchbasePersistentEntity; + } - environment.getPropertySources().addFirst(new MockPropertySource().withProperty("valid.document.expiry", "20")); - assertThat(entity.getExpiry()).isEqualTo(20); - } + @Configuration + static class Config {} - @Test - public void failsIfExpiryExpressionMissesRequiredProperty() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Could not resolve placeholder 'missing.expiry'"); - getBasicCouchbasePersistentEntity(ExpiryWithMissingProperty.class).getExpiry(); - } + public static class SimpleDocument {} - @Test - public void doesNotAllowUseExpiryAndExpressionSimultaneously() throws Exception { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("You cannot use 'expiry' and 'expiryExpression' at the same time"); - expectedException.expectMessage(ExpiryAndExpression.class.getName()); - getBasicCouchbasePersistentEntity(ExpiryAndExpression.class).getExpiry(); - } + @Document(expiry = 10) + public static class SimpleDocumentWithExpiry {} - private BasicCouchbasePersistentEntity getBasicCouchbasePersistentEntity(Class clazz) { - BasicCouchbasePersistentEntity basicCouchbasePersistentEntity = new BasicCouchbasePersistentEntity(ClassTypeInformation.from(clazz)); - basicCouchbasePersistentEntity.setEnvironment(environment); - return basicCouchbasePersistentEntity; - } + @Document(expiry = 10, touchOnRead = true) + public static class SimpleDocumentWithTouchOnRead {} - public static class SimpleDocument { - } + /** + * Simple POJO to test default expiry. + */ + @Document + private class DefaultExpiry {} - @Document(expiry = 10) - public static class SimpleDocumentWithExpiry { - } + /** + * Simple POJO to test default expiry unit. + */ + @Document(expiry = 78) + private class DefaultExpiryUnit {} - @Document(expiry = 10, touchOnRead = true) - public static class SimpleDocumentWithTouchOnRead { - } + /** + * Simple POJO to test limit expiry. + */ + @Document(expiry = 30, expiryUnit = TimeUnit.DAYS) + private class LimitDaysExpiry {} - /** - * Simple POJO to test default expiry. - */ - @Document - private class DefaultExpiry { - } + /** + * Simple POJO to test larger than 30 days expiry. + */ + @Document(expiry = 31, expiryUnit = TimeUnit.DAYS) + public class OverLimitDaysExpiry {} - /** - * Simple POJO to test default expiry unit. - */ - @Document(expiry = 78) - private class DefaultExpiryUnit { - } + /** + * Simple POJO to test larger than 30 days expiry defined as an expression + */ + @Document(expiryExpression = "${document.expiry.larger.than.30days:31}", expiryUnit = TimeUnit.DAYS) + public class OverLimitDaysExpiryExpression {} - /** - * Simple POJO to test limit expiry. - */ - @Document(expiry = 30, expiryUnit = TimeUnit.DAYS) - private class LimitDaysExpiry { - } + /** + * Simple POJO to test larger than 30 days expiry, when expressed in default time unit (SECONDS). + */ + @Document(expiry = 31 * 24 * 60 * 60) + public class OverLimitSecondsExpiry {} - /** - * Simple POJO to test larger than 30 days expiry. - */ - @Document(expiry = 31, expiryUnit = TimeUnit.DAYS) - public class OverLimitDaysExpiry { - } + /** + * Simple POJO to test constant expiry expression + */ + @Document(expiryExpression = "10") + private class ConstantExpiryExpression {} - /** - * Simple POJO to test larger than 30 days expiry defined as an expression - */ - @Document(expiryExpression = "${document.expiry.larger.than.30days:31}", expiryUnit = TimeUnit.DAYS) - public class OverLimitDaysExpiryExpression { - } + /** + * Simple POJO to test valid expiry expression by resolving simple property from environment + */ + @Document(expiryExpression = "${valid.document.expiry}") + private class ExpiryWithValidExpression {} - /** - * Simple POJO to test larger than 30 days expiry, when expressed in default time unit (SECONDS). - */ - @Document(expiry = 31 * 24 * 60 * 60) - public class OverLimitSecondsExpiry { - } + /** + * Simple POJO to test invalid expiry expression + */ + @Document(expiryExpression = "${invalid.document.expiry}") + private class ExpiryWithInvalidExpression {} - /** - * Simple POJO to test constant expiry expression - */ - @Document(expiryExpression = "10") - private class ConstantExpiryExpression { - } + /** + * Simple POJO to test expiry expression logic failure to resolve property placeholder + */ + @Document(expiryExpression = "${missing.expiry}") + private class ExpiryWithMissingProperty {} - /** - * Simple POJO to test valid expiry expression by resolving simple property from environment - */ - @Document(expiryExpression = "${valid.document.expiry}") - private class ExpiryWithValidExpression { - } - - /** - * Simple POJO to test invalid expiry expression - */ - @Document(expiryExpression = "${invalid.document.expiry}") - private class ExpiryWithInvalidExpression { - } - - /** - * Simple POJO to test expiry expression logic failure to resolve property placeholder - */ - @Document(expiryExpression = "${missing.expiry}") - private class ExpiryWithMissingProperty { - } - - /** - * Simple POJO to test that expiry and expiry expression cannot be used simultaneously - */ - @Document(expiry = 10, expiryExpression = "10") - private class ExpiryAndExpression { - } + /** + * Simple POJO to test that expiry and expiry expression cannot be used simultaneously + */ + @Document(expiry = 10, expiryExpression = "10") + private class ExpiryAndExpression {} } diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentPropertyTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentPropertyTests.java index d2a541fc..6d728eab 100644 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentPropertyTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentPropertyTests.java @@ -16,11 +16,12 @@ package org.springframework.data.couchbase.core.mapping; -import java.lang.reflect.Field; -import java.util.Optional; +import static org.assertj.core.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; +import java.lang.reflect.Field; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; @@ -28,8 +29,6 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.ClassTypeInformation; import org.springframework.util.ReflectionUtils; -import static org.assertj.core.api.Assertions.assertThat; - /** * Verifies the correct behavior of properties on persistable objects. * @@ -38,133 +37,77 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class BasicCouchbasePersistentPropertyTests { - /** - * Holds the entity to test against (contains the properties). - */ - CouchbasePersistentEntity entity; + /** + * Holds the entity to test against (contains the properties). + */ + CouchbasePersistentEntity entity; - /** - * Create an instance of the demo entity. - */ - @Before - public void setUp() { - entity = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(Beer.class)); - } + /** + * Create an instance of the demo entity. + */ + @BeforeEach + void beforeEach() { + entity = new BasicCouchbasePersistentEntity<>(ClassTypeInformation.from(Beer.class)); + } - /** - * Verifies the name of the property without annotations. - */ - @Test - public void usesPropertyFieldName() { - Field field = ReflectionUtils.findField(Beer.class, "description"); - assertThat(getPropertyFor(field).getFieldName()).isEqualTo("description"); - } + /** + * Verifies the name of the property without annotations. + */ + @Test + void usesPropertyFieldName() { + Field field = ReflectionUtils.findField(Beer.class, "description"); + assertThat(getPropertyFor(field).getFieldName()).isEqualTo("description"); + } - /** - * Verifies the name of the property with custom name annotation. - */ - @Test - public void usesAnnotatedFieldName() { - Field field = ReflectionUtils.findField(Beer.class, "name"); - assertThat(getPropertyFor(field).getFieldName()).isEqualTo("foobar"); - } + /** + * Verifies the name of the property with custom name annotation. + */ + @Test + void usesAnnotatedFieldName() { + Field field = ReflectionUtils.findField(Beer.class, "name"); + assertThat(getPropertyFor(field).getFieldName()).isEqualTo("name"); + } - @Test - public void testPrefersSpringIdAnnotation() { - BasicCouchbasePersistentEntity test = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(Beer.class)); + @Test + void testSdkIdAnnotationEvaluatedAfterSpringIdAnnotationIsIgnored() { + BasicCouchbasePersistentEntity test = new BasicCouchbasePersistentEntity<>( + ClassTypeInformation.from(Beer.class)); + Field springIdField = ReflectionUtils.findField(Beer.class, "springId"); + CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField); - Field sdkIdField = ReflectionUtils.findField(Beer.class, "sdkId"); - CouchbasePersistentProperty sdkIdProperty = getPropertyFor(sdkIdField); - Field springIdField = ReflectionUtils.findField(Beer.class, "springId"); - CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField); - test.addPersistentProperty(sdkIdProperty); - test.addPersistentProperty(springIdProperty); + // here this simulates the order in which the annotations would be found + // when "overriding" Spring @Id with SDK's @Id... + test.addPersistentProperty(springIdProperty); - assertThat(sdkIdProperty.getFieldName()).isEqualTo("sdkId"); - assertThat(springIdProperty.getFieldName()).isEqualTo("springId"); + assertThat(test.getIdProperty()).isEqualTo(springIdProperty); + } - assertThat(sdkIdProperty.isIdProperty()).isTrue(); - assertThat(springIdProperty.isIdProperty()).isTrue(); - - CouchbasePersistentProperty property = test.getIdProperty(); - assertThat(property).isEqualTo(springIdProperty); - } - - @Test - public void testAcceptsSdkIdAnnotation() { - BasicCouchbasePersistentEntity test = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(SdkIdentified.class)); - Field id = ReflectionUtils.findField(SdkIdentified.class, "id"); - CouchbasePersistentProperty idProperty = getPropertyFor(id); - test.addPersistentProperty(idProperty); - - CouchbasePersistentProperty property = test.getIdProperty(); - assertThat(property).isEqualTo(idProperty); - } - - @Test - public void testSdkIdAnnotationEvaluatedAfterSpringIdAnnotationIsIgnored() { - BasicCouchbasePersistentEntity test = new BasicCouchbasePersistentEntity( - ClassTypeInformation.from(Beer.class)); - Field sdkIdField = ReflectionUtils.findField(Beer.class, "sdkId"); - CouchbasePersistentProperty sdkIdProperty = getPropertyFor(sdkIdField); - Field springIdField = ReflectionUtils.findField(Beer.class, "springId"); - CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField); - - //here this simulates the order in which the annotations would be found - // when "overriding" Spring @Id with SDK's @Id... - test.addPersistentProperty(springIdProperty); - - assertThat(test.getIdProperty()).isEqualTo(springIdProperty); - - test.addPersistentProperty(sdkIdProperty); - assertThat(test.getIdProperty()).isEqualTo(springIdProperty); - } - - /** - * Helper method to create a property out of the field. - * - * @param field the field to retrieve the properties from. - * @return the actual BasicCouchbasePersistentProperty instance. - */ - private CouchbasePersistentProperty getPropertyFor(Field field) { + /** + * Helper method to create a property out of the field. + * + * @param field the field to retrieve the properties from. + * @return the actual BasicCouchbasePersistentProperty instance. + */ + private CouchbasePersistentProperty getPropertyFor(Field field) { ClassTypeInformation type = ClassTypeInformation.from(field.getDeclaringClass()); return new BasicCouchbasePersistentProperty(Property.of(type, field), entity, SimpleTypeHolder.DEFAULT, PropertyNameFieldNamingStrategy.INSTANCE); - } + } - /** - * Simple POJO to test attribute properties and annotations. - */ - public class Beer { + /** + * Simple POJO to test attribute properties and annotations. + */ + public class Beer { - @com.couchbase.client.java.repository.annotation.Id - private String sdkId; + @org.springframework.data.couchbase.core.mapping.Field String name; + String description; + @Id private String springId; - @Id - private String springId; + public String getId() { + return springId; + } + } - @com.couchbase.client.java.repository.annotation.Field("foobar") - String name; - - String description; - - public String getId() { - return springId; - } - } - - /** - * Simple POJO to test that a single ID property from the SDK is taken into account. - */ - public class SdkIdentified { - @com.couchbase.client.java.repository.annotation.Id - private String id; - - String value; - } } diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/CustomConverterIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/CustomConverterIntegrationTests.java deleted file mode 100644 index e134ad79..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/CustomConverterIntegrationTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core.mapping; - -import java.util.Arrays; -import java.util.UUID; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.ReadingConverter; -import org.springframework.data.convert.WritingConverter; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.TestContainerResource; -import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions; -import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -/** - * @author Subhashni Balakrishnan - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -public class CustomConverterIntegrationTests { - - @Autowired - private MappingCouchbaseConverter converter; - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - - private TestUUIDRepository repository; - - @WritingConverter - public enum UUIDToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(UUID source) { - return source.toString(); - } - } - - @ReadingConverter - public enum StringToUUIDConverter implements Converter { - INSTANCE; - - @Override - public UUID convert(String source) { - return UUID.fromString(source); - } - } - - @Before - public void setup() { - converter.setCustomConversions(new CouchbaseCustomConversions(Arrays.asList(UUIDToStringConverter.INSTANCE, StringToUUIDConverter.INSTANCE))); - converter.afterPropertiesSet(); - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = getRepositoryWithRetry(factory, TestUUIDRepository.class); - } - - @Test - public void testIdConversion() { - TestUUID doc = new TestUUID(); - doc.id = UUID.randomUUID(); - repository.save(doc); - repository.findById(doc.id); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/CustomConvertersTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/CustomConvertersTests.java index fc1a06a6..635e014d 100644 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/CustomConvertersTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/CustomConvertersTests.java @@ -16,30 +16,20 @@ package org.springframework.data.couchbase.core.mapping; -import java.text.Format; -import java.text.SimpleDateFormat; +import static org.assertj.core.api.Assertions.*; + import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.annotation.Id; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; -import org.springframework.data.couchbase.UnitTestApplicationConfig; import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import com.couchbase.client.java.repository.annotation.Field; -import com.couchbase.client.java.repository.annotation.Id; - -import static org.assertj.core.api.Assertions.assertThat; /** * Tests to verify custom mapping logic. @@ -47,142 +37,130 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Michael Nitschinger * @author Mark Paluch */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = UnitTestApplicationConfig.class) public class CustomConvertersTests { - @Autowired - private MappingCouchbaseConverter converter; + private MappingCouchbaseConverter converter; - @After - public void cleanup() { - converter.setCustomConversions(new CouchbaseCustomConversions(Collections.emptyList())); - } + @BeforeEach + void beforeEach() { + converter = new MappingCouchbaseConverter(); + } - @Test - public void shouldWriteWithCustomConverter() { - List converters = new ArrayList(); - converters.add(DateToStringConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); - converter.afterPropertiesSet(); + @Test + void shouldWriteWithCustomConverter() { + List converters = new ArrayList<>(); + converters.add(DateToStringConverter.INSTANCE); + converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + converter.afterPropertiesSet(); - Date date = new Date(); - BlogPost post = new BlogPost(); - post.created = date; + Date date = new Date(); + BlogPost post = new BlogPost(); + post.created = date; - CouchbaseDocument doc = new CouchbaseDocument(); - converter.write(post, doc); + CouchbaseDocument doc = new CouchbaseDocument(); + converter.write(post, doc); - assertThat(doc.getPayload().get("created")).isEqualTo(date.toString()); - } + assertThat(doc.getPayload().get("created")).isEqualTo(date.toString()); + } - @Test - public void shouldReadWithCustomConverter() { - List converters = new ArrayList(); - converters.add(IntegerToStringConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); - converter.afterPropertiesSet(); + @Test + void shouldReadWithCustomConverter() { + List converters = new ArrayList<>(); + converters.add(IntegerToStringConverter.INSTANCE); + converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + converter.afterPropertiesSet(); - CouchbaseDocument doc = new CouchbaseDocument(); - doc.getPayload().put("content", 10); - Counter loaded = converter.read(Counter.class, doc); - assertThat(loaded.content).isEqualTo("even"); - } + CouchbaseDocument doc = new CouchbaseDocument(); + doc.getPayload().put("content", 10); + Counter loaded = converter.read(Counter.class, doc); + assertThat(loaded.content).isEqualTo("even"); + } - @Test - public void shouldWriteConvertFullDocument() { - List converters = new ArrayList(); - converters.add(BlogPostToCouchbaseDocumentConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); - converter.afterPropertiesSet(); + @Test + void shouldWriteConvertFullDocument() { + List converters = new ArrayList<>(); + converters.add(BlogPostToCouchbaseDocumentConverter.INSTANCE); + converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + converter.afterPropertiesSet(); - BlogPost post = new BlogPost(); - post.id = "foobar"; - post.title = "The Foo of the Bar"; + BlogPost post = new BlogPost(); + post.id = "foobar"; + post.title = "The Foo of the Bar"; - CouchbaseDocument doc = new CouchbaseDocument(); - converter.write(post, doc); + CouchbaseDocument doc = new CouchbaseDocument(); + converter.write(post, doc); - assertThat(doc.getPayload().get("title")).isEqualTo("The Foo of the Bar"); - assertThat(doc.getPayload().get("slug")).isEqualTo("the_foo_of_the_bar"); - } + assertThat(doc.getPayload().get("title")).isEqualTo("The Foo of the Bar"); + assertThat(doc.getPayload().get("slug")).isEqualTo("the_foo_of_the_bar"); + } - @Test - public void shouldReadConvertFullDocument() { - List converters = new ArrayList(); - converters.add(CouchbaseDocumentToBlogPostConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); - converter.afterPropertiesSet(); + @Test + void shouldReadConvertFullDocument() { + List converters = new ArrayList<>(); + converters.add(CouchbaseDocumentToBlogPostConverter.INSTANCE); + converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + converter.afterPropertiesSet(); - CouchbaseDocument doc = new CouchbaseDocument(); - doc.getPayload().put("title", "My Title"); + CouchbaseDocument doc = new CouchbaseDocument(); + doc.getPayload().put("title", "My Title"); - BlogPost loaded = converter.read(BlogPost.class, doc); - assertThat(loaded.id).isEqualTo("modified"); - assertThat(loaded.title).isEqualTo("My Title!!"); - } + BlogPost loaded = converter.read(BlogPost.class, doc); + assertThat(loaded.id).isEqualTo("modified"); + assertThat(loaded.title).isEqualTo("My Title!!"); + } - public static class BlogPost { - @Id //also tests DATACOUCH-145 (this is SDK's @Id) - public String id = "key"; + public enum IntegerToStringConverter implements Converter { + INSTANCE; - @Field - public Date created; + @Override + public String convert(Integer source) { + return source % 2 == 0 ? "even" : "odd"; + } + } - @Field - public String title; + public enum DateToStringConverter implements Converter { + INSTANCE; - } + @Override + public String convert(Date source) { + return source.toString(); + } + } - public class Counter { - @Field - public String content; - } + @WritingConverter + public enum BlogPostToCouchbaseDocumentConverter implements Converter { + INSTANCE; - public static enum IntegerToStringConverter implements Converter { - INSTANCE; + @Override + public CouchbaseDocument convert(BlogPost source) { + return new CouchbaseDocument().setId(source.id).put("title", source.title).put("slug", + source.title.toLowerCase().replaceAll(" ", "_")); + } + } - @Override - public String convert(Integer source) { - return source % 2 == 0 ? "even" : "odd"; - } - } + @ReadingConverter + public enum CouchbaseDocumentToBlogPostConverter implements Converter { + INSTANCE; - public static enum DateToStringConverter implements Converter { - INSTANCE; + @Override + public BlogPost convert(CouchbaseDocument source) { + BlogPost post = new BlogPost(); + post.id = "modified"; + post.title = source.getPayload().get("title") + "!!"; + return post; + } + } - public static Format FORMATTER = new SimpleDateFormat("yyyy HH"); + public static class BlogPost { + @Id public String id = "key"; - @Override - public String convert(Date source) { - return source.toString(); - } - } + @Field public Date created; - @WritingConverter - public static enum BlogPostToCouchbaseDocumentConverter implements Converter { - INSTANCE; + @Field public String title; - @Override - public CouchbaseDocument convert(BlogPost source) { - return new CouchbaseDocument() - .setId(source.id) - .put("title", source.title) - .put("slug", source.title.toLowerCase().replaceAll(" ", "_")); - } - } - - @ReadingConverter - public static enum CouchbaseDocumentToBlogPostConverter implements Converter { - INSTANCE; - - @Override - public BlogPost convert(CouchbaseDocument source) { - BlogPost post = new BlogPost(); - post.id = "modified"; - post.title = source.getPayload().get("title") + "!!"; - return post; - } - } + } + public static class Counter { + @Field public String content; + } } diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java index 0a66f36f..3f842d59 100644 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java @@ -16,43 +16,25 @@ package org.springframework.data.couchbase.core.mapping; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.data.Offset.offset; +import static org.junit.jupiter.api.Assertions.*; + import java.math.BigDecimal; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; - import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; +import org.junit.jupiter.api.Test; import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; -import org.springframework.data.couchbase.UnitTestApplicationConfig; import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions; import org.springframework.data.couchbase.core.convert.CouchbaseJsr310Converters.LocalDateTimeToLongConverter; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.domain.User; import org.springframework.data.mapping.MappingException; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import com.couchbase.client.java.repository.annotation.Field; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.data.Offset.offset; /** * @author Michael Nitschinger @@ -60,713 +42,616 @@ import static org.assertj.core.data.Offset.offset; * @author Subhashni Balakrishnan * @author Mark Paluch */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = UnitTestApplicationConfig.class) public class MappingCouchbaseConverterTests { - @Autowired - private MappingCouchbaseConverter converter; - - @Test - public void shouldNotThrowNPE() { - CouchbaseDocument converted = new CouchbaseDocument(); - converter.write(null, converted); - assertThat(converted.getId()).isNull(); - assertThat(converted.getExpiration()).isEqualTo(0); - } - - @Test(expected = MappingException.class) - public void doesNotAllowSimpleType1() { - converter.write("hello", new CouchbaseDocument()); - } - - @Test(expected = MappingException.class) - public void doesNotAllowSimpleType2() { - converter.write(true, new CouchbaseDocument()); - } - - @Test(expected = MappingException.class) - public void doesNotAllowSimpleType3() { - converter.write(42, new CouchbaseDocument()); - } - - @Test(expected = MappingException.class) - public void needsIDOnEntity() { - converter.write(new EntityWithoutID("foo"), - new CouchbaseDocument()); - } - - @Test - public void writesString() throws Exception { - CouchbaseDocument converted = new CouchbaseDocument(); - StringEntity entity = new StringEntity("foobar"); - - converter.write(entity, converted); - Map result = converted.export(); - assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); - assertThat(result.get("attr0")).isEqualTo("foobar"); - assertThat(converted.getId()).isEqualTo(BaseEntity.ID); - } - - @Test - public void readsString() { - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", StringEntity.class.getName()); - source.put("attr0", "foobar"); - - StringEntity converted = converter.read(StringEntity.class, source); - assertThat(converted.attr0).isEqualTo("foobar"); - } - - @Test - public void writesNumber() { - CouchbaseDocument converted = new CouchbaseDocument(); - NumberEntity entity = new NumberEntity(42); - - converter.write(entity, converted); - Map result = converted.export(); - assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); - assertThat(result.get("attr0")).isEqualTo(42L); - assertThat(converted.getId()).isEqualTo(BaseEntity.ID); - } - - @Test - public void readsNumber() { - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", NumberEntity.class.getName()); - source.put("attr0", 42); - - NumberEntity converted = converter.read(NumberEntity.class, source); - assertThat(converted.attr0).isEqualTo(42); - } - - @Test - public void writesBoolean() { - CouchbaseDocument converted = new CouchbaseDocument(); - BooleanEntity entity = new BooleanEntity(true); - - converter.write(entity, converted); - Map result = converted.export(); - assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); - assertThat(result.get("attr0")).isEqualTo(true); - assertThat(converted.getId()).isEqualTo("mockid"); - } - - @Test - public void readsBoolean() { - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", BooleanEntity.class.getName()); - source.put("attr0", true); - - BooleanEntity converted = converter.read(BooleanEntity.class, source); - assertThat(converted.attr0).isTrue(); - } - - @Test - public void writesMixedSimpleTypes() { - CouchbaseDocument converted = new CouchbaseDocument(); - MixedSimpleEntity entity = new MixedSimpleEntity("a", 5, -0.3, true); - - converter.write(entity, converted); - Map result = converted.export(); - assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); - assertThat(result.get("attr0")).isEqualTo("a"); - assertThat(result.get("attr1")).isEqualTo(5); - assertThat(result.get("attr2")).isEqualTo(-0.3); - assertThat(result.get("attr3")).isEqualTo(true); - } - - @Test - public void readsMixedSimpleTypes() { - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", MixedSimpleEntity.class.getName()); - source.put("attr0", "a"); - source.put("attr1", 5); - source.put("attr2", -0.3); - source.put("attr3", true); - - MixedSimpleEntity converted = converter.read(MixedSimpleEntity.class, source); - assertThat(converted.attr0).isEqualTo("a"); - assertThat(converted.attr1).isEqualTo(5); - assertThat(converted.attr2).isCloseTo(-0.3, offset(0.0)); - assertThat(converted.attr3).isTrue(); - } - - @Test - public void readsID() { - CouchbaseDocument document = new CouchbaseDocument("001"); - - BasicCouchbasePersistentPropertyTests.Beer beer = converter.read(BasicCouchbasePersistentPropertyTests.Beer.class, - document); - - assertThat(beer.getId()).isEqualTo("001"); - } - - @Test - public void writesUninitializedValues() { - CouchbaseDocument converted = new CouchbaseDocument(); - UninitializedEntity entity = new UninitializedEntity(); - - converter.write(entity, converted); - Map result = converted.export(); - assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); - assertThat(result.get("attr1")).isEqualTo(0); - } - - @Test - public void readsUninitializedValues() { - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", UninitializedEntity.class.getName()); - source.put("attr1", 0); - - UninitializedEntity converted = converter.read(UninitializedEntity.class, source); - assertThat(converted.attr0).isNull(); - assertThat(converted.attr1).isEqualTo(0); - assertThat(converted.attr2).isNull(); - } - - @Test - public void writesAndReadsMapsAndNestedMaps() { - CouchbaseDocument converted = new CouchbaseDocument(); - - Map attr0 = new HashMap(); - Map attr1 = new TreeMap(); - Map attr2 = new LinkedHashMap(); - Map> attr3 = - new HashMap>(); - - attr0.put("foo", "bar"); - attr1.put("bar", true); - attr3.put("hashmap", attr0); - - MapEntity entity = new MapEntity(attr0, attr1, attr2, attr3); - - converter.write(entity, converted); - Map result = converted.export(); - assertThat(result.get("attr0")).isEqualTo(attr0); - assertThat(result.get("attr1")).isEqualTo(attr1); - assertThat(result.get("attr2")).isEqualTo(attr2); - assertThat(result.get("attr3")).isEqualTo(attr3); - - CouchbaseDocument cattr0 = new CouchbaseDocument(); - cattr0.put("foo", "bar"); - - CouchbaseDocument cattr1 = new CouchbaseDocument(); - cattr1.put("bar", true); - - CouchbaseDocument cattr2 = new CouchbaseDocument(); - - CouchbaseDocument cattr3 = new CouchbaseDocument(); - cattr3.put("hashmap", cattr0); - - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", MapEntity.class.getName()); - source.put("attr0", cattr0); - source.put("attr1", cattr1); - source.put("attr2", cattr2); - source.put("attr3", cattr3); - - MapEntity readConverted = converter.read(MapEntity.class, source); - assertThat(readConverted.attr0).isEqualTo(attr0); - assertThat(readConverted.attr1).isEqualTo(attr1); - assertThat(readConverted.attr2).isEqualTo(attr2); - assertThat(readConverted.attr3).isEqualTo(attr3); - } - - @Test - public void writesAndReadsListAndNestedList() { - CouchbaseDocument converted = new CouchbaseDocument(); - List attr0 = new ArrayList(); - List attr1 = new LinkedList(); - List> attr2 = new ArrayList>(); - - attr0.add("foo"); - attr0.add("bar"); - attr2.add(attr0); - - ListEntity entity = new ListEntity(attr0, attr1, attr2); - - converter.write(entity, converted); - Map result = converted.export(); - assertThat(result.get("attr0")).isEqualTo(attr0); - assertThat(result.get("attr1")).isEqualTo(attr1); - assertThat(result.get("attr2")).isEqualTo(attr2); - - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", ListEntity.class.getName()); - CouchbaseList cattr0 = new CouchbaseList(); - cattr0.put("foo"); - cattr0.put("bar"); - CouchbaseList cattr1 = new CouchbaseList(); - CouchbaseList cattr2 = new CouchbaseList(); - cattr2.put(cattr0); - source.put("attr0", cattr0); - source.put("attr1", cattr1); - source.put("attr2", cattr2); - - ListEntity readConverted = converter.read(ListEntity.class, source); - assertThat(readConverted.attr0.size()).isEqualTo(2); - assertThat(readConverted.attr1.size()).isEqualTo(0); - assertThat(readConverted.attr2.size()).isEqualTo(1); - assertThat(readConverted.attr2.get(0).size()).isEqualTo(2); - } - - @Test - public void writesAndReadsSetAndNestedSet() { - CouchbaseDocument converted = new CouchbaseDocument(); - Set attr0 = new HashSet(); - TreeSet attr1 = new TreeSet(); - Set> attr2 = new HashSet>(); - - attr0.add("foo"); - attr0.add("bar"); - attr2.add(attr0); - - SetEntity entity = new SetEntity(attr0, attr1, attr2); - - converter.write(entity, converted); - Map result = converted.export(); - assertThat(((Collection) result.get("attr0")).size()).isEqualTo(attr0.size()); - assertThat(((Collection) result.get("attr1")).size()).isEqualTo(attr1.size()); - assertThat(((Collection) result.get("attr2")).size()).isEqualTo(attr2.size()); - - CouchbaseList cattr0 = new CouchbaseList(); - cattr0.put("foo"); - cattr0.put("bar"); - - CouchbaseList cattr1 = new CouchbaseList(); - - CouchbaseList cattr2 = new CouchbaseList(); - cattr2.put(cattr0); - - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", SetEntity.class.getName()); - source.put("attr0", cattr0); - source.put("attr1", cattr1); - source.put("attr2", cattr2); - - SetEntity readConverted = converter.read(SetEntity.class, source); - assertThat(readConverted.attr0).isEqualTo(attr0); - assertThat(readConverted.attr1).isEqualTo(attr1); - assertThat(readConverted.attr2).isEqualTo(attr2); - } - - @Test - public void writesAndReadsValueClass() { - CouchbaseDocument converted = new CouchbaseDocument(); - - final String email = "foo@bar.com"; - final Email addy = new Email(email); - List listOfEmails = new ArrayList(); - listOfEmails.add(addy); - - ValueEntity entity = new ValueEntity(addy, listOfEmails); - converter.write(entity, converted); - Map result = converted.export(); - - assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); - assertThat(result.get("email")).isEqualTo(new HashMap() {{ - put("emailAddr", email); - }}); - - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", ValueEntity.class.getName()); - CouchbaseDocument emailDoc = new CouchbaseDocument(); - emailDoc.put("emailAddr", "foo@bar.com"); - source.put("email", emailDoc); - CouchbaseList listOfEmailsDoc = new CouchbaseList(); - listOfEmailsDoc.put(emailDoc); - source.put("listOfEmails", listOfEmailsDoc); - - ValueEntity readConverted = converter.read(ValueEntity.class, source); - assertThat(readConverted.email.emailAddr).isEqualTo(addy.emailAddr); - assertThat(readConverted.listOfEmails.get(0).emailAddr) - .isEqualTo(listOfEmails.get(0).emailAddr); - } - - @Test - public void writesAndReadsCustomConvertedClass() { - List converters = new ArrayList(); - converters.add(BigDecimalToStringConverter.INSTANCE); - converters.add(StringToBigDecimalConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); - converter.afterPropertiesSet(); - - CouchbaseDocument converted = new CouchbaseDocument(); - - final String valueStr = "12.345"; - final BigDecimal value = new BigDecimal(valueStr); - final String value2Str = "0.6789"; - final BigDecimal value2 = new BigDecimal(value2Str); - List listOfValues = new ArrayList(); - listOfValues.add(value); - listOfValues.add(value2); - Map mapOfValues = new HashMap(); - mapOfValues.put("val1", value); - mapOfValues.put("val2", value2); - - CustomEntity entity = new CustomEntity(value, listOfValues, mapOfValues); - converter.write(entity, converted); - - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", CustomEntity.class.getName()); - source.put("value", valueStr); - CouchbaseList listOfValuesDoc = new CouchbaseList(); - listOfValuesDoc.put(valueStr); - listOfValuesDoc.put(value2Str); - source.put("listOfValues", listOfValuesDoc); - CouchbaseDocument mapOfValuesDoc = new CouchbaseDocument(); - mapOfValuesDoc.put("val1", valueStr); - mapOfValuesDoc.put("val2", value2Str); - source.put("mapOfValues", mapOfValuesDoc); - - assertThat(valueStr) - .isEqualTo(((CouchbaseList) converted.getPayload().get("listOfValues")) - .get(0)); - assertThat(value2Str) - .isEqualTo(((CouchbaseList) converted.getPayload().get("listOfValues")) - .get(1)); - assertThat(converted.export().toString()).isEqualTo(source.export().toString()); - - CustomEntity readConverted = converter.read(CustomEntity.class, source); - assertThat(readConverted.value).isEqualTo(value); - assertThat(readConverted.listOfValues.get(0)).isEqualTo(listOfValues.get(0)); - assertThat(readConverted.listOfValues.get(1)).isEqualTo(listOfValues.get(1)); - assertThat(readConverted.mapOfValues.get("val1")).isEqualTo(mapOfValues.get("val1")); - assertThat(readConverted.mapOfValues.get("val2")).isEqualTo(mapOfValues.get("val2")); - } - - @Test - public void writesAndReadsClassContainingCustomConvertedObjects() { - List converters = new ArrayList(); - converters.add(BigDecimalToStringConverter.INSTANCE); - converters.add(StringToBigDecimalConverter.INSTANCE); - converter.setCustomConversions(new CouchbaseCustomConversions(converters)); - converter.afterPropertiesSet(); - - CouchbaseDocument converted = new CouchbaseDocument(); - - final String weightStr = "12.34"; - final BigDecimal weight = new BigDecimal(weightStr); - final CustomObject addy = new CustomObject(weight); - List listOfObjects = new ArrayList(); - listOfObjects.add(addy); - Map mapOfObjects = new HashMap(); - mapOfObjects.put("obj0", addy); - mapOfObjects.put("obj1", addy); - - CustomObjectEntity entity = new CustomObjectEntity(addy, listOfObjects, mapOfObjects); - converter.write(entity, converted); - - CouchbaseDocument source = new CouchbaseDocument(); - source.put("_class", CustomObjectEntity.class.getName()); - CouchbaseDocument objectDoc = new CouchbaseDocument(); - objectDoc.put("weight", weightStr); - source.put("object", objectDoc); - CouchbaseList listOfObjectsDoc = new CouchbaseList(); - listOfObjectsDoc.put(objectDoc); - source.put("listOfObjects", listOfObjectsDoc); - CouchbaseDocument mapOfObjectsDoc = new CouchbaseDocument(); - mapOfObjectsDoc.put("obj0", objectDoc); - mapOfObjectsDoc.put("obj1", objectDoc); - source.put("mapOfObjects", mapOfObjectsDoc); - assertThat(converted.export().toString()).isEqualTo(source.export().toString()); - - CustomObjectEntity readConverted = converter.read(CustomObjectEntity.class, source); - assertThat(readConverted.object.weight).isEqualTo(addy.weight); - assertThat(readConverted.listOfObjects.get(0).weight) - .isEqualTo(listOfObjects.get(0).weight); - assertThat(readConverted.mapOfObjects.get("obj0").weight) - .isEqualTo(mapOfObjects.get("obj0").weight); - assertThat(readConverted.mapOfObjects.get("obj1").weight) - .isEqualTo(mapOfObjects.get("obj1").weight); - } - - @Test - public void writesAndReadsDates() { - Date created = new Date(); - Calendar modified = Calendar.getInstance(); - LocalDateTime deleted = LocalDateTime.now(); - DateEntity entity = new DateEntity(created, modified, deleted); - - CouchbaseDocument converted = new CouchbaseDocument(); - converter.write(entity, converted); - assertThat(converted.getPayload().get("created")).isEqualTo(created.getTime()); - assertThat(converted.getPayload().get("modified")) - .isEqualTo(modified.getTimeInMillis() / 1000); - LocalDateTimeToLongConverter localDateTimeToDateconverter = LocalDateTimeToLongConverter.INSTANCE; - assertThat(converted.getPayload().get("deleted")) - .isEqualTo(localDateTimeToDateconverter.convert(deleted)); - - DateEntity read = converter.read(DateEntity.class, converted); - assertThat(read.created.getTime()).isEqualTo(created.getTime()); - assertThat(read.modified.getTimeInMillis() / 1000) - .isEqualTo(modified.getTimeInMillis() / 1000); - assertThat(read.deleted.truncatedTo(ChronoUnit.MILLIS)) - .isEqualTo(deleted.truncatedTo(ChronoUnit.MILLIS)); - } - - @Test - public void shouldPrioritizeSpringIdOverSdkId() { - SpringIdentified entity = new SpringIdentified(); - CouchbaseDocument converted = new CouchbaseDocument(); - converter.write(entity, converted); - - assertThat(converted.getId()).isEqualTo("realId"); - } - - @Test - public void shouldIgnoreSdkIdIfSpringIdFound() { - AmbiguousIdentified entity = new AmbiguousIdentified(); - CouchbaseDocument converted = new CouchbaseDocument(); - converter.write(entity, converted); - - assertThat(converted.getId()).isEqualTo("springId"); - } - - @Test - public void testStrictFieldCheckingIgnoresUnannotated() throws Exception{ - try { - CouchbaseDocument converted = new CouchbaseDocument(); - AnnotatedEntity entity = new AnnotatedEntity(); - - converter.setEnableStrictFieldChecking(true); - converter.write(entity,converted); - - assertThat(converted.getId() != null).isTrue(); - assertThat(converted.getPayload().containsKey("annotatedField")).isTrue(); - assertThat(converted.getPayload().containsKey("nonAnnotatedField")).isFalse(); - } finally { - converter.setEnableStrictFieldChecking(false); - } - } - - @Test - public void testLenientFieldCheckingStoresUnannotated() throws Exception{ - try { - CouchbaseDocument converted = new CouchbaseDocument(); - AnnotatedEntity entity = new AnnotatedEntity(); - - converter.setEnableStrictFieldChecking(false); - converter.write(entity,converted); - - assertThat(converted.getId() != null).isTrue(); - assertThat(converted.getPayload().containsKey("annotatedField")).isTrue(); - assertThat(converted.getPayload().containsKey("nonAnnotatedField")).isTrue(); - } finally { - converter.setEnableStrictFieldChecking(false); - } - } - - - - static class EntityWithoutID { - private String attr0; - - public EntityWithoutID(String a0) { - attr0 = a0; - } - } - - static class BaseEntity { - public static final String ID = "mockid"; - @Id - private String id = ID; - } - - static class StringEntity extends BaseEntity { - private String attr0; - - public StringEntity(String attr0) { - this.attr0 = attr0; - } - } - - static class NumberEntity extends BaseEntity { - private long attr0; - - public NumberEntity(long attr0) { - this.attr0 = attr0; - } - } - - static class BooleanEntity extends BaseEntity { - private boolean attr0; - - public BooleanEntity(boolean attr0) { - this.attr0 = attr0; - } - } - - static class MixedSimpleEntity extends BaseEntity { - private String attr0; - private int attr1; - private double attr2; - private boolean attr3; - - public MixedSimpleEntity(String attr0, int attr1, double attr2, boolean attr3) { - this.attr0 = attr0; - this.attr1 = attr1; - this.attr2 = attr2; - this.attr3 = attr3; - } - } - - static class UninitializedEntity extends BaseEntity { - private String attr0 = null; - private int attr1; - private Integer attr2; - } - - static class MapEntity extends BaseEntity { - private Map attr0; - private Map attr1; - private Map attr2; - private Map> attr3; - - public MapEntity(Map attr0, Map attr1, Map attr2, Map> attr3) { - this.attr0 = attr0; - this.attr1 = attr1; - this.attr2 = attr2; - this.attr3 = attr3; - } - } - - static class ListEntity extends BaseEntity { - private List attr0; - private List attr1; - private List> attr2; - - ListEntity(List attr0, List attr1, List> attr2) { - this.attr0 = attr0; - this.attr1 = attr1; - this.attr2 = attr2; - } - } - - static class SetEntity extends BaseEntity { - private Set attr0; - private Set attr1; - private Set> attr2; - - SetEntity(Set attr0, Set attr1, Set> attr2) { - this.attr0 = attr0; - this.attr1 = attr1; - this.attr2 = attr2; - } - } - - static class ValueEntity extends BaseEntity { - private Email email; - private List listOfEmails; - - public ValueEntity(Email email, List listOfEmails) { - this.email = email; - this.listOfEmails = listOfEmails; - } - } - - static class Email { - private String emailAddr; - - public Email(String emailAddr) { - this.emailAddr = emailAddr; - } - } - - static class CustomEntity extends BaseEntity { - private BigDecimal value; - private List listOfValues; - private Map mapOfValues; - - public CustomEntity(BigDecimal value, List listOfValues, Map mapOfValues) { - this.value = value; - this.listOfValues = listOfValues; - this.mapOfValues = mapOfValues; - } - } - - static class CustomObjectEntity extends BaseEntity { - private CustomObject object; - private List listOfObjects; - private Map mapOfObjects; - - public CustomObjectEntity(CustomObject object, List listOfObjects, Map mapOfObjects) { - this.object = object; - this.listOfObjects = listOfObjects; - this.mapOfObjects = mapOfObjects; - } - } - - static class CustomObject { - private BigDecimal weight; - - public CustomObject(BigDecimal weight) { - this.weight = weight; - } - } - - static class AnnotatedEntity{ - @Id private String uuid; - @Field private String annotatedField; - private String nonAnnotatedField; - - public AnnotatedEntity(){ - this.uuid = java.util.UUID.randomUUID().toString(); - this.annotatedField = "Annotated Property"; - this.nonAnnotatedField = "Non Annotated Property"; - } - } - - - - @WritingConverter - public static enum BigDecimalToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(BigDecimal source) { - return source.toPlainString(); - } - } - - @ReadingConverter - public static enum StringToBigDecimalConverter implements Converter { - INSTANCE; - - @Override - public BigDecimal convert(String source) { - return new BigDecimal(source); - } - } - - static class DateEntity extends BaseEntity { - private Date created; - private Calendar modified; - private LocalDateTime deleted; - - public DateEntity(Date created, Calendar modified, LocalDateTime deleted) { - this.created = created; - this.modified = modified; - this.deleted = deleted; - } - } - - static class SdkIdentified { - @com.couchbase.client.java.repository.annotation.Id - public String id = "id"; - } - - static class SpringIdentified extends SdkIdentified { - @org.springframework.data.annotation.Id - public String realId = "realId"; - } - - static class AmbiguousIdentified { - @org.springframework.data.annotation.Id - public String springId = "springId"; - - @com.couchbase.client.java.repository.annotation.Id - public String sdkId = "sdkId"; - } + private static MappingCouchbaseConverter converter = new MappingCouchbaseConverter(); + + static { + converter.afterPropertiesSet(); + } + + @Test + void shouldNotThrowNPE() { + CouchbaseDocument converted = new CouchbaseDocument(); + converter.write(null, converted); + assertThat(converted.getId()).isNull(); + assertThat(converted.getExpiration()).isEqualTo(0); + } + + @Test + void doesNotAllowSimpleType1() { + assertThrows(MappingException.class, () -> converter.write("hello", new CouchbaseDocument())); + } + + @Test + void doesNotAllowSimpleType2() { + assertThrows(MappingException.class, () -> converter.write(true, new CouchbaseDocument())); + } + + @Test + void doesNotAllowSimpleType3() { + assertThrows(MappingException.class, () -> converter.write(42, new CouchbaseDocument())); + } + + @Test + void needsIDOnEntity() { + assertThrows(MappingException.class, () -> converter.write(new EntityWithoutID("foo"), new CouchbaseDocument())); + } + + @Test + void writesString() { + CouchbaseDocument converted = new CouchbaseDocument(); + StringEntity entity = new StringEntity("foobar"); + + converter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); + assertThat(result.get("attr0")).isEqualTo("foobar"); + assertThat(converted.getId()).isEqualTo(BaseEntity.ID); + } + + @Test + void readsString() { + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", StringEntity.class.getName()); + source.put("attr0", "foobar"); + + StringEntity converted = converter.read(StringEntity.class, source); + assertThat(converted.attr0).isEqualTo("foobar"); + } + + @Test + void writesNumber() { + CouchbaseDocument converted = new CouchbaseDocument(); + NumberEntity entity = new NumberEntity(42); + + converter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); + assertThat(result.get("attr0")).isEqualTo(42L); + assertThat(converted.getId()).isEqualTo(BaseEntity.ID); + } + + @Test + void readsNumber() { + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", NumberEntity.class.getName()); + source.put("attr0", 42); + + NumberEntity converted = converter.read(NumberEntity.class, source); + assertThat(converted.attr0).isEqualTo(42); + } + + @Test + void writesBoolean() { + CouchbaseDocument converted = new CouchbaseDocument(); + BooleanEntity entity = new BooleanEntity(true); + + converter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); + assertThat(result.get("attr0")).isEqualTo(true); + assertThat(converted.getId()).isEqualTo("mockid"); + } + + @Test + void readsBoolean() { + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", BooleanEntity.class.getName()); + source.put("attr0", true); + + BooleanEntity converted = converter.read(BooleanEntity.class, source); + assertThat(converted.attr0).isTrue(); + } + + @Test + void writesMixedSimpleTypes() { + CouchbaseDocument converted = new CouchbaseDocument(); + MixedSimpleEntity entity = new MixedSimpleEntity("a", 5, -0.3, true); + + converter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); + assertThat(result.get("attr0")).isEqualTo("a"); + assertThat(result.get("attr1")).isEqualTo(5); + assertThat(result.get("attr2")).isEqualTo(-0.3); + assertThat(result.get("attr3")).isEqualTo(true); + } + + @Test + void readsMixedSimpleTypes() { + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", MixedSimpleEntity.class.getName()); + source.put("attr0", "a"); + source.put("attr1", 5); + source.put("attr2", -0.3); + source.put("attr3", true); + + MixedSimpleEntity converted = converter.read(MixedSimpleEntity.class, source); + assertThat(converted.attr0).isEqualTo("a"); + assertThat(converted.attr1).isEqualTo(5); + assertThat(converted.attr2).isCloseTo(-0.3, offset(0.0)); + assertThat(converted.attr3).isTrue(); + } + + @Test + void readsID() { + CouchbaseDocument document = new CouchbaseDocument("001"); + User user = converter.read(User.class, document); + assertThat(user.getId()).isEqualTo("001"); + } + + @Test + void writesUninitializedValues() { + CouchbaseDocument converted = new CouchbaseDocument(); + UninitializedEntity entity = new UninitializedEntity(); + + converter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); + assertThat(result.get("attr1")).isEqualTo(0); + } + + @Test + void readsUninitializedValues() { + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", UninitializedEntity.class.getName()); + source.put("attr1", 0); + + UninitializedEntity converted = converter.read(UninitializedEntity.class, source); + assertThat(converted.attr0).isNull(); + assertThat(converted.attr1).isEqualTo(0); + assertThat(converted.attr2).isNull(); + } + + @Test + void writesAndReadsMapsAndNestedMaps() { + CouchbaseDocument converted = new CouchbaseDocument(); + + Map attr0 = new HashMap<>(); + Map attr1 = new TreeMap<>(); + Map attr2 = new LinkedHashMap<>(); + Map> attr3 = new HashMap<>(); + + attr0.put("foo", "bar"); + attr1.put("bar", true); + attr3.put("hashmap", attr0); + + MapEntity entity = new MapEntity(attr0, attr1, attr2, attr3); + + converter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("attr0")).isEqualTo(attr0); + assertThat(result.get("attr1")).isEqualTo(attr1); + assertThat(result.get("attr2")).isEqualTo(attr2); + assertThat(result.get("attr3")).isEqualTo(attr3); + + CouchbaseDocument cattr0 = new CouchbaseDocument(); + cattr0.put("foo", "bar"); + + CouchbaseDocument cattr1 = new CouchbaseDocument(); + cattr1.put("bar", true); + + CouchbaseDocument cattr2 = new CouchbaseDocument(); + + CouchbaseDocument cattr3 = new CouchbaseDocument(); + cattr3.put("hashmap", cattr0); + + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", MapEntity.class.getName()); + source.put("attr0", cattr0); + source.put("attr1", cattr1); + source.put("attr2", cattr2); + source.put("attr3", cattr3); + + MapEntity readConverted = converter.read(MapEntity.class, source); + assertThat(readConverted.attr0).isEqualTo(attr0); + assertThat(readConverted.attr1).isEqualTo(attr1); + assertThat(readConverted.attr2).isEqualTo(attr2); + assertThat(readConverted.attr3).isEqualTo(attr3); + } + + @Test + void writesAndReadsListAndNestedList() { + CouchbaseDocument converted = new CouchbaseDocument(); + List attr0 = new ArrayList<>(); + List attr1 = new LinkedList<>(); + List> attr2 = new ArrayList<>(); + + attr0.add("foo"); + attr0.add("bar"); + attr2.add(attr0); + + ListEntity entity = new ListEntity(attr0, attr1, attr2); + + converter.write(entity, converted); + Map result = converted.export(); + assertThat(result.get("attr0")).isEqualTo(attr0); + assertThat(result.get("attr1")).isEqualTo(attr1); + assertThat(result.get("attr2")).isEqualTo(attr2); + + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", ListEntity.class.getName()); + CouchbaseList cattr0 = new CouchbaseList(); + cattr0.put("foo"); + cattr0.put("bar"); + CouchbaseList cattr1 = new CouchbaseList(); + CouchbaseList cattr2 = new CouchbaseList(); + cattr2.put(cattr0); + source.put("attr0", cattr0); + source.put("attr1", cattr1); + source.put("attr2", cattr2); + + ListEntity readConverted = converter.read(ListEntity.class, source); + assertThat(readConverted.attr0.size()).isEqualTo(2); + assertThat(readConverted.attr1.size()).isEqualTo(0); + assertThat(readConverted.attr2.size()).isEqualTo(1); + assertThat(readConverted.attr2.get(0).size()).isEqualTo(2); + } + + @Test + void writesAndReadsSetAndNestedSet() { + CouchbaseDocument converted = new CouchbaseDocument(); + Set attr0 = new HashSet<>(); + TreeSet attr1 = new TreeSet<>(); + Set> attr2 = new HashSet<>(); + + attr0.add("foo"); + attr0.add("bar"); + attr2.add(attr0); + + SetEntity entity = new SetEntity(attr0, attr1, attr2); + + converter.write(entity, converted); + Map result = converted.export(); + assertThat(((Collection) result.get("attr0")).size()).isEqualTo(attr0.size()); + assertThat(((Collection) result.get("attr1")).size()).isEqualTo(attr1.size()); + assertThat(((Collection) result.get("attr2")).size()).isEqualTo(attr2.size()); + + CouchbaseList cattr0 = new CouchbaseList(); + cattr0.put("foo"); + cattr0.put("bar"); + + CouchbaseList cattr1 = new CouchbaseList(); + + CouchbaseList cattr2 = new CouchbaseList(); + cattr2.put(cattr0); + + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", SetEntity.class.getName()); + source.put("attr0", cattr0); + source.put("attr1", cattr1); + source.put("attr2", cattr2); + + SetEntity readConverted = converter.read(SetEntity.class, source); + assertThat(readConverted.attr0).isEqualTo(attr0); + assertThat(readConverted.attr1).isEqualTo(attr1); + assertThat(readConverted.attr2).isEqualTo(attr2); + } + + @Test + void writesAndReadsValueClass() { + CouchbaseDocument converted = new CouchbaseDocument(); + + final String email = "foo@bar.com"; + final Email addy = new Email(email); + List listOfEmails = new ArrayList(); + listOfEmails.add(addy); + + ValueEntity entity = new ValueEntity(addy, listOfEmails); + converter.write(entity, converted); + Map result = converted.export(); + + assertThat(result.get("_class")).isEqualTo(entity.getClass().getName()); + assertThat(result.get("email")).isEqualTo(new HashMap() { + { + put("emailAddr", email); + } + }); + + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", ValueEntity.class.getName()); + CouchbaseDocument emailDoc = new CouchbaseDocument(); + emailDoc.put("emailAddr", "foo@bar.com"); + source.put("email", emailDoc); + CouchbaseList listOfEmailsDoc = new CouchbaseList(); + listOfEmailsDoc.put(emailDoc); + source.put("listOfEmails", listOfEmailsDoc); + + ValueEntity readConverted = converter.read(ValueEntity.class, source); + assertThat(readConverted.email.emailAddr).isEqualTo(addy.emailAddr); + assertThat(readConverted.listOfEmails.get(0).emailAddr).isEqualTo(listOfEmails.get(0).emailAddr); + } + + @Test + void writesAndReadsCustomConvertedClass() { + List converters = new ArrayList<>(); + converters.add(BigDecimalToStringConverter.INSTANCE); + converters.add(StringToBigDecimalConverter.INSTANCE); + converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + converter.afterPropertiesSet(); + + CouchbaseDocument converted = new CouchbaseDocument(); + + final String valueStr = "12.345"; + final BigDecimal value = new BigDecimal(valueStr); + final String value2Str = "0.6789"; + final BigDecimal value2 = new BigDecimal(value2Str); + List listOfValues = new ArrayList<>(); + listOfValues.add(value); + listOfValues.add(value2); + Map mapOfValues = new HashMap<>(); + mapOfValues.put("val1", value); + mapOfValues.put("val2", value2); + + CustomEntity entity = new CustomEntity(value, listOfValues, mapOfValues); + converter.write(entity, converted); + + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", CustomEntity.class.getName()); + source.put("value", valueStr); + CouchbaseList listOfValuesDoc = new CouchbaseList(); + listOfValuesDoc.put(valueStr); + listOfValuesDoc.put(value2Str); + source.put("listOfValues", listOfValuesDoc); + CouchbaseDocument mapOfValuesDoc = new CouchbaseDocument(); + mapOfValuesDoc.put("val1", valueStr); + mapOfValuesDoc.put("val2", value2Str); + source.put("mapOfValues", mapOfValuesDoc); + + assertThat(valueStr).isEqualTo(((CouchbaseList) converted.getPayload().get("listOfValues")).get(0)); + assertThat(value2Str).isEqualTo(((CouchbaseList) converted.getPayload().get("listOfValues")).get(1)); + assertThat(converted.export().toString()).isEqualTo(source.export().toString()); + + CustomEntity readConverted = converter.read(CustomEntity.class, source); + assertThat(readConverted.value).isEqualTo(value); + assertThat(readConverted.listOfValues.get(0)).isEqualTo(listOfValues.get(0)); + assertThat(readConverted.listOfValues.get(1)).isEqualTo(listOfValues.get(1)); + assertThat(readConverted.mapOfValues.get("val1")).isEqualTo(mapOfValues.get("val1")); + assertThat(readConverted.mapOfValues.get("val2")).isEqualTo(mapOfValues.get("val2")); + } + + @Test + void writesAndReadsClassContainingCustomConvertedObjects() { + List converters = new ArrayList<>(); + converters.add(BigDecimalToStringConverter.INSTANCE); + converters.add(StringToBigDecimalConverter.INSTANCE); + converter.setCustomConversions(new CouchbaseCustomConversions(converters)); + converter.afterPropertiesSet(); + + CouchbaseDocument converted = new CouchbaseDocument(); + + final String weightStr = "12.34"; + final BigDecimal weight = new BigDecimal(weightStr); + final CustomObject addy = new CustomObject(weight); + List listOfObjects = new ArrayList<>(); + listOfObjects.add(addy); + Map mapOfObjects = new HashMap<>(); + mapOfObjects.put("obj0", addy); + mapOfObjects.put("obj1", addy); + + CustomObjectEntity entity = new CustomObjectEntity(addy, listOfObjects, mapOfObjects); + converter.write(entity, converted); + + CouchbaseDocument source = new CouchbaseDocument(); + source.put("_class", CustomObjectEntity.class.getName()); + CouchbaseDocument objectDoc = new CouchbaseDocument(); + objectDoc.put("weight", weightStr); + source.put("object", objectDoc); + CouchbaseList listOfObjectsDoc = new CouchbaseList(); + listOfObjectsDoc.put(objectDoc); + source.put("listOfObjects", listOfObjectsDoc); + CouchbaseDocument mapOfObjectsDoc = new CouchbaseDocument(); + mapOfObjectsDoc.put("obj0", objectDoc); + mapOfObjectsDoc.put("obj1", objectDoc); + source.put("mapOfObjects", mapOfObjectsDoc); + assertThat(converted.export().toString()).isEqualTo(source.export().toString()); + + CustomObjectEntity readConverted = converter.read(CustomObjectEntity.class, source); + assertThat(readConverted.object.weight).isEqualTo(addy.weight); + assertThat(readConverted.listOfObjects.get(0).weight).isEqualTo(listOfObjects.get(0).weight); + assertThat(readConverted.mapOfObjects.get("obj0").weight).isEqualTo(mapOfObjects.get("obj0").weight); + assertThat(readConverted.mapOfObjects.get("obj1").weight).isEqualTo(mapOfObjects.get("obj1").weight); + } + + @Test + void writesAndReadsDates() { + Date created = new Date(); + Calendar modified = Calendar.getInstance(); + LocalDateTime deleted = LocalDateTime.now(); + DateEntity entity = new DateEntity(created, modified, deleted); + + CouchbaseDocument converted = new CouchbaseDocument(); + converter.write(entity, converted); + assertThat(converted.getPayload().get("created")).isEqualTo(created.getTime()); + assertThat(converted.getPayload().get("modified")).isEqualTo(modified.getTimeInMillis() / 1000); + LocalDateTimeToLongConverter localDateTimeToDateconverter = LocalDateTimeToLongConverter.INSTANCE; + assertThat(converted.getPayload().get("deleted")).isEqualTo(localDateTimeToDateconverter.convert(deleted)); + + DateEntity read = converter.read(DateEntity.class, converted); + assertThat(read.created.getTime()).isEqualTo(created.getTime()); + assertThat(read.modified.getTimeInMillis() / 1000).isEqualTo(modified.getTimeInMillis() / 1000); + assertThat(read.deleted.truncatedTo(ChronoUnit.MILLIS)).isEqualTo(deleted.truncatedTo(ChronoUnit.MILLIS)); + } + + @WritingConverter + public enum BigDecimalToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(BigDecimal source) { + return source.toPlainString(); + } + } + + @ReadingConverter + public enum StringToBigDecimalConverter implements Converter { + INSTANCE; + + @Override + public BigDecimal convert(String source) { + return new BigDecimal(source); + } + } + + static class EntityWithoutID { + + private String attr0; + + public EntityWithoutID(String a0) { + attr0 = a0; + } + } + + static class BaseEntity { + public static final String ID = "mockid"; + @Id private String id = ID; + } + + static class StringEntity extends BaseEntity { + private String attr0; + + public StringEntity(String attr0) { + this.attr0 = attr0; + } + } + + static class NumberEntity extends BaseEntity { + private long attr0; + + public NumberEntity(long attr0) { + this.attr0 = attr0; + } + } + + static class BooleanEntity extends BaseEntity { + private boolean attr0; + + public BooleanEntity(boolean attr0) { + this.attr0 = attr0; + } + } + + static class MixedSimpleEntity extends BaseEntity { + private String attr0; + private int attr1; + private double attr2; + private boolean attr3; + + public MixedSimpleEntity(String attr0, int attr1, double attr2, boolean attr3) { + this.attr0 = attr0; + this.attr1 = attr1; + this.attr2 = attr2; + this.attr3 = attr3; + } + } + + static class UninitializedEntity extends BaseEntity { + private String attr0 = null; + private int attr1; + private Integer attr2; + } + + static class MapEntity extends BaseEntity { + private Map attr0; + private Map attr1; + private Map attr2; + private Map> attr3; + + public MapEntity(Map attr0, Map attr1, Map attr2, + Map> attr3) { + this.attr0 = attr0; + this.attr1 = attr1; + this.attr2 = attr2; + this.attr3 = attr3; + } + } + + static class ListEntity extends BaseEntity { + private List attr0; + private List attr1; + private List> attr2; + + ListEntity(List attr0, List attr1, List> attr2) { + this.attr0 = attr0; + this.attr1 = attr1; + this.attr2 = attr2; + } + } + + static class SetEntity extends BaseEntity { + private Set attr0; + private Set attr1; + private Set> attr2; + + SetEntity(Set attr0, Set attr1, Set> attr2) { + this.attr0 = attr0; + this.attr1 = attr1; + this.attr2 = attr2; + } + } + + static class ValueEntity extends BaseEntity { + private Email email; + private List listOfEmails; + + public ValueEntity(Email email, List listOfEmails) { + this.email = email; + this.listOfEmails = listOfEmails; + } + } + + static class Email { + private String emailAddr; + + public Email(String emailAddr) { + this.emailAddr = emailAddr; + } + } + + static class CustomEntity extends BaseEntity { + private BigDecimal value; + private List listOfValues; + private Map mapOfValues; + + public CustomEntity(BigDecimal value, List listOfValues, Map mapOfValues) { + this.value = value; + this.listOfValues = listOfValues; + this.mapOfValues = mapOfValues; + } + } + + static class CustomObjectEntity extends BaseEntity { + private CustomObject object; + private List listOfObjects; + private Map mapOfObjects; + + public CustomObjectEntity(CustomObject object, List listOfObjects, + Map mapOfObjects) { + this.object = object; + this.listOfObjects = listOfObjects; + this.mapOfObjects = mapOfObjects; + } + } + + static class CustomObject { + private BigDecimal weight; + + public CustomObject(BigDecimal weight) { + this.weight = weight; + } + } + + static class DateEntity extends BaseEntity { + private Date created; + private Calendar modified; + private LocalDateTime deleted; + + public DateEntity(Date created, Calendar modified, LocalDateTime deleted) { + this.created = created; + this.modified = modified; + this.deleted = deleted; + } + } + } diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/TestUUID.java b/src/test/java/org/springframework/data/couchbase/core/mapping/TestUUID.java deleted file mode 100644 index 6ab7d425..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/TestUUID.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core.mapping; - -import java.util.UUID; -import com.couchbase.client.java.repository.annotation.Id; -import org.springframework.data.couchbase.core.query.ViewIndexed; - -/** - * @author Subhashni Balakrishnan - */ -@ViewIndexed(designDoc = "testUUID", viewName = "all") -public class TestUUID { - @Id - public UUID id; -} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/TestUUIDRepository.java b/src/test/java/org/springframework/data/couchbase/core/mapping/TestUUIDRepository.java deleted file mode 100644 index be5a9062..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/TestUUIDRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core.mapping; - -import java.util.UUID; - -import org.springframework.data.couchbase.repository.CouchbaseRepository; - -/** - * @author Subhashni Balakrishnan - */ -public interface TestUUIDRepository extends CouchbaseRepository { -} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListenerTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListenerTests.java deleted file mode 100644 index 8e2f9325..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/event/AbstractCouchbaseEventListenerTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core.mapping.event; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Michael Nitschinger - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = EventContextConfiguration.class) -@TestExecutionListeners({DependencyInjectionTestExecutionListener.class}) -public class AbstractCouchbaseEventListenerTests { - - @Autowired - private CouchbaseTemplate couchbaseTemplate; - - @Autowired - private SimpleMappingEventListener eventListener; - - @Test - public void shouldEmitEvents() { - int beforeSave = eventListener.onBeforeSaveEvents.size(); - int afterSave = eventListener.onAfterSaveEvents.size(); - int beforeConvert = eventListener.onBeforeConvertEvents.size(); - - couchbaseTemplate.save(new User("john smith", 18)); - - assertThat(eventListener.onBeforeSaveEvents.size()).isEqualTo(beforeSave + 1); - assertThat(eventListener.onAfterSaveEvents.size()).isEqualTo(afterSave + 1); - assertThat(eventListener.onBeforeConvertEvents.size()).isEqualTo(beforeConvert + 1); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/EventContextConfiguration.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/EventContextConfiguration.java deleted file mode 100644 index 87580df4..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/event/EventContextConfiguration.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core.mapping.event; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.UnitTestApplicationConfig; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; - -/** - * @author Michael Nitschinger - */ -@Configuration -public class EventContextConfiguration extends UnitTestApplicationConfig { - - @Bean - public LocalValidatorFactoryBean validator() { - return new LocalValidatorFactoryBean(); - } - - @Bean - public ValidatingCouchbaseEventListener validatingCouchbaseEventListener() { - return new ValidatingCouchbaseEventListener(validator()); - } - - @Bean - public SimpleMappingEventListener simpleMappingEventListener() { - return new SimpleMappingEventListener(); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/SimpleMappingEventListener.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/SimpleMappingEventListener.java deleted file mode 100644 index 735863e5..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/event/SimpleMappingEventListener.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core.mapping.event; - -import java.util.ArrayList; - -import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; - -/** - * @author Michael Nitschinger - */ -public class SimpleMappingEventListener extends AbstractCouchbaseEventListener { - - public final ArrayList> onBeforeConvertEvents = new ArrayList>(); - public final ArrayList> onBeforeSaveEvents = new ArrayList>(); - public final ArrayList> onAfterSaveEvents = new ArrayList>(); - - @Override - public void onBeforeConvert(Object source) { - onBeforeConvertEvents.add(new BeforeConvertEvent(source)); - } - - @Override - public void onBeforeSave(Object source, CouchbaseDocument doc) { - onBeforeSaveEvents.add(new BeforeSaveEvent(source, doc)); - } - - @Override - public void onAfterSave(Object source, CouchbaseDocument doc) { - onAfterSaveEvents.add(new AfterSaveEvent(source, doc)); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/User.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/User.java deleted file mode 100644 index 87eb9d85..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/event/User.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core.mapping.event; - -import javax.validation.constraints.Min; -import javax.validation.constraints.Size; - -import org.springframework.data.annotation.Id; - -/** - * @author Michael Nitschinger - */ -public class User { - - @Id - private String id; - - @Size(min = 10) - private String name; - - @Min(18) - private Integer age; - - public User(String name, Integer age) { - id = "id"; - this.name = name; - this.age = age; - } - - public String getName() { - return name; - } - - public Integer getAge() { - return age; - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListenerTests.java b/src/test/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListenerTests.java deleted file mode 100644 index a584801f..00000000 --- a/src/test/java/org/springframework/data/couchbase/core/mapping/event/ValidatingCouchbaseEventListenerTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.core.mapping.event; - -import javax.validation.ConstraintViolationException; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author Michael Nitschinger - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = EventContextConfiguration.class) -public class ValidatingCouchbaseEventListenerTests { - - @Autowired - CouchbaseTemplate template; - - @Test - public void shouldThrowConstraintViolationException() { - User user = new User("john", 17); - - try { - template.save(user); - fail("Expected ConstraintViolationException"); - } - catch (ConstraintViolationException e) { - assertThat(e.getConstraintViolations().size()).isEqualTo(2); - } - } - - @Test - public void shouldNotThrowAnyExceptions() { - template.save(new User("john smith", 18)); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java b/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java new file mode 100644 index 00000000..6022d13d --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core.query; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.data.couchbase.core.query.QueryCriteria.*; + +class QueryCriteriaTests { + + @Test + void testSimpleCriteria() { + QueryCriteria c = where("name").is("Bubba"); + assertEquals("`name` = \"Bubba\"", c.export()); + } + + @Test + public void testNullValue() { + QueryCriteria c = where("name").is(null); + assertEquals("`name` = null", c.export()); + } + + @Test + void testSimpleNumber() { + QueryCriteria c = where("name").is(5); + assertEquals("`name` = 5", c.export()); + } + + @Test + void testNotEqualCriteria() { + QueryCriteria c = where("name").ne("Bubba"); + assertEquals("`name` != \"Bubba\"", c.export()); + } + + @Test + void testChainedCriteria() { + QueryCriteria c = where("name").is("Bubba").and("age").lt(21).or("country").is("Austria"); + assertEquals("`name` = \"Bubba\" and `age` < 21 or `country` = \"Austria\"", c.export()); + } + + @Test + void testNestedAndCriteria() { + QueryCriteria c = where("name").is("Bubba").and(where("age").gt(12).or("country").is("Austria")); + assertEquals("`name` = \"Bubba\" and (`age` > 12 or `country` = \"Austria\")", c.export()); + } + + @Test + void testNestedOrCriteria() { + QueryCriteria c = where("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria")); + assertEquals("`name` = \"Bubba\" or (`age` > 12 or `country` = \"Austria\")", c.export()); + } + +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/Person.java b/src/test/java/org/springframework/data/couchbase/domain/Airport.java similarity index 57% rename from src/test/java/org/springframework/data/couchbase/repository/cdi/Person.java rename to src/test/java/org/springframework/data/couchbase/domain/Airport.java index e4611d11..e7a9608c 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/Person.java +++ b/src/test/java/org/springframework/data/couchbase/domain/Airport.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2017-2019 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. @@ -14,40 +14,36 @@ * limitations under the License. */ -package org.springframework.data.couchbase.repository.cdi; +package org.springframework.data.couchbase.domain; import org.springframework.data.annotation.Id; -import com.couchbase.client.java.repository.annotation.Field; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.couchbase.core.mapping.Document; -/** - * @author Mark Paluch - */ -public class Person { +@Document +public class Airport { + @Id String id; - @Id private String id; + String iata; - @Field private String name; + String icao; - public Person() {} - - public Person(String id, String name) { + @PersistenceConstructor + public Airport(String id, String iata, String icao) { this.id = id; - this.name = name; + this.iata = iata; + this.icao = icao; } public String getId() { return id; } - public void setId(String id) { - this.id = id; + public String getIata() { + return iata; } - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; + public String getIcao() { + return icao; } } diff --git a/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java new file mode 100644 index 00000000..61289143 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/AirportRepository.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.domain; + +import org.springframework.data.couchbase.repository.ScanConsistency; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import com.couchbase.client.java.query.QueryScanConsistency; + +import java.util.List; + +@Repository +public interface AirportRepository extends PagingAndSortingRepository { + + @Override + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Iterable findAll(); + + List findAllByIata(String iata); + +} diff --git a/src/test/java/org/springframework/data/couchbase/domain/User.java b/src/test/java/org/springframework/data/couchbase/domain/User.java new file mode 100644 index 00000000..595b06df --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/User.java @@ -0,0 +1,72 @@ +/* + * 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.domain; + +import java.util.Objects; + +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.couchbase.core.mapping.Document; + +@Document +public class User { + + @Id private String id; + + private String firstname; + private String lastname; + + @PersistenceConstructor + public User(final String id, final String firstname, final String lastname) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + } + + public String getId() { + return id; + } + + public String getFirstname() { + return firstname; + } + + public String getLastname() { + return lastname; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + User user = (User) o; + return Objects.equals(id, user.id) && Objects.equals(firstname, user.firstname) + && Objects.equals(lastname, user.lastname); + } + + @Override + public int hashCode() { + return Objects.hash(id, firstname, lastname); + } + + @Override + public String toString() { + return "User{" + "id='" + id + '\'' + ", firstname='" + firstname + '\'' + ", lastname='" + lastname + '\'' + '}'; + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/CouchbasePagingAndSortingRepository.java b/src/test/java/org/springframework/data/couchbase/domain/UserRepository.java similarity index 62% rename from src/main/java/org/springframework/data/couchbase/repository/CouchbasePagingAndSortingRepository.java rename to src/test/java/org/springframework/data/couchbase/domain/UserRepository.java index 0e9c4e55..923e678b 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/CouchbasePagingAndSortingRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/UserRepository.java @@ -14,18 +14,19 @@ * limitations under the License. */ -package org.springframework.data.couchbase.repository; - -import java.io.Serializable; +package org.springframework.data.couchbase.domain; import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface UserRepository extends PagingAndSortingRepository { + + List findByFirstname(String firstname); + + List findByFirstnameAndLastname(String firstname, String lastname); + -/** - * Couchbase specific {@link org.springframework.data.repository.Repository} interface that is - * a {@link PagingAndSortingRepository}. - * - * @author Simon Baslé - */ -public interface CouchbasePagingAndSortingRepository - extends CouchbaseRepository, PagingAndSortingRepository { } diff --git a/src/test/java/org/springframework/data/couchbase/monitor/ClientInfoIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/monitor/ClientInfoIntegrationTests.java deleted file mode 100644 index 59f68d7c..00000000 --- a/src/test/java/org/springframework/data/couchbase/monitor/ClientInfoIntegrationTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-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.monitor; - - -import com.couchbase.client.java.Bucket; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.test.context.ContextConfiguration; - -import static org.assertj.core.api.Assertions.*; - -/** - * @author Michael Nitschinger - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -public class ClientInfoIntegrationTests { - - /** - * Contains a reference to the actual CouchbaseClient. - */ - @Autowired - private Bucket client; - - private ClientInfo ci; - - @Before - public void setup() throws Exception { - ci = new ClientInfo(client); - } - - @Test - public void hostNames() { - String hostnames = ci.getHostNames(); - assertThat(hostnames).isNotEmpty(); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/monitor/ClusterInfoIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/monitor/ClusterInfoIntegrationTests.java deleted file mode 100644 index 45c90066..00000000 --- a/src/test/java/org/springframework/data/couchbase/monitor/ClusterInfoIntegrationTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2013-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.monitor; - - -import com.couchbase.client.java.Bucket; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.*; - -/** - * @author Michael Nitschinger - */ -@Ignore(value = "Cant run get cluster info on test container") -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -public class ClusterInfoIntegrationTests { - - /** - * Contains a reference to the actual CouchbaseClient. - */ - @Autowired - private Bucket client; - - private ClusterInfo ci; - - @Before - public void setup() throws Exception { - ci = new ClusterInfo(client); - } - - @Test - public void totalDiskAssigned() { - assertThat(ci.getTotalDiskAssigned()).isGreaterThan(0); - } - - @Test - public void totalRAMUsed() { - assertThat(ci.getTotalRAMUsed()).isGreaterThan(0); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseIdGenerationIntegrationTests.java deleted file mode 100644 index 6e3a04d2..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseIdGenerationIntegrationTests.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.repository; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.annotation.Id; - -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.core.mapping.id.GeneratedValue; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; -import org.springframework.test.context.ContextConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.UNIQUE; - -/** - * @author Maxence Labusquiere - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -public class CouchbaseIdGenerationIntegrationTests { - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - private CrudRepository entityRepository; - - @Before - public void setUp() throws Exception { - entityRepository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(EntityRepository.class); - } - - @Test - public void idFieldEntityIsFillWithGeneratedValueOnSave() { - SimpleClassWithGeneratedIdValueUsingUUID entity = new SimpleClassWithGeneratedIdValueUsingUUID(); - SimpleClassWithGeneratedIdValueUsingUUID savedEntity = entityRepository.save(entity); - assertThat(savedEntity.id != null).as("Expected generated value").isTrue(); - if (entityRepository.existsById(savedEntity.id)) { - entityRepository.existsById(savedEntity.id); - } - } - - - @Test - public void ifIdFieldIsAlreadySetNothingIsDone() { - String id = "AnId"; - SimpleClassWithGeneratedIdValueUsingUUID entity = new SimpleClassWithGeneratedIdValueUsingUUID(); - entity.setId(id); - SimpleClassWithGeneratedIdValueUsingUUID savedEntity = entityRepository.save(entity); - assertThat(savedEntity.id == id).as("Expected same id instance").isTrue(); - if (entityRepository.existsById(savedEntity.id)) { - entityRepository.existsById(savedEntity.id); - } - } - - @Document - static class SimpleClassWithGeneratedIdValueUsingUUID { - @Id - @GeneratedValue(strategy = UNIQUE) - public String id; - - public String value = "new"; - - public void setId(String id) { - this.id = id; - } - } - - @Repository - interface EntityRepository extends CrudRepository { - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryKeyValueIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryKeyValueIntegrationTests.java new file mode 100644 index 00000000..01b9cb50 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryKeyValueIntegrationTests.java @@ -0,0 +1,80 @@ +/* + * 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.repository; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Optional; +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.domain.User; +import org.springframework.data.couchbase.domain.UserRepository; +import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +@SpringJUnitConfig(CouchbaseRepositoryKeyValueIntegrationTests.Config.class) +public class CouchbaseRepositoryKeyValueIntegrationTests extends ClusterAwareIntegrationTests { + + @Autowired UserRepository userRepository; + + @Test + void saveAndFindById() { + User user = new User(UUID.randomUUID().toString(), "f", "l"); + + assertFalse(userRepository.existsById(user.getId())); + + userRepository.save(user); + + Optional found = userRepository.findById(user.getId()); + assertTrue(found.isPresent()); + found.ifPresent(u -> assertEquals(user, u)); + + assertTrue(userRepository.existsById(user.getId())); + } + + @Configuration + @EnableCouchbaseRepositories("org.springframework.data.couchbase") + static class Config extends AbstractCouchbaseConfiguration { + + @Override + public String getConnectionString() { + return connectionString(); + } + + @Override + public String getUserName() { + return config().adminUsername(); + } + + @Override + public String getPassword() { + return config().adminPassword(); + } + + @Override + public String getBucketName() { + return bucketName(); + } + + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java new file mode 100644 index 00000000..8c2b0fde --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryQueryIntegrationTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017-2019 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.repository; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.CouchbaseClientFactory; +import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.domain.Airport; +import org.springframework.data.couchbase.domain.AirportRepository; +import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import com.couchbase.client.core.error.IndexExistsException; + +@SpringJUnitConfig(CouchbaseRepositoryQueryIntegrationTests.Config.class) +public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegrationTests { + + @Autowired CouchbaseClientFactory clientFactory; + + @Autowired AirportRepository airportRepository; + + @BeforeEach + void beforeEach() { + try { + clientFactory.getCluster().queryIndexes().createPrimaryIndex(bucketName()); + } catch (IndexExistsException ex) { + // ignore, all good. + } + } + + @Test + void shouldSaveAndFindAll() { + Airport vie = new Airport("airports::vie", "vie", "loww"); + airportRepository.save(vie); + + List all = StreamSupport.stream(airportRepository.findAll().spliterator(), false) + .collect(Collectors.toList()); + + assertFalse(all.isEmpty()); + assertTrue(all.stream().anyMatch(a -> a.getId().equals("airports::vie"))); + } + + @Test + void findBySimpleProperty() { + List airports = airportRepository.findAllByIata("vie"); + // TODO + System.err.println(airports); + } + + @Configuration + @EnableCouchbaseRepositories("org.springframework.data.couchbase") + static class Config extends AbstractCouchbaseConfiguration { + + @Override + public String getConnectionString() { + return connectionString(); + } + + @Override + public String getUserName() { + return config().adminUsername(); + } + + @Override + public String getPassword() { + return config().adminPassword(); + } + + @Override + public String getBucketName() { + return bucketName(); + } + + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewIntegrationTests.java deleted file mode 100644 index 8798a19c..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewIntegrationTests.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.view.Stale; -import com.couchbase.client.java.view.ViewQuery; -import com.couchbase.client.java.view.ViewResult; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Matchers; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.mapping.PropertyReferenceException; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @author David Harrigan - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(CouchbaseRepositoryViewListener.class) -public class CouchbaseRepositoryViewIntegrationTests { - - @Autowired - private Bucket client; - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private CustomUserRepository repository; - - @Before - public void setup() throws Exception { - repository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(CustomUserRepository.class); - } - - @Test - public void shouldFindAllWithCustomView() { - client.query(ViewQuery.from("user", "customFindAllView").stale(Stale.FALSE)); - Iterable allUsers = repository.findAll(); - assertThat(allUsers).hasSize(100); - } - - @Test - public void shouldCountWithCustomView() { - ViewResult clientResult = client.query(ViewQuery.from("userCustom", "customCountView") - .reduce().stale(Stale.FALSE)); - final Object clientRowValue = clientResult.allRows().get(0).value(); - final long value = repository.count(); - assertThat(value).isEqualTo(100L); - assertThat(clientRowValue).isInstanceOf(Number.class); - assertThat(((Number) clientRowValue).longValue()).isEqualTo(value); - } - - @Test - public void shouldDetectMethodNameWithoutPropertyAndIssueGenericQueryOnView() { - Iterable users = repository.findRandomMethodName(); - assertThat(users).isNotNull(); - assertThat(users.iterator().hasNext()).isTrue(); - - try { - repository.findIncorrectExplicitView(); - fail("Expected InvalidDataAccessResourceException"); - } catch (InvalidDataAccessResourceUsageException e) { - assertThat(e.getMessage().startsWith("View user/allSomething does not exist")) - .as(e.getMessage()).isTrue(); - } - } - - @Test(expected = PropertyReferenceException.class) - public void shouldFailDeriveOnBadProperty() { - repository.findAllByUsernameEqualAndUserblablaIs("uname-1", "blabla"); - } - - @Test - public void shouldDeriveViewParametersAndReduce() { - long count = repository.countByUsernameGreaterThanEqualAndUsernameLessThan("uname-8", "uname-9"); - assertThat(count).isEqualTo(12); - } - - @Test - public void shouldDeriveViewParametersAndReduceNonNumerical() { - JsonObject reduceResult = repository.findByAgeLessThan(50); - - assertThat(reduceResult).isNotNull(); - assertThat((long) reduceResult.getLong("count")).isEqualTo(51); - assertThat((long) reduceResult.getLong("max")).isEqualTo(50); - assertThat((long) reduceResult.getLong("min")).isEqualTo(0); - assertThat((long) reduceResult.getLong("sum")).isEqualTo(1275); - } - - @Test - public void shouldDeriveViewParameters() { - String lowKey = "uname-1"; - String middleKey = "uname-10"; - String highKey = "uname-11"; - List keys = Arrays.asList(lowKey, middleKey, highKey); - - User u1 = repository.findByUsernameIs(lowKey).get(0); - User u2 = repository.findByUsernameIs(middleKey).get(0); - User u3 = repository.findByUsernameIs(highKey).get(0); - - assertThat(u1.getUsername()).isEqualTo(lowKey); - assertThat(u2.getUsername()).isEqualTo(middleKey); - assertThat(u3.getUsername()).isEqualTo(highKey); - - List in = repository.findAllByUsernameIn(keys); - List gteLte = repository.findByUsernameGreaterThanEqualAndUsernameLessThanEqual(lowKey, highKey); - List between = repository.findByUsernameBetween(lowKey, highKey); - List gteLimited = repository.findTop3ByUsernameGreaterThanEqual(lowKey); - - // the results are unordered, so compare using Set - Set expected = new HashSet<>(Arrays.asList(u1, u2, u3)); - assertThat(new HashSet<>(in)).isEqualTo(expected); - assertThat(new HashSet<>(gteLte)).isEqualTo(expected); - assertThat(new HashSet<>(between)).isEqualTo(expected); - assertThat(new HashSet<>(gteLimited)).isEqualTo(expected); - } - - @Test - public void shouldDeriveToEmptyClause() { - List users = repository.findAllByUsername(); - assertThat(users).isNotNull(); - assertThat(users.size()).isEqualTo(100); - } - - @Test - public void shouldDetermineViewNameFromMethodPrefix() { - try { - repository.findByIncorrectView(); - fail("Expected InvalidDataAccessResourceException"); - } catch (InvalidDataAccessResourceUsageException e) { - assertThat(e.getMessage().startsWith("View user/byIncorrectView does not exist")) - .as(e.getMessage()).isTrue(); - } - } - - @Test - public void shouldDetermineViewNameFromCountPrefixAndReduce() { - long count = repository.countCustomFindAllView(); - assertThat(count).isEqualTo(100); - - try { - repository.countCustomFindInvalid(); - fail("Expected InvalidDataAccessResourceException"); - } catch (InvalidDataAccessResourceUsageException e) { - assertThat(e.getMessage().startsWith("View user/customFindInvalid does not exist")) - .as(e.getMessage()).isTrue(); - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java deleted file mode 100644 index b2777b00..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import java.util.Arrays; -import java.util.Collections; -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.view.DefaultView; -import com.couchbase.client.java.view.DesignDocument; -import com.couchbase.client.java.view.View; - -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -/** - * @author Michael Nitschinger - * @author Simon Baslé - */ -public class CouchbaseRepositoryViewListener extends DependencyInjectionTestExecutionListener { - - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - populateTestData(client, clusterInfo); - createAndWaitForDesignDocs(client); - } - - private void populateTestData(final Bucket client, ClusterInfo clusterInfo) { - CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); - for (int i = 0; i < 100; i++) { - template.save(new User("testuser-" + i, "uname-" + i, i), PersistTo.MASTER, ReplicateTo.NONE); - } - } - - private void createAndWaitForDesignDocs(final Bucket client) { - String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(null, null); } }"; - String mapFunctionName = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(doc.username, null); } }"; - String mapFunctionAge = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(doc.age, doc.age); } }"; - View view = DefaultView.create("customFindAllView", mapFunction, "_count"); - View customFindByNameView = DefaultView.create("customFindByNameView", mapFunctionName, "_count"); - View customFindByAgeStatsView = DefaultView.create("customFindByAgeStatsView", mapFunctionAge, "_stats"); - List views = Arrays.asList(view, customFindByNameView, customFindByAgeStatsView); - DesignDocument designDoc = DesignDocument.create("user", views); - client.bucketManager().upsertDesignDocument(designDoc); - - view = DefaultView.create("customCountView", mapFunction, "_count"); - views = Collections.singletonList(view); - designDoc = DesignDocument.create("userCustom", views); - client.bucketManager().upsertDesignDocument(designDoc); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/CustomUserRepository.java b/src/test/java/org/springframework/data/couchbase/repository/CustomUserRepository.java deleted file mode 100644 index 93b31f58..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/CustomUserRepository.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import java.util.List; - -import com.couchbase.client.java.document.json.JsonObject; - -import org.springframework.data.couchbase.core.query.View; - -/** - * @author David Harrigan - * @author Simon Baslé - */ -public interface CustomUserRepository extends CouchbaseRepository { - - @Override - @View(designDocument = "user", viewName = "customFindAllView") - Iterable findAll(); - - @Override - @View(designDocument = "userCustom", viewName = "customCountView") - long count(); - - @View(viewName = "allSomething") - Iterable findIncorrectExplicitView(); - - @View(viewName = "customFindAllView") - Iterable findRandomMethodName(); - - @View(viewName = "customFindByNameView") - long countByUsernameGreaterThanEqualAndUsernameLessThan(String lowBound, String highBound); - - @View(viewName = "customFindByNameView") - List findByUsernameIs(String lowKey); - - @View(viewName = "customFindByNameView") - List findAllByUsernameIn(List keys); - - @View(viewName = "customFindByNameView") - List findByUsernameGreaterThanEqualAndUsernameLessThanEqual(String lowKey, String highKey); - - @View(viewName = "customFindByNameView") - List findByUsernameBetween(String lowKey, String highKey); - - @View(viewName = "customFindByNameView") - List findTop3ByUsernameGreaterThanEqual(String lowKey); - - @View(viewName = "customFindAllView") - List findAllByUsername(); - - @View(viewName = "customFindAllView") - List findAllByUsernameEqualAndUserblablaIs(String s, String blabla); - - @View - List findByIncorrectView(); - - @View - long countCustomFindAllView(); - - @View - long countCustomFindInvalid(); - - @View(viewName = "customFindByAgeStatsView", reduce = true) - JsonObject findByAgeLessThan(int maxAge); -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/DimensionalPartyRepository.java b/src/test/java/org/springframework/data/couchbase/repository/DimensionalPartyRepository.java deleted file mode 100644 index 4d12cf13..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/DimensionalPartyRepository.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -import com.couchbase.client.java.document.json.JsonArray; - -import org.springframework.data.couchbase.core.query.Dimensional; -import org.springframework.data.geo.Box; -import org.springframework.data.geo.Circle; -import org.springframework.data.geo.Distance; -import org.springframework.data.geo.Point; -import org.springframework.data.geo.Polygon; -import org.springframework.data.repository.CrudRepository; - -public interface DimensionalPartyRepository extends CrudRepository { - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation") - List findByLocationNear(Point p, Distance d); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation") - List findByLocationWithin(Box boundingBox); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation") - List findByLocationWithin(Polygon zone); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation") - List findByLocationWithin(Point[] points); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation") - List findByLocationWithin(Point point); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation") - List findByLocationWithin(JsonArray range); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation") - List findByLocationWithin(Point lowerLeft, Point upperRight); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocationAndAttendees", dimensions = 3) - List findByLocationWithin(Circle zone); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocationAndAttendees", dimensions = 3) - List findByLocationWithinAndAttendeesGreaterThan(Polygon zone, double minAttendees); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocationAndAttendees", dimensions = 3) - List findByLocationWithin(JsonArray startRange, JsonArray endRange); - - //TODO more coverage of operators? - - @IndexedByLocation - List findByLocationIsWithin(Point a, Point b); - - @Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation", dimensions = 2) - @Retention(RetentionPolicy.RUNTIME) - @interface IndexedByLocation { } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/DimensionalQueryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/DimensionalQueryIntegrationTests.java deleted file mode 100644 index f0515a6f..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/DimensionalQueryIntegrationTests.java +++ /dev/null @@ -1,346 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import com.couchbase.client.java.document.json.JsonArray; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.geo.Box; -import org.springframework.data.geo.Circle; -import org.springframework.data.geo.Distance; -import org.springframework.data.geo.Point; -import org.springframework.data.geo.Polygon; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -/** - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(PartyPopulatorListener.class) -public class DimensionalQueryIntegrationTests { - - @Autowired - private RepositoryOperationsMapping templateMapping; - - @Autowired - private IndexManager indexManager; - - private DimensionalPartyRepository repository; - - @Before - public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(templateMapping, indexManager); - repository = getRepositoryWithRetry(factory, DimensionalPartyRepository.class); - } - - @Test - public void testFindWithingPolygon() { - Set expectedKeys = new HashSet(); - expectedKeys.add("testparty-2"); - expectedKeys.add("testparty-3"); - expectedKeys.add("testparty-4"); - expectedKeys.add("testparty-5"); - //a zone that engulfs parties 2, 3, 4 and 5, in the shape of a downward arrow pointing right - Polygon zone = new Polygon(new Point(1, -2), - new Point(3, -1.5), - new Point(6, -4), - new Point(5.5, -5.5), - new Point(3, -5)); - - List parties = repository.findByLocationWithin(zone); - - assertThat(parties.size()).isEqualTo(4); - for (Party party : parties) { - assertThat(expectedKeys.contains(party.getKey())).isTrue(); - } - } - - @Test - public void testByLocationNear() { - Set expectedKeys = new HashSet(); - expectedKeys.add("testparty-0"); - expectedKeys.add("testparty-1"); - - List parties = repository.findByLocationNear(new Point(0, 0), new Distance(1.5)); - assertThat(parties.size()).isEqualTo(2); - for (Party party : parties) { - assertThat(expectedKeys.contains(party.getKey())).isTrue(); - } - - //with this one, testparty-2 is within the bounding box but not in correct distance - parties = repository.findByLocationNear(new Point(0, 0), new Distance(2.5)); - assertThat(parties.size()).isEqualTo(2); - for (Party party : parties) { - assertThat(expectedKeys.contains(party.getKey())).isTrue(); - } - - //here we adjust the distance so that testparty-2 falls just on the edge - parties = repository.findByLocationNear(new Point(0, 0), new Distance(2.8284271247461903)); - expectedKeys.add("testparty-2"); - assertThat(parties.size()).isEqualTo(3); - for (Party party : parties) { - assertThat(expectedKeys.contains(party.getKey())).isTrue(); - } - } - - @Test - public void testByLocationAndSparseAttendees() { - Set expectedKeys = new HashSet(); - expectedKeys.add("testparty-4"); - expectedKeys.add("testparty-5"); - //a zone that engulfs parties 2, 3, 4 and 5 (with resp. 120, 130, 140 and 150 attendees) - //in the shape of a downward arrow pointing right - Polygon zone = new Polygon(new Point(1, -2), - new Point(3, -1.5), - new Point(6, -4), - new Point(5.5, -5.5), - new Point(3, -5)); - - //first check the zone contains 4 parties - List allPartiesInZone = repository.findByLocationWithinAndAttendeesGreaterThan(zone, -1); - List allPartiesInZoneWithoutAttendeeCriteria = repository.findByLocationWithin(zone); - assertThat(allPartiesInZone.size()).as(allPartiesInZone.toString()).isEqualTo(4); - assertThat(allPartiesInZone).isEqualTo(allPartiesInZoneWithoutAttendeeCriteria); - - //check parties are limited by the attendees - List parties = repository.findByLocationWithinAndAttendeesGreaterThan(zone, 140); - for (Party party : parties) { - System.out.println(party.getKey() + " : " + party.getLocation() + " " + party.getAttendees()); - } - - assertThat(parties.size()).as(parties.toString()).isEqualTo(2); - for (Party party : parties) { - assertThat(party.getAttendees() >= 140).isTrue(); - assertThat(expectedKeys.contains(party.getKey())).isTrue(); - } - } - - @Test - public void testByLocationInCircle() { - Circle zoneBboxFalse = new Circle(new Point(-5,5), new Distance(5.5)); - Circle zoneEdge = new Circle(new Point(-5, 0), new Distance(5)); - Circle zoneInside = new Circle(new Point(6, -6), new Distance(10)); - Circle zoneEmpty = new Circle(new Point(6,6), new Distance(3)); - - List parties = repository.findByLocationWithin(zoneBboxFalse); - assertThat(parties.size()).isEqualTo(0); - - parties = repository.findByLocationWithin(zoneEdge); - assertThat(parties.size()).isEqualTo(1); - assertThat(parties.get(0).getKey()).isEqualTo("testparty-0"); - - parties = repository.findByLocationWithin(zoneInside); - assertThat(parties.size()).isEqualTo(12); //all the parties except the special one at 100, 100 - - parties = repository.findByLocationWithin(zoneEmpty); - assertThat(parties.size()).isEqualTo(0); - } - - @Test - public void testByLocationInBox() { - Box zone1 = new Box(new Point(-10.5, -0.5), new Point(0.5, 10.5)); - Box zone2 = new Box(new Point(-4, -16), new Point(16, 4)); - Box zoneEmpty = new Box(new Point(3, 3), new Point(9, 9)); - - List parties = repository.findByLocationWithin(zone1); - - assertThat(parties.size()).isEqualTo(1); - assertThat(parties.get(0).getKey()).isEqualTo("testparty-0"); - - parties = repository.findByLocationWithin(zone2); - - assertThat(parties.size()).isEqualTo(12); //all the parties except the special one at 100, 100 - - parties = repository.findByLocationWithin(zoneEmpty); - assertThat(parties.size()).isEqualTo(0); - } - - @Test - public void testByLocationInPolygon() { - //slightly skewed square triangle that cuts just short of (0,0) - //bounding box of (-1,-1),(0.5,1) - Polygon zoneFalsePositive = new Polygon( - new Point(-1, 1), - new Point(0.5, 1), - new Point(-1, -1) - ); - //square triangle that comes through (0,0) - //bounding box of (-1,-1),(1,1) - Polygon zoneEdge = new Polygon( - new Point(-1, 1), - new Point(1, 1), - new Point(-1, -1) - ); - //bounding box of (-4, -16),(16,4) - Polygon zoneWithin = new Polygon( - new Point(-4, -10), - new Point(-3, 4), - new Point(14, 2), - new Point(16, -16)); - //bounding box of (3,3)(9,9) - Polygon zoneEmpty = new Polygon( - new Point(3, 3), - new Point(3, 7), - new Point(6, 7), - new Point(6, 9), - new Point(9, 9), - new Point(9, 5), - new Point(6, 5), - new Point(6, 3)); - - List parties = repository.findByLocationWithin(zoneFalsePositive); - assertThat(parties.size()) - .as("points outside a polygon but within bounding box shouldn't be considered within") - .isEqualTo(0); - - parties = repository.findByLocationWithin(zoneEdge); - assertThat(parties.size()) - .as("point on edge of a polygon shouldn't be considered within").isEqualTo(0); - - parties = repository.findByLocationWithin(zoneWithin); - assertThat(parties.size()).isEqualTo(12); //all the parties except the special one at 100, 100 - - parties = repository.findByLocationWithin(zoneEmpty); - assertThat(parties.size()).isEqualTo(0); - } - - @Test - public void testByLocationWithinTwoPoints() { - Point zone1LowerLeft = new Point(-10.5, -0.5); - Point zone1UpperRight = new Point(0.5, 10.5); - Point zone2LowerLeft = new Point(-4, -16); - Point zone2UpperRight = new Point(16, 4); - Point zoneEmptyLowerLeft = new Point(3, 3); - Point zoneEmptyUpperRight = new Point(9, 9); - - List parties = repository.findByLocationWithin(zone1LowerLeft, zone1UpperRight); - - assertThat(parties.size()).isEqualTo(1); - assertThat(parties.get(0).getKey()).isEqualTo("testparty-0"); - - parties = repository.findByLocationWithin(zone2LowerLeft, zone2UpperRight); - - assertThat(parties.size()).isEqualTo(12); //all the parties except the special one at 100, 100 - - parties = repository.findByLocationWithin(zoneEmptyLowerLeft, zoneEmptyUpperRight); - assertThat(parties.size()).isEqualTo(0); - } - - @Test - public void testPolygonAndArrayOfPointsProduceSameResult() { - Set expectedKeys = new HashSet(); - expectedKeys.add("testparty-2"); - expectedKeys.add("testparty-3"); - expectedKeys.add("testparty-4"); - expectedKeys.add("testparty-5"); - //a zone that engulfs parties 2, 3, 4 and 5, in the shape of a downward arrow pointing right - Polygon zone = new Polygon(new Point(1, -2), - new Point(3, -1.5), - new Point(6, -4), - new Point(5.5, -5.5), - new Point(3, -5)); - Point[] points = zone.getPoints().toArray(new Point[5]); - - List fromZone = repository.findByLocationWithin(zone); - List fromPoints = repository.findByLocationWithin(points); - - assertThat(fromZone.size()).isEqualTo(4); - assertThat(fromPoints).isEqualTo(fromZone); - Set keys = new HashSet(); - for (Party party : fromZone) { - keys.add(party.getKey()); - } - assertThat(keys).isEqualTo(expectedKeys); - } - - @Test - public void testProvidingOnePointIsRejected() { - try { - repository.findByLocationWithin(new Point(0, 0)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()) - .isEqualTo("Cannot compute a bounding box for within, 2 Point needed, missing parameter"); - } - - try { - repository.findByLocationWithin(new Point(0, 0), null); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()) - .isEqualTo("Cannot compute a bounding box for within, 2 Point needed, got null"); - } - } - - @Test - public void testProvidingOneJsonArrayIsRejected() { - try { - List parties = repository.findByLocationWithin(JsonArray.from(0,0)); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()) - .isEqualTo("2 JsonArray required for within: startRange and endRange, missing parameter"); - } - } - - @Test(expected = CouchbaseQueryExecutionException.class) - public void testJsonArrayWithNonNumericalValueProducesServerSideError() { - repository.findByLocationWithin(JsonArray.from("toto", -2), JsonArray.from(4, 1)); - } - - @Test - public void testWithinJsonArrayRangesFiltersLocationAndAttendees() { - List parties = repository.findByLocationWithin(JsonArray.from(0, -4, 115), JsonArray.from(4, 1, 132)); - assertThat(parties.size()).isEqualTo(2); - } - - @Test - public void testDimensionalAnnotationCanBeUsedAsMeta() { - try { - //will trigger a specific message if parsed by SpatialViewQueryCreator - repository.findByLocationIsWithin(new Point(0,0), null); - fail("expected IllegalArgumentException from SpatialViewQueryCreator"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()) - .isEqualTo("Cannot compute a bounding box for within, 2 Point needed, got null"); - } - - //when it is correctly formed, it actually returns data - assertThat(repository - .findByLocationIsWithin(new Point(-10.5, -0.5), new Point(0.5, 10.5)).size()) - .isEqualTo(1); - } - - @Test - public void testFindWithinBoxCornerOrderDoesMatter() { - Point a = new Point(0, -4); - Point b = new Point(6, -2); - Box box1 = new Box(a, b); - Box box2 = new Box(b, a); - - final List parties1 = repository.findByLocationWithin(box1); - final List parties2 = repository.findByLocationWithin(box2); - - assertThat(parties1.size()).isEqualTo(3); - assertThat(parties2).isNotEqualTo(parties1); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/Item.java b/src/test/java/org/springframework/data/couchbase/repository/Item.java deleted file mode 100644 index 7e5b3638..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/Item.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import com.couchbase.client.java.repository.annotation.Field; - -import org.springframework.data.annotation.Id; - -public class Item { - - @Id - public String id; - - @Field("desc") - public String description; - - public Item(String id, String description) { - this.id = id; - this.description = description; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Item item = (Item) o; - - if (!id.equals(item.id)) return false; - return !(description != null ? !description.equals(item.description) : item.description != null); - - } - - @Override - public int hashCode() { - int result = id.hashCode(); - result = 31 * result + (description != null ? description.hashCode() : 0); - return result; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/ItemRepository.java b/src/test/java/org/springframework/data/couchbase/repository/ItemRepository.java deleted file mode 100644 index b56caa06..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ItemRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import java.util.List; - -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; -import org.springframework.data.repository.CrudRepository; - -@N1qlPrimaryIndexed -public interface ItemRepository extends CrudRepository { - - List findAllByDescriptionNotNull(); -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryIntegrationTests.java deleted file mode 100644 index 6a6beb2d..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryIntegrationTests.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataRetrievalFailureException; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.mapping.model.MappingInstantiationException; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -/** - * This tests PaginAndSortingRepository features in the Couchbase connector. - * - * @author Simon Baslé - * @author Subhashni Balakrishnan - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(PartyPopulatorListener.class) -public class N1qlCouchbaseRepositoryIntegrationTests { - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private PartyPagingRepository repository; - - private PartyRepository partyRepository; - - private ItemRepository itemRepository; - - private final String KEY_PARTY = "Party1"; - private final String KEY_ITEM = "Item1"; - - - @Before - public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = getRepositoryWithRetry(factory, PartyPagingRepository.class); - partyRepository = getRepositoryWithRetry(factory, PartyRepository.class); - itemRepository = getRepositoryWithRetry(factory, ItemRepository.class); - - partyRepository.save(new Party(KEY_PARTY, "partyName", "MatchingDescription", null, 1, null)); - itemRepository.save(new Item(KEY_ITEM, "MatchingDescription")); - } - - @After - public void cleanUp() { - try { itemRepository.deleteById(KEY_ITEM); } catch (DataRetrievalFailureException e) {} - try { partyRepository.deleteById(KEY_PARTY); } catch (DataRetrievalFailureException e) {} - } - - @Test - public void shouldBeThreadsafe() { - // This doesn't guarantee it, but we should catch most thread issues without - // taking too long here... - int runs = 50; - for (int i=0; i> callables = new ArrayList<>(); - for (int thread = 0; thread < threads; ++thread) { - final int counter = thread; - Callable booleanSupplier = () -> { - String expectedName = "party like it's 199" + counter%12; - String foundName = partyRepository.findByName(expectedName).get(0).getName(); - return expectedName.equals(foundName); //should never get false - }; - callables.add(booleanSupplier); - } - try { - List> futures = service.invokeAll(callables); - service.shutdown(); - service.awaitTermination(5, TimeUnit.SECONDS); - for (Future future: futures) { - assertThat(future.get()).isTrue(); - } - } catch (InterruptedException | ExecutionException e) { - fail("Threads failed to run " + e.getMessage()); - } - } - - @Test - public void shouldFindAllWithSort() { - Iterable allByAttendanceDesc = repository.findAll(Sort.by(Sort.Direction.DESC, "attendees")); - long previousAttendance = Long.MAX_VALUE; - for (Party party : allByAttendanceDesc) { - assertThat(party.getAttendees() <= previousAttendance).isTrue(); - previousAttendance = party.getAttendees(); - } - assertThat(previousAttendance == Long.MAX_VALUE) - .as("Expected to find several parties").isFalse(); - } - - @Test - public void shouldSortOnRenamedFieldIfJsonNameIsProvidedInSort() { - Iterable parties = repository.findAll(Sort.by(Sort.Direction.DESC, "desc")); - String previousDesc = null; - for (Party party : parties) { - if (previousDesc != null) { - assertThat(party.getDescription().compareTo(previousDesc) <= 0).isTrue(); - } - previousDesc = party.getDescription(); - } - assertThat(previousDesc).as("Expected to find several parties").isNotNull(); - } - - @Test - public void shouldSortWithoutCaseSensitivity() { - Iterable parties = repository.findAll(Sort.by(new Sort.Order(Sort.Direction.DESC, "desc").ignoreCase())); - String previousDesc = null; - for (Party party : parties) { - if (previousDesc != null) { - assertThat(party.getDescription().compareToIgnoreCase(previousDesc) <= 0) - .isTrue(); - } - previousDesc = party.getDescription(); - } - assertThat(previousDesc).as("Expected to find several parties").isNotNull(); - } - - @Test - public void shouldPageThroughEntities() { - Pageable pageable = PageRequest.of(0, 8); - - Page page1 = repository.findAll(pageable); - assertThat(page1.getTotalElements() >= 12) - .as("Query for parties should be atleast 12").isTrue(); - assertThat(page1.getNumberOfElements()).isEqualTo(8); - } - - @Test - public void shouldPageThroughSortedEntities() { - Pageable pageable = PageRequest.of(0, 8, Sort.Direction.DESC, "attendees"); - - Page page1 = repository.findAll(pageable); - assertThat(page1.getTotalElements() >= 12) - .as("Query for parties should be atleast 12").isTrue(); - assertThat(page1.getNumberOfElements()).isEqualTo(8); - - List parties = page1.getContent(); - Long previousAttendees = null; - for (Party party : parties) { - if (previousAttendees != null) { - assertThat(party.getAttendees() <= previousAttendees).isTrue(); - } - previousAttendees = party.getAttendees(); - } - } - - @Test - public void testWrapWhereCriteria() { - List partyList = partyRepository.findByDescriptionOrName("MatchingDescription", "partyName"); - assertThat(partyList.size() == 1).isTrue(); - } - - @Test - public void shouldPageWithStringBasedQuery() { - Pageable pageable = PageRequest.of(0, 8, Sort.Direction.DESC, "attendees"); - Page page1 = partyRepository.findPartiesWithAttendee(1, pageable); - assertThat(page1.getTotalElements() >= 12) - .as("Query for parties with attendees should be atleast 12").isTrue(); - assertThat(page1.getNumberOfElements()).isEqualTo(8); - - List parties = page1.getContent(); - Long previousAttendees = null; - for (Party party : parties) { - if (previousAttendees != null) { - assertThat(party.getAttendees() <= previousAttendees).isTrue(); - } - previousAttendees = party.getAttendees(); - } - Page page2 = partyRepository.findPartiesWithAttendee(1, page1.nextPageable()); - assertThat(page2.getNumberOfElements()).isEqualTo(8); - parties = page2.getContent(); - for (Party party : parties) { - if (previousAttendees != null) { - assertThat(party.getAttendees() <= previousAttendees).isTrue(); - } - previousAttendees = party.getAttendees(); - } - } - - //Fails on deserialization as a different entity item is also present - @Test(expected = MappingInstantiationException.class) - public void shouldFailWithMissingFilterStringBasedQuery() { - Sort sort = Sort.by(Sort.Direction.DESC, "attendees"); - partyRepository.findParties(sort); - } - - @Test - public void testDeleteQuery() { - partyRepository.save(new Party("testDeleteQuery", "delete", "delete", null, 0, null)); - List partyList = partyRepository.removeByDescriptionOrName("delete", "delete"); - assertThat(partyList.size() == 1).isTrue(); - } - - @Test - public void testSpelDateConvertion() { - final String key = "testSpelDateConvertion"; - Calendar cal = Calendar.getInstance(); - cal.clear(); - cal.set(2018, Calendar.SEPTEMBER, 10); - Date date = cal.getTime(); - partyRepository.save(new Party(key, "", "", date, 0, null)); - List partyList = partyRepository.getByEventDate(date); - assertThat(partyList.size() == 1).isTrue(); - assertThat(key).as("Key mismatch").isEqualTo(partyList.get(0).getKey()); - } - - @Test - public void testN1qlQueryWithInvalidValue() { - partyRepository.save(new Party("testN1qlQueryWithInvalidValue", "", "testN1qlQueryWithInvalidValue", null, 0, null)); - final String description = "testN1qlQueryWithInvalidValue* OR `description` LIKE \"\""; - List partyList = partyRepository.findByDescriptionStartingWith(description); - assertThat(partyList.size() == 0).isTrue(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/N1qlCrudRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/N1qlCrudRepositoryIntegrationTests.java deleted file mode 100644 index e6f5a2f4..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/N1qlCrudRepositoryIntegrationTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import java.util.Date; -import java.util.List; - -import com.couchbase.client.java.Bucket; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataRetrievalFailureException; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.geo.Point; -import org.springframework.test.context.ContextConfiguration; - -/** - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -public class N1qlCrudRepositoryIntegrationTests { - - @Autowired - private Bucket client; - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private PartyRepository partyRepository; - private ItemRepository itemRepository; - - private static final String KEY_ITEM = "itemNotParty"; - private static final String KEY_PARTY = "partyNotItem"; - private static final String KEY_PARTY_KEYWORD = "partyHasKeyword"; - - private static final Item item = new Item(KEY_ITEM, "short description"); - private static final Party party = new Party(KEY_PARTY, "partyName", "short description", new Date(), 120, new Point(500, 500)); - - @Before - public void setup() throws Exception { - CouchbaseRepositoryFactory factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - - partyRepository = getRepositoryWithRetry(factory, PartyRepository.class); - itemRepository = getRepositoryWithRetry(factory, ItemRepository.class); - - itemRepository.save(item); - partyRepository.save(party); - } - - @After - public void cleanUp() { - try { itemRepository.deleteById(KEY_ITEM); } catch (DataRetrievalFailureException e) {} - try { partyRepository.deleteById(KEY_PARTY); } catch (DataRetrievalFailureException e) {} - try { partyRepository.deleteById(KEY_PARTY_KEYWORD); } catch (DataRetrievalFailureException e) {} - } - - @Test - public void shouldDistinguishBetweenItemsAndParties() { - List items = itemRepository.findAllByDescriptionNotNull(); - List parties = partyRepository.findAllByDescriptionNotNull(); - - assertThat(items.contains(item)).isTrue(); - assertThat(parties.contains(party)).isTrue(); - - assertThat(items.contains(party)).isFalse(); - assertThat(parties.contains(item)).isFalse(); - } - - @Test - public void shouldSaveObjectWithN1qlKeywordField() { - Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "desc is a N1QL keyword", new Date(), 40, new Point(500, 500)); - partyRepository.save(partyHasKeyword); - List parties = partyRepository.findAllByDescriptionNotNull(); - - assertThat(client.exists(KEY_PARTY_KEYWORD)).isTrue(); - assertThat(parties.contains(partyHasKeyword)).isTrue(); - for (Object o : parties) { - if (!(o instanceof Party)) { - fail("expected only Party objects"); - } - } - } - - @Test - public void shouldGenerateCountProjection() { - Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", null, new Date(), 40, new Point(500, 500)); - partyRepository.save(partyHasKeyword); - long countTotal = partyRepository.count(); - long countCustom = partyRepository.countAllByDescriptionNotNull(); - assertThat(countCustom).isEqualTo(countTotal - 1); - } - - @Test - public void shouldCountWhenReturningLongAndUsingStringSelectFromSpEL() { - Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "second party", new Date(), 40, new Point(500, 500)); - partyRepository.save(partyHasKeyword); - - long countTotal = partyRepository.count(); - long countCustom = partyRepository.countCustom(); - assertThat(countCustom).isEqualTo(countTotal); - } - - @Test - public void shouldCustomCountWhenReturningLongAndUsingStringWithoutSpEL() { - Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "second party", new Date(), 40, new Point(500, 500)); - partyRepository.save(partyHasKeyword); - - long countTotal = partyRepository.count(); - long countCustom = partyRepository.countCustomPlusFive(); - assertThat(countCustom).isEqualTo(countTotal + 5); - } - - @Test(expected = CouchbaseQueryExecutionException.class) - public void shouldFailConversionWithStringReturnType() { - Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "desc is a N1QL keyword", new Date(), 40, new Point(500, 500)); - partyRepository.save(partyHasKeyword); - - String someString = partyRepository.findSomeString(); - } - - @Test - public void shouldDoNumericProjectionWithStringBasedQuery() { - Party partyHasKeyword = new Party(KEY_PARTY_KEYWORD, "party", "desc is a N1QL keyword", new Date(), 4000000, new Point(500, 500)); - partyRepository.save(partyHasKeyword); - - long max = partyRepository.findMaxAttendees(); - assertThat(max).isEqualTo(4000000); - } - - @Test - public void shouldDoBooleanProjectionWithStringBasedQuery() { - boolean someBoolean = partyRepository.justABoolean(); - assertThat(someBoolean).isEqualTo(true); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/N1qlPlaceholderIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/N1qlPlaceholderIntegrationTests.java deleted file mode 100644 index 5a2b8ecf..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/N1qlPlaceholderIntegrationTests.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.query.Param; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -/** - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(PartyPopulatorListener.class) -public class N1qlPlaceholderIntegrationTests { - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private CouchbaseRepositoryFactory factory; - private PartyRepository partyRepository; - - @Before - public void setup() throws Exception { - factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - partyRepository = getRepositoryWithRetry(factory, PartyRepository.class); - } - - @Test - public void shouldFindUsingNamedParameters() { - String included = "90"; - String excluded = "New Year"; - int min = 200; - List result = partyRepository.findAllWithNamedParams(excluded, included, min); - - assertThat(result.size()).isEqualTo(2); - for (Party party : result) { - assertThat(party.getDescription().contains(included)).isTrue(); - assertThat(party.getDescription().contains(excluded)).isFalse(); - assertThat(party.getAttendees() >= min).isTrue(); - } - } - - @Test - public void shouldFindUsingPositionalParameters() { - String included = "90"; - String excluded = "New Year"; - int min = 200; - List result = partyRepository.findAllWithPositionalParams(excluded, included, min); - - assertThat(result.size()).isEqualTo(2); - for (Party party : result) { - assertThat(party.getDescription().contains(included)).isTrue(); - assertThat(party.getDescription().contains(excluded)).isFalse(); - assertThat(party.getAttendees() >= min).isTrue(); - } - } - - @Test - public void shouldIgnoreQuotedNamedParamsAndParamAnnotationIfPosUsed() { - String included = "90"; - String excluded = "New Year"; - int min = 200; - List result = partyRepository.findAllWithPositionalParamsAndQuotedNamedParams(excluded, included, min); - - assertThat(result.size()).isEqualTo(2); - for (Party party : result) { - assertThat(party.getDescription().contains(included)).isTrue(); - assertThat(party.getDescription().contains(excluded)).isFalse(); - assertThat(party.getAttendees() >= min).isTrue(); - } - } - - private interface BadRepository extends CrudRepository { - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" + - " AND `desc` NOT LIKE '%' || $included || '%'") - List findAllWithMixedParamsInQuery(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long min); - } - - @Test - public void shouldFailUsingMixedParameters() { - try { - factory.getRepository(BadRepository.class); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage()).as(e.toString()) - .isEqualTo("Using both named (1) and positional (2) placeholders is not supported, please choose " + - "one over the other in findAllWithMixedParamsInQuery"); - } - } - - @Test - public void deleteQueryTest() { - String included = "90"; - String excluded = "New Year"; - int max = 200; - List result = partyRepository.removeWithPositionalParams(excluded, included, max); - - assertThat(result.size()).isEqualTo(10); - for (Party party : result) { - assertThat(party.getDescription().contains(included)).isTrue(); - assertThat(party.getDescription().contains(excluded)).isFalse(); - assertThat(party.getAttendees() < max).isTrue(); - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/PageAndSliceIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/PageAndSliceIntegrationTests.java deleted file mode 100644 index 9b348760..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/PageAndSliceIntegrationTests.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import java.util.ArrayList; -import java.util.List; - -import com.couchbase.client.java.Bucket; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Slice; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class) -public class PageAndSliceIntegrationTests { - - @Autowired - private Bucket client; - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private UserRepository repository; - - @Before - public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = getRepositoryWithRetry(factory, UserRepository.class); - } - @Test - public void shouldPageThroughResults() { - Page page1 = repository.findByAgeGreaterThan(9, PageRequest.of(0, 40)); //there are 90 matching users - Page page2 = repository.findByAgeGreaterThan(9, page1.nextPageable()); - Page page3 = repository.findByAgeGreaterThan(9, page2.nextPageable()); - - assertThat(page1.getTotalElements()).isEqualTo(90); - assertThat(page1.getTotalPages()).isEqualTo(3); - assertThat(page1.hasContent()).isTrue(); - assertThat(page1.hasNext()).isTrue(); - assertThat(page1.getNumberOfElements()).isEqualTo(40); - - assertThat(page2.hasContent()).isTrue(); - assertThat(page2.hasNext()).isTrue(); - assertThat(page2.getNumberOfElements()).isEqualTo(40); - - assertThat(page3.hasContent()).isTrue(); - assertThat(page3.hasNext()).isFalse(); - assertThat(page3.getNumberOfElements()).isEqualTo(10); - } - - @Test(expected = UnsupportedOperationException.class) - public void shouldThrowWhenPageableIsNullInPageQuery() { - repository.findByAgeGreaterThan(9, null); - } - - @Test - public void shouldSliceThroughResults() { - int count = 0; - List allMatching = new ArrayList(10); - Slice slice = repository.findByAgeLessThan(9, PageRequest.of(0, 3)); //9 matching users (ages 0-8) - allMatching.addAll(slice.getContent()); - while(slice.hasNext()) { - slice = repository.findByAgeLessThan(9, slice.nextPageable()); - allMatching.addAll(slice.getContent()); - assertThat(slice.getContent().size()).isEqualTo(3); - } - assertThat(allMatching.size()).isEqualTo(9); - } - - @Test(expected = UnsupportedOperationException.class) - public void shouldThrowWhenPageableIsNullSliceQuery() { - repository.findByAgeLessThan(9, null); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/Party.java b/src/test/java/org/springframework/data/couchbase/repository/Party.java deleted file mode 100644 index 7456f86c..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/Party.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import java.util.Date; - -import org.springframework.data.annotation.Id; -import org.springframework.data.geo.Point; - -import com.couchbase.client.java.repository.annotation.Field; - -/** - * An entity used to test conversion of parameters in query derivations. - * - * @author Simon Baslé - */ -public class Party { - - @Id - private final String key; - - private final String name; - - @Field("desc") - private final String description; - - private final Date eventDate; - - private final long attendees; - - private final Point location; - - public Party(String key, String name, String description, Date eventDate, long attendees, Point location) { - this.key = key; - this.name = name; - this.description = description; - this.eventDate = eventDate; - this.attendees = attendees; - this.location = location; - } - - public String getKey() { - return key; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public Date getEventDate() { - return eventDate; - } - - public long getAttendees() { - return attendees; - } - - public Point getLocation() { - return location; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Party party = (Party) o; - - return key.equals(party.key); - - } - - @Override - public int hashCode() { - return key.hashCode(); - } - - @Override - public String toString() { - return "Party{" + - "name='" + name + '\'' + - ", eventDate=" + eventDate + - ", location=" + location + - '}'; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/PartyPagingRepository.java b/src/test/java/org/springframework/data/couchbase/repository/PartyPagingRepository.java deleted file mode 100644 index 26c6458b..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/PartyPagingRepository.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.springframework.data.couchbase.repository; - -public interface PartyPagingRepository extends CouchbasePagingAndSortingRepository { -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/PartyPopulatorListener.java b/src/test/java/org/springframework/data/couchbase/repository/PartyPopulatorListener.java deleted file mode 100644 index 20cec7a3..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/PartyPopulatorListener.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -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.view.DefaultView; -import com.couchbase.client.java.view.DesignDocument; -import com.couchbase.client.java.view.SpatialView; -import com.couchbase.client.java.view.View; - -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.geo.Point; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -/** - * @author Simon Baslé - */ -public class PartyPopulatorListener extends DependencyInjectionTestExecutionListener { - - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - populateTestData(client, clusterInfo); - createAndWaitForDesignDocs(client); - } - - private void populateTestData(Bucket client, ClusterInfo clusterInfo) { - CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); - - Calendar cal = Calendar.getInstance(); - cal.clear(); - cal.set(Calendar.YEAR, 2015); - cal.set(Calendar.DAY_OF_MONTH, 10); - cal.set(Calendar.MONTH, Calendar.JANUARY); - for (int i = 0; i < 12; i++) { - Party p = new Party("testparty-" + i, "party like it's 199" + i, - "An awesome party, 90's themed, every 10 of the month", - cal.getTime(), 100 + i * 10, - new Point(i, -i)); - template.save(p, PersistTo.MASTER, ReplicateTo.NONE); - cal.roll(Calendar.MONTH, true); - } - - cal.clear(); - cal.set(Calendar.YEAR, 1990); - cal.set(Calendar.MONTH, Calendar.JANUARY); - cal.set(Calendar.DAY_OF_MONTH, 01); - template.save(new Party("aTestParty", "New Year's Eve 90", "Happy New Year", cal.getTime(), 1230000, new Point(100, 100))); - template.save(new Party("lowercaseParty", "lowercase party", "lowercase party", cal.getTime(), 1000, new Point(100, 100))); - template.save(new Party("uppercaseParty", "Uppercase party", "Uppercase party", cal.getTime(), 1000, new Point(100, 100))); - } - - private void createAndWaitForDesignDocs(Bucket client) { - //standard views - List views = new ArrayList(); - String mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " + - "{ emit(doc.eventDate, null); } }"; - views.add(DefaultView.create("byDate", mapFunction, "_count")); - - //create the view design document - DesignDocument designDoc = DesignDocument.create("party", views); - client.bucketManager().upsertDesignDocument(designDoc); - - //geo views - List geoViews = new ArrayList(); - mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " + - "{ emit([doc.location.x, doc.location.y], null); } }"; - geoViews.add(SpatialView.create("byLocation", mapFunction)); - - mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " + - "{ emit([doc.location.x, doc.location.y, doc.attendees], null); } }"; - geoViews.add(SpatialView.create("byLocationAndAttendees", mapFunction)); - - //create the geo views design document - DesignDocument geoDesignDoc = DesignDocument.create("partyGeo", geoViews); - client.bucketManager().upsertDesignDocument(geoDesignDoc); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/PartyRepository.java b/src/test/java/org/springframework/data/couchbase/repository/PartyRepository.java deleted file mode 100644 index 3326b124..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/PartyRepository.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2017-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.couchbase.repository; - -import com.couchbase.client.java.query.consistency.ScanConsistency; -import java.util.Date; -import java.util.List; - -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.core.query.View; -import org.springframework.data.couchbase.core.query.ViewIndexed; -import org.springframework.data.couchbase.core.query.WithConsistency; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.Param; - -/** - * @author Simon Baslé - * @author Subhashni Balakrishnan - */ -@ViewIndexed(designDoc = "party", viewName = "all") -@N1qlPrimaryIndexed -@N1qlSecondaryIndexed(indexName = "party") -public interface PartyRepository extends CouchbaseRepository { - - List findByAttendeesGreaterThanEqual(int minAttendees); - - List findByName(String name); - - List findByEventDateIs(Date targetDate); - - @View(designDocument = "party", viewName = "byDate") - List findFirst3ByEventDateGreaterThanEqual(Date targetDate); - - List findAllByDescriptionNotNull(); - - long countAllByDescriptionNotNull(); - - @WithConsistency(ScanConsistency.NOT_BOUNDED) - @Query("SELECT MAX(attendees) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") - long findMaxAttendees(); - - @Query("SELECT `desc` FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") - String findSomeString(); - - @Query("SELECT count(*) + 5 FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") - long countCustomPlusFive(); - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}") - long countCustom(); - - @Query("SELECT 1 = 1") - boolean justABoolean(); - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `attendees` >= $1") - Page findPartiesWithAttendee(int count, Pageable pageable); - - @Query("#{#n1ql.selectEntity}") - List findParties(Sort sort); - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $included || '%' AND attendees >= $min" + - " AND `desc` NOT LIKE '%' || $excluded || '%'") - List findAllWithNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long minimumAttendees); - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" + - " AND `desc` NOT LIKE '%' || $1 || '%'") - List findAllWithPositionalParams(String ex, String inc, long minimumAttendees); - - @Query("#{#n1ql.delete} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees < $3" + - " AND `desc` NOT LIKE '%' || $1 || '%' #{#n1ql.returning}") - List removeWithPositionalParams(String ex, String inc, long minimumAttendees); - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" + - " AND `desc` NOT LIKE '%' || $1 || '%' AND `desc` != \"this is \\\"$excluded\\\"\"") - List findAllWithPositionalParamsAndQuotedNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long min); - - List findByDescriptionOrName(String description, String name); - - List removeByDescriptionOrName(String description, String name); - - @Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and eventDate = $1") - List getByEventDate(Date eventDate); - - List findByDescriptionStartingWith(String description); -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/QueryDerivationConversionIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/QueryDerivationConversionIntegrationTests.java deleted file mode 100644 index 8d75fd80..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/QueryDerivationConversionIntegrationTests.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Optional; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.document.JsonDocument; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Simon Baslé - * @author Mark Paluch - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(PartyPopulatorListener.class) -public class QueryDerivationConversionIntegrationTests { - - @Autowired - private Bucket client; - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private PartyRepository repository; - - @Before - public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = factory.getRepository(PartyRepository.class); - } - - @Test - public void testConvertsDateParameterInN1qlQuery() { - Optional partyApril = repository.findById("testparty-3"); - assertThat(partyApril.isPresent()).isTrue(); - - Calendar cal = Calendar.getInstance(); - cal.clear(); - cal.set(2015, Calendar.APRIL, 10); - Date find = cal.getTime(); - - List parties = repository.findByEventDateIs(find); - assertThat(parties).isNotNull(); - assertThat(parties.size()).isEqualTo(1); - assertThat(parties.get(0).getEventDate()).isEqualTo(find); - - JsonDocument doc = client.get(parties.get(0).getKey()); - assertThat(doc.content().get("eventDate")).isEqualTo(find.getTime()); - } - - @Test - public void testAcceptLongParameterInN1qlQuery() { - List newYear90 = repository.findByAttendeesGreaterThanEqual(1200000); - assertThat(newYear90).isNotNull(); - assertThat(newYear90.size()).isEqualTo(1); - assertThat(newYear90.get(0).getKey()).isEqualTo("aTestParty"); - } - - @Test - public void testConvertDateParameterInViewQuery() { - Calendar cal = Calendar.getInstance(); - cal.clear(); - cal.set(2015, Calendar.AUGUST, 12); - Date find = cal.getTime(); - - List afterSummerParties = repository.findFirst3ByEventDateGreaterThanEqual(find); - assertThat(afterSummerParties).isNotNull(); - assertThat(afterSummerParties.size()).isEqualTo(3); - for (Party afterSummerParty : afterSummerParties) { - assert(afterSummerParty.getEventDate().after(find)); - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactiveN1qlCouchbaseRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/ReactiveN1qlCouchbaseRepositoryIntegrationTests.java deleted file mode 100644 index 1dc06255..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveN1qlCouchbaseRepositoryIntegrationTests.java +++ /dev/null @@ -1,157 +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.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import java.util.Calendar; -import java.util.Date; -import java.util.List; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -/** - * This tests ReactiveSortingRepository features in the Couchbase connector. - * - * @author Subhashni Balakrishnan - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class) -@TestExecutionListeners(PartyPopulatorListener.class) -public class ReactiveN1qlCouchbaseRepositoryIntegrationTests { - - @Autowired - private ReactiveRepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private ReactivePartySortingRepository repository; - - private ReactivePartyRepository partyRepository; - - private ItemRepository itemRepository; - - private final String KEY_PARTY = "ReactiveParty1"; - private final String KEY_ITEM = "ReactiveItem1"; - - - @Before - public void setup() throws Exception { - ReactiveRepositoryFactorySupport factory = new ReactiveCouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = getRepositoryWithRetry(factory, ReactivePartySortingRepository.class); - partyRepository = getRepositoryWithRetry(factory, ReactivePartyRepository.class); - itemRepository = getRepositoryWithRetry(factory, ItemRepository.class); - } - - @After - public void cleanUp() { - try { itemRepository.deleteById(KEY_ITEM); } catch (DataRetrievalFailureException e) {} - try { partyRepository.deleteById(KEY_PARTY); } catch (DataRetrievalFailureException e) {} - } - - @Test - public void shouldFindAllWithSort() { - Iterable allByAttendanceDesc = repository.findAll(Sort.by(Sort.Direction.DESC, "attendees")).collectList().block(); - long previousAttendance = Long.MAX_VALUE; - for (Party party : allByAttendanceDesc) { - assertThat(party.getAttendees() <= previousAttendance).isTrue(); - previousAttendance = party.getAttendees(); - } - assertThat(previousAttendance == Long.MAX_VALUE) - .as("Expected to find several parties").isFalse(); - } - - @Test - public void shouldSortOnRenamedFieldIfJsonNameIsProvidedInSort() { - Iterable parties = repository.findAll(Sort.by(Sort.Direction.DESC, "desc")).collectList().block(); - String previousDesc = null; - for (Party party : parties) { - if (previousDesc != null) { - assertThat(party.getDescription().compareTo(previousDesc) <= 0).isTrue(); - } - previousDesc = party.getDescription(); - } - assertThat(previousDesc).as("Expected to find several parties").isNotNull(); - } - - @Test - public void shouldSortWithoutCaseSensitivity() { - Iterable parties = repository.findAll(Sort.by(new Sort.Order(Sort.Direction.DESC, "desc").ignoreCase())).collectList().block(); - String previousDesc = null; - for(Party party : parties) { - if (previousDesc != null) { - assertThat(party.getDescription().compareToIgnoreCase(previousDesc) <= 0) - .isTrue(); - } - previousDesc = party.getDescription(); - } - assertThat(previousDesc).as("Expected to find several parties").isNotNull(); - } - - @Test - public void testCustomSpelCountQuery() { - long count = partyRepository.countCustom().block(); - assertThat(count >= 12).as("Count query for parties should be atleast 12") - .isTrue(); - } - - @Test - public void testPartTreeQuery() { - long count = partyRepository.countAllByDescriptionNotNull().block(); - assertThat(count >= 12) - .as("Count query for parties with description not null should be atleast 12") - .isTrue(); - } - - @Test - public void testSpelDateConvertion() { - final String key = "testReactiveSpelDateConvertion"; - Calendar cal = Calendar.getInstance(); - cal.clear(); - cal.set(2018, Calendar.SEPTEMBER, 15); - Date date = cal.getTime(); - partyRepository.save(new Party(key, "", "", date, 0, null)).block(); - List partyList = partyRepository.getByEventDate(date).collectList().block(); - assertThat(partyList.size() == 1).isTrue(); - assertThat(key).as("Key mismatch").isEqualTo(partyList.get(0).getKey()); - } - - @Test - public void testN1qlQueryWithInvalidValue() { - partyRepository.save(new Party("testReactiveN1qlQueryWithInvalidValue", "", "testReactiveN1qlQueryWithInvalidValue", null, 0, null)); - final String description = "testReactiveN1qlQueryWithInvalidValue* OR `description` LIKE \"\""; - List partyList = partyRepository.findByDescriptionStartingWith(description).collectList().block(); - assertThat(partyList.size() == 0).isTrue(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactivePartyRepository.java b/src/test/java/org/springframework/data/couchbase/repository/ReactivePartyRepository.java deleted file mode 100644 index 389e9d90..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactivePartyRepository.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import java.util.Date; - -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.core.query.View; -import org.springframework.data.couchbase.core.query.ViewIndexed; -import org.springframework.data.repository.query.Param; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * @author Subhashni Balakrishnan - */ -@ViewIndexed(designDoc = "reactiveParty", viewName = "all") -@N1qlSecondaryIndexed(indexName = "reactiveParty") -public interface ReactivePartyRepository extends ReactiveCouchbaseRepository { - - Flux findByAttendeesGreaterThanEqual(int minAttendees); - - Flux findByEventDateIs(Date targetDate); - - @View(designDocument = "reactiveParty", viewName = "byDate") - Flux findFirst3ByEventDateGreaterThanEqual(Date targetDate); - - Flux findAllByDescriptionNotNull(); - - Mono countAllByDescriptionNotNull(); - - @Query("SELECT MAX(attendees) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") - Mono findMaxAttendees(); - - @Query("SELECT `desc` FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") - Mono findSomeString(); - - @Query("SELECT count(*) + 5 FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") - Mono countCustomPlusFive(); - - @Query("SELECT count(*) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") - Mono countCustom(); - - @Query("SELECT 1 = 1") - Mono justABoolean(); - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $included || '%' AND attendees >= $min" + - " AND `desc` NOT LIKE '%' || $excluded || '%'") - Flux findAllWithNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long minimumAttendees); - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" + - " AND `desc` NOT LIKE '%' || $1 || '%'") - Flux findAllWithPositionalParams(String ex, String inc, long minimumAttendees); - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" + - " AND `desc` NOT LIKE '%' || $1 || '%' AND `desc` != \"this is \\\"$excluded\\\"\"") - Flux findAllWithPositionalParamsAndQuotedNamedParams(@Param("excluded") String ex, @Param("included") String inc, @Param("min") long min); - - Flux findByDescriptionOrName(String description, String name); - - @Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and eventDate = $1") - Flux getByEventDate(Date eventDate); - - Flux findByDescriptionStartingWith(String description); -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactivePartySortingRepository.java b/src/test/java/org/springframework/data/couchbase/repository/ReactivePartySortingRepository.java deleted file mode 100644 index 82ee6901..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactivePartySortingRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.springframework.data.couchbase.repository; - - -/** - * @author Subhashni Balakrishnan - */ -public interface ReactivePartySortingRepository extends ReactiveCouchbaseSortingRepository { -} - diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactivePlace.java b/src/test/java/org/springframework/data/couchbase/repository/ReactivePlace.java deleted file mode 100644 index 54705217..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactivePlace.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import com.couchbase.client.java.repository.annotation.Field; -import com.couchbase.client.java.repository.annotation.Id; -import org.springframework.data.couchbase.core.mapping.id.GeneratedValue; -import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy; - -/** - * @author David Kelly - */ -public class ReactivePlace { - @Id - @GeneratedValue(strategy= GenerationStrategy.UNIQUE) - public String id; - - @Field("name") - public String name; - - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public void setId(String id) { - this.id = id; - } - - public ReactivePlace(String name) { - this.name = name; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactivePlaceIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/ReactivePlaceIntegrationTests.java deleted file mode 100644 index c767c069..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactivePlaceIntegrationTests.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import com.couchbase.client.java.Bucket; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory; -import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import static junit.framework.TestCase.assertNull; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -/** - * @author David Kelly - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class) -@TestExecutionListeners(SimpleReactiveCouchbaseRepositoryListener.class) -public class ReactivePlaceIntegrationTests { - @Rule - public TestName testName = new TestName(); - - @Autowired - private Bucket client; - - @Autowired - private ReactiveRepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private ReactivePlaceRepository repository; - - @Before - public void setup() throws Exception { - ReactiveRepositoryFactorySupport factory = new ReactiveCouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = getRepositoryWithRetry(factory, ReactivePlaceRepository.class); - } - - @Test - public void shouldReturnGeneratedId() { - ReactivePlace place = new ReactivePlace("somePlace"); - assertNull(place.getId()); - ReactivePlace returned = repository.save(place).block(); - assertThat(returned.getId()).isNotNull(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactivePlaceRepository.java b/src/test/java/org/springframework/data/couchbase/repository/ReactivePlaceRepository.java deleted file mode 100644 index 08bd0ffa..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactivePlaceRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.core.query.ViewIndexed; -import reactor.core.publisher.Flux; - -import java.util.List; - -/** - * @author David Kelly - */ -@N1qlPrimaryIndexed -public interface ReactivePlaceRepository extends ReactiveCouchbaseRepository { - - @Query("#{#n1ql.selectEntity} WHERE name = $1") - Flux findByName(String name); - -} - diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactiveUser.java b/src/test/java/org/springframework/data/couchbase/repository/ReactiveUser.java deleted file mode 100644 index 53c5a8a2..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveUser.java +++ /dev/null @@ -1,67 +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.repository; - -import org.springframework.data.annotation.Id; - -/** - * @author Subhashni Balakrishnan - */ -public class ReactiveUser { - - @Id - private final String key; - - private final String username; - - private final int age; - - public ReactiveUser(String key, String username, int age) { - this.key = key; - this.username = username; - this.age = age; - } - - public String getUsername() { - return username; - } - - public String getKey() { - return key; - } - - public int getAge() { - return age; - } - - @Override - public String toString() { - return this.key; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ReactiveUser user = (ReactiveUser) o; - return key.equals(user.key); - } - - @Override - public int hashCode() { - return key.hashCode(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/ReactiveUserRepository.java b/src/test/java/org/springframework/data/couchbase/repository/ReactiveUserRepository.java deleted file mode 100644 index 4a5a873d..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/ReactiveUserRepository.java +++ /dev/null @@ -1,56 +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.repository; - -import com.couchbase.client.java.query.consistency.ScanConsistency; -import java.util.List; - -import com.couchbase.client.java.view.ViewQuery; - -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.core.query.View; -import org.springframework.data.couchbase.core.query.ViewIndexed; -import org.springframework.data.couchbase.core.query.WithConsistency; -import reactor.core.publisher.Flux; - -/** - * @author Subhashni Balakrishnan - */ -@ViewIndexed(designDoc = "reactiveUser", viewName = "all") -@N1qlSecondaryIndexed(indexName = "reactiveUser") -public interface ReactiveUserRepository extends ReactiveCouchbaseRepository { - - @View(designDocument = "user", viewName = "all") - Flux customViewQuery(ViewQuery query); - - @Query("#{#n1ql.selectEntity} WHERE username = $1 and #{#n1ql.filter}") - Flux findByUsername(String username); - - @Query("SELECT * FROM #{#n1ql.bucket} WHERE username = $1 and #{#n1ql.filter}") - Flux findByUsernameBadSelect(String username); - - @Query("#{#n1ql.selectEntity} WHERE username LIKE '%-#{3 + 1}' and #{#n1ql.filter}") - Flux findByUsernameWithSpelAndPlaceholder(); - - @Query - Flux findByUsernameRegexAndUsernameIn(String regex, List sample); - - Flux findByUsernameContains(String contains); - - Flux findByUsernameNear(String place);//this is to check that there's a N1QL derivation AND it fails -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/RepositoryIndexUsageTest.java b/src/test/java/org/springframework/data/couchbase/repository/RepositoryIndexUsageTest.java deleted file mode 100644 index 2503ca9f..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/RepositoryIndexUsageTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.data.domain.Sort.Direction; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.couchbase.repository.support.N1qlCouchbaseRepository; -import org.springframework.data.couchbase.repository.support.ViewMetadataProvider; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.error.DocumentDoesNotExistException; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.view.ViewQuery; -import com.couchbase.client.java.view.ViewResult; -import com.couchbase.client.java.view.ViewRow; - -public class RepositoryIndexUsageTest { - - private static final org.springframework.data.couchbase.core.query.Consistency CONSISTENCY = Consistency.STRONGLY_CONSISTENT; - - private CouchbaseOperations couchbaseOperations; - private N1qlCouchbaseRepository repository; - - @Before - public void initMocks() { - ViewRow mockCountRow1 = mock(ViewRow.class); - when(mockCountRow1.value()).thenReturn("100"); - when(mockCountRow1.id()).thenReturn("id1"); - ViewRow mockCountRow2 = mock(ViewRow.class); - when(mockCountRow2.value()).thenReturn("200"); - when(mockCountRow2.id()).thenReturn("id2"); - List allCountRows = Arrays.asList(mockCountRow1, mockCountRow2); - - ViewResult mockCountResult = mock(ViewResult.class); - when(mockCountResult.iterator()).thenReturn(allCountRows.iterator()); - - Bucket mockBucket = mock(Bucket.class); - when(mockBucket.name()).thenReturn("mockBucket"); - - CouchbaseConverter mockConverter = mock(CouchbaseConverter.class); - when(mockConverter.getTypeKey()).thenReturn("mockType"); - when(mockConverter.convertForWriteIfNeeded(any(Object.class))).thenAnswer( - invocation -> invocation.getArgument(0)); - - couchbaseOperations = mock(CouchbaseOperations.class); - when(couchbaseOperations.getDefaultConsistency()).thenReturn(CONSISTENCY); - when(couchbaseOperations.getCouchbaseBucket()).thenReturn(mockBucket); - when(couchbaseOperations.getConverter()).thenReturn(mockConverter); - when(couchbaseOperations.findByView(any(ViewQuery.class), any(Class.class))).thenReturn(allCountRows); - when(couchbaseOperations.findByN1QL(any(N1qlQuery.class), any(Class.class))).thenReturn(Collections.emptyList()); - when(couchbaseOperations.queryView(any(ViewQuery.class))).thenReturn(mockCountResult); - when(couchbaseOperations.queryN1QL(any(N1qlQuery.class))).thenReturn(null); - - CouchbaseEntityInformation metadata = mock(CouchbaseEntityInformation.class); - when(metadata.getJavaType()).thenReturn(String.class); - - repository = new N1qlCouchbaseRepository(metadata, couchbaseOperations); - repository.setViewMetadataProvider(mock(ViewMetadataProvider.class)); - } - - @Test - public void testFindAllUsesViewWithConfiguredConsistency() { - String expectedQueryParams = "ViewQuery(string/all){params=\"reduce=false&stale=false\"}"; - repository.findAll(); - - verify(couchbaseOperations, never()).queryView(any(ViewQuery.class)); - verify(couchbaseOperations, never()).findByN1QL(any(N1qlQuery.class), any(Class.class)); - verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class)); - ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(ViewQuery.class); - verify(couchbaseOperations).findByView(queryCaptor.capture(), any(Class.class)); - String sQuery = queryCaptor.getValue().toString(); - assertThat(sQuery).isEqualTo(expectedQueryParams); - } - - @Test - public void testFindAllKeysUsesViewWithConfiguredConsistency() { - String expectedQueryParams = "ViewQuery(string/all){params=\"reduce=false&stale=false\", keys=\"[\"someKey\"]\"}"; - repository.findAllById(Collections.singleton("someKey")); - - verify(couchbaseOperations, never()).queryView(any(ViewQuery.class)); - verify(couchbaseOperations, never()).findByN1QL(any(N1qlQuery.class), any(Class.class)); - verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class)); - ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(ViewQuery.class); - verify(couchbaseOperations).findByView(queryCaptor.capture(), any(Class.class)); - String sQuery = queryCaptor.getValue().toString(); - assertThat(sQuery).isEqualTo(expectedQueryParams); - } - - @Test - public void testCountUsesViewWithConfiguredConsistencyAndReduces() { - String expectedQueryParams = "ViewQuery(string/all){params=\"reduce=true&stale=false\"}"; - repository.count(); - - verify(couchbaseOperations, never()).findByView(any(ViewQuery.class), any(Class.class)); - verify(couchbaseOperations, never()).findByN1QL(any(N1qlQuery.class), any(Class.class)); - verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class)); - ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(ViewQuery.class); - verify(couchbaseOperations).queryView(queryCaptor.capture()); - String sQuery = queryCaptor.getValue().toString(); - assertThat(sQuery).isEqualTo(expectedQueryParams); - } - - @Test - public void testCountParsesAndAddsLongValuesFromRows() { - long count = repository.count(); - assertThat(count).isEqualTo(300L); - } - - @Test - public void testDeleteAllUsesViewWithConfiguredConsistency() { - String expectedQueryParams = "ViewQuery(string/all){params=\"reduce=false&stale=false\"}"; - repository.deleteAll(); - - verify(couchbaseOperations, never()).findByView(any(ViewQuery.class), any(Class.class)); - verify(couchbaseOperations, never()).findByN1QL(any(N1qlQuery.class), any(Class.class)); - verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class)); - ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(ViewQuery.class); - verify(couchbaseOperations).queryView(queryCaptor.capture()); - String sQuery = queryCaptor.getValue().toString(); - assertThat(sQuery).isEqualTo(expectedQueryParams); - } - - @Test - public void testFindAllSortedUsesN1qlWithConfiguredConsistencyAndOrderBy() { - String expectedOrderClause = "ORDER BY `length` ASC"; - Sort sort = Sort.by(Direction.ASC, "length"); - repository.findAll(sort); - - verify(couchbaseOperations, never()).findByView(any(ViewQuery.class), any(Class.class)); - verify(couchbaseOperations, never()).queryView(any(ViewQuery.class)); - verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class)); - ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(N1qlQuery.class); - verify(couchbaseOperations).findByN1QL(queryCaptor.capture(), any(Class.class)); - - JsonObject query = queryCaptor.getValue().n1ql(); - assertThat(query.getString("scan_consistency")) - .isEqualTo(CONSISTENCY.n1qlConsistency().n1ql()); - String statement = query.getString("statement"); - assertThat(statement.contains(expectedOrderClause)) - .as("Expected " + expectedOrderClause + " in " + statement).isTrue(); - } - - @Test - public void testFindAllPagedUsesUsesN1qlConfiguredConsistencyAndLimitOffset() { - String expectedLimitClause = "LIMIT 10 OFFSET 0"; - repository.findAll(PageRequest.of(0, 10)); - - verify(couchbaseOperations, never()).findByView(any(ViewQuery.class), any(Class.class)); - verify(couchbaseOperations, never()).queryView(any(ViewQuery.class)); - verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class)); - ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(N1qlQuery.class); - verify(couchbaseOperations).findByN1QL(queryCaptor.capture(), any(Class.class)); - - JsonObject query = queryCaptor.getValue().n1ql(); - assertThat(query.getString("scan_consistency")) - .isEqualTo(CONSISTENCY.n1qlConsistency().n1ql()); - String statement = query.getString("statement"); - assertThat(statement.contains(expectedLimitClause)) - .as("Expected " + expectedLimitClause + " in " + statement).isTrue(); - } - - @Test - public void testDeleteAllSwallowsDocumentDoesNotExistException() { - doThrow(new DataRetrievalFailureException("ignored", new DocumentDoesNotExistException())).when(couchbaseOperations).remove("id1"); - doThrow(new DataRetrievalFailureException("thrown")).when(couchbaseOperations).remove("id2"); - try { - repository.deleteAll(); - fail("Expected DataRetrievalFailureException on id2"); - } catch (DataRetrievalFailureException e) { - if (!"thrown".equals(e.getMessage())) { - fail("DataRetrievalFailureException caused by DocumentDoesNotExistException should have been ignored"); - } - } - verify(couchbaseOperations).remove("id1"); - verify(couchbaseOperations).remove("id2"); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryIntegrationTests.java deleted file mode 100644 index 3765d9aa..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryIntegrationTests.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicLong; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.dao.OptimisticLockingFailureException; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.Version; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.AsyncUtils; -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.document.JsonDocument; -import com.couchbase.client.java.error.CASMismatchException; -import com.couchbase.client.java.error.DocumentDoesNotExistException; -import com.couchbase.client.java.view.Stale; -import com.couchbase.client.java.view.ViewQuery; - -/** - * @author Michael Nitschinger - * @author Mark Paluch - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class) -public class SimpleCouchbaseRepositoryIntegrationTests { - - @Rule - public TestName testName = new TestName(); - - @Autowired - private Bucket client; - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private UserRepository repository; - private VersionedDataRepository versionedDataRepository; - - @Before - public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = getRepositoryWithRetry(factory, UserRepository.class); - versionedDataRepository = getRepositoryWithRetry(factory, VersionedDataRepository.class); - } - - private void remove(String key) { - try { - client.remove(key); - } catch (DocumentDoesNotExistException e) { - } - } - - @Test - public void simpleCrud() { - String key = "my_unique_user_key"; - User instance = new User(key, "foobar", 22); - repository.save(instance); - - Optional found = repository.findById(key); - assertThat(found.isPresent()).isTrue(); - - found.ifPresent(actual -> { - assertThat(actual.getKey()).isEqualTo(instance.getKey()); - assertThat(actual.getUsername()).isEqualTo(instance.getUsername()); - - assertThat(repository.existsById(key)).isTrue(); - repository.delete(actual); - }); - - assertThat(repository.findById(key).isPresent()).isFalse(); - assertThat(repository.existsById(key)).isFalse(); - } - - @Test - /** - * This test uses/assumes a default viewName called "all" that is configured on Couchbase. - */ - public void shouldFindAll() { - // do a non-stale query to populate data for testing. - client.query(ViewQuery.from("user", "all").stale(Stale.FALSE)); - - Iterable allUsers = repository.findAll(); - int size = 0; - for (User u : allUsers) { - size++; - assertThat(u.getKey()).isNotNull(); - assertThat(u.getUsername()).isNotNull(); - } - assertThat(size).isEqualTo(100); - } - - @Test - public void shouldCount() { - // do a non-stale query to populate data for testing. - client.query(ViewQuery.from("user", "all").stale(Stale.FALSE)); - - assertThat(repository.count()).isEqualTo(100); - } - - @Test - @Ignore("View based query with copy of params from a ViewQuery in the method parameter not implemented") - //TODO re-enable test once ViewQuery parameters other than designDoc/viewName can be copied - public void shouldFindCustom() { - Iterable users = repository.customViewQuery(ViewQuery.from("", "").limit(2).stale(Stale.FALSE)); - int size = 0; - for (User u : users) { - size++; - assertThat(u.getKey()).isNotNull(); - assertThat(u.getUsername()).isNotNull(); - } - assertThat(size).isEqualTo(2); - } - - @Test - public void shouldFindByUsernameUsingN1ql() { - User user = repository.findByUsername("uname-1"); - assertThat(user).isNotNull(); - assertThat(user.getKey()).isEqualTo("testuser-1"); - assertThat(user.getUsername()).isEqualTo("uname-1"); - } - - @Test - public void shouldFailFindByUsernameWithNoIdOrCas() { - try { - User user = repository.findByUsernameBadSelect("uname-1"); - fail("shouldFailFindByUsernameWithNoIdOrCas"); - } catch (CouchbaseQueryExecutionException e) { - assertThat(e.getMessage().contains("_ID")).as("_ID expected in exception " + e) - .isTrue(); - assertThat(e.getMessage().contains("_CAS")).as("_CAS expected in exception " + e) - .isTrue(); - } catch (Exception e) { - fail("CouchbaseQueryExecutionException expected"); - } - } - - @Test - public void shouldFindFromUsernameInlineWithSpelParsing() { - User user = repository.findByUsernameWithSpelAndPlaceholder(); - assertThat(user).isNotNull(); - assertThat(user.getKey()).isEqualTo("testuser-4"); - assertThat(user.getUsername()).isEqualTo("uname-4"); - } - - @Test - public void shouldFindFromDeriveQueryWithRegexpAndIn() { - User user = repository.findByUsernameRegexAndUsernameIn("uname-[123]", Arrays.asList("uname-2", "uname-4")); - assertThat(user).isNotNull(); - assertThat(user.getKey()).isEqualTo("testuser-2"); - assertThat(user.getUsername()).isEqualTo("uname-2"); - } - - @Test - public void shouldFindContainsWithoutAnnotation() { - List users = repository.findByUsernameContains("-9"); - assertThat(users).isNotNull(); - assertThat(users.isEmpty()).isFalse(); - for (User user : users) { - assertThat(user.getUsername().startsWith("uname-9")).isTrue(); - } - } - - @Test - public void shouldDefaultToN1qlQueryDerivation() { - try { - User u = repository.findByUsernameNear("london"); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - if (!e.getMessage().contains("N1QL")) { - fail(e.getMessage()); - } - } - } - - @Test - public void shouldTakeVersionIntoAccountWhenDoingMultipleUpdates() { - final String key = "versionedUserTest"; - VersionedData initial = new VersionedData(key, "ABCD"); - versionedDataRepository.save(initial); - assertThat(initial.version).isNotEqualTo(0L); - - Optional fetch1 = versionedDataRepository.findById(key); - - assertThat(fetch1.isPresent()).isTrue(); - fetch1.ifPresent(actual -> { - assertThat(actual).isNotSameAs(initial); - assertThat(initial.version).isEqualTo(actual.version); - }); - - VersionedData versionedData = fetch1.get(); - - JsonDocument bypass = client.get(key); - bypass.content().put("data", "BBBB"); - JsonDocument bypassed = client.upsert(bypass); - - assertThat(versionedData.version).isNotEqualTo(bypassed.cas()); - System.out.println(bypassed.cas()); - - try { - versionedData.setData("ZZZZ"); - versionedDataRepository.save(versionedData); - fail("Expected CAS failure"); - } catch (OptimisticLockingFailureException e) { - //success - assertThat(e.getCause() instanceof CASMismatchException) - .as("optimistic locking should have CASMismatchException as cause, got " + e - .getCause()).isTrue(); - } finally { - client.remove(key); - } - } - - // todo: investigate cause of intermittent failure - @Ignore("Fails intermittently (see DATACOUCH-452)") - @Test - public void shouldUpdateDocumentConcurrently() throws Exception { - final String key = testName.getMethodName(); - remove(key); - - final AtomicLong counter = new AtomicLong(); - final AtomicLong updatedCounter = new AtomicLong(); - VersionedData initial = new VersionedData(key, "value-initial"); - versionedDataRepository.save(initial); - assertThat(initial.version).isNotEqualTo(0L); - - Callable task = new Callable() { - @Override - public Void call() throws Exception { - boolean updated = false; - while(!updated) { - long counterValue = counter.incrementAndGet(); - VersionedData messageData = versionedDataRepository.findById(key).get(); - messageData.data = "value-" + counterValue; - try { - versionedDataRepository.save(messageData); - updated = true; - updatedCounter.incrementAndGet(); - } catch (OptimisticLockingFailureException e) { - } - } - return null; - } - }; - AsyncUtils.executeConcurrently(5, task); - - assertThat(versionedDataRepository.findById(key).get().data) - .isNotEqualTo(initial.data); - assertThat(updatedCounter.intValue()).isEqualTo(5); - } - - @Test - public void shouldFailOnMultipleConcurrentSaves() throws Exception { - final String key = testName.getMethodName(); - remove(key); - - final AtomicLong counter = new AtomicLong(); - final AtomicLong optimisticLockCounter = new AtomicLong(); - - Callable task = new Callable() { - @Override - public Void call() throws Exception { - long counterValue = counter.incrementAndGet(); - VersionedData messageData = new VersionedData(key, "value-" + counterValue); - try { - versionedDataRepository.save(messageData); - } catch (OptimisticLockingFailureException e) { - optimisticLockCounter.incrementAndGet(); - } - return null; - } - }; - - AsyncUtils.executeConcurrently(5, task); - - assertThat(optimisticLockCounter.intValue()).isEqualTo(4); - } - - - public interface VersionedDataRepository extends CouchbaseRepository { } - - @Document - public static class VersionedData { - - @Id - private final String key; - - @Version - public long version = 0L; - - private String data; - - public VersionedData(String key, String data) { - this.key = key; - this.data = data; - } - - public String getKey() { - return key; - } - - public String getData() { - return data; - } - - public void setData(String data) { - this.data = data; - } - - @Override - public String toString() { - return this.key + " " + this.data; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - VersionedData vd = (VersionedData) o; - return key.equals(vd.key); - } - - @Override - public int hashCode() { - return key.hashCode(); - } - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java b/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java deleted file mode 100644 index 6395e1bb..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import java.util.Collections; -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.view.DefaultView; -import com.couchbase.client.java.view.DesignDocument; -import com.couchbase.client.java.view.View; - -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -/** - * @author Michael Nitschinger - */ -public class SimpleCouchbaseRepositoryListener extends DependencyInjectionTestExecutionListener { - - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - populateTestData(client, clusterInfo); - createAndWaitForDesignDocs(client); - } - - private void populateTestData(Bucket client, ClusterInfo clusterInfo) { - CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); - - for (int i = 0; i < 100; i++) { - User u = new User("testuser-" + i, "uname-" + i, i); - template.save(u, PersistTo.MASTER, ReplicateTo.NONE); - } - - } - - private void createAndWaitForDesignDocs(Bucket client) { - String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository." + - "User\") { emit(null, null); } }"; - View view = DefaultView.create("all", mapFunction, "_count"); - List views = Collections.singletonList(view); - DesignDocument designDoc = DesignDocument.create("user", views); - client.bucketManager().upsertDesignDocument(designDoc); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryDeleteAllIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryDeleteAllIntegrationTests.java deleted file mode 100644 index d8a3407f..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryDeleteAllIntegrationTests.java +++ /dev/null @@ -1,89 +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.repository; - -import com.couchbase.client.java.Bucket; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory; -import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -/** - * @author David Kelly - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class) -@TestExecutionListeners(SimpleReactiveCouchbaseRepositoryListener.class) -public class SimpleReactiveCouchbaseRepositoryDeleteAllIntegrationTests { - // We do these tests here, rather in the simple crud tests, so we don't interact - // with those tests that make assumptions about what documents should be - // in the repo. - @Rule - public TestName testName = new TestName(); - - @Autowired - private Bucket client; - - @Autowired - private ReactiveRepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private ReactiveUserRepository repository; - - private long getCount() { - return repository.count().block().longValue(); - } - - @Before - public void setup() throws Exception { - ReactiveRepositoryFactorySupport factory = new ReactiveCouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = getRepositoryWithRetry(factory, ReactiveUserRepository.class); - } - - @Test - public void simpleDeleteAll() { - String key = "my_unique_user_key"; - - // in case there are other tests later, lets insure there is at least one - // User in the repository - ReactiveUser instance = new ReactiveUser(key, "foobar", 22); - repository.save(instance).block(); - - // we put a user in, lets be sure the count reflects that. - assertThat(getCount() > 0L).isTrue(); - - repository.deleteAll().block(); - - // after deleteAll, we should have a count of 0 - assertThat(getCount()).isEqualTo(0L); - - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryIntegrationTests.java deleted file mode 100644 index 64f879b2..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryIntegrationTests.java +++ /dev/null @@ -1,188 +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.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import java.util.Arrays; -import java.util.List; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException; -import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.couchbase.repository.support.ReactiveCouchbaseRepositoryFactory; -import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.error.DocumentDoesNotExistException; -import com.couchbase.client.java.view.Stale; -import com.couchbase.client.java.view.ViewQuery; - -/** - * @author Subhashni Balakrishnan - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class) -@TestExecutionListeners(SimpleReactiveCouchbaseRepositoryListener.class) -public class SimpleReactiveCouchbaseRepositoryIntegrationTests { - - @Rule - public TestName testName = new TestName(); - - @Autowired - private Bucket client; - - @Autowired - private ReactiveRepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private ReactiveUserRepository repository; - - @Before - public void setup() throws Exception { - ReactiveRepositoryFactorySupport factory = new ReactiveCouchbaseRepositoryFactory(operationsMapping, indexManager); - repository = getRepositoryWithRetry(factory, ReactiveUserRepository.class); - } - - private void remove(String key) { - try { - client.remove(key); - } catch (DocumentDoesNotExistException e) { - } - } - - @Test - public void simpleCrud() { - String key = "my_unique_user_key"; - ReactiveUser instance = new ReactiveUser(key, "foobar", 22); - repository.save(instance).block(); - - ReactiveUser found = repository.findById(key).block(); - assertThat(found.getKey()).isEqualTo(instance.getKey()); - assertThat(found.getUsername()).isEqualTo(instance.getUsername()); - - assertThat(repository.existsById(key).block()).isTrue(); - repository.delete(found).block(); - - assertThat(repository.findById(key).block()).isNull(); - assertThat(repository.existsById(key).block()).isFalse(); - } - - @Test - /** - * This test uses/assumes a default viewName called "all" that is configured on Couchbase. - */ - public void shouldFindAll() { - // do a non-stale query to populate data for testing. - client.query(ViewQuery.from("reactiveUser", "all").stale(Stale.FALSE)); - - List allUsers = repository.findAll().collectList().block(); - int size = 0; - for (ReactiveUser u : allUsers) { - size++; - assertThat(u.getKey()).isNotNull(); - assertThat(u.getUsername()).isNotNull(); - } - assertThat(size).isEqualTo(100); - } - - @Test - public void shouldCount() { - // do a non-stale query to populate data for testing. - client.query(ViewQuery.from("reactiveUser", "all").stale(Stale.FALSE)); - - assertThat(repository.count().block().toString()).isEqualTo("100"); - } - - @Test - public void shouldFindByUsernameUsingN1ql() { - ReactiveUser user = repository.findByUsername("reactiveuname-1").single().block(); - assertThat(user).isNotNull(); - assertThat(user.getKey()).isEqualTo("reactivetestuser-1"); - assertThat(user.getUsername()).isEqualTo("reactiveuname-1"); - } - - @Test - public void shouldFailFindByUsernameWithNoIdOrCas() { - try { - ReactiveUser user = repository.findByUsernameBadSelect("reactiveuname-1").single().block(); - fail("shouldFailFindByUsernameWithNoIdOrCas"); - } catch (CouchbaseQueryExecutionException e) { - assertThat(e.getMessage().contains("_ID")) - .as("_ID expected in exception " + e).isTrue(); - assertThat(e.getMessage().contains("_CAS")) - .as("_CAS expected in exception " + e).isTrue(); - } catch (Exception e) { - fail("CouchbaseQueryExecutionException expected"); - } - } - - @Test - public void shouldFindFromUsernameInlineWithSpelParsing() { - ReactiveUser user = repository.findByUsernameWithSpelAndPlaceholder().take(1).blockLast(); - assertThat(user).isNotNull(); - assert(user.getUsername().startsWith("reactive")); - assert(user.getUsername().startsWith("reactive")); - } - - @Test - public void shouldFindFromDeriveQueryWithRegexpAndIn() { - ReactiveUser user = repository.findByUsernameRegexAndUsernameIn("reactiveuname-[123]", Arrays.asList("reactiveuname-2", "reactiveuname-4")).take(1).blockLast(); - assertThat(user).isNotNull(); - assertThat(user.getKey()).isEqualTo("reactivetestuser-2"); - assertThat(user.getUsername()).isEqualTo("reactiveuname-2"); - } - - @Test - public void shouldFindContainsWithoutAnnotation() { - List users = repository.findByUsernameContains("reactive").collectList().block(); - assertThat(users).isNotNull(); - assertThat(users.isEmpty()).isFalse(); - for (ReactiveUser user : users) { - assertThat(user.getUsername().startsWith("reactive")).isTrue(); - } - } - - @Test - public void shouldDefaultToN1qlQueryDerivation() { - try { - ReactiveUser u = repository.findByUsernameNear("london").single().block(); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - if (!e.getMessage().contains("N1QL")) { - fail(e.getMessage()); - } - } - } - - - - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryListener.java b/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryListener.java deleted file mode 100644 index 48464643..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/SimpleReactiveCouchbaseRepositoryListener.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.springframework.data.couchbase.repository; - -import java.util.Collections; -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.view.DefaultView; -import com.couchbase.client.java.view.DesignDocument; -import com.couchbase.client.java.view.View; -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -/** - * @author Subhashni Balakrishnan - */ -public class SimpleReactiveCouchbaseRepositoryListener extends DependencyInjectionTestExecutionListener { - - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - populateTestData(client, clusterInfo); - createAndWaitForDesignDocs(client); - } - - private void populateTestData(Bucket client, ClusterInfo clusterInfo) { - RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(clusterInfo, client); - - for (int i = 0; i < 100; i++) { - ReactiveUser u = new ReactiveUser("reactivetestuser-" + i, "reactiveuname-" + i, i); - template.save(u, PersistTo.MASTER, ReplicateTo.NONE).subscribe(); - } - - } - - private void createAndWaitForDesignDocs(Bucket client) { - String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository." + - "ReactiveUser\") { emit(null, null); } }"; - View view = DefaultView.create("all", mapFunction, "_count"); - List views = Collections.singletonList(view); - DesignDocument designDoc = DesignDocument.create("reactiveUser", views); - client.bucketManager().upsertDesignDocument(designDoc); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/User.java b/src/test/java/org/springframework/data/couchbase/repository/User.java deleted file mode 100644 index e99c6c37..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/User.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import org.springframework.data.annotation.Id; - -/** - * @author Michael Nitschinger - * @author Simon Baslé - */ -public class User { - - @Id - private final String key; - - private final String username; - - private final int age; - - public User(String key, String username, int age) { - this.key = key; - this.username = username; - this.age = age; - } - - public String getUsername() { - return username; - } - - public String getKey() { - return key; - } - - public int getAge() { - return age; - } - - @Override - public String toString() { - return this.key; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - User user = (User) o; - return key.equals(user.key); - } - - @Override - public int hashCode() { - return key.hashCode(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/UserRepository.java b/src/test/java/org/springframework/data/couchbase/repository/UserRepository.java deleted file mode 100644 index c598cff8..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/UserRepository.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-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.repository; - -import java.util.List; - -import com.couchbase.client.java.view.ViewQuery; - -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.core.query.View; -import org.springframework.data.couchbase.core.query.ViewIndexed; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; - -/** - * @author Michael Nitschinger - */ -@ViewIndexed(designDoc = "user", viewName = "all") -@N1qlSecondaryIndexed(indexName = "User") -public interface UserRepository extends CouchbaseRepository { - - @View(designDocument = "user", viewName = "all") - Iterable customViewQuery(ViewQuery query); - - @Query("#{#n1ql.selectEntity} WHERE username = $1 and #{#n1ql.filter}") - User findByUsername(String username); - - @Query("SELECT * FROM #{#n1ql.bucket} WHERE username = $1 and #{#n1ql.filter} ") - User findByUsernameBadSelect(String username); - - @Query("#{#n1ql.selectEntity} WHERE username LIKE '%-4' and #{#n1ql.filter}") - User findByUsernameWithSpelAndPlaceholder(); - - @Query - User findByUsernameRegexAndUsernameIn(String regex, List sample); - - List findByUsernameContains(String contains); - - User findByUsernameNear(String place);//this is to check that there's a N1QL derivation AND it fails - - Page findByAgeGreaterThan(int minAge, Pageable pageable); - - Slice findByAgeLessThan(int maxAge, Pageable pageable); -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedApplicationConfig.java deleted file mode 100644 index 7252a4d1..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedApplicationConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.springframework.data.couchbase.repository.auditing; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; - -@Configuration -@EnableCouchbaseRepositories -@EnableCouchbaseAuditing(modifyOnCreate = false) -public class AuditedApplicationConfig extends IntegrationTestApplicationConfig { - - @Bean - public AuditedAuditorAware couchbaseAuditorAware() { - return new AuditedAuditorAware(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedAuditorAware.java b/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedAuditorAware.java deleted file mode 100644 index c8e3b9f6..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedAuditorAware.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.springframework.data.couchbase.repository.auditing; - -import org.springframework.data.domain.AuditorAware; - -import java.util.Optional; - -public class AuditedAuditorAware implements AuditorAware { - - private Optional auditor = Optional.of("auditor"); - - @Override - public Optional getCurrentAuditor() { - return auditor; - } - - public void setAuditor(String auditor) { - this.auditor = Optional.of(auditor); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedItem.java b/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedItem.java deleted file mode 100644 index 6b347631..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedItem.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.springframework.data.couchbase.repository.auditing; - -import java.util.Date; - -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.annotation.Version; -import org.springframework.data.couchbase.core.mapping.Document; - -@Document -public class AuditedItem { - - @Id - private final String id; - - private String value; - - @CreatedBy - private String creator; - - @LastModifiedBy - private String lastModifiedBy; - - @LastModifiedDate - private Date lastModification; - - @CreatedDate - private Date creationDate; - - @Version - private long version; - - public AuditedItem(String id, String value) { - this.id = id; - this.value = value; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getCreator() { - return creator; - } - - public void setCreator(String creator) { - this.creator = creator; - } - - public String getLastModifiedBy() { - return lastModifiedBy; - } - - public void setLastModifiedBy(String lastModifiedBy) { - this.lastModifiedBy = lastModifiedBy; - } - - public Date getLastModification() { - return lastModification; - } - - public void setLastModification(Date lastModification) { - this.lastModification = lastModification; - } - - public Date getCreationDate() { - return creationDate; - } - - public void setCreationDate(Date creationDate) { - this.creationDate = creationDate; - } - - public long getVersion() { - return version; - } - - public void setVersion(long version) { - this.version = version; - } - - @Override - public String toString() { - return "AuditedItem{" + - "id='" + id + '\'' + - ", value='" + value + '\'' + - ", creator='" + creator + '\'' + - ", lastModifiedBy='" + lastModifiedBy + '\'' + - ", lastModification=" + lastModification + - ", creationDate=" + creationDate + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AuditedItem that = (AuditedItem) o; - - if (!id.equals(that.id)) return false; - if (!value.equals(that.value)) return false; - if (creator != null ? !creator.equals(that.creator) : that.creator != null) return false; - if (lastModifiedBy != null ? !lastModifiedBy.equals(that.lastModifiedBy) : that.lastModifiedBy != null) - return false; - if (lastModification != null ? !lastModification.equals(that.lastModification) : that.lastModification != null) - return false; - return creationDate != null ? creationDate.equals(that.creationDate) : that.creationDate == null; - - } - - @Override - public int hashCode() { - int result = id.hashCode(); - result = 31 * result + value.hashCode(); - result = 31 * result + (creator != null ? creator.hashCode() : 0); - result = 31 * result + (lastModifiedBy != null ? lastModifiedBy.hashCode() : 0); - result = 31 * result + (lastModification != null ? lastModification.hashCode() : 0); - result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0); - return result; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedRepository.java b/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedRepository.java deleted file mode 100644 index b9c87d54..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditedRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.springframework.data.couchbase.repository.auditing; - -import org.springframework.data.couchbase.repository.CouchbaseRepository; - -public interface AuditedRepository extends CouchbaseRepository { - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditingIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditingIntegrationTests.java deleted file mode 100644 index 61cdce0b..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/auditing/AuditingIntegrationTests.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.springframework.data.couchbase.repository.auditing; - -import java.util.Date; -import java.util.Optional; - -import org.junit.After; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.TestContainerResource; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Simon Baslé - * @author Mark Paluch - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = AuditedApplicationConfig.class) -public class AuditingIntegrationTests { - - @Autowired - private AuditedRepository repository; - - @Autowired - private AuditedAuditorAware auditorAware; - - private static final String KEY = "auditedTest"; - - @After - public void cleanupAuditedEntity() { - repository.getCouchbaseOperations().getCouchbaseBucket().remove(KEY); - } - - @Test - public void testCreationEventIsRegistered() { - assertThat(repository.existsById(KEY)).isFalse(); - Date start = new Date(); - AuditedItem item = new AuditedItem(KEY, "creation"); - - auditorAware.setAuditor("auditor"); - repository.save(item); - Optional persisted = repository.findById(KEY); - - assertThat(persisted.isPresent()).isTrue(); - - persisted.ifPresent(actual -> { - - assertThat(actual.getCreationDate()).as("expected creation date audit trail") - .isNotNull(); - assertThat(actual.getCreator()).as("expected creation user audit trail") - .isEqualTo("auditor"); - - assertThat(actual.getCreationDate().after(start)).as("creation date is too early") - .isTrue(); - assertThat(actual.getCreationDate().before(new Date())) - .as("creation date is too late").isTrue(); - - assertThat(actual.getLastModification()) - .as("expected modification date to be empty").isNull(); - assertThat(actual.getLastModifiedBy()).as("expected modification user to be empty") - .isNull(); - - assertThat(actual.getVersion()).as("expected version to be non null").isNotNull(); - assertThat(actual.getVersion() > 0L).as("expected version to be greater than 0") - .isTrue(); - }); - } - - @Test - public void testUpdateEventIsRegistered() { - assertThat(repository.existsById(KEY)).isFalse(); - - String expectedCreator = "user1"; - String expectedUpdater = "user2"; - AuditedItem item = new AuditedItem(KEY, "creation"); - auditorAware.setAuditor(expectedCreator); - - repository.save(item); - AuditedItem created = repository.findById(KEY).orElse(null); - - auditorAware.setAuditor(expectedUpdater); - repository.save(item); - AuditedItem updated = repository.findById(KEY).orElse(null); - - assertThat(updated).as("expected entity to be persisted").isNotNull(); - assertThat(updated.getCreationDate()).as("expected creation date audit trail") - .isNotNull(); - assertThat(updated.getCreator()).as("expected creation user audit trail") - .isEqualTo(expectedCreator); - - assertThat(updated.getLastModification()).as("expected modification date audit trail") - .isNotNull(); - assertThat(updated.getCreationDate().before(updated.getLastModification())) - .as("expected modification date to be after creation date").isTrue(); - assertThat(updated.getLastModifiedBy()) - .as("expected modification user to be the modifier") - .isEqualTo(expectedUpdater); - - assertThat(updated.getVersion()).as("expected version to be non null").isNotNull(); - assertThat(updated.getVersion() > 0L).as("expected version to be greater than 0") - .isTrue(); - assertThat(created.getVersion() != updated.getVersion()) - .as("expected updated version to be different from the one at creation") - .isTrue(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonRepository.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonRepository.java deleted file mode 100644 index da233030..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiPersonRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import org.springframework.data.couchbase.repository.CouchbaseRepository; - -/** - * @author Mark Paluch - */ -public interface CdiPersonRepository extends CouchbaseRepository, CdiPersonFragment { - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiRepositoryClient.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiRepositoryClient.java deleted file mode 100644 index 2c324950..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiRepositoryClient.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import javax.inject.Inject; - -import com.couchbase.client.java.Bucket; - -/** - * @author Mark Paluch - */ -class CdiRepositoryClient { - - @Inject - private CdiPersonRepository cdiPersonRepository; - - @Inject - @OtherQualifier - @PersonDB - private QualifiedPersonRepository qualifiedPersonRepository; - - @Inject - private Bucket couchbaseClient; - - public CdiPersonRepository getCdiPersonRepository() { - return cdiPersonRepository; - } - - public QualifiedPersonRepository getQualifiedPersonRepository() { - return qualifiedPersonRepository; - } - - public Bucket getCouchbaseClient() { - return couchbaseClient; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiRepositoryIntegrationTests.java deleted file mode 100644 index 81f405bc..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/CdiRepositoryIntegrationTests.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.view.DefaultView; -import com.couchbase.client.java.view.DesignDocument; -import com.couchbase.client.java.view.View; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.test.context.ContextConfiguration; - -import javax.enterprise.inject.se.SeContainer; -import javax.enterprise.inject.se.SeContainerInitializer; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mark Paluch - */ -@SuppressWarnings("SpringJavaAutowiringInspection") -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = CdiRepositoryIntegrationTests.class) -public class CdiRepositoryIntegrationTests { - - private static SeContainer cdiContainer; - private CdiPersonRepository repository; - private QualifiedPersonRepository qualifiedPersonRepository; - private Bucket couchbaseClient; - - @BeforeClass - public static void init() { - cdiContainer = SeContainerInitializer.newInstance() // - .disableDiscovery() // - .addPackages(CdiRepositoryClient.class) // - .initialize(); - } - - @AfterClass - public static void shutdown() { - cdiContainer.close(); - } - - @Before - public void setUp() { - - CdiRepositoryClient repositoryClient = cdiContainer.select(CdiRepositoryClient.class).get(); - repository = repositoryClient.getCdiPersonRepository(); - qualifiedPersonRepository = repositoryClient.getQualifiedPersonRepository(); - - couchbaseClient = repositoryClient.getCouchbaseClient(); - createAndWaitForDesignDocs(couchbaseClient); - - } - - private void createAndWaitForDesignDocs(Bucket client) { - String mapFunction = "function (doc, meta) { if(doc._class == \"" + Person.class.getName() - + "\") { emit(null, null); } }"; - View view = DefaultView.create("all", mapFunction, "_count"); - List views = Collections.singletonList(view); - DesignDocument designDoc = DesignDocument.create("person", views); - client.bucketManager().upsertDesignDocument(designDoc); - } - - /** - * @see DATACOUCH-109 - */ - @Test - public void testCdiRepository() { - assertThat(repository).isNotNull(); - repository.deleteAll(); - - Person bean = new Person("key", "username"); - - repository.save(bean); - - assertThat(repository.existsById(bean.getId())).isTrue(); - - Optional retrieved = repository.findById(bean.getId()); - assertThat(retrieved.isPresent()).isTrue(); - retrieved.ifPresent(actual -> { - assertThat(actual.getName()).isEqualTo(bean.getName()); - assertThat(actual.getId()).isEqualTo(bean.getId()); - }); - } - - /** - * @see DATACOUCH-203 - */ - @Test - public void testQualifiedCdiRepository() { - assertThat(qualifiedPersonRepository).isNotNull(); - qualifiedPersonRepository.deleteAll(); - - Person bean = new Person("key", "username"); - - qualifiedPersonRepository.save(bean); - - assertThat(qualifiedPersonRepository.existsById(bean.getId())).isTrue(); - - Optional retrieved = qualifiedPersonRepository.findById(bean.getId()); - assertThat(retrieved.isPresent()).isTrue(); - retrieved.ifPresent(actual -> { - assertThat(actual.getName()).isEqualTo(bean.getName()); - assertThat(actual.getId()).isEqualTo(bean.getId()); - }); - } - - /** - * @see DATACOUCH-109 - */ - @Test - public void testCustomRepository() { - - assertThat(repository.returnTwo()).isEqualTo(2); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClientProducer.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClientProducer.java deleted file mode 100644 index 4d477179..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClientProducer.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Disposes; -import javax.enterprise.inject.Produces; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.Cluster; -import com.couchbase.client.java.CouchbaseCluster; - -import org.springframework.data.couchbase.config.CouchbaseBucketFactoryBean; - -/** - * Producer for {@link Bucket}. A default {@link CouchbaseCluster} with defaults - * from {@link CouchbaseBucketFactoryBean} are sufficient for our test. - * - * @author Mark Paluch - * @author Simon Baslé - */ -class CouchbaseClientProducer { - - @Produces - @ApplicationScoped - public Cluster cluster() { - return CouchbaseCluster.create(); - } - - @Produces - public Bucket createCouchbaseClient(Cluster cluster) throws Exception { - CouchbaseBucketFactoryBean couchbaseFactoryBean = new CouchbaseBucketFactoryBean(cluster, "protected", "protected", "password"); - couchbaseFactoryBean.afterPropertiesSet(); - return couchbaseFactoryBean.getObject(); - } - - public void close(@Disposes Bucket couchbaseClient) { - couchbaseClient.close(); - } - - public void close(@Disposes Cluster cluster) { - cluster.disconnect(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClusterInfoProducer.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClusterInfoProducer.java deleted file mode 100644 index d5397977..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseClusterInfoProducer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import javax.enterprise.inject.Produces; - -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 org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.CouchbaseTemplate; - -/** - * Produces a {@link ClusterInfo} instance for test usage. - * - * @author Simon Baslé - * @author Mark Paluch - */ -class CouchbaseClusterInfoProducer { - - @Produces - public ClusterInfo createClusterInfo(Cluster cluster) throws Exception { - return cluster.clusterManager("protected", "password").info(); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseOperationsProducer.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseOperationsProducer.java deleted file mode 100644 index d9a27add..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/CouchbaseOperationsProducer.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2014-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.repository.cdi; - -import javax.enterprise.inject.Produces; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.cluster.ClusterInfo; - -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.CouchbaseTemplate; - -/** - * Produces a {@link CouchbaseOperations} instance for test usage. - * - * @author Mark Paluch - */ -class CouchbaseOperationsProducer { - - @Produces - public CouchbaseOperations createCouchbaseOperations(Bucket couchbaseClient, ClusterInfo clusterInfo) throws Exception { - return new CouchbaseTemplate(clusterInfo, couchbaseClient); - } - - @Produces - @OtherQualifier - @PersonDB - public CouchbaseOperations createQualifiedCouchbaseOperations(Bucket couchbaseClient, ClusterInfo clusterInfo) throws Exception { - return new CouchbaseTemplate(clusterInfo, couchbaseClient); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/OtherQualifier.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/OtherQualifier.java deleted file mode 100644 index 967c6b71..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/OtherQualifier.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016-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.repository.cdi; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import javax.inject.Qualifier; - -/** - * @author Mark Paluch - * @see DATACOUCH-203 - */ -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) -@interface OtherQualifier { - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/PersonDB.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/PersonDB.java deleted file mode 100644 index e552c0d9..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/PersonDB.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2016-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.repository.cdi; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import javax.inject.Qualifier; - -/** - * @author Mark Paluch - * @see DATACOUCH-203 - */ -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) -@interface PersonDB { - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/cdi/QualifiedPersonRepository.java b/src/test/java/org/springframework/data/couchbase/repository/cdi/QualifiedPersonRepository.java deleted file mode 100644 index d92935c4..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/cdi/QualifiedPersonRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2016-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.repository.cdi; - -import org.springframework.data.repository.CrudRepository; - -/** - * @author Mark Paluch - * @see DATACOUCH-203 - */ -@PersonDB -@OtherQualifier -public interface QualifiedPersonRepository extends CrudRepository { - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtensionUnitTests.java b/src/test/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtensionUnitTests.java deleted file mode 100644 index 9857cd01..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/config/CouchbaseRepositoryConfigurationExtensionUnitTests.java +++ /dev/null @@ -1,72 +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.repository.config; - -import static org.assertj.core.api.Assertions.*; - -import java.io.Serializable; - -import org.junit.Test; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.repository.CouchbaseRepository; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; - -/** - * Unit tests for {@link CouchbaseRepositoryConfigurationExtension}. - * - * @author Oliver Gierke - */ -public class CouchbaseRepositoryConfigurationExtensionUnitTests { - - @Test // DATACOUCH-330 - public void atDocumentIsIdentifyingAnnotation() { - - CustomCouchbaseRepositoryConfigurationExtension extension = new CustomCouchbaseRepositoryConfigurationExtension(); - RepositoryMetadata metadata = DefaultRepositoryMetadata.getMetadata(ByAggregateRootAnnotationRepository.class); - - assertThat(extension.isStrictRepositoryCandidate(metadata)).isTrue(); - } - - @Test // DATACOUCH-330 - public void couchbaseRepositoryIsIdentifyingAnnotation() { - - CustomCouchbaseRepositoryConfigurationExtension extension = new CustomCouchbaseRepositoryConfigurationExtension(); - RepositoryMetadata metadata = DefaultRepositoryMetadata.getMetadata(ByRepository.class); - - assertThat(extension.isStrictRepositoryCandidate(metadata)).isTrue(); - } - - @Document - static class AggregateRoot {} - - interface ByAggregateRootAnnotationRepository extends Repository {} - - interface ByRepository extends CouchbaseRepository {}; - - static class CustomCouchbaseRepositoryConfigurationExtension extends CouchbaseRepositoryConfigurationExtension { - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#isStrictRepositoryCandidate(org.springframework.data.repository.core.RepositoryMetadata) - */ - @Override - protected boolean isStrictRepositoryCandidate(RepositoryMetadata metadata) { - return super.isStrictRepositoryCandidate(metadata); - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/config/ReactiveCouchbaseRepositoryConfigurationExtensionUnitTests.java b/src/test/java/org/springframework/data/couchbase/repository/config/ReactiveCouchbaseRepositoryConfigurationExtensionUnitTests.java deleted file mode 100644 index bfb3fd4f..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/config/ReactiveCouchbaseRepositoryConfigurationExtensionUnitTests.java +++ /dev/null @@ -1,116 +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.repository.config; - -import java.util.Collection; - -import org.junit.Test; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.core.env.Environment; -import org.springframework.core.env.StandardEnvironment; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.type.StandardAnnotationMetadata; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository; -import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; -import org.springframework.data.repository.config.RepositoryConfiguration; -import org.springframework.data.repository.config.RepositoryConfigurationSource; -import org.springframework.data.repository.reactive.ReactiveCrudRepository; -import org.springframework.data.repository.reactive.RxJava2CrudRepository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * Unit tests for {@link ReactiveCouchbaseRepositoryConfigurationExtension}. - * - * @author Mark Paluch - */ -public class ReactiveCouchbaseRepositoryConfigurationExtensionUnitTests { - - StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(Config.class, true); - ResourceLoader loader = new PathMatchingResourcePatternResolver(); - Environment environment = new StandardEnvironment(); - BeanDefinitionRegistry registry = new DefaultListableBeanFactory(); - - RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata, - EnableReactiveCouchbaseRepositories.class, loader, environment, registry); - - @Test // DATACOUCH-350 - public void isStrictMatchIfDomainTypeIsAnnotatedWithDocument() { - - ReactiveCouchbaseRepositoryConfigurationExtension extension = new ReactiveCouchbaseRepositoryConfigurationExtension(); - assertHasRepo(SampleRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true)); - } - - @Test // DATACOUCH-350 - public void isStrictMatchIfRepositoryExtendsStoreSpecificBase() { - - ReactiveCouchbaseRepositoryConfigurationExtension extension = new ReactiveCouchbaseRepositoryConfigurationExtension(); - assertHasRepo(StoreRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true)); - } - - @Test // DATACOUCH-350 - public void isNotStrictMatchIfDomainTypeIsNotAnnotatedWithDocument() { - - ReactiveCouchbaseRepositoryConfigurationExtension extension = new ReactiveCouchbaseRepositoryConfigurationExtension(); - assertDoesNotHaveRepo(UnannotatedRepository.class, - extension.getRepositoryConfigurations(configurationSource, loader, true)); - } - - private static void assertHasRepo(Class repositoryInterface, - Collection> configs) { - - for (RepositoryConfiguration config : configs) { - if (config.getRepositoryInterface().equals(repositoryInterface.getName())) { - return; - } - } - - fail("Expected to find config for repository interface " - .concat(repositoryInterface.getName()).concat(" but got ") - .concat(configs.toString())); - } - - private static void assertDoesNotHaveRepo(Class repositoryInterface, - Collection> configs) { - - for (RepositoryConfiguration config : configs) { - if (config.getRepositoryInterface().equals(repositoryInterface.getName())) { - fail("Expected not to find config for repository interface " - .concat(repositoryInterface.getName())); - } - } - } - - @EnableReactiveCouchbaseRepositories(considerNestedRepositories = true) - static class Config { - - } - - @Document - static class Sample {} - - static class Store {} - - interface SampleRepository extends ReactiveCrudRepository {} - - interface UnannotatedRepository extends RxJava2CrudRepository {} - - interface StoreRepository extends ReactiveCouchbaseRepository {} -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/extending/base/RepositoryBaseIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/extending/base/RepositoryBaseIntegrationTests.java deleted file mode 100644 index af7f63e0..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/extending/base/RepositoryBaseIntegrationTests.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2013-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.repository.extending.base; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.List; - -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.util.features.CouchbaseFeature; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.annotation.Id; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.repository.User; -import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; -import org.springframework.data.couchbase.repository.extending.base.impl.MyRepository; -import org.springframework.data.couchbase.repository.extending.base.impl.MyRepositoryImpl; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.test.context.ContextConfiguration; - -/** - * This tests custom implementation of base repository. - * - * @author Simon Baslé - */ -@SuppressWarnings("SpringJavaAutowiringInspection") -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration -public class RepositoryBaseIntegrationTests { - - private static CouchbaseOperations mockOpsA; - - @BeforeClass - public static void initMocks() { - ClusterInfo info = mock(ClusterInfo.class); - when(info.checkAvailable(any(CouchbaseFeature.class))).thenReturn(true); - - mockOpsA = mock(CouchbaseOperations.class); - when(mockOpsA.getCouchbaseClusterInfo()).thenReturn(info); - when(mockOpsA.exists(any(String.class))).thenReturn(true); - } - - @Autowired - ItemRepository repositoryA; - - @Autowired - UserRepository repositoryB; - - public interface ItemRepository extends MyRepository { - // - } - - public interface UserRepository extends MyRepository { - // - } - - @Configuration - @EnableCouchbaseRepositories(considerNestedRepositories = true, repositoryBaseClass = MyRepositoryImpl.class) - static class Config extends AbstractCouchbaseConfiguration { - - @Override - protected List getBootstrapHosts() { - return Arrays.asList("127.0.0.1"); - } - - @Override - protected String getBucketName() { - return "protected"; - } - - @Override - protected String getBucketPassword() { - return "password"; - } - - @Bean - public CouchbaseOperations couchbaseOperations() { - return mockOpsA; - } - - //this is for dev so it is ok to auto-create indexes - @Override - public IndexManager indexManager() { - return new IndexManager(); - } - } - - @Test - public void testRepositoryBaseIsChanged() { - assertThat(repositoryA).isNotNull(); - assertThat(repositoryB).isNotNull(); - - assertThat(repositoryA.sharedCustomMethod("toto")).isEqualTo(4); - assertThat(repositoryA.sharedCustomMethod("anna")).isEqualTo(4000); - - assertThat(repositoryB.sharedCustomMethod("sameInput")) - .isEqualTo(repositoryA.sharedCustomMethod("sameInput")); - assertThat(repositoryB.sharedCustomMethod("anna")) - .isEqualTo(repositoryA.sharedCustomMethod("anna")); - } - - private static class Item { - @Id - public String id; - - public String value; - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/extending/base/impl/MyRepository.java b/src/test/java/org/springframework/data/couchbase/repository/extending/base/impl/MyRepository.java deleted file mode 100644 index 9177a6b4..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/extending/base/impl/MyRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.springframework.data.couchbase.repository.extending.base.impl; - -import java.io.Serializable; - -import org.springframework.data.couchbase.repository.CouchbaseRepository; -import org.springframework.data.repository.NoRepositoryBean; - -@NoRepositoryBean -public interface MyRepository extends CouchbaseRepository { - - int sharedCustomMethod(ID id); -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/extending/base/impl/MyRepositoryImpl.java b/src/test/java/org/springframework/data/couchbase/repository/extending/base/impl/MyRepositoryImpl.java deleted file mode 100644 index 3958946e..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/extending/base/impl/MyRepositoryImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.springframework.data.couchbase.repository.extending.base.impl; - -import java.io.Serializable; - -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.repository.extending.base.impl.MyRepository; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.couchbase.repository.support.N1qlCouchbaseRepository; - -public class MyRepositoryImpl - extends N1qlCouchbaseRepository - implements MyRepository { - - public MyRepositoryImpl(CouchbaseEntityInformation metadata, CouchbaseOperations couchbaseOperations) { - super(metadata, couchbaseOperations); - } - - @Override - public int sharedCustomMethod(ID id) { - String key = String.valueOf(id); - if (key.startsWith("a")) - return key.length() * 1000; - return key.length(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyItem.java b/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyItem.java deleted file mode 100644 index 3e6cbbbd..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyItem.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.springframework.data.couchbase.repository.extending.method; - -import org.springframework.data.annotation.Id; - -class MyItem { - @Id - public final String id; - - public final String value; - - public MyItem(String id, String value) { - this.id = id; - this.value = value; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepository.java b/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepository.java deleted file mode 100644 index 1f1fe452..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.springframework.data.couchbase.repository.extending.method; - -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; -import org.springframework.data.couchbase.core.query.ViewIndexed; -import org.springframework.data.repository.CrudRepository; - -@N1qlPrimaryIndexed -@ViewIndexed(designDoc = "myItem", viewName = "all") -public interface MyRepository extends CrudRepository, MyRepositoryCustom { -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepositoryCustom.java b/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepositoryCustom.java deleted file mode 100644 index 006bf811..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepositoryCustom.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.springframework.data.couchbase.repository.extending.method; - -public interface MyRepositoryCustom { - - long customCountItems(); - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepositoryImpl.java b/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepositoryImpl.java deleted file mode 100644 index 50a32ecf..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/extending/method/MyRepositoryImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.springframework.data.couchbase.repository.extending.method; - -import java.util.List; - -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.consistency.ScanConsistency; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; -import org.springframework.data.couchbase.repository.Item; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.couchbase.repository.query.CountFragment; -import org.springframework.data.couchbase.repository.query.support.N1qlUtils; -import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation; - -public class MyRepositoryImpl implements MyRepositoryCustom { - - @Autowired - RepositoryOperationsMapping templateProvider; - - @Override - public long customCountItems() { - CouchbaseOperations template = templateProvider.resolve(MyRepository.class, Item.class); - - CouchbasePersistentEntity itemPersistenceEntity = (CouchbasePersistentEntity) - template.getConverter() - .getMappingContext() - .getRequiredPersistentEntity(MyItem.class); - - CouchbaseEntityInformation itemEntityInformation = - new MappingCouchbaseEntityInformation(itemPersistenceEntity); - - Statement countStatement = N1qlUtils.createCountQueryForEntity( - template.getCouchbaseBucket().name(), - template.getConverter(), - itemEntityInformation); - - ScanConsistency consistency = template.getDefaultConsistency().n1qlConsistency(); - N1qlParams queryParams = N1qlParams.build().consistency(consistency); - N1qlQuery query = N1qlQuery.simple(countStatement, queryParams); - - List countFragments = template.findByN1QLProjection(query, CountFragment.class); - - if (countFragments == null || countFragments.isEmpty()) { - return 0L; - } else { - return countFragments.get(0).count * -1L; - } - } - - public long count() { - return 100; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/extending/method/RepositoryCustomMethodIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/extending/method/RepositoryCustomMethodIntegrationTests.java deleted file mode 100644 index aa27fcc7..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/extending/method/RepositoryCustomMethodIntegrationTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2013-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.repository.extending.method; - -import java.util.Arrays; -import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This tests custom repository methods. - * - * @author Simon Baslé - */ -@SuppressWarnings("SpringJavaAutowiringInspection") -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration -public class RepositoryCustomMethodIntegrationTests { - - @Autowired - MyRepository repository; - - @Configuration - @EnableCouchbaseRepositories - static class Config extends AbstractCouchbaseConfiguration { - - @Override - protected List getBootstrapHosts() { - return Arrays.asList("127.0.0.1"); - } - - @Override - protected String getBucketName() { - return "protected"; - } - - @Override - protected String getBucketPassword() { - return "password"; - } - - //this is for dev so it is ok to auto-create indexes - @Override - public IndexManager indexManager() { - return new IndexManager(); - } - - @Override - protected Consistency getDefaultConsistency() { - return Consistency.STRONGLY_CONSISTENT; - } - } - - private static final String KEY = "customMethodTestItem"; - - @Before - public void initData() { - try { repository.deleteById(KEY); } catch (Exception e) { } - repository.save(new MyItem(KEY, "new item for custom count")); - } - - @After - public void clearData() { - repository.deleteById(KEY); - } - - @Test - public void testRepositoryCustomMethodIsWeavedIn() { - long customCount = repository.customCountItems(); - assertThat(customCount).isEqualTo(-1L); - } - - @Test - public void testRepositoryCrudMethodIsReplaced() { - long count = repository.count(); - assertThat(count).isEqualTo(100L); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryIntegrationTests.java deleted file mode 100644 index 9665e73b..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionRepositoryIntegrationTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2013-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.repository.feature; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.util.features.CouchbaseFeature; -import com.couchbase.client.java.util.features.Version; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.UnsupportedCouchbaseFeatureException; -import org.springframework.data.couchbase.repository.User; -import org.springframework.data.couchbase.repository.UserRepository; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; - -/** - * An integration test that validates feature checking with Java Config. - * - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = FeatureDetectionTestApplicationConfig.class) -public class FeatureDetectionRepositoryIntegrationTests { - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - @Autowired - private ClusterInfo clusterInfo; - - @Before - public void checkClusterInfo() { - Assume.assumeTrue(clusterInfo.getMinVersion() == Version.NO_VERSION); - } - - @Test - public void testN1qlIncompatibleClusterFailsFastForN1qlBasedRepository() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - try { - factory.getRepository(UserRepository.class); - fail("expected UnsupportedCouchbaseFeatureException"); - } catch (UnsupportedCouchbaseFeatureException e) { - assertThat(e.getFeature()).isEqualTo(CouchbaseFeature.N1QL); - } - } - - @Test - public void testN1qlIncompatibleClusterDoesntFailForViewBasedRepository() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - ViewOnlyUserRepository repository = getRepositoryWithRetry(factory, ViewOnlyUserRepository.class); - assertThat(repository).isNotNull(); - } - - @Test - public void testN1qlIncompatibleClusterTemplateFails() { - final CouchbaseOperations template = operationsMapping.getDefault(); - - N1qlQuery query = N1qlQuery.simple("SELECT * FROM `" + template.getCouchbaseBucket().name() + "`"); - try { - template.findByN1QL(query, User.class); - fail("expected findByN1QL to fail with UnsupportedCouchbaseFeatureException"); - } catch (UnsupportedCouchbaseFeatureException e) { - assertThat(e.getFeature()).isEqualTo(CouchbaseFeature.N1QL); - } - - try { - template.findByN1QLProjection(query, User.class); - fail("expected findByN1QLProjection to fail with UnsupportedCouchbaseFeatureException"); - } catch (UnsupportedCouchbaseFeatureException e) { - assertThat(e.getFeature()).isEqualTo(CouchbaseFeature.N1QL); - } - - try { - template.queryN1QL(query); - fail("expected queryN1QL to fail with UnsupportedCouchbaseFeatureException"); - } catch (UnsupportedCouchbaseFeatureException e) { - assertThat(e.getFeature()).isEqualTo(CouchbaseFeature.N1QL); - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionTestApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionTestApplicationConfig.java deleted file mode 100644 index e3764bbd..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/feature/FeatureDetectionTestApplicationConfig.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.springframework.data.couchbase.repository.feature; - -import java.util.Collections; -import java.util.List; - -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.cluster.DefaultClusterInfo; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.env.CouchbaseEnvironment; -import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.WriteResultChecking; -import org.springframework.data.couchbase.repository.support.IndexManager; - -@Configuration -public class FeatureDetectionTestApplicationConfig extends AbstractCouchbaseConfiguration { - - @Bean - public String couchbaseAdminUser() { - return "Administrator"; - } - - @Bean - public String couchbaseAdminPassword() { - return "password"; - } - - @Override - protected List getBootstrapHosts() { - return Collections.singletonList("127.0.0.1"); - } - - @Override - protected String getBucketName() { - return "protected"; - } - - @Override - protected String getBucketPassword() { - return "password"; - } - - - @Override - protected CouchbaseEnvironment getEnvironment() { - return DefaultCouchbaseEnvironment.builder() - .connectTimeout(10000) - .kvTimeout(10000) - .queryTimeout(10000) - .viewTimeout(10000) - .build(); - } - - @Override - public CouchbaseTemplate couchbaseTemplate() throws Exception { - CouchbaseTemplate template = super.couchbaseTemplate(); - template.setWriteResultChecking(WriteResultChecking.LOG); - return template; - } - - @Override - public ClusterInfo couchbaseClusterInfo() throws Exception { - return new DefaultClusterInfo(JsonObject.empty()); - } - - //this is for dev so it is ok to auto-create indexes - @Override - public IndexManager indexManager() { - return new IndexManager(); - } - - //change the name of the field that will hold type information - @Override - public String typeKey() { - return "javaClass"; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/feature/ViewOnlyUserRepository.java b/src/test/java/org/springframework/data/couchbase/repository/feature/ViewOnlyUserRepository.java deleted file mode 100644 index edc8a7dc..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/feature/ViewOnlyUserRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.springframework.data.couchbase.repository.feature; - -import org.springframework.data.couchbase.repository.User; -import org.springframework.data.repository.CrudRepository; - -public interface ViewOnlyUserRepository extends CrudRepository { -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/index/AnotherIndexedUserRepository.java b/src/test/java/org/springframework/data/couchbase/repository/index/AnotherIndexedUserRepository.java deleted file mode 100644 index 70c68f42..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/index/AnotherIndexedUserRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.springframework.data.couchbase.repository.index; - -import java.util.List; - -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.core.query.ViewIndexed; -import org.springframework.data.couchbase.repository.CouchbaseRepository; -import org.springframework.data.couchbase.repository.User; - -@N1qlSecondaryIndexed(indexName = IndexedRepositoryIntegrationTests.IGNORED_SECONDARY) -@ViewIndexed(designDoc = IndexedRepositoryIntegrationTests.VIEW_DOC, viewName = IndexedRepositoryIntegrationTests.IGNORED_VIEW_NAME) -public interface AnotherIndexedUserRepository extends CouchbaseRepository { - - public List findByAge(int age); -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/index/IndexedFooRepository.java b/src/test/java/org/springframework/data/couchbase/repository/index/IndexedFooRepository.java deleted file mode 100644 index c6f7e615..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/index/IndexedFooRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.springframework.data.couchbase.repository.index; - -import org.springframework.data.annotation.Id; -import org.springframework.data.couchbase.core.mapping.Document; -import org.springframework.data.couchbase.core.query.ViewIndexed; -import org.springframework.data.couchbase.repository.CouchbaseRepository; - -@ViewIndexed(designDoc = "foo") -public interface IndexedFooRepository extends CouchbaseRepository { - - @Document - final class Foo { - @Id - private String id; - - private String value1; - - private int value2; - - public Foo(String id, String value1, int value2) { - this.id = id; - this.value1 = value1; - this.value2 = value2; - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/index/IndexedRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/index/IndexedRepositoryIntegrationTests.java deleted file mode 100644 index 7fb4b16f..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/index/IndexedRepositoryIntegrationTests.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2013-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.repository.index; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -import java.util.Arrays; - -import com.couchbase.client.java.error.DesignDocumentDoesNotExistException; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.N1qlQueryResult; -import com.couchbase.client.java.view.DesignDocument; -import com.couchbase.client.java.view.View; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -/** - * This tests automatic index creation features in the Couchbase connector. - * Automatic index creation is performed before construction of the repository implementation. - * - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(IndexedRepositoryTestListener.class) -public class IndexedRepositoryIntegrationTests { - - public static final String SECONDARY = "autogeneratedIndexIndexedUserN1qlSecondary"; - public static final String VIEW_DOC = "autogeneratedIndex"; - public static final String VIEW_NAME = "IndexedUserView"; - - public static final String IGNORED_VIEW_NAME = "AnotherIndexedUserView"; - public static final String IGNORED_SECONDARY = "AnotherIndexedUserN1qlSecondary"; - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private CouchbaseOperations template; - private RepositoryFactorySupport factory; - - private RepositoryFactorySupport ignoringIndexFactory; - private IndexManager ignoringIndexManager = new IndexManager(false, false, false); - - @Before - public void setup() throws Exception { - factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - template = operationsMapping.getDefault(); - ignoringIndexFactory = new CouchbaseRepositoryFactory(operationsMapping, ignoringIndexManager); - } - - @Test - public void shouldFindN1qlPrimaryIndex() { - IndexedUserRepository repository = getRepositoryWithRetry(factory, IndexedUserRepository.class); - - String bucket = template.getCouchbaseBucket().name(); - N1qlQuery existQuery = N1qlQuery.simple("SELECT 1 FROM `"+ bucket +"`"); - N1qlQueryResult exist = template.queryN1QL(existQuery); - - assertThat(exist.finalSuccess()).isTrue(); - } - - @Test - public void shouldFindN1qlSecondaryIndex() { - IndexedUserRepository repository = getRepositoryWithRetry(factory, IndexedUserRepository.class); - - String bucket = template.getCouchbaseBucket().name(); - N1qlQuery existQuery = N1qlQuery.simple("SELECT 1 FROM `"+ bucket +"` USE INDEX (" + SECONDARY +")"); - N1qlQueryResult exist = template.queryN1QL(existQuery); - - assertThat(exist.finalSuccess()).isTrue(); - } - - @Test - public void shouldFindViewIndex() { - IndexedUserRepository repository = getRepositoryWithRetry(factory, IndexedUserRepository.class); - - DesignDocument designDoc = null; - try { - designDoc = template.getCouchbaseBucket() - .bucketManager() - .getDesignDocument(VIEW_DOC); - } catch(DesignDocumentDoesNotExistException ex) { - - } - - assertThat(designDoc).isNotNull(); - for (View view : designDoc.views()) { - if (view.name().equals(VIEW_NAME)) return; - } - fail("View not found"); - } - @Test - public void shouldNotFindN1qlSecondaryIndexWithIgnoringIndexManager() { - AnotherIndexedUserRepository repository = getRepositoryWithRetry(ignoringIndexFactory, AnotherIndexedUserRepository.class); - - String bucket = template.getCouchbaseBucket().name(); - N1qlQuery existQuery = N1qlQuery.simple("SELECT 1 FROM `"+ bucket +"` USE INDEX (" + IGNORED_SECONDARY +")"); - N1qlQueryResult exist = template.queryN1QL(existQuery); - - assertThat(exist.finalSuccess()).isFalse(); - } - - @Test - public void shouldNotFindViewIndexWithIgnoringIndexManager() { - AnotherIndexedUserRepository repository = getRepositoryWithRetry(ignoringIndexFactory, AnotherIndexedUserRepository.class); - - DesignDocument designDoc = null; - try { - designDoc = template.getCouchbaseBucket() - .bucketManager() - .getDesignDocument(VIEW_DOC); - } catch(DesignDocumentDoesNotExistException ex) { - //ignored - } - - if (designDoc != null) { - for (View view : designDoc.views()) { - if (view.name().equals(IGNORED_VIEW_NAME)) fail("Found unexpected " + IGNORED_VIEW_NAME); - } - } - } - - @Test - public void shouldFindListOfIdsThroughDefaulViewIndexed() { - IndexedFooRepository.Foo foo1 = new IndexedFooRepository.Foo("foo1", "foo", 1); - IndexedFooRepository.Foo foo2 = new IndexedFooRepository.Foo("foo2", "bar", 2); - - IndexedFooRepository repository = getRepositoryWithRetry(factory, IndexedFooRepository.class); - - DesignDocument designDoc = template.getCouchbaseBucket() - .bucketManager() - .getDesignDocument("foo"); - - assertThat(designDoc).isNotNull(); - boolean foundView = false; - for (View view : designDoc.views()) { - if (view.name().equals("all")) { - foundView = true; - break; - } - } - assertThat(foundView).as("Expected to find view \"all\" on design document \"foo\"") - .isTrue(); - - repository.save(foo1); - repository.save(foo2); - - int count = 0; - for (Object o : repository.findAllById(Arrays.asList("foo1", "foo2"))) { - count++; - } - assertThat(count).isEqualTo(2L); - count = 0; - for (Object o : repository.findAllById(Arrays.asList("foo1", "foo3"))) { - count++; - } - assertThat(count).isEqualTo(1L); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/index/IndexedRepositoryTestListener.java b/src/test/java/org/springframework/data/couchbase/repository/index/IndexedRepositoryTestListener.java deleted file mode 100644 index 626ec0f0..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/index/IndexedRepositoryTestListener.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.springframework.data.couchbase.repository.index; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.error.DesignDocumentDoesNotExistException; -import com.couchbase.client.java.query.Index; -import com.couchbase.client.java.query.N1qlQuery; - -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -/** - * A test listener that will remove the indexes created in {@link IndexedRepositoryIntegrationTests} before test case is run. - * - * @author Simon Baslé - */ -public class IndexedRepositoryTestListener extends DependencyInjectionTestExecutionListener { - - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - try { - client.bucketManager().removeDesignDocument(IndexedRepositoryIntegrationTests.VIEW_DOC); - client.bucketManager().removeDesignDocument("foo"); - } catch (DesignDocumentDoesNotExistException ex) { - //ignore - } - client.query(N1qlQuery.simple(Index.dropPrimaryIndex(client.name()))); - client.query(N1qlQuery.simple(Index.dropIndex(client.name(), IndexedRepositoryIntegrationTests.SECONDARY))); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/index/IndexedUserRepository.java b/src/test/java/org/springframework/data/couchbase/repository/index/IndexedUserRepository.java deleted file mode 100644 index a4b8e7d7..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/index/IndexedUserRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.springframework.data.couchbase.repository.index; - -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.CouchbaseRepository; -import org.springframework.data.couchbase.repository.User; - -@N1qlPrimaryIndexed -@N1qlSecondaryIndexed(indexName = IndexedRepositoryIntegrationTests.SECONDARY) -@ViewIndexed(designDoc = IndexedRepositoryIntegrationTests.VIEW_DOC, viewName = IndexedRepositoryIntegrationTests.VIEW_NAME) -public interface IndexedUserRepository extends CouchbaseRepository { -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/join/Author.java b/src/test/java/org/springframework/data/couchbase/repository/join/Author.java deleted file mode 100644 index f612d20b..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/join/Author.java +++ /dev/null @@ -1,72 +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.repository.join; -import java.util.List; - -import com.couchbase.client.java.repository.annotation.Field; -import org.springframework.data.annotation.Id; -import org.springframework.data.couchbase.core.query.FetchType; -import org.springframework.data.couchbase.core.query.N1qlJoin; -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; - -/** - * Author test class for N1QL Join tests - * - * @author Tayeb Chlyah - */ -@N1qlPrimaryIndexed -public class Author { - @Id - String id; - - @Field("name") - String name; - - @N1qlJoin(on = "lks.name=rks.authorName", fetchType = FetchType.IMMEDIATE) - List books; - - @N1qlJoin(on = "lks.name=rks.name", fetchType = FetchType.IMMEDIATE) - Address address; - - public Author(String id, String name) { - this.id = id; - this.name = name; - } - - public String getId() { - return this.id; - } - - public String getName() { - return this.name; - } - - public void setBooks(List books) { - this.books = books; - } - - public List getBooks() { - return books; - } - - public Address getAddress() { - return address; - } - - public void setAddress(Address address) { - this.address = address; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/join/AuthorAndBookPopulatorListener.java b/src/test/java/org/springframework/data/couchbase/repository/join/AuthorAndBookPopulatorListener.java deleted file mode 100644 index 33351c3d..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/join/AuthorAndBookPopulatorListener.java +++ /dev/null @@ -1,53 +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.repository.join; - -import com.couchbase.client.java.Bucket; -import com.couchbase.client.java.cluster.ClusterInfo; -import org.springframework.data.couchbase.config.BeanNames; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; - -/** - * Populates author and book documents for N1ql Join tests - * - * @author Tayeb Chlyah - */ -public class AuthorAndBookPopulatorListener extends DependencyInjectionTestExecutionListener { - - @Override - public void beforeTestClass(final TestContext testContext) throws Exception { - Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); - ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); - populateTestData(client, clusterInfo); - } - - void populateTestData(Bucket client, ClusterInfo clusterInfo) { - CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); - for(int i=0;i<5;i++) { - Author author = new Author("Author" + i,"foo"+ i); - template.save(author); - for (int j=0;j<5;j++) { - Book book = new Book("Book" + i+j, "foo"+i, ""); - template.save(book); - } - Address address = new Address("Address" + i, "foo" + i, "bar"); - template.save(address); - } - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/join/Book.java b/src/test/java/org/springframework/data/couchbase/repository/join/Book.java deleted file mode 100644 index b26055a2..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/join/Book.java +++ /dev/null @@ -1,48 +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.repository.join; - -import org.springframework.data.annotation.Id; - -/** - * Book test class for N1QL Join tests - */ -public class Book { - @Id - String name; - - String authorName; - - String description; - - public Book(String name, String authorName, String description) { - this.name = name; - this.authorName = authorName; - this.description = description; - } - - public String getName() { - return this.name; - } - - public String getAuthorName() { - return this.authorName; - } - - public String getDescription() { - return this.description; - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/join/N1qlJoinIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/join/N1qlJoinIntegrationTests.java deleted file mode 100644 index 611aa406..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/join/N1qlJoinIntegrationTests.java +++ /dev/null @@ -1,88 +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.repository.join; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.data.couchbase.CouchbaseTestHelper.getRepositoryWithRetry; - -/** - * N1ql Join tests - * - * @author Subhashni Balakrishnan - * @author Tayeb Chlyah - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) -@TestExecutionListeners(listeners = {AuthorAndBookPopulatorListener.class}) -public class N1qlJoinIntegrationTests { - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - private BookRepository bookRepository; - - private AuthorRepository authorRepository; - - private AddressRepository addressRepository; - - @Before - public void setup() throws Exception { - RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager); - bookRepository = getRepositoryWithRetry(factory, BookRepository.class); - authorRepository = getRepositoryWithRetry(factory, AuthorRepository.class); - addressRepository = getRepositoryWithRetry(factory, AddressRepository.class); - } - - @Test - public void testN1qlJoin() { - Author a = authorRepository.findById("Author" + 1).get(); - assertThat(a.books.size() == 5).isTrue(); - for(Book b:a.books) { - assertThat(b.authorName).as("Book Join on author name mismatch") - .isEqualTo(a.name); - } - assertThat(a.address).isNotNull(); - assertThat(a.address.name).as("Address Join on author name mismatch") - .isEqualTo(a.name); - } - - @Test - public void testN1qlJoinWithNoResults() { - final String name = "testN1qlJoinWithNoResults"; - Author a = new Author(name, name); - authorRepository.save(a); - - Author saveda = authorRepository.findById(name).get(); - assertThat(saveda.books.isEmpty()).isTrue(); - assertThat(saveda.address).isNull(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQueryTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQueryTest.java deleted file mode 100644 index bf5592b5..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQueryTest.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright 2015-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.repository.query; - -import static com.couchbase.client.java.query.Select.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.stream.Stream; - -import com.couchbase.client.java.document.json.JsonValue; -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.core.query.WithConsistency; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.repository.query.ParameterAccessor; -import org.springframework.data.repository.query.QueryMethod; - -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.document.json.JsonObject; -import com.couchbase.client.java.query.N1qlParams; -import com.couchbase.client.java.query.N1qlQuery; -import com.couchbase.client.java.query.ParameterizedN1qlQuery; -import com.couchbase.client.java.query.SimpleN1qlQuery; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import org.springframework.data.repository.query.ReturnedType; - -/** - * Unit tests for {@link AbstractN1qlBasedQuery}. - * - * @author Simon Basle - * @author Subhashni Balakrishnan - * @author Oliver Gierke - */ -public class AbstractN1qlBasedQueryTest { - - CouchbaseMappingContext context = new CouchbaseMappingContext(); - ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory(); - RepositoryMetadata metadata = DefaultRepositoryMetadata.getMetadata(SampleRepository.class); - - @Test - public void testEmptyArgumentsShouldProduceSimpleN1qlQuery() throws Exception { - Statement st = select("*"); - N1qlQuery query = AbstractN1qlBasedQuery.buildQuery(st, JsonArray.empty(), ScanConsistency.NOT_BOUNDED); - JsonObject queryObject = query.n1ql(); - - assertThat(query instanceof SimpleN1qlQuery).isTrue(); - assertThat(query.statement().toString()).isEqualTo(st.toString()); - assertThat(query.params()) - .isEqualTo(N1qlParams.build().consistency(ScanConsistency.NOT_BOUNDED)); - assertThat(queryObject.containsKey("args")).isFalse(); - } - - @Test - public void testSimpleArgumentShouldProduceParametrizedQuery() throws Exception { - Statement st = select("*"); - List params = new ArrayList(2); - params.add("test"); - JsonArray placeholderValues = JsonArray.from(params); - N1qlQuery query = AbstractN1qlBasedQuery.buildQuery(st, placeholderValues, ScanConsistency.NOT_BOUNDED); - JsonObject queryObject = query.n1ql(); - - assertThat(query instanceof ParameterizedN1qlQuery).isTrue(); - assertThat(query.statement().toString()).isEqualTo(st.toString()); - assertThat(query.params()) - .isEqualTo(N1qlParams.build().consistency(ScanConsistency.NOT_BOUNDED)); - assertThat(queryObject.containsKey("args")).isTrue(); - JsonArray args = queryObject.getArray("args"); - assertThat(args.size()).isEqualTo(1); - assertThat(args.get(0)).isEqualTo("test"); - } - - @Test - public void testMultipleArgumentsShouldProduceParametrizedQuery() throws Exception { - Statement st = select("*"); - List params = new ArrayList(2); - params.add(123L); - params.add("test"); - JsonArray placeholderValues = JsonArray.from(params); - N1qlQuery query = AbstractN1qlBasedQuery.buildQuery(st, placeholderValues, ScanConsistency.NOT_BOUNDED); - JsonObject queryObject = query.n1ql(); - - assertThat(query instanceof ParameterizedN1qlQuery).isTrue(); - assertThat(query.statement().toString()).isEqualTo(st.toString()); - assertThat(query.params()) - .isEqualTo(N1qlParams.build().consistency(ScanConsistency.NOT_BOUNDED)); - assertThat(queryObject.containsKey("args")).isTrue(); - JsonArray args = queryObject.getArray("args"); - assertThat(args.size()).isEqualTo(2); - assertThat(args.get(0)).isEqualTo(123L); - assertThat(args.get(1)).isEqualTo("test"); - } - - @Test - public void shouldChooseCollectionExecutionWhenCollectionType() throws Exception { - - Method method = SampleRepository.class.getMethod("findAll"); - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, projectionFactory, context); - - N1qlQuery query = Mockito.mock(N1qlQuery.class); - Pageable pageable = Mockito.mock(Pageable.class); - AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class); - when(mock.executeDependingOnType(any(N1qlQuery.class), any(N1qlQuery.class), any(QueryMethod.class), any(Pageable.class), any(Class.class))) - .thenCallRealMethod(); - - mock.executeDependingOnType(query, query, queryMethod, pageable, Object.class); - verify(mock).executeCollection(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeEntity(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeStream(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executePaged(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSliced(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSingleProjection(any(N1qlQuery.class)); - } - - @Test - public void shouldChooseEntityExecutionWhenEntityType() throws Exception { - - Method method = SampleRepository.class.getMethod("findById", Integer.class); - - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, projectionFactory, context); - - N1qlQuery query = Mockito.mock(N1qlQuery.class); - Pageable pageable = Mockito.mock(Pageable.class); - AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class); - when(mock.executeDependingOnType(any(N1qlQuery.class), any(N1qlQuery.class), any(QueryMethod.class), any(Pageable.class), any(Class.class))) - .thenCallRealMethod(); - - mock.executeDependingOnType(query, query, queryMethod, pageable, Sample.class); - verify(mock, never()).executeCollection(any(N1qlQuery.class), any(Class.class)); - verify(mock).executeEntity(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeStream(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executePaged(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSliced(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSingleProjection(any(N1qlQuery.class)); - } - - @Test - public void shouldChooseStreamExecutionWhenStreamType() throws Exception { - - Method method = SampleRepository.class.getMethod("streamAll"); - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, projectionFactory, context); - - N1qlQuery query = Mockito.mock(N1qlQuery.class); - Pageable pageable = Mockito.mock(Pageable.class); - AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class); - when(mock.executeDependingOnType(any(N1qlQuery.class), any(N1qlQuery.class), any(QueryMethod.class), any(Pageable.class), - any(Class.class))) - .thenCallRealMethod(); - - mock.executeDependingOnType(query, query, queryMethod, pageable, Sample.class); - verify(mock, never()).executeCollection(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeEntity(any(N1qlQuery.class), any(Class.class)); - verify(mock).executeStream(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executePaged(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSliced(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSingleProjection(any(N1qlQuery.class)); - } - - @Test - public void shouldChoosePagedExecutionWhenPageType() throws Exception { - - Method method = SampleRepository.class.getMethod("findAllPaged", Pageable.class); - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, projectionFactory, context); - - N1qlQuery query = Mockito.mock(N1qlQuery.class); - Pageable pageable = Mockito.mock(Pageable.class); - AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class); - when(mock.executeDependingOnType(any(N1qlQuery.class), any(N1qlQuery.class), any(QueryMethod.class), any(Pageable.class), - any(Class.class))) - .thenCallRealMethod(); - - mock.executeDependingOnType(query, query, queryMethod, pageable, Sample.class); - - verify(mock, never()).executeCollection(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeEntity(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeStream(any(N1qlQuery.class), any(Class.class)); - verify(mock).executePaged(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSliced(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSingleProjection(any(N1qlQuery.class)); - } - - @Test - public void shouldChooseSlicedExecutionWhenSliceType() throws Exception { - - Method method = SampleRepository.class.getMethod("findAllSliced", Pageable.class); - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, projectionFactory, context); - - N1qlQuery query = Mockito.mock(N1qlQuery.class); - Pageable pageable = Mockito.mock(Pageable.class); - AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class); - when(mock.executeDependingOnType(any(N1qlQuery.class), any(N1qlQuery.class), any(QueryMethod.class), any(Pageable.class), - any(Class.class))).thenCallRealMethod(); - - mock.executeDependingOnType(query, query, queryMethod, pageable, Sample.class); - - verify(mock, never()).executeCollection(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeEntity(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeStream(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executePaged(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock).executeSliced(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSingleProjection(any(N1qlQuery.class)); - } - - @Test - public void shouldExecuteSingleProjectionWhenRandomObjectReturnType() throws Exception { - - Method method = SampleRepository.class.getMethod("countDown"); - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, projectionFactory, context); - - N1qlQuery query = Mockito.mock(N1qlQuery.class); - Pageable pageable = Mockito.mock(Pageable.class); - AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class); - when(mock.executeDependingOnType(any(N1qlQuery.class), any(N1qlQuery.class), any(QueryMethod.class), any(Pageable.class), - any(Class.class))).thenCallRealMethod(); - - mock.executeDependingOnType(query, query, queryMethod, pageable, Sample.class); - - verify(mock, never()).executeCollection(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeEntity(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeStream(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executePaged(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSliced(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock).executeSingleProjection(any(N1qlQuery.class)); - } - - @Test - public void shouldExecuteSingleProjectionWhenPrimitiveReturnType() throws Exception { - - Method method = SampleRepository.class.getMethod("longMethod"); - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, projectionFactory, context); - - N1qlQuery query = Mockito.mock(N1qlQuery.class); - Pageable pageable = Mockito.mock(Pageable.class); - AbstractN1qlBasedQuery mock = mock(AbstractN1qlBasedQuery.class); - when(mock.executeDependingOnType(any(N1qlQuery.class), any(N1qlQuery.class), any(QueryMethod.class), any(Pageable.class), - any(Class.class))).thenCallRealMethod(); - - mock.executeDependingOnType(query, query, queryMethod, pageable, Sample.class); - - verify(mock, never()).executeCollection(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeEntity(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executeStream(any(N1qlQuery.class), any(Class.class)); - verify(mock, never()).executePaged(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock, never()).executeSliced(any(N1qlQuery.class), any(N1qlQuery.class), any(Pageable.class), any(Class.class)); - verify(mock).executeSingleProjection(any(N1qlQuery.class)); - } - - @Test // DATACOUCH-206 - public void shouldPickConsistencyFromAnnotation() throws NoSuchMethodException { - Class repositoryClass = SampleRepository.class; - - CouchbaseQueryMethod defaultQueryMethod = new CouchbaseQueryMethod(repositoryClass.getMethod("findAll"), - metadata, - projectionFactory, - context); - - - CouchbaseQueryMethod unboundedQueryMethod = new CouchbaseQueryMethod(repositoryClass.getMethod("streamAll"), - metadata, - projectionFactory, - context); - - CouchbaseTemplate template = mock(CouchbaseTemplate.class); - when(template.getDefaultConsistency()).thenReturn(Consistency.STRONGLY_CONSISTENT); - - ScanConsistency defaultConsistency = new SampleQuery(defaultQueryMethod, template).getScanConsistency(); - assertThat(Consistency.STRONGLY_CONSISTENT.n1qlConsistency()) - .isEqualTo(defaultConsistency); - - ScanConsistency unboundedConsistency = new SampleQuery(unboundedQueryMethod, template).getScanConsistency(); - assertThat(ScanConsistency.NOT_BOUNDED).isEqualTo(unboundedConsistency); - - } - - static class Sample { - Integer id; - } - - interface SampleRepository extends Repository { - - Collection findAll(); - - Sample findById(Integer id); - - @WithConsistency(ScanConsistency.NOT_BOUNDED) - Stream streamAll(); - - Page findAllPaged(Pageable pageable); - - Slice findAllSliced(Pageable pageable); - - void modifyingMethod(); - - CountDownLatch countDown(); - - long longMethod(); - } - - class SampleQuery extends AbstractN1qlBasedQuery { - - protected SampleQuery(CouchbaseQueryMethod queryMethod, - CouchbaseOperations couchbaseOperations) { - super(queryMethod, couchbaseOperations); - } - - @Override - protected Statement getCount(ParameterAccessor accessor, Object[] runtimeParameters) { - return null; - } - - @Override - protected boolean useGeneratedCountQuery() { - return false; - } - - @Override - protected Statement getStatement(ParameterAccessor accessor, Object[] runtimeParameters, ReturnedType returnedType) { - return null; - } - - @Override - protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { - return null; - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTest.java deleted file mode 100644 index c58b886f..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTest.java +++ /dev/null @@ -1,664 +0,0 @@ -package org.springframework.data.couchbase.repository.query; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import com.couchbase.client.java.document.json.JsonArray; -import com.couchbase.client.java.query.dsl.Expression; - -import org.junit.Test; -import org.springframework.data.couchbase.repository.query.support.N1qlQueryCreatorUtils; -import org.springframework.data.repository.query.parser.Part; - -import static org.assertj.core.api.Assertions.assertThat; - -public class N1qlQueryCreatorTest { - - //==== The tests below check mapping between a Part.Type and the corresponding N1QL expression ==== - - @Test - public void testBETWEEN() throws Exception { - Part.Type keyword = Part.Type.BETWEEN; - Iterator values = Arrays.asList("a", "b", 1, 2, "C", "D").iterator(); - String expected = "doc.field BETWEEN $0 AND $1"; - String expectedNum = "doc.field BETWEEN $0 AND $1"; - String expectedIgnoreCase = "LOWER(doc.field) BETWEEN LOWER($0) AND LOWER($1)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a").add("b")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1).add(2)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("C").add("D")); - } - - @Test - public void testIS_NOT_NULL() throws Exception { - Part.Type keyword = Part.Type.IS_NOT_NULL; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field IS NOT NULL"; - String expectedNum = "doc.field IS NOT NULL"; - String expectedIgnoreCase = "LOWER(doc.field) IS NOT NULL"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create()); - assertThat(phexpNum).isEqualTo(JsonArray.create()); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create()); - } - - @Test - public void testIS_NULL() throws Exception { - Part.Type keyword = Part.Type.IS_NULL; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field IS NULL"; - String expectedNum = "doc.field IS NULL"; - String expectedIgnoreCase = "LOWER(doc.field) IS NULL"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create()); - assertThat(phexpNum).isEqualTo(JsonArray.create()); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create()); - } - - @Test - public void testLESS_THAN() throws Exception { - Part.Type keyword = Part.Type.LESS_THAN; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field < $0"; - String expectedNum = "doc.field < $0"; - String expectedIgnoreCase = "LOWER(doc.field) < LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testLESS_THAN_EQUAL() throws Exception { - Part.Type keyword = Part.Type.LESS_THAN_EQUAL; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field <= $0"; - String expectedNum = "doc.field <= $0"; - String expectedIgnoreCase = "LOWER(doc.field) <= LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testGREATER_THAN() throws Exception { - Part.Type keyword = Part.Type.GREATER_THAN; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field > $0"; - String expectedNum = "doc.field > $0"; - String expectedIgnoreCase = "LOWER(doc.field) > LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testGREATER_THAN_EQUAL() throws Exception { - Part.Type keyword = Part.Type.GREATER_THAN_EQUAL; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field >= $0"; - String expectedNum = "doc.field >= $0"; - String expectedIgnoreCase = "LOWER(doc.field) >= LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testBEFORE() throws Exception { - Part.Type keyword = Part.Type.BEFORE; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field < $0"; - String expectedNum = "doc.field < $0"; - String expectedIgnoreCase = "LOWER(doc.field) < LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testAFTER() throws Exception { - Part.Type keyword = Part.Type.AFTER; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field > $0"; - String expectedNum = "doc.field > $0"; - String expectedIgnoreCase = "LOWER(doc.field) > LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testNOT_LIKE() throws Exception { - Part.Type keyword = Part.Type.NOT_LIKE; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field NOT LIKE $0"; - String expectedNum = "doc.field NOT LIKE $0"; - String expectedIgnoreCase = "LOWER(doc.field) NOT LIKE LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testLIKE() throws Exception { - Part.Type keyword = Part.Type.LIKE; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field LIKE $0"; - String expectedNum = "doc.field LIKE $0"; - String expectedIgnoreCase = "LOWER(doc.field) LIKE LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testSTARTING_WITH() throws Exception { - Part.Type keyword = Part.Type.STARTING_WITH; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field LIKE $0 || '%'"; - String expectedNum = "doc.field LIKE $0 || '%'"; - String expectedIgnoreCase = "LOWER(doc.field) LIKE LOWER($0) || '%'"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testENDING_WITH() throws Exception { - Part.Type keyword = Part.Type.ENDING_WITH; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field LIKE '%' || $0"; - String expectedNum = "doc.field LIKE '%' || $0"; - String expectedIgnoreCase = "LOWER(doc.field) LIKE '%' || LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testNOT_CONTAINING() throws Exception { - Part.Type keyword = Part.Type.NOT_CONTAINING; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field NOT LIKE '%' || $0 || '%'"; - String expectedNum = "doc.field NOT LIKE '%' || $0 || '%'"; - String expectedIgnoreCase = "LOWER(doc.field) NOT LIKE '%' || LOWER($0) || '%'"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testCONTAINING() throws Exception { - Part.Type keyword = Part.Type.CONTAINING; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field LIKE '%' || $0 || '%'"; - String expectedNum = "doc.field LIKE '%' || $0 || '%'"; - String expectedIgnoreCase = "LOWER(doc.field) LIKE '%' || LOWER($0) || '%'"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testNOT_IN() throws Exception { - Part.Type keyword = Part.Type.NOT_IN; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field NOT IN $0"; - String expectedNum = "doc.field NOT IN $0"; - String expectedIgnoreCase = "LOWER(doc.field) NOT IN $0"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add(JsonArray.create().add("a"))); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(JsonArray.create().add(1))); - assertThat(phexpIgnoreCase) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("b"))); - } - - @Test - public void testNOTINwithCollection() throws Exception { - Part.Type keyword = Part.Type.NOT_IN; - List val1 = Arrays.asList("av1", "av2"); - List val2 = Arrays.asList("bv1", "bv2"); - Iterator values = Arrays.asList(val1, val2).iterator(); - String expected = "doc.field NOT IN $0"; - String expectedIgnoreCase = "LOWER(doc.field) NOT IN $0"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("av1").add("av2"))); - assertThat(phexpIgnoreCase) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("bv1").add("bv2"))); - } - - @Test - public void testNOTINwithArray() throws Exception { - Part.Type keyword = Part.Type.NOT_IN; - String[] val1 = {"av1", "av2"}; - String[] val2 = {"bv1", "bv2"}; - Iterator values = Arrays.asList(val1, val2).iterator(); - String expected = "doc.field NOT IN $0"; - String expectedIgnoreCase = "LOWER(doc.field) NOT IN $0"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("av1").add("av2"))); - assertThat(phexpIgnoreCase) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("bv1").add("bv2"))); - } - - @Test - public void testIN() throws Exception { - Part.Type keyword = Part.Type.IN; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field IN $0"; - String expectedNum = "doc.field IN $0"; - String expectedIgnoreCase = "LOWER(doc.field) IN $0"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add(JsonArray.create().add("a"))); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(JsonArray.create().add(1))); - assertThat(phexpIgnoreCase) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("b"))); - } - - @Test - public void testINwithCollection() throws Exception { - Part.Type keyword = Part.Type.IN; - List val1 = Arrays.asList("av1", "av2"); - List val2 = Arrays.asList("bv1", "bv2"); - Iterator values = Arrays.asList(val1, val2).iterator(); - String expected = "doc.field IN $0"; - String expectedIgnoreCase = "LOWER(doc.field) IN $0"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("av1").add("av2"))); - assertThat(phexpIgnoreCase) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("bv1").add("bv2"))); - } - - @Test - public void testINwithArray() throws Exception { - Part.Type keyword = Part.Type.IN; - String[] val1 = {"av1", "av2"}; - String[] val2 = {"bv1", "bv2"}; - Iterator values = Arrays.asList(val1, val2).iterator(); - String expected = "doc.field IN $0"; - String expectedIgnoreCase = "LOWER(doc.field) IN $0"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("av1").add("av2"))); - assertThat(phexpIgnoreCase) - .isEqualTo(JsonArray.create().add(JsonArray.create().add("bv1").add("bv2"))); - } - - @Test(expected = IllegalArgumentException.class) - public void testNEAR() throws Exception { - Part.Type keyword = Part.Type.NEAR; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), JsonArray.create());; - } - - @Test(expected = IllegalArgumentException.class) - public void testWITHIN() throws Exception { - Part.Type keyword = Part.Type.WITHIN; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), JsonArray.create());; - } - - @Test - public void testREGEX() throws Exception { - Part.Type keyword = Part.Type.REGEX; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "REGEXP_LIKE(doc.field, $0)"; - String expectedNum = "REGEXP_LIKE(doc.field, $0)"; - String expectedIgnoreCase = "REGEXP_LIKE(LOWER(doc.field), $0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add("1")); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - @Test - public void testEXISTS() throws Exception { - Part.Type keyword = Part.Type.EXISTS; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field IS NOT MISSING"; - String expectedNum = "doc.field IS NOT MISSING"; - String expectedIgnoreCase = "LOWER(doc.field) IS NOT MISSING"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create()); - assertThat(phexpNum).isEqualTo(JsonArray.create()); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create()); - } - - @Test - public void testTRUE() throws Exception { - Part.Type keyword = Part.Type.TRUE; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field = TRUE"; - String expectedNum = "doc.field = TRUE"; - String expectedIgnoreCase = "LOWER(doc.field) = TRUE"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create()); - assertThat(phexpNum).isEqualTo(JsonArray.create()); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create()); - } - - @Test - public void testFALSE() throws Exception { - Part.Type keyword = Part.Type.FALSE; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field = FALSE"; - String expectedNum = "doc.field = FALSE"; - String expectedIgnoreCase = "LOWER(doc.field) = FALSE"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create()); - assertThat(phexpNum).isEqualTo(JsonArray.create()); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create()); - } - - @Test - public void testNEGATING_SIMPLE_PROPERTY() throws Exception { - Part.Type keyword = Part.Type.NEGATING_SIMPLE_PROPERTY; - Iterator values = Arrays.asList("a", 1, "b").iterator(); - String expected = "doc.field != $0"; - String expectedNum = "doc.field != $0"; - String expectedIgnoreCase = "LOWER(doc.field) != LOWER($0)"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - } - - enum TestEnum { - TEST - } - - @Test - public void testSIMPLE_PROPERTY() throws Exception { - Part.Type keyword = Part.Type.SIMPLE_PROPERTY; - Iterator values = Arrays.asList("a", 1, "b", TestEnum.TEST).iterator(); - String expected = "doc.field = $0"; - String expectedNum = "doc.field = $0"; - String expectedIgnoreCase = "LOWER(doc.field) = LOWER($0)"; - String expectedEnum = "doc.field = $0"; - - JsonArray phexp = JsonArray.create(); - Expression exp = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexp); - JsonArray phexpNum = JsonArray.create(); - Expression expNum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpNum); - JsonArray phexpIgnoreCase = JsonArray.create(); - Expression expIgnoreCase = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", true, values, new AtomicInteger(), phexpIgnoreCase); - JsonArray phexpEnum = JsonArray.create(); - Expression expEnum = N1qlQueryCreatorUtils.createExpression(keyword, "doc.field", false, values, new AtomicInteger(), phexpEnum); - - assertThat(exp.toString()).isEqualTo(expected); - assertThat(expNum.toString()).isEqualTo(expectedNum); - assertThat(expIgnoreCase.toString()).isEqualTo(expectedIgnoreCase); - assertThat(expEnum.toString()).isEqualTo(expectedEnum); - assertThat(phexp).isEqualTo(JsonArray.create().add("a")); - assertThat(phexpNum).isEqualTo(JsonArray.create().add(1)); - assertThat(phexpIgnoreCase).isEqualTo(JsonArray.create().add("b")); - assertThat(phexpEnum).isEqualTo(JsonArray.create().add("TEST")); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java new file mode 100644 index 00000000..73490452 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017-2019 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.repository.query; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.couchbase.core.convert.CouchbaseConverter; +import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +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.query.Query; +import org.springframework.data.couchbase.domain.User; +import org.springframework.data.couchbase.domain.UserRepository; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.repository.query.DefaultParameters; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.parser.PartTree; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.data.couchbase.core.query.QueryCriteria.*; + +class N1qlQueryCreatorTests { + + MappingContext, CouchbasePersistentProperty> context; + CouchbaseConverter converter; + + @BeforeEach + public void beforeEach() { + context = new CouchbaseMappingContext(); + converter = new MappingCouchbaseConverter(context); + } + + @Test + void createsQueryCorrectly() throws Exception { + String input = "findByFirstname"; + PartTree tree = new PartTree(input, User.class); + Method method = UserRepository.class.getMethod(input, String.class); + + N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "Oliver"), context); + Query query = creator.createQuery(); + + assertEquals(query.export(), " WHERE " + where("firstname").is("Oliver").export()); + } + + @Test + void createsAndQueryCorrectly() throws Exception { + String input = "findByFirstnameAndLastname"; + PartTree tree = new PartTree(input, User.class); + Method method = UserRepository.class.getMethod(input, String.class, String.class); + + N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), "John", "Doe"), context); + Query query = creator.createQuery(); + + assertEquals(query.export(), " WHERE " + where("firstname").is("John").and("lastname").is("Doe").export()); + } + + private ParameterAccessor getAccessor(Parameters params, Object... values) { + return new ParametersParameterAccessor(params, values); + } + + private Parameters getParameters(Method method) { + return new DefaultParameters(method); + } + +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/PartTreeN1qBasedQueryTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/PartTreeN1qBasedQueryTest.java deleted file mode 100644 index 5c8e92d1..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/PartTreeN1qBasedQueryTest.java +++ /dev/null @@ -1,235 +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.repository.query; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -import com.couchbase.client.java.document.json.JsonObject; -import org.junit.Test; -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.couchbase.core.Beer; -import org.springframework.data.couchbase.core.BeerDTO; -import org.springframework.data.couchbase.core.BeerProjection; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; -import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Direction; -import org.springframework.data.mapping.PersistentPropertyPath; -import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.core.EntityMetadata; -import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.repository.query.ParameterAccessor; -import org.springframework.data.repository.query.ResultProcessor; - -import com.couchbase.client.java.CouchbaseBucket; -import com.couchbase.client.java.query.Statement; - -/** - * @author Mark Paluch - */ -public class PartTreeN1qBasedQueryTest { - - @Test - public void testGetCountExcludesStaticSortClause() throws Exception { - - PageRequest pr = PageRequest.of(0, 10); - - CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class); - CouchbaseBucket couchbaseBucket = mock(CouchbaseBucket.class); - CouchbaseConverter couchbaseConverter = mock(CouchbaseConverter.class); - MappingContext mappingContext = mock(MappingContext.class); - PersistentPropertyPath persistentPropertyPath = mock(PersistentPropertyPath.class); - CouchbasePersistentProperty leafProperty = mock(CouchbasePersistentProperty.class); - EntityMetadata entityInformation = mock(EntityMetadata.class); - ParameterAccessor accessor = mock(ParameterAccessor.class); - ProjectionFactory factory = mock(ProjectionFactory.class); - - Method method = TestRepository.class.getMethod("findByNameOrderByName", String.class, Pageable.class); - RepositoryMetadata metadata = new DefaultRepositoryMetadata(TestRepository.class); - - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); - - when(accessor.getSort()).thenReturn(Sort.unsorted()); - when(accessor.getPageable()).thenReturn(Pageable.unpaged()); - when(entityInformation.getJavaType()).thenReturn(Beer.class); - when(couchbaseOperations.getCouchbaseBucket()).thenReturn(couchbaseBucket); - when(couchbaseBucket.name()).thenReturn("default"); - when(couchbaseOperations.getConverter()).thenReturn(couchbaseConverter); - when(couchbaseConverter.getMappingContext()).thenReturn(mappingContext); - when(mappingContext.getPersistentPropertyPath(isA(PropertyPath.class))).thenReturn(persistentPropertyPath); - when(persistentPropertyPath.toDotPath(isA(Converter.class))).thenReturn("name"); - when(persistentPropertyPath.getLeafProperty()).thenReturn(leafProperty); - when(leafProperty.getType()).thenReturn((Class) String.class); - when(accessor.iterator()).thenReturn(Arrays.asList((Object) "value", pr).iterator()); - when(couchbaseConverter.getTypeKey()).thenReturn("_class"); - when(couchbaseConverter.convertForWriteIfNeeded(eq("value"))).thenReturn("value"); - when(accessor.getPageable()).thenReturn(pr); - - PartTreeN1qlBasedQuery query = new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); - Statement statement = query.getCount(accessor, new Object[] { "value", pr }); - - assertThat(statement.toString()) - .isEqualTo("SELECT COUNT(*) AS count FROM `default` WHERE (name = $1) " - + "AND `_class` = \"org.springframework.data.couchbase.core.Beer\""); - - } - - @Test - public void testGetCountExcludesDynamicSortClause() throws Exception { - - Sort sort = Sort.by(Direction.ASC, "name"); - PageRequest pr = PageRequest.of(0, 10, sort); - - CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class); - CouchbaseBucket couchbaseBucket = mock(CouchbaseBucket.class); - CouchbaseConverter couchbaseConverter = mock(CouchbaseConverter.class); - MappingContext mappingContext = mock(MappingContext.class); - PersistentPropertyPath persistentPropertyPath = mock(PersistentPropertyPath.class); - CouchbasePersistentProperty leafProperty = mock(CouchbasePersistentProperty.class); - EntityMetadata entityInformation = mock(EntityMetadata.class); - ParameterAccessor accessor = mock(ParameterAccessor.class); - ProjectionFactory factory = mock(ProjectionFactory.class); - - Method method = TestRepository.class.getMethod("findByName", String.class, Pageable.class); - RepositoryMetadata metadata = new DefaultRepositoryMetadata(TestRepository.class); - - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); - - when(accessor.getPageable()).thenReturn(Pageable.unpaged()); - when(entityInformation.getJavaType()).thenReturn(Beer.class); - when(couchbaseOperations.getCouchbaseBucket()).thenReturn(couchbaseBucket); - when(couchbaseBucket.name()).thenReturn("default"); - when(couchbaseOperations.getConverter()).thenReturn(couchbaseConverter); - when(couchbaseConverter.getMappingContext()).thenReturn(mappingContext); - when(mappingContext.getPersistentPropertyPath(isA(PropertyPath.class))).thenReturn(persistentPropertyPath); - when(persistentPropertyPath.toDotPath(isA(Converter.class))).thenReturn("name"); - when(persistentPropertyPath.getLeafProperty()).thenReturn(leafProperty); - when(leafProperty.getType()).thenReturn((Class) String.class); - when(accessor.iterator()).thenReturn(Arrays.asList((Object) "value", pr).iterator()); - when(accessor.getSort()).thenReturn(sort); - when(couchbaseConverter.getTypeKey()).thenReturn("_class"); - when(couchbaseConverter.convertForWriteIfNeeded(eq("value"))).thenReturn("value"); - when(accessor.getPageable()).thenReturn(pr); - - PartTreeN1qlBasedQuery query = new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); - Statement statement = query.getCount(accessor, new Object[] { "value", pr }); - - assertThat(statement.toString()) - .isEqualTo("SELECT COUNT(*) AS count FROM `default` WHERE (name = $1) " - + "AND `_class` = \"org.springframework.data.couchbase.core.Beer\""); - - } - - @Test - public void testProjectionInterface() throws Exception { - - CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class); - CouchbaseBucket couchbaseBucket = mock(CouchbaseBucket.class); - CouchbaseConverter couchbaseConverter = mock(CouchbaseConverter.class); - EntityMetadata entityInformation = mock(EntityMetadata.class); - ParameterAccessor accessor = mock(ParameterAccessor.class); - - ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); - factory.createProjection(BeerProjection.class); - MappingContext mappingContext = new CouchbaseMappingContext(); - RepositoryMetadata metadata = new DefaultRepositoryMetadata(TestRepository.class); - Method method = TestRepository.class.getMethod("findAllProjectedBy"); - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); - - when(accessor.getDynamicProjection()).thenReturn(Optional.of(BeerProjection.class)); - when(entityInformation.getJavaType()).thenReturn(Beer.class); - when(couchbaseOperations.getCouchbaseBucket()).thenReturn(couchbaseBucket); - when(couchbaseBucket.name()).thenReturn("B"); - when(couchbaseOperations.getConverter()).thenReturn(couchbaseConverter); - when(couchbaseOperations.getConverter().getMappingContext()).thenReturn(mappingContext); - when(couchbaseConverter.getTypeKey()).thenReturn("_class"); - when(couchbaseConverter.convertForWriteIfNeeded(eq("value"))).thenReturn("value"); - - ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor); - - PartTreeN1qlBasedQuery query = new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); - Statement statement = query.getStatement(accessor, null, processor.getReturnedType()); - - assertThat(statement.toString()) - .isEqualTo("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.`desc` FROM `B` WHERE " - + "`_class` = \"org.springframework.data.couchbase.core.Beer\""); - - } - - @Test - public void testProjectionDTO() throws Exception { - CouchbaseOperations couchbaseOperations = mock(CouchbaseOperations.class); - CouchbaseBucket couchbaseBucket = mock(CouchbaseBucket.class); - CouchbaseConverter couchbaseConverter = mock(CouchbaseConverter.class); - EntityMetadata entityInformation = mock(EntityMetadata.class); - ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); - ParameterAccessor accessor = mock(ParameterAccessor.class); - - RepositoryMetadata metadata = new DefaultRepositoryMetadata(TestRepository.class); - Method method = TestRepository.class.getMethod("findAllDtoedBy"); - MappingContext mappingContext = new CouchbaseMappingContext(); - CouchbaseQueryMethod queryMethod = new CouchbaseQueryMethod(method, metadata, factory, mappingContext); - - when(accessor.getDynamicProjection()).thenReturn(Optional.of(BeerDTO.class)); - when(entityInformation.getJavaType()).thenReturn(Beer.class); - when(couchbaseOperations.getCouchbaseBucket()).thenReturn(couchbaseBucket); - when(couchbaseBucket.name()).thenReturn("B"); - when(couchbaseOperations.getConverter()).thenReturn(couchbaseConverter); - when(couchbaseOperations.getConverter().getMappingContext()).thenReturn(mappingContext); - when(couchbaseConverter.getTypeKey()).thenReturn("_class"); - - ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor); - - PartTreeN1qlBasedQuery query = new PartTreeN1qlBasedQuery(queryMethod, couchbaseOperations); - Statement statement = query.getStatement(accessor, null, processor.getReturnedType()); - - assertThat(statement.toString()) - .isEqualTo("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.`name`, `B`.`desc` FROM `B` " - + "WHERE `_class` = \"org.springframework.data.couchbase.core.Beer\""); - - } - - public static interface TestRepository extends CrudRepository { - - Page findByNameOrderByName(String name, Pageable pageRequest); - - Page findByName(String name, Pageable pageRequest); - - Collection findAllProjectedBy(); - - List findAllDtoedBy(); - - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/ReactiveAbstractN1qlBasedQueryTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/ReactiveAbstractN1qlBasedQueryTest.java deleted file mode 100644 index 517838e8..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/ReactiveAbstractN1qlBasedQueryTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-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.repository.query; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -import com.couchbase.client.java.document.json.JsonValue; -import com.couchbase.client.java.query.Statement; -import com.couchbase.client.java.query.consistency.ScanConsistency; -import org.junit.*; -import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations; -import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate; -import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; -import org.springframework.data.couchbase.core.query.Consistency; -import org.springframework.data.couchbase.core.query.WithConsistency; -import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository; -import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; -import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.repository.query.ParameterAccessor; -import org.springframework.data.repository.query.ReturnedType; -import reactor.core.publisher.Flux; - -/** - * Unit tests for {@link ReactiveAbstractN1qlBasedQuery}. - * - * @author Johannes Jasper - */ -public class ReactiveAbstractN1qlBasedQueryTest { - - CouchbaseMappingContext context = new CouchbaseMappingContext(); - ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory(); - RepositoryMetadata metadata = DefaultRepositoryMetadata.getMetadata(SampleRepository.class); - - - @Test // DATACOUCH-206 - public void shouldPickConsistencyFromAnnotation() throws NoSuchMethodException { - Class repositoryClass = SampleRepository.class; - - CouchbaseQueryMethod defaultQueryMethod = new CouchbaseQueryMethod(repositoryClass.getMethod("findAll"), - metadata, - projectionFactory, - context); - - - CouchbaseQueryMethod unboundedQueryMethod = new CouchbaseQueryMethod(repositoryClass.getMethod("findByName"), - metadata, - projectionFactory, - context); - - RxJavaCouchbaseTemplate template = mock(RxJavaCouchbaseTemplate.class); - when(template.getDefaultConsistency()).thenReturn(Consistency.STRONGLY_CONSISTENT); - - ScanConsistency defaultConsistency = new SampleQuery(defaultQueryMethod, template).getScanConsistency(); - assertThat(Consistency.STRONGLY_CONSISTENT.n1qlConsistency()) - .isEqualTo(defaultConsistency); - - ScanConsistency unboundedConsistency = new SampleQuery(unboundedQueryMethod, template).getScanConsistency(); - assertThat(ScanConsistency.NOT_BOUNDED).isEqualTo(unboundedConsistency); - - } - - static class Sample { - Integer name; - } - - interface SampleRepository extends ReactiveCouchbaseRepository { - - Flux findAll(); - - @WithConsistency(ScanConsistency.NOT_BOUNDED) - Flux findByName(); - } - - class SampleQuery extends ReactiveAbstractN1qlBasedQuery { - - protected SampleQuery(CouchbaseQueryMethod queryMethod, - RxJavaCouchbaseOperations couchbaseOperations) { - super(queryMethod, couchbaseOperations); - } - - @Override - protected Statement getStatement(ParameterAccessor accessor, - Object[] runtimeParameters, - ReturnedType returnedType) { - return null; - } - - @Override - protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { - return null; - } - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1QlBasedQueryTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1QlBasedQueryTest.java deleted file mode 100644 index 9df5956e..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1QlBasedQueryTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.springframework.data.couchbase.repository.query; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.eq; -import static org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser.*; -import org.junit.Before; -import org.junit.Test; -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; - -public class StringN1QlBasedQueryTest { - private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser(); - private static final EvaluationContext SPEL_EVALUATION_CONTEXT = new StandardEvaluationContext(); - - private static String spel(String expression) { - return "#{" + expression + "}"; - } - CouchbaseConverter couchbaseConverter; - - @Before - public void setup() { - this.couchbaseConverter = mock(CouchbaseConverter.class); - when(couchbaseConverter.convertForWriteIfNeeded(eq("value"))).thenReturn("value"); - } - - @Test - public void testReplaceAllFullSelectPlaceholder() throws Exception { - String statement = spel(SPEL_SELECT_FROM_CLAUSE) + " where " + spel(SPEL_SELECT_FROM_CLAUSE); - String parsed = new StringBasedN1qlQueryParser(statement, null, "B", this.couchbaseConverter, "_class", String.class) - .doParse(SPEL_PARSER, SPEL_EVALUATION_CONTEXT, false); - - assertThat(parsed) - .isEqualTo("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.* FROM `B` where " - + "SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS, `B`.* FROM `B`"); - } - - @Test - public void testReplaceAllBucketPlaceholder() throws Exception { - String statement = "SELECT * FROM " + spel(SPEL_BUCKET) + " WHERE " + spel(SPEL_BUCKET) + ".test = 1"; - String parsed = new StringBasedN1qlQueryParser(statement, null, "B", this.couchbaseConverter, "_class", String.class) - .doParse(SPEL_PARSER, SPEL_EVALUATION_CONTEXT, false); - - assertThat(parsed).isEqualTo("SELECT * FROM `B` WHERE `B`.test = 1"); - } - - @Test - public void testReplaceAllEntityPlaceholder() throws Exception { - String statement = "SELECT " + spel(SPEL_ENTITY) + " FROM a where a.test = 1 and " + spel(SPEL_ENTITY); - String parsed = new StringBasedN1qlQueryParser(statement, null, "B", this.couchbaseConverter, "_class", String.class) - .doParse(SPEL_PARSER, SPEL_EVALUATION_CONTEXT, false); - - assertThat(parsed) - .isEqualTo("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS FROM a where a.test = 1 and " - + "META(`B`).id AS _ID, META(`B`).cas AS _CAS"); - } - - @Test - public void testReplaceTypePlaceholder() throws Exception { - String statement = "SELECT " + spel(SPEL_ENTITY) + " FROM a WHERE a.test = 1 AND " + spel(SPEL_FILTER); - String parsed = new StringBasedN1qlQueryParser(statement, null, "B", this.couchbaseConverter, "@class", String.class) - .doParse(SPEL_PARSER, SPEL_EVALUATION_CONTEXT, false); - - assertThat(parsed) - .isEqualTo("SELECT META(`B`).id AS _ID, META(`B`).cas AS _CAS FROM a WHERE a.test = 1 AND `@class` = " - + "\"java.lang.String\""); - } - - @Test - public void testReplaceSelectFromPlaceholderWithCountIfCountTrue() { - String statement = spel(SPEL_SELECT_FROM_CLAUSE) + " WHERE true"; - String parsed = new StringBasedN1qlQueryParser(statement, null, "B", this.couchbaseConverter, "_class", String.class) - .doParse(SPEL_PARSER, SPEL_EVALUATION_CONTEXT, true); - - assertThat(parsed) - .isEqualTo("SELECT COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM `B` WHERE true"); - } - - @Test - public void testDeletePlaceholder() throws Exception { - String statement = spel(SPEL_DELETE) + " WHERE test = 1 AND " + spel(SPEL_FILTER); - String parsed = new StringBasedN1qlQueryParser(statement, null, "B", this.couchbaseConverter, "_class", String.class) - .doParse(SPEL_PARSER, SPEL_EVALUATION_CONTEXT, true); - - assertThat(parsed).isEqualTo("DELETE FROM `B` WHERE test = 1 AND `_class` = " - + "\"java.lang.String\""); - } - - @Test - public void testReturningPlaceholder() throws Exception { - String statement = spel(SPEL_DELETE) + " WHERE test = 1 AND " + spel(SPEL_FILTER) + spel(SPEL_RETURNING) ; - String parsed = new StringBasedN1qlQueryParser(statement, null, "B", this.couchbaseConverter, "_class", String.class) - .doParse(SPEL_PARSER, SPEL_EVALUATION_CONTEXT, true); - - assertThat(parsed).isEqualTo("DELETE FROM `B` WHERE test = 1 AND `_class` = " - + "\"java.lang.String\" returning `B`.*, META(`B`).id AS _ID, META(`B`).cas AS _CAS"); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/support/AbstractPointInShapeEvaluatorTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/support/AbstractPointInShapeEvaluatorTest.java deleted file mode 100644 index a1bfbad1..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/support/AbstractPointInShapeEvaluatorTest.java +++ /dev/null @@ -1,249 +0,0 @@ -package org.springframework.data.couchbase.repository.query.support; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.geo.Circle; -import org.springframework.data.geo.Distance; -import org.springframework.data.geo.Point; -import org.springframework.data.geo.Polygon; - -import static org.assertj.core.api.Assertions.*; - -/** - * An abstract base for testing {@link PointInShapeEvaluator} implementations. - * Each implementation can be commonly tested by extending this base and instantiating the evaluator in createEvaluator. - * - * @author Simon Baslé - */ -public abstract class AbstractPointInShapeEvaluatorTest { - - public abstract PointInShapeEvaluator createEvaluator(); - - private PointInShapeEvaluator evaluator; - - protected static final class LocatedValue { - public final Point location; - public final String name; - - public LocatedValue(Point location, String name) { - this.location = location; - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - - protected static final Converter LOCATED_VALUE_POINT_CONVERTER = new Converter() { - @Override - public Point convert(LocatedValue source) { - return source.location; - } - }; - - @Before - public void init() { - evaluator = createEvaluator(); - } - - @Test - public void testPointInPolygon() throws Exception { - Polygon openTriangle = new Polygon( - new Point(1.0, 2.0), - new Point(2.0, 2.0), - new Point(1.5, 0.0) - ); - - Polygon closedTriangle = new Polygon( - new Point(1.0, 2.0), - new Point(2.0, 2.0), - new Point(1.5, 0.0), - new Point(1.0, 2.0) //explicitly closing the shape - ); - - Point inside = new Point(1.6, 1.8); - Point outside = new Point(1.1, 0.3); - Point edge = new Point(1.0, 2.0); - - assertThat(evaluator.pointInPolygon(inside, openTriangle)) - .as("point inside open polygon failed").isTrue(); - assertThat(evaluator.pointInPolygon(outside, openTriangle)) - .as("point outside open polygon failed").isFalse(); - assertThat(evaluator.pointInPolygon(edge, openTriangle)) - .as("point on edge of open polygon should not be considered within the polygon") - .isFalse(); - assertThat(evaluator.pointInPolygon(inside, closedTriangle)) - .as("point inside closed polygon failed").isTrue(); - assertThat(evaluator.pointInPolygon(outside, closedTriangle)) - .as("point outside closed polygon failed").isFalse(); - assertThat(evaluator.pointInPolygon(edge, closedTriangle)) - .as("point on edge of closed polygon should not be considered within the polygon") - .isFalse(); - } - - @Test - public void testPointInPolygonArray() throws Exception { - Point[] openTriangle = new Point[] { - new Point(1.0, 2.0), - new Point(2.0, 2.0), - new Point(1.5, 0.0) - }; - - Point[] closedTriangle = new Point[] { - new Point(1.0, 2.0), - new Point(2.0, 2.0), - new Point(1.5, 0.0), - new Point(1.0, 2.0) //explicitly closing the shape - }; - - Point inside = new Point(1.6, 1.8); - Point outside = new Point(1.1, 0.3); - Point edge = new Point(1.0, 2.0); - - assertThat(evaluator.pointInPolygon(inside, openTriangle)) - .as("point inside open polygon failed").isTrue(); - assertThat(evaluator.pointInPolygon(outside, openTriangle)) - .as("point outside open polygon failed").isFalse(); - assertThat(evaluator.pointInPolygon(edge, openTriangle)) - .as("point on edge of open polygon should not be considered within the polygon") - .isFalse(); - assertThat(evaluator.pointInPolygon(inside, closedTriangle)) - .as("point inside closed polygon failed").isTrue(); - assertThat(evaluator.pointInPolygon(outside, closedTriangle)) - .as("point outside closed polygon failed").isFalse(); - assertThat(evaluator.pointInPolygon(edge, closedTriangle)) - .as("point on edge of closed polygon should not be considered within the polygon") - .isFalse(); - } - - @Test - public void testPointInCircle() throws Exception { - Circle circle = new Circle(new Point(0, 0), new Distance(2)); - - Point inside = new Point(-0.3, 1.4); - Point outside = new Point(1.3, 2d); - Point onEdge = new Point(-2d, 0d); - - assertThat(evaluator.pointInCircle(inside, circle)).as("point inside failed") - .isTrue(); - assertThat(evaluator.pointInCircle(outside, circle)).as("point outside failed") - .isFalse(); - assertThat(evaluator.pointInCircle(onEdge, circle)) - .as("point on edge of circle should be considered within / near").isTrue(); - } - - @Test - public void testPointInCircleCenterRadius() throws Exception { - Point center = new Point(0, 0); - Distance radius = new Distance(2); - - Point inside = new Point(-0.3, 1.8); - Point outside = new Point(1.3, 2d); - Point onEdge = new Point(-2d, 0d); - - assertThat(evaluator.pointInCircle(inside, center, radius)).as("point inside failed") - .isTrue(); - assertThat(evaluator.pointInCircle(outside, center, radius)) - .as("point outside failed").isFalse(); - assertThat(evaluator.pointInCircle(onEdge, center, radius)) - .as("point on edge of circle should be considered within / near").isTrue(); - } - - @Test - public void testRemoveFalsePositivesPolygon() throws Exception { - Polygon openTriangle = new Polygon( - new Point(1.0, 2.0), - new Point(2.0, 2.0), - new Point(1.5, 0.0) - ); - - Polygon closedTriangle = new Polygon( - new Point(1.0, 2.0), - new Point(2.0, 2.0), - new Point(1.5, 0.0), - new Point(1.0, 2.0) //explicitly closing the shape - ); - - LocatedValue inside = new LocatedValue(new Point(1.6, 1.8), "inside"); - LocatedValue outside = new LocatedValue(new Point(1.1, 0.3), "outside"); - LocatedValue edge = new LocatedValue(new Point(1.5, 0d), "edge"); - - List expected = Collections.singletonList(inside); - List tested = Arrays.asList(inside, outside, edge); - - List filteredOpen = evaluator.removeFalsePositives(tested, LOCATED_VALUE_POINT_CONVERTER, openTriangle); - List filteredClosed = evaluator.removeFalsePositives(tested, LOCATED_VALUE_POINT_CONVERTER, closedTriangle); - - assertThat(filteredOpen).isEqualTo(expected); - assertThat(filteredClosed).isEqualTo(expected); - } - - @Test - public void testRemoveFalsePositivesPolygonArray() throws Exception { - Point[] openTriangle = new Point[] { - new Point(1.0, 2.0), - new Point(2.0, 2.0), - new Point(1.5, 0.0) - }; - - Point[] closedTriangle = new Point[] { - new Point(1.0, 2.0), - new Point(2.0, 2.0), - new Point(1.5, 0.0), - new Point(1.0, 2.0) //explicitly closing the shape - }; - - LocatedValue inside = new LocatedValue(new Point(1.6, 1.8), "inside"); - LocatedValue outside = new LocatedValue(new Point(1.1, 0.3), "outside"); - LocatedValue edge = new LocatedValue(new Point(1.5, 0d), "edge"); - - List expected = Collections.singletonList(inside); - List tested = Arrays.asList(inside, outside, edge); - - List filteredOpen = evaluator.removeFalsePositives(tested, LOCATED_VALUE_POINT_CONVERTER, openTriangle); - List filteredClosed = evaluator.removeFalsePositives(tested, LOCATED_VALUE_POINT_CONVERTER, closedTriangle); - - assertThat(filteredOpen).isEqualTo(expected); - assertThat(filteredClosed).isEqualTo(expected); - } - - @Test - public void testRemoveFalsePositivesCircle() throws Exception { - Circle circle = new Circle(new Point(0, 0), new Distance(2)); - - LocatedValue inside = new LocatedValue(new Point(-0.3, 1.4), "inside"); - LocatedValue outside = new LocatedValue(new Point(1.3, 2d), "outside"); - LocatedValue edge = new LocatedValue(new Point(-2d, 0d), "edge"); - - List expected = Arrays.asList(inside, edge); - List tested = Arrays.asList(inside, outside, edge); - - List filtered = evaluator.removeFalsePositives(tested, LOCATED_VALUE_POINT_CONVERTER, circle); - - assertThat(filtered).isEqualTo(expected); - } - - @Test - public void testRemoveFalsePositivesCircleCenterRadius() throws Exception { - Point center = new Point(0, 0); - Distance radius = new Distance(2); - - LocatedValue inside = new LocatedValue(new Point(-0.3, 1.4), "inside"); - LocatedValue outside = new LocatedValue(new Point(1.3, 2d), "outside"); - LocatedValue edge = new LocatedValue(new Point(-2d, 0d), "edge"); - - List expected = Arrays.asList(inside, edge); - List tested = Arrays.asList(inside, outside, edge); - - List filtered = evaluator.removeFalsePositives(tested, LOCATED_VALUE_POINT_CONVERTER, center, radius); - - assertThat(filtered).isEqualTo(expected); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/support/AwtPointInShapeEvaluatorTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/support/AwtPointInShapeEvaluatorTest.java deleted file mode 100644 index 784d8dc3..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/support/AwtPointInShapeEvaluatorTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.springframework.data.couchbase.repository.query.support; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test case for the {@link AwtPointInShapeEvaluator}. - * - * @author Simon Baslé - */ -public class AwtPointInShapeEvaluatorTest extends AbstractPointInShapeEvaluatorTest { - - @Override - public PointInShapeEvaluator createEvaluator() { - return new AwtPointInShapeEvaluator(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/support/GeoUtilsTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/support/GeoUtilsTest.java deleted file mode 100644 index f2c8e1ae..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/support/GeoUtilsTest.java +++ /dev/null @@ -1,354 +0,0 @@ -package org.springframework.data.couchbase.repository.query.support; - -import org.junit.Test; -import org.springframework.data.geo.Box; -import org.springframework.data.geo.Circle; -import org.springframework.data.geo.Distance; -import org.springframework.data.geo.Point; -import org.springframework.data.geo.Polygon; -import org.springframework.data.geo.Shape; - -import com.couchbase.client.java.document.json.JsonArray; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.data.Offset.offset; - -/** - * Unit tests for the {@link GeoUtils} utility class. - * @author Simon Baslé - */ -public class GeoUtilsTest { - - @Test - public void testGetBoundingBoxForNear() throws Exception { - final Distance distance = new Distance(120); - double[] bbox = GeoUtils.getBoundingBoxForNear(new Point(1, 1), distance); - assertThat(bbox[0]).isCloseTo(-119d, offset(0d)); //xMin - assertThat(bbox[2]).isCloseTo(121d, offset(0d)); //xMax - assertThat(bbox[1]).isCloseTo(-119d, offset(0d)); //yMin - assertThat(bbox[3]).isCloseTo(121d, offset(0d)); //yMax - - bbox = GeoUtils.getBoundingBoxForNear(new Point(-3,-5), distance); - assertThat(bbox[0]).isCloseTo(-123d, offset(0d)); //xMin - assertThat(bbox[2]).isCloseTo(117d, offset(0d)); //xMax - assertThat(bbox[1]).isCloseTo(-125d, offset(0d)); //yMin - assertThat(bbox[3]).isCloseTo(115d, offset(0d)); //yMax - } - - @Test - public void testGetBoundingBoxForNearNegativeDistance() throws Exception { - final Distance distance = new Distance(-120); - double[] bbox = GeoUtils.getBoundingBoxForNear(new Point(1, 1), distance); - assertThat(bbox[0]).isCloseTo(-119d, offset(0d)); //xMin - assertThat(bbox[2]).isCloseTo(121d, offset(0d)); //xMax - assertThat(bbox[1]).isCloseTo(-119d, offset(0d)); //yMin - assertThat(bbox[3]).isCloseTo(121d, offset(0d)); //yMax - - bbox = GeoUtils.getBoundingBoxForNear(new Point(-3,-5), distance); - assertThat(bbox[0]).isCloseTo(-123d, offset(0d)); //xMin - assertThat(bbox[2]).isCloseTo(117d, offset(0d)); //xMax - assertThat(bbox[1]).isCloseTo(-125d, offset(0d)); //yMin - assertThat(bbox[3]).isCloseTo(115d, offset(0d)); //yMax - } - - @Test(expected = NullPointerException.class) - public void testGetBoundingBoxForNearNullOrigin() { - GeoUtils.getBoundingBoxForNear(null, new Distance(100)); - } - - @Test(expected = NullPointerException.class) - public void testGetBoundingBoxForNearNullDistance() { - GeoUtils.getBoundingBoxForNear(new Point(1, 1), null); - } - - @Test(expected = NullPointerException.class) - public void testGetBoundingBoxForNearNullOriginAndDistance() { - GeoUtils.getBoundingBoxForNear(null, null); - } - - @Test(expected = IllegalArgumentException.class) - public void testConvertPointsTo2DRangesIsBoundingBoxFailsIfLessThan2Points() { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Point p1 = new Point(2,3); - - GeoUtils.convertPointsTo2DRanges(startRange, endRange, true, p1); - } - - @Test(expected = IllegalArgumentException.class) - public void testConvertPointsTo2DRangesIsBoundingBoxFailsIfMoreThan2Points() { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Point p1 = new Point(2,3); - Point p2 = new Point(4,5); - Point p3 = new Point(6,7); - - GeoUtils.convertPointsTo2DRanges(startRange, endRange, true, p1, p2, p3); - } - - @Test(expected = IllegalArgumentException.class) - public void testConvertPointsTo2DRangesIsBoundingBoxFailsIfNotOrderedPoints() { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Point p1 = new Point(2,3); - Point p2 = new Point(4,5); - - GeoUtils.convertPointsTo2DRanges(startRange, endRange, true, p2, p1); - } - - @Test - public void testConvertPointsTo2DRanges2PointsBoundingBox() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Point p1 = new Point(2,3); - Point p2 = new Point(4,5); - - GeoUtils.convertPointsTo2DRanges(startRange, endRange, true, p1, p2); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(2d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(3d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(4d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(5d, offset(0d)); - } - - @Test - public void testConvertPointsTo2DRanges2PointsPoly() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Point p1 = new Point(2,3); - Point p2 = new Point(4,5); - - GeoUtils.convertPointsTo2DRanges(startRange, endRange, false, p1, p2); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(2d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(3d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(4d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(5d, offset(0d)); - } - - @Test - public void testConvertPointsTo2DRanges3PointsPoly() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Point p1 = new Point(2,3); - Point p2 = new Point(-4, 3); - Point p3 = new Point(6, -12); - - GeoUtils.convertPointsTo2DRanges(startRange, endRange, false, p1, p2, p3); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(-4d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(-12d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(6d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(3d, offset(0d)); - } - - @Test - public void testConvertPointsTo2DRanges8Points() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - //bounding box of (3,3)(9,9) - GeoUtils.convertPointsTo2DRanges(startRange, endRange, false, - new Point(3, 3), - new Point(3, 7), - new Point(6, 7), - new Point(6, 9), - new Point(9, 9), - new Point(9, 5), - new Point(6, 5), - new Point(6, 3)); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(3d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(3d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(9d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(9d, offset(0d)); - } - - @Test(expected = IllegalArgumentException.class) - public void testConvertPointsTo2DRangesNullPoints() throws Exception { - GeoUtils.convertPointsTo2DRanges(JsonArray.empty(), JsonArray.empty(), false, null); - } - - @Test(expected = IllegalArgumentException.class) - public void testConvertPointsTo2DRangesEmptyPoints() throws Exception { - GeoUtils.convertPointsTo2DRanges(JsonArray.empty(), JsonArray.empty(), false, new Point[0]); - } - - @Test - public void testConvertShapeTo2DRangesBox() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Box box = new Box(new Point(0, 5), new Point(10, 30)); - - GeoUtils.convertShapeTo2DRanges(startRange, endRange, box); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(0d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(5d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(10d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(30, offset(0d)); - } - - @Test - public void testConvertShapeTo2DRangesBoxIsNotReordered() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Box box = new Box(new Point(0, 5), new Point(10, -3)); - - GeoUtils.convertShapeTo2DRanges(startRange, endRange, box); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(0d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(5d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(10d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(-3d, offset(0d)); - } - - @Test - public void testConvertPolygonBoxTo2DRangesIsReordered() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Polygon box = new Polygon(new Point(0, 5), new Point(10, -3), new Point(0, 5)); - - GeoUtils.convertShapeTo2DRanges(startRange, endRange, box); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(0d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(-3d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(10d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(5d, offset(0d)); - } - - @Test - public void testConvertShapeTo2DRangesPolygon() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Polygon polygon = new Polygon( - new Point(3, 3), - new Point(3, 7), - new Point(6, 7), - new Point(6, 9), - new Point(9, 9), - new Point(9, 5), - new Point(6, 5), - new Point(6, 3) - ); - - GeoUtils.convertShapeTo2DRanges(startRange, endRange, polygon); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(3d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(3d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(9d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(9d, offset(0d)); - } - - @Test - public void testConvertShapeTo2DRangesPolygonIsSameAsMultiplePointsTo2DRanges() throws Exception { - JsonArray startRangePoints = JsonArray.create(); - JsonArray endRangePoints = JsonArray.create(); - JsonArray startRangePolygon = JsonArray.create(); - JsonArray endRangePolygon = JsonArray.create(); - - GeoUtils.convertPointsTo2DRanges(startRangePoints, endRangePoints, false, - new Point(3, 3), - new Point(3, 7), - new Point(6, 7), - new Point(6, 9), - new Point(9, 9), - new Point(9, 5), - new Point(6, 5), - new Point(6, 3)); - - Polygon polygon = new Polygon( - new Point(3, 3), - new Point(3, 7), - new Point(6, 7), - new Point(6, 9), - new Point(9, 9), - new Point(9, 5), - new Point(6, 5), - new Point(6, 3)); - - GeoUtils.convertShapeTo2DRanges(startRangePolygon, endRangePolygon, polygon); - - assertThat(startRangePoints.size()).isEqualTo(2); - assertThat(endRangePoints.size()).isEqualTo(2); - assertThat(startRangePoints.getDouble(0)).isCloseTo(3d, offset(0d)); - assertThat(startRangePoints.getDouble(1)).isCloseTo(3d, offset(0d)); - assertThat(endRangePoints.getDouble(0)).isCloseTo(9d, offset(0d)); - assertThat(endRangePoints.getDouble(1)).isCloseTo(9d, offset(0d)); - assertThat(endRangePolygon).isEqualTo(endRangePoints); - assertThat(startRangePolygon).isEqualTo(startRangePoints); - } - - @Test - public void testConvertShapeTo2DRangesCircle() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - Circle circle = new Circle(new Point(0, 0), new Distance(3)); - - GeoUtils.convertShapeTo2DRanges(startRange, endRange, circle); - - assertThat(startRange.size()).isEqualTo(2); - assertThat(endRange.size()).isEqualTo(2); - assertThat(startRange.getDouble(0)).isCloseTo(-3d, offset(0d)); - assertThat(startRange.getDouble(1)).isCloseTo(-3d, offset(0d)); - assertThat(endRange.getDouble(0)).isCloseTo(3d, offset(0d)); - assertThat(endRange.getDouble(1)).isCloseTo(3d, offset(0d)); - } - - @Test - public void testCircleApproximationIsSameAsNearApproximation() throws Exception { - JsonArray startRangeCircle = JsonArray.create(); - JsonArray endRangeCircle = JsonArray.create(); - Point origin = new Point(0, 0); - Distance distance = new Distance(3); - Circle circle = new Circle(origin, distance); - - GeoUtils.convertShapeTo2DRanges(startRangeCircle, endRangeCircle, circle); - double[] bbox = GeoUtils.getBoundingBoxForNear(origin, distance); - - assertThat(startRangeCircle.size()).isEqualTo(2); - assertThat(endRangeCircle.size()).isEqualTo(2); - - assertThat(bbox[0]).isCloseTo(-3d, offset(0d)); - assertThat(bbox[1]).isCloseTo(-3d, offset(0d)); - assertThat(bbox[2]).isCloseTo(3d, offset(0d)); - assertThat(bbox[3]).isCloseTo(3d, offset(0d)); - - assertThat(startRangeCircle.getDouble(0)).isCloseTo(bbox[0], offset(0d)); - assertThat(startRangeCircle.getDouble(1)).isCloseTo(bbox[1], offset(0d)); - assertThat(endRangeCircle.getDouble(0)).isCloseTo(bbox[2], offset(0d)); - assertThat(endRangeCircle.getDouble(1)).isCloseTo(bbox[3], offset(0d)); - } - - @Test(expected = IllegalArgumentException.class) - public void testConvertShapeTo2DRangesOtherShape() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - - Shape customShape = new Shape() {}; - - GeoUtils.convertShapeTo2DRanges(startRange, endRange, customShape); - } - - @Test(expected = NullPointerException.class) - public void testConvertShapeTo2DRangesNullShape() throws Exception { - JsonArray startRange = JsonArray.create(); - JsonArray endRange = JsonArray.create(); - - GeoUtils.convertShapeTo2DRanges(startRange, endRange, null); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/support/N1qlUtilsTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/support/N1qlUtilsTest.java deleted file mode 100644 index 6355b711..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/query/support/N1qlUtilsTest.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.springframework.data.couchbase.repository.query.support; - -import static com.couchbase.client.java.query.dsl.Expression.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.data.couchbase.core.Beer; -import org.springframework.data.couchbase.core.convert.CouchbaseConverter; -import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; -import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; -import org.springframework.data.couchbase.repository.query.CountFragment; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Order; -import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.repository.core.EntityMetadata; - -public class N1qlUtilsTest { - - @Test - public void testEscapedBucket() throws Exception { - String bucketName = "b"; - String expected = "`b`"; - - String real = N1qlUtils.escapedBucket(bucketName).toString(); - - assertThat(real).isEqualTo(expected); - } - - @Test - public void testCreateSelectClauseForEntity() throws Exception { - String expected = "SELECT META(`b`).id AS _ID, META(`b`).cas AS _CAS, `b`.*"; - String real = N1qlUtils.createSelectClauseForEntity("b").toString(); - - assertThat(real).isEqualTo(expected); - } - - @Test - public void testCreateSelectFromForEntity() throws Exception { - String expected = "SELECT META(`b`).id AS _ID, META(`b`).cas AS _CAS, `b`.* FROM `b`"; - String real = N1qlUtils.createSelectFromForEntity("b").toString(); - - assertThat(real).isEqualTo(expected); - } - - @Test - public void testCreateWhereFilterForEntity() throws Exception { - String expected = "`_class` = \"java.lang.String\""; - CouchbaseConverter converter = mock(CouchbaseConverter.class); - when(converter.getTypeKey()).thenReturn("_class"); - EntityMetadata metadata = mock(EntityMetadata.class); - when(metadata.getJavaType()).thenReturn(String.class); - - String real = N1qlUtils.createWhereFilterForEntity(null, converter, metadata).toString(); - - assertThat(real).isEqualTo(expected); - } - - @Test - public void testCreateWhereFilterForEntityTakesTypeKeyIntoAccount() throws Exception { - String expected = "`hohoho` = \"java.lang.String\""; - CouchbaseConverter converter = mock(CouchbaseConverter.class); - when(converter.getTypeKey()).thenReturn("hohoho"); - EntityMetadata metadata = mock(EntityMetadata.class); - when(metadata.getJavaType()).thenReturn(String.class); - - String real = N1qlUtils.createWhereFilterForEntity(null, converter, metadata).toString(); - - assertThat(real).isEqualTo(expected); - } - - @Test - public void testGetPathWithAlternativeFieldNamesCallsMapperOnce() throws Exception { - CouchbaseConverter converter = mock(CouchbaseConverter.class); - PropertyPath descPropertyPath = PropertyPath.from("active", Beer.class); - MappingContext mockMapping = mock(CouchbaseMappingContext.class); - when(converter.getMappingContext()).thenReturn(mockMapping); - - N1qlUtils.getPathWithAlternativeFieldNames(converter, descPropertyPath); - verify(mockMapping).getPersistentPropertyPath(descPropertyPath); - verifyNoMoreInteractions(mockMapping); - } - - @Test - @Ignore("rather test the dotted path converter") - public void testGetDottedPathWithAlternativeFieldNames() throws Exception { - - } - - @Test - public void testCreateSortUsesPropertiesAsIsWithEscaping() throws Exception { - CouchbaseConverter converter = mock(CouchbaseConverter.class); - com.couchbase.client.java.query.dsl.Sort[] realSort = - N1qlUtils.createSort(Sort.by("description", "attendees"), converter); - - assertThat(realSort.length).isEqualTo(2); - assertThat(realSort[0].toString()) - .isEqualTo(com.couchbase.client.java.query.dsl.Sort.asc("`description`") - .toString()); - assertThat(realSort[1].toString()) - .isEqualTo(com.couchbase.client.java.query.dsl.Sort.asc("`attendees`") - .toString()); - - verifyZeroInteractions(converter); - } - - @Test - public void testCreateSortIgnoresCaseWhenSpecified() throws Exception { - CouchbaseConverter converter = mock(CouchbaseConverter.class); - Sort sortDescription = Sort.by(Order.asc("description").ignoreCase(), Order.asc("attendees")); - com.couchbase.client.java.query.dsl.Sort[] realSort = N1qlUtils.createSort(sortDescription, converter); - - assertThat(realSort.length).isEqualTo(2); - assertThat(realSort[0].toString()).isEqualTo(com.couchbase.client.java.query.dsl.Sort - .asc("LOWER(TOSTRING(`description`))").toString()); - assertThat(realSort[1].toString()) - .isEqualTo(com.couchbase.client.java.query.dsl.Sort.asc("`attendees`") - .toString()); - - verifyZeroInteractions(converter); - } - - @Test - public void testCreateCountQueryForEntity() throws Exception { - CouchbaseConverter converter = mock(CouchbaseConverter.class); - when(converter.getTypeKey()).thenReturn("_class", "otherField"); - CouchbaseEntityInformation entityInformation = mock(CouchbaseEntityInformation.class); - when(entityInformation.getJavaType()).thenReturn(String.class); - - String expectedDefault = "SELECT COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM `b` WHERE `_class` = \"java.lang.String\""; - String expectedTypeKey = "SELECT COUNT(*) AS " + CountFragment.COUNT_ALIAS + " FROM `b` WHERE `otherField` = \"java.lang.String\""; - String real = N1qlUtils.createCountQueryForEntity("b", converter, entityInformation).toString(); - String realWithTypeKey = N1qlUtils.createCountQueryForEntity("b", converter, entityInformation).toString(); - - assertThat(real).isEqualTo(expectedDefault); - assertThat(realWithTypeKey).isEqualTo(expectedTypeKey); - } - - @Test - public void testCreateWhereFilterForEntityWithBaseCriteria() throws Exception { - String expected = "(field1 >= 30 OR field2 = \"foo\") AND `_class` = \"java.lang.String\""; - CouchbaseConverter converter = mock(CouchbaseConverter.class); - when(converter.getTypeKey()).thenReturn("_class"); - EntityMetadata metadata = mock(EntityMetadata.class); - when(metadata.getJavaType()).thenReturn(String.class); - - String real = N1qlUtils.createWhereFilterForEntity( - x("field1").gte(30).or(x("field2").eq(s("foo"))), - converter, metadata).toString(); - - assertThat(real).isEqualTo(expected); - } - - @Test - public void testOrderByWithNestedField() throws Exception { - CouchbaseConverter converter = mock(CouchbaseConverter.class); - com.couchbase.client.java.query.dsl.Sort[] realSort = - N1qlUtils.createSort(Sort.by("party.attendees"), converter); - - assertThat(realSort[0].toString()).isEqualTo("`party`.`attendees` ASC"); - verifyZeroInteractions(converter); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/spel/SpelConfig.java b/src/test/java/org/springframework/data/couchbase/repository/spel/SpelConfig.java deleted file mode 100644 index 4a159e54..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/spel/SpelConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.springframework.data.couchbase.repository.spel; - -import java.util.Collections; -import java.util.Map; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.IntegrationTestApplicationConfig; -import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; -import org.springframework.data.spel.spi.EvaluationContextExtension; - -@Configuration -@EnableCouchbaseRepositories -public class SpelConfig extends IntegrationTestApplicationConfig { - - @Bean - public EvaluationContextExtension customSpelExtension() { - return new CustomSpelExtension(); - } - - public static class CustomSpelExtension implements EvaluationContextExtension { - - /** - * Returns the identifier of the extension. The id can be leveraged by users to fully qualify property lookups and - * thus overcome ambiguities in case multiple extensions expose properties with the same name. - * - * @return the extension id, must not be {@literal null}. - */ - @Override - public String getExtensionId() { - return "custom"; - } - - @Override - public Map getProperties() { - return Collections.singletonMap("oneCustomer", "uname-3"); - } - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/spel/SpelRepository.java b/src/test/java/org/springframework/data/couchbase/repository/spel/SpelRepository.java deleted file mode 100644 index eb2aeeab..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/spel/SpelRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.springframework.data.couchbase.repository.spel; - -import java.util.List; - -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.couchbase.repository.User; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; - -@N1qlPrimaryIndexed -public interface SpelRepository extends CrudRepository { - - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND username = \"#{oneCustomer}\"") - List findCustomUsers(); - - //notice how the SpEL syntax #{[0]} considers the first method argument, - //and N1QL placeholder resolution will still consider every method argument as a placeholder value. - //thus N1QL placeholder used is $2, to match criteriaValue - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND #{[0]} = $2") - List findUserWithDynamicCriteria(String criteriaField, Object criteriaValue); -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/spel/SpelRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/spel/SpelRepositoryIntegrationTests.java deleted file mode 100644 index c627fcfa..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/spel/SpelRepositoryIntegrationTests.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2013-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.repository.spel; - -import java.util.List; - -import com.couchbase.client.java.Bucket; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.repository.SimpleCouchbaseRepositoryListener; -import org.springframework.data.couchbase.repository.User; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Simon Baslé - */ -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = SpelConfig.class) -@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class) -public class SpelRepositoryIntegrationTests { - - @Autowired - private Bucket client; - - @Autowired - private RepositoryOperationsMapping operationsMapping; - - @Autowired - private IndexManager indexManager; - - @Autowired - private SpelRepository repository; - - @Test - public void testSpelExtensionResolved() { - List users = repository.findCustomUsers(); - assertThat(users.size()).isEqualTo(1); - assertThat(users.get(0).getKey()).isEqualTo("testuser-3"); - assertThat(users.get(0).getUsername()).isEqualTo("uname-3"); - } - - @Test - public void testSpelArgumentResolution() { - List usersByName = repository.findUserWithDynamicCriteria("username", "uname-5"); - List usersByAge = repository.findUserWithDynamicCriteria("age", 4); - - assertThat(usersByName).hasSize(1); - assertThat(usersByAge).hasSize(1); - assertThat(usersByName.get(0).getKey()).isEqualTo("testuser-5"); - assertThat(usersByAge.get(0).getKey()).isEqualTo("testuser-4"); - } - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/wiring/RepositoryTemplateWiringIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/wiring/RepositoryTemplateWiringIntegrationTests.java deleted file mode 100644 index edb897c4..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/wiring/RepositoryTemplateWiringIntegrationTests.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.springframework.data.couchbase.repository.wiring; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import com.couchbase.client.java.cluster.ClusterInfo; -import com.couchbase.client.java.util.features.CouchbaseFeature; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.annotation.Id; -; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.core.CouchbaseOperations; -import org.springframework.data.couchbase.core.CouchbaseTemplate; -import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; -import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; -import org.springframework.data.couchbase.repository.CouchbaseRepository; -import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; -import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; -import org.springframework.data.couchbase.repository.support.IndexManager; -import org.springframework.stereotype.Repository; -import org.springframework.test.context.ContextConfiguration; - -/** - * This test case demonstrates (with a bit of mocking) that the framework will take the - * {@link RepositoryOperationsMapping} into account in its wiring of repositories with - * underlying {@link CouchbaseOperations} beans. - * - * @author Simon Baslé - * @author Mark Paluch - */ -@SuppressWarnings("SpringJavaAutowiringInspection") -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration -public class RepositoryTemplateWiringIntegrationTests { - - private static CouchbaseOperations mockOpsA; - private static CouchbaseOperations mockOpsB; - private static CouchbaseOperations mockOpsC; - - @BeforeClass - public static void initMocks() { - ClusterInfo info = mock(ClusterInfo.class); - when(info.checkAvailable(any(CouchbaseFeature.class))).thenReturn(true); - - mockOpsA = mock(CouchbaseOperations.class); - when(mockOpsA.getCouchbaseClusterInfo()).thenReturn(info); - when(mockOpsA.exists(any(String.class))).thenReturn(true); - when(mockOpsA.getConverter()).thenReturn(new MappingCouchbaseConverter(new CouchbaseMappingContext())); - - - mockOpsB = mock(CouchbaseOperations.class); - when(mockOpsB.getCouchbaseClusterInfo()).thenReturn(info); - when(mockOpsB.exists(any(String.class))).thenReturn(false); - when(mockOpsB.getConverter()).thenReturn(new MappingCouchbaseConverter(new CouchbaseMappingContext())); - - - mockOpsC = spy(new CouchbaseTemplate(info, null)); - Misc cValue = new Misc(); - cValue.id = "mock"; - cValue.random = true; - when(mockOpsC.getConverter()).thenReturn(new MappingCouchbaseConverter(new CouchbaseMappingContext())); - doReturn(cValue).when(mockOpsC).findById(any(String.class), any(Class.class)); - } - - @Autowired - BucketARepository repositoryA; - - @Autowired - BucketBRepository repositoryB; - - @Autowired - BucketCRepository repositoryC; - - @Configuration - @EnableCouchbaseRepositories(basePackageClasses = RepositoryTemplateWiringIntegrationTests.class, considerNestedRepositories = true) - static class Config extends AbstractCouchbaseConfiguration { - - @Override - protected List getBootstrapHosts() { - return Arrays.asList("127.0.0.1"); - } - - @Override - protected String getBucketName() { - return "protected"; - } - - @Override - protected String getBucketPassword() { - return "password"; - } - - @Bean - public CouchbaseOperations templateA() { - return mockOpsA; - } - - @Bean - public CouchbaseOperations templateB() { - return mockOpsB; - } - - @Bean - public CouchbaseOperations templateC() { - return mockOpsC; - } - - //this is for dev so it is ok to auto-create indexes - @Override - public IndexManager indexManager() { - return new IndexManager(); - } - - @Override - public void configureRepositoryOperationsMapping(RepositoryOperationsMapping base) { - base.setDefault(templateC()) - .map(BucketBRepository.class, templateB()) - .mapEntity(Item.class, templateA()); - } - } - - @Test - public void testRepositoriesAreInstanciatedWithCorrectTemplates() { - assertThat(repositoryA).isNotNull(); - assertThat(repositoryB).isNotNull(); - assertThat(repositoryC).isNotNull(); - - boolean existA = repositoryA.existsById("testA"); - boolean existB = repositoryB.existsById("testB"); - Optional valueC = repositoryC.findById("toto"); - - assertThat(existA).isTrue(); - assertThat(existB).isFalse(); - assertThat(valueC.isPresent()).isTrue(); - valueC.ifPresent(actual -> { - assertThat(actual.id).isEqualTo("mock"); - assertThat(actual.random).isEqualTo(true); - }); - - verify(mockOpsA).exists("testA"); - verify(mockOpsB).exists("testB"); - verify(mockOpsC).findById(any(String.class), eq(Misc.class)); - } - - private static class Item { - @Id - public String id; - - public String value; - } - - private static class Misc { - @Id - public String id; - - public boolean random; - } - - @Repository - static interface BucketARepository extends CouchbaseRepository {} - - @Repository - static interface BucketBRepository extends CouchbaseRepository {} - - @Repository - static interface BucketCRepository extends CouchbaseRepository {} - -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/xmlconfig/XmlItemRepository.java b/src/test/java/org/springframework/data/couchbase/repository/xmlconfig/XmlItemRepository.java deleted file mode 100644 index ccfde783..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/xmlconfig/XmlItemRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.springframework.data.couchbase.repository.xmlconfig; - -import org.springframework.data.couchbase.repository.CouchbaseRepository; -import org.springframework.data.couchbase.repository.Item; - -public interface XmlItemRepository extends CouchbaseRepository { -} diff --git a/src/test/java/org/springframework/data/couchbase/repository/xmlconfig/XmlRepositoryConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/xmlconfig/XmlRepositoryConfigurationIntegrationTests.java deleted file mode 100644 index 6b7929d4..00000000 --- a/src/test/java/org/springframework/data/couchbase/repository/xmlconfig/XmlRepositoryConfigurationIntegrationTests.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.springframework.data.couchbase.repository.xmlconfig; - -import org.junit.Before; -import org.junit.Test; - -import org.junit.runner.RunWith; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionReader; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.core.io.ClassPathResource; -import org.springframework.data.couchbase.ContainerResourceRunner; -import org.springframework.test.context.ContextConfiguration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Simon Baslé - */ -@SuppressWarnings("SpringJavaAutowiringInspection") -@RunWith(ContainerResourceRunner.class) -@ContextConfiguration(classes = XmlRepositoryConfigurationIntegrationTests.class) -public class XmlRepositoryConfigurationIntegrationTests { - - DefaultListableBeanFactory factory; - BeanDefinitionReader reader; - - @Before - public void setUp() { - factory = new DefaultListableBeanFactory(); - reader = new XmlBeanDefinitionReader(factory); - } - - @Test - public void testInstantiateRepositoryFromXml() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-repository-bean.xml")); - - BeanDefinition definition = factory.getBeanDefinition("xmlItemRepository"); - assertThat(definition.getBeanClassName()) - .isEqualTo("org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactoryBean"); - assertDefinitionProperty(definition, "couchbaseOperations"); - - Object bean = factory.getBean("xmlItemRepository"); - assertThat(bean instanceof XmlItemRepository).isTrue(); - } - - private void assertDefinitionProperty(BeanDefinition definition, String property) { - assertThat(definition.getPropertyValues().contains(property)) - .as("bean definition properties don't include " + property).isTrue(); - } -} diff --git a/src/test/java/org/springframework/data/couchbase/util/Capabilities.java b/src/test/java/org/springframework/data/couchbase/util/Capabilities.java new file mode 100644 index 00000000..91d66416 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/util/Capabilities.java @@ -0,0 +1,55 @@ +/* + * 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.util; + +/** + * Certain capabilities used to figure out if a test can be run or not. + */ +public enum Capabilities { + /** + * This cluster is able to perform sync replications. + */ + SYNC_REPLICATION, + /** + * This cluster is able to handle N1QL queries. + */ + QUERY, + /** + * This cluster is able to handle Analytics queries. + */ + ANALYTICS, + /** + * This cluster is able to handle Search queries. + */ + SEARCH, + /** + * This cluster is able to give us a config without opening a bucket. + */ + GLOBAL_CONFIG, + /** + * This cluster is able to assign users to groups. + */ + USER_GROUPS, + /** + * The cluster has collections enabled. + */ + COLLECTIONS, + /** + * The cluster has views enabled. + */ + VIEWS +} diff --git a/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java new file mode 100644 index 00000000..1f9161a3 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/util/ClusterAwareIntegrationTests.java @@ -0,0 +1,80 @@ +/* + * 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.util; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.couchbase.client.core.env.Authenticator; +import com.couchbase.client.core.env.PasswordAuthenticator; +import com.couchbase.client.core.env.SeedNode; + +/** + * Parent class which drives all dynamic integration tests based on the configured cluster setup. + * + * @since 2.0.0 + */ +@ExtendWith(ClusterInvocationProvider.class) +public abstract class ClusterAwareIntegrationTests { + + private static TestClusterConfig testClusterConfig; + + @BeforeAll + static void setup(TestClusterConfig config) { + testClusterConfig = config; + } + + /** + * Returns the current config for the integration test cluster. + */ + public static TestClusterConfig config() { + return testClusterConfig; + } + + public static Authenticator authenticator() { + return PasswordAuthenticator.create(config().adminUsername(), config().adminPassword()); + } + + public static String bucketName() { + return config().bucketname(); + } + + /** + * Creates the right connection string out of the seed nodes in the config. + * + * @return the connection string to connect. + */ + public static String connectionString() { + return seedNodes().stream().map(s -> { + if (s.kvPort().isPresent()) { + return s.address() + ":" + s.kvPort().get(); + } else { + return s.address(); + } + }).collect(Collectors.joining(",")); + } + + public static Set seedNodes() { + return config().nodes().stream().map(cfg -> SeedNode.create(cfg.hostname(), + Optional.ofNullable(cfg.ports().get(Services.KV)), Optional.ofNullable(cfg.ports().get(Services.MANAGER)))) + .collect(Collectors.toSet()); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/util/ClusterInvocationProvider.java b/src/test/java/org/springframework/data/couchbase/util/ClusterInvocationProvider.java new file mode 100644 index 00000000..e4d159f6 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/util/ClusterInvocationProvider.java @@ -0,0 +1,123 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.util; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.support.AnnotationSupport; + +/** + * This invocation provider starts and stops the couchbase cluster before and after all tests are executed. + *

    + * Note that the internals on what and how the containers are managed is up to the {@link TestCluster} implementation, + * and if it is unmanaged than this very well be mostly a "stub". + *

    + * + * @since 2.0.0 + */ +public class ClusterInvocationProvider implements BeforeAllCallback, ParameterResolver, ExecutionCondition { + + /** + * Identifier for the container in the root store. + */ + private static final String STORE_KEY = "db"; + + /** + * Helper method to initialize the test cluster when not initialized yet. + * + * @param ctx the junit extension context. + * @return the test cluster initialized. + */ + private TestCluster accessAndMaybeInitTestCluster(final ExtensionContext ctx) { + return (TestCluster) rootStore(ctx).getOrComputeIfAbsent(STORE_KEY, key -> { + TestCluster testCluster = TestCluster.create(); + testCluster.start(); + return testCluster; + }); + } + + @Override + public void beforeAll(final ExtensionContext ctx) { + accessAndMaybeInitTestCluster(ctx); + } + + /** + * Grabs the root store from the global namespace in junit. + * + * @param ctx the extension context from the root store. + * @return the loaded store. + */ + private ExtensionContext.Store rootStore(final ExtensionContext ctx) { + return ctx.getParent().get().getStore(ExtensionContext.Namespace.GLOBAL); + } + + @Override + public boolean supportsParameter(final ParameterContext pCtx, final ExtensionContext eCtx) { + return pCtx.getParameter().getType().isAssignableFrom(TestClusterConfig.class); + } + + @Override + public Object resolveParameter(final ParameterContext pCtx, final ExtensionContext eCtx) { + return ((TestCluster) rootStore(eCtx).get(STORE_KEY)).config(); + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + TestCluster testCluster = accessAndMaybeInitTestCluster(context); + + Optional annotation = AnnotationSupport.findAnnotation(context.getElement(), IgnoreWhen.class); + if (annotation.isPresent()) { + IgnoreWhen found = annotation.get(); + for (ClusterType type : found.clusterTypes()) { + if (testCluster.type().equals(type)) { + return ConditionEvaluationResult + .disabled("Test disabled on this ClusterType (" + type + ") based on @IgnoreWhen"); + } + } + int numNodes = testCluster.config().nodes().size(); + if (numNodes > found.nodesGreaterThan() || numNodes < found.nodesLessThan()) { + return ConditionEvaluationResult + .disabled("Test disabled on the number of nodes (" + numNodes + ") based on @IgnoreWhen"); + } + int numReplicas = testCluster.config().numReplicas(); + if (numReplicas > found.replicasGreaterThan() || numReplicas < found.replicasLessThan()) { + return ConditionEvaluationResult + .disabled("Test disabled on the number of replicas (" + numReplicas + ") based on @IgnoreWhen"); + } + for (Capabilities neededCapability : found.missesCapabilities()) { + if (!testCluster.config().capabilities().contains(neededCapability)) { + return ConditionEvaluationResult.disabled("Test disabled because capability of " + neededCapability + + " is missing on cluster based on @IgnoreWhen"); + } + } + for (Capabilities unwantedCapability : found.hasCapabilities()) { + if (testCluster.config().capabilities().contains(unwantedCapability)) { + return ConditionEvaluationResult.disabled("Test disabled because capability of " + unwantedCapability + + " is present on cluster based on @IgnoreWhen"); + } + } + } + return ConditionEvaluationResult.enabled("Test is allowed to run based on @IgnoreWhen"); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/join/AuthorRepository.java b/src/test/java/org/springframework/data/couchbase/util/ClusterType.java similarity index 67% rename from src/test/java/org/springframework/data/couchbase/repository/join/AuthorRepository.java rename to src/test/java/org/springframework/data/couchbase/util/ClusterType.java index 51087a6b..51e5283e 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/join/AuthorRepository.java +++ b/src/test/java/org/springframework/data/couchbase/util/ClusterType.java @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.couchbase.repository.join; +package org.springframework.data.couchbase.util; -import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; -import org.springframework.data.couchbase.repository.CouchbaseRepository; - -@N1qlPrimaryIndexed -public interface AuthorRepository extends CouchbaseRepository { +public enum ClusterType { + // CONTAINERIZED, + MOCKED, UNMANAGED } diff --git a/src/main/java/org/springframework/data/couchbase/core/query/N1qlPrimaryIndexed.java b/src/test/java/org/springframework/data/couchbase/util/IgnoreWhen.java similarity index 63% rename from src/main/java/org/springframework/data/couchbase/core/query/N1qlPrimaryIndexed.java rename to src/test/java/org/springframework/data/couchbase/util/IgnoreWhen.java index a5d2da6a..fc5dc242 100644 --- a/src/main/java/org/springframework/data/couchbase/core/query/N1qlPrimaryIndexed.java +++ b/src/test/java/org/springframework/data/couchbase/util/IgnoreWhen.java @@ -13,23 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.springframework.data.couchbase.core.query; +package org.springframework.data.couchbase.util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.data.couchbase.repository.CouchbaseRepository; - -/** - * This annotation is targeted at {@link CouchbaseRepository Repository} interfaces, indicating that the framework - * should ensure a N1QL Primary Index is present on the repository's associated bucket when the repository is created. - * - * @author Simon Baslé - */ -@Target({ElementType.TYPE}) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) -public @interface N1qlPrimaryIndexed { +public @interface IgnoreWhen { + + ClusterType[] clusterTypes() default {}; + + Capabilities[] missesCapabilities() default {}; + + Capabilities[] hasCapabilities() default {}; + + int nodesLessThan() default 0; + + int nodesGreaterThan() default Integer.MAX_VALUE; + + int replicasLessThan() default 0; + + int replicasGreaterThan() default Integer.MAX_VALUE; + } diff --git a/src/test/java/org/springframework/data/couchbase/util/MockTestCluster.java b/src/test/java/org/springframework/data/couchbase/util/MockTestCluster.java new file mode 100644 index 00000000..0f2a224d --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/util/MockTestCluster.java @@ -0,0 +1,104 @@ +/* + * 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.util; + +import java.io.IOException; +import java.util.*; + +import com.couchbase.mock.Bucket; +import com.couchbase.mock.BucketConfiguration; +import com.couchbase.mock.CouchbaseMock; +import com.couchbase.mock.memcached.MemcachedServer; +import com.couchbase.mock.security.sasl.ShaSaslServerFactory; + +/** + * Implements the integration test cluster on top of the CouchbaseMock library. + * + * @since 2.0.0 + */ +public class MockTestCluster extends TestCluster { + + private static final int RANDOM_PORT = 0; + + private final Properties properties; + + private volatile CouchbaseMock mock; + + MockTestCluster(Properties properties) { + this.properties = properties; + } + + @Override + ClusterType type() { + return ClusterType.MOCKED; + } + + @Override + TestClusterConfig _start() throws Exception { + BucketConfiguration bucketConfig = new BucketConfiguration(); + bucketConfig.type = Bucket.BucketType.COUCHBASE; + bucketConfig.numVBuckets = 1024; + bucketConfig.numNodes = Integer.parseInt(properties.getProperty("cluster.mocked.numNodes")); + bucketConfig.numReplicas = Integer.parseInt(properties.getProperty("cluster.mocked.numReplicas")); + + bucketConfig.name = UUID.randomUUID().toString(); + bucketConfig.password = UUID.randomUUID().toString(); + + try { + mock = new CouchbaseMock(RANDOM_PORT, Collections.singletonList(bucketConfig)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + mock.start(); + mock.waitForStartup(); + + for (Bucket bucket : mock.getBuckets().values()) { + for (MemcachedServer server : bucket.getServers()) { + server.setCccpEnabled(true); + + List mechs = new ArrayList<>(Arrays.asList(ShaSaslServerFactory.SUPPORTED_MECHS)); + mechs.add("PLAIN"); + server.setSaslMechanisms(mechs); + } + } + + List nodeConfigs = new ArrayList<>(); + for (Bucket bucket : mock.getBuckets().values()) { + for (MemcachedServer server : bucket.getServers()) { + Map ports = new HashMap<>(); + ports.put(Services.KV, server.getPort()); + ports.put(Services.MANAGER, mock.getHttpPort()); + ports.put(Services.VIEW, mock.getHttpPort()); // mock has views on the same http port + + nodeConfigs.add(new TestNodeConfig(server.getHostname(), ports)); + } + } + + return new TestClusterConfig(bucketConfig.name, bucketConfig.name, bucketConfig.password, nodeConfigs, + bucketConfig.numReplicas, Optional.empty(), // mock does not support certs + EnumSet.of(Capabilities.VIEWS) // mock only has a limited set of capabilities we can utilize + ); + } + + @Override + public void close() { + if (mock != null) { + mock.stop(); + } + } +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/join/BookRepository.java b/src/test/java/org/springframework/data/couchbase/util/Services.java similarity index 65% rename from src/test/java/org/springframework/data/couchbase/repository/join/BookRepository.java rename to src/test/java/org/springframework/data/couchbase/util/Services.java index 84f4d675..f4ef3726 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/join/BookRepository.java +++ b/src/test/java/org/springframework/data/couchbase/util/Services.java @@ -13,11 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.couchbase.repository.join; +package org.springframework.data.couchbase.util; -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed; -import org.springframework.data.couchbase.repository.CouchbaseRepository; - -@N1qlSecondaryIndexed(indexName = "bookIndex") -interface BookRepository extends CouchbaseRepository { +public enum Services { + KV, KV_TLS, MANAGER, MANAGER_TLS, QUERY, QUERY_TLS, ANALYTICS, ANALYTICS_TLS, SEARCH, SEARCH_TLS, VIEW, VIEW_TLS } diff --git a/src/test/java/org/springframework/data/couchbase/util/TestCluster.java b/src/test/java/org/springframework/data/couchbase/util/TestCluster.java new file mode 100644 index 00000000..776283df --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/util/TestCluster.java @@ -0,0 +1,233 @@ +/* + * 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.util; + +import static java.nio.charset.StandardCharsets.*; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +abstract class TestCluster implements ExtensionContext.Store.CloseableResource { + protected static final ObjectMapper MAPPER = new ObjectMapper(); + private static final Logger LOGGER = LoggerFactory.getLogger(TestCluster.class); + /** + * The topology spec defined by the child implementations. + */ + private volatile TestClusterConfig config; + + TestCluster() {} + + /** + * Creates the Test cluster (either managed ur unmanaged). + */ + static TestCluster create() { + Properties properties = loadProperties(); + String clusterType = properties.getProperty("cluster.type"); + + // if (clusterType.equals("containerized")) { + // return new ContainerizedTestCluster(properties); + if (clusterType.equals("mocked")) { + return new MockTestCluster(properties); + } else if (clusterType.equals("unmanaged")) { + return new UnmanagedTestCluster(properties); + } else { + throw new IllegalStateException("Unsupported test cluster type: " + clusterType); + } + } + + /** + * Load properties from the defaults file and then override with sys properties. + */ + private static Properties loadProperties() { + Properties defaults = new Properties(); + try { + try { + // This file is unversioned. Good practice is to copy integration.properties to this and make changes to this. + URL url = TestCluster.class.getResource("/integration.local.properties"); + if (url != null) { + LOGGER.info("Found test config file {}", url.getPath()); + } + defaults.load(url.openStream()); + } catch (Exception ex) { + URL url = TestCluster.class.getResource("/integration.properties"); + if (url != null) { + LOGGER.info("Found test config file {}", url.getPath()); + } + defaults.load(url.openStream()); + } + } catch (Exception ex) { + throw new RuntimeException("Could not load properties", ex); + } + + Properties all = new Properties(System.getProperties()); + for (Map.Entry property : defaults.entrySet()) { + if (all.getProperty(property.getKey().toString()) == null) { + all.put(property.getKey(), property.getValue()); + } + } + return all; + } + + /** + * Implemented by the child class to start the cluster if needed. + */ + abstract TestClusterConfig _start() throws Exception; + + abstract ClusterType type(); + + void start() { + try { + config = _start(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public TestClusterConfig config() { + return config; + } + + /** + * Helper method to extract the node configs from a raw bucket config. + * + * @param config the config. + * @return the extracted node configs. + */ + @SuppressWarnings({ "unchecked" }) + protected List nodesFromRaw(final String inputHost, final String config) { + List result = new ArrayList<>(); + Map decoded; + try { + decoded = (Map) MAPPER.readValue(config.getBytes(UTF_8), Map.class); + } catch (IOException e) { + throw new RuntimeException("Error decoding, raw: " + config, e); + } + List> ext = (List>) decoded.get("nodesExt"); + for (Map node : ext) { + Map services = (Map) node.get("services"); + String hostname = (String) node.get("hostname"); + if (hostname == null) { + hostname = inputHost; + } + Map ports = new HashMap<>(); + if (services.containsKey("kv")) { + ports.put(Services.KV, services.get("kv")); + } + if (services.containsKey("kvSSL")) { + ports.put(Services.KV_TLS, services.get("kvSSL")); + } + if (services.containsKey("mgmt")) { + ports.put(Services.MANAGER, services.get("mgmt")); + } + if (services.containsKey("mgmtSSL")) { + ports.put(Services.MANAGER_TLS, services.get("mgmtSSL")); + } + if (services.containsKey("n1ql")) { + ports.put(Services.QUERY, services.get("n1ql")); + } + if (services.containsKey("n1qlSSL")) { + ports.put(Services.QUERY_TLS, services.get("n1qlSSL")); + } + if (services.containsKey("cbas")) { + ports.put(Services.ANALYTICS, services.get("cbas")); + } + if (services.containsKey("cbasSSL")) { + ports.put(Services.ANALYTICS_TLS, services.get("cbasSSL")); + } + if (services.containsKey("fts")) { + ports.put(Services.SEARCH, services.get("fts")); + } + if (services.containsKey("ftsSSL")) { + ports.put(Services.SEARCH_TLS, services.get("ftsSSL")); + } + if (services.containsKey("capi")) { + ports.put(Services.VIEW, services.get("capi")); + } + if (services.containsKey("capiSSL")) { + ports.put(Services.VIEW_TLS, services.get("capiSSL")); + } + result.add(new TestNodeConfig(hostname, ports)); + } + return result; + } + + protected int replicasFromRaw(final String config) { + Map decoded; + try { + decoded = (Map) MAPPER.readValue(config.getBytes(UTF_8), Map.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + Map serverMap = (Map) decoded.get("vBucketServerMap"); + return (int) serverMap.get("numReplicas"); + } + + protected Set capabilitiesFromRaw(final String config) { + Set capabilities = new HashSet<>(); + Map decoded; + try { + decoded = (Map) MAPPER.readValue(config.getBytes(UTF_8), Map.class); + } catch (IOException e) { + throw new RuntimeException("Error decoding, raw: " + config, e); + } + List> ext = (List>) decoded.get("nodesExt"); + + for (Map node : ext) { + Map services = (Map) node.get("services"); + for (String name : services.keySet()) { + if (name.equals("n1ql") || name.equals("n1qlSSL")) { + capabilities.add(Capabilities.QUERY); + } + if (name.equals("cbas") || name.equals("cbasSSL")) { + capabilities.add(Capabilities.ANALYTICS); + } + if (name.equals("fts") || name.equals("ftsSSL")) { + capabilities.add(Capabilities.SEARCH); + } + if (name.equals("capi") || name.equals("capiSSL")) { + capabilities.add(Capabilities.VIEWS); + } + } + } + List bucketCapabilities = (List) decoded.get("bucketCapabilities"); + if (bucketCapabilities.contains("durableWrite")) { + capabilities.add(Capabilities.SYNC_REPLICATION); + /// GCCCP was also added in 6.5 when sync replication was added, so we can assume the same. + capabilities.add(Capabilities.GLOBAL_CONFIG); + // same for user groups + capabilities.add(Capabilities.USER_GROUPS); + } + if (bucketCapabilities.contains("collections")) { + capabilities.add(Capabilities.COLLECTIONS); + } + return capabilities; + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/util/TestClusterConfig.java b/src/test/java/org/springframework/data/couchbase/util/TestClusterConfig.java new file mode 100644 index 00000000..3dc867ac --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/util/TestClusterConfig.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.util; + +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * This configuration is populated from the cluster container and represents the settings that can be used from the + * tests for bootstrapping their own code. + * + * @since 2.0.0 + */ +public class TestClusterConfig { + + private final String bucketname; + private final String adminUsername; + private final String adminPassword; + private final List nodes; + private final int numReplicas; + private final Optional clusterCert; + private final Set capabilities; + + TestClusterConfig(String bucketname, String adminUsername, String adminPassword, List nodes, + int numReplicas, Optional clusterCert, Set capabilities) { + this.bucketname = bucketname; + this.adminUsername = adminUsername; + this.adminPassword = adminPassword; + this.nodes = nodes; + this.numReplicas = numReplicas; + this.clusterCert = clusterCert; + this.capabilities = capabilities; + } + + public String bucketname() { + return bucketname; + } + + public String adminUsername() { + return adminUsername; + } + + public String adminPassword() { + return adminPassword; + } + + public List nodes() { + return nodes; + } + + public int numReplicas() { + return numReplicas; + } + + public Set capabilities() { + return capabilities; + } + + public Optional clusterCert() { + return clusterCert; + } + + /** + * Finds the first node with a given service enabled in the config. + *

    + * This method can be used to find bootstrap nodes and similar. + *

    + * + * @param service the service to find. + * @return a node config if found, empty otherwise. + */ + public Optional firstNodeWith(Services service) { + return nodes.stream().filter(n -> n.ports().containsKey(service)).findFirst(); + } + + @Override + public String toString() { + return "TestClusterConfig{" + "bucketname='" + bucketname + '\'' + ", adminUsername='" + adminUsername + '\'' + + ", adminPassword='" + adminPassword + '\'' + ", nodes=" + nodes + ", numReplicas=" + numReplicas + + ", clusterCert=" + clusterCert + ", capabilities=" + capabilities + '}'; + } +} diff --git a/src/test/java/org/springframework/data/couchbase/util/TestNodeConfig.java b/src/test/java/org/springframework/data/couchbase/util/TestNodeConfig.java new file mode 100644 index 00000000..1d364e0a --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/util/TestNodeConfig.java @@ -0,0 +1,44 @@ +/* + * 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.util; + +import java.util.Map; + +public class TestNodeConfig { + + private final String hostname; + + private final Map ports; + + public TestNodeConfig(String hostname, Map ports) { + this.hostname = hostname; + this.ports = ports; + } + + public String hostname() { + return hostname; + } + + public Map ports() { + return ports; + } + + @Override + public String toString() { + return "TestNodeConfig{" + "hostname='" + hostname + '\'' + ", ports=" + ports + '}'; + } +} diff --git a/src/test/java/org/springframework/data/couchbase/util/UnmanagedTestCluster.java b/src/test/java/org/springframework/data/couchbase/util/UnmanagedTestCluster.java new file mode 100644 index 00000000..cb3f6de5 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/util/UnmanagedTestCluster.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012-2020 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.util; + +import static java.nio.charset.StandardCharsets.*; + +import okhttp3.Credentials; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.UUID; + +public class UnmanagedTestCluster extends TestCluster { + + private final OkHttpClient httpClient = new OkHttpClient.Builder().build(); + private final String seedHost; + private final int seedPort; + private final String adminUsername; + private final String adminPassword; + private final int numReplicas; + private volatile String bucketname; + + UnmanagedTestCluster(final Properties properties) { + seedHost = properties.getProperty("cluster.unmanaged.seed").split(":")[0]; + seedPort = Integer.parseInt(properties.getProperty("cluster.unmanaged.seed").split(":")[1]); + adminUsername = properties.getProperty("cluster.adminUsername"); + adminPassword = properties.getProperty("cluster.adminPassword"); + numReplicas = Integer.parseInt(properties.getProperty("cluster.unmanaged.numReplicas")); + } + + @Override + ClusterType type() { + return ClusterType.UNMANAGED; + } + + @Override + TestClusterConfig _start() throws Exception { + bucketname = UUID.randomUUID().toString(); + + Response postResponse = httpClient + .newCall(new Request.Builder().header("Authorization", Credentials.basic(adminUsername, adminPassword)) + .url("http://" + seedHost + ":" + seedPort + "/pools/default/buckets") + .post(new FormBody.Builder().add("name", bucketname).add("bucketType", "membase").add("ramQuotaMB", "100") + .add("replicaNumber", Integer.toString(numReplicas)).add("flushEnabled", "1").build()) + .build()) + .execute(); + + if (postResponse.code() != 202) { + throw new Exception("Could not create bucket: " + postResponse + ", Reason: " + postResponse.body().string()); + } + + Response getResponse = httpClient + .newCall(new Request.Builder().header("Authorization", Credentials.basic(adminUsername, adminPassword)) + .url("http://" + seedHost + ":" + seedPort + "/pools/default/b/" + bucketname).build()) + .execute(); + + String raw = getResponse.body().string(); + + waitUntilAllNodesHealthy(); + + Optional cert = loadClusterCertificate(); + + return new TestClusterConfig(bucketname, adminUsername, adminPassword, nodesFromRaw(seedHost, raw), + replicasFromRaw(raw), cert, capabilitiesFromRaw(raw)); + } + + private Optional loadClusterCertificate() { + try { + Response getResponse = httpClient + .newCall(new Request.Builder().header("Authorization", Credentials.basic(adminUsername, adminPassword)) + .url("http://" + seedHost + ":" + seedPort + "/pools/default/certificate").build()) + .execute(); + + String raw = getResponse.body().string(); + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate cert = cf.generateCertificate(new ByteArrayInputStream(raw.getBytes(UTF_8))); + return Optional.of((X509Certificate) cert); + } catch (Exception ex) { + // could not load certificate, maybe add logging? could be CE instance. + return Optional.empty(); + } + } + + private void waitUntilAllNodesHealthy() throws Exception { + while (true) { + Response getResponse = httpClient + .newCall(new Request.Builder().header("Authorization", Credentials.basic(adminUsername, adminPassword)) + .url("http://" + seedHost + ":" + seedPort + "/pools/default/").build()) + .execute(); + + String raw = getResponse.body().string(); + + Map decoded; + try { + decoded = (Map) MAPPER.readValue(raw, Map.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + + List> nodes = (List>) decoded.get("nodes"); + int healthy = 0; + for (Map node : nodes) { + String status = (String) node.get("status"); + if (status.equals("healthy")) { + healthy++; + } + } + if (healthy == nodes.size()) { + break; + } + Thread.sleep(100); + } + } + + @Override + public void close() { + try { + httpClient + .newCall(new Request.Builder().header("Authorization", Credentials.basic(adminUsername, adminPassword)) + .url("http://" + seedHost + ":" + seedPort + "/pools/default/buckets/" + bucketname).delete().build()) + .execute(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/src/test/kotlin/org/springframework/data/couchbase/core/CouchbaseOperationsExtensionsIntegrationTest.kt b/src/test/kotlin/org/springframework/data/couchbase/core/CouchbaseOperationsExtensionsIntegrationTest.kt deleted file mode 100644 index 145f0225..00000000 --- a/src/test/kotlin/org/springframework/data/couchbase/core/CouchbaseOperationsExtensionsIntegrationTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.springframework.data.couchbase.core - -import org.junit.Test -import org.junit.runner.RunWith -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.data.annotation.Id -import org.springframework.data.couchbase.ContainerResourceRunner -import org.springframework.data.couchbase.IntegrationTestApplicationConfig -import org.springframework.test.context.ContextConfiguration -import kotlin.test.assertEquals - -@RunWith(ContainerResourceRunner::class) -@ContextConfiguration(classes = [IntegrationTestApplicationConfig::class]) -class CouchbaseOperationsExtensionsIntegrationTest { - - @Autowired - lateinit var template: CouchbaseTemplate - - @Test - fun `findById should call the reified extension`() { - val entityId = "CouchbaseOperationsExtensionsTestFindById" - val entity = Entity(entityId) - template.save(entity) - val stored = template.findById(entityId) - assertEquals(entity.id, stored.id) - } - - class Entity constructor(id: String) { - @Id - var id = id - } -} diff --git a/src/test/kotlin/org/springframework/data/couchbase/core/RxJavaCouchbaseOperationsExtensionsIntegrationTest.kt b/src/test/kotlin/org/springframework/data/couchbase/core/RxJavaCouchbaseOperationsExtensionsIntegrationTest.kt deleted file mode 100644 index f05e34f0..00000000 --- a/src/test/kotlin/org/springframework/data/couchbase/core/RxJavaCouchbaseOperationsExtensionsIntegrationTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.springframework.data.couchbase.core - -import org.junit.Test -import org.junit.runner.RunWith -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.data.annotation.Id -import org.springframework.data.couchbase.ContainerResourceRunner -import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig -import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed -import org.springframework.test.context.ContextConfiguration -import kotlin.test.assertEquals - - -@RunWith(ContainerResourceRunner::class) -@ContextConfiguration(classes = [ReactiveIntegrationTestApplicationConfig::class]) -class RxJavaCouchbaseOperationsExtensionsIntegrationTest { - - @Autowired - lateinit var template: RxJavaCouchbaseTemplate - - @Test - fun `findById should call the reified extension`() { - val entityId = "RxJavaCouchbaseOperationsExtensionsTestFindById" - val entity = Entity(entityId) - template.save(entity).toBlocking().single() - val stored = template.findById(entityId).toBlocking().single() - assertEquals(entity.id, stored.id) - } - - class Entity constructor(id: String) { - @Id - var id = id - } -} - diff --git a/src/test/resources/META-INF/beans.xml b/src/test/resources/META-INF/beans.xml index 3571dab6..786d9607 100644 --- a/src/test/resources/META-INF/beans.xml +++ b/src/test/resources/META-INF/beans.xml @@ -1,6 +1,6 @@ - + diff --git a/src/test/resources/configurations/couchbase-consistency.xml b/src/test/resources/configurations/couchbase-consistency.xml index 1dbc757e..7b59d34a 100644 --- a/src/test/resources/configurations/couchbase-consistency.xml +++ b/src/test/resources/configurations/couchbase-consistency.xml @@ -1,7 +1,7 @@ - diff --git a/src/test/resources/configurations/couchbase-multi-bucket-bean.xml b/src/test/resources/configurations/couchbase-multi-bucket-bean.xml index 7d0e057a..9fe579ff 100644 --- a/src/test/resources/configurations/couchbase-multi-bucket-bean.xml +++ b/src/test/resources/configurations/couchbase-multi-bucket-bean.xml @@ -1,18 +1,17 @@ - + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - - - - + + + + \ No newline at end of file diff --git a/src/test/resources/configurations/couchbase-repository-bean.xml b/src/test/resources/configurations/couchbase-repository-bean.xml index 6a1730fe..af6f17d7 100644 --- a/src/test/resources/configurations/couchbase-repository-bean.xml +++ b/src/test/resources/configurations/couchbase-repository-bean.xml @@ -1,7 +1,7 @@ - diff --git a/src/test/resources/configurations/couchbase-template-bean.xml b/src/test/resources/configurations/couchbase-template-bean.xml index 80068b1b..2662a892 100644 --- a/src/test/resources/configurations/couchbase-template-bean.xml +++ b/src/test/resources/configurations/couchbase-template-bean.xml @@ -1,13 +1,12 @@ - - diff --git a/src/test/resources/configurations/couchbase-template-with-translation-service-bean.xml b/src/test/resources/configurations/couchbase-template-with-translation-service-bean.xml index 6adfbfb1..1ebcadaa 100644 --- a/src/test/resources/configurations/couchbase-template-with-translation-service-bean.xml +++ b/src/test/resources/configurations/couchbase-template-with-translation-service-bean.xml @@ -1,7 +1,7 @@ - @@ -12,6 +12,7 @@ - + \ No newline at end of file diff --git a/src/test/resources/configurations/couchbase-typekey.xml b/src/test/resources/configurations/couchbase-typekey.xml index b21f92b0..8205ae03 100644 --- a/src/test/resources/configurations/couchbase-typekey.xml +++ b/src/test/resources/configurations/couchbase-typekey.xml @@ -1,7 +1,7 @@ - @@ -13,19 +13,20 @@ - + - - - - - - - + + + + + + + - + \ No newline at end of file diff --git a/src/test/resources/configurations/couchbaseBucket-bean.xml b/src/test/resources/configurations/couchbaseBucket-bean.xml index d38d1c0c..b8d5f37a 100644 --- a/src/test/resources/configurations/couchbaseBucket-bean.xml +++ b/src/test/resources/configurations/couchbaseBucket-bean.xml @@ -1,19 +1,19 @@ - + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - + - + - + - + - + \ No newline at end of file diff --git a/src/test/resources/configurations/couchbaseCluster-bean.xml b/src/test/resources/configurations/couchbaseCluster-bean.xml index 141004ff..00ef9f9e 100644 --- a/src/test/resources/configurations/couchbaseCluster-bean.xml +++ b/src/test/resources/configurations/couchbaseCluster-bean.xml @@ -1,14 +1,13 @@ - + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - + - + diff --git a/src/test/resources/configurations/couchbaseEnv-bean.xml b/src/test/resources/configurations/couchbaseEnv-bean.xml index 2916df65..28a0090b 100644 --- a/src/test/resources/configurations/couchbaseEnv-bean.xml +++ b/src/test/resources/configurations/couchbaseEnv-bean.xml @@ -1,14 +1,13 @@ - + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> - + - + + /> \ No newline at end of file diff --git a/src/test/resources/configurations/couchbaseSingleEnv-bean.xml b/src/test/resources/configurations/couchbaseSingleEnv-bean.xml index 83ca59e2..41f40187 100644 --- a/src/test/resources/configurations/couchbaseSingleEnv-bean.xml +++ b/src/test/resources/configurations/couchbaseSingleEnv-bean.xml @@ -1,10 +1,9 @@ - + http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> diff --git a/src/test/resources/integration.properties b/src/test/resources/integration.properties new file mode 100644 index 00000000..70414578 --- /dev/null +++ b/src/test/resources/integration.properties @@ -0,0 +1,15 @@ +# Couchbase Integration-Test Default Properties +# If set to false, it is assumed that the host is managing the cluster and +# as a result no containers or anything will be spun up. +# Options: containerized, mocked, unmanaged +cluster.type=unmanaged +# Default configs for both cases +cluster.adminUsername=Administrator +cluster.adminPassword=password +# Default configs for the mocked environment +cluster.mocked.numNodes=1 +cluster.mocked.numReplicas=1 +# Entry point configuration if not managed +# value of hostname and ns_server port +cluster.unmanaged.seed=127.0.0.1:8091 +cluster.unmanaged.numReplicas=0 \ No newline at end of file diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml index c67b1d6a..b45efd19 100644 --- a/src/test/resources/logback.xml +++ b/src/test/resources/logback.xml @@ -1,29 +1,29 @@ - - - %d %5p %40.40c:%4L - %m%n - - + + + %d %5p %40.40c:%4L - %m%n + + - + - - - + + + - - - - - + + + + + \ No newline at end of file diff --git a/template.mf b/template.mf index 881b342d..5e195fed 100644 --- a/template.mf +++ b/template.mf @@ -17,4 +17,4 @@ Import-Template: org.springframework.*;version="${spring:[=.=.=.=,+1.0.0)}", org.springframework.data.*;version="${springdata.commons:[=.=.=.=,+1.0.0)}", org.w3c.*;version="0.0.0", - rx.*;version="${rxjava:[=.=.=,+1.0.0)}" +