From 8ddeae76a18347ff99ddf0bd6be5c5b54f8c602d Mon Sep 17 00:00:00 2001 From: costin Date: Tue, 29 Jun 2010 20:58:20 +0300 Subject: [PATCH] + initial commit --- .classpath | 10 + .gitignore | 2 + .project | 29 ++ .settings/org.eclipse.jdt.core.prefs | 13 + .settings/org.maven.ide.eclipse.prefs | 9 + .springBeans | 13 + pom.xml | 86 ++++++ .../data/gemfire/CacheFactoryBean.java | 179 ++++++++++++ .../data/gemfire/ClientRegionFactoryBean.java | 86 ++++++ .../data/gemfire/DeclarableSupport.java | 86 ++++++ .../gemfire/GemfireBeanFactoryLocator.java | 158 +++++++++++ .../data/gemfire/GemfireCacheUtils.java | 263 ++++++++++++++++++ .../gemfire/GemfireCancellationException.java | 33 +++ .../data/gemfire/GemfireIndexException.java | 48 ++++ .../data/gemfire/GemfireQueryException.java | 39 +++ .../data/gemfire/GemfireSystemException.java | 38 +++ .../gemfire/GemfireTransactionManager.java | 257 +++++++++++++++++ .../data/gemfire/Interest.java | 138 +++++++++ .../data/gemfire/RegexInterest.java | 44 +++ .../data/gemfire/RegionFactoryBean.java | 214 ++++++++++++++ .../data/gemfire/WiringDeclarableSupport.java | 66 +++++ .../AsmInstantiatorGenerator.java | 247 ++++++++++++++++ .../gemfire/serialization/EnumSerializer.java | 101 +++++++ .../serialization/InstantiatorGenerator.java | 36 +++ .../serialization/WiringInstantiator.java | 110 ++++++++ .../data/gemfire/CacheIntegrationTest.java | 66 +++++ .../data/gemfire/DeclarableSupportTest.java | 54 ++++ .../GemfireBeanFactoryLocatorTest.java | 137 +++++++++ .../data/gemfire/RegionIntegrationTest.java | 102 +++++++ .../data/gemfire/TxIntegrationTest.java | 88 ++++++ .../data/gemfire/UserObject.java | 73 +++++ .../AsmInstantiatorFactoryTest.java | 90 ++++++ .../serialization/WiringInstantiatorTest.java | 118 ++++++++ src/test/resources/cache-with-declarable.xml | 32 +++ src/test/resources/cache.xml | 39 +++ .../data/gemfire/basic-cache.xml | 31 +++ .../data/gemfire/basic-region.xml | 38 +++ .../data/gemfire/basic-tx-config.xml | 14 + .../gemfire/cache-with-declarable-ctx.xml | 15 + .../data/gemfire/locatorContext.xml | 11 + .../gemfire/serialization/simple-config.xml | 28 ++ 41 files changed, 3241 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.maven.ide.eclipse.prefs create mode 100644 .springBeans create mode 100644 pom.xml create mode 100644 src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java create mode 100644 src/main/java/org/springframework/data/gemfire/ClientRegionFactoryBean.java create mode 100644 src/main/java/org/springframework/data/gemfire/DeclarableSupport.java create mode 100644 src/main/java/org/springframework/data/gemfire/GemfireBeanFactoryLocator.java create mode 100644 src/main/java/org/springframework/data/gemfire/GemfireCacheUtils.java create mode 100644 src/main/java/org/springframework/data/gemfire/GemfireCancellationException.java create mode 100644 src/main/java/org/springframework/data/gemfire/GemfireIndexException.java create mode 100644 src/main/java/org/springframework/data/gemfire/GemfireQueryException.java create mode 100644 src/main/java/org/springframework/data/gemfire/GemfireSystemException.java create mode 100644 src/main/java/org/springframework/data/gemfire/GemfireTransactionManager.java create mode 100644 src/main/java/org/springframework/data/gemfire/Interest.java create mode 100644 src/main/java/org/springframework/data/gemfire/RegexInterest.java create mode 100644 src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java create mode 100644 src/main/java/org/springframework/data/gemfire/WiringDeclarableSupport.java create mode 100644 src/main/java/org/springframework/data/gemfire/serialization/AsmInstantiatorGenerator.java create mode 100644 src/main/java/org/springframework/data/gemfire/serialization/EnumSerializer.java create mode 100644 src/main/java/org/springframework/data/gemfire/serialization/InstantiatorGenerator.java create mode 100644 src/main/java/org/springframework/data/gemfire/serialization/WiringInstantiator.java create mode 100644 src/test/java/org/springframework/data/gemfire/CacheIntegrationTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/DeclarableSupportTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/GemfireBeanFactoryLocatorTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/RegionIntegrationTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/TxIntegrationTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/UserObject.java create mode 100644 src/test/java/org/springframework/data/gemfire/serialization/AsmInstantiatorFactoryTest.java create mode 100644 src/test/java/org/springframework/data/gemfire/serialization/WiringInstantiatorTest.java create mode 100644 src/test/resources/cache-with-declarable.xml create mode 100644 src/test/resources/cache.xml create mode 100644 src/test/resources/org/springframework/data/gemfire/basic-cache.xml create mode 100644 src/test/resources/org/springframework/data/gemfire/basic-region.xml create mode 100644 src/test/resources/org/springframework/data/gemfire/basic-tx-config.xml create mode 100644 src/test/resources/org/springframework/data/gemfire/cache-with-declarable-ctx.xml create mode 100644 src/test/resources/org/springframework/data/gemfire/locatorContext.xml create mode 100644 src/test/resources/org/springframework/data/gemfire/serialization/simple-config.xml diff --git a/.classpath b/.classpath new file mode 100644 index 00000000..438f0fe3 --- /dev/null +++ b/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d44cd35e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +bin diff --git a/.project b/.project new file mode 100644 index 00000000..0f6d6363 --- /dev/null +++ b/.project @@ -0,0 +1,29 @@ + + + spring-gemfire + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.maven.ide.eclipse.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..d1e62708 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,13 @@ +#Sun Jun 06 11:27:13 EEST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/.settings/org.maven.ide.eclipse.prefs b/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 00000000..66669eb4 --- /dev/null +++ b/.settings/org.maven.ide.eclipse.prefs @@ -0,0 +1,9 @@ +#Fri Jun 04 17:37:03 EEST 2010 +activeProfiles= +eclipse.preferences.version=1 +fullBuildGoals=process-test-resources +includeModules=false +resolveWorkspaceProjects=true +resourceFilterGoals=process-resources resources\:testResources +skipCompilerPlugin=true +version=1 diff --git a/.springBeans b/.springBeans new file mode 100644 index 00000000..4dff6476 --- /dev/null +++ b/.springBeans @@ -0,0 +1,13 @@ + + + 1 + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..e4fe3597 --- /dev/null +++ b/pom.xml @@ -0,0 +1,86 @@ + + 4.0.0 + org.springframework.data.gemfire + spring-gemfire + 1.0.0.M1-SNAPSHOT + Gemfire - Spring Framework Integration + + + 3.0.3.RELEASE + 4.7 + + + + + junit + junit + ${junit.version} + test + + + org.springframework + spring-context + ${spring.version} + compile + + + org.springframework + spring-tx + ${spring.version} + compile + + + org.springframework + spring-webmvc + ${spring.version} + compile + + + javax.inject + javax.inject + 1 + test + + + javax.annotation + jsr250-api + 1.0 + test + + + org.springframework + spring-test + ${spring.version} + test + + + + + gemstone + gemfire + 6.0.1 + compile + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + + \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java b/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java new file mode 100644 index 00000000..751b4300 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/CacheFactoryBean.java @@ -0,0 +1,179 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.util.StringUtils; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.CacheClosedException; +import com.gemstone.gemfire.cache.CacheFactory; +import com.gemstone.gemfire.distributed.DistributedMember; +import com.gemstone.gemfire.distributed.DistributedSystem; + +/** + * Factory used for configuring a Gemfire Cache manager. Allows either retrieval of an existing, opened cache + * or the creation of a new one. + * + * @author Costin Leau + */ +public class CacheFactoryBean implements BeanNameAware, BeanFactoryAware, BeanClassLoaderAware, DisposableBean, + InitializingBean, FactoryBean { + + private static final Log log = LogFactory.getLog(CacheFactoryBean.class); + + private Cache cache; + private String name; + private Resource cacheXml; + private Properties properties; + private DistributedSystem system; + private ClassLoader beanClassLoader; + private GemfireBeanFactoryLocator factoryLocator = new GemfireBeanFactoryLocator(); + + private BeanFactory beanFactory; + private String beanName; + + public void afterPropertiesSet() throws Exception { + // initialize locator + factoryLocator.setBeanFactory(beanFactory); + factoryLocator.setBeanName(beanName); + factoryLocator.afterPropertiesSet(); + + Properties cfgProps = mergeProperties(); + system = DistributedSystem.connect(cfgProps); + + DistributedMember member = system.getDistributedMember(); + log.info("Connected to Distributed System ['" + system.getName() + "'=" + member.getId() + "@" + + member.getHost() + "]"); + + // use the bean class loader to load Declarable classes + Thread th = Thread.currentThread(); + ClassLoader oldTCCL = th.getContextClassLoader(); + + try { + th.setContextClassLoader(beanClassLoader); + // first look for open caches + String msg = null; + try { + cache = CacheFactory.getInstance(system); + msg = "Retrieved existing"; + } catch (CacheClosedException ex) { + // fall back to cache creation + cache = CacheFactory.create(system); + msg = "Created"; + } + + log.info(msg + " Gemfire Cache ['" + cache.getName() + "'] v. " + CacheFactory.getVersion()); + + // load/init cache.xml + if (cacheXml != null) { + cache.loadCacheXml(cacheXml.getInputStream()); + } + + if (log.isDebugEnabled()) + log.debug("Initialized cache from " + cacheXml); + } finally { + th.setContextClassLoader(oldTCCL); + } + } + + private Properties mergeProperties() { + Properties cfgProps = new Properties(properties); + if (StringUtils.hasText(name)) { + cfgProps.setProperty("name", name.trim()); + } + return cfgProps; + } + + public void destroy() throws Exception { + if (cache != null && !cache.isClosed()) { + cache.close(); + } + cache = null; + + if (system != null && system.isConnected()) { + system.releaseThreadsSockets(); + system.disconnect(); + } + system = null; + + factoryLocator.destroy(); + } + + public Cache getObject() throws Exception { + return cache; + } + + public Class getObjectType() { + return (cache != null ? cache.getClass() : Cache.class); + } + + public boolean isSingleton() { + return true; + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public void setBeanName(String name) { + this.beanName = name; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + /** + * Sets the cache properties. + * + * @param properties the properties to set + */ + public void setProperties(Properties properties) { + this.properties = properties; + } + + /** + * Sets the name of the cache. + * + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the cache configuration. + * + * @param cacheXml the cacheXml to set + */ + public void setCacheXml(Resource cacheXml) { + this.cacheXml = cacheXml; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/ClientRegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/ClientRegionFactoryBean.java new file mode 100644 index 00000000..0482480a --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/ClientRegionFactoryBean.java @@ -0,0 +1,86 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.util.Assert; + +import com.gemstone.gemfire.cache.Region; + +/** + * Client extension for Gemfire regions. + * + * @author Costin Leau + */ +public class ClientRegionFactoryBean extends RegionFactoryBean { + + private Interest[] interests; + + @Override + protected void postProcess(Region region) { + Assert.notEmpty(interests); + + for (Interest interest : interests) { + if (interest instanceof RegexInterest) { + // do the cast since it's safe + region.registerInterestRegex((String) interest.getKey(), interest.getPolicy(), interest.isDurable()); + } + else { + region.registerInterest(interest.getKey(), interest.getPolicy(), interest.isDurable()); + } + } + } + + @Override + public void destroy() throws Exception { + Region region = getObject(); + // unregister interests + try { + if (region != null) { + for (Interest interest : interests) { + if (interest instanceof RegexInterest) { + region.unregisterInterestRegex((String) interest.getKey()); + } + else { + region.unregisterInterest(interest.getKey()); + } + } + } + // should not really happen since interests are validated at start/registration + } catch (UnsupportedOperationException ex) { + log.warn("Cannot unregister cache interests", ex); + } + + super.destroy(); + } + + + /** + * Set the interests for this client region. Both key and regex interest are supported. + * + * @param interests the interests to set + */ + public void setInterests(Interest[] interests) { + this.interests = interests; + } + + /** + * @return the interests + */ + Interest[] getInterests() { + return interests; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/DeclarableSupport.java b/src/main/java/org/springframework/data/gemfire/DeclarableSupport.java new file mode 100644 index 00000000..7d63e977 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/DeclarableSupport.java @@ -0,0 +1,86 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import java.util.Properties; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.access.BeanFactoryReference; + +import com.gemstone.gemfire.cache.CacheCallback; +import com.gemstone.gemfire.cache.Declarable; + +/** + * Convenience class for Spring-aware GemFire Declarable components. + * Provides a reference to the current Spring application context, e.g. for bean lookup or resource loading. + * + * Note that in most cases, one can just declare the same components as Spring beans, through + * {@link RegionFactoryBean} which gives access to the full container capabilities and does not enforce the + * {@link Declarable} interface to be implemented. + * + * @author Costin Leau + */ +public abstract class DeclarableSupport implements CacheCallback, Declarable { + + private String factoryKey = null; + private BeanFactoryReference bfReference = null; + + public DeclarableSupport() { + } + + /** + * This implementation uses this method as a lifecycle hook to initialize + * the bean factory locator. + * + * {@inheritDoc} + * + * @see #setFactoryKey(String) + */ + public final void init(Properties props) { + bfReference = new GemfireBeanFactoryLocator().useBeanFactory(factoryKey); + initInstance(props); + } + + /** + * Initialize the current instance based on the given properties. + * + * @param props + */ + protected void initInstance(Properties props) { + } + + protected BeanFactory getBeanFactory() { + return bfReference.getFactory(); + } + + public void close() { + bfReference.release(); + bfReference = null; + } + + /** + * Sets the key under which the enclosing beanFactory can be found. + * Needed only if multiple beanFactories are used with GemFire inside + * the same class loader / class space. + * + * @see GemfireBeanFactoryLocator + * @param key + */ + public void setFactoryKey(String key) { + this.factoryKey = key; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/GemfireBeanFactoryLocator.java b/src/main/java/org/springframework/data/gemfire/GemfireBeanFactoryLocator.java new file mode 100644 index 00000000..12ba6ab8 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/GemfireBeanFactoryLocator.java @@ -0,0 +1,158 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.access.BeanFactoryLocator; +import org.springframework.beans.factory.access.BeanFactoryReference; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * {@link BeanFactoryLocator} used for storing Spring application context/bean factory for Gemfire + * user components (or {@link com.gemstone.gemfire.cache.Declarable}. As opposed to the "traditional" + * {@link org.springframework.beans.factory.access.SingletonBeanFactoryLocator} this implementation does + * not require any configuration file; it rather assume declaration inside an application context + * (usually through {@link com.gemstone.gemfire.cache.CacheFactory} which it will store under the name + * and aliases of the bean (so the same "registry" can be used for storing multiple BeanFactories). + * If there is only one BeanFactory registered then a null value can be used with {@link #setBeanName(String)}. + * + *

