diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java new file mode 100644 index 00000000..aa06cd10 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.HashSet; +import java.util.List; +import java.util.Set; + +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.CouchbaseCluster; +import com.couchbase.client.java.env.CouchbaseEnvironment; +import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ConfigurationCondition; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.data.annotation.Persistent; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.core.mapping.Document; +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; + +/** + * Base class for Spring Data Couchbase configuration using JavaConfig. + * + * @author Michael Nitschinger + * @author Simon Baslé + */ +@Configuration +public abstract class AbstractCouchbaseConfiguration { + + /** + * The list of hostnames (or IP addresses) to bootstrap from. + * + * @return the list of bootstrap hosts. + */ + protected abstract List bootstrapHosts(); + + /** + * The name of the bucket to connect to. + * + * @return the name of the bucket. + */ + protected abstract String getBucketName(); + + /** + * The password of the bucket (can be an empty string). + * + * @return the password of the bucket. + */ + protected abstract String getBucketPassword(); + + /** + * Override this method if you use Couchbase outside of the Spring context. + * If non-null, defines the {@link CouchbaseEnvironment} to use for connection (it is your + * responsibility to shutdown() it). + * + * @return a pre-existing environment managed outside of Spring, or null if instead a managed + * environment is to be used. + */ + protected CouchbaseEnvironment sharedEnvironment() { + return null; //assume most of the time we'll create a dedicated one + } + + /** + * Override this method if you want a customized {@link CouchbaseEnvironment} but only + * use it in the Spring context. This environment will be managed by Spring, which will + * call its shutdown() method upon bean destruction. + * + * @return a customized environment to be managed by Spring, defaults to a {@link DefaultCouchbaseEnvironment}. + */ + protected CouchbaseEnvironment managedEnvironment() { + return DefaultCouchbaseEnvironment.create(); + } + + @Bean(destroyMethod = "shutdown", name = BeanNames.COUCHBASE_ENV) + @Conditional(ConfigurationCondition.class) + public CouchbaseEnvironment couchbaseEnvironment() { + CouchbaseEnvironment env = sharedEnvironment(); + if (env != null) { + //the shared environment shouldn't have its shutdown method called when + //the bean is destroyed, it is the responsibility of the user to destroy it. + return new CouchbaseEnvironmentNoShutdownProxy(env); + } else { + return managedEnvironment(); + } + } + + /** + * Returns the {@link Cluster} instance to connect to. + * + * @throws Exception on Bean construction failure. + */ + @Bean(destroyMethod = "disconnect", name = BeanNames.COUCHBASE_CLUSTER) + public Cluster couchbaseCluster() throws Exception { + return CouchbaseCluster.create(couchbaseEnvironment(), bootstrapHosts()); + } + + /** + * Return the {@link Bucket} instance to connect to. + * + * @throws Exception on Bean construction failure. + */ + @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 + return couchbaseCluster().openBucket(getBucketName(), getBucketPassword()); + } + + /** + * Creates a {@link CouchbaseTemplate}. + * + * @throws Exception on Bean construction failure. + */ + @Bean(name = BeanNames.COUCHBASE_TEMPLATE) + public CouchbaseTemplate couchbaseTemplate() throws Exception { + //TODO use mappingCouchbaseConverter and translationService when implemented + return new CouchbaseTemplate(couchbaseClient()); + } + + //TODO create beans for mappingCouchbaseConverter, translationService, couchbaseMappingContext when implemented + //TODO for mappingCouchbaseConverter, allow registering of customConversions + + /** + * Scans the mapping base package for classes annotated with {@link Document}. + * + * @throws ClassNotFoundException if initial entity sets could not be loaded. + */ + protected Set> getInitialEntitySet() throws ClassNotFoundException { + String basePackage = getMappingBasePackage(); + Set> initialEntitySet = new HashSet>(); + + if (StringUtils.hasText(basePackage)) { + ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class)); + componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); + for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { + initialEntitySet.add(ClassUtils.forName(candidate.getBeanClassName(), AbstractCouchbaseConfiguration.class.getClassLoader())); + } + } + + return initialEntitySet; + } + + /** + * Return the base package to scan for mapped {@link Document}s. Will return the package name of the configuration + * class (the concrete class, not this one here) by default. + *

+ *

So if you have a {@code com.acme.AppConfig} extending {@link AbstractCouchbaseConfiguration} the base package + * will be considered {@code com.acme} unless the method is overridden to implement alternate behavior.

+ * + * @return the base package to scan for mapped {@link Document} classes or {@literal null} to not enable scanning for + * entities. + */ + protected String getMappingBasePackage() { + return getClass().getPackage().getName(); + } + + /** + * Set to true if field names should be abbreviated with the {@link CamelCaseAbbreviatingFieldNamingStrategy}. + * + * @return true if field names should be abbreviated, default is false. + */ + protected boolean abbreviateFieldNames() { + return false; + } + + /** + * Configures a {@link FieldNamingStrategy} on the CouchbaseMappingContext instance created. + * + * @return the naming strategy. + */ + protected FieldNamingStrategy fieldNamingStrategy() { + //TODO implement a CouchbaseMappingContext, use this method (update link in javadoc) + return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy() : PropertyNameFieldNamingStrategy.INSTANCE; + } +} diff --git a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java index 942d8e50..0479c499 100644 --- a/src/main/java/org/springframework/data/couchbase/config/BeanNames.java +++ b/src/main/java/org/springframework/data/couchbase/config/BeanNames.java @@ -24,15 +24,19 @@ package org.springframework.data.couchbase.config; */ public class BeanNames { + /** + * Refers to the bean. + */ + static final String COUCHBASE_ENV = "couchbaseEnv"; /** * Refers to the "" bean. */ - static final String COUCHBASE_CLUSTER = "cluster"; + static final String COUCHBASE_CLUSTER = "couchbaseCluster"; /** * Refers to the "" bean. */ - static final String COUCHBASE_BUCKET = "bucket"; + static final String COUCHBASE_BUCKET = "couchbaseBucket"; /** * Refers to the "" bean. @@ -44,4 +48,5 @@ public class BeanNames { */ static final String TRANSLATION_SERVICE = "translationService"; + } diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownProxy.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownProxy.java new file mode 100644 index 00000000..6733a942 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseEnvironmentNoShutdownProxy.java @@ -0,0 +1,242 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.event.EventBus; +import com.couchbase.client.core.retry.RetryStrategy; +import com.couchbase.client.core.time.Delay; +import com.couchbase.client.deps.io.netty.channel.EventLoopGroup; +import com.couchbase.client.java.env.CouchbaseEnvironment; +import rx.Observable; +import rx.Scheduler; + +/** + * A proxy around a {@link CouchbaseEnvironment} that prevents its {@link #shutdown()} method + * to be invoked. Useful when the delegate is not to be lifecycle-managed by Spring. + * + * @author Simon Baslé + */ +public class CouchbaseEnvironmentNoShutdownProxy implements CouchbaseEnvironment { + + private final CouchbaseEnvironment delegate; + + public CouchbaseEnvironmentNoShutdownProxy(CouchbaseEnvironment delegate) { + this.delegate = delegate; + } + + @Override + public Observable shutdown() { + return Observable.just(false); + } + + //===== DELEGATION METHODS ===== + + @Override + public EventLoopGroup ioPool() { + return delegate.ioPool(); + } + + @Override + public Scheduler scheduler() { + return delegate.scheduler(); + } + + @Override + public boolean dcpEnabled() { + return delegate.dcpEnabled(); + } + + @Override + public boolean sslEnabled() { + return delegate.sslEnabled(); + } + + @Override + public String sslKeystoreFile() { + return delegate.sslKeystoreFile(); + } + + @Override + public String sslKeystorePassword() { + return delegate.sslKeystorePassword(); + } + + @Override + public boolean queryEnabled() { + return delegate.queryEnabled(); + } + + @Override + public int queryPort() { + return delegate.queryPort(); + } + + @Override + public boolean bootstrapHttpEnabled() { + return delegate.bootstrapHttpEnabled(); + } + + @Override + public boolean bootstrapCarrierEnabled() { + return delegate.bootstrapCarrierEnabled(); + } + + @Override + public int bootstrapHttpDirectPort() { + return delegate.bootstrapHttpDirectPort(); + } + + @Override + public int bootstrapHttpSslPort() { + return delegate.bootstrapHttpSslPort(); + } + + @Override + public int bootstrapCarrierDirectPort() { + return delegate.bootstrapCarrierDirectPort(); + } + + @Override + public int bootstrapCarrierSslPort() { + return delegate.bootstrapCarrierSslPort(); + } + + @Override + public int ioPoolSize() { + return delegate.ioPoolSize(); + } + + @Override + public int computationPoolSize() { + return delegate.computationPoolSize(); + } + + @Override + public Delay observeIntervalDelay() { + return delegate.observeIntervalDelay(); + } + + @Override + public Delay reconnectDelay() { + return delegate.reconnectDelay(); + } + + @Override + public Delay retryDelay() { + return delegate.retryDelay(); + } + + @Override + public int requestBufferSize() { + return delegate.requestBufferSize(); + } + + @Override + public int responseBufferSize() { + return delegate.responseBufferSize(); + } + + @Override + public int kvEndpoints() { + return delegate.kvEndpoints(); + } + + @Override + public int viewEndpoints() { + return delegate.viewEndpoints(); + } + + @Override + public int queryEndpoints() { + return delegate.queryEndpoints(); + } + + @Override + public String userAgent() { + return delegate.userAgent(); + } + + @Override + public String packageNameAndVersion() { + return delegate.packageNameAndVersion(); + } + + @Override + public RetryStrategy retryStrategy() { + return delegate.retryStrategy(); + } + + @Override + public long maxRequestLifetime() { + return delegate.maxRequestLifetime(); + } + + @Override + public long autoreleaseAfter() { + return delegate.autoreleaseAfter(); + } + + @Override + public long keepAliveInterval() { + return delegate.keepAliveInterval(); + } + + @Override + public EventBus eventBus() { + return delegate.eventBus(); + } + + @Override + public boolean bufferPoolingEnabled() { + return delegate.bufferPoolingEnabled(); + } + + @Override + public long managementTimeout() { + return delegate.managementTimeout(); + } + + @Override + public long queryTimeout() { + return delegate.queryTimeout(); + } + + @Override + public long viewTimeout() { + return delegate.viewTimeout(); + } + + @Override + public long kvTimeout() { + return delegate.kvTimeout(); + } + + @Override + public long connectTimeout() { + return delegate.connectTimeout(); + } + + @Override + public long disconnectTimeout() { + return delegate.disconnectTimeout(); + } + + @Override + public boolean dnsSrvEnabled() { + return delegate.dnsSrvEnabled(); + } +}