DATACOUCH-504 - Migrate to Couchbase SDK 3

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

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase;
import java.io.Closeable;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.Scope;
public interface CouchbaseClientFactory extends Closeable {
Cluster getCluster();
Bucket getBucket();
Scope getScope();
PersistenceExceptionTranslator getExceptionTranslator();
Collection getCollection(String name);
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase;
import com.couchbase.client.java.env.ClusterEnvironment;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator;
import com.couchbase.client.core.env.Authenticator;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.ClusterOptions;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.Scope;
public class SimpleCouchbaseClientFactory implements CouchbaseClientFactory {
private final Cluster cluster;
private final Bucket bucket;
private final Scope scope;
private final PersistenceExceptionTranslator exceptionTranslator;
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
final String bucketName) {
this(connectionString, authenticator, bucketName, null);
}
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
final String bucketName, final String scopeName) {
this(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator)), bucketName, scopeName);
}
public SimpleCouchbaseClientFactory(final String connectionString, final Authenticator authenticator,
final String bucketName, final String scopeName, final ClusterEnvironment environment) {
this(Cluster.connect(connectionString, ClusterOptions.clusterOptions(authenticator).environment(environment)), bucketName, scopeName);
}
SimpleCouchbaseClientFactory(final Cluster cluster, final String bucketName, final String scopeName) {
this.cluster = cluster;
this.bucket = cluster.bucket(bucketName);
this.scope = scopeName == null ? bucket.defaultScope() : bucket.scope(scopeName);
this.exceptionTranslator = new CouchbaseExceptionTranslator();
}
public CouchbaseClientFactory withScope(final String scopeName) {
return new SimpleCouchbaseClientFactory(cluster, bucket.name(), scopeName);
}
@Override
public Cluster getCluster() {
return cluster;
}
@Override
public Bucket getBucket() {
return bucket;
}
@Override
public Scope getScope() {
return scope;
}
@Override
public Collection getCollection(final String collectionName) {
final Scope scope = getScope();
if (collectionName == null) {
if (!scope.name().equals(CollectionIdentifier.DEFAULT_SCOPE)) {
throw new IllegalStateException("A collectionName must be provided if a non-default scope is used!");
}
return getBucket().defaultCollection();
}
return scope.collection(collectionName);
}
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return exceptionTranslator;
}
@Override
public void close() {
cluster.disconnect();
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.cache;
import org.springframework.util.Assert;
/**
* {@link CacheKeyPrefix} provides a hook for creating custom prefixes prepended to the actual {@literal key} stored in
* Couchbase.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Michael Nitschinger
* @since 4.0.0
*/
@FunctionalInterface
public interface CacheKeyPrefix {
/**
* Default separator.
*
* @since 2.3
*/
String SEPARATOR = "::";
/**
* Creates a default {@link CacheKeyPrefix} scheme that prefixes cache keys with {@code cacheName} followed by double
* colons. A cache named {@code myCache} will prefix all cache keys with {@code myCache::}.
*
* @return the default {@link CacheKeyPrefix} scheme.
*/
static CacheKeyPrefix simple() {
return name -> name + SEPARATOR;
}
/**
* Creates a {@link CacheKeyPrefix} scheme that prefixes cache keys with the given {@code prefix}. The prefix is
* prepended to the {@code cacheName} followed by double colons. A prefix {@code cb-} with a cache named
* {@code myCache} results in {@code cb-myCache::}.
*
* @param prefix must not be {@literal null}.
* @return the default {@link CacheKeyPrefix} scheme.
* @since 4.0.0
*/
static CacheKeyPrefix prefixed(final String prefix) {
Assert.notNull(prefix, "Prefix must not be null!");
return name -> prefix + name + SEPARATOR;
}
/**
* Compute the prefix for the actual {@literal key} stored in Couchbase.
*
* @param cacheName will never be {@literal null}.
* @return never {@literal null}.
*/
String compute(String cacheName);
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.cache;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.Callable;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
public class CouchbaseCache extends AbstractValueAdaptingCache {
private final String name;
private final CouchbaseCacheWriter cacheWriter;
private final CouchbaseCacheConfiguration cacheConfig;
private final ConversionService conversionService;
protected CouchbaseCache(final String name, final CouchbaseCacheWriter cacheWriter,
final CouchbaseCacheConfiguration cacheConfig) {
super(cacheConfig.getAllowCacheNullValues());
Assert.notNull(name, "Name must not be null!");
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
Assert.notNull(cacheConfig, "CacheConfig must not be null!");
this.name = name;
this.cacheWriter = cacheWriter;
this.cacheConfig = cacheConfig;
this.conversionService = cacheConfig.getConversionService();
}
private static <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
try {
return valueLoader.call();
} catch (Exception e) {
throw new ValueRetrievalException(key, valueLoader, e);
}
}
@Override
public String getName() {
return name;
}
@Override
public CouchbaseCacheWriter getNativeCache() {
return cacheWriter;
}
@Override
protected Object lookup(final Object key) {
return cacheWriter.get(cacheConfig.getCollectionName(), createCacheKey(key), cacheConfig.getValueTranscoder());
}
@Override
@SuppressWarnings("unchecked")
public synchronized <T> T get(final Object key, final Callable<T> valueLoader) {
ValueWrapper result = get(key);
if (result != null) {
return (T) result.get();
}
T value = valueFromLoader(key, valueLoader);
put(key, value);
return value;
}
@Override
public void put(final Object key, final Object value) {
if (!isAllowNullValues() && value == null) {
throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or "
+ "configure CouchbaseCache to allow 'null' via CouchbaseCacheConfiguration.",
name));
}
cacheWriter.put(cacheConfig.getCollectionName(), createCacheKey(key), value, cacheConfig.getExpiry(),
cacheConfig.getValueTranscoder());
}
@Override
public ValueWrapper putIfAbsent(final Object key, final Object value) {
if (!isAllowNullValues() && value == null) {
return get(key);
}
Object result = cacheWriter.putIfAbsent(cacheConfig.getCollectionName(), createCacheKey(key), value,
cacheConfig.getExpiry(), cacheConfig.getValueTranscoder());
if (result == null) {
return null;
}
return new SimpleValueWrapper(result);
}
@Override
public void evict(final Object key) {
cacheWriter.remove(cacheConfig.getCollectionName(), createCacheKey(key));
}
@Override
public boolean evictIfPresent(final Object key) {
return cacheWriter.remove(cacheConfig.getCollectionName(), createCacheKey(key));
}
@Override
public boolean invalidate() {
return cacheWriter.clear(cacheConfig.getKeyPrefixFor(name)) > 0;
}
@Override
public void clear() {
cacheWriter.clear(cacheConfig.getKeyPrefixFor(name));
}
/**
* Customization hook for creating cache key before it gets serialized.
*
* @param key will never be {@literal null}.
* @return never {@literal null}.
*/
protected String createCacheKey(final Object key) {
String convertedKey = convertKey(key);
if (!cacheConfig.usePrefix()) {
return convertedKey;
}
return prefixCacheKey(convertedKey);
}
/**
* Convert {@code key} to a {@link String} representation used for cache key creation.
*
* @param key will never be {@literal null}.
* @return never {@literal null}.
* @throws IllegalStateException if {@code key} cannot be converted to {@link String}.
*/
protected String convertKey(final Object key) {
if (key instanceof String) {
return (String) key;
}
TypeDescriptor source = TypeDescriptor.forObject(key);
if (conversionService.canConvert(source, TypeDescriptor.valueOf(String.class))) {
try {
return conversionService.convert(key, String.class);
} catch (ConversionFailedException e) {
// may fail if the given key is a collection
if (isCollectionLikeOrMap(source)) {
return convertCollectionLikeOrMapKey(key, source);
}
throw e;
}
}
Method toString = ReflectionUtils.findMethod(key.getClass(), "toString");
if (toString != null && !Object.class.equals(toString.getDeclaringClass())) {
return key.toString();
}
throw new IllegalStateException(String.format(
"Cannot convert cache key %s to String. Please register a suitable Converter via "
+ "'CouchbaseCacheConfiguration.configureKeyConverters(...)' or override '%s.toString()'.",
source, key.getClass().getSimpleName()));
}
private String prefixCacheKey(final String key) {
// allow contextual cache names by computing the key prefix on every call.
return cacheConfig.getKeyPrefixFor(name) + key;
}
private boolean isCollectionLikeOrMap(final TypeDescriptor source) {
return source.isArray() || source.isCollection() || source.isMap();
}
private String convertCollectionLikeOrMapKey(final Object key, final TypeDescriptor source) {
if (source.isMap()) {
StringBuilder target = new StringBuilder("{");
for (Map.Entry<?, ?> entry : ((Map<?, ?>) key).entrySet()) {
target.append(convertKey(entry.getKey())).append("=").append(convertKey(entry.getValue()));
}
target.append("}");
return target.toString();
} else if (source.isCollection() || source.isArray()) {
StringJoiner sj = new StringJoiner(",");
Collection<?> collection = source.isCollection() ? (Collection<?>) key
: Arrays.asList(ObjectUtils.toObjectArray(key));
for (Object val : collection) {
sj.add(convertKey(val));
}
return "[" + sj.toString() + "]";
}
throw new IllegalArgumentException(String.format("Cannot convert cache key %s to String.", key));
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.cache;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.util.Assert;
import com.couchbase.client.java.codec.SerializableTranscoder;
import com.couchbase.client.java.codec.Transcoder;
public class CouchbaseCacheConfiguration {
private final Duration expiry;
private final boolean cacheNullValues;
private final CacheKeyPrefix keyPrefix;
private final boolean usePrefix;
private final Transcoder valueTranscoder;
private final ConversionService conversionService;
private final String collectionName;
private CouchbaseCacheConfiguration(final Duration expiry, final boolean cacheNullValues, final boolean usePrefix,
final CacheKeyPrefix keyPrefix, final ConversionService conversionService, final Transcoder valueTranscoder,
final String collectionName) {
this.expiry = expiry;
this.cacheNullValues = cacheNullValues;
this.usePrefix = usePrefix;
this.keyPrefix = keyPrefix;
this.conversionService = conversionService;
this.valueTranscoder = valueTranscoder;
this.collectionName = collectionName;
}
public static CouchbaseCacheConfiguration defaultCacheConfig() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
registerDefaultConverters(conversionService);
return new CouchbaseCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(), conversionService,
SerializableTranscoder.INSTANCE, null);
}
/**
* Registers default cache key converters. The following converters get registered:
* <ul>
* <li>{@link String} to {@link byte byte[]} using UTF-8 encoding.</li>
* <li>{@link SimpleKey} to {@link String}</li>
*
* @param registry must not be {@literal null}.
*/
public static void registerDefaultConverters(final ConverterRegistry registry) {
Assert.notNull(registry, "ConverterRegistry must not be null!");
registry.addConverter(String.class, byte[].class, source -> source.getBytes(StandardCharsets.UTF_8));
registry.addConverter(SimpleKey.class, String.class, SimpleKey::toString);
}
/**
* Set the expiry to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache.
*
* @param expiry must not be {@literal null}.
* @return new {@link CouchbaseCacheConfiguration}.
*/
public CouchbaseCacheConfiguration entryExpiry(final Duration expiry) {
Assert.notNull(expiry, "Expiry duration must not be null!");
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, usePrefix, keyPrefix, conversionService,
valueTranscoder, collectionName);
}
/**
* Sets a custom transcoder to use for reads and writes.
*
* @param valueTranscoder the transcoder that should be used.
* @return new {@link CouchbaseCacheConfiguration}.
*/
public CouchbaseCacheConfiguration valueTranscoder(final Transcoder valueTranscoder) {
Assert.notNull(valueTranscoder, "Transcoder must not be null!");
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, usePrefix, keyPrefix, conversionService,
valueTranscoder, collectionName);
}
/**
* Disable caching {@literal null} values. <br />
* <strong>NOTE</strong> any {@link org.springframework.cache.Cache#put(Object, Object)} operation involving
* {@literal null} value will error. Nothing will be written to Couchbase, nothing will be removed. An already
* existing key will still be there afterwards with the very same value as before.
*
* @return new {@link CouchbaseCacheConfiguration}.
*/
public CouchbaseCacheConfiguration disableCachingNullValues() {
return new CouchbaseCacheConfiguration(expiry, false, usePrefix, keyPrefix, conversionService, valueTranscoder,
collectionName);
}
/**
* Prefix the {@link CouchbaseCache#getName() cache name} with the given value. <br />
* The generated cache key will be: {@code prefix + cache name + "::" + cache entry key}.
*
* @param prefix the prefix to prepend to the cache name.
* @return this.
* @see #computePrefixWith(CacheKeyPrefix)
* @see CacheKeyPrefix#prefixed(String)
*/
public CouchbaseCacheConfiguration prefixCacheNameWith(final String prefix) {
return computePrefixWith(CacheKeyPrefix.prefixed(prefix));
}
/**
* Use the given {@link CacheKeyPrefix} to compute the prefix for the actual Couchbase {@literal key} given the
* {@literal cache name} as function input.
*
* @param cacheKeyPrefix must not be {@literal null}.
* @return new {@link CouchbaseCacheConfiguration}.
* @see CacheKeyPrefix
*/
public CouchbaseCacheConfiguration computePrefixWith(CacheKeyPrefix cacheKeyPrefix) {
Assert.notNull(cacheKeyPrefix, "Function for computing prefix must not be null!");
return new CouchbaseCacheConfiguration(expiry, cacheNullValues, true, cacheKeyPrefix, conversionService,
valueTranscoder, collectionName);
}
/**
* @return The expiration time (ttl) for cache entries. Never {@literal null}.
*/
public Duration getExpiry() {
return expiry;
}
/**
* @return {@literal true} if caching {@literal null} is allowed.
*/
public boolean getAllowCacheNullValues() {
return cacheNullValues;
}
/**
* @return The {@link ConversionService} used for cache key to {@link String} conversion. Never {@literal null}.
*/
public ConversionService getConversionService() {
return conversionService;
}
/**
* @return {@literal true} if cache keys need to be prefixed with the {@link #getKeyPrefixFor(String)} if present or
* the default which resolves to {@link Cache#getName()}.
*/
public boolean usePrefix() {
return usePrefix;
}
/**
* Get the computed {@literal key} prefix for a given {@literal cacheName}.
*
* @return never {@literal null}.
*/
public String getKeyPrefixFor(final String cacheName) {
Assert.notNull(cacheName, "Cache name must not be null!");
return keyPrefix.compute(cacheName);
}
/**
* Get the transcoder for encoding and decoding cache values.
*/
public Transcoder getValueTranscoder() {
return valueTranscoder;
}
/**
* The name of the collection to use for this cache - if empty uses the default collection.
*/
public String getCollectionName() {
return collectionName;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.cache;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.cache.Cache;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public class CouchbaseCacheManager extends AbstractCacheManager {
private final CouchbaseCacheWriter cacheWriter;
private final CouchbaseCacheConfiguration defaultCacheConfig;
private final Map<String, CouchbaseCacheConfiguration> initialCacheConfiguration;
private final boolean allowInFlightCacheCreation;
/**
* Creates new {@link CouchbaseCacheManager} using given {@link CouchbaseCacheWriter} and default
* {@link CouchbaseCacheConfiguration}.
*
* @param cacheWriter must not be {@literal null}.
* @param defaultCacheConfiguration must not be {@literal null}. Maybe just use
* {@link CouchbaseCacheConfiguration#defaultCacheConfig()}.
* @param allowInFlightCacheCreation allow create unconfigured caches.
*/
private CouchbaseCacheManager(final CouchbaseCacheWriter cacheWriter,
final CouchbaseCacheConfiguration defaultCacheConfiguration, final boolean allowInFlightCacheCreation) {
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
this.cacheWriter = cacheWriter;
this.defaultCacheConfig = defaultCacheConfiguration;
this.initialCacheConfiguration = new LinkedHashMap<>();
this.allowInFlightCacheCreation = allowInFlightCacheCreation;
}
public static CouchbaseCacheManager create(final CouchbaseClientFactory connectionFactory) {
return new CouchbaseCacheManager(new DefaultCouchbaseCacheWriter(connectionFactory),
CouchbaseCacheConfiguration.defaultCacheConfig(), true);
}
@Override
protected Collection<? extends Cache> loadCaches() {
final List<CouchbaseCache> caches = new LinkedList<>();
for (Map.Entry<String, CouchbaseCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
caches.add(createCouchbaseCache(entry.getKey(), entry.getValue()));
}
return caches;
}
@Override
protected CouchbaseCache getMissingCache(final String name) {
return allowInFlightCacheCreation ? createCouchbaseCache(name, defaultCacheConfig) : null;
}
/**
* Configuration hook for creating {@link CouchbaseCache} with given name and {@code cacheConfig}.
*
* @param name must not be {@literal null}.
* @param cacheConfig can be {@literal null}.
* @return never {@literal null}.
*/
protected CouchbaseCache createCouchbaseCache(final String name,
@Nullable final CouchbaseCacheConfiguration cacheConfig) {
return new CouchbaseCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.cache;
import java.time.Duration;
import org.springframework.lang.Nullable;
import com.couchbase.client.java.codec.Transcoder;
public interface CouchbaseCacheWriter {
/**
* Write the given key/value pair to Couchbase an set the expiration time if defined.
*
* @param collectionName The cache name must not be {@literal null}.
* @param key The key for the cache entry. Must not be {@literal null}.
* @param value The value stored for the key. Must not be {@literal null}.
* @param expiry Optional expiration time. Can be {@literal null}.
* @param transcoder Optional transcoder to use. Can be {@literal null}.
*/
void put(String collectionName, String key, Object value, @Nullable Duration expiry, @Nullable Transcoder transcoder);
/**
* Write the given value to Couchbase if the key does not already exist.
*
* @param collectionName The cache name must not be {@literal null}.
* @param key The key for the cache entry. Must not be {@literal null}.
* @param value The value stored for the key. Must not be {@literal null}.
* @param expiry Optional expiration time. Can be {@literal null}.
* @param transcoder Optional transcoder to use. Can be {@literal null}.
*/
@Nullable
Object putIfAbsent(String collectionName, String key, Object value, @Nullable Duration expiry,
@Nullable Transcoder transcoder);
/**
* Get the binary value representation from Couchbase stored for the given key.
*
* @param collectionName must not be {@literal null}.
* @param key must not be {@literal null}.
* @param transcoder Optional transcoder to use. Can be {@literal null}.
* @return {@literal null} if key does not exist.
*/
@Nullable
Object get(String collectionName, String key, @Nullable Transcoder transcoder);
/**
* Remove the given key from Couchbase.
*
* @param collectionName The cache name must not be {@literal null}.
* @param key The key for the cache entry. Must not be {@literal null}.
* @return true if the document existed on removal, false otherwise.
*/
boolean remove(String collectionName, String key);
/**
* Clears the cache with the given key pattern prefix.
*
* @param pattern the pattern to clear.
* @return the number of cleared items.
*/
long clear(String pattern);
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.cache;
import static com.couchbase.client.java.kv.GetOptions.*;
import static com.couchbase.client.java.kv.InsertOptions.*;
import static com.couchbase.client.java.kv.UpsertOptions.*;
import static com.couchbase.client.java.query.QueryOptions.*;
import java.time.Duration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import com.couchbase.client.core.error.DocumentExistsException;
import com.couchbase.client.core.error.DocumentNotFoundException;
import com.couchbase.client.core.io.CollectionIdentifier;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.Scope;
import com.couchbase.client.java.codec.Transcoder;
import com.couchbase.client.java.json.JsonObject;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.UpsertOptions;
import com.couchbase.client.java.query.QueryMetrics;
import com.couchbase.client.java.query.QueryResult;
public class DefaultCouchbaseCacheWriter implements CouchbaseCacheWriter {
private final CouchbaseClientFactory clientFactory;
public DefaultCouchbaseCacheWriter(final CouchbaseClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public void put(final String collectionName, final String key, final Object value, final Duration expiry,
final Transcoder transcoder) {
UpsertOptions options = upsertOptions();
if (expiry != null) {
options.expiry(expiry);
}
if (transcoder != null) {
options.transcoder(transcoder);
}
getCollection(collectionName).upsert(key, value, options);
}
@Override
public Object putIfAbsent(final String collectionName, final String key, final Object value, final Duration expiry,
final Transcoder transcoder) {
InsertOptions options = insertOptions();
if (expiry != null) {
options.expiry(expiry);
}
if (transcoder != null) {
options.transcoder(transcoder);
}
try {
getCollection(collectionName).insert(key, value, options);
return null;
} catch (final DocumentExistsException ex) {
// If the document exists, return the current one per contract
return get(collectionName, key, transcoder);
}
}
@Override
public Object get(final String collectionName, final String key, final Transcoder transcoder) {
// TODO .. the decoding side transcoding needs to be figured out?
try {
return getCollection(collectionName).get(key, getOptions().transcoder(transcoder)).contentAs(Object.class);
} catch (DocumentNotFoundException ex) {
return null;
}
}
@Override
public boolean remove(final String collectionName, final String key) {
try {
getCollection(collectionName).remove(key);
return true;
} catch (final DocumentNotFoundException ex) {
return false;
}
}
@Override
public long clear(final String pattern) {
QueryResult result = clientFactory.getCluster().query(
"DELETE FROM `" + clientFactory.getBucket().name() + "` where meta().id LIKE $pattern",
queryOptions().metrics(true).parameters(JsonObject.create().put("pattern", pattern + "%")));
return result.metaData().metrics().map(QueryMetrics::mutationCount).orElse(0L);
}
private Collection getCollection(final String collectionName) {
final Scope scope = clientFactory.getScope();
if (collectionName == null) {
if (!scope.name().equals(CollectionIdentifier.DEFAULT_SCOPE)) {
throw new IllegalStateException("A collectionName must be provided if a non-default scope is used!");
}
return clientFactory.getBucket().defaultCollection();
}
return scope.collection(collectionName);
}
}

View File

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

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
/**
* Provides beans to setup SDC using {@link CouchbaseConfigurer}.
* This is used by Spring boot to provide auto configuration support.
*
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
@Configuration
public abstract class AbstractCouchbaseDataConfiguration extends CouchbaseConfigurationSupport {
protected abstract CouchbaseConfigurer couchbaseConfigurer();
/**
* Creates a {@link CouchbaseTemplate}.
*
* This uses {@link #mappingCouchbaseConverter()}, {@link #translationService()} and {@link #getDefaultConsistency()}
* for construction.
*
* Additionally, it will expect injection of a {@link ClusterInfo} and a {@link Bucket} beans from the context (most
* probably from another configuration). For a self-sufficient configuration that defines such beans, see
* {@link AbstractCouchbaseConfiguration}.
*
* @throws Exception on Bean construction failure.
*/
@Bean(name = BeanNames.COUCHBASE_TEMPLATE)
public CouchbaseTemplate couchbaseTemplate() throws Exception {
CouchbaseTemplate template = new CouchbaseTemplate(couchbaseConfigurer().couchbaseClusterInfo(),
couchbaseConfigurer().couchbaseClient(), mappingCouchbaseConverter(), translationService());
template.setDefaultConsistency(getDefaultConsistency());
return template;
}
/**
* Creates the {@link RepositoryOperationsMapping} bean which will be used by the framework to choose which
* {@link CouchbaseOperations} should back which {@link CouchbaseRepository}.
* Override {@link #configureRepositoryOperationsMapping(RepositoryOperationsMapping)} in order to customize this.
*
* @throws Exception
*/
@Bean(name = BeanNames.COUCHBASE_OPERATIONS_MAPPING)
public RepositoryOperationsMapping repositoryOperationsMapping(CouchbaseTemplate couchbaseTemplate) throws Exception {
//create a base mapping that associates all repositories to the default template
RepositoryOperationsMapping baseMapping = new RepositoryOperationsMapping(couchbaseTemplate);
//let the user tune it
configureRepositoryOperationsMapping(baseMapping);
return baseMapping;
}
/**
* In order to customize the mapping between repositories/entity types to couchbase templates,
* use the provided mapping's api (eg. in order to have different buckets backing different repositories).
*
* @param mapping the default mapping (will associate all repositories to the default template).
*/
protected void configureRepositoryOperationsMapping(RepositoryOperationsMapping mapping) {
//NO_OP
}
}

View File

@@ -1,144 +0,0 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import java.util.List;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import java.lang.reflect.Proxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
* Base class for Reactive Spring Data Couchbase configuration java config
*
* @author Subhashni Balakrishnan
*/
@Configuration
public abstract class AbstractReactiveCouchbaseConfiguration
extends AbstractReactiveCouchbaseDataConfiguration implements CouchbaseConfigurer {
/**
* The list of hostnames (or IP addresses) to bootstrap from.
*
* @return the list of bootstrap hosts.
*/
protected abstract List<String> getBootstrapHosts();
/**
* The name of the bucket to connect to.
*
* @return the name of the bucket.
*/
protected abstract String getBucketName();
/**
* The user of the bucket. Override the method for users in Couchbase Server 5.0+.
*
* @return the user name.
*/
protected String getUsername() { return getBucketName(); }
/**
* The password of the bucket/User of the bucket (can be an empty string).
*
* @return the password of the bucket/user.
*/
protected abstract String getBucketPassword();
/**
* Is the {@link #getEnvironment()} to be destroyed by Spring?
*
* @return true if Spring should destroy the environment with the context, false otherwise.
*/
protected boolean isEnvironmentManagedBySpring() {
return true;
}
/**
* Override this method if you want a customized {@link CouchbaseEnvironment}.
* This environment will be managed by Spring, which will call its shutdown()
* method upon bean destruction, unless you override {@link #isEnvironmentManagedBySpring()}
* as well to return false.
*
* @return a customized environment, defaults to a {@link DefaultCouchbaseEnvironment}.
*/
protected CouchbaseEnvironment getEnvironment() {
return DefaultCouchbaseEnvironment.create();
}
@Override
protected CouchbaseConfigurer couchbaseConfigurer() {
return this;
}
@Override
@Bean(destroyMethod = "shutdown", name = BeanNames.COUCHBASE_ENV)
public CouchbaseEnvironment couchbaseEnvironment() {
if (isEnvironmentManagedBySpring()) {
return getEnvironment();
} else {
CouchbaseEnvironment proxy = (CouchbaseEnvironment) java.lang.reflect.Proxy.newProxyInstance(CouchbaseEnvironment.class.getClassLoader(),
new Class[]{CouchbaseEnvironment.class},
new CouchbaseEnvironmentNoShutdownInvocationHandler(getEnvironment()));
return proxy;
}
}
/**
* Returns the {@link Cluster} instance to connect to.
*
* @throws Exception on Bean construction failure.
*/
@Override
@Bean(destroyMethod = "disconnect", name = BeanNames.COUCHBASE_CLUSTER)
public Cluster couchbaseCluster() throws Exception {
return CouchbaseCluster.create(couchbaseEnvironment(), getBootstrapHosts());
}
@Override
@Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().clusterManager(getUsername(), getBucketPassword()).info();
}
/**
* Return the {@link Bucket} instance to connect to.
*
* @throws Exception on Bean construction failure.
*/
@Override
@Bean(destroyMethod = "close", name = BeanNames.COUCHBASE_BUCKET)
public Bucket couchbaseClient() throws Exception {
//@Bean method can use another @Bean method in the same @Configuration by directly invoking it
Cluster cluster = couchbaseCluster();
if(!getUsername().contentEquals(getBucketName())){
cluster.authenticate(getUsername(), getBucketPassword());
} else if (!getBucketPassword().isEmpty()) {
return cluster.openBucket(getBucketName(), getBucketPassword());
}
return cluster.openBucket(getBucketName());
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2017-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.core.RxJavaCouchbaseTemplate;
import org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository;
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
import org.springframework.data.couchbase.core.RxJavaCouchbaseOperations;
/**
* Provides beans to setup reactive repositories in SDC using {@link CouchbaseConfigurer}.
*
* @author Subhashni Balakrishnan
*/
@Configuration
public abstract class AbstractReactiveCouchbaseDataConfiguration extends CouchbaseConfigurationSupport {
protected abstract CouchbaseConfigurer couchbaseConfigurer();
/**
* Creates a {@link RxJavaCouchbaseTemplate}.
*
* This uses {@link #mappingCouchbaseConverter()}, {@link #translationService()} and {@link #getDefaultConsistency()}
* for construction.
*
*
* @throws Exception on Bean construction failure.
*/
@Bean(name = BeanNames.RXJAVA1_COUCHBASE_TEMPLATE)
public RxJavaCouchbaseTemplate reactiveCouchbaseTemplate() throws Exception {
RxJavaCouchbaseTemplate template = new RxJavaCouchbaseTemplate(couchbaseConfigurer().couchbaseClusterInfo(),
couchbaseConfigurer().couchbaseClient(), mappingCouchbaseConverter(), translationService());
template.setDefaultConsistency(getDefaultConsistency());
return template;
}
/**
* Creates the {@link ReactiveRepositoryOperationsMapping} bean which will be used by the framework to choose which
* {@link RxJavaCouchbaseOperations} should back which {@link ReactiveCouchbaseRepository}.
* Override {@link #configureReactiveRepositoryOperationsMapping} in order to customize this.
*
* @throws Exception
*/
@Bean(name = BeanNames.REACTIVE_COUCHBASE_OPERATIONS_MAPPING)
public ReactiveRepositoryOperationsMapping reactiveRepositoryOperationsMapping(RxJavaCouchbaseTemplate couchbaseTemplate) throws Exception {
//create a base mapping that associates all repositories to the default template
ReactiveRepositoryOperationsMapping baseMapping = new ReactiveRepositoryOperationsMapping(couchbaseTemplate);
//let the user tune it
configureReactiveRepositoryOperationsMapping(baseMapping);
return baseMapping;
}
/**
* In order to customize the mapping between repositories/entity types to couchbase templates,
* use the provided mapping's api (eg. in order to have different buckets backing different repositories).
*
* @param mapping the default mapping (will associate all repositories to the default template).
*/
protected void configureReactiveRepositoryOperationsMapping(ReactiveRepositoryOperationsMapping mapping) {
//NO_OP
}
}

View File

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

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseBucket;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator;
/**
* The Factory Bean to help {@link CouchbaseBucketParser} constructing a {@link Bucket} from a given
* {@link Cluster} reference.
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
public class CouchbaseBucketFactoryBean extends AbstractFactoryBean<Bucket> implements PersistenceExceptionTranslator {
private final Cluster cluster;
private final String bucketName;
private final String username;
private final String password;
private final PersistenceExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator();
public CouchbaseBucketFactoryBean(Cluster cluster) {
this(cluster, null, null, null);
}
public CouchbaseBucketFactoryBean(Cluster cluster, String bucketName) {
this(cluster, bucketName, bucketName, null);
}
public CouchbaseBucketFactoryBean(Cluster cluster, String bucketName, String username, String password) {
this.cluster = cluster;
this.bucketName = bucketName;
this.username = username;
this.password = password;
}
@Override
public Class<?> getObjectType() {
return CouchbaseBucket.class;
}
@Override
protected Bucket createInstance() throws Exception {
if (bucketName == null) {
return cluster.openBucket();
}
else if (password == null) {
return cluster.openBucket(bucketName);
}
else if (bucketName.contentEquals(username)) {
return cluster.openBucket(bucketName, password);
}
else {
cluster.authenticate(username, password);
return cluster.openBucket(bucketName);
}
}
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
}
}

View File

@@ -1,113 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* The parser for XML definition of a {@link Bucket}, to be constructed from a {@link Cluster} reference.
* If no reference is given, the default reference <code>{@value BeanNames#COUCHBASE_CLUSTER}</code> is used.
*
* See attributes {@link #CLUSTER_REF_ATTR}, {@link #BUCKETNAME_ATTR}, {@link #USERNAME_ATTR} and {@link #BUCKETPASSWORD_ATTR}.
*
* @author Simon Baslé
*/
public class CouchbaseBucketParser extends AbstractSingleBeanDefinitionParser {
/**
* The <code>cluster-ref</code> attribute in a bucket definition defines the cluster to build from.
*/
public static final String CLUSTER_REF_ATTR = "cluster-ref";
/**
* The <code>bucketName</code> attribute in a bucket definition defines the name of the bucket to open.
*/
public static final String BUCKETNAME_ATTR = "bucketName";
/*
* The <code>username</code> attribute in a bucket definition defines the user of the bucket to open.
*/
public static final String USERNAME_ATTR = "username";
/**
* The <code>bucketPassword</code> attribute in a bucket definition defines the password of the bucket/user of the bucket to open.
*/
public static final String BUCKETPASSWORD_ATTR = "bucketPassword";
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_BUCKET;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseBucketFactoryBean.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param builder the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder builder) {
String clusterRef = element.getAttribute(CLUSTER_REF_ATTR);
if (!StringUtils.hasText(clusterRef)) {
clusterRef = BeanNames.COUCHBASE_CLUSTER;
}
builder.addConstructorArgReference(clusterRef);
String bucketName = element.getAttribute(BUCKETNAME_ATTR);
if (StringUtils.hasText(bucketName)) {
builder.addConstructorArgValue(bucketName);
}
String username = element.getAttribute(USERNAME_ATTR);
if (StringUtils.hasText(username)) {
builder.addConstructorArgValue(username);
}
String password = element.getAttribute(BUCKETPASSWORD_ATTR);
if (StringUtils.hasText(password)) {
builder.addConstructorArgValue(password);
}
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.core.CouchbaseExceptionTranslator;
/**
* The Factory Bean to help {@link CouchbaseClusterInfoParser} constructing a {@link ClusterInfo} from a given
* {@link Cluster} reference.
*
* @author Simon Baslé
*/
public class CouchbaseClusterInfoFactoryBean extends AbstractFactoryBean<ClusterInfo> implements PersistenceExceptionTranslator {
private final Cluster cluster;
private final String login;
private final String password;
private final PersistenceExceptionTranslator exceptionTranslator = new CouchbaseExceptionTranslator();
public CouchbaseClusterInfoFactoryBean(Cluster cluster, String login, String password) {
this.cluster = cluster;
this.login = login;
this.password = password;
}
@Override
public Class<?> getObjectType() {
return ClusterInfo.class;
}
@Override
protected ClusterInfo createInstance() throws Exception {
return cluster.clusterManager(login, password).info();
}
@Override
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return exceptionTranslator.translateExceptionIfPossible(ex);
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.cluster.ClusterInfo;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* The parser for XML definition of a {@link ClusterInfo}, to be constructed from a {@link Cluster} reference.
* If no reference is given, the default reference <code>{@value BeanNames#COUCHBASE_CLUSTER_INFO}</code> is used.
* <p/>
* See attributes {@link #CLUSTER_REF_ATTR}, {@link #LOGIN_ATTR} and {@link #PASSWORD_ATTR}.
*
* @author Simon Baslé
*/
public class CouchbaseClusterInfoParser extends AbstractSingleBeanDefinitionParser {
/**
* The <code>cluster-ref</code> attribute in a cluster info definition defines the cluster to build from.
*/
public static final String CLUSTER_REF_ATTR = "cluster-ref";
/**
* The <code>login</code> attribute in a cluster info definition defines the credential to use (can also be a
* bucket level credential).
*/
public static final String LOGIN_ATTR = "login";
/**
* The <code>password</code> attribute in a cluster info definition defines the credential's password.
*/
public static final String PASSWORD_ATTR = "password";
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_CLUSTER_INFO;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseClusterInfoFactoryBean.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param builder the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder builder) {
String clusterRef = element.getAttribute(CLUSTER_REF_ATTR);
if (!StringUtils.hasText(clusterRef)) {
clusterRef = BeanNames.COUCHBASE_CLUSTER;
}
builder.addConstructorArgReference(clusterRef);
String login = element.getAttribute(LOGIN_ATTR);
if (!StringUtils.hasText(login)) {
login = "default";
}
builder.addConstructorArgValue(login);
String password = element.getAttribute(PASSWORD_ATTR);
if (!StringUtils.hasText(password)) {
password = "";
}
builder.addConstructorArgValue(password);
}
}

View File

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

View File

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

View File

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

View File

@@ -1,248 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import com.couchbase.client.core.retry.BestEffortRetryStrategy;
import com.couchbase.client.core.retry.FailFastRetryStrategy;
import com.couchbase.client.core.retry.RetryStrategy;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment.Builder;
import org.springframework.beans.factory.config.AbstractFactoryBean;
/**
* Factory Bean to help create a CouchbaseEnvironment (by offering setters for supported tuning methods).
*
* @author Simon Baslé
* @author Simon Bland
* @author Subhashni Balakrishnan
*/
/*package*/ class CouchbaseEnvironmentFactoryBean extends AbstractFactoryBean<CouchbaseEnvironment> {
public static final String RETRYSTRATEGY_FAILFAST = "FailFast";
public static final String RETRYSTRATEGY_BESTEFFORT = "BestEffort";
private final Builder couchbaseEnvBuilder = DefaultCouchbaseEnvironment.builder();
/*
These are tunings that are not practical to be exposed in a xml configuration
or not supposed to be modified that easily:
observeIntervalDelay
reconnectDelay
retryDelay
userAgent
packageNameAndVersion
ioPool
scheduler
eventBus
systemMetricsCollectorConfig
networkLatencyMetricsCollectorConfig
requestBufferWaitStrategy
sslKeystore
memcachedHashingStrategy
kvIoPool
queryIoPool
searchIoPool
viewIoPool
kvServiceConfig
queryServiceConfig
searchServiceConfig
viewServiceConfig
cryptoManager
tracer
orphanResponseReporter
*/
@Override
public Class<?> getObjectType() {
return DefaultCouchbaseEnvironment.class;
}
@Override
protected CouchbaseEnvironment createInstance() throws Exception {
return couchbaseEnvBuilder.build();
}
/**
* Sets the {@link RetryStrategy} to use from an enum-like String value.
* Either "FailFast" or "BestEffort" are recognized.
*
* @param retryStrategy the string value enum from which to choose a strategy.
*/
public void setRetryStrategy(String retryStrategy) {
if (RETRYSTRATEGY_FAILFAST.equals(retryStrategy)) {
this.couchbaseEnvBuilder.retryStrategy(FailFastRetryStrategy.INSTANCE);
} else if (RETRYSTRATEGY_BESTEFFORT.equals(retryStrategy)) {
this.couchbaseEnvBuilder.retryStrategy(BestEffortRetryStrategy.INSTANCE);
}
}
//==== SETTERS for the factory bean ====
public void setManagementTimeout(long managementTimeout) {
this.couchbaseEnvBuilder.managementTimeout(managementTimeout);
}
public void setQueryTimeout(long queryTimeout) {
this.couchbaseEnvBuilder.queryTimeout(queryTimeout);
}
public void setViewTimeout(long viewTimeout) {
this.couchbaseEnvBuilder.viewTimeout(viewTimeout);
}
public void setKvTimeout(long kvTimeout) {
this.couchbaseEnvBuilder.kvTimeout(kvTimeout);
}
public void setConnectTimeout(long connectTimeout) {
this.couchbaseEnvBuilder.connectTimeout(connectTimeout);
}
public void setDisconnectTimeout(long disconnectTimeout) {
this.couchbaseEnvBuilder.disconnectTimeout(disconnectTimeout);
}
public void setDnsSrvEnabled(boolean dnsSrvEnabled) {
this.couchbaseEnvBuilder.dnsSrvEnabled(dnsSrvEnabled);
}
public void setSslEnabled(boolean sslEnabled) {
this.couchbaseEnvBuilder.sslEnabled(sslEnabled);
}
public void setSslKeystoreFile(String sslKeystoreFile) {
this.couchbaseEnvBuilder.sslKeystoreFile(sslKeystoreFile);
}
public void setSslKeystorePassword(String sslKeystorePassword) {
this.couchbaseEnvBuilder.sslKeystorePassword(sslKeystorePassword);
}
public void setBootstrapHttpEnabled(boolean bootstrapHttpEnabled) {
this.couchbaseEnvBuilder.bootstrapHttpEnabled(bootstrapHttpEnabled);
}
public void setBootstrapCarrierEnabled(boolean bootstrapCarrierEnabled) {
this.couchbaseEnvBuilder.bootstrapCarrierEnabled(bootstrapCarrierEnabled);
}
public void setBootstrapHttpDirectPort(int bootstrapHttpDirectPort) {
this.couchbaseEnvBuilder.bootstrapHttpDirectPort(bootstrapHttpDirectPort);
}
public void setBootstrapHttpSslPort(int bootstrapHttpSslPort) {
this.couchbaseEnvBuilder.bootstrapHttpSslPort(bootstrapHttpSslPort);
}
public void setBootstrapCarrierDirectPort(int bootstrapCarrierDirectPort) {
this.couchbaseEnvBuilder.bootstrapCarrierDirectPort(bootstrapCarrierDirectPort);
}
public void setBootstrapCarrierSslPort(int bootstrapCarrierSslPort) {
this.couchbaseEnvBuilder.bootstrapCarrierSslPort(bootstrapCarrierSslPort);
}
public void setIoPoolSize(int ioPoolSize) {
this.couchbaseEnvBuilder.ioPoolSize(ioPoolSize);
}
public void setComputationPoolSize(int computationPoolSize) {
this.couchbaseEnvBuilder.computationPoolSize(computationPoolSize);
}
public void setResponseBufferSize(int responseBufferSize) {
this.couchbaseEnvBuilder.responseBufferSize(responseBufferSize);
}
public void setRequestBufferSize(int requestBufferSize) {
this.couchbaseEnvBuilder.requestBufferSize(requestBufferSize);
}
public void setKvEndpoints(int kvEndpoints) {
this.couchbaseEnvBuilder.kvEndpoints(kvEndpoints);
}
public void setViewEndpoints(int viewEndpoints) {
this.couchbaseEnvBuilder.viewEndpoints(viewEndpoints);
}
public void setQueryEndpoints(int queryEndpoints) {
this.couchbaseEnvBuilder.queryEndpoints(queryEndpoints);
}
public void setMaxRequestLifetime(long maxRequestLifetime) {
this.couchbaseEnvBuilder.maxRequestLifetime(maxRequestLifetime);
}
public void setKeepAliveInterval(long keepAliveInterval) {
this.couchbaseEnvBuilder.keepAliveInterval(keepAliveInterval);
}
public void setAutoreleaseAfter(long autoreleaseAfter) {
this.couchbaseEnvBuilder.autoreleaseAfter(autoreleaseAfter);
}
public void setBufferPoolingEnabled(boolean bufferPoolingEnabled) {
this.couchbaseEnvBuilder.bufferPoolingEnabled(bufferPoolingEnabled);
}
public void setTcpNodelayEnabled(boolean tcpNodelayEnabled) {
this.couchbaseEnvBuilder.tcpNodelayEnabled(tcpNodelayEnabled);
}
public void setMutationTokensEnabled(boolean mutationTokensEnabled) {
this.couchbaseEnvBuilder.mutationTokensEnabled(mutationTokensEnabled);
}
public void setAnalyticsTimeout(long analyticsTimeout) {
this.couchbaseEnvBuilder.analyticsTimeout(analyticsTimeout);
}
public void setConfigPollInterval(long configPollInterval) {
this.couchbaseEnvBuilder.configPollInterval(configPollInterval);
}
public void setConfigPollFloorInterval(long configPollFloorInterval) {
this.couchbaseEnvBuilder.configPollFloorInterval(configPollFloorInterval);
}
public void setCertAuthEnabled(boolean certAuthEnabled) {
this.couchbaseEnvBuilder.certAuthEnabled(certAuthEnabled);
}
public void setOperationTracingEnabled(boolean operationTracingEnabled) {
this.couchbaseEnvBuilder.operationTracingEnabled(operationTracingEnabled);
}
public void setOperationTracingServerDurationEnabled(boolean operationTracingServerDurationEnabled) {
this.couchbaseEnvBuilder.operationTracingServerDurationEnabled(operationTracingServerDurationEnabled);
}
public void setOrphanResponseReportingEnabled(boolean orphanResponseReportingEnabled) {
this.couchbaseEnvBuilder.orphanResponseReportingEnabled(orphanResponseReportingEnabled);
}
public void setCompressionMinSize(int compressionMinSize) {
this.couchbaseEnvBuilder.compressionMinSize(compressionMinSize);
}
public void setCompressionMinRatio(double compressionMinRatio) {
this.couchbaseEnvBuilder.compressionMinRatio(compressionMinRatio);
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import com.couchbase.client.java.env.CouchbaseEnvironment;
/**
* A dynamic proxy around a {@link CouchbaseEnvironment} that prevents its {@link CouchbaseEnvironment#shutdown()} method
* to be invoked. Useful when the delegate is not to be lifecycle-managed by Spring.
*
* @author Simon Baslé
* @author Jonathan Edwards
* @author Subhashni Balakrishnan
*/
public class CouchbaseEnvironmentNoShutdownInvocationHandler implements InvocationHandler {
private final CouchbaseEnvironment environment;
public CouchbaseEnvironmentNoShutdownInvocationHandler(CouchbaseEnvironment environment) {
this.environment = environment;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().contentEquals("shutdown")) {
return false;
}
return method.invoke(this.environment, args);
}
}

View File

@@ -1,150 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import static org.springframework.data.config.ParsingUtils.setPropertyValue;
import com.couchbase.client.core.retry.RetryStrategy;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
/**
* Allows creation of a {@link DefaultCouchbaseEnvironment} via spring XML configuration.
* <p>
* The following properties are supported:<br/><ul>
* <li>{@link DefaultCouchbaseEnvironment.Builder#managementTimeout(long) managementTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#queryTimeout(long) queryTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#viewTimeout(long) viewTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#kvTimeout(long) kvTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#connectTimeout(long) connectTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#disconnectTimeout(long) disconnectTimeout}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#dnsSrvEnabled(boolean) dnsSrvEnabled}</li>
*
* <li>{@link DefaultCouchbaseEnvironment.Builder#sslEnabled(boolean) sslEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#sslKeystoreFile(String) sslKeystoreFile}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#sslKeystorePassword(String) sslKeystorePassword}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpEnabled(boolean) bootstrapHttpEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierEnabled(boolean) bootstrapCarrierEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpDirectPort(int) bootstrapHttpDirectPort}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapHttpSslPort(int) bootstrapHttpSslPort}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierDirectPort(int) bootstrapCarrierDirectPort}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bootstrapCarrierSslPort(int) bootstrapCarrierSslPort}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#ioPoolSize(int) ioPoolSize}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#computationPoolSize(int) computationPoolSize}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#responseBufferSize(int) responseBufferSize}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#requestBufferSize(int) requestBufferSize}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#kvEndpoints(int) kvEndpoints}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#viewEndpoints(int) viewEndpoints}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#queryEndpoints(int) queryEndpoints}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#retryStrategy(RetryStrategy) retryStrategy}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#maxRequestLifetime(long) maxRequestLifetime}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#keepAliveInterval(long) keepAliveInterval}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#autoreleaseAfter(long) autoreleaseAfter}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#bufferPoolingEnabled(boolean) bufferPoolingEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#tcpNodelayEnabled(boolean) tcpNodelayEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#mutationTokensEnabled(boolean) mutationTokensEnabled}</li>
* <li>{@link DefaultCouchbaseEnvironment.Builder#analyticsTimeout(long) analyticsTimeout}</li>
* </ul>
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
public class CouchbaseEnvironmentParser extends AbstractSingleBeanDefinitionParser {
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_ENV;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseEnvironmentFactoryBean.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param envElement the XML element which contains the attributes.
* @param envDefinitionBuilder the builder which builds the bean.
*/
@Override
protected void doParse(final Element envElement, final BeanDefinitionBuilder envDefinitionBuilder) {
setPropertyValue(envDefinitionBuilder, envElement, "managementTimeout", "managementTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "queryTimeout", "queryTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "viewTimeout", "viewTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "kvTimeout", "kvTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "connectTimeout", "connectTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "disconnectTimeout", "disconnectTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "dnsSrvEnabled", "dnsSrvEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "sslEnabled", "sslEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "sslKeystoreFile", "sslKeystoreFile");
setPropertyValue(envDefinitionBuilder, envElement, "sslKeystorePassword", "sslKeystorePassword");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapHttpEnabled", "bootstrapHttpEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapCarrierEnabled", "bootstrapCarrierEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapHttpDirectPort", "bootstrapHttpDirectPort");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapHttpSslPort", "bootstrapHttpSslPort");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapCarrierDirectPort", "bootstrapCarrierDirectPort");
setPropertyValue(envDefinitionBuilder, envElement, "bootstrapCarrierSslPort", "bootstrapCarrierSslPort");
setPropertyValue(envDefinitionBuilder, envElement, "ioPoolSize", "ioPoolSize");
setPropertyValue(envDefinitionBuilder, envElement, "computationPoolSize", "computationPoolSize");
setPropertyValue(envDefinitionBuilder, envElement, "responseBufferSize", "responseBufferSize");
setPropertyValue(envDefinitionBuilder, envElement, "requestBufferSize", "requestBufferSize");
setPropertyValue(envDefinitionBuilder, envElement, "kvEndpoints", "kvEndpoints");
setPropertyValue(envDefinitionBuilder, envElement, "viewEndpoints", "viewEndpoints");
setPropertyValue(envDefinitionBuilder, envElement, "queryEndpoints", "queryEndpoints");
setPropertyValue(envDefinitionBuilder, envElement, "maxRequestLifetime", "maxRequestLifetime");
setPropertyValue(envDefinitionBuilder, envElement, "keepAliveInterval", "keepAliveInterval");
setPropertyValue(envDefinitionBuilder, envElement, "autoreleaseAfter", "autoreleaseAfter");
setPropertyValue(envDefinitionBuilder, envElement, "bufferPoolingEnabled", "bufferPoolingEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "tcpNodelayEnabled", "tcpNodelayEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "mutationTokensEnabled", "mutationTokensEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "analyticsTimeout", "analyticsTimeout");
setPropertyValue(envDefinitionBuilder, envElement, "configPollInterval", "configPollInterval");
setPropertyValue(envDefinitionBuilder, envElement, "configPollFloorInterval", "configPollFloorInterval");
setPropertyValue(envDefinitionBuilder, envElement, "certAuthEnabled", "certAuthEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "operationTracingEnabled", "operationTracingEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "operationTracingServerDurationEnabled", "operationTracingServerDurationEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "orphanResponseReportingEnabled", "orphanResponseReportingEnabled");
setPropertyValue(envDefinitionBuilder, envElement, "compressionMinSize", "compressionMinSize");
setPropertyValue(envDefinitionBuilder, envElement, "compressionMinRatio", "compressionMinRatio");
//retry strategy is particular, in the xsd this is an enum (FailFast, BestEffort)
setPropertyValue(envDefinitionBuilder, envElement, "retryStrategy", "retryStrategy");
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.couchbase.repository.support.IndexManager;
import org.springframework.util.StringUtils;
/**
* The XML parser for a {@link IndexManager} definition.
*
* @author Simon Baslé
*/
public class CouchbaseIndexManagerParser extends AbstractSingleBeanDefinitionParser {
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_INDEX_MANAGER;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return IndexManager.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param bean the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
//the following values default to false, so you must opt-in by providing them
boolean processViews = Boolean.parseBoolean(element.getAttribute("processViews"));
boolean processPrimary = Boolean.parseBoolean(element.getAttribute("processPrimary"));
boolean processSecondary = Boolean.parseBoolean(element.getAttribute("processSecondary"));
bean.addConstructorArgValue(processViews);
bean.addConstructorArgValue(processPrimary);
bean.addConstructorArgValue(processSecondary);
}
}

View File

@@ -1,96 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import com.couchbase.client.java.Bucket;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.couchbase.monitor.ClientInfo;
import org.springframework.data.couchbase.monitor.ClusterInfo;
import org.springframework.util.StringUtils;
/**
* Enables Parsing of the "<couchbase:jmx />" configuration bean.
* <p/>
* In order to enable JMX, different JmxComponents need to be registered. The dependency to the original
* {@link Bucket} object is solved through the "bucket-ref" attribute.
*
* @author Michael Nitschinger
* @author Simon Baslé
*/
public class CouchbaseJmxParser implements BeanDefinitionParser {
/**
* Parse the element and dispatch the registration of the JMX components.
*
* @param element the XML element which contains the attributes.
* @param parserContext encapsulates the parsing state and configuration.
* @return null, because no bean instance needs to be returned.
*/
public BeanDefinition parse(final Element element, final ParserContext parserContext) {
String bucketName = element.getAttribute("bucket-ref");
if (!StringUtils.hasText(bucketName)) {
bucketName = BeanNames.COUCHBASE_BUCKET;
}
registerJmxComponents(bucketName, element, parserContext);
return null;
}
/**
* Register the JMX components in the context.
*
* @param element the XML element which contains the attributes.
* @param parserContext encapsulates the parsing state and configuration.
* @parma refBucketName the reference name to the couchbase bucket.
*/
protected void registerJmxComponents(final String refBucketName,
final Element element, final ParserContext parserContext) {
Object eleSource = parserContext.extractSource(element);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
createBeanDefEntry(ClientInfo.class, compositeDef, refBucketName, eleSource, parserContext);
createBeanDefEntry(ClusterInfo.class, compositeDef, refBucketName, eleSource, parserContext);
parserContext.registerComponent(compositeDef);
}
/**
* Creates Bean Definitions for JMX components and adds them as a nested component.
*
* @param clazz the class type to register.
* @param compositeDef component that can hold nested components.
* @param refName the reference name to the couchbase bucket.
* @param eleSource source element to reference.
* @param parserContext encapsulates the parsing state and configuration.
*/
protected void createBeanDefEntry(final Class<?> clazz, final CompositeComponentDefinition compositeDef,
final String refName, final Object eleSource, final ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
builder.getRawBeanDefinition().setSource(eleSource);
builder.addConstructorArgReference(refName);
BeanDefinition assertDef = builder.getBeanDefinition();
String assertName = parserContext.getReaderContext().registerWithGeneratedName(assertDef);
compositeDef.addNestedComponent(new BeanComponentDefinition(assertDef, assertName));
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import org.springframework.beans.factory.xml.NamespaceHandler;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.data.couchbase.repository.config.CouchbaseRepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryBeanDefinitionParser;
/**
* {@link NamespaceHandler} for Couchbase configuration.
* <p/>
* This handler acts as a container for one or more bean parsers and registers them. During parsing, the elements
* get analyzed and the appropriate registered parser is called.
*
* @author Michael Nitschinger
*/
public class CouchbaseNamespaceHandler extends NamespaceHandlerSupport {
/**
* Register bean definition parsers in the namespace handler.
*/
public final void init() {
CouchbaseRepositoryConfigurationExtension extension = new CouchbaseRepositoryConfigurationExtension();
registerBeanDefinitionParser("repositories", new RepositoryBeanDefinitionParser(extension));
registerBeanDefinitionParser("env", new CouchbaseEnvironmentParser());
registerBeanDefinitionParser("cluster", new CouchbaseClusterParser());
registerBeanDefinitionParser("clusterInfo", new CouchbaseClusterInfoParser());
registerBeanDefinitionParser("bucket", new CouchbaseBucketParser());
registerBeanDefinitionParser("jmx", new CouchbaseJmxParser());
registerBeanDefinitionParser("template", new CouchbaseTemplateParser());
registerBeanDefinitionParser("translation-service", new CouchbaseTranslationServiceParser());
registerBeanDefinitionParser("indexManager", new CouchbaseIndexManagerParser());
}
}

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.query.Consistency;
import org.springframework.util.StringUtils;
/**
* Parser for "<couchbase:template />" bean definitions.
* <p/>
* The outcome of this bean definition parser will be a constructed {@link CouchbaseTemplate}.
*
* @author Michael Nitschinger
*/
public class CouchbaseTemplateParser extends AbstractSingleBeanDefinitionParser {
private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTemplateParser.class);
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with.
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_TEMPLATE;
}
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return CouchbaseTemplate.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param bean the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
String clusterInfoRef = element.getAttribute("clusterInfo-ref");
String bucketRef = element.getAttribute("bucket-ref");
String converterRef = element.getAttribute("converter-ref");
String translationServiceRef = element.getAttribute("translation-service-ref");
bean.addConstructorArgReference(StringUtils.hasText(clusterInfoRef) ? clusterInfoRef : BeanNames.COUCHBASE_CLUSTER_INFO);
bean.addConstructorArgReference(StringUtils.hasText(bucketRef) ? bucketRef : BeanNames.COUCHBASE_BUCKET);
if (StringUtils.hasText(converterRef)) {
bean.addConstructorArgReference(converterRef);
}
if (StringUtils.hasText(translationServiceRef)) {
bean.addConstructorArgReference(translationServiceRef);
}
String consistencyValue = element.getAttribute("consistency");
if (consistencyValue != null) {
try {
Consistency consistency = Consistency.valueOf(consistencyValue);
bean.addPropertyValue("defaultConsistency", consistency);
} catch (IllegalArgumentException e) {
//bad consistency, leave default and log
LOGGER.warn("Parsed bad consistency value " + consistencyValue + " in xml template configuration, using default");
}
}
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.util.StringUtils;
/**
* Enables Parsing of the "<couchbase:translation-service />" configuration bean.
*
* @author David Harrigan
*/
public class CouchbaseTranslationServiceParser extends AbstractSingleBeanDefinitionParser {
/**
* Defines the bean class that will be constructed.
*
* @param element the XML element which contains the attributes.
* @return the class type to instantiate.
*/
@Override
protected Class getBeanClass(final Element element) {
return JacksonTranslationService.class;
}
/**
* Parse the bean definition and build up the bean.
*
* @param element the XML element which contains the attributes.
* @param bean the builder which builds the bean.
*/
@Override
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
final String objectMapper = element.getAttribute("objectMapper");
if (StringUtils.hasText(objectMapper)) {
bean.addPropertyReference("objectMapper", objectMapper);
}
else {
bean.addPropertyValue("objectMapper", new ObjectMapper());
}
}
/**
* Resolve the bean ID and assign a default if not set.
*
* @param element the XML element which contains the attributes.
* @param definition the bean definition to work with.
* @param parserContext encapsulates the parsing state and configuration.
* @return the ID to work with (e.g., "couchbaseTranslationService")
*/
@Override
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_TRANSLATION_SERVICE;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
public class CouchbaseTemplateSupport {
private final CouchbaseConverter converter;
private final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
// TODO: this should be replaced I think
private final TranslationService translationService;
public CouchbaseTemplateSupport(final CouchbaseConverter converter) {
this.converter = converter;
this.mappingContext = converter.getMappingContext();
this.translationService = new JacksonTranslationService();
}
public CouchbaseDocument encodeEntity(final Object entityToEncode) {
final CouchbaseDocument converted = new CouchbaseDocument();
converter.write(entityToEncode, converted);
return converted;
}
public <T> T decodeEntity(String id, String source, long cas, Class<T> entityClass) {
final CouchbaseDocument converted = new CouchbaseDocument(id);
converted.setId(id);
T readEntity = converter.read(entityClass, (CouchbaseDocument) translationService.decode(source, converted));
final ConvertingPropertyAccessor<T> accessor = getPropertyAccessor(readEntity);
CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(readEntity.getClass());
if (persistentEntity.getVersionProperty() != null) {
accessor.setProperty(persistentEntity.getVersionProperty(), cas);
}
return accessor.getBean();
}
public void applyUpdatedCas(final Object entity, final long cas) {
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(entity);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entity.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
if (versionProperty != null) {
accessor.setProperty(versionProperty, cas);
}
}
public String getJavaNameForEntity(final Class<?> clazz) {
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
MappingCouchbaseEntityInformation<?, Object> info = new MappingCouchbaseEntityInformation<>(persistentEntity);
return info.getJavaType().getName();
}
private <T> ConvertingPropertyAccessor<T> getPropertyAccessor(final T source) {
CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(source.getClass());
PersistentPropertyAccessor<T> accessor = entity.getPropertyAccessor(source);
return new ConvertingPropertyAccessor<>(accessor, converter.getConversionService());
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.Map;
public interface ExecutableExistsByIdOperation {
ExecutableExistsById existsById();
interface TerminatingExistsById {
boolean one(String id);
Map<String, Boolean> all(Collection<String> ids);
}
interface ExistsByIdWithCollection extends TerminatingExistsById {
TerminatingExistsById inCollection(String collection);
}
interface ExecutableExistsById extends ExistsByIdWithCollection {}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.Map;
import org.springframework.util.Assert;
public class ExecutableExistsByIdOperationSupport implements ExecutableExistsByIdOperation {
private final CouchbaseTemplate template;
ExecutableExistsByIdOperationSupport(CouchbaseTemplate template) {
this.template = template;
}
@Override
public ExecutableExistsById existsById() {
return new ExecutableExistsByIdSupport(template, null);
}
static class ExecutableExistsByIdSupport implements ExecutableExistsById {
private final CouchbaseTemplate template;
private final ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport reactiveSupport;
ExecutableExistsByIdSupport(final CouchbaseTemplate template, final String collection) {
this.template = template;
this.reactiveSupport = new ReactiveExistsByIdOperationSupport.ReactiveExistsByIdSupport(template.reactive(),
collection);
}
@Override
public boolean one(final String id) {
return reactiveSupport.one(id).block();
}
@Override
public Map<String, Boolean> all(final Collection<String> ids) {
return reactiveSupport.all(ids).block();
}
@Override
public TerminatingExistsById inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableExistsByIdSupport(template, collection);
}
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
import org.springframework.lang.Nullable;
public interface ExecutableFindByAnalyticsOperation {
<T> ExecutableFindByAnalytics<T> findByAnalytics(Class<T> domainType);
interface TerminatingFindByAnalytics<T> {
/**
* Get exactly zero or one result.
*
* @return {@link Optional#empty()} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
default Optional<T> one() {
return Optional.ofNullable(oneValue());
}
/**
* Get exactly zero or one result.
*
* @return {@literal null} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
@Nullable
T oneValue();
/**
* Get the first or no result.
*
* @return {@link Optional#empty()} if no match found.
*/
default Optional<T> first() {
return Optional.ofNullable(firstValue());
}
/**
* Get the first or no result.
*
* @return {@literal null} if no match found.
*/
@Nullable
T firstValue();
/**
* Get all matching elements.
*
* @return never {@literal null}.
*/
List<T> all();
/**
* Stream all matching elements.
*
* @return a {@link Stream} of results. Never {@literal null}.
*/
Stream<T> stream();
/**
* Get the number of matching elements.
*
* @return total number of matching elements.
*/
long count();
/**
* Check for the presence of matching elements.
*
* @return {@literal true} if at least one matching element exists.
*/
boolean exists();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindByAnalyticsWithQuery<T> extends TerminatingFindByAnalytics<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindByAnalytics}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindByAnalytics<T> matching(AnalyticsQuery query);
}
interface ExecutableFindByAnalytics<T> extends FindByAnalyticsWithQuery<T> {}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
public class ExecutableFindByAnalyticsOperationSupport implements ExecutableFindByAnalyticsOperation {
private static final AnalyticsQuery ALL_QUERY = new AnalyticsQuery();
private final CouchbaseTemplate template;
public ExecutableFindByAnalyticsOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
return new ExecutableFindByAnalyticsSupport<>(template, domainType, ALL_QUERY);
}
static class ExecutableFindByAnalyticsSupport<T> implements ExecutableFindByAnalytics<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport<T> reactiveSupport;
ExecutableFindByAnalyticsSupport(final CouchbaseTemplate template, final Class<T> domainType,
final AnalyticsQuery query) {
this.template = template;
this.domainType = domainType;
this.reactiveSupport = new ReactiveFindByAnalyticsOperationSupport.ReactiveFindByAnalyticsSupport<>(
template.reactive(), domainType, query);
}
@Override
public T oneValue() {
return reactiveSupport.one().block();
}
@Override
public T firstValue() {
return reactiveSupport.first().block();
}
@Override
public List<T> all() {
return reactiveSupport.all().collectList().block();
}
@Override
public TerminatingFindByAnalytics<T> matching(final AnalyticsQuery query) {
return new ExecutableFindByAnalyticsSupport<>(template, domainType, query);
}
@Override
public Stream<T> stream() {
return reactiveSupport.all().toStream();
}
@Override
public long count() {
return reactiveSupport.count().block();
}
@Override
public boolean exists() {
return count() > 0;
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
public interface ExecutableFindByIdOperation {
<T> ExecutableFindById<T> findById(Class<T> domainType);
interface TerminatingFindById<T> {
T one(String id);
Collection<? extends T> all(Collection<String> ids);
}
interface FindByIdWithCollection<T> extends TerminatingFindById<T> {
TerminatingFindById<T> inCollection(String collection);
}
interface FindByIdWithProjection<T> extends FindByIdWithCollection<T> {
FindByIdWithCollection<T> project(String... fields);
}
interface ExecutableFindById<T> extends FindByIdWithProjection<T> {}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.util.Assert;
public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOperation {
private final CouchbaseTemplate template;
ExecutableFindByIdOperationSupport(CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableFindById<T> findById(Class<T> domainType) {
return new ExecutableFindByIdSupport<>(template, domainType, null, null);
}
static class ExecutableFindByIdSupport<T> implements ExecutableFindById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final List<String> fields;
private final ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport<T> reactiveSupport;
ExecutableFindByIdSupport(CouchbaseTemplate template, Class<T> domainType, String collection, List<String> fields) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.fields = fields;
this.reactiveSupport = new ReactiveFindByIdOperationSupport.ReactiveFindByIdSupport<>(template.reactive(),
domainType, collection, fields);
}
@Override
public T one(final String id) {
return reactiveSupport.one(id).block();
}
@Override
public Collection<? extends T> all(final Collection<String> ids) {
return reactiveSupport.all(ids).collectList().block();
}
@Override
public TerminatingFindById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, collection, fields);
}
@Override
public FindByIdWithCollection<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ExecutableFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields));
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.lang.Nullable;
import com.couchbase.client.java.query.QueryScanConsistency;
public interface ExecutableFindByQueryOperation {
<T> ExecutableFindByQuery<T> findByQuery(Class<T> domainType);
interface TerminatingFindByQuery<T> {
/**
* Get exactly zero or one result.
*
* @return {@link Optional#empty()} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
default Optional<T> one() {
return Optional.ofNullable(oneValue());
}
/**
* Get exactly zero or one result.
*
* @return {@literal null} if no match found.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
*/
@Nullable
T oneValue();
/**
* Get the first or no result.
*
* @return {@link Optional#empty()} if no match found.
*/
default Optional<T> first() {
return Optional.ofNullable(firstValue());
}
/**
* Get the first or no result.
*
* @return {@literal null} if no match found.
*/
@Nullable
T firstValue();
/**
* Get all matching elements.
*
* @return never {@literal null}.
*/
List<T> all();
/**
* Stream all matching elements.
*
* @return a {@link Stream} of results. Never {@literal null}.
*/
Stream<T> stream();
/**
* Get the number of matching elements.
*
* @return total number of matching elements.
*/
long count();
/**
* Check for the presence of matching elements.
*
* @return {@literal true} if at least one matching element exists.
*/
boolean exists();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindByQueryWithQuery<T> extends TerminatingFindByQuery<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindByQuery}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindByQuery<T> matching(Query query);
}
interface FindByQueryConsistentWith<T> extends FindByQueryWithQuery<T> {
FindByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface ExecutableFindByQuery<T> extends FindByQueryConsistentWith<T> {}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public class ExecutableFindByQueryOperationSupport implements ExecutableFindByQueryOperation {
private static final Query ALL_QUERY = new Query();
private final CouchbaseTemplate template;
public ExecutableFindByQueryOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableFindByQuery<T> findByQuery(final Class<T> domainType) {
return new ExecutableFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED);
}
static class ExecutableFindByQuerySupport<T> implements ExecutableFindByQuery<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final Query query;
private final ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport<T> reactiveSupport;
private final QueryScanConsistency scanConsistency;
ExecutableFindByQuerySupport(final CouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.reactiveSupport = new ReactiveFindByQueryOperationSupport.ReactiveFindByQuerySupport<T>(template.reactive(),
domainType, query, scanConsistency);
this.scanConsistency = scanConsistency;
}
@Override
public T oneValue() {
return reactiveSupport.one().block();
}
@Override
public T firstValue() {
return reactiveSupport.first().block();
}
@Override
public List<T> all() {
return reactiveSupport.all().collectList().block();
}
@Override
public TerminatingFindByQuery<T> matching(final Query query) {
return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public FindByQueryWithQuery<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ExecutableFindByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public Stream<T> stream() {
return reactiveSupport.all().toStream();
}
@Override
public long count() {
return reactiveSupport.count().block();
}
@Override
public boolean exists() {
return count() > 0;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
public interface ExecutableFindFromReplicasByIdOperation {
<T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType);
interface TerminatingFindFromReplicasById<T> {
T any(String id);
Collection<? extends T> any(Collection<String> ids);
}
interface FindFromReplicasByIdWithCollection<T> extends TerminatingFindFromReplicasById<T> {
TerminatingFindFromReplicasById<T> inCollection(String collection);
}
interface ExecutableFindFromReplicasById<T> extends FindFromReplicasByIdWithCollection<T> {}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import org.springframework.util.Assert;
public class ExecutableFindFromReplicasByIdOperationSupport implements ExecutableFindFromReplicasByIdOperation {
private final CouchbaseTemplate template;
ExecutableFindFromReplicasByIdOperationSupport(CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, null);
}
static class ExecutableFindFromReplicasByIdSupport<T> implements ExecutableFindFromReplicasById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport<T> reactiveSupport;
ExecutableFindFromReplicasByIdSupport(CouchbaseTemplate template, Class<T> domainType, String collection) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.reactiveSupport = new ReactiveFindFromReplicasByIdOperationSupport.ReactiveFindFromReplicasByIdSupport<>(
template.reactive(), domainType, collection);
}
@Override
public T any(String id) {
return reactiveSupport.any(id).block();
}
@Override
public Collection<? extends T> any(Collection<String> ids) {
return reactiveSupport.any(ids).collectList().block();
}
@Override
public TerminatingFindFromReplicasById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableFindFromReplicasByIdSupport<>(template, domainType, collection);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ExecutableInsertByIdOperation {
<T> ExecutableInsertById<T> insertById(Class<T> domainType);
interface TerminatingInsertById<T> {
T one(T object);
Collection<? extends T> all(Collection<? extends T> objects);
}
interface InsertByIdWithCollection<T> extends TerminatingInsertById<T> {
TerminatingInsertById<T> inCollection(String collection);
}
interface InsertByIdWithDurability<T> extends InsertByIdWithCollection<T> {
InsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
InsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ExecutableInsertById<T> extends InsertByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByIdOperation {
private final CouchbaseTemplate template;
public ExecutableInsertByIdOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableInsertById<T> insertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ExecutableInsertByIdSupport<T> implements ExecutableInsertById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport<T> reactiveSupport;
ExecutableInsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveSupport = new ReactiveInsertByIdOperationSupport.ReactiveInsertByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public T one(final T object) {
return reactiveSupport.one(object).block();
}
@Override
public Collection<? extends T> all(Collection<? extends T> objects) {
return reactiveSupport.all(objects).collectList().block();
}
@Override
public TerminatingInsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public InsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public InsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.List;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ExecutableRemoveByIdOperation {
ExecutableRemoveById removeById();
interface TerminatingRemoveById {
RemoveResult one(String id);
List<RemoveResult> all(Collection<String> ids);
}
interface RemoveByIdWithCollection extends TerminatingRemoveById {
TerminatingRemoveById inCollection(String collection);
}
interface RemoveByIdWithDurability extends RemoveByIdWithCollection {
RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel);
RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ExecutableRemoveById extends RemoveByIdWithDurability {}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import java.util.List;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByIdOperation {
private final CouchbaseTemplate template;
public ExecutableRemoveByIdOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public ExecutableRemoveById removeById() {
return new ExecutableRemoveByIdSupport(template, null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE);
}
static class ExecutableRemoveByIdSupport implements ExecutableRemoveById {
private final CouchbaseTemplate template;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport reactiveRemoveByIdSupport;
ExecutableRemoveByIdSupport(final CouchbaseTemplate template, final String collection, final PersistTo persistTo,
final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveRemoveByIdSupport = new ReactiveRemoveByIdOperationSupport.ReactiveRemoveByIdSupport(
template.reactive(), collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveResult one(final String id) {
return reactiveRemoveByIdSupport.one(id).block();
}
@Override
public List<RemoveResult> all(final Collection<String> ids) {
return reactiveRemoveByIdSupport.all(ids).collectList().block();
}
@Override
public TerminatingRemoveById inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.List;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public interface ExecutableRemoveByQueryOperation {
<T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType);
interface TerminatingRemoveByQuery<T> {
List<RemoveResult> all();
}
interface RemoveByQueryWithQuery<T> extends TerminatingRemoveByQuery<T> {
TerminatingRemoveByQuery<T> matching(Query query);
}
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryWithQuery<T> {
RemoveByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface ExecutableRemoveByQuery<T> extends RemoveByQueryConsistentWith<T> {}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.List;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public class ExecutableRemoveByQueryOperationSupport implements ExecutableRemoveByQueryOperation {
private static final Query ALL_QUERY = new Query();
private final CouchbaseTemplate template;
public ExecutableRemoveByQueryOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED);
}
static class ExecutableRemoveByQuerySupport<T> implements ExecutableRemoveByQuery<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final Query query;
private final ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport<T> reactiveSupport;
private final QueryScanConsistency scanConsistency;
ExecutableRemoveByQuerySupport(final CouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.reactiveSupport = new ReactiveRemoveByQueryOperationSupport.ReactiveRemoveByQuerySupport<>(
template.reactive(), domainType, query, scanConsistency);
this.scanConsistency = scanConsistency;
}
@Override
public List<RemoveResult> all() {
return reactiveSupport.all().collectList().block();
}
@Override
public TerminatingRemoveByQuery<T> matching(final Query query) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public RemoveByQueryWithQuery<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ExecutableRemoveByQuerySupport<>(template, domainType, query, scanConsistency);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ExecutableReplaceByIdOperation {
<T> ExecutableReplaceById<T> replaceById(Class<T> domainType);
interface TerminatingReplaceById<T> {
T one(T object);
Collection<? extends T> all(Collection<? extends T> objects);
}
interface ReplaceByIdWithCollection<T> extends TerminatingReplaceById<T> {
TerminatingReplaceById<T> inCollection(String collection);
}
interface ReplaceByIdWithDurability<T> extends ReplaceByIdWithCollection<T> {
ReplaceByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
ReplaceByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ExecutableReplaceById<T> extends ReplaceByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceByIdOperation {
private final CouchbaseTemplate template;
public ExecutableReplaceByIdOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableReplaceById<T> replaceById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ExecutableReplaceByIdSupport<T> implements ExecutableReplaceById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport<T> reactiveSupport;
ExecutableReplaceByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveSupport = new ReactiveReplaceByIdOperationSupport.ReactiveReplaceByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public T one(final T object) {
return reactiveSupport.one(object).block();
}
@Override
public Collection<? extends T> all(Collection<? extends T> objects) {
return reactiveSupport.all(objects).collectList().block();
}
@Override
public TerminatingReplaceById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ExecutableUpsertByIdOperation {
<T> ExecutableUpsertById<T> upsertById(Class<T> domainType);
interface TerminatingUpsertById<T> {
T one(T object);
Collection<? extends T> all(Collection<? extends T> objects);
}
interface UpsertByIdWithCollection<T> extends TerminatingUpsertById<T> {
TerminatingUpsertById<T> inCollection(String collection);
}
interface UpsertByIdWithDurability<T> extends UpsertByIdWithCollection<T> {
UpsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
UpsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ExecutableUpsertById<T> extends UpsertByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByIdOperation {
private final CouchbaseTemplate template;
public ExecutableUpsertByIdOperationSupport(final CouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ExecutableUpsertById<T> upsertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ExecutableUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ExecutableUpsertByIdSupport<T> implements ExecutableUpsertById<T> {
private final CouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
private final ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport<T> reactiveSupport;
ExecutableUpsertByIdSupport(final CouchbaseTemplate template, final Class<T> domainType, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
this.reactiveSupport = new ReactiveUpsertByIdOperationSupport.ReactiveUpsertByIdSupport<>(template.reactive(),
domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public T one(final T object) {
return reactiveSupport.one(object).block();
}
@Override
public Collection<? extends T> all(Collection<? extends T> objects) {
return reactiveSupport.all(objects).collectList().block();
}
@Override
public TerminatingUpsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ExecutableUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
}
}

View File

@@ -14,18 +14,9 @@
* limitations under the License.
*/
package org.springframework.data.couchbase.repository;
package org.springframework.data.couchbase.core;
import java.io.Serializable;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
* Couchbase specific {@link org.springframework.data.repository.Repository} interface that is
* a {@link PagingAndSortingRepository}.
*
* @author Simon Baslé
*/
public interface CouchbasePagingAndSortingRepository<T, ID extends Serializable>
extends CouchbaseRepository<T, ID>, PagingAndSortingRepository<T, ID> {
}
public interface FluentCouchbaseOperations extends ExecutableUpsertByIdOperation, ExecutableInsertByIdOperation,
ExecutableReplaceByIdOperation, ExecutableFindByIdOperation, ExecutableFindFromReplicasByIdOperation,
ExecutableFindByQueryOperation, ExecutableFindByAnalyticsOperation, ExecutableExistsByIdOperation,
ExecutableRemoveByIdOperation, ExecutableRemoveByQueryOperation {}

View File

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

View File

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

View File

@@ -0,0 +1,35 @@
/*
* 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.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
/**
* Defines common operations on the Couchbase data source, most commonly implemented by {@link CouchbaseTemplate}.
*/
public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOperations {
CouchbaseConverter getConverter();
String getBucketName();
String getScopeName();
CouchbaseClientFactory getCouchbaseClientFactory();
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import com.couchbase.client.java.Collection;
public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations {
private final CouchbaseClientFactory clientFactory;
private final CouchbaseConverter converter;
private final PersistenceExceptionTranslator exceptionTranslator;
private final CouchbaseTemplateSupport templateSupport;
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this.clientFactory = clientFactory;
this.converter = converter;
this.exceptionTranslator = clientFactory.getExceptionTranslator();
this.templateSupport = new CouchbaseTemplateSupport(converter);
}
@Override
public <T> ReactiveFindById<T> findById(Class<T> domainType) {
return new ReactiveFindByIdOperationSupport(this).findById(domainType);
}
@Override
public ReactiveExistsById existsById() {
return new ReactiveExistsByIdOperationSupport(this).existsById();
}
@Override
public <T> ReactiveFindByAnalytics<T> findByAnalytics(Class<T> domainType) {
return new ReactiveFindByAnalyticsOperationSupport(this).findByAnalytics(domainType);
}
@Override
public <T> ReactiveFindByQuery<T> findByQuery(Class<T> domainType) {
return new ReactiveFindByQueryOperationSupport(this).findByQuery(domainType);
}
@Override
public <T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ReactiveFindFromReplicasByIdOperationSupport(this).findFromReplicasById(domainType);
}
@Override
public <T> ReactiveInsertById<T> insertById(Class<T> domainType) {
return new ReactiveInsertByIdOperationSupport(this).insertById(domainType);
}
@Override
public ReactiveRemoveById removeById() {
return new ReactiveRemoveByIdOperationSupport(this).removeById();
}
@Override
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ReactiveRemoveByQueryOperationSupport(this).removeByQuery(domainType);
}
@Override
public <T> ReactiveReplaceById<T> replaceById(Class<T> domainType) {
return new ReactiveReplaceByIdOperationSupport(this).replaceById(domainType);
}
@Override
public <T> ReactiveUpsertById<T> upsertById(Class<T> domainType) {
return new ReactiveUpsertByIdOperationSupport(this).upsertById(domainType);
}
@Override
public String getBucketName() {
return clientFactory.getBucket().name();
}
@Override
public String getScopeName() {
return clientFactory.getScope().name();
}
@Override
public CouchbaseClientFactory getCouchbaseClientFactory() {
return clientFactory;
}
/**
* Provides access to a {@link Collection} on the configured {@link CouchbaseClientFactory}.
*
* @param collectionName the name of the collection, if null is passed in the default collection is assumed.
* @return the collection instance.
*/
public Collection getCollection(final String collectionName) {
return clientFactory.getCollection(collectionName);
}
@Override
public CouchbaseConverter getConverter() {
return converter;
}
CouchbaseTemplateSupport support() {
return templateSupport;
}
/**
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
* exception if the conversation failed. Thus allows safe re-throwing of the return value.
*
* @param ex the exception to translate
*/
RuntimeException potentiallyConvertRuntimeException(RuntimeException ex) {
RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
return resolved == null ? ex : resolved;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.Map;
public interface ReactiveExistsByIdOperation {
ReactiveExistsById existsById();
interface TerminatingExistsById {
Mono<Boolean> one(String id);
Mono<Map<String, Boolean>> all(Collection<String> ids);
}
interface ExistsByIdWithCollection extends TerminatingExistsById {
TerminatingExistsById inCollection(String collection);
}
interface ReactiveExistsById extends ExistsByIdWithCollection {}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import static com.couchbase.client.java.kv.ExistsOptions.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import java.util.Collection;
import java.util.Map;
import org.springframework.util.Assert;
import com.couchbase.client.java.kv.ExistsResult;
public class ReactiveExistsByIdOperationSupport implements ReactiveExistsByIdOperation {
private final ReactiveCouchbaseTemplate template;
ReactiveExistsByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public ReactiveExistsById existsById() {
return new ReactiveExistsByIdSupport(template, null);
}
static class ReactiveExistsByIdSupport implements ReactiveExistsById {
private final ReactiveCouchbaseTemplate template;
private final String collection;
ReactiveExistsByIdSupport(final ReactiveCouchbaseTemplate template, final String collection) {
this.template = template;
this.collection = collection;
}
@Override
public Mono<Boolean> one(final String id) {
return Mono.just(id).flatMap(
docId -> template.getCollection(collection).reactive().exists(id, existsOptions()).map(ExistsResult::exists))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Mono<Map<String, Boolean>> all(final Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(id -> one(id).map(result -> Tuples.of(id, result)))
.collectMap(Tuple2::getT1, Tuple2::getT2);
}
@Override
public TerminatingExistsById inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveExistsByIdSupport(template, collection);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
public interface ReactiveFindByAnalyticsOperation {
<T> ReactiveFindByAnalytics<T> findByAnalytics(Class<T> domainType);
/**
* Compose find execution by calling one of the terminating methods.
*/
interface TerminatingFindByAnalytics<T> {
Mono<T> one();
Mono<T> first();
Flux<T> all();
Mono<Long> count();
Mono<Boolean> exists();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindByAnalyticsWithQuery<T> extends TerminatingFindByAnalytics<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindByAnalytics}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindByAnalytics<T> matching(AnalyticsQuery query);
}
interface ReactiveFindByAnalytics<T> extends FindByAnalyticsWithQuery<T> {}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.AnalyticsQuery;
import com.couchbase.client.java.analytics.ReactiveAnalyticsResult;
public class ReactiveFindByAnalyticsOperationSupport implements ReactiveFindByAnalyticsOperation {
private static final AnalyticsQuery ALL_QUERY = new AnalyticsQuery();
private final ReactiveCouchbaseTemplate template;
public ReactiveFindByAnalyticsOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindByAnalytics<T> findByAnalytics(final Class<T> domainType) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, ALL_QUERY);
}
static class ReactiveFindByAnalyticsSupport<T> implements ReactiveFindByAnalytics<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final AnalyticsQuery query;
ReactiveFindByAnalyticsSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final AnalyticsQuery query) {
this.template = template;
this.domainType = domainType;
this.query = query;
}
@Override
public TerminatingFindByAnalytics<T> matching(AnalyticsQuery query) {
return new ReactiveFindByAnalyticsSupport<>(template, domainType, query);
}
@Override
public Mono<T> one() {
return all().single();
}
@Override
public Mono<T> first() {
return all().next();
}
@Override
public Flux<T> all() {
return Flux.defer(() -> {
String statement = assembleEntityQuery(false);
return template.getCouchbaseClientFactory().getCluster().reactive().analyticsQuery(statement)
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).map(row -> {
String id = row.getString("__id");
long cas = row.getLong("__cas");
row.removeKey("__id");
row.removeKey("__cas");
return template.support().decodeEntity(id, row.toString(), cas, domainType);
});
});
}
@Override
public Mono<Long> count() {
return Mono.defer(() -> {
String statement = assembleEntityQuery(true);
return template.getCouchbaseClientFactory().getCluster().reactive().analyticsQuery(statement)
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveAnalyticsResult::rowsAsObject).map(row -> row.getLong("__count")).next();
});
}
@Override
public Mono<Boolean> exists() {
return count().map(count -> count > 0);
}
private String assembleEntityQuery(final boolean count) {
final String bucket = "`" + template.getBucketName() + "`";
final StringBuilder statement = new StringBuilder("SELECT ");
if (count) {
statement.append("count(*) as __count");
} else {
statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*");
}
final String dataset = template.support().getJavaNameForEntity(domainType);
statement.append(" FROM ").append(dataset);
query.appendSort(statement);
query.appendSkipAndLimit(statement);
return statement.toString();
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
public interface ReactiveFindByIdOperation {
<T> ReactiveFindById<T> findById(Class<T> domainType);
interface TerminatingFindById<T> {
Mono<T> one(String id);
Flux<? extends T> all(Collection<String> ids);
}
interface FindByIdWithCollection<T> extends TerminatingFindById<T> {
TerminatingFindById<T> inCollection(String collection);
}
interface FindByIdWithProjection<T> extends FindByIdWithCollection<T> {
FindByIdWithCollection<T> project(String... fields);
}
interface ReactiveFindById<T> extends FindByIdWithProjection<T> {}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import static com.couchbase.client.java.kv.GetOptions.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.util.Assert;
import com.couchbase.client.java.codec.RawJsonTranscoder;
import com.couchbase.client.java.kv.GetOptions;
public class ReactiveFindByIdOperationSupport implements ReactiveFindByIdOperation {
private final ReactiveCouchbaseTemplate template;
ReactiveFindByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindById<T> findById(Class<T> domainType) {
return new ReactiveFindByIdSupport<>(template, domainType, null, null);
}
static class ReactiveFindByIdSupport<T> implements ReactiveFindById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final List<String> fields;
ReactiveFindByIdSupport(ReactiveCouchbaseTemplate template, Class<T> domainType, String collection,
List<String> fields) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.fields = fields;
}
@Override
public Mono<T> one(final String id) {
return Mono.just(id).flatMap(docId -> {
GetOptions options = getOptions().transcoder(RawJsonTranscoder.INSTANCE);
if (fields != null && !fields.isEmpty()) {
options.project(fields);
}
return template.getCollection(collection).reactive().get(docId, options);
}).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> all(final Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(this::one);
}
@Override
public TerminatingFindById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, collection, fields);
}
@Override
public FindByIdWithCollection<T> project(String... fields) {
Assert.notEmpty(fields, "Fields must not be null nor empty.");
return new ReactiveFindByIdSupport<>(template, domainType, collection, Arrays.asList(fields));
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public interface ReactiveFindByQueryOperation {
<T> ReactiveFindByQuery<T> findByQuery(Class<T> domainType);
/**
* Compose find execution by calling one of the terminating methods.
*/
interface TerminatingFindByQuery<T> {
Mono<T> one();
Mono<T> first();
Flux<T> all();
Mono<Long> count();
Mono<Boolean> exists();
}
/**
* Terminating operations invoking the actual query execution.
*
* @author Christoph Strobl
* @since 2.0
*/
interface FindByQueryWithQuery<T> extends TerminatingFindByQuery<T> {
/**
* Set the filter query to be used.
*
* @param query must not be {@literal null}.
* @return new instance of {@link TerminatingFindByQuery}.
* @throws IllegalArgumentException if query is {@literal null}.
*/
TerminatingFindByQuery<T> matching(Query query);
}
interface FindByQueryConsistentWith<T> extends FindByQueryWithQuery<T> {
FindByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface ReactiveFindByQuery<T> extends FindByQueryConsistentWith<T> {}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.core.query.QueryCriteria;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
import com.couchbase.client.java.query.ReactiveQueryResult;
public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryOperation {
private static final Query ALL_QUERY = new Query();
private final ReactiveCouchbaseTemplate template;
public ReactiveFindByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindByQuery<T> findByQuery(final Class<T> domainType) {
return new ReactiveFindByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED);
}
static class ReactiveFindByQuerySupport<T> implements ReactiveFindByQuery<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final Query query;
private final QueryScanConsistency scanConsistency;
ReactiveFindByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.scanConsistency = scanConsistency;
}
@Override
public TerminatingFindByQuery<T> matching(Query query) {
return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public FindByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency) {
return new ReactiveFindByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public Mono<T> one() {
return all().single();
}
@Override
public Mono<T> first() {
return all().next();
}
@Override
public Flux<T> all() {
return Flux.defer(() -> {
String statement = assembleEntityQuery(false);
return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions())
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> {
String id = row.getString("__id");
long cas = row.getLong("__cas");
row.removeKey("__id");
row.removeKey("__cas");
return template.support().decodeEntity(id, row.toString(), cas, domainType);
});
});
}
@Override
public Mono<Long> count() {
return Mono.defer(() -> {
String statement = assembleEntityQuery(true);
return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions())
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject).map(row -> row.getLong("__count")).next();
});
}
@Override
public Mono<Boolean> exists() {
return count().map(count -> count > 0);
}
private String assembleEntityQuery(final boolean count) {
final String bucket = "`" + template.getBucketName() + "`";
final StringBuilder statement = new StringBuilder("SELECT ");
if (count) {
statement.append("count(*) as __count");
} else {
statement.append("meta().id as __id, meta().cas as __cas, ").append(bucket).append(".*");
}
statement.append(" FROM ").append(bucket);
String typeKey = template.getConverter().getTypeKey();
String typeValue = template.support().getJavaNameForEntity(domainType);
query.addCriteria(QueryCriteria.where(typeKey).is(typeValue));
query.appendWhere(statement);
query.appendSort(statement);
query.appendSkipAndLimit(statement);
return statement.toString();
}
private QueryOptions buildQueryOptions() {
final QueryOptions options = QueryOptions.queryOptions();
if (scanConsistency != null) {
options.scanConsistency(scanConsistency);
}
return options;
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
public interface ReactiveFindFromReplicasByIdOperation {
<T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType);
interface TerminatingFindFromReplicasById<T> {
Mono<T> any(String id);
Flux<? extends T> any(Collection<String> ids);
}
interface FindFromReplicasByIdWithCollection<T> extends TerminatingFindFromReplicasById<T> {
TerminatingFindFromReplicasById<T> inCollection(String collection);
}
interface ReactiveFindFromReplicasById<T> extends FindFromReplicasByIdWithCollection<T> {}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import static com.couchbase.client.java.kv.GetAnyReplicaOptions.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.java.codec.RawJsonTranscoder;
import com.couchbase.client.java.kv.GetAnyReplicaOptions;
public class ReactiveFindFromReplicasByIdOperationSupport implements ReactiveFindFromReplicasByIdOperation {
private final ReactiveCouchbaseTemplate template;
ReactiveFindFromReplicasByIdOperationSupport(ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveFindFromReplicasById<T> findFromReplicasById(Class<T> domainType) {
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, null);
}
static class ReactiveFindFromReplicasByIdSupport<T> implements ReactiveFindFromReplicasById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
ReactiveFindFromReplicasByIdSupport(ReactiveCouchbaseTemplate template, Class<T> domainType, String collection) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
}
@Override
public Mono<T> any(final String id) {
return Mono.just(id).flatMap(docId -> {
GetAnyReplicaOptions options = getAnyReplicaOptions().transcoder(RawJsonTranscoder.INSTANCE);
return template.getCollection(collection).reactive().getAnyReplica(docId, options);
}).map(result -> template.support().decodeEntity(id, result.contentAs(String.class), result.cas(), domainType))
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> any(Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(this::any);
}
@Override
public TerminatingFindFromReplicasById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveFindFromReplicasByIdSupport<>(template, domainType, collection);
}
}
}

View File

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

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ReactiveInsertByIdOperation {
<T> ReactiveInsertById<T> insertById(Class<T> domainType);
interface TerminatingInsertById<T> {
Mono<T> one(T object);
Flux<? extends T> all(Collection<? extends T> objects);
}
interface InsertByIdWithCollection<T> extends TerminatingInsertById<T> {
TerminatingInsertById<T> inCollection(String collection);
}
interface InsertByIdWithDurability<T> extends InsertByIdWithCollection<T> {
InsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
InsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReactiveInsertById<T> extends InsertByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.InsertOptions;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
private final ReactiveCouchbaseTemplate template;
public ReactiveInsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveInsertById<T> insertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveInsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ReactiveInsertByIdSupport<T> implements ReactiveInsertById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
ReactiveInsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return template.getCollection(collection).reactive()
.insert(converted.getId(), converted.getPayload(), buildInsertOptions()).map(result -> {
template.support().applyUpdatedCas(object, result.cas());
return o;
});
}).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> all(Collection<? extends T> objects) {
return Flux.fromIterable(objects).flatMap(this::one);
}
private InsertOptions buildInsertOptions() {
final InsertOptions options = InsertOptions.insertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
return options;
}
@Override
public TerminatingInsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public InsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public InsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveInsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ReactiveRemoveByIdOperation {
ReactiveRemoveById removeById();
interface TerminatingRemoveById {
Mono<RemoveResult> one(String id);
Flux<RemoveResult> all(Collection<String> ids);
}
interface RemoveByIdWithCollection extends TerminatingRemoveById {
TerminatingRemoveById inCollection(String collection);
}
interface RemoveByIdWithDurability extends RemoveByIdWithCollection {
RemoveByIdWithCollection withDurability(DurabilityLevel durabilityLevel);
RemoveByIdWithCollection withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReactiveRemoveById extends RemoveByIdWithDurability {}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.RemoveOptions;
import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOperation {
private final ReactiveCouchbaseTemplate template;
public ReactiveRemoveByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public ReactiveRemoveById removeById() {
return new ReactiveRemoveByIdSupport(template, null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE);
}
static class ReactiveRemoveByIdSupport implements ReactiveRemoveById {
private final ReactiveCouchbaseTemplate template;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
ReactiveRemoveByIdSupport(final ReactiveCouchbaseTemplate template, final String collection,
final PersistTo persistTo, final ReplicateTo replicateTo, final DurabilityLevel durabilityLevel) {
this.template = template;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
}
@Override
public Mono<RemoveResult> one(final String id) {
return Mono.just(id).flatMap(docId -> template.getCollection(collection).reactive()
.remove(id, buildRemoveOptions()).map(r -> RemoveResult.from(docId, r))).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<RemoveResult> all(final Collection<String> ids) {
return Flux.fromIterable(ids).flatMap(this::one);
}
private RemoveOptions buildRemoveOptions() {
final RemoveOptions options = RemoveOptions.removeOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
return options;
}
@Override
public TerminatingRemoveById inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveByIdWithCollection withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public RemoveByIdWithCollection withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveRemoveByIdSupport(template, collection, persistTo, replicateTo, durabilityLevel);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryScanConsistency;
public interface ReactiveRemoveByQueryOperation {
<T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType);
interface TerminatingRemoveByQuery<T> {
Flux<RemoveResult> all();
}
interface RemoveByQueryWithQuery<T> extends TerminatingRemoveByQuery<T> {
TerminatingRemoveByQuery<T> matching(Query query);
}
interface RemoveByQueryConsistentWith<T> extends RemoveByQueryWithQuery<T> {
RemoveByQueryWithQuery<T> consistentWith(QueryScanConsistency scanConsistency);
}
interface ReactiveRemoveByQuery<T> extends RemoveByQueryConsistentWith<T> {}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import java.util.Optional;
import org.springframework.data.couchbase.core.query.Query;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
import com.couchbase.client.java.query.ReactiveQueryResult;
public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQueryOperation {
private static final Query ALL_QUERY = new Query();
private final ReactiveCouchbaseTemplate template;
public ReactiveRemoveByQueryOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, QueryScanConsistency.NOT_BOUNDED);
}
static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final Query query;
private final QueryScanConsistency scanConsistency;
ReactiveRemoveByQuerySupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType, final Query query,
final QueryScanConsistency scanConsistency) {
this.template = template;
this.domainType = domainType;
this.query = query;
this.scanConsistency = scanConsistency;
}
@Override
public Flux<RemoveResult> all() {
return Flux.defer(() -> {
String bucket = "`" + template.getBucketName() + "`";
String typeKey = template.getConverter().getTypeKey();
String typeValue = template.support().getJavaNameForEntity(domainType);
String where = " WHERE `" + typeKey + "` = \"" + typeValue + "\"";
String returning = " RETURNING meta().*";
String statement = "DELETE FROM " + bucket + " " + where + returning;
return template.getCouchbaseClientFactory().getCluster().reactive().query(statement, buildQueryOptions())
.onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
}).flatMapMany(ReactiveQueryResult::rowsAsObject)
.map(row -> new RemoveResult(row.getString("id"), row.getLong("cas"), Optional.empty()));
});
}
private QueryOptions buildQueryOptions() {
final QueryOptions options = QueryOptions.queryOptions();
if (scanConsistency != null) {
options.scanConsistency(scanConsistency);
}
return options;
}
@Override
public TerminatingRemoveByQuery<T> matching(final Query query) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency);
}
@Override
public RemoveByQueryWithQuery<T> consistentWith(final QueryScanConsistency scanConsistency) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, query, scanConsistency);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ReactiveReplaceByIdOperation {
<T> ReactiveReplaceById<T> replaceById(Class<T> domainType);
interface TerminatingReplaceById<T> {
Mono<T> one(T object);
Flux<? extends T> all(Collection<? extends T> objects);
}
interface ReplaceByIdWithCollection<T> extends TerminatingReplaceById<T> {
TerminatingReplaceById<T> inCollection(String collection);
}
interface ReplaceByIdWithDurability<T> extends ReplaceByIdWithCollection<T> {
ReplaceByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
ReplaceByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReactiveReplaceById<T> extends ReplaceByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplaceOptions;
import com.couchbase.client.java.kv.ReplicateTo;
public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
private final ReactiveCouchbaseTemplate template;
public ReactiveReplaceByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveReplaceById<T> replaceById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveReplaceByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ReactiveReplaceByIdSupport<T> implements ReactiveReplaceById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
ReactiveReplaceByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return template.getCollection(collection).reactive()
.replace(converted.getId(), converted.getPayload(), buildReplaceOptions()).map(result -> {
template.support().applyUpdatedCas(object, result.cas());
return o;
});
}).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> all(Collection<? extends T> objects) {
return Flux.fromIterable(objects).flatMap(this::one);
}
private ReplaceOptions buildReplaceOptions() {
final ReplaceOptions options = ReplaceOptions.replaceOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
return options;
}
@Override
public TerminatingReplaceById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
@Override
public ReplaceByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveReplaceByIdSupport<>(template, domainType, collection, persistTo, replicateTo,
durabilityLevel);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
public interface ReactiveUpsertByIdOperation {
<T> ReactiveUpsertById<T> upsertById(Class<T> domainType);
interface TerminatingUpsertById<T> {
Mono<T> one(T object);
Flux<? extends T> all(Collection<? extends T> objects);
}
interface UpsertByIdWithCollection<T> extends TerminatingUpsertById<T> {
TerminatingUpsertById<T> inCollection(String collection);
}
interface UpsertByIdWithDurability<T> extends UpsertByIdWithCollection<T> {
UpsertByIdWithCollection<T> withDurability(DurabilityLevel durabilityLevel);
UpsertByIdWithCollection<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
}
interface ReactiveUpsertById<T> extends UpsertByIdWithDurability<T> {}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2012-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
import org.springframework.util.Assert;
import com.couchbase.client.core.msg.kv.DurabilityLevel;
import com.couchbase.client.java.kv.PersistTo;
import com.couchbase.client.java.kv.ReplicateTo;
import com.couchbase.client.java.kv.UpsertOptions;
public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOperation {
private final ReactiveCouchbaseTemplate template;
public ReactiveUpsertByIdOperationSupport(final ReactiveCouchbaseTemplate template) {
this.template = template;
}
@Override
public <T> ReactiveUpsertById<T> upsertById(final Class<T> domainType) {
Assert.notNull(domainType, "DomainType must not be null!");
return new ReactiveUpsertByIdSupport<>(template, domainType, null, PersistTo.NONE, ReplicateTo.NONE,
DurabilityLevel.NONE);
}
static class ReactiveUpsertByIdSupport<T> implements ReactiveUpsertById<T> {
private final ReactiveCouchbaseTemplate template;
private final Class<T> domainType;
private final String collection;
private final PersistTo persistTo;
private final ReplicateTo replicateTo;
private final DurabilityLevel durabilityLevel;
ReactiveUpsertByIdSupport(final ReactiveCouchbaseTemplate template, final Class<T> domainType,
final String collection, final PersistTo persistTo, final ReplicateTo replicateTo,
final DurabilityLevel durabilityLevel) {
this.template = template;
this.domainType = domainType;
this.collection = collection;
this.persistTo = persistTo;
this.replicateTo = replicateTo;
this.durabilityLevel = durabilityLevel;
}
@Override
public Mono<T> one(T object) {
return Mono.just(object).flatMap(o -> {
CouchbaseDocument converted = template.support().encodeEntity(o);
return template.getCollection(collection).reactive()
.upsert(converted.getId(), converted.getPayload(), buildUpsertOptions()).map(result -> {
template.support().applyUpdatedCas(object, result.cas());
return o;
});
}).onErrorMap(throwable -> {
if (throwable instanceof RuntimeException) {
return template.potentiallyConvertRuntimeException((RuntimeException) throwable);
} else {
return throwable;
}
});
}
@Override
public Flux<? extends T> all(Collection<? extends T> objects) {
return Flux.fromIterable(objects).flatMap(this::one);
}
private UpsertOptions buildUpsertOptions() {
final UpsertOptions options = UpsertOptions.upsertOptions();
if (persistTo != PersistTo.NONE || replicateTo != ReplicateTo.NONE) {
options.durability(persistTo, replicateTo);
} else if (durabilityLevel != DurabilityLevel.NONE) {
options.durability(durabilityLevel);
}
return options;
}
@Override
public TerminatingUpsertById<T> inCollection(final String collection) {
Assert.hasText(collection, "Collection must not be null nor empty.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final DurabilityLevel durabilityLevel) {
Assert.notNull(durabilityLevel, "Durability Level must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
@Override
public UpsertByIdWithCollection<T> withDurability(final PersistTo persistTo, final ReplicateTo replicateTo) {
Assert.notNull(persistTo, "PersistTo must not be null.");
Assert.notNull(replicateTo, "ReplicateTo must not be null.");
return new ReactiveUpsertByIdSupport<>(template, domainType, collection, persistTo, replicateTo, durabilityLevel);
}
}
}

View File

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

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2017-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.core;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.PersistTo;
import com.couchbase.client.java.ReplicateTo;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.query.AsyncN1qlQueryResult;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.view.AsyncSpatialViewResult;
import com.couchbase.client.java.view.AsyncViewResult;
import com.couchbase.client.java.view.SpatialViewQuery;
import com.couchbase.client.java.view.ViewQuery;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.query.Consistency;
import rx.Observable;
/**
* @author Subhashni Balakrishnan
* @author Alex Derkach
* @since 3.0
*/
public interface RxJavaCouchbaseOperations {
<T>Observable<T> save(T objectToSave);
<T>Observable<T> save(Iterable<T> batchToSave);
<T>Observable<T> save(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> save(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> insert(T objectToSave);
<T>Observable<T> insert(Iterable<T> batchToSave);
<T>Observable<T> insert(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> insert(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> update(T objectToSave);
<T>Observable<T> update(Iterable<T> batchToSave);
<T>Observable<T> update(T objectToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> update(Iterable<T> batchToSave, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> remove(T objectToRemove);
<T>Observable<T> remove(T objectToRemove, PersistTo persistTo, ReplicateTo replicateTo);
<T>Observable<T> remove(Iterable<T> batchToRemove);
<T>Observable<T> remove(Iterable<T> batchToRemove, PersistTo persistTo, ReplicateTo replicateTo);
Observable<Boolean> exists(String id);
<T>Observable<T> findById(String id, Class<T> entityClass);
Observable<AsyncN1qlQueryResult> queryN1QL(N1qlQuery n1ql);
Observable<AsyncViewResult> queryView(ViewQuery query);
Observable<AsyncSpatialViewResult> querySpatialView(SpatialViewQuery query);
<T>Observable<T> findByView(ViewQuery query, Class<T> entityClass);
<T>Observable<T> findByN1QL(N1qlQuery n1ql, Class<T> entityClass);
<T>Observable<T> findBySpatialView(SpatialViewQuery query, Class<T> entityClass);
<T>Observable<T> findByN1QLProjection(N1qlQuery n1ql, Class<T> fragmentClass);
Consistency getDefaultConsistency();
/**
* Returns the linked {@link Bucket} to this template.
*
* @return the client used for the template.
*/
Bucket getCouchbaseBucket();
CouchbaseConverter getConverter();
ClusterInfo getCouchbaseClusterInfo();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String, Object> source = (Map<String, Object>) 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<String, Object> source = (Map<String, Object>) 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);
}
}

View File

@@ -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<Converter<?, ?>> getConvertersToRegister() {
List<Converter<?, ?>> 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<Converter<?, ?>> getConvertersToRegister() {
List<Converter<?, ?>> 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<Number, LocalDateTime> {
@ReadingConverter
public enum NumberToLocalDateTimeConverter implements Converter<Number, LocalDateTime> {
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<LocalDateTime, Long> {
@WritingConverter
public enum LocalDateTimeToLongConverter implements Converter<LocalDateTime, Long> {
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<Number, LocalDate> {
@ReadingConverter
public enum NumberToLocalDateConverter implements Converter<Number, LocalDate> {
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<LocalDate, Long> {
@WritingConverter
public enum LocalDateToLongConverter implements Converter<LocalDate, Long> {
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<Number, LocalTime> {
@ReadingConverter
public enum NumberToLocalTimeConverter implements Converter<Number, LocalTime> {
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<LocalTime, Long> {
@WritingConverter
public enum LocalTimeToLongConverter implements Converter<LocalTime, Long> {
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<Number, Instant> {
@ReadingConverter
public enum NumberToInstantConverter implements Converter<Number, Instant> {
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<Instant, Long> {
@WritingConverter
public enum InstantToLongConverter implements Converter<Instant, Long> {
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<ZoneId, String> {
@WritingConverter
public enum ZoneIdToStringConverter implements Converter<ZoneId, String> {
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<String, ZoneId> {
@ReadingConverter
public enum StringToZoneIdConverter implements Converter<String, ZoneId> {
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<Duration, String> {
@WritingConverter
public enum DurationToStringConverter implements Converter<Duration, String> {
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<String, Duration> {
@ReadingConverter
public enum StringToDurationConverter implements Converter<String, Duration> {
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<Period, String> {
@WritingConverter
public enum PeriodToStringConverter implements Converter<Period, String> {
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<String, Period> {
@ReadingConverter
public enum StringToPeriodConverter implements Converter<String, Period> {
INSTANCE;
INSTANCE;
@Override
public Period convert(String s) {
return Period.parse(s);
}
}
@Override
public Period convert(String s) {
return Period.parse(s);
}
}
}

View File

@@ -26,6 +26,6 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
*/
public interface CouchbaseTypeMapper extends TypeMapper<CouchbaseDocument> {
String getTypeKey();
String getTypeKey();
}

View File

@@ -23,6 +23,4 @@ import org.springframework.data.convert.EntityWriter;
*
* @author Michael Nitschinger
*/
public interface CouchbaseWriter<T, ConvertedCouchbaseDocument> extends
EntityWriter<T, ConvertedCouchbaseDocument> {
}
public interface CouchbaseWriter<T, ConvertedCouchbaseDocument> extends EntityWriter<T, ConvertedCouchbaseDocument> {}

View File

@@ -21,8 +21,10 @@ import java.util.List;
/**
* Value object to capture custom conversion.
* <p/>
* <p>Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper
* inspection nor nested conversion.</p>
* <p>
* Types that can be mapped directly onto JSON are considered simple ones, because they neither need deeper inspection
* nor nested conversion.
* </p>
*
* @author Michael Nitschinger
* @author Oliver Gierke
@@ -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);
}
}

View File

@@ -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<Converter<?, ?>> getConvertersToRegister() {
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
/**
* Returns all converters by this class that can be registered.
*
* @return the list of converters to register.
*/
public static Collection<Converter<?, ?>> getConvertersToRegister() {
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
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<Date, String> {
INSTANCE;
@WritingConverter
public enum DateToStringConverter implements Converter<Date, String> {
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<Object, Date> {
INSTANCE;
@ReadingConverter
public enum SerializedObjectToDateConverter implements Converter<Object, Date> {
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<Date, Long> {
INSTANCE;
@WritingConverter
public enum DateToLongConverter implements Converter<Date, Long> {
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<Calendar, Long> {
INSTANCE;
@WritingConverter
public enum CalendarToLongConverter implements Converter<Calendar, Long> {
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<Number, Calendar> {
INSTANCE;
@ReadingConverter
public enum NumberToCalendarConverter implements Converter<Number, Calendar> {
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<LocalDate, Long> {
INSTANCE;
@WritingConverter
public enum LocalDateToLongConverter implements Converter<LocalDate, Long> {
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<LocalDateTime, Long> {
INSTANCE;
@WritingConverter
public enum LocalDateTimeToLongConverter implements Converter<LocalDateTime, Long> {
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<DateTime, Long> {
INSTANCE;
@WritingConverter
public enum DateTimeToLongConverter implements Converter<DateTime, Long> {
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<Number, LocalDate> {
INSTANCE;
@ReadingConverter
public enum NumberToLocalDateConverter implements Converter<Number, LocalDate> {
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<Number, LocalDateTime> {
INSTANCE;
@ReadingConverter
public enum NumberToLocalDateTimeConverter implements Converter<Number, LocalDateTime> {
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<Number, DateTime> {
INSTANCE;
@ReadingConverter
public enum NumberToDateTimeConverter implements Converter<Number, DateTime> {
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());
}
}
}

View File

@@ -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<CouchbaseDocument> 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<CouchbaseDocument> {
public static final class CouchbaseDocumentTypeAliasAccessor implements TypeAliasAccessor<CouchbaseDocument> {
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);
}
}
}
}

View File

@@ -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 <R> List<R> doResolve(CouchbaseTemplate template, N1qlJoinResolverParameters parameters,
Class<R> 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 <R> List<R> doResolve(CouchbaseTemplate template,
N1qlJoinResolverParameters parameters,
Class<R> 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;
}
}
}

View File

@@ -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;

View File

@@ -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<String, Object> 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<String, Object> 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> T decodeFragment(String source, Class<T> target) {
try {
return objectMapper.readValue(source, target);
}
catch (IOException e) {
throw new RuntimeException("Cannot decode ad-hoc JSON", e);
}
}
@Override
public <T> T decodeFragment(String source, Class<T> 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);
}
}

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