In most cases, one does not need to use this class directly as it is used internally + * by {@link com.gemstone.gemfire.cache.CacheFactory}. + * + * @author Costin Leau + */ +public class GemfireBeanFactoryLocator implements BeanFactoryLocator, BeanFactoryAware, BeanNameAware, DisposableBean, + InitializingBean { + + private static final Log log = LogFactory.getLog(GemfireBeanFactoryLocator.class); + + // alias/bean name <-> BeanFactory lookup + private static final ConcurrentMap beanFactories = new ConcurrentHashMap(); + + // default factory to return + private static volatile boolean canUseDefaultBeanFactory = true; + private static volatile BeanFactory defaultFactory = null; + + private static class SimpleBeanFactoryReference implements BeanFactoryReference { + + private BeanFactory bf; + + SimpleBeanFactoryReference(BeanFactory bf) { + this.bf = bf; + } + + public BeanFactory getFactory() { + Assert.notNull(bf, "beanFactory already released or closed"); + return bf; + } + + public void release() throws FatalBeanException { + bf = null; + } + } + + private BeanFactory beanFactory; + private String[] names; + + // default factory name + private String factoryName = GemfireBeanFactoryLocator.class.getName(); + + public void afterPropertiesSet() { + // add the factory as default if possible (if it's the only one) + synchronized (GemfireBeanFactoryLocator.class) { + canUseDefaultBeanFactory = beanFactories.isEmpty(); + if (canUseDefaultBeanFactory) { + if (defaultFactory == null) { + defaultFactory = beanFactory; + if (log.isDebugEnabled()) + log.debug("default beanFactoryReference=" + defaultFactory); + } + else { + if (log.isDebugEnabled()) + log.debug("more then one beanFactory - default not possible to determine"); + canUseDefaultBeanFactory = false; + defaultFactory = null; + } + } + } + + // add aliases + if (StringUtils.hasText(factoryName)) { + String[] aliases = beanFactory.getAliases(factoryName); + names = (String[]) ObjectUtils.addObjectToArray(aliases, factoryName); + + for (String name : names) { + if (log.isDebugEnabled()) + log.debug("adding key=" + name + " w/ reference=" + beanFactory); + + if (beanFactories.containsKey(name) || beanFactories.putIfAbsent(name, beanFactory) != null) { + throw new IllegalArgumentException("a beanFactoryReference already exists for key " + factoryName); + } + } + } + } + + public void destroy() { + for (String name : names) { + beanFactories.remove(name); + } + + if (beanFactory == defaultFactory) { + defaultFactory = null; + } + } + + public BeanFactoryReference useBeanFactory(final String factoryKey) throws BeansException { + // see if there is a default FactoryBean + BeanFactory factory; + + if (!StringUtils.hasText(factoryKey)) { + if (!canUseDefaultBeanFactory) + throw new IllegalArgumentException( + "a non-null factoryKey needs to be specified as there are more then one factoryKeys available "); + factory = defaultFactory; + } + else { + factory = beanFactories.get(factoryKey); + if (factory == null) + throw new IllegalArgumentException("there is no beanFactory under key " + factoryKey); + } + + return new SimpleBeanFactoryReference(factory); + } + + public void setBeanName(String name) { + factoryName = name; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/GemfireCacheUtils.java b/src/main/java/org/springframework/data/gemfire/GemfireCacheUtils.java new file mode 100644 index 00000000..7d3671c7 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/GemfireCacheUtils.java @@ -0,0 +1,263 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DataRetrievalFailureException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.dao.PermissionDeniedDataAccessException; +import org.springframework.dao.PessimisticLockingFailureException; +import org.springframework.dao.TypeMismatchDataAccessException; + +import com.gemstone.gemfire.CancelException; +import com.gemstone.gemfire.CopyException; +import com.gemstone.gemfire.GemFireCacheException; +import com.gemstone.gemfire.GemFireCheckedException; +import com.gemstone.gemfire.GemFireConfigException; +import com.gemstone.gemfire.GemFireException; +import com.gemstone.gemfire.GemFireIOException; +import com.gemstone.gemfire.IncompatibleSystemException; +import com.gemstone.gemfire.InternalGemFireException; +import com.gemstone.gemfire.InvalidValueException; +import com.gemstone.gemfire.LicenseException; +import com.gemstone.gemfire.NoSystemException; +import com.gemstone.gemfire.SystemConnectException; +import com.gemstone.gemfire.SystemIsRunningException; +import com.gemstone.gemfire.UnmodifiableException; +import com.gemstone.gemfire.admin.AdminException; +import com.gemstone.gemfire.admin.RegionNotFoundException; +import com.gemstone.gemfire.admin.RuntimeAdminException; +import com.gemstone.gemfire.cache.CacheException; +import com.gemstone.gemfire.cache.CacheExistsException; +import com.gemstone.gemfire.cache.CacheLoaderException; +import com.gemstone.gemfire.cache.CacheRuntimeException; +import com.gemstone.gemfire.cache.CacheWriterException; +import com.gemstone.gemfire.cache.CacheXmlException; +import com.gemstone.gemfire.cache.CommitConflictException; +import com.gemstone.gemfire.cache.CommitIncompleteException; +import com.gemstone.gemfire.cache.DiskAccessException; +import com.gemstone.gemfire.cache.EntryDestroyedException; +import com.gemstone.gemfire.cache.EntryExistsException; +import com.gemstone.gemfire.cache.EntryNotFoundException; +import com.gemstone.gemfire.cache.EntryNotFoundInRegion; +import com.gemstone.gemfire.cache.FailedSynchronizationException; +import com.gemstone.gemfire.cache.OperationAbortedException; +import com.gemstone.gemfire.cache.PartitionedRegionDistributionException; +import com.gemstone.gemfire.cache.PartitionedRegionStorageException; +import com.gemstone.gemfire.cache.RegionDestroyedException; +import com.gemstone.gemfire.cache.RegionExistsException; +import com.gemstone.gemfire.cache.ResourceException; +import com.gemstone.gemfire.cache.RoleException; +import com.gemstone.gemfire.cache.StatisticsDisabledException; +import com.gemstone.gemfire.cache.SynchronizationCommitConflictException; +import com.gemstone.gemfire.cache.VersionException; +import com.gemstone.gemfire.cache.client.ServerConnectivityException; +import com.gemstone.gemfire.cache.execute.FunctionException; +import com.gemstone.gemfire.cache.query.CqClosedException; +import com.gemstone.gemfire.cache.query.IndexMaintenanceException; +import com.gemstone.gemfire.cache.query.QueryException; +import com.gemstone.gemfire.cache.query.QueryExecutionTimeoutException; +import com.gemstone.gemfire.distributed.LeaseExpiredException; +import com.gemstone.gemfire.security.GemFireSecurityException; + +/** + * Helper class featuring methods for GemFire Cache or Region handling. + * + * @author Costin Leau + */ +public abstract class GemfireCacheUtils { + + /** + * Converts the given (unchecked) Gemfire exception to an appropriate one from the + * org.springframework.dao hierarchy. + * + * @param ex Gemfire unchecked exception + * @return new the corresponding DataAccessException instance + */ + public static DataAccessException convertGemfireAccessException(GemFireException ex) { + if (ex instanceof CacheException) { + if (ex instanceof CacheExistsException) { + return new DataIntegrityViolationException(ex.getMessage(), ex); + } + if (ex instanceof CommitConflictException) { + return new DataIntegrityViolationException(ex.getMessage(), ex); + } + if (ex instanceof CommitIncompleteException) { + return new DataIntegrityViolationException(ex.getMessage(), ex); + } + if (ex instanceof EntryExistsException) { + return new DuplicateKeyException(ex.getMessage(), ex); + } + if (ex instanceof EntryNotFoundException) { + return new DataRetrievalFailureException(ex.getMessage(), ex); + } + if (ex instanceof RegionExistsException) { + return new DataIntegrityViolationException(ex.getMessage(), ex); + } + } + if (ex instanceof CacheRuntimeException) { + if (ex instanceof CacheXmlException) { + return new GemfireSystemException(ex); + } + if (ex instanceof CancelException) { + // all cancellations go wrapped by this exception + return new GemfireCancellationException((CancelException) ex); + } + if (ex instanceof CqClosedException) { + return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } + if (ex instanceof DiskAccessException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof EntryDestroyedException) { + return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } + if (ex instanceof FailedSynchronizationException) { + return new PessimisticLockingFailureException(ex.getMessage(), ex); + } + if (ex instanceof IndexMaintenanceException) { + return new GemfireIndexException((IndexMaintenanceException) ex); + } + if (ex instanceof OperationAbortedException) { + // treat user exceptions first + if (ex instanceof CacheLoaderException) { + return new GemfireSystemException(ex); + } + if (ex instanceof CacheWriterException) { + return new GemfireSystemException(ex); + } + // the rest are treated as resource failures + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof PartitionedRegionDistributionException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof PartitionedRegionStorageException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof QueryExecutionTimeoutException) { + return new GemfireQueryException((QueryExecutionTimeoutException) ex); + } + if (ex instanceof RegionDestroyedException) { + return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex); + } + if (ex instanceof RegionNotFoundException) { + return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex); + } + if (ex instanceof ResourceException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof RoleException) { + return new GemfireSystemException(ex); + } + if (ex instanceof StatisticsDisabledException) { + return new GemfireSystemException(ex); + } + if (ex instanceof SynchronizationCommitConflictException) { + return new PessimisticLockingFailureException(ex.getMessage(), ex); + } + } + if (ex instanceof CopyException) { + return new GemfireSystemException(ex); + } + if (ex instanceof EntryNotFoundInRegion) { + return new DataRetrievalFailureException(ex.getMessage(), ex); + } + if (ex instanceof FunctionException) { + return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } + if (ex instanceof GemFireCacheException) { + return convertGemfireAccessException(((GemFireCacheException) ex).getCacheException()); + } + if (ex instanceof GemFireConfigException) { + return new GemfireSystemException(ex); + } + if (ex instanceof GemFireIOException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof GemFireSecurityException) { + return new PermissionDeniedDataAccessException(ex.getMessage(), ex); + } + if (ex instanceof IncompatibleSystemException) { + return new GemfireSystemException(ex); + } + if (ex instanceof InternalGemFireException) { + return new GemfireSystemException(ex); + } + if (ex instanceof InvalidValueException) { + return new TypeMismatchDataAccessException(ex.getMessage(), ex); + } + if (ex instanceof LeaseExpiredException) { + return new PessimisticLockingFailureException(ex.getMessage(), ex); + } + if (ex instanceof LicenseException) { + return new GemfireSystemException(ex); + } + if (ex instanceof NoSystemException) { + return new GemfireSystemException(ex); + } + if (ex instanceof RuntimeAdminException) { + return new GemfireSystemException(ex); + } + if (ex instanceof ServerConnectivityException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof SystemConnectException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + if (ex instanceof SystemIsRunningException) { + return new GemfireSystemException(ex); + } + if (ex instanceof UnmodifiableException) { + return new GemfireSystemException(ex); + } + // fall back + return new GemfireSystemException(ex); + } + + /** + * Converts the given (checked) Gemfire exception to an appropriate one from the + * org.springframework.dao hierarchy. + * + * @param ex Gemfire unchecked exception + * @return new the corresponding DataAccessException instance + */ + public static DataAccessException convertGemfireAccessException(GemFireCheckedException ex) { + // query exceptions + if (ex instanceof QueryException) { + return new GemfireQueryException((QueryException) ex); + } + // version exception + if (ex instanceof VersionException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + // util.version exception (seems quite similar to the exception above) + if (ex instanceof com.gemstone.gemfire.cache.util.VersionException) { + return new DataAccessResourceFailureException(ex.getMessage(), ex); + } + // admin exception + if (ex instanceof AdminException) { + return new GemfireSystemException(ex); + } + // fall back + return new GemfireSystemException(ex); + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/GemfireCancellationException.java b/src/main/java/org/springframework/data/gemfire/GemfireCancellationException.java new file mode 100644 index 00000000..b807240b --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/GemfireCancellationException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +import com.gemstone.gemfire.CancelException; + +/** + * GemFire-specific class for exceptions caused by system cancellations. + * + * @author Costin Leau + */ +public class GemfireCancellationException extends InvalidDataAccessResourceUsageException { + + public GemfireCancellationException(CancelException ex) { + super(ex.getMessage(), ex); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java b/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java new file mode 100644 index 00000000..57badca1 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/GemfireIndexException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.dao.DataIntegrityViolationException; + +import com.gemstone.gemfire.cache.query.IndexCreationException; +import com.gemstone.gemfire.cache.query.IndexExistsException; +import com.gemstone.gemfire.cache.query.IndexMaintenanceException; +import com.gemstone.gemfire.cache.query.IndexNameConflictException; + +/** + * Gemfire-specific subclass thrown on index creation. + * + * @author Costin Leau + */ +public class GemfireIndexException extends DataIntegrityViolationException { + + public GemfireIndexException(IndexCreationException ex) { + super(ex.getMessage(), ex); + } + + public GemfireIndexException(IndexExistsException ex) { + super(ex.getMessage(), ex); + } + + public GemfireIndexException(IndexNameConflictException ex) { + super(ex.getMessage(), ex); + } + + public GemfireIndexException(IndexMaintenanceException ex) { + super(ex.getMessage(), ex); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/GemfireQueryException.java b/src/main/java/org/springframework/data/gemfire/GemfireQueryException.java new file mode 100644 index 00000000..8673bf40 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/GemfireQueryException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.dao.InvalidDataAccessResourceUsageException; + +import com.gemstone.gemfire.cache.query.QueryException; +import com.gemstone.gemfire.cache.query.QueryExecutionTimeoutException; + +/** + * GemFire-specific subclass of {@link InvalidDataAccessResourceUsageException} thrown on invalid + * OQL query syntax. + * + * @author Costin Leau + */ +public class GemfireQueryException extends InvalidDataAccessResourceUsageException { + + public GemfireQueryException(QueryException ex) { + super(ex.getMessage(), ex); + } + + public GemfireQueryException(QueryExecutionTimeoutException ex) { + super(ex.getMessage(), ex); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/GemfireSystemException.java b/src/main/java/org/springframework/data/gemfire/GemfireSystemException.java new file mode 100644 index 00000000..246081f8 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/GemfireSystemException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.dao.UncategorizedDataAccessException; + +import com.gemstone.gemfire.GemFireCheckedException; +import com.gemstone.gemfire.GemFireException; + +/** + * GemFire-specific subclass of UncategorizedDataAccessException, for GemFire system errors that do not match any concrete org.springframework.dao exceptions. + * + * @author Costin Leau + */ +public class GemfireSystemException extends UncategorizedDataAccessException { + + public GemfireSystemException(GemFireCheckedException ex) { + super(ex.getMessage(), ex); + } + + public GemfireSystemException(GemFireException ex) { + super(ex.getMessage(), ex); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/GemfireTransactionManager.java b/src/main/java/org/springframework/data/gemfire/GemfireTransactionManager.java new file mode 100644 index 00000000..1da19ef6 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/GemfireTransactionManager.java @@ -0,0 +1,257 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.transaction.NoTransactionException; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.support.AbstractPlatformTransactionManager; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.ResourceTransactionManager; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.CacheTransactionManager; +import com.gemstone.gemfire.cache.CommitConflictException; +import com.gemstone.gemfire.cache.Region; + +/** + * Local transaction manager for Gemfire Enterprise Fabric (GEF). Provides a {@link PlatformTransactionManager} + * implementation for a single Gemfire {@link CacheTransactionManager}. + * + * Binds one or multiple Gemfire regions for the specified {@link Cache} to the thread, potentially allowing for one + * region per cache model. + * + *

+ * This local strategy is an alternative to executing cache operations within JTA transactions. Its advantage is that + * is able to work in any environment, for example a standalone application or a test suite. It is not able to + * provide XA transactions, for example to share transactions with data access. + * + *

+ * To prevent dirty reads, by default, the cache is configured to return copies rather then direct references for + * get operations. As a workaround, one could use explicitly deep copy objects before making changes + * to them to avoid unnecessary copying on every fetch. + * + * @see com.gemstone.gemfire.cache.CacheTransactionManager + * @see com.gemstone.gemfire.cache.Cache#setCopyOnRead(boolean) + * @see com.gemstone.gemfire.cache.Region#get(Object) + * @see com.gemstone.gemfire.CopyHelper#copy(Object) + * @see #setCopyOnRead(boolean) + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager + * + * @author Costin Leau + */ +// TODO add lenient behavior if a transaction is already started on the current thread (what should happen then) +public class GemfireTransactionManager extends AbstractPlatformTransactionManager implements InitializingBean, + ResourceTransactionManager { + + private Cache cache; + private boolean copyOnRead = true; + + /** + * Creates a new GemfireTransactionManager instance. + */ + public GemfireTransactionManager() { + } + + /** + * Creates a new GemfireTransactionManager instance. + * + * @param cache + */ + public GemfireTransactionManager(Cache cache) { + this.cache = cache; + afterPropertiesSet(); + } + + public void afterPropertiesSet() { + Assert.notNull(cache, "Cache property is required"); + cache.setCopyOnRead(copyOnRead); + } + + @Override + protected Object doGetTransaction() throws TransactionException { + CacheTransactionObject txObject = new CacheTransactionObject(); + CacheHolder cacheHolder = (CacheHolder) TransactionSynchronizationManager.getResource(getCache()); + txObject.setHolder(cacheHolder); + return txObject; + } + + protected boolean isExistingTransaction(Object transaction) throws TransactionException { + CacheTransactionObject txObject = (CacheTransactionObject) transaction; + // Consider a pre-bound cache as transaction. + return (txObject.getHolder() != null); + } + + @Override + protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { + CacheTransactionObject txObject = (CacheTransactionObject) transaction; + + Cache cache = null; + + try { + cache = getCache(); + if (logger.isDebugEnabled()) { + logger.debug("Acquired Cache [" + cache + "] for local Cache transaction"); + } + + txObject.setHolder(new CacheHolder()); + cache.getCacheTransactionManager().begin(); + TransactionSynchronizationManager.bindResource(cache, txObject.getHolder()); + } + + catch (IllegalStateException ex) { + throw new CannotCreateTransactionException( + "An ongoing transaction already is already associated with the current thread; are there multiple transaction managers ?", + ex); + } + } + + @Override + protected void doCommit(DefaultTransactionStatus status) throws TransactionException { + CacheTransactionObject txObject = (CacheTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Committing Gemfire local transaction on Cache [" + cache + "]"); + } + try { + cache.getCacheTransactionManager().commit(); + } catch (IllegalStateException ex) { + throw new NoTransactionException( + "No transaction associated with the current thread; are there multiple transaction managers ?", ex); + } catch (CommitConflictException ex) { + // TODO: can this be replaced with HeuristicCompletionException ? + throw new TransactionSystemException("Unexpected failure on commit of Cache local transaction", ex); + } + } + + @Override + protected void doRollback(DefaultTransactionStatus status) throws TransactionException { + CacheTransactionObject txObject = (CacheTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Rolling back Cache local transaction for [" + cache + "]"); + } + try { + cache.getCacheTransactionManager().rollback(); + } catch (IllegalStateException ex) { + throw new NoTransactionException( + "No transaction associated with the current thread; are there multiple transaction managers ?", ex); + } + } + + @Override + protected void doSetRollbackOnly(DefaultTransactionStatus status) { + CacheTransactionObject txObject = (CacheTransactionObject) status.getTransaction(); + if (status.isDebug()) { + logger.debug("Setting Gemfire local transaction [" + txObject.getHolder() + "] rollback-only"); + } + txObject.getHolder().setRollbackOnly(); + } + + protected void doCleanupAfterCompletion(Object transaction) { + // Remove the cache holder from the thread. + TransactionSynchronizationManager.unbindResource(cache); + } + + @Override + protected final boolean useSavepointForNestedTransaction() { + return false; + } + + /** + * Returns the Cache that this instance manages local transactions for. + * + * @return Gemfire cache + */ + public Cache getCache() { + return cache; + } + + /** + * Sets the Cache that this instance manages local transactions for. + * + * @param cache Gemfire cache + */ + public void setCache(Cache cache) { + this.cache = cache; + } + + public Object getResourceFactory() { + return getCache(); + } + + /** + * Sets the Gemfire {@link Region} (as an alternative in setting in the cache directly). + * + * @param region Gemfire region + */ + public void setRegion(Region region) { + Assert.notNull(region, "non-null arguments are required"); + this.cache = region.getCache(); + } + + /** + * Indicates whether the cache returns direct references or copies of the objects (default) it manages. + * While copies imply additional work for every fetch operation, direct references can cause dirty reads + * across concurrent threads in the same VM, whether or not transactions are used. + *

+ * One could explicitly deep copy objects before making changes (for example by using {@link com.gemstone.gemfire.CopyHelper#copy(Object)} + * in which case this setting can be set to false. However, unless there is a measurable + * performance penalty, the recommendation is to keep this setting to true + * + * @param copyOnRead whether copies (default) rather then direct references will be returned on + * fetch operations + */ + public void setCopyOnRead(boolean copyOnRead) { + this.copyOnRead = copyOnRead; + } + + /** + * GemfireTM local transaction object. + * + * @author Costin Leau + */ + private static class CacheTransactionObject { + private CacheHolder cacheHolder; + + public CacheHolder getHolder() { + return cacheHolder; + } + + public void setHolder(CacheHolder cacheHolder) { + this.cacheHolder = cacheHolder; + } + } + + private static class CacheHolder { + + private boolean rollbackOnly = false; + + + public boolean isRollbackOnly() { + return rollbackOnly; + } + + public void setRollbackOnly() { + rollbackOnly = true; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/Interest.java b/src/main/java/org/springframework/data/gemfire/Interest.java new file mode 100644 index 00000000..49eab8ad --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/Interest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.Constants; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.cache.InterestResultPolicy; + +/** + * Basic holder class for registering an interest. Useful for configuring Gemfire caches through XML + * and or JavaBeans means. + * + * @author Costin Leau + */ +public class Interest implements InitializingBean { + + private static final Constants constants = new Constants(InterestResultPolicy.class); + + private K key; + private InterestResultPolicy policy = InterestResultPolicy.DEFAULT; + private boolean durable = false; + + public Interest() { + } + + public Interest(K key) { + this(key, InterestResultPolicy.DEFAULT, false); + } + + public Interest(K key, InterestResultPolicy policy) { + this(key, policy, false); + } + + public Interest(K key, String policy) { + this(key, policy, false); + } + + public Interest(K key, String policy, boolean durable) { + this.key = key; + this.policy = (InterestResultPolicy) constants.asObject(policy); + this.durable = durable; + afterPropertiesSet(); + } + + public Interest(K key, InterestResultPolicy policy, boolean durable) { + this.key = key; + this.policy = policy; + this.durable = durable; + afterPropertiesSet(); + } + + + public void afterPropertiesSet() { + Assert.notNull(key, "a non-null key is required"); + } + + /** + * Returns the key of interest. + * + * @return the key + */ + protected K getKey() { + return key; + } + + /** + * Sets the key of interest. + * + * @param key the key to set + */ + public void setKey(K key) { + this.key = key; + } + + /** + * Returns the interest policy. + * + * @return the policy + */ + protected InterestResultPolicy getPolicy() { + return policy; + } + + /** + * Sets the interest policy. The argument is set as an Object + * to be able to accept both InterestResultType instances but also + * Strings (for XML configurations). + * + * @param policy the policy to set + */ + public void setPolicy(Object policy) { + if (policy instanceof InterestResultPolicy) { + this.policy = (InterestResultPolicy) policy; + } + else { + if (policy instanceof String) { + this.policy = (InterestResultPolicy) constants.asObject((String) policy); + } + else { + throw new IllegalArgumentException("Unknown argument type for property 'policy'" + policy); + } + } + } + + /** + * Returns the interest durability. + * + * @return the durable + */ + protected boolean isDurable() { + return durable; + } + + /** + * Sets the interest durability. + * + * @param durable the durable to set + */ + public void setDurable(boolean durable) { + this.durable = durable; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/RegexInterest.java b/src/main/java/org/springframework/data/gemfire/RegexInterest.java new file mode 100644 index 00000000..e01f448f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/RegexInterest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.util.Assert; + +/** + * Cache interest based on regular expression rather then individual key types. + * + * @author Costin Leau + */ +public class RegexInterest extends Interest { + + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + Assert.hasText(getKey(), "A non-empty regex is required"); + } + + /** + * Returns the regex backing this interest. + * Similar to {@link #getKey()}. + * + * @return interest regex + */ + //TODO: is this really required + public String getRegex() { + return getKey(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java b/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java new file mode 100644 index 00000000..8e55d310 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/RegionFactoryBean.java @@ -0,0 +1,214 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +import com.gemstone.gemfire.cache.AttributesFactory; +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.CacheListener; +import com.gemstone.gemfire.cache.CacheLoader; +import com.gemstone.gemfire.cache.CacheWriter; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; + +/** + * FactoryBean for creating generic Gemfire {@link Region}s. + * + * @author Costin Leau + */ +public class RegionFactoryBean implements DisposableBean, FactoryBean>, InitializingBean { + + protected final Log log = LogFactory.getLog(getClass()); + + private Cache cache; + private String name; + private boolean destroy = false; + private Resource snapshot; + + private CacheListener cacheListeners[]; + private CacheLoader cacheLoader; + private CacheWriter cacheWriter; + + private RegionAttributes attributes; + + private Region region; + + public void afterPropertiesSet() throws Exception { + Assert.notNull(cache, "Cache property must be set"); + Assert.hasText(name, "Name property must be set"); + + // first get cache + region = cache.getRegion(name); + if (region != null) { + log.info("Retrieved region [" + name + "] from cache"); + } + // fall back to cache creation if one is not found + else { + if (attributes != null) + AttributesFactory.validateAttributes(attributes); + + AttributesFactory attrFactory = (attributes != null ? new AttributesFactory(attributes) + : new AttributesFactory()); + if (!ObjectUtils.isEmpty(cacheListeners)) { + for (CacheListener listener : cacheListeners) { + attrFactory.addCacheListener(listener); + } + } + + if (cacheLoader != null) { + attrFactory.setCacheLoader(cacheLoader); + } + + if (cacheWriter != null) { + attrFactory.setCacheWriter(cacheWriter); + } + + region = cache.createRegion(name, attrFactory.create()); + log.info("Created new cache region [" + name + "]"); + if (snapshot != null) { + region.loadSnapshot(snapshot.getInputStream()); + } + } + + postProcess(region); + } + + /** + * Post-process the region object for this factory bean during the initialization process. + * The object is already initialized and configured by the factory bean before this method + * is invoked. + * + * @param region + */ + protected void postProcess(Region region) { + // do nothing + } + + public void destroy() throws Exception { + if (region != null && destroy) { + region.destroyRegion(); + } + region = null; + } + + public Region getObject() throws Exception { + return region; + } + + public Class getObjectType() { + return (region != null ? region.getClass() : Region.class); + } + + public boolean isSingleton() { + return true; + } + + /** + * Sets the cache used for creating the region. + * + * @see org.springframework.data.gemfire.CacheFactoryBean + * @param cache the cache to set + */ + public void setCache(Cache cache) { + this.cache = cache; + } + + /** + * Sets the name of the cache region. If no cache is found under + * the given name, a new one will be created. + * + * @see com.gemstone.gemfire.cache.Region#getFullPath() + * + * @param name the region name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Indicates whether the region referred by this factory bean, + * will be destroyed on shutdown (default false). + * + * @param destroy the destroy to set + */ + public void setDestroy(boolean destroy) { + this.destroy = destroy; + } + + /** + * Sets the snapshots used for loading a newly created region. + * That is, the snapshot will be used only when a new region is created - if the region + * already exists, no loading will be performed. + * + * @see #setName(String) + * @param snapshot the snapshot to set + */ + public void setSnapshot(Resource snapshot) { + this.snapshot = snapshot; + } + + /** + * Sets the cache listeners used for the region used by this factory. + * Used only when a new region is created.Overrides the settings + * specified through {@link #setAttributes(RegionAttributes)}. + * + * @param cacheListeners the cacheListeners to set on a newly created region + */ + public void setCacheListeners(CacheListener[] cacheListeners) { + this.cacheListeners = cacheListeners; + } + + /** + * Sets the cache loader used for the region used by this factory. + * Used only when a new region is created.Overrides the settings + * specified through {@link #setAttributes(RegionAttributes)}. + * + * @param cacheLoader the cacheLoader to set on a newly created region + */ + public void setCacheLoader(CacheLoader cacheLoader) { + this.cacheLoader = cacheLoader; + } + + /** + * Sets the cache loader used for the region used by this factory. + * Used only when a new region is created. Overrides the settings + * specified through {@link #setAttributes(RegionAttributes)}. + * + * @param cacheWriter the cacheWriter to set on a newly created region + */ + public void setCacheWriter(CacheWriter cacheWriter) { + this.cacheWriter = cacheWriter; + } + + /** + * Sets the cache loader used for the region used by this factory. + * Used only when a new region is created. + * + * @param attributes the attributes to set on a newly created region + */ + public void setAttributes(RegionAttributes attributes) { + this.attributes = attributes; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/WiringDeclarableSupport.java b/src/main/java/org/springframework/data/gemfire/WiringDeclarableSupport.java new file mode 100644 index 00000000..c94b229d --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/WiringDeclarableSupport.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import java.util.Properties; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.wiring.BeanConfigurerSupport; +import org.springframework.beans.factory.wiring.BeanWiringInfo; +import org.springframework.beans.factory.wiring.BeanWiringInfoResolver; + +import com.gemstone.gemfire.cache.Declarable; + +/** + * Dedicated {@link Declarable} support class for wiring the declaring instance through + * the Spring container. + * This implementation will first look for a 'bean-name' property which will be used to + * locate a 'template' bean definition. In case the property is not given, a bean named + * after the class will be searched and if none is found, autowiring will be performed, + * based on the settings defined in the Spring container. + * + * @author Costin Leau + */ +public class WiringDeclarableSupport extends DeclarableSupport { + + private final String BEAN_NAME_PROP = "bean-name"; + + @Override + protected void initInstance(Properties props) { + BeanFactory bf = getBeanFactory(); + BeanConfigurerSupport configurer = new BeanConfigurerSupport(); + configurer.setBeanFactory(bf); + + final String beanName = props.getProperty(BEAN_NAME_PROP); + // key specified, search for a bean + if (beanName != null) { + if (!bf.containsBean(beanName)) { + throw new IllegalArgumentException("Cannot find bean named '" + beanName + "'"); + } + configurer.setBeanWiringInfoResolver(new BeanWiringInfoResolver() { + + public BeanWiringInfo resolveWiringInfo(Object beanInstance) { + return new BeanWiringInfo(beanName); + } + }); + } + + configurer.afterPropertiesSet(); + configurer.configureBean(this); + configurer.destroy(); + } +} diff --git a/src/main/java/org/springframework/data/gemfire/serialization/AsmInstantiatorGenerator.java b/src/main/java/org/springframework/data/gemfire/serialization/AsmInstantiatorGenerator.java new file mode 100644 index 00000000..cd9ed0cf --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/serialization/AsmInstantiatorGenerator.java @@ -0,0 +1,247 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire.serialization; + +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.springframework.asm.ClassWriter; +import org.springframework.asm.FieldVisitor; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.Type; +import org.springframework.beans.BeanUtils; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.DataSerializable; +import com.gemstone.gemfire.Instantiator; + +/** + * ASM based {@link InstantiatorFactory} implementation. This class relies on ASM 2.x package + * repacked by Spring framework to minimize the number of dependencies and avoid any versioning + * confusion. + * + * @author Costin Leau + */ +public class AsmInstantiatorGenerator implements InstantiatorGenerator, Opcodes { + + private static final String PKG = "org/springextensions/gef/serialization"; + private static final String CLASS_LABEL = "Instantiator$Synthetic"; + private static final String INSTANTIATOR_NAME = Type.getInternalName(Instantiator.class); + private static final String SERIALIZABLE_NAME = Type.getInternalName(Serializable.class); + private static final String CLASS_DESCRIPTOR = Type.getDescriptor(Class.class); + private static final String CLASS_FIELD_NAME = "clazz"; + private static final String ID_FIELD_NAME = "classId"; + + private static final String INIT = ""; + private static final String CINIT = ""; + private static final String NEW_INSTANCE = "newInstance"; + private static final String NEW_INSTANCE_DESC = Type.getMethodDescriptor(Type.getType(DataSerializable.class), + new Type[] {}); + + // generated class counter + private static final AtomicLong counter = new AtomicLong(1); + + // class cache + private final ConcurrentMap, Instantiator> cache = new ConcurrentHashMap, Instantiator>(); + + + private static final class BytecodeClassLoader extends ClassLoader { + + public BytecodeClassLoader(ClassLoader loader) { + super(loader); + } + + public Class loadClass(String name, byte[] bytecode) { + return defineClass(name, bytecode, 0, bytecode.length); + } + } + + private final BytecodeClassLoader classLoader; + + public AsmInstantiatorGenerator() { + this(AsmInstantiatorGenerator.class.getClassLoader()); + } + + public AsmInstantiatorGenerator(ClassLoader classLoader) { + Assert.notNull(classLoader); + this.classLoader = new BytecodeClassLoader(classLoader); + } + + public Instantiator getInstantiator(Class clazz, int classId) { + Instantiator instantiator = cache.get(clazz); + if (instantiator == null) { + synchronized (cache) { + instantiator = cache.get(clazz); + if (instantiator == null) { + // create Instantiator + instantiator = createInstantiator(clazz, classId); + cache.putIfAbsent(clazz, instantiator); + } + } + } + return instantiator; + } + + /** + * Returns an instance of the custom instantiator created for the given class. + * + * @param clazz + * @param classId + * @return + */ + private Instantiator createInstantiator(Class clazz, int classId) { + validateClass(clazz); + Class clz = createCustomInstantiatorClass(clazz, classId); + return (Instantiator) BeanUtils.instantiate(clz); + } + + /** + * Does basic sanity checks to make sure the constructor can be properly invoked by our generated + * class. + * + * @param clazz + */ + private void validateClass(Class clazz) { + Assert.isTrue(!Modifier.isAbstract(clazz.getModifiers()), "Cannot instantiate abstract classes"); + Assert.isTrue(Modifier.isPublic(clazz.getModifiers()), "Only public classes are supported"); + try { + Constructor ctor = clazz.getConstructor(); + Assert.isTrue(Modifier.isPublic(ctor.getModifiers()), "Default constructor is not public"); + + } catch (Exception ex) { + throw new IllegalArgumentException("Class " + clazz + " unsuitable for instantiation", ex); + } + } + + /** + * Generates a new Instantiator class for the given custom class. + * + * The generated class has the following definition: + * + *

+	 * package org.springframework.data.gemfire.serialization;
+	 * 
+	 * public class <T>Instantiator$SyntheticCounter extends Instantiator implements Serializable {
+	 *
+	 *  private static final Class<T> clazz = T.class;
+	 *  private static final int classId = value;
+	 *
+	 *  public DateInstantiator() {
+	 *     this(clazz, classId);
+	 *  }
+	 *	
+	 *  public DateInstantiator(Class c, int classId) {
+	 *     super(c, classId);
+	 *  }
+	 *
+	 *  public T newInstance() {
+	 *     return new T();
+	 *  }
+	 * }
+	 * 
+ * + * @param clazz + * @return + */ + Class createCustomInstantiatorClass(Class clazz, int classId) { + String classInternalName = PKG + clazz.getSimpleName() + CLASS_LABEL + counter.getAndIncrement(); + byte[] bytecode = generateClassBytecode(classInternalName, clazz, classId); + // translate internal name to binary form + return classLoader.loadClass(classInternalName.replace('/', '.'), bytecode); + } + + byte[] generateClassBytecode(String className, Class clazz, int classId) { + ClassWriter cw = new ClassWriter(false); + + cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, className, null, INSTANTIATOR_NAME, new String[] { SERIALIZABLE_NAME }); + FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, CLASS_FIELD_NAME, CLASS_DESCRIPTOR, null, + null); + fv.visitEnd(); + fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, ID_FIELD_NAME, Type.INT_TYPE.getDescriptor(), null, + Integer.valueOf(classId)); + fv.visitEnd(); + + String voidNoArgMethodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}); + + // field class loading + MethodVisitor mv = cw.visitMethod(ACC_STATIC, CINIT, voidNoArgMethodDescriptor, null, null); + mv.visitCode(); + mv.visitLdcInsn(Type.getType(clazz)); + mv.visitFieldInsn(PUTSTATIC, className, CLASS_FIELD_NAME, CLASS_DESCRIPTOR); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 0); + mv.visitEnd(); + + String voidArgClassAndIntDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { + Type.getType(Class.class), Type.INT_TYPE }); + + // default constructor + mv = cw.visitMethod(ACC_PUBLIC, INIT, voidNoArgMethodDescriptor, null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETSTATIC, className, CLASS_FIELD_NAME, CLASS_DESCRIPTOR); + mv.visitFieldInsn(GETSTATIC, className, ID_FIELD_NAME, Type.INT_TYPE.getDescriptor()); + mv.visitMethodInsn(INVOKESPECIAL, className, INIT, voidArgClassAndIntDescriptor); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 1); + mv.visitEnd(); + + // two-arg constructor + mv = cw.visitMethod(ACC_PUBLIC, INIT, voidArgClassAndIntDescriptor, null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ILOAD, 2); + mv.visitMethodInsn(INVOKESPECIAL, INSTANTIATOR_NAME, INIT, voidArgClassAndIntDescriptor); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + + Type customClassType = Type.getType(clazz); + String customTypeNoArgDesc = Type.getMethodDescriptor(customClassType, new Type[] {}); + + // newInstance overloaded method + mv = cw.visitMethod(ACC_PUBLIC, NEW_INSTANCE, customTypeNoArgDesc, null, null); + + mv.visitCode(); + mv.visitTypeInsn(NEW, customClassType.getInternalName()); + mv.visitInsn(DUP); + mv.visitMethodInsn(INVOKESPECIAL, customClassType.getInternalName(), INIT, voidNoArgMethodDescriptor); + mv.visitInsn(ARETURN); + mv.visitMaxs(2, 1); + mv.visitEnd(); + + // plus original method signature + mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, NEW_INSTANCE, NEW_INSTANCE_DESC, null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, className, NEW_INSTANCE, customTypeNoArgDesc); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + + // end class generation + cw.visitEnd(); + + return cw.toByteArray(); + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/serialization/EnumSerializer.java b/src/main/java/org/springframework/data/gemfire/serialization/EnumSerializer.java new file mode 100644 index 00000000..fdb6fb0f --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/serialization/EnumSerializer.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire.serialization; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.gemstone.gemfire.DataSerializer; +import com.gemstone.gemfire.internal.InternalDataSerializer; + +/** + * Generic Serializer for JDK Enums. The class needs to be registered only once - custom enums + * will be then understood by the converter by calling {@link #addEnum(Class)}. + * + * @author Costin Leau + */ +public class EnumSerializer extends DataSerializer implements Serializable { + + private static final long serialVersionUID = -7069461993489626976L; + + private static final ConcurrentMap, Enum[]> supportedClasses = new ConcurrentHashMap, Enum[]>(); + + private int id = 1024; + + @Override + public boolean toData(Object o, DataOutput out) throws IOException { + if (o instanceof Enum) { + // add enum to the set + Enum enm = (Enum) o; + Class cls = enm.getDeclaringClass(); + addEnum(cls); + DataSerializer.writeClass(cls, out); + out.writeInt(enm.ordinal()); + } + return false; + } + + @SuppressWarnings("unchecked") + @Override + public Object fromData(DataInput in) throws IOException, ClassNotFoundException { + Class cls = DataSerializer.readClass(in); + if (cls.isEnum()) { + addEnum(cls); + int ordinal = in.readInt(); + return supportedClasses.get(cls)[ordinal]; + } + throw new IOException("Non-enum class read from the stream -" + cls); + } + + @SuppressWarnings("unchecked") + public void addEnum(Class enumClass) { + if (!supportedClasses.containsKey(enumClass)) { + supportedClasses.put(enumClass, (Enum[]) enumClass.getEnumConstants()); + } + + // if registered, re-register the serializer to propagate the changes + if (InternalDataSerializer.getSerializer(getId()) != null) { + if (InternalDataSerializer.getSerializer(enumClass) == null) { + InternalDataSerializer.unregister(getId()); + InternalDataSerializer.register(getClass()); + } + } + } + + @Override + public Class[] getSupportedClasses() { + return supportedClasses.keySet().toArray(new Class[supportedClasses.size()]); + } + + @Override + public int getId() { + return id; + } + + /** + * Sets the id for this serializer. Default is 1024; + * + * @param id the id to set + */ + public void setId(int id) { + this.id = id; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/gemfire/serialization/InstantiatorGenerator.java b/src/main/java/org/springframework/data/gemfire/serialization/InstantiatorGenerator.java new file mode 100644 index 00000000..1d0c4706 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/serialization/InstantiatorGenerator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire.serialization; + +import com.gemstone.gemfire.DataSerializable; +import com.gemstone.gemfire.Instantiator; + +/** + * Factory that generates {@link Instantiator} classes to improve instantiation of + * custom types. + * + * @author Costin Leau + */ +public interface InstantiatorGenerator { + + /** + * Returns a (potentially new) Instantiator that optimizes the instantiation of the given types. + * + * @return + */ + Instantiator getInstantiator(Class clazz, int classId); +} diff --git a/src/main/java/org/springframework/data/gemfire/serialization/WiringInstantiator.java b/src/main/java/org/springframework/data/gemfire/serialization/WiringInstantiator.java new file mode 100644 index 00000000..29af5831 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/serialization/WiringInstantiator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire.serialization; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.wiring.BeanConfigurerSupport; + +import com.gemstone.gemfire.DataSerializable; +import com.gemstone.gemfire.Instantiator; + +/** + * Instantiator that performs instance wiring using the Spring IoC container, allowing common properties + * to be injected before the object is hydrated/deserialized. The newly created instances can be configured + * either by relying on an existing bean definition (which acts as a template) or by providing an embedded + * configuration through annotations. + * + *

+ * Can reuse existing instantiators to optimize instance creation. If one is not provided, it will fallback + * to reflection invocation. + * + * @see org.springframework.beans.factory.wiring.BeanConfigurerSupport + * @see org.springframework.beans.factory.wiring.BeanWiringInfoResolver + * @see org.springframework.beans.factory.annotation.Autowired + * @see javax.annotation.Resource + * @see javax.inject.Inject + * + * @author Costin Leau + */ +public class WiringInstantiator extends Instantiator implements BeanFactoryAware, InitializingBean, + DisposableBean { + + private final Instantiator instantiator; + private final Class clazz; + private BeanConfigurerSupport configurer; + private BeanFactory beanFactory; + + public WiringInstantiator(Instantiator instantiator) { + super(instantiator.getInstantiatedClass(), instantiator.getId()); + this.instantiator = instantiator; + this.clazz = null; + } + + public WiringInstantiator(Class c, int classId) { + super(c, classId); + instantiator = null; + clazz = c; + } + + + public void afterPropertiesSet() { + if (configurer == null) { + configurer = new BeanConfigurerSupport(); + configurer.setBeanFactory(beanFactory); + configurer.afterPropertiesSet(); + } + } + + public void destroy() throws Exception { + configurer.destroy(); + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + + @Override + public DataSerializable newInstance() { + DataSerializable instance = createInstance(); + configurer.configureBean(instance); + return instance; + } + + private DataSerializable createInstance() { + if (instantiator != null) { + return instantiator.newInstance(); + } + + return BeanUtils.instantiate(clazz); + } + + /** + * Sets the manager responsible for configuring the newly created instances. + * The given configurer needs to be configured and initialized before-hand. + * + * @param configurer the configurer to set + */ + public void setConfigurer(BeanConfigurerSupport configurer) { + this.configurer = configurer; + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/gemfire/CacheIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/CacheIntegrationTest.java new file mode 100644 index 00000000..4b58e265 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/CacheIntegrationTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.gemstone.gemfire.cache.Cache; + +/** + * Integration test trying various basic configurations of Gemfire through Spring. + * + * @author Costin Leau + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "basic-cache.xml" }) +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +public class CacheIntegrationTest { + + @Autowired + private ApplicationContext ctx; + + @Test + public void testBasicCache() throws Exception { + ctx.getBean("default-cache"); + } + + @Test + public void testCacheWithProps() throws Exception { + Cache cache = ctx.getBean("cache-with-props", Cache.class); + // the name property seems to be ignored + // Assert.assertEquals("cache-with-props", cache.getDistributedSystem().getName()); + } + + @Test + public void testNamedCache() throws Exception { + ctx.getBean("named-cache"); + } + + @Test + public void testCacheWithXml() throws Exception { + Cache cache = ctx.getBean("cache-with-xml", Cache.class); + //Assert.assertEquals("cache-with-props", cache.getDistributedSystem().getName()); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/DeclarableSupportTest.java b/src/test/java/org/springframework/data/gemfire/DeclarableSupportTest.java new file mode 100644 index 00000000..3ded2b07 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/DeclarableSupportTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + + +/** + * Integration test for declarable support (and GEF bean factory locator). + * + * @author Costin Leau + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "cache-with-declarable-ctx.xml" }) +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +public class DeclarableSupportTest { + + @Autowired + private BeanFactory ctx; + + @Test + public void testUserObject() throws Exception { + assertNotNull(UserObject.THIS); + UserObject obj = UserObject.THIS; + assertSame(ctx, obj.getBeanFactory()); + assertSame(ctx.getBean("bean"), obj.getProp2()); + assertEquals("Enescu", obj.getProp1()); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/GemfireBeanFactoryLocatorTest.java b/src/test/java/org/springframework/data/gemfire/GemfireBeanFactoryLocatorTest.java new file mode 100644 index 00000000..ae02e198 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/GemfireBeanFactoryLocatorTest.java @@ -0,0 +1,137 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.access.BeanFactoryReference; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.GemfireBeanFactoryLocator; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Costin Leau + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "locatorContext.xml" }) +public class GemfireBeanFactoryLocatorTest { + + @Autowired + private ApplicationContext applicationContext; + + private GemfireBeanFactoryLocator locator1, locator2; + private String INSTANCE_1 = "instance1"; + private String INSTANCE_2 = "instance2"; + + @Before + public void init() { + locator1 = new GemfireBeanFactoryLocator(); + locator1.setBeanName(INSTANCE_1); + locator1.setBeanFactory(applicationContext); + locator1.afterPropertiesSet(); + + locator2 = new GemfireBeanFactoryLocator(); + locator2.setBeanName(INSTANCE_2); + locator2.setBeanFactory(applicationContext); + locator2.afterPropertiesSet(); + } + + @After + public void destroy() { + BeanFactoryReference ref1; + try { + ref1 = locator1.useBeanFactory(INSTANCE_1); + ref1.release(); + BeanFactoryReference ref2 = locator2.useBeanFactory(INSTANCE_2); + ref2.release(); + + } catch (IllegalArgumentException e) { + // it's okay + } + locator1.destroy(); + locator2.destroy(); + locator1 = null; + locator2 = null; + } + + @Test + public void testBeanFactoryRelease() throws Exception { + } + + @Test + public void testFactoryLocator() throws Exception { + BeanFactoryReference reference1 = locator1.useBeanFactory(INSTANCE_1); + BeanFactoryReference reference2 = locator2.useBeanFactory(INSTANCE_2); + BeanFactoryReference aliasRef1 = locator1.useBeanFactory("alias1"); + BeanFactoryReference aliasRef2 = locator1.useBeanFactory("alias2"); + + // verify the static map + BeanFactory factory1 = reference1.getFactory(); + BeanFactory factory2 = reference2.getFactory(); + BeanFactory factory3 = reference2.getFactory(); + // get the alias from different factories + BeanFactory alias1 = aliasRef1.getFactory(); + BeanFactory alias2 = aliasRef2.getFactory(); + + assertSame(factory1, factory2); + assertSame(factory1, factory3); + // verify it's the same bean factory as the application context + assertSame(factory1, applicationContext); + + // verify aliases + assertSame(alias1, alias2); + assertSame(factory1, alias1); + + aliasRef1.release(); + aliasRef2.release(); + reference1.release(); + reference2.release(); + } + + @Test + public void testDefaultFactoryLocator() throws Exception { + try { + locator1.useBeanFactory(null); + fail("there are more then one bean factories registered - should have thrown exception"); + } catch (IllegalArgumentException e) { + // it's okay + } + } + + @Test + public void testFactoryLocatorContract() throws Exception { + BeanFactoryReference factory1 = locator1.useBeanFactory(INSTANCE_1); + assertNotNull(factory1.getFactory()); + + factory1.release(); + try { + factory1.getFactory(); + fail("should have received exception"); + } catch (IllegalArgumentException e) { + // it's okay + } + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/gemfire/RegionIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/RegionIntegrationTest.java new file mode 100644 index 00000000..05434d17 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/RegionIntegrationTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.ClientRegionFactoryBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.gemstone.gemfire.cache.CacheListener; +import com.gemstone.gemfire.cache.CacheLoader; +import com.gemstone.gemfire.cache.CacheLoaderException; +import com.gemstone.gemfire.cache.LoaderHelper; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.util.CacheListenerAdapter; +import com.gemstone.gemfire.cache.util.CacheWriterAdapter; + + +/** + * @author Costin Leau + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "basic-region.xml" }) +@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) +public class RegionIntegrationTest { + + private static class CacheList extends CacheListenerAdapter { + } + + private static class CacheLoad implements CacheLoader { + + public V load(LoaderHelper arg0) throws CacheLoaderException { + return null; + } + + public void close() { + } + } + + private static class CacheWrite extends CacheWriterAdapter { + } + + @Autowired + private ApplicationContext ctx; + + @Test + public void testBasicRegion() throws Exception { + Region region = ctx.getBean("basic", Region.class); + assertEquals("basic", region.getName()); + } + + @Test + public void testExistingRegion() throws Exception { + Region region = ctx.getBean("root", Region.class); + // the name property seems to be ignored + assertEquals("root", region.getName()); + } + + @Test + public void testRegionWithListeners() throws Exception { + Region region = ctx.getBean("listeners", Region.class); + assertEquals("listeners", region.getName()); + CacheListener[] listeners = region.getAttributes().getCacheListeners(); + assertEquals(2, listeners.length); + assertSame(CacheList.class, listeners[0].getClass()); + assertSame(CacheLoad.class, region.getAttributes().getCacheLoader().getClass()); + assertSame(CacheWrite.class, region.getAttributes().getCacheWriter().getClass()); + } + + //@Test + //TODO: Disabled since for some reason in Spring, I get the bean rather then the client + public void testRegionInterest() throws Exception { + ClientRegionFactoryBean regionFB = (ClientRegionFactoryBean) ctx.getBean("&basic-client"); + System.out.println("**** interests are " + Arrays.toString(regionFB.getInterests())); + //BeanDefinition bd = ((BeanDefinitionRegistry) ctx.getAutowireCapableBeanFactory()).getBeanDefinition("basic-client"); + // System.out.println(bd.getPropertyValues().getPropertyValue("interests").getValue()); + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/gemfire/TxIntegrationTest.java b/src/test/java/org/springframework/data/gemfire/TxIntegrationTest.java new file mode 100644 index 00000000..21385ef4 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/TxIntegrationTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import javax.annotation.Resource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.transaction.AfterTransaction; +import org.springframework.test.context.transaction.BeforeTransaction; +import org.springframework.transaction.annotation.Transactional; + +/** + * Simple TX integration test. + * + * @author Costin Leau + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "basic-tx-config.xml" }) +@Transactional +public class TxIntegrationTest { + + @Resource(name = "rollback-region") + private Map rollbackRegion; + @Resource(name = "commit-region") + private Map commitRegion; + + private boolean txCommit = false; + + @BeforeTransaction + public void addItemsToTheCache() { + rollbackRegion.put("Vlaicu", "Aurel"); + rollbackRegion.put("Coanda", "Henri"); + commitRegion.put("Coanda", "Henri"); + commitRegion.put("Vlaicu", "Aurel"); + txCommit = false; + } + + @Test + public void testTransactionRollback() throws Exception { + rollbackRegion.remove("Coanda"); + rollbackRegion.put("Ciurcu", "Alexandru"); + } + + @Test + @Rollback(value = false) + public void testTransactionCommit() throws Exception { + commitRegion.put("Poenaru", "Petrache"); + commitRegion.remove("Coanda"); + commitRegion.put("Vlaicu", "A"); + + txCommit = true; + } + + @AfterTransaction + public void testTxOutcome() { + if (txCommit) { + assertFalse(commitRegion.containsKey("Coanda")); + assertTrue(commitRegion.containsKey("Poenaru")); + assertTrue(commitRegion.containsValue("A")); + } + assertTrue(rollbackRegion.containsKey("Coanda")); + assertTrue(rollbackRegion.containsKey("Vlaicu")); + assertFalse(rollbackRegion.containsKey("Ciurcu")); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/UserObject.java b/src/test/java/org/springframework/data/gemfire/UserObject.java new file mode 100644 index 00000000..46f50246 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/UserObject.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire; + +import org.springframework.data.gemfire.WiringDeclarableSupport; + +import com.gemstone.gemfire.cache.CacheLoader; +import com.gemstone.gemfire.cache.CacheLoaderException; +import com.gemstone.gemfire.cache.LoaderHelper; + +/** + * User object used for testing Spring wiring. + * + * @author Costin Leau + */ +public class UserObject extends WiringDeclarableSupport implements CacheLoader { + + public static UserObject THIS; + + private String prop1; + private Object prop2; + + public UserObject() { + System.out.println("Initialized"); + THIS = this; + } + + public Object load(LoaderHelper helper) throws CacheLoaderException { + return new Object(); + } + + /** + * @return the prop1 + */ + public String getProp1() { + return prop1; + } + + /** + * @param prop1 the prop1 to set + */ + public void setProp1(String prop1) { + this.prop1 = prop1; + } + + /** + * @return the prop2 + */ + public Object getProp2() { + return prop2; + } + + /** + * @param prop2 the prop2 to set + */ + public void setProp2(Object prop2) { + this.prop2 = prop2; + } +} \ No newline at end of file diff --git a/src/test/java/org/springframework/data/gemfire/serialization/AsmInstantiatorFactoryTest.java b/src/test/java/org/springframework/data/gemfire/serialization/AsmInstantiatorFactoryTest.java new file mode 100644 index 00000000..0d01c123 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/serialization/AsmInstantiatorFactoryTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire.serialization; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.gemfire.serialization.AsmInstantiatorGenerator; + +import com.gemstone.gemfire.DataSerializable; +import com.gemstone.gemfire.Instantiator; + +/** + * @author Costin Leau + */ +public class AsmInstantiatorFactoryTest { + + public static class SomeClass implements DataSerializable { + public static boolean instantiated = false; + + public SomeClass() { + instantiated = true; + } + + public void fromData(DataInput in) throws IOException, ClassNotFoundException { + } + + public void toData(DataOutput out) throws IOException { + } + } + + private AsmInstantiatorGenerator asmFactory = null; + + @Before + public void setUp() { + SomeClass.instantiated = false; + asmFactory = new AsmInstantiatorGenerator(); + } + + @Test + public void testClassGeneration() throws Exception { + Instantiator instantiator = asmFactory.getInstantiator(SomeClass.class, 100); + assertEquals(100, instantiator.getId()); + assertEquals(SomeClass.class, instantiator.getInstantiatedClass()); + Object instance = instantiator.newInstance(); + assertEquals(SomeClass.class, instance.getClass()); + assertTrue(SomeClass.instantiated); + } + + @Test + public void testGeneratedClassName() throws Exception { + Instantiator instantiator = asmFactory.getInstantiator(SomeClass.class, 100); + assertTrue(instantiator.getClass().getName().contains("$")); + } + + @Test + public void testInterfaces() throws Exception { + Instantiator instantiator = asmFactory.getInstantiator(SomeClass.class, 100); + assertTrue(instantiator instanceof Serializable); + } + + @Test + public void testCacheInPlace() throws Exception { + Instantiator instance1 = asmFactory.getInstantiator(SomeClass.class, 120); + Instantiator instance2 = asmFactory.getInstantiator(SomeClass.class, 125); + assertSame(instance1, instance2); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/serialization/WiringInstantiatorTest.java b/src/test/java/org/springframework/data/gemfire/serialization/WiringInstantiatorTest.java new file mode 100644 index 00000000..516699d1 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/serialization/WiringInstantiatorTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.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.gemfire.serialization; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.awt.Point; +import java.awt.Shape; +import java.beans.Beans; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.gemfire.serialization.AsmInstantiatorGenerator; +import org.springframework.data.gemfire.serialization.WiringInstantiator; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.gemstone.gemfire.DataSerializable; + +/** + * @author Costin Leau + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("simple-config.xml") +public class WiringInstantiatorTest { + + @Autowired + private ApplicationContext ctx; + @Autowired + private WiringInstantiator instantiator; + + + public static class AnnotatedBean implements DataSerializable { + @Autowired + Point point; + Shape shape; + + @Autowired + void initShape(Shape shape) { + this.shape = shape; + } + + public void fromData(DataInput in) throws IOException, ClassNotFoundException { + } + + public void toData(DataOutput out) throws IOException { + } + } + + public static class TemplateWiringBean implements DataSerializable { + Point point; + Beans beans; + + public void setBeans(Beans bs) { + this.beans = bs; + } + + public void fromData(DataInput in) throws IOException, ClassNotFoundException { + } + + public void toData(DataOutput out) throws IOException { + } + } + + @Test + public void testAutowiredBean() throws Exception { + Object instance = instantiator.newInstance(); + assertNotNull(instance); + assertTrue(instance instanceof AnnotatedBean); + AnnotatedBean bean = (AnnotatedBean) instance; + + assertNotNull(bean.point); + assertNotNull(bean.shape); + + assertSame(bean.point, ctx.getBean("point")); + assertSame(bean.shape, ctx.getBean("area")); + } + + @Test + public void testTemplateBean() throws Exception { + WiringInstantiator instantiator2 = new WiringInstantiator( + new AsmInstantiatorGenerator().getInstantiator( + TemplateWiringBean.class, 99)); + instantiator2.setBeanFactory(ctx.getAutowireCapableBeanFactory()); + instantiator2.afterPropertiesSet(); + + Object instance = instantiator2.newInstance(); + + assertTrue(instance instanceof TemplateWiringBean); + TemplateWiringBean bean = (TemplateWiringBean) instance; + + assertTrue(bean.point == null); + assertNotNull(bean.beans); + + assertSame(bean.beans, ctx.getBean("beans")); + } +} \ No newline at end of file diff --git a/src/test/resources/cache-with-declarable.xml b/src/test/resources/cache-with-declarable.xml new file mode 100644 index 00000000..4b644e1f --- /dev/null +++ b/src/test/resources/cache-with-declarable.xml @@ -0,0 +1,32 @@ + + + + + java.lang.String + + org.springframework.data.gemfire.UserObject + + + + + + + + + + + + + + + + + + + + Application Version + 1.0 + + + + diff --git a/src/test/resources/cache.xml b/src/test/resources/cache.xml new file mode 100644 index 00000000..5c221040 --- /dev/null +++ b/src/test/resources/cache.xml @@ -0,0 +1,39 @@ + + + + + java.lang.String + + + + + + + + + + + + + + + + + + Application Version + 1.0 + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/basic-cache.xml b/src/test/resources/org/springframework/data/gemfire/basic-cache.xml new file mode 100644 index 00000000..640a2d29 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/basic-cache.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + cache-with-props + + + + + + + + cache-with-props + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/basic-region.xml b/src/test/resources/org/springframework/data/gemfire/basic-region.xml new file mode 100644 index 00000000..2a86e119 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/basic-region.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/basic-tx-config.xml b/src/test/resources/org/springframework/data/gemfire/basic-tx-config.xml new file mode 100644 index 00000000..467e5aba --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/basic-tx-config.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/cache-with-declarable-ctx.xml b/src/test/resources/org/springframework/data/gemfire/cache-with-declarable-ctx.xml new file mode 100644 index 00000000..8bcc11df --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/cache-with-declarable-ctx.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/test/resources/org/springframework/data/gemfire/locatorContext.xml b/src/test/resources/org/springframework/data/gemfire/locatorContext.xml new file mode 100644 index 00000000..a12e6305 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/locatorContext.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/org/springframework/data/gemfire/serialization/simple-config.xml b/src/test/resources/org/springframework/data/gemfire/serialization/simple-config.xml new file mode 100644 index 00000000..15853681 --- /dev/null +++ b/src/test/resources/org/springframework/data/gemfire/serialization/simple-config.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + +