Prechádzať zdrojové kódy

将jcyq-rose上传仓库

hmy 1 rok pred
commit
4d64bc4a72
100 zmenil súbory, kde vykonal 9354 pridanie a 0 odobranie
  1. 35 0
      .gitignore
  2. 96 0
      pom.xml
  3. 59 0
      rose-context/pom.xml
  4. 226 0
      rose-context/src/main/java/com/wzzx/rose/junit/RoseContextLoader.java
  5. 34 0
      rose-context/src/main/java/com/wzzx/rose/junit/RoseTestCase.java
  6. 8 0
      rose-context/src/main/java/com/wzzx/rose/package-info.java
  7. 99 0
      rose-context/src/main/java/com/wzzx/rose/scanning/RoseSettings.java
  8. 50 0
      rose-context/src/main/java/net/paoding/rose/app/RoseAppContext.java
  9. 106 0
      rose-context/src/main/java/net/paoding/rose/scanning/LoadScope.java
  10. 239 0
      rose-context/src/main/java/net/paoding/rose/scanning/ResourceRef.java
  11. 338 0
      rose-context/src/main/java/net/paoding/rose/scanning/RoseScanner.java
  12. 215 0
      rose-context/src/main/java/net/paoding/rose/scanning/context/AnnotationContext.java
  13. 239 0
      rose-context/src/main/java/net/paoding/rose/scanning/context/AnnotationWebContext.java
  14. 45 0
      rose-context/src/main/java/net/paoding/rose/scanning/context/RoseContext.java
  15. 20 0
      rose-context/src/main/java/net/paoding/rose/scanning/context/RoseSpringApplicationBuilder.java
  16. 45 0
      rose-context/src/main/java/net/paoding/rose/scanning/context/RoseWebContext.java
  17. 154 0
      rose-context/src/main/java/net/paoding/rose/scanning/context/core/RoseResources.java
  18. 32 0
      rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileContent.java
  19. 53 0
      rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileName.java
  20. 56 0
      rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileNameImpl.java
  21. 99 0
      rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileObject.java
  22. 117 0
      rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileSystemManager.java
  23. 62 0
      rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileType.java
  24. 195 0
      rose-context/src/main/java/net/paoding/rose/scanning/vfs/JarFileObject.java
  25. 145 0
      rose-context/src/main/java/net/paoding/rose/scanning/vfs/SimpleFileObject.java
  26. 26 0
      rose-context/src/main/resources/applicationContext-context.xml
  27. 68 0
      rose-jade/pom.xml
  28. 67 0
      rose-jade/src/main/java/com/wzzx/rose/jade/shard/PartitionConfig.java
  29. 10 0
      rose-jade/src/main/java/com/wzzx/rose/jade/shard/PartitionItem.java
  30. 63 0
      rose-jade/src/main/java/com/wzzx/rose/jade/shard/Table.java
  31. 109 0
      rose-jade/src/main/java/com/wzzx/rose/jade/statement/PreProcessor.java
  32. 33 0
      rose-jade/src/main/java/com/wzzx/rose/jade/transaction/Transaction.java
  33. 90 0
      rose-jade/src/main/java/com/wzzx/rose/jade/transaction/TransactionHandler.java
  34. 57 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/Cache.java
  35. 47 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/CacheDelete.java
  36. 70 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/DAO.java
  37. 29 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/KeyColumnOfMap.java
  38. 46 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/ReturnGeneratedKeys.java
  39. 64 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/RowHandler.java
  40. 81 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/SQL.java
  41. 49 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/SQLParam.java
  42. 44 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/SQLType.java
  43. 24 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/ShardBy.java
  44. 49 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/ShardParam.java
  45. 25 0
      rose-jade/src/main/java/net/paoding/rose/jade/annotation/UseMaster.java
  46. 223 0
      rose-jade/src/main/java/net/paoding/rose/jade/context/JadeInvocationHandler.java
  47. 113 0
      rose-jade/src/main/java/net/paoding/rose/jade/context/application/JadeFactory.java
  48. 471 0
      rose-jade/src/main/java/net/paoding/rose/jade/context/spring/JadeBeanFactoryPostProcessor.java
  49. 231 0
      rose-jade/src/main/java/net/paoding/rose/jade/context/spring/JadeComponentProvider.java
  50. 167 0
      rose-jade/src/main/java/net/paoding/rose/jade/context/spring/JadeFactoryBean.java
  51. 126 0
      rose-jade/src/main/java/net/paoding/rose/jade/context/spring/SpringDataSourceFactory.java
  52. 43 0
      rose-jade/src/main/java/net/paoding/rose/jade/context/spring/SpringDataSourceFactoryDelegate.java
  53. 76 0
      rose-jade/src/main/java/net/paoding/rose/jade/context/spring/SpringInterpreterFactory.java
  54. 87 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataAccess.java
  55. 34 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataAccessFactory.java
  56. 58 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataAccessFactoryAdapter.java
  57. 269 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataAccessImpl.java
  58. 51 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataSourceFactory.java
  59. 84 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataSourceHolder.java
  60. 124 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/datasource/HierarchicalDataSourceFactory.java
  61. 109 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/datasource/MasterSlaveDataSourceFactory.java
  62. 73 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/datasource/RandomDataSourceFactory.java
  63. 71 0
      rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/datasource/SimpleDataSourceFactory.java
  64. 66 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/AbstractCollectionRowMapper.java
  65. 49 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/ArrayRowMapper.java
  66. 267 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/BeanPropertyRowMapper.java
  67. 192 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/DefaultRowMapperFactory.java
  68. 40 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/ListRowMapper.java
  69. 121 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/MapEntryColumnRowMapper.java
  70. 74 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/MapEntryImpl.java
  71. 96 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/MapEntryRowMapper.java
  72. 44 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/RowMapperFactory.java
  73. 40 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/SetRowMapper.java
  74. 21 0
      rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/TypeUtils.java
  75. 81 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/DAOMetaData.java
  76. 62 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/DefaultInterpreterFactory.java
  77. 141 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/GenericUtils.java
  78. 39 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/Interpreter.java
  79. 37 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/InterpreterComparator.java
  80. 26 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/InterpreterFactory.java
  81. 141 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/JdbcStatement.java
  82. 29 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/Querier.java
  83. 173 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/SelectQuerier.java
  84. 121 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/SimpleInterpreter.java
  85. 47 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/Statement.java
  86. 231 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementMetaData.java
  87. 45 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementRuntime.java
  88. 97 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementRuntimeImpl.java
  89. 36 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementWrapper.java
  90. 28 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementWrapperProvider.java
  91. 93 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/SystemInterpreter.java
  92. 210 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/UpdateQuerier.java
  93. 45 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/CacheInterface.java
  94. 35 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/CacheProvider.java
  95. 180 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/CachedStatement.java
  96. 92 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/MockCache.java
  97. 45 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/MockCacheProvider.java
  98. 38 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/expression/ExprResolver.java
  99. 44 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/expression/ExqlContext.java
  100. 0 0
      rose-jade/src/main/java/net/paoding/rose/jade/statement/expression/ExqlPattern.java

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+# Compiled class file
+*.class
+
+.DS_Store
+.idea/
+*.iml
+target/
+ 
+# Log file
+*.log
+
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar
+*.tar.gz
+*.rar
+*.settings
+*.project
+.settings/
+*.settings 
+.classpath 
+.project 
+*.idea 
+logs 
+.factorypath 
+*.mvn 
+.mvn 
+*.iml 
+*.factorypath 
+*.node_modules 
+node_modules 
+.sts4-cache

+ 96 - 0
pom.xml

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>net.paoding</groupId>
+	<artifactId>root</artifactId>
+	<version>1.3-SNAPSHOT</version>
+	<packaging>pom</packaging>
+	<parent>
+		<groupId>com.wzzx</groupId>
+		<artifactId>wzzx-root-pom</artifactId>
+		<version>3.0.0-SNAPSHOT</version>
+	</parent>
+
+	<modules>
+		<module>rose</module>
+		<module>rose-context</module>
+		<module>rose-jade</module>
+		<module>rose-pipe</module>
+		<module>rose-velocity</module>
+	</modules>
+	<properties>
+		<spring.boot.version>2.2.1.RELEASE</spring.boot.version>
+		<paoding.version>1.3-SNAPSHOT</paoding.version>
+		<paoding.version>1.3-SNAPSHOT</paoding.version>
+		<paoding.version>1.3-SNAPSHOT</paoding.version>
+		<paoding.version>1.3-SNAPSHOT</paoding.version>
+		<paoding.version>1.3-SNAPSHOT</paoding.version>
+		<paoding.version>1.3-SNAPSHOT</paoding.version>
+		<paoding.version>1.3-SNAPSHOT</paoding.version>
+		<paoding.version>1.3-SNAPSHOT</paoding.version>
+	</properties>
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-starter-parent</artifactId>
+				<version>${spring.boot.version}</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
+			<dependency>
+				<groupId>commons-collections</groupId>
+				<artifactId>commons-collections</artifactId>
+				<version>3.2.1</version>
+			</dependency>
+			<dependency>
+				<groupId>net.paoding</groupId>
+				<artifactId>rose-velocity</artifactId>
+				<version>1.3</version>
+			</dependency>
+			<dependency>
+				<groupId>commons-logging</groupId>
+				<artifactId>commons-logging</artifactId>
+				<version>1.1.1</version>
+			</dependency>
+			<dependency>
+				<groupId>commons-fileupload</groupId>
+				<artifactId>commons-fileupload</artifactId>
+				<version>1.2.2</version>
+			</dependency>
+			<dependency>
+				<groupId>org.apache.velocity.tools</groupId>
+				<artifactId>velocity-tools-view</artifactId>
+				<version>3.1</version>
+			</dependency>
+			<dependency>
+				<groupId>com.metaparadigm</groupId>
+				<artifactId>json-rpc</artifactId>
+				<version>1.0</version>
+			</dependency>
+			<dependency>
+				<groupId>commons-jexl</groupId>
+				<artifactId>commons-jexl</artifactId>
+				<version>1.1</version>
+			</dependency>
+			<dependency>
+				<groupId>javax.servlet</groupId>
+				<artifactId>jsp-api</artifactId>
+				<version>2.0</version>
+			</dependency>
+			<dependency>
+				<groupId>commons-lang</groupId>
+				<artifactId>commons-lang</artifactId>
+				<version>2.6</version>
+			</dependency>
+			<dependency>
+				<groupId>com.alibaba</groupId>
+				<artifactId>fastjson</artifactId>
+				<version>1.2.28</version>
+			</dependency>
+		</dependencies>
+
+	</dependencyManagement>
+</project>

+ 59 - 0
rose-context/pom.xml

@@ -0,0 +1,59 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>rose-context</artifactId>
+	<parent>
+		<groupId>net.paoding</groupId>
+		<artifactId>root</artifactId>
+		<version>1.3-SNAPSHOT</version>
+	</parent>
+
+	<dependencies>
+		<dependency>
+			<groupId>commons-collections</groupId>
+			<artifactId>commons-collections</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-logging</groupId>
+			<artifactId>commons-logging</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-lang</groupId>
+			<artifactId>commons-lang</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-context</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+		</dependency>
+	</dependencies>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<configuration>
+					<archive>
+						<manifestEntries>
+							<Rose>*</Rose>
+						</manifestEntries>
+					</archive>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 226 - 0
rose-context/src/main/java/com/wzzx/rose/junit/RoseContextLoader.java

@@ -0,0 +1,226 @@
+//package com.wzzx.rose.junit;
+//
+//import java.lang.reflect.Modifier;
+//import java.util.ArrayList;
+//import java.util.List;
+//
+//import net.paoding.rose.scanning.context.AnnotationContext;
+//
+//import org.apache.commons.logging.Log;
+//import org.apache.commons.logging.LogFactory;
+//import org.springframework.beans.factory.support.BeanDefinitionReader;
+//import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+//import org.springframework.context.ConfigurableApplicationContext;
+//import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
+//import org.springframework.context.annotation.AnnotationConfigUtils;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.context.support.GenericApplicationContext;
+//import org.springframework.test.context.ContextConfigurationAttributes;
+//import org.springframework.test.context.MergedContextConfiguration;
+//import org.springframework.test.context.support.AbstractContextLoader;
+//import org.springframework.test.context.support.AbstractGenericContextLoader;
+//import org.springframework.util.Assert;
+//import org.springframework.util.ObjectUtils;
+//
+//import org.springframework.test.context.support.AnnotationConfigContextLoader;
+//
+///**
+// * 仅用于spring-test
+// *
+// * @see AnnotationConfigContextLoader
+// *
+// * @author jingyun.zou@renren-inc.com
+// * {@link net.paoding.rose.junit.RoseTestCase}
+// */
+//@Deprecated
+//class RoseContextLoader extends AbstractContextLoader {
+//
+//	protected static final Log logger = LogFactory.getLog(RoseContextLoader.class);
+//
+//	/**
+//	 * Load a Spring ApplicationContext from the supplied {@link MergedContextConfiguration}.
+//	 * <p>Implementation details:
+//	 * <ul>
+//	 * <li>Creates a {@link GenericApplicationContext} instance.</li>
+//	 * <li>Sets the <em>active bean definition profiles</em> from the supplied
+//	 * <code>MergedContextConfiguration</code> in the
+//	 * {@link org.springframework.core.env.Environment Environment} of the context.</li>
+//	 * <li>Calls {@link #prepareContext()} to allow for customizing the context
+//	 * before bean definitions are loaded.</li>
+//	 * <li>Calls {@link #customizeBeanFactory()} to allow for customizing the
+//	 * context's <code>DefaultListableBeanFactory</code>.</li>
+//	 * <li>Delegates to {@link #loadBeanDefinitions()} to populate the context
+//	 * from the configuration locations or classes in the supplied
+//	 * <code>MergedContextConfiguration</code>.</li>
+//	 * <li>Delegates to {@link AnnotationConfigUtils} for
+//	 * {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering}
+//	 * annotation configuration processors.</li>
+//	 * <li>Calls {@link #customizeContext()} to allow for customizing the context
+//	 * before it is refreshed.</li>
+//	 * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the
+//	 * context and registers a JVM shutdown hook for it.</li>
+//	 * </ul>
+//	 * @return a new application context
+//	 * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
+//	 * @see GenericApplicationContext
+//	 * @since 3.1
+//	 */
+//	public ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
+//		if (logger.isDebugEnabled()) {
+//			logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].",
+//				mergedConfig));
+//		}
+//		AnnotationContext context = new AnnotationContext();
+//		DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();
+//
+//		context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
+//		loadBeanDefinitions(context, mergedConfig);
+//		AnnotationConfigUtils.registerAnnotationConfigProcessors(factory);
+//		// context.refresh();	// RoseAppContext refresh itself
+//		context.registerShutdownHook();
+//		return context;
+//	}
+//
+//	/**
+//	 * Register {@link org.springframework.context.annotation.Configuration configuration classes}
+//	 * in the supplied {@link GenericApplicationContext context} from the classes
+//	 * in the supplied {@link MergedContextConfiguration}.
+//	 * <p>Each class must represent an annotated configuration class or component. An
+//	 * {@link AnnotatedBeanDefinitionReader} is used to register the appropriate
+//	 * bean definitions.
+//	 * <p>Note that this method does not call {@link #createBeanDefinitionReader}
+//	 * since <code>AnnotatedBeanDefinitionReader</code> is not an instance of
+//	 * {@link BeanDefinitionReader}.
+//	 * @param context the context in which the configuration classes should be registered
+//	 * @param mergedConfig the merged configuration from which the classes should be retrieved
+//	 * @see AbstractGenericContextLoader#loadBeanDefinitions
+//	 */
+//	protected void loadBeanDefinitions(AnnotationContext context, MergedContextConfiguration mergedConfig) {
+//		Class<?>[] configClasses = mergedConfig.getClasses();
+//		if (logger.isDebugEnabled()) {
+//			logger.debug("Registering configuration classes: " + ObjectUtils.nullSafeToString(configClasses));
+//		}
+//		DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();
+//		new AnnotatedBeanDefinitionReader(factory).register(configClasses);
+//	}
+//
+//	/**
+//	 * Process configuration classes in the supplied {@link ContextConfigurationAttributes}.
+//	 * <p>If the configuration classes are <code>null</code> or empty and
+//	 * {@link #isGenerateDefaultLocations()} returns <code>true</code>, this
+//	 * <code>SmartContextLoader</code> will attempt to {@link
+//	 * #detectDefaultConfigurationClasses detect default configuration classes}.
+//	 * If defaults are detected they will be
+//	 * {@link ContextConfigurationAttributes#setClasses(Class[]) set} in the
+//	 * supplied configuration attributes. Otherwise, properties in the supplied
+//	 * configuration attributes will not be modified.
+//	 * @param configAttributes the context configuration attributes to process
+//	 * @see org.springframework.test.context.SmartContextLoader#processContextConfiguration()
+//	 * @see #isGenerateDefaultLocations()
+//	 * @see #detectDefaultConfigurationClasses()
+//	 */
+//	public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
+//		if (ObjectUtils.isEmpty(configAttributes.getClasses()) && isGenerateDefaultLocations()) {
+//			Class<?>[] defaultConfigClasses = detectDefaultConfigurationClasses(configAttributes.getDeclaringClass());
+//			configAttributes.setClasses(defaultConfigClasses);
+//		}
+//	}
+//
+//	// --- AnnotationConfigContextLoader ---------------------------------------
+//
+//	private boolean isStaticNonPrivateAndNonFinal(Class<?> clazz) {
+//		Assert.notNull(clazz, "Class must not be null");
+//		int modifiers = clazz.getModifiers();
+//		return (Modifier.isStatic(modifiers) && !Modifier.isPrivate(modifiers) && !Modifier.isFinal(modifiers));
+//	}
+//
+//	/**
+//	 * Determine if the supplied {@link Class} meets the criteria for being
+//	 * considered a <em>default configuration class</em> candidate.
+//	 * <p>Specifically, such candidates:
+//	 * <ul>
+//	 * <li>must not be <code>null</code></li>
+//	 * <li>must not be <code>private</code></li>
+//	 * <li>must not be <code>final</code></li>
+//	 * <li>must be <code>static</code></li>
+//	 * <li>must be annotated with {@code @Configuration}</li>
+//	 * </ul>
+//	 * @param clazz the class to check
+//	 * @return <code>true</code> if the supplied class meets the candidate criteria
+//	 */
+//	private boolean isDefaultConfigurationClassCandidate(Class<?> clazz) {
+//		return clazz != null && isStaticNonPrivateAndNonFinal(clazz) && clazz.isAnnotationPresent(Configuration.class);
+//	}
+//
+//	/**
+//	 * Detect the default configuration classes for the supplied test class.
+//	 * <p>The returned class array will contain all static inner classes of
+//	 * the supplied class that meet the requirements for {@code @Configuration}
+//	 * class implementations as specified in the documentation for
+//	 * {@link Configuration @Configuration}.
+//	 * <p>The implementation of this method adheres to the contract defined in the
+//	 * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
+//	 * SPI. Specifically, this method uses introspection to detect default
+//	 * configuration classes that comply with the constraints required of
+//	 * {@code @Configuration} class implementations. If a potential candidate
+//	 * configuration class does meet these requirements, this method will log a
+//	 * warning, and the potential candidate class will be ignored.
+//	 * @param declaringClass the test class that declared {@code @ContextConfiguration}
+//	 * @return an array of default configuration classes, potentially empty but
+//	 * never <code>null</code>
+//	 */
+//	protected Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
+//		Assert.notNull(declaringClass, "Declaring class must not be null");
+//
+//		List<Class<?>> configClasses = new ArrayList<Class<?>>();
+//
+//		for (Class<?> candidate : declaringClass.getDeclaredClasses()) {
+//			if (isDefaultConfigurationClassCandidate(candidate)) {
+//				configClasses.add(candidate);
+//			}
+//			else {
+//				if (logger.isDebugEnabled()) {
+//					logger.debug(String.format(
+//						"Ignoring class [%s]; it must be static, non-private, non-final, and annotated "
+//								+ "with @Configuration to be considered a default configuration class.",
+//						candidate.getName()));
+//				}
+//			}
+//		}
+//
+//		if (configClasses.isEmpty()) {
+//			if (logger.isInfoEnabled()) {
+//				logger.info(String.format("Could not detect default configuration classes for test class [%s]: "
+//						+ "%s does not declare any static, non-private, non-final, inner classes "
+//						+ "annotated with @Configuration.", declaringClass.getName(), declaringClass.getSimpleName()));
+//			}
+//		}
+//
+//		return configClasses.toArray(new Class<?>[configClasses.size()]);
+//	}
+//
+//	@Override
+//	public final ConfigurableApplicationContext loadContext(String... locations) throws Exception {
+//		throw new UnsupportedOperationException(
+//				"RoseContextLoader does not support the loadContext(locations) method");
+//	}
+//
+//	@Override
+//	protected String getResourceSuffix() {
+//		throw new UnsupportedOperationException(
+//				"RoseContextLoader does not support the getResourceSuffix() method");
+//	}
+//
+//
+//	@Override
+//	protected String[] generateDefaultLocations(Class<?> clazz) {
+//		throw new UnsupportedOperationException(
+//				"RoseContextLoader does not support the generateDefaultLocations(Class) method");
+//	}
+//
+//	@Override
+//	protected String[] modifyLocations(Class<?> clazz, String... locations) {
+//		throw new UnsupportedOperationException(
+//				"RoseContextLoader does not support the modifyLocations(Class, String...) method");
+//	}
+//}

+ 34 - 0
rose-context/src/main/java/com/wzzx/rose/junit/RoseTestCase.java

@@ -0,0 +1,34 @@
+//package com.wzzx.rose.junit;
+//
+//import java.util.concurrent.ConcurrentHashMap;
+//
+//import org.junit.runner.RunWith;
+//import org.springframework.beans.factory.InitializingBean;
+//import org.springframework.test.context.ContextConfiguration;
+//import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+///**
+// * @author liufei
+// * com.wzzx.rose.junit.RoseTestCase.java
+// * {@link net.paoding.rose.junit.RoseTestCase}
+// */
+//@Deprecated
+//@SuppressWarnings("deprecation")
+//@RunWith(SpringJUnit4ClassRunner.class)
+//@ContextConfiguration(loader = RoseContextLoader.class)
+//abstract public class RoseTestCase implements InitializingBean {
+//
+//	private static ConcurrentHashMap<Class<?>, Boolean> CLASSES
+//		= new ConcurrentHashMap<Class<?>, Boolean>();
+//
+//	@Override
+//	public final void afterPropertiesSet() throws Exception {
+//		if (null == CLASSES.putIfAbsent(getClass(), Boolean.TRUE))
+//			beforeClass();
+//	}
+//
+//	/**
+//	 * 同@BeforeClass一样仅执行一次
+//	 */
+//	protected void beforeClass() {
+//	}
+//}

+ 8 - 0
rose-context/src/main/java/com/wzzx/rose/package-info.java

@@ -0,0 +1,8 @@
+/**
+ * 
+ */
+/**
+ * @author liufei
+ * 2013年11月22日 下午3:29:35
+ */
+package com.wzzx.rose;

+ 99 - 0
rose-context/src/main/java/com/wzzx/rose/scanning/RoseSettings.java

@@ -0,0 +1,99 @@
+package com.wzzx.rose.scanning;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+import net.paoding.rose.scanning.LoadScope;
+import net.paoding.rose.scanning.ResourceRef;
+import net.paoding.rose.scanning.RoseScanner;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+public class RoseSettings {
+
+	protected static Log logger = LogFactory.getLog(RoseSettings.class);
+
+	protected static ResourcePatternResolver resourcePatternResolver;
+
+	public static Properties findSettingsProperties() {
+		return findSettingsProperties((String[])null);
+	}
+
+	public static Properties findSettingsProperties(LoadScope scope) {
+		return findSettingsProperties(scope.getScope("applicationContext"));
+	}
+
+	public static Properties findSettingsProperties(String[] scope) {
+		if (logger.isInfoEnabled()) {
+			logger.info("[applicationContext] start to find settings properties ...");
+		}
+
+		// new again, 不同的classloader
+		resourcePatternResolver = new PathMatchingResourcePatternResolver();
+
+		List<Resource> properties = new ArrayList<Resource>();
+		try {
+			List<ResourceRef> resources = RoseScanner.getInstance().getJarOrClassesFolderResources(scope);
+
+			for (ResourceRef ref : resources) {
+				if ("jar".equals(ref.getProtocol()))
+					properties.addAll(findProperties(ref));
+			}
+			// 目录的配置覆盖jar里的配置
+			for (ResourceRef ref : resources) {
+				if (! "jar".equals(ref.getProtocol()))
+					properties.addAll(findProperties(ref));
+			}
+		}
+		catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+
+		int index = 0;
+		Resource settings = null;
+		Properties props = new Properties();
+		for (Resource resource : properties) {
+			try {
+				if ("settings.properties".equals(resource.getFilename()))
+					settings = resource;
+
+				PropertiesLoaderUtils.fillProperties(props, resource);
+				index++;
+			}
+			catch (IOException e) {
+				logger.error("Load properties file fail : " + resource);
+			}
+		}
+
+		try {
+			if (settings != null)
+				PropertiesLoaderUtils.fillProperties(props, settings);
+		}
+		catch (IOException e) {
+			logger.warn("Load settings.properties fail : " + settings);
+		}
+
+		if (logger.isInfoEnabled()) {
+			logger.info("[applicationContext] FOUND " + index + " properties files");
+			if (settings != null)
+				logger.info("[applicationContext] FOUND settings.properties: " + settings);
+		}
+		if (logger.isDebugEnabled()) {
+			logger.debug("[applicationContext] FOUND settings : " + props);
+		}
+		return props;
+	}
+
+	static List<Resource> findProperties(ResourceRef ref) throws IOException {
+		Resource[] founds = ref.getInnerResources(resourcePatternResolver, "settings*.properties");
+		return Arrays.asList(founds);
+	}
+}

+ 50 - 0
rose-context/src/main/java/net/paoding/rose/app/RoseAppContext.java

@@ -0,0 +1,50 @@
+/*
+* Copyright 2007-2009 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.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 net.paoding.rose.app;
+
+/**
+ * <strong>此类将在2010-06-01删除</strong>,请现在立即改使用另外一个package的同名类
+ * {@link net.paoding.rose.scanning.context.AnnotationContext}
+ * <p>
+ * 
+ * @deprecated
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author han.liao [in355hz@gmail.com]
+ */
+@Deprecated
+public class RoseAppContext extends net.paoding.rose.scanning.context.AnnotationContext {
+
+    /**
+     * 创建 RoseAppContext.
+     * 
+     * @param contextConfigLocation - 配置加载路径
+     */
+    public RoseAppContext() {
+        super();
+    }
+
+    /**
+     * 创建 RoseAppContext.
+     * 
+     * @param packages - "com.xiaonei.yourapp, com.xiaonei.myapp" ... <br>
+     *        表示加载这些package所在的jar或根class目录的/applicationContext*.xml文件
+     */
+    public RoseAppContext(String packages, boolean refresh) {
+        super(packages, refresh);
+    }
+
+}

+ 106 - 0
rose-context/src/main/java/net/paoding/rose/scanning/LoadScope.java

@@ -0,0 +1,106 @@
+/*
+ * Copyright 2007-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 net.paoding.rose.scanning;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public class LoadScope {
+
+    // controllers->com.yourcompany.yourapp
+    // applicationContext->com.yourcampany.yourapp
+    // ...
+    private Map<String, String[]> load = new HashMap<String, String[]>();
+
+    /**
+     * 通过一个一个开发者设置的字符串,创建一个LoadScope对象。
+     * 如果在loadScope中没有指定componetType的,使用defType作为他的componetType.
+     * <p>
+     * loadScopeString: componetConf [; componetConf]*<br>
+     * componetConf: [componetType = ] componetConfValue<br>
+     * componetType: 'controllers' | 'applicationContext' | 'messages' |
+     * '*' <br>
+     * componetConfValue: package [, packages]*<br>
+     * 
+     * @param loadScopeString
+     * @param defType
+     */
+    public LoadScope(String loadScopeString, String defType) {
+        init(loadScopeString, defType);
+    }
+
+    public String[] getScope(String componentType) {
+        String[] scope = this.load.get(componentType);
+        if (scope == null) {
+            scope = this.load.get("*");
+        }
+        return scope;
+    }
+
+    private void init(String loadScope, String defType) {
+        if (StringUtils.isBlank(loadScope) || "*".equals(loadScope)) {
+            return;
+        }
+        loadScope = loadScope.trim();
+        String[] componetConfs = StringUtils.split(loadScope, ";");
+        for (String componetConf : componetConfs) {
+            if (StringUtils.isBlank(loadScope)) {
+                continue;
+            }
+            // 代表"controllers=com.renren.xoa, com.renren.yourapp"串
+            componetConf = componetConf.trim();
+            int componetTypeIndex;
+            String componetType = defType; // 代表"controllers", "applicationContext", "dao", "messages", "*"等串
+            String componetConfValue = componetConf;
+            if ((componetTypeIndex = componetConf.indexOf('=')) != -1) {
+                componetType = componetConf.substring(0, componetTypeIndex).trim();
+                componetConfValue = componetConf.substring(componetTypeIndex + 1).trim();
+            }
+            if (componetType.startsWith("!")) {
+                componetType = componetType.substring(1);
+            } else {
+                componetConfValue = componetConfValue + ", net.paoding.rose";
+            }
+            String[] packages = StringUtils.split(componetConfValue, ", \t\n\r\0");//都好和\t之间有一个空格
+            this.load.put(componetType, packages);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        for (Entry<String, String[]> componetConf : load.entrySet()) {
+            String componetType = componetConf.getKey();
+            String componetConfValue[] = componetConf.getValue();
+            sb.append(componetType).append("=");
+            for (String value : componetConfValue) {
+                sb.append(value).append(";");
+            }
+            if (componetConfValue.length > 0) {
+                sb.setLength(sb.length() - 1);
+            }
+        }
+        return super.toString();
+    }
+}

+ 239 - 0
rose-context/src/main/java/net/paoding/rose/scanning/ResourceRef.java

@@ -0,0 +1,239 @@
+/*
+ * Copyright 2007-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 net.paoding.rose.scanning;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.UrlResource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.util.Assert;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * 
+ * 
+ * @author zhiliang.wang 王志亮 [qieqie.wang@gmail.com]
+ */
+public class ResourceRef implements Comparable<ResourceRef> {
+
+    private static final Log logger = LogFactory.getLog(ResourceRef.class);
+
+    private Properties properties = new Properties();
+
+    private Resource resource;
+
+    private String[] modifiers;
+
+    public static ResourceRef toResourceRef(Resource folder) throws IOException {
+        ResourceRef rr = new ResourceRef(folder, null, null);
+        String[] modifiers = null;
+        Resource rosePropertiesResource = rr.getInnerResource("META-INF/rose.properties");
+        if (rosePropertiesResource.exists()) {
+            if (logger.isDebugEnabled()) {
+                logger.debug("found rose.properties: " + rosePropertiesResource.getURI());
+            }
+            InputStream in = rosePropertiesResource.getInputStream();
+            rr.properties.load(in);
+            in.close();
+            String attrValue = rr.properties.getProperty("rose");
+            if (attrValue == null) {
+                attrValue = rr.properties.getProperty("Rose");
+            }
+            if (attrValue != null) {
+                modifiers = StringUtils.split(attrValue, ", ;\n\r\t");
+                if (logger.isDebugEnabled()) {
+                    logger.debug("modifiers[by properties][" + rr.getResource().getURI() + "]="
+                            + Arrays.toString(modifiers));
+                }
+            }
+        }
+        //
+        if (modifiers == null) {
+            if (!"jar".equals(rr.getProtocol())) {
+                modifiers = new String[] { "**" };
+                if (logger.isDebugEnabled()) {
+                    logger.debug("modifiers[by default][" + rr.getResource().getURI() + "]="
+                            + Arrays.toString(modifiers));
+                }
+            } else {
+        		JarFile jarFile = new JarFile(rr.getResource().getFile());
+                Manifest manifest = jarFile.getManifest();
+                if (manifest != null) {
+                    Attributes attributes = manifest.getMainAttributes();
+                    String attrValue = attributes.getValue("rose");
+                    if (attrValue == null) {
+                        attrValue = attributes.getValue("Rose");
+                    }
+                    if (attrValue != null) {
+                        modifiers = StringUtils.split(attrValue, ", ;\n\r\t");
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("modifiers[by manifest.mf][" + rr.getResource().getURI()
+                                    + "]=" + Arrays.toString(modifiers));
+                        }
+                    }
+                }
+                jarFile.close();
+            }
+        }
+        rr.setModifiers(modifiers);
+        return rr;
+    }
+
+    public ResourceRef(Resource resource, String[] modifiers, Properties p) {
+        setResource(resource);
+        if (modifiers != null) {
+            setModifiers(modifiers);
+        }
+        if (p != null) {
+            properties.putAll(p);
+        }
+    }
+
+    public Properties getProperties() {
+        return properties;
+    }
+
+    public void setResource(Resource resource) {
+        this.resource = resource;
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public String[] getModifiers() {
+        return modifiers;
+    }
+
+    public void setModifiers(String[] modifiers) {
+        this.modifiers = modifiers;
+        if (modifiers == null) {
+            properties.remove("rose");
+        } else {
+            StringBuilder sb = new StringBuilder();
+            final String separator = ", ";
+            for (String m : modifiers) {
+                sb.append(m).append(separator);
+            }
+            if (sb.length() > 0) {
+                sb.setLength(sb.length() - separator.length());
+            }
+            properties.put("rose", sb.toString());
+        }
+    }
+
+    public boolean hasModifier(String modifier) {
+        if (modifier.startsWith("<") && modifier.endsWith(">")) {
+            return ArrayUtils.contains(modifiers, modifier.substring(1, modifier.length() - 1));
+        }
+        return ArrayUtils.contains(modifiers, "**") || ArrayUtils.contains(modifiers, "*")
+                || ArrayUtils.contains(modifiers, modifier);
+    }
+
+    public Resource getInnerResource(String subPath) throws IOException {
+        Assert.isTrue(!subPath.startsWith("/"));
+        String rootPath = resource.getURI().getPath();
+        if (getProtocol().equals("jar")) {
+            return new UrlResource("jar:file:" + rootPath + "!/" + subPath);
+        } else {
+            return new FileSystemResource(rootPath + subPath); // 已使用FileSystemResource不用file:打头
+        }
+    }
+
+    public Resource[] getInnerResources(ResourcePatternResolver resourcePatternResolver,
+            String subPath) throws IOException {
+        subPath = getInnerResourcePattern(subPath);
+        return resourcePatternResolver.getResources(subPath);
+    }
+
+    public String getInnerResourcePattern(String subPath) throws IOException {
+        Assert.isTrue(!subPath.startsWith("/"), subPath);
+        String rootPath = resource.getURI().getPath();
+        if (getProtocol().equals("jar")) {
+            subPath = "jar:file:" + rootPath + ResourceUtils.JAR_URL_SEPARATOR + subPath;
+        } else {
+            subPath = "file:" + rootPath + subPath;
+        }
+        return subPath;
+    }
+
+    public String getProtocol() {
+        if (resource.getFilename().toLowerCase().endsWith(".jar")
+                || resource.getFilename().toLowerCase().endsWith(".zip")
+                || resource.getFilename().toLowerCase().endsWith(".tar")
+                || resource.getFilename().toLowerCase().endsWith(".gz")) {
+        	try {
+				if(resource.getFile().isFile()){
+					 return "jar";
+				}
+			} catch (IOException e) {
+				// TODO Auto-generated catch block
+				logger.error(e, e);
+			}
+        }
+        return "file";
+    }
+
+    @Override
+    public int compareTo(ResourceRef o) {
+        try {
+            return this.resource.getURI().compareTo(o.resource.getURI());
+        } catch (IOException e) {
+            throw new Error(e);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return 13 * resource.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (resource == null) return false;
+        if (obj instanceof Resource) {
+            return resource.equals(obj);
+        } else if (obj instanceof ResourceRef) {
+            return resource.equals(((ResourceRef) obj).resource);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        String[] modifiers = this.modifiers;
+        if (modifiers == null) {
+            modifiers = new String[0];
+        }
+        try {
+            return resource.getURL().getFile() + Arrays.toString(modifiers);
+        } catch (IOException e) {
+            return resource + Arrays.toString(modifiers);
+        }
+    }
+}

+ 338 - 0
rose-context/src/main/java/net/paoding/rose/scanning/RoseScanner.java

@@ -0,0 +1,338 @@
+/*
+ * Copyright 2007-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 net.paoding.rose.scanning;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.collections.map.ReferenceMap;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.util.Assert;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * @author zhiliang.wang 王志亮 [qieqie.wang@gmail.com]
+ */
+public class RoseScanner {
+
+	private static ReferenceMap INSTANCES = new ReferenceMap();
+
+	public static RoseScanner getInstance() {
+		ClassLoader cl = Thread.currentThread().getContextClassLoader();
+		RoseScanner scanner = (RoseScanner) INSTANCES.get(cl);
+		if (scanner == null) {
+			synchronized (INSTANCES) {
+				scanner = (RoseScanner) INSTANCES.get(cl);
+				if (scanner == null) {
+					scanner = new RoseScanner();
+					INSTANCES.put(cl, scanner);
+				}
+			}
+		}
+		return scanner;
+	}
+
+	// -------------------------------------------------------------
+
+	protected Log logger = LogFactory.getLog(getClass());
+
+	protected Date createTime = new Date();
+
+	protected ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+
+	private List<ResourceRef> classesFolderResources;
+
+	private List<ResourceRef> jarResources;
+
+	// -------------------------------------------------------------
+
+	private RoseScanner() {
+	}
+
+	public Date getCreateTime() {
+		return createTime;
+	}
+
+	// -------------------------------------------------------------
+	public List<ResourceRef> getJarOrClassesFolderResources()
+			throws IOException {
+		return getJarOrClassesFolderResources(null);
+	}
+
+	public List<ResourceRef> getJarOrClassesFolderResources(String[] scope)
+			throws IOException {
+		if (logger.isInfoEnabled()) {
+			logger.info("[findFiles] start to found classes folders "
+					+ "or rosed jar files by scope:" + Arrays.toString(scope));
+		}
+		List<ResourceRef> resources;
+		if (scope == null) {
+			resources = new LinkedList<ResourceRef>();
+			if (logger.isDebugEnabled()) {
+				logger.debug("[findFiles] call 'classesFolder'");
+			}
+			resources.addAll(getClassesFolderResources());
+			//
+			if (logger.isDebugEnabled()) {
+				logger.debug("[findFiles] exits from 'classesFolder'");
+				logger.debug("[findFiles] call 'jarFile'");
+			}
+			resources.addAll(getJarResources());
+			if (logger.isDebugEnabled()) {
+				logger.debug("[findFiles] exits from 'jarFile'");
+			}
+		} else if (scope.length == 0) {
+			return new ArrayList<ResourceRef>();
+		} else {
+			resources = new LinkedList<ResourceRef>();
+			for (String scopeEntry : scope) {
+				String packagePath = scopeEntry.replace('.', '/');
+				Resource[] packageResources = resourcePatternResolver
+						.getResources("classpath*:" + packagePath);
+				for (Resource pkgResource : packageResources) {
+					String uri = pkgResource.getURI().toString();
+					uri = StringUtils.removeEnd(uri, "/");
+					packagePath = StringUtils.removeEnd(packagePath, "/");
+					uri = StringUtils.removeEnd(uri, packagePath);
+					int beginIndex = uri.lastIndexOf("file:");
+					if (beginIndex == -1) {
+						beginIndex = 0;
+					} else {
+						beginIndex += "file:".length();
+					}
+					int endIndex = uri.lastIndexOf('!');
+					if (endIndex == -1) {
+						endIndex = uri.length();
+					}
+					String path = uri.substring(beginIndex, endIndex);
+					Resource folder = new FileSystemResource(path);
+					ResourceRef ref = ResourceRef.toResourceRef(folder);
+					if (!resources.contains(ref)) {
+						resources.add(ref);
+						if (logger.isDebugEnabled()) {
+							logger.debug("[findFiles] found classes folders "
+									+ "or rosed jar files by scope:" + ref);
+						}
+					}
+				}
+			}
+		}
+		//
+		if (logger.isInfoEnabled()) {
+			logger.info("[findFiles] found " + resources.size()
+					+ " classes folders " + "or rosed jar files : " + resources);
+		}
+
+		return resources;
+	}
+
+	/**
+	 * 将要被扫描的普通类地址(比如WEB-INF/classes或target/classes之类的地址)
+	 * 
+	 * @param resourceLoader
+	 * @return
+	 * @throws IOException
+	 * @throws URISyntaxException
+	 */
+	public List<ResourceRef> getClassesFolderResources() throws IOException {
+		if (classesFolderResources == null) {
+			if (logger.isInfoEnabled()) {
+				logger.info("[classesFolder] start to found available classes folders ...");
+			}
+			List<ResourceRef> classesFolderResources = new ArrayList<ResourceRef>();
+			Enumeration<URL> founds = resourcePatternResolver.getClassLoader()
+					.getResources("");
+			while (founds.hasMoreElements()) {
+				URL urlObject = founds.nextElement();
+				if (!"file".equals(urlObject.getProtocol())) {
+					if (logger.isDebugEnabled()) {
+						logger.debug("[classesFolder] Ignored classes folder because "
+								+ "not a file protocol url: " + urlObject);
+					}
+					continue;
+				}
+				String path = urlObject.getPath();
+				Assert.isTrue(path.endsWith("/"));
+				// if (!path.endsWith("/classes/") && !path.endsWith("/bin/")) {
+				// if (logger.isInfoEnabled()) {
+				// logger.info("[classesFolder] Ignored classes folder because "
+				// + "not ends with '/classes/' or '/bin/': " + urlObject);
+				// }
+				// continue;
+				// }
+				File file;
+				try {
+					file = new File(urlObject.toURI());
+				} catch (URISyntaxException e) {
+					throw new IOException(e);
+				}
+				if (file.isFile()) {
+					if (logger.isDebugEnabled()) {
+						logger.debug("[classesFolder] Ignored because not a directory: "
+								+ urlObject);
+					}
+					continue;
+				}
+				Resource resource = new FileSystemResource(file);
+				ResourceRef resourceRef = ResourceRef.toResourceRef(resource);
+				if (classesFolderResources.contains(resourceRef)) {
+					// 删除重复的地址
+					if (logger.isDebugEnabled()) {
+						logger.debug("[classesFolder] remove replicated classes folder: "
+								+ resourceRef);
+					}
+				} else {
+					classesFolderResources.add(resourceRef);
+					if (logger.isDebugEnabled()) {
+						logger.debug("[classesFolder] add classes folder: "
+								+ resourceRef);
+					}
+				}
+			}
+			// 删除含有一个地址包含另外一个地址的
+			Collections.sort(classesFolderResources);
+			List<ResourceRef> toRemove = new LinkedList<ResourceRef>();
+			for (int i = 0; i < classesFolderResources.size(); i++) {
+				ResourceRef ref = classesFolderResources.get(i);
+				String refURI = ref.getResource().getURI().toString();
+				for (int j = i + 1; j < classesFolderResources.size(); j++) {
+					ResourceRef refj = classesFolderResources.get(j);
+					String refjURI = refj.getResource().getURI().toString();
+					if (refURI.startsWith(refjURI)) {
+						toRemove.add(refj);
+						if (logger.isInfoEnabled()) {
+							logger.info("[classesFolder] remove wrapper classes folder: " //
+									+ refj);
+						}
+					} else if (refjURI.startsWith(refURI)
+							&& refURI.length() != refjURI.length()) {
+						toRemove.add(ref);
+						if (logger.isInfoEnabled()) {
+							logger.info("[classesFolder] remove wrapper classes folder: " //
+									+ ref);
+						}
+					}
+				}
+			}
+			classesFolderResources.removeAll(toRemove);
+			//
+			this.classesFolderResources = new ArrayList<ResourceRef>(
+					classesFolderResources);
+			if (logger.isInfoEnabled()) {
+				logger.info("[classesFolder] found "
+						+ classesFolderResources.size() + " classes folders: "
+						+ classesFolderResources);
+			}
+		} else {
+			if (logger.isInfoEnabled()) {
+				logger.info("[classesFolder] found cached "
+						+ classesFolderResources.size() + " classes folders: "
+						+ classesFolderResources);
+			}
+		}
+		return Collections.unmodifiableList(classesFolderResources);
+	}
+
+	/**
+	 * 将要被扫描的jar资源
+	 * 
+	 * @param resourceLoader
+	 * @return
+	 * @throws IOException
+	 */
+	public List<ResourceRef> getJarResources() throws IOException {
+		if (jarResources == null) {
+			if (logger.isInfoEnabled()) {
+				logger.info("[jarFile] start to found available jar files for rose to scanning...");
+			}
+			List<ResourceRef> jarResources = new LinkedList<ResourceRef>();
+			Resource[] metaInfResources = resourcePatternResolver
+					.getResources("classpath*:/META-INF/");
+			for (Resource metaInfResource : metaInfResources) {
+				URL urlObject = metaInfResource.getURL();
+				if (ResourceUtils.isJarURL(urlObject)) {
+					try {
+						String path = URLDecoder.decode(urlObject.getPath(),
+								"UTF-8"); // fix 20%
+						if (path.startsWith("file:")) {
+							path = path.substring("file:".length(),
+									path.lastIndexOf('!'));
+						} else {
+							path = path.substring(0, path.lastIndexOf('!'));
+						}
+						Resource resource = new FileSystemResource(path);
+						if (jarResources.contains(resource)) {
+							if (logger.isDebugEnabled()) {
+								logger.debug("[jarFile] skip replicated jar resource: "
+										+ path);// 在多个 linux环境 下发现有重复,fix it!
+							}
+						} else {
+							ResourceRef ref = ResourceRef
+									.toResourceRef(resource);
+							if (ref.getModifiers() != null) {
+								jarResources.add(ref);
+								if (logger.isInfoEnabled()) {
+									logger.info("[jarFile] add jar resource: "
+											+ ref);
+								}
+							} else {
+								if (logger.isDebugEnabled()) {
+									logger.debug("[jarFile] not rose jar resource: "
+											+ path);
+								}
+							}
+						}
+					} catch (Exception e) {
+						logger.error(urlObject, e);
+					}
+				} else {
+					if (logger.isDebugEnabled()) {
+						logger.debug("[jarFile] not rose type(not a jar) "
+								+ urlObject);
+					}
+				}
+			}
+			this.jarResources = jarResources;
+			if (logger.isInfoEnabled()) {
+				logger.info("[jarFile] found " + jarResources.size()
+						+ " jar files: " + jarResources);
+			}
+		} else {
+			if (logger.isInfoEnabled()) {
+				logger.info("[jarFile] found cached " + jarResources.size()
+						+ " jar files: " + jarResources);
+			}
+		}
+		return Collections.unmodifiableList(jarResources);
+	}
+
+}

+ 215 - 0
rose-context/src/main/java/net/paoding/rose/scanning/context/AnnotationContext.java

@@ -0,0 +1,215 @@
+/*
+* Copyright 2007-2009 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.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 net.paoding.rose.scanning.context;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Properties;
+
+import javax.servlet.ServletContext;
+
+import net.paoding.rose.scanning.LoadScope;
+import net.paoding.rose.scanning.context.core.RoseResources;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.beans.factory.xml.ResourceEntityResolver;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextException;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigUtils;
+import org.springframework.context.support.AbstractXmlApplicationContext;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+import org.springframework.core.io.Resource;
+
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.wzzx.rose.scanning.RoseSettings;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author han.liao [in355hz@gmail.com]
+ * 
+ */
+public class AnnotationContext extends AnnotationConfigServletWebServerApplicationContext {
+	private boolean validating = true;
+	private Log logger = LogFactory.getLog(getClass());
+
+	private String[] scopeValues;
+	private String[] configLocations;
+
+	public AnnotationContext() {
+		this("", false);
+
+	}
+
+	public void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
+//		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
+		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
+//		// Configure the bean definition reader with this context's
+//		// resource loading environment.
+		beanDefinitionReader.setEnvironment(this.getEnvironment());
+		beanDefinitionReader.setResourceLoader(this);
+		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
+//		// Allow a subclass to provide custom initialization of the reader,
+//		// then proceed with actually loading the bean definitions.
+		initBeanDefinitionReader(beanDefinitionReader);
+		loadBeanDefinitions(beanDefinitionReader);
+	}
+
+	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
+		Resource[] configResources = getConfigResources();
+		if (configResources != null) {
+			reader.loadBeanDefinitions(configResources);
+		}
+		ServletContext context = getServletContext();
+		// context.
+		String[] configLocations = getConfigLocations();
+		if (configLocations != null) {
+			reader.loadBeanDefinitions(configLocations);
+		}
+	}
+
+	/**
+	 * Initialize the bean definition reader used for loading the bean definitions
+	 * of this context. Default implementation is empty.
+	 * <p>
+	 * Can be overridden in subclasses, e.g. for turning off XML validation or using
+	 * a different XmlBeanDefinitionParser implementation.
+	 * 
+	 * @param reader the bean definition reader used by this context
+	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass
+	 */
+	protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
+		reader.setValidating(this.validating);
+	}
+
+	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
+		refreshBeanFactory();
+		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
+		try {
+			loadBeanDefinitions((DefaultListableBeanFactory) beanFactory);
+		} catch (BeansException | IOException e) {
+
+			e.printStackTrace();
+		}
+		return beanFactory;
+	}
+
+	public AnnotationContext(String scope, boolean refresh) {
+		this(new LoadScope(scope, "applicationContext"), refresh);
+	}
+
+	public AnnotationContext(LoadScope scope, boolean refresh) {
+		this.scopeValues = scope.getScope("applicationContext");
+		logger.info("create a RoseAppContext, with scope='" + scope + "'");
+		if (refresh) {
+			refresh();
+		}
+	}
+
+	// @Override
+//    public void refresh() throws BeansException, IllegalStateException {
+//        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+//        ClassLoader current = getClassLoader();
+//        try {
+//            Thread.currentThread().setContextClassLoader(current);
+//            super.refresh();
+//        }
+//        finally {
+//            if (current == Thread.currentThread().getContextClassLoader())
+//                Thread.currentThread().setContextClassLoader(cl);
+//        }
+//    }
+
+	/**
+	 * 返回对应类型的唯一 Bean, 包括可能的祖先 {@link ApplicationContext} 中对应类型的 Bean.
+	 * 
+	 * @param beanType - Bean 的类型
+	 * 
+	 * @throws BeansException
+	 */
+	public <T> T getBean(Class<T> beanType) throws BeansException {
+		return beanType.cast(BeanFactoryUtils.beanOfTypeIncludingAncestors(this, beanType));
+	}
+
+	protected final Resource[] getConfigResources() {
+		try {
+			return getConfigResourcesThrowsIOException();
+		} catch (IOException e) {
+			throw new ApplicationContextException("getConfigResources", e);
+		}
+	}
+
+	protected Resource[] getConfigResourcesThrowsIOException() throws IOException {
+		return RoseResources.findContextResources(this.scopeValues).toArray(new Resource[0]);
+	}
+
+	@Override
+	protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
+		prepareBeanFactoryByRose(beanFactory);
+		super.prepareBeanFactory(beanFactory);
+	}
+
+	/** Rose对BeanFactory的特殊处理,必要时可以覆盖这个方法去掉Rose的特有的处理 */
+	protected void prepareBeanFactoryByRose(ConfigurableListableBeanFactory beanFactory) {
+		Properties settings = RoseSettings.findSettingsProperties(scopeValues);
+		logger.info("load properties info :" + settings.toString());
+		String asm = settings.getProperty("com.fastjson.asm", "false");
+		if ("false".equalsIgnoreCase(asm)) {
+			ParserConfig.getGlobalInstance().setAsmEnable(false);
+		} else if ("true".equalsIgnoreCase(asm)) {
+			ParserConfig.getGlobalInstance().setAsmEnable(true);
+		}
+		beanFactory.registerSingleton("settings", settings);
+		BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
+		AnnotationConfigUtils.registerAnnotationConfigProcessors(registry);
+
+	}
+
+	@Override
+	public String[] getConfigLocations() {
+		return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
+	}
+
+	/**
+	 * Return the default config locations to use, for the case where no explicit
+	 * config locations have been specified.
+	 * <p>
+	 * The default implementation returns {@code null}, requiring explicit config
+	 * locations.
+	 * 
+	 * @return an array of default config locations, if any
+	 * @see #setConfigLocations
+	 */
+	protected String[] getDefaultConfigLocations() {
+		return null;
+	}
+
+	public AnnotationContext getApplicationContext() {
+		return this;
+	}
+
+}

+ 239 - 0
rose-context/src/main/java/net/paoding/rose/scanning/context/AnnotationWebContext.java

@@ -0,0 +1,239 @@
+/*
+* Copyright 2007-2009 the original author or authors.
+*
+* Licensed under the Apache License, Version 2.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 net.paoding.rose.scanning.context;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Properties;
+
+import javax.servlet.ServletContext;
+
+import net.paoding.rose.scanning.LoadScope;
+import net.paoding.rose.scanning.context.core.RoseResources;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.beans.factory.xml.ResourceEntityResolver;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextException;
+import org.springframework.context.annotation.AnnotationConfigUtils;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+import org.springframework.core.io.Resource;
+
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.wzzx.rose.scanning.RoseSettings;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author han.liao [in355hz@gmail.com]
+ * 
+ */
+public class AnnotationWebContext extends AnnotationConfigServletWebServerApplicationContext {
+	private Log logger = LogFactory.getLog(getClass());
+	/** Default config location for the root context */
+	//public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext*.xml";
+	public static final String DEFAULT_CONFIG_LOCATION = "";
+	private boolean validating = true;
+	private LoadScope scope;
+	private String[] configLocations;
+	public AnnotationWebContext() {
+		scope = new LoadScope("", "controllers");
+	}
+
+	public AnnotationWebContext(ServletContext servletContext, boolean refresh) {
+		this(servletContext, "", refresh);
+	}
+
+	public AnnotationWebContext(ServletContext servletContext, String scope, boolean refresh) {
+		this(servletContext, new LoadScope(scope, "controllers"), refresh);
+	}
+
+	public AnnotationWebContext(ServletContext servletContext, LoadScope scope, boolean refresh) {
+		this.scope = scope;
+		this.setServletContext(servletContext);
+		if (refresh) {
+			refresh();
+		}
+	}
+
+	/**
+	 * 返回对应类型的唯一 Bean, 包括可能的祖先 {@link ApplicationContext} 中对应类型的 Bean.
+	 * 
+	 * @param beanType - Bean 的类型
+	 * 
+	 * @throws BeansException
+	 */
+	public <T> T getBean(Class<T> beanType) throws BeansException {
+		return beanType.cast(BeanFactoryUtils.beanOfTypeIncludingAncestors(this, beanType));
+	}
+
+	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
+		refreshBeanFactory();
+		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
+		try {
+			loadBeanDefinitions((DefaultListableBeanFactory) beanFactory);
+		} catch (BeansException | IOException e) {
+
+			e.printStackTrace();
+		}
+		return beanFactory;
+	}
+
+	/**
+	 * Initialize the bean definition reader used for loading the bean definitions
+	 * of this context. Default implementation is empty.
+	 * <p>
+	 * Can be overridden in subclasses, e.g. for turning off XML validation or using
+	 * a different XmlBeanDefinitionParser implementation.
+	 * 
+	 * @param reader the bean definition reader used by this context
+	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass
+	 */
+	protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
+		reader.setValidating(this.validating);
+	}
+
+	public void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
+//		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
+		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
+//		// Configure the bean definition reader with this context's
+//		// resource loading environment.
+		beanDefinitionReader.setEnvironment(this.getEnvironment());
+		beanDefinitionReader.setResourceLoader(this);
+		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
+//		// Allow a subclass to provide custom initialization of the reader,
+//		// then proceed with actually loading the bean definitions.
+		initBeanDefinitionReader(beanDefinitionReader);
+		loadBeanDefinitions(beanDefinitionReader);
+	}
+
+	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
+		Resource[] configResources = getConfigResourcesThrows();
+		if (configResources != null) {
+			reader.loadBeanDefinitions(configResources);
+		}
+		String[] configLocations = getConfigLocations();
+		if (configLocations != null) {
+			for (int i = 0; i < configLocations.length; i++) {
+				reader.loadBeanDefinitions(configLocations[i]);
+			}
+		}
+	}
+
+	@Override
+	public String[] getConfigLocations() {
+		return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
+	}
+
+	protected Resource[] getConfigResourcesThrows() throws IOException {
+		return RoseResources.findContextResources(scope).toArray(new Resource[0]);
+	}
+
+	/**
+	 * The default location for the root context is
+	 * "/WEB-INF/applicationContext*.xml".
+	 */
+	protected String[] getDefaultConfigLocations() {
+		//return new String[] { AnnotationWebContext.DEFAULT_CONFIG_LOCATION };
+		return new String[] {};
+	}
+
+	@Override
+	protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
+		Properties settings = RoseSettings.findSettingsProperties(scope);
+		// fastjson 反解析全局变量设置
+		String asm = settings.getProperty("com.fastjson.asm", "false");
+		if ("false".equalsIgnoreCase(asm)) {
+			ParserConfig.getGlobalInstance().setAsmEnable(false);
+		} else if ("true".equalsIgnoreCase(asm)) {
+			ParserConfig.getGlobalInstance().setAsmEnable(true);
+		}
+		beanFactory.registerSingleton("settings", settings);
+		try {
+			prepareBeanFactoryByRose(beanFactory);
+		} catch (IOException e) {
+			throw new ApplicationContextException("", e);
+		}
+		super.prepareBeanFactory(beanFactory);
+	}
+
+	/**
+	 * Rose对BeanFactory的特殊处理,必要时可以覆盖这个方法去掉Rose的特有的处理
+	 * 
+	 * @throws IOException
+	 */
+	protected void prepareBeanFactoryByRose(ConfigurableListableBeanFactory beanFactory) throws IOException {
+		BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
+		AnnotationConfigUtils.registerAnnotationConfigProcessors(registry);
+
+		String[] messageBaseNames = getMessageBaseNames();
+		if (messageBaseNames != null && messageBaseNames.length > 0) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("[roseWebApp/messages] starting registerMessageSourceIfNecessary");
+			}
+			registerMessageSourceIfNecessary(registry, messageBaseNames);
+			if (logger.isDebugEnabled()) {
+				logger.debug("[roseWebApp/messages] finished registerMessageSourceIfNecessary");
+			}
+		}
+	}
+
+	protected String[] getMessageBaseNames() throws IOException {
+		//
+		if (logger.isInfoEnabled()) {
+			logger.info("[roseWebApp/messages] start  ...");
+		}
+		String[] messageBasenames = RoseResources.findMessageBasenames(scope);
+
+		if (logger.isInfoEnabled()) {
+			logger.info("[roseWebApp/messages] exits ");
+			logger.info("[roseWebApp/messages] add default messages base name: " + "'/WEB-INF/messages'");
+		}
+
+		messageBasenames = Arrays.copyOf(messageBasenames, messageBasenames.length + 1);
+		messageBasenames[messageBasenames.length - 1] = "/WEB-INF/messages";
+
+		return messageBasenames;
+
+	}
+
+	/** 如果配置文件没有自定义的messageSource定义,则由Rose根据最佳实践进行预设 */
+	public static void registerMessageSourceIfNecessary(BeanDefinitionRegistry registry, String[] messageBaseNames) {
+		if (!registry.containsBeanDefinition(MESSAGE_SOURCE_BEAN_NAME)) {
+			GenericBeanDefinition messageSource = new GenericBeanDefinition();
+			messageSource.setBeanClass(ReloadableResourceBundleMessageSource.class);
+			MutablePropertyValues propertyValues = new MutablePropertyValues();
+			propertyValues.addPropertyValue("useCodeAsDefaultMessage", true);
+			propertyValues.addPropertyValue("defaultEncoding", "UTF-8"); // properties文件也将使用UTF-8编辑,而非默认的ISO-9959-1
+			propertyValues.addPropertyValue("cacheSeconds", 60); // 暂时hardcode! seconds
+			propertyValues.addPropertyValue("basenames", messageBaseNames);
+
+			messageSource.setPropertyValues(propertyValues);
+			registry.registerBeanDefinition(MESSAGE_SOURCE_BEAN_NAME, messageSource);
+		}
+	}
+
+}

+ 45 - 0
rose-context/src/main/java/net/paoding/rose/scanning/context/RoseContext.java

@@ -0,0 +1,45 @@
+package net.paoding.rose.scanning.context;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public class RoseContext extends SpringApplication {
+	public RoseContext(Class<?>... primarySources) {
+		super(null, primarySources);
+	}
+
+	private static final String ROSE_CONTEXT_CLASS = "net.paoding.rose.scanning.context.AnnotationContext";
+
+	/**
+	 * Strategy method used to create the {@link ApplicationContext}. By default
+	 * this method will respect any explicitly set application context or
+	 * application context class before falling back to a suitable default.
+	 * 
+	 * @return the application context (not yet refreshed)
+	 * @see #setApplicationContextClass(Class)
+	 */
+	protected ConfigurableApplicationContext createApplicationContext() {
+		Class<?> contextClass = null;
+		if (contextClass == null) {
+			try {
+				switch (this.getWebApplicationType()) {
+				case SERVLET:
+					contextClass = Class.forName(ROSE_CONTEXT_CLASS);
+					break;
+				case REACTIVE:
+					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
+					break;
+				default:
+					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
+				}
+			} catch (ClassNotFoundException ex) {
+				throw new IllegalStateException(
+						"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
+						ex);
+			}
+		}
+		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
+	}
+}

+ 20 - 0
rose-context/src/main/java/net/paoding/rose/scanning/context/RoseSpringApplicationBuilder.java

@@ -0,0 +1,20 @@
+package net.paoding.rose.scanning.context;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+public class RoseSpringApplicationBuilder extends SpringApplicationBuilder {
+	/**
+	 * Creates a new {@link org.springframework.boot.SpringApplication} instances
+	 * from the given sources. Subclasses may override in order to provide a custom
+	 * subclass of {@link org.springframework.boot.SpringApplication}
+	 * 
+	 * @param sources the sources
+	 * @return the {@link org.springframework.boot.SpringApplication} instance
+	 * @since 1.1.0
+	 */
+	protected SpringApplication createSpringApplication(Class<?>... sources) {
+
+		return new RoseWebContext(sources);
+	}
+}

+ 45 - 0
rose-context/src/main/java/net/paoding/rose/scanning/context/RoseWebContext.java

@@ -0,0 +1,45 @@
+package net.paoding.rose.scanning.context;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public class RoseWebContext extends SpringApplication {
+	public RoseWebContext(Class<?>... primarySources) {
+		super(null, primarySources);
+	}
+
+	private static final String ROSE_CONTEXT_CLASS = "net.paoding.rose.scanning.context.AnnotationWebContext";
+
+	/**
+	 * Strategy method used to create the {@link ApplicationContext}. By default
+	 * this method will respect any explicitly set application context or
+	 * application context class before falling back to a suitable default.
+	 * 
+	 * @return the application context (not yet refreshed)
+	 * @see #setApplicationContextClass(Class)
+	 */
+	protected ConfigurableApplicationContext createApplicationContext() {
+		Class<?> contextClass = null;
+		if (contextClass == null) {
+			try {
+				switch (this.getWebApplicationType()) {
+				case SERVLET:
+					contextClass = Class.forName(ROSE_CONTEXT_CLASS);
+					break;
+				case REACTIVE:
+					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
+					break;
+				default:
+					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
+				}
+			} catch (ClassNotFoundException ex) {
+				throw new IllegalStateException(
+						"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
+						ex);
+			}
+		}
+		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
+	}
+}

+ 154 - 0
rose-context/src/main/java/net/paoding/rose/scanning/context/core/RoseResources.java

@@ -0,0 +1,154 @@
+/*
+ * Copyright 2007-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 net.paoding.rose.scanning.context.core;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.paoding.rose.scanning.LoadScope;
+import net.paoding.rose.scanning.ResourceRef;
+import net.paoding.rose.scanning.RoseScanner;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public class RoseResources {
+
+    protected static Log logger = LogFactory.getLog(RoseResources.class);
+
+    public static List<Resource> findContextResources(LoadScope load) throws IOException {
+        String[] scopeValues = load.getScope("applicationContext");
+        return findContextResources(scopeValues);
+    }
+
+    public static List<Resource> findContextResources(String[] scope) throws IOException {
+        if (logger.isInfoEnabled()) {
+            logger.info("[applicationContext] start to found applicationContext files ...");
+        }
+        if (logger.isDebugEnabled()) {
+            if (scope == null) {
+                logger.debug("[applicationContext] use default scope"
+                        + " [all class folders and rosed jar files]");
+            } else {
+                logger.debug("[applicationContext] use scope: " + Arrays.toString(scope));
+            }
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug("[applicationContext] call 'findFiles'");
+        }
+        List<ResourceRef> resources = RoseScanner.getInstance().getJarOrClassesFolderResources(
+                scope);
+        if (logger.isDebugEnabled()) {
+            logger.debug("[applicationContext] exits from 'findFiles'");
+            logger.debug("[applicationContext] it has " + resources.size()
+                    + " classes folders or jar files " + "in the applicationContext scope: "
+                    + resources);
+
+            logger.debug("[applicationContext] iterates the 'findFiles'"
+                    + " classes folders or jar files; size=" + resources.size());
+        }
+
+        List<Resource> ctxResources = new LinkedList<Resource>();
+        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+        int index = 0;
+        for (ResourceRef ref : resources) {
+            index++;
+            if (ref.hasModifier("applicationContext")) {
+                Resource[] founds = ref.getInnerResources(resourcePatternResolver,
+                        "applicationContext*.xml");
+                List<Resource> asList = Arrays.asList(founds);
+                ctxResources.addAll(asList);
+                if (logger.isDebugEnabled()) {
+                    logger.debug("[applicationContext] found applicationContext resources ("
+                            + index + "/" + resources.size() + ": " + asList);
+                }
+            } else {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("[applicationContext] ignored bacause not marked as"
+                            + " 'rose:applicationContext' or 'rose:*'  (" + index + "/"
+                            + resources.size() + ": " + ref);
+                }
+            }
+        }
+        if (logger.isInfoEnabled()) {
+            logger.info("[applicationContext] FOUND " + ctxResources.size()
+                    + " applcationContext files: " + ctxResources);
+        }
+        return ctxResources;
+    }
+
+    public static String[] findMessageBasenames(LoadScope load) throws IOException {
+        String[] scopeValues = load.getScope("messages");
+        return findMessageBasenames(scopeValues);
+
+    }
+
+    public static String[] findMessageBasenames(String[] scope) throws IOException {
+        if (logger.isInfoEnabled()) {
+            logger.info("[messages] start to found rose.root 'messages' files ...");
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("[messages] call 'scoped'");
+        }
+        List<ResourceRef> resources = RoseScanner.getInstance().getJarOrClassesFolderResources(
+                scope);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("[messages] exits from 'scoped'");
+            logger.debug("[messages] it has " + resources.size() + " classes folders or jar files "
+                    + "in the messages scope: " + resources);
+
+            logger.debug("[messages] iterates the 'scoped'"
+                    + " classes folders or jar files; size=" + resources.size());
+        }
+
+        List<String> messagesResources = new LinkedList<String>();
+        int index = 0;
+        for (ResourceRef ref : resources) {
+            index++;
+            if (ref.hasModifier("messages")) {
+                messagesResources.add(ref.getInnerResourcePattern("messages"));
+                if (logger.isDebugEnabled()) {
+                    logger.debug("[messages] add messages base name (" + index + "/"
+                            + resources.size() + ": " + ref);
+                }
+            } else {
+                if (logger.isDebugEnabled()) {
+                    logger
+                            .debug("[messages] ignored bacause not marked as 'rose:messages' or 'rose:*'  ("
+                                    + index + "/" + resources.size() + ": " + ref);
+                }
+            }
+        }
+        if (logger.isInfoEnabled()) {
+            logger.info("[messages] found " + messagesResources.size() + " messages base names: "
+                    + messagesResources);
+        }
+
+        return messagesResources.toArray(new String[0]);
+    }
+}

+ 32 - 0
rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileContent.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007-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 net.paoding.rose.scanning.vfs;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+
+public interface FileContent {
+
+    public InputStream getInputStream() throws IOException;
+
+}

+ 53 - 0
rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileName.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright 2007-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 net.paoding.rose.scanning.vfs;
+
+import java.io.IOException;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public interface FileName {
+
+    /**
+     * 返回所代表的文件或实体对象
+     * 
+     * @return
+     * @throws IOException
+     */
+    public FileObject getFileObject() throws IOException;
+
+    /**
+     * 基础文件名,即使是目录也不以'/'开始或结尾
+     * 
+     * @return
+     * @throws IOException
+     */
+    public String getBaseName() throws IOException;
+
+    /**
+     * 一个下级文件或实体相对于本文件或实体的路径,得到的返回字符串不以'/'开始,如果subFileName是一个子目录的话,返回的结果将以
+     * '/'结尾
+     * 
+     * @param subFileName
+     * @return
+     * @throws IOException
+     */
+    public String getRelativeName(FileName subFileName) throws IOException;
+
+}

+ 56 - 0
rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileNameImpl.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2007-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 net.paoding.rose.scanning.vfs;
+
+import java.io.IOException;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ *
+ */
+public class FileNameImpl implements FileName {
+
+    private final FileObject fileObject;
+
+    private final String baseName;
+
+    public FileNameImpl(FileObject fileObject, String baseName) {
+        this.fileObject = fileObject;
+        this.baseName = baseName;
+    }
+
+    @Override
+    public String getBaseName() {
+        return baseName;
+    }
+
+    @Override
+    public FileObject getFileObject() {
+        return fileObject;
+    }
+
+    @Override
+    public String getRelativeName(FileName subFileName) throws IOException {
+        String basePath = fileObject.getURL().getPath();
+        String subPath = subFileName.getFileObject().getURL().getPath();
+        if (!subPath.startsWith(basePath)) {
+            throw new IllegalArgumentException("basePath='" + basePath + "'; subPath='" + subPath
+                    + "'");
+        }
+        return subPath.substring(basePath.length());
+    }
+}

+ 99 - 0
rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileObject.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright 2007-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 net.paoding.rose.scanning.vfs;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public interface FileObject {
+
+    /**
+     * 该文件是否存在
+     * 
+     * @return
+     * @throws IOException
+     */
+    boolean exists() throws IOException;
+
+    /**
+     * 返回该文件或实体的名称对象
+     * 
+     * @return
+     * @throws IOException
+     */
+    FileName getName() throws IOException;
+
+    /**
+     * 返回子文件或实体(包括目录)
+     * 
+     * @return
+     * @throws IOException
+     */
+    FileObject[] getChildren() throws IOException;
+
+    /**
+     * 文件的类型,目录或文件。jar文件里面实体也可能是目录或文件。
+     * 
+     * @return
+     * @throws IOException
+     */
+    FileType getType() throws IOException;
+
+    /**
+     * 返回该文件或实体的URL对象
+     * 
+     * @return
+     * @throws IOException
+     */
+    URL getURL() throws IOException;
+
+    /**
+     * 父目录对象
+     * 
+     * @return
+     * @throws IOException
+     */
+    FileObject getParent() throws IOException;
+
+    /**
+     * 子文件或实体
+     * 
+     * @param name
+     * @return
+     * @throws IOException
+     */
+    FileObject getChild(String name) throws IOException;
+
+    /**
+     * 文件实体内容,如果是目录将抛出IOE异常
+     * 
+     * @return
+     * @throws IOException
+     */
+    FileContent getContent() throws IOException;
+
+    @Override
+    public boolean equals(Object obj);
+
+    @Override
+    public int hashCode();
+
+}

+ 117 - 0
rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileSystemManager.java

@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-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 net.paoding.rose.scanning.vfs;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Map;
+
+import org.apache.commons.collections.map.LRUMap;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public class FileSystemManager {
+
+    protected Log logger = LogFactory.getLog(FileSystemManager.class);
+
+    private boolean traceEnabled = logger.isTraceEnabled();
+
+    @SuppressWarnings("unchecked")
+    private Map<String, FileObject> cached = new LRUMap(10000);
+
+    public FileObject resolveFile(String urlString) throws IOException {
+        if (traceEnabled) {
+            logger.trace("[fs] resolveFile ... by urlString '" + urlString + "'");
+        }
+        FileObject object = cached.get(urlString);
+        if (object == null && !urlString.endsWith("/")) {
+            object = cached.get(urlString + "/");
+        }
+        if (object != null) {
+            if (traceEnabled) {
+                logger.trace("[fs][s] found cached file for urlString '" + urlString + "'");
+            }
+            return object;
+        }
+        // not found in cache, resolves it!
+        return resolveFile(new URL(urlString));
+    }
+
+    public synchronized FileObject resolveFile(URL url) throws IOException {
+        try {
+            if (traceEnabled) {
+                logger.trace("[fs] resolveFile ... by url '" + url + "'");
+            }
+            String urlString = url.toString();
+            FileObject object = cached.get(urlString);
+
+            if (object != null) {
+                if (traceEnabled) {
+                    logger.trace("[fs] found cached file for url '" + urlString + "'");
+                }
+                return object;
+            }
+            if (ResourceUtils.isJarURL(url)) {
+                if (!urlString.endsWith("/")) {
+                    object = resolveFile(urlString + "/");
+                }
+                if (object == null || !object.exists()) {
+                    object = new JarFileObject(this, url);
+                    if (traceEnabled) {
+                        logger.trace("[fs] create jarFileObject for '" + urlString + "'");
+                    }
+                }
+            } else {
+                File file = ResourceUtils.getFile(url);
+                if (file.isDirectory()) {
+                    if (!urlString.endsWith("/")) {
+                        urlString = urlString + "/";
+                        url = new URL(urlString);
+                    }
+                } else if (file.isFile()) {
+                    if (urlString.endsWith("/")) {
+                        urlString = StringUtils.removeEnd(urlString, "/");
+                        url = new URL(urlString);
+                    }
+                }
+                object = new SimpleFileObject(this, url);
+                if (traceEnabled) {
+                    logger.trace("[fs] create simpleFileObject for '" + urlString + "'");
+                }
+            }
+            if (object.exists()) {
+                cached.put(urlString, object);
+            }
+            return object;
+        } catch (IOException e) {
+            logger.error(e.getMessage() + ":" + url, e);
+            throw e;
+        }
+    }
+
+    public synchronized void clearCache() {
+        cached.clear();
+    }
+
+}

+ 62 - 0
rose-context/src/main/java/net/paoding/rose/scanning/vfs/FileType.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2007-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 net.paoding.rose.scanning.vfs;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public class FileType {
+
+    public static final FileType FOLDER = new FileType(true, false);
+
+    public static final FileType FILE = new FileType(false, true);
+
+    public static final FileType UNKNOWN = new FileType(false, false);
+
+    private boolean hasChildren = false;
+
+    private boolean hasContent = false;
+
+    private FileType(boolean hasChildren, boolean hasContent) {
+        this.hasChildren = hasChildren;
+        this.hasContent = hasContent;
+    }
+
+    public boolean hasChildren() {
+        return hasChildren;
+    }
+
+    public boolean hasContent() {
+        return hasContent;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof FileType)) {
+            return false;
+        }
+        FileType t = (FileType) obj;
+        return hasChildren == t.hasChildren && hasContent == t.hasContent;
+    }
+
+    @Override
+    public String toString() {
+        return hasChildren ? "FOLDER" : hasContent ? "FILE" : "UNKNOWN";
+    }
+
+}

+ 195 - 0
rose-context/src/main/java/net/paoding/rose/scanning/vfs/JarFileObject.java

@@ -0,0 +1,195 @@
+/*
+ * Copyright 2007-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 net.paoding.rose.scanning.vfs;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.springframework.util.ResourceUtils;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public class JarFileObject implements FileObject {
+
+    // 文件解析管理器,可保证同样的url对应同一个fileObject对象
+    private final FileSystemManager fs;
+
+    // 该文件对象的url,如jar:file:/path/to/your/jarfile.jar!/net/paoding/
+    private final URL url;
+
+    // 该文件对象的url,如jar:file:/path/to/your/jarfile.jar!/net/paoding/
+    private final String urlString;
+
+    // 文件名称对象,用来获取文件名称、计算相对地址等
+    private final FileName fileName;
+
+    // 该文件所属的jar文件的根级FileObject对象,如jar:file:/path/to/your/jarfile.jar!/
+    private final JarFileObject root;
+
+    // 该文件所属的jar文件的JarFile对象
+    private final JarFile jarFile;
+
+    // 该文件在JarFile中的JarEntry对象,如net/paoding, com/yourcampany/yourapp
+    // 如果是根地址,如xxx.jar!/,entry将为null
+    private final JarEntry entry;
+
+    JarFileObject(FileSystemManager fs, URL url) throws FileNotFoundException, IOException {
+        this.fs = fs;
+        String urlString = url.toString();
+        String entryName = urlString.substring(urlString.indexOf(ResourceUtils.JAR_URL_SEPARATOR)
+                + ResourceUtils.JAR_URL_SEPARATOR.length());
+        if (entryName.length() == 0) {
+            this.root = this;
+            int beginIndex = urlString.indexOf(ResourceUtils.FILE_URL_PREFIX)
+                    + ResourceUtils.FILE_URL_PREFIX.length();
+            int endIndex = urlString.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
+            this.jarFile = new JarFile(urlString.substring(beginIndex, endIndex));
+        } else {
+            this.root = (JarFileObject) fs.resolveFile(urlString.substring(//
+                    0, urlString.indexOf(ResourceUtils.JAR_URL_SEPARATOR)
+                            + ResourceUtils.JAR_URL_SEPARATOR.length()));
+            this.jarFile = root.jarFile;
+        }
+        this.entry = jarFile.getJarEntry(entryName);
+        this.url = url;
+        this.urlString = urlString;
+        int indexSep = entryName.lastIndexOf('/');
+        if (indexSep == -1) {
+            this.fileName = new FileNameImpl(this, entryName);
+        } else {
+            if (entryName.endsWith("/")) {
+                int index = entryName.lastIndexOf('/', entryName.length() - 2);
+                this.fileName = new FileNameImpl(this, entryName.substring(index + 1, indexSep));
+            } else {
+                this.fileName = new FileNameImpl(this, entryName.substring(indexSep + 1));
+            }
+        }
+    }
+
+    @Override
+    public FileObject getChild(String name) throws IOException {
+        return fs.resolveFile(urlString + name);
+    }
+
+    @Override
+    public FileObject[] getChildren() throws IOException {
+        List<FileObject> children = new LinkedList<FileObject>();
+        Enumeration<JarEntry> e = jarFile.entries();
+        String entryName = root == this ? "" : entry.getName();
+        while (e.hasMoreElements()) {
+            JarEntry entry = e.nextElement();
+            if (entry.getName().length() > entryName.length()
+                    && entry.getName().startsWith(entryName)) {
+                int index = entry.getName().indexOf('/', entryName.length() + 1);
+                // 儿子=文件或子目录
+                if (index == -1 || index == entry.getName().length() - 1) {
+                    children.add(fs.resolveFile(root.urlString + entry.getName()));
+                }
+            }
+        }
+        return children.toArray(new FileObject[0]);
+    }
+
+    @Override
+    public FileContent getContent() throws IOException {
+        if (getType() != FileType.FILE) {
+            throw new IOException("can not read");
+        }
+        return new FileContent() {
+
+            @Override
+            public InputStream getInputStream() throws IOException {
+                return getURL().openStream();
+            }
+        };
+    }
+
+    @Override
+    public FileName getName() throws IOException {
+        return fileName;
+    }
+
+    @Override
+    public FileObject getParent() throws IOException {
+        if (entry == null) {
+            return null;
+        }
+        if (entry.getName().length() == 0) {
+            return null;
+        }
+        int lastSep = entry.getName().lastIndexOf('/');
+        if (lastSep == -1) {
+            return root;
+        }
+        String entryName = entry.getName();
+        String parentEntryName;
+        if (entry.isDirectory()) {
+            parentEntryName = entryName.substring(0, 1 + entryName.lastIndexOf('/', entryName
+                    .length() - 2));
+        } else {
+            parentEntryName = entryName.substring(0, 1 + lastSep);
+        }
+        return fs.resolveFile(root.urlString + parentEntryName);
+    }
+
+    @Override
+    public FileType getType() throws IOException {
+        return (entry == null || entry.isDirectory()) ? FileType.FOLDER : FileType.FILE;
+    }
+
+    @Override
+    public URL getURL() throws IOException {
+        return url;
+    }
+
+    @Override
+    public boolean exists() throws IOException {
+        return root == this || entry != null;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof JarFileObject)) {
+            return false;
+        }
+        JarFileObject t = (JarFileObject) obj;
+        return this.urlString.equals(t.urlString);
+    }
+
+    @Override
+    public int hashCode() {
+        return urlString.hashCode() * 13;
+    }
+
+    @Override
+    public String toString() {
+        return urlString;
+    }
+
+}

+ 145 - 0
rose-context/src/main/java/net/paoding/rose/scanning/vfs/SimpleFileObject.java

@@ -0,0 +1,145 @@
+/*
+ * Copyright 2007-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 net.paoding.rose.scanning.vfs;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.springframework.util.ResourceUtils;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public class SimpleFileObject implements FileObject {
+
+    private final URL url;
+
+    private final String urlString;
+
+    private final File file;
+
+    private final FileName fileName;
+
+    private final FileSystemManager fs;
+
+    SimpleFileObject(FileSystemManager fs, URL url) throws FileNotFoundException,
+            MalformedURLException {
+        this.fs = fs;
+        File file = ResourceUtils.getFile(url);
+        String urlString = url.toString();
+        this.url = url;
+        this.file = file;
+        this.urlString = urlString;
+        this.fileName = new FileNameImpl(this, file.getName());
+    }
+
+    @Override
+    public FileObject getChild(final String child) throws IOException {
+        return fs.resolveFile(urlString + child);
+    }
+
+    @Override
+    public FileObject[] getChildren() throws MalformedURLException, IOException {
+        File[] files = file.listFiles();
+        FileObject[] children = new FileObject[files.length];
+        for (int i = 0; i < children.length; i++) {
+            if (files[i].isDirectory()) {
+                children[i] = fs.resolveFile(urlString + files[i].getName() + "/");
+            } else {
+                children[i] = fs.resolveFile(urlString + files[i].getName());
+            }
+        }
+        return children;
+    }
+
+    @Override
+    public FileContent getContent() throws IOException {
+        if (!file.canRead()) {
+            throw new IOException("can not read");
+        }
+        return new FileContent() {
+
+            @Override
+            public InputStream getInputStream() throws IOException {
+                return new FileInputStream(file);
+            }
+        };
+    }
+
+    @Override
+    public FileName getName() {
+        return fileName;
+    }
+
+    @Override
+    public FileObject getParent() throws MalformedURLException, IOException {
+        File parent = file.getParentFile();
+        if (parent == null) {
+            return null;
+        }
+        return fs.resolveFile(parent.toURI().toURL());
+    }
+
+    @Override
+    public FileType getType() {
+        if (file.isFile()) {
+            return FileType.FILE;
+        } else if (file.isDirectory()) {
+            return FileType.FOLDER;
+        }
+        return FileType.UNKNOWN;
+    }
+
+    @Override
+    public URL getURL() throws MalformedURLException {
+        return url;
+    }
+
+    @Override
+    public boolean exists() throws IOException {
+        return file.exists();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof SimpleFileObject)) {
+            return false;
+        }
+        SimpleFileObject t = (SimpleFileObject) obj;
+        return this.file.equals(t.file);
+    }
+
+    @Override
+    public int hashCode() {
+        return file.hashCode() * 13;
+    }
+
+    @Override
+    public String toString() {
+        return urlString;
+    }
+
+}

+ 26 - 0
rose-context/src/main/resources/applicationContext-context.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+    xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
+    xmlns:aop="http://www.springframework.org/schema/aop"
+    xsi:schemaLocation="http://www.springframework.org/schema/beans
+                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
+                        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
+                        http://www.springframework.org/schema/context
+                        http://www.springframework.org/schema/context/spring-context-3.1.xsd
+                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
+                        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd"
+    default-autowire="no" default-lazy-init="false">
+
+    <!-- <util:properties id="settings" location="classpath:settings.properties" />
+    <bean id="settings" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+        <property name="locations">
+            <list>
+                <value>classpath*:settings-*.properties</value>
+                <value>classpath:settings.properties</value>
+            </list>
+        </property>
+        <property name="ignoreResourceNotFound" value="true"/>
+    </bean>
+     -->
+</beans>

+ 68 - 0
rose-jade/pom.xml

@@ -0,0 +1,68 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<artifactId>rose-jade</artifactId>
+	<parent>
+		<groupId>net.paoding</groupId>
+		<artifactId>root</artifactId>
+		<version>1.3-SNAPSHOT</version>
+	</parent>
+	<dependencies>
+		<dependency>
+			<groupId>commons-collections</groupId>
+			<artifactId>commons-collections</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-logging</groupId>
+			<artifactId>commons-logging</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-jdbc</artifactId>
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.paoding</groupId>
+			<artifactId>rose-context</artifactId>
+			<version>${paoding.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-lang</groupId>
+			<artifactId>commons-lang</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-context</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-jexl</groupId>
+			<artifactId>commons-jexl</artifactId>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<!-- 发布配置 -->
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-release-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<configuration>
+					<archive>
+						<manifestEntries>
+							<Rose>*</Rose>
+						</manifestEntries>
+					</archive>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 67 - 0
rose-jade/src/main/java/com/wzzx/rose/jade/shard/PartitionConfig.java

@@ -0,0 +1,67 @@
+package com.wzzx.rose.jade.shard;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+@SuppressWarnings("unchecked")
+public class PartitionConfig {
+    private static final Log logger = LogFactory.getLog(PartitionConfig.class);
+    private static Map<String, PartitionConfig> CONFIGS = new ConcurrentHashMap<String, PartitionConfig>();
+
+    private String catelog;
+    private String tableName;
+    private String pattern;
+    private int partitions;
+
+    public PartitionConfig(String catelog, String tableName) {
+        this.catelog = catelog;
+        this.tableName = tableName;
+        pattern = tableName + "_{0}";
+
+        String key = getKey(catelog, tableName);
+        if (CONFIGS.put(key, this) != null) {
+            logger.warn("Duplicate PartitionConfig : " + key);
+        }
+    }
+
+    public static String getKey(String catelog, String tableName) {
+        return catelog + "$" + tableName;
+    }
+
+    public static <T extends PartitionConfig> T getConfig(String key) {
+        if (key == null)
+            return null;
+        return (T) CONFIGS.get(key);
+    }
+
+    public static <T extends PartitionConfig> T getConfig(String catelog, String tableName) {
+        return (T) CONFIGS.get(getKey(catelog, tableName));
+    }
+
+    public String getCatelog() {
+        return catelog;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public int getPartitions() {
+        return partitions;
+    }
+
+    public void setPartitions(int partitions) {
+        this.partitions = partitions;
+    }
+
+    public String getPattern() {
+        return pattern;
+    }
+
+    public void setPattern(String pattern) {
+        this.pattern = pattern;
+    }
+}

+ 10 - 0
rose-jade/src/main/java/com/wzzx/rose/jade/shard/PartitionItem.java

@@ -0,0 +1,10 @@
+package com.wzzx.rose.jade.shard;
+
+/**
+ * 用于insert(Model)
+ *
+ * @author jingyun.zou@renren-inc.com
+ */
+public interface PartitionItem {
+	Object getShardByKey();
+}

+ 63 - 0
rose-jade/src/main/java/com/wzzx/rose/jade/shard/Table.java

@@ -0,0 +1,63 @@
+package com.wzzx.rose.jade.shard;
+
+import java.text.MessageFormat;
+
+/**
+ * 用于散表
+ *
+ * final static Table alias = new Table("user");
+ * @SQL ("SELECT id FROM $alias WHERE id=:1")
+ * int get (@ShradBy int id)
+ *
+ * @author jingyun.zou@renren-inc.com
+ */
+public class Table {
+    private String name, configKey;
+
+    public Table(String name) {
+        this.name = name;
+    }
+
+    public void setCatelog(String catelog) {
+        configKey = PartitionConfig.getKey(catelog, name);
+    }
+
+    protected PartitionConfig getConfig() {
+        return PartitionConfig.getConfig(configKey);
+    }
+
+    public String partition(Object val, PartitionConfig config) {
+        if (val instanceof Number) {
+            long part = ((Number) val).longValue() % config.getPartitions();
+            return MessageFormat.format(config.getPattern(), part);
+        }
+        return name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        PartitionConfig config = getConfig();
+        if (config == null || config.getPartitions()<=0)
+            return name;
+
+        Object param = PARAM.get();
+        if (param instanceof PartitionItem)
+            param = ((PartitionItem) param).getShardByKey();
+
+        return partition(param, config);
+    }
+
+    private static ThreadLocal<Object> PARAM = new ThreadLocal<Object>();
+
+    public static void setShardByParam(Object value) {
+        PARAM.set(value);
+    }
+
+    public static void clearShardByParam() {
+        PARAM.set(null);
+    }
+}

+ 109 - 0
rose-jade/src/main/java/com/wzzx/rose/jade/statement/PreProcessor.java

@@ -0,0 +1,109 @@
+package com.wzzx.rose.jade.statement;
+
+import java.sql.SQLSyntaxErrorException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.jdbc.BadSqlGrammarException;
+
+import net.paoding.rose.jade.annotation.SQLParam;
+import net.paoding.rose.jade.statement.StatementMetaData;
+import net.paoding.rose.jade.statement.expression.ExqlContext;
+import net.paoding.rose.jade.statement.expression.ExqlPattern;
+import net.paoding.rose.jade.statement.expression.impl.ExqlCompiler;
+import net.paoding.rose.jade.statement.expression.impl.ExqlContextImpl;
+
+/**
+ * 预处理,可以用于探测 Parameter 对应的 SQL Column
+ *
+ * @see SystemInterpreter
+ * @see JadeInvocationHandler
+ *
+ * @author jingyun.zou@renren-inc.com
+ */
+public class PreProcessor {
+
+    public static ExqlContext prepare(StatementMetaData meta) {
+        String sql = meta.getSQL();
+        int count = meta.getParameterCount();
+
+        Map<String, Object> parameters;
+        if (count == 0) {
+            parameters = new HashMap<String, Object>(4);
+        } else {
+            parameters = new HashMap<String, Object>(count * 2 + 4);
+            for (int i = 0; i < count; i++) {
+                parameters.put(":" + (i+1), PLACE_HOLDER[i]);
+                SQLParam sqlParam = meta.getSQLParamAt(i);
+                if (sqlParam != null) {
+                    parameters.put(sqlParam.value(), PLACE_HOLDER[i]);
+                }
+            }
+        }
+        ExqlCompiler compiler = new ExqlCompiler(sql);
+        ExqlPattern pattern = compiler.compile();  // No Cache
+        ExqlContextImpl context = new ExqlContextImpl(sql.length() + 32);
+
+        try {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> constants = (Map<String, Object>)meta.getDAOMetaData().getConstants();
+            for (Class<?> clazz : meta.getMethod().getParameterTypes()) {
+                if (clazz.isEnum()) {
+                    if (! (constants instanceof HashMap))
+                        constants = new HashMap<String, Object>(constants);
+                    String name = "Enum" + clazz.getSimpleName();
+                    constants.put(name, name);
+                }
+            }
+            pattern.execute(context, parameters, constants);
+        } catch (Exception e) {
+            String daoInfo = meta.toString();
+            throw new BadSqlGrammarException(daoInfo, sql,
+                    new SQLSyntaxErrorException(daoInfo + " @SQL('" + sql + "')", e));
+        }
+        return context;
+    }
+
+    /**
+     * 将 :2.id 对应到 PlaceHolder [holder=2, field="id"]
+     * obj.getFoo(), obj.isFoo(), obj.get("foo")  // TODO obj.foo
+     */
+    @SuppressWarnings({"serial", "unchecked"})
+    public static class PlaceHolder extends HashMap<String, Object> {
+
+        private final Object holder;
+        private Object field;
+
+        public PlaceHolder(Object holder) {
+            super(1);
+            this.holder = holder;
+        }
+
+        public <T> T getHolder() {
+            return (T)holder;
+        }
+
+        public <T> T getFieldName() {
+            return (T)field;
+        }
+
+        @Override
+        public boolean containsKey(Object key) {
+            return true;
+        }
+
+        @Override
+        public Object get(Object key) {
+            PlaceHolder holder = new PlaceHolder(this.holder);
+            holder.field = key;
+            return holder;
+        }
+    }
+
+    private static final int MAX_PARAMS = 30;
+    private static final PlaceHolder[] PLACE_HOLDER = new PlaceHolder[MAX_PARAMS];
+    static {
+        for (int i=0; i<MAX_PARAMS; i++)
+            PLACE_HOLDER[i] = new PlaceHolder(i);
+    }
+}

+ 33 - 0
rose-jade/src/main/java/com/wzzx/rose/jade/transaction/Transaction.java

@@ -0,0 +1,33 @@
+package com.wzzx.rose.jade.transaction;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.annotation.SQL;
+import net.paoding.rose.jade.annotation.UseMaster;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+
+public interface Transaction {
+	/**
+	 * 用于获取Master Datasource, 不执行SQL
+	 */
+	@UseMaster
+	@SQL("BEGIN TRANSACTION")
+	TransactionStatus getTransaction() throws TransactionException;
+
+	TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
+
+	void commit(TransactionStatus status) throws TransactionException;
+
+	void rollback(TransactionStatus status) throws TransactionException;
+
+	public <T> T execute(TransactionCallback<T> action) throws TransactionException;
+	
+	public DataSource getDatasource();
+	
+	public JdbcTemplate getJdbcTemplate();
+}

+ 90 - 0
rose-jade/src/main/java/com/wzzx/rose/jade/transaction/TransactionHandler.java

@@ -0,0 +1,90 @@
+package com.wzzx.rose.jade.transaction;
+
+import java.lang.reflect.Method;
+
+import net.paoding.rose.jade.context.JadeInvocationHandler;
+import net.paoding.rose.jade.dataaccess.DataAccess;
+import net.paoding.rose.jade.dataaccess.DataAccessFactory;
+import net.paoding.rose.jade.rowmapper.RowMapperFactory;
+import net.paoding.rose.jade.statement.DAOMetaData;
+import net.paoding.rose.jade.statement.InterpreterFactory;
+import net.paoding.rose.jade.statement.StatementMetaData;
+import net.paoding.rose.jade.statement.StatementWrapperProvider;
+import net.paoding.rose.jade.statement.cached.CacheProvider;
+
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+/**
+ * @author jingyun.zou@renren-inc.com
+ */
+public class TransactionHandler extends JadeInvocationHandler {
+
+	private StatementMetaData metaData;
+	
+	public TransactionHandler(final DAOMetaData daoMetaData,
+			InterpreterFactory interpreterFactory,
+			RowMapperFactory rowMapperFactory,
+			DataAccessFactory dataAccessFactory, CacheProvider cacheProvider,
+			StatementWrapperProvider statementWrapperProvider) {
+		super(daoMetaData, interpreterFactory, rowMapperFactory,
+				dataAccessFactory, cacheProvider, statementWrapperProvider);
+		try {
+			Method method = Transaction.class.getMethod("getTransaction");
+			metaData = new StatementMetaData(daoMetaData, method);
+		} catch (NoSuchMethodException e) {
+		}
+	}
+
+	/**
+	 * Master可能变更
+	 */
+	private PlatformTransactionManager getTransactionManager(Method method) {
+		DataAccess da = dataAccessFactory.getDataAccess(metaData, null);
+		return new DataSourceTransactionManager(da.getDataSource());
+	}
+
+	@Override
+	public Object invoke(Object proxy, Method method, Object[] args)
+			throws Throwable {
+		if (method.getDeclaringClass() != Transaction.class)
+			return super.invoke(proxy, method, args);
+
+		PlatformTransactionManager manager = getTransactionManager(method);
+
+		String name = method.getName();
+		if ("getTransaction".equals(name)) {
+			TransactionDefinition def = null;
+			if (args != null && args.length == 1)
+				def = (TransactionDefinition) args[0];
+			return manager.getTransaction(def);
+		}
+
+		if ("execute".equals(name)) {
+			return new TransactionTemplate(manager)
+					.execute((TransactionCallback<?>) args[0]);
+		}
+
+		if ("commit".equals(name)) {
+			manager.commit((TransactionStatus) args[0]);
+		} else if ("rollback".equals(name)) {
+			if (args[0] == null)
+				return null;
+			manager.rollback((TransactionStatus) args[0]);
+		}
+		
+		if("getDatasource".equals(name)){
+			return dataAccessFactory.getDataAccess(metaData, null).getDataSource();
+		}
+		
+		if("getJdbcTemplate".equals(name)){
+			return dataAccessFactory.getDataAccess(metaData, null).getJdbcTemplate();
+		}
+		
+		return null;
+	}
+}

+ 57 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/Cache.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 使用:{#link Cache} 标注需要缓存的 DAO 接口方法。默认的 expiry 为 0 表示没有过期限制。
+ * 
+ * @author han.liao
+ */
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Cache {
+
+    /**
+     * 标注 DAO 方法的缓存 Pool
+     * 
+     * @return 缓存 Pool
+     */
+    String pool() default "default";
+
+    /**
+     * 标注 DAO 方法的缓存 Key.
+     * 
+     * @return 缓存 Key
+     */
+    String key();
+
+    /**
+     * 标注 DAO 缓存的过期时间。
+     * 
+     * @return 缓存过期时间
+     */
+    int expiry() default 0;
+    
+    
+    Class<?> cl() default Object.class;
+}

+ 47 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/CacheDelete.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 使用:{#link CacheDelete} 标注需要在执行后清除缓存的 DAO 接口方法。
+ * 
+ * @author han.liao
+ */
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface CacheDelete {
+
+    /**
+     * 标注需要清除的缓存 Pool
+     * 
+     * @return 缓存 Pool
+     */
+    String pool() default "default";
+
+    /**
+     * 标注需要清除的缓存 Key.
+     * 
+     * @return 缓存 Key
+     */
+    String[] key();
+}

+ 70 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/DAO.java

@@ -0,0 +1,70 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.Connection;
+
+
+/**
+ * 
+ * 用此{@link DAO}注解标注在一个符合Jade编写规范的DAO接口类上,明确标注这是Jade DAO接口。
+ * 
+ * <p>
+ * 一个Jade DAO需要符合以下基本要求:
+ * <ul>
+ * <li>1、 在dao package或子package下,如com.renren.myapp.dao;</li>
+ * <li>2、 是一个public的java interface 类型;</li>
+ * <li>3、 名称必须以大写DAO字母结尾,如UserDAO;</li>
+ * <li>4、 必须标注@DAO 注解;</li>
+ * <li>5、 不是其它类的内部接口;</li>
+ * <p>
+ * 
+ * 如果DAO接口被打包成为一个jar的,为了要被Jade识别,必须在这个jar的 META-INFO/rose.properties
+ * 文件中包含这个属性:rose=dao (rose=*亦可)。
+ * 
+ * 
+ * @see http://code.google.com/p/paoding-rose/wiki/Jade_DAO_Spec
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface DAO {
+
+    /**
+     * 指定所使用数据库连接的catalog属性,设置为空(空串)等价于没有设置,表示不使用catalog属性。
+     * <p>
+     * 
+     * 一般情况下您不需要做任何设置,除非您所在的公司或组织有进一步的规范。
+     * <p>
+     * 
+     * catalog的意义请参考 {@link Connection#setCatalog(String)}
+     * ,特别地,在支持垂直切分的数据源中,也可以使用catalog作为切分的一个参考
+     * <p>
+     * 
+     * @see Connection#setCatalog(String)
+     * @return
+     */
+    String catalog() default "";
+}

+ 29 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/KeyColumnOfMap.java

@@ -0,0 +1,29 @@
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Jade支持DAO方法返回Map形式的,默认情况下Jade选取第一列作为Map的key。
+ * <p>
+ * 我们推荐您在写返回map的SQL时,把key放到第一列,但是如果真不想这样做,你可以通过本注解,即{@link KeyColumnOfMap}
+ * 进行指定。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface KeyColumnOfMap {
+
+    /**
+     * 指出要被当成map key的字段名称
+     * 
+     * @return
+     */
+    String value();
+}

+ 46 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/ReturnGeneratedKeys.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 
+ * 将 {@link ReturnGeneratedKeys} 声明在insert语句的方法上,表示返回的是插入的id。
+ * 
+ * <pre>
+ * 1、在@SQL上声明注解 @ReturnGeneratedKeys
+ * 2、方法返回值改为欲返回的数值类型,比如long、int等
+ * 例子:
+ * 
+ * &#064;ReturnGeneratedKeys
+ * &#064;SQL(&quot;insert into role(id, name) values(myseq.nextal, :1)&quot;)
+ * public long save(String name);
+ * </pre>
+ * 
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ReturnGeneratedKeys {
+}

+ 64 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/RowHandler.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RowHandler {
+
+    /**
+     * 指定自己设置的 rowMapper 类;rowMapper类应该做到无状态。
+     * 
+     * @return
+     */
+    Class<? extends RowMapper> rowMapper() default ByDefault.class;
+
+    /**
+     * 这是一个检查开关,默认为true;
+     * <p>
+     * true代表如果不是所有列都被映射给一个 Bean 的属性,抛出异常。
+     * 
+     * @return
+     */
+    boolean checkColumns() default true;
+
+    /**
+     * 这是一个检查开关,默认为false; true代表如果不是每一个bean 属性都设置了SQL查询结果的值,抛出异常。
+     * 
+     * @return
+     */
+    boolean checkProperties() default false;
+
+    class ByDefault implements RowMapper {
+
+        @Override
+        public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
+            return null;
+        }
+
+    }
+}

+ 81 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/SQL.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 用{@link SQL}注解标注在Jade DAO方法上,表示这个DAO方法所要执行的SQL语句。
+ * <p>
+ * 
+ * Jade把SQL语句分为查询类型和更改类型,Jade进行SQL类型分类的基于两个目的:<br>
+ * <ul>
+ * <li>1)因为这两种类型的SQL返回结果不一样,查询类型返回结果是一个结果集, 更新类型的SQL返回结果只是一个数字表示更新的条目;</li>
+ * <li>2)是为了能够使SQL能够在master- slave的数据库架构中发往正确的目的数据库执行。</li>
+ * </ul>
+ * <p>
+ * 
+ * 简单地,Jade认为所有以SELECT开始在SQL是查询类型的,其他的都是更新类型的。不过当然这种分法非常不合理,
+ * 比如SHOW语句所代表的就应该是查询类型的,在这种情况下,我们还是希望由开发者您在{@link SQL}
+ * 指定吧,如果有需要执行一些非SELECT的查询类型的语句的话。
+ * <p>
+ * 
+ * 在写SQL时可把SQL参数值直接放到SQL中,如下:<br>
+ * <span style='margin-left:50px;'>
+ * <code>@SQL("SELECT id, account, name FROM user WHERE id='12345'")</code>
+ * </span>
+ * <p>
+ * 也可由DAO方法的命名参数指定,因此支持了动态参数,即以冒号开始并紧跟一个名字字符串表示,如下:<br>
+ * <span style='margin-left:50px;'>
+ * <code>@SQL("SELECT id, account, name FROM user WHERE id=:userId")<span>
+ * <br>
+ * <span style='margin-left:50px;'> public User getUser(@SQLParam("userId") String id);</code><span>
+ * <p>
+ * OR<br>
+ * <span style='margin-left:50px;'>
+ * <code>@SQL("SELECT id, account, name FROM user where id=:user.id")<span>
+ * <br>
+ * <span style='margin-left:50px;'> public User getUser(@SQLParam("user") User user);</code><span> <br>
+ * <p>
+ * 在此,我们也示例了{@link SQL}注解所使用语句和标准的SQL的有所区别。为了更加有效地支持编程,此处的SQL具有较为丰富的法,具体请见:
+ * http://paoding-rose.googlecode.com/....
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface SQL {
+
+    /**
+     * 
+     * @return Jade支持的SQL语句
+     */
+    String value();
+
+    /**
+     * 返回该语句的类型,查询类型或变更类型。
+     * 默认Jade认为只有以SELECT开始的才是查询类型,其他的为变更类型。开发者通过这个属性用来变更Jade默认的处理!
+     * 
+     * @return 查询类型
+     */
+    SQLType type() default SQLType.AUTO_DETECT;
+}

+ 49 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/SQLParam.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 用{@link SQLParam} 注解标注DAO方法的参数,指定参数的名称,使得可以在SQL中通过":参数名"的方式使用它。
+ * Jade通过PreparedStatment动态地把参数值提交给数据库执行。
+ * <p>
+ * 
+ * <span style='margin-left:50px;'>
+ * <code>@SQL("SELECT id, account, name FROM user WHERE id=:userId")<span>
+ * <br>
+ * <span style='margin-left:50px;'> public User getUser(@SQLParam("userId") String id);</code><span>
+ * <p>
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+@Target( { ElementType.PARAMETER })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface SQLParam {
+
+    /**
+     * 指出这个值是 SQL 语句中哪个参数的值
+     * 
+     * @return 对应 SQL 语句中哪个参数
+     */
+    String value();
+}

+ 44 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/SQLType.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.annotation;
+
+/**
+ * SQL类型标识。
+ * <p>
+ * 在使用{@link SQL}
+ * 注解时,Jade将以SELECT开始的语句认为是查询类型SQL语句,其它的语句被认为是更新类型,开发者可以根据实际改变Jade的默认判断
+ * ,比如SHOW语句实际应该是查询类型语句,而非更新类型语句。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public enum SQLType {
+    /**
+     * 查询类型语句
+     */
+    READ,
+
+    /**
+     * 更新类型语句
+     */
+    WRITE,
+
+    /**
+     * 未知类型,将使用Jade的默认规则判断:所有以SELECT开始的语句是查询类型的,其他的是更新类型的
+     */
+    AUTO_DETECT,
+
+}

+ 24 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/ShardBy.java

@@ -0,0 +1,24 @@
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 对于sql中没有含有散表参数的或散表参数名称和配置的散表名称不一致的,通过将 ShardBy 配置在这个参数上,表示使用该参数进行散表.
+ * <p>
+ * &reg;SQL(&quot;....where name like :1&quot;)<br>
+ * public List<Xxx> find(String likeValue, &reg;ShardBy String pageId);
+ * 
+ * <pre>
+ * </pre>
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ */
+@Target({ ElementType.PARAMETER })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ShardBy {
+}

+ 49 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/ShardParam.java

@@ -0,0 +1,49 @@
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 请改为使用ShardBy放在具体的参数前。
+ * <p>
+ * 如果原来是这样的<br>
+ * &reg;ShardParam(name = "page_id", value=":2")<br>
+ * &reg;SQL("....where name like :1")<br>
+ * public void find(String likeValue, String pageId);
+ * <p>
+ * 现在改为:<br>
+ * &reg;SQL("....where name like :1")<br>
+ * public void find(String likeValue, &reg;ShardBy String pageId);
+ * <p>
+ * 
+ * 把 {@link ShardParam} 标注在 SQL 查询的散表参数上,说明该参数值用于散库 / 散表。
+ * 
+ * @author han.liao [in355hz@gmail.com]
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ */
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Deprecated
+public @interface ShardParam {
+
+    // 匹配所有列
+    final String WIDECARD = "*";
+
+    /**
+     * 指出这个参数作为 SQL 语句中哪个散表字段。
+     * 
+     * @return 对应的散表字段
+     */
+    String name() default WIDECARD;
+
+    /**
+     * 指出这个参数值如何计算。
+     * 
+     * @return 计算参数值
+     */
+    String value();
+}

+ 25 - 0
rose-jade/src/main/java/net/paoding/rose/jade/annotation/UseMaster.java

@@ -0,0 +1,25 @@
+package net.paoding.rose.jade.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 使用:{#link UseMaster} 标注需要强制查询 master 数据库的 DAO 接口方法。
+ * 
+ * @author han.liao
+ */
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface UseMaster {
+
+    /**
+     * 是否需要强制查询 master 数据库。
+     * 
+     * @return 强制查询 master 数据库
+     */
+    boolean value() default true;
+}

+ 223 - 0
rose-jade/src/main/java/net/paoding/rose/jade/context/JadeInvocationHandler.java

@@ -0,0 +1,223 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.context;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.paoding.rose.jade.annotation.DAO;
+import net.paoding.rose.jade.annotation.SQLParam;
+import net.paoding.rose.jade.annotation.SQLType;
+import net.paoding.rose.jade.dataaccess.DataAccessFactory;
+import net.paoding.rose.jade.rowmapper.RowMapperFactory;
+import net.paoding.rose.jade.statement.DAOMetaData;
+import net.paoding.rose.jade.statement.Interpreter;
+import net.paoding.rose.jade.statement.InterpreterFactory;
+import net.paoding.rose.jade.statement.JdbcStatement;
+import net.paoding.rose.jade.statement.Querier;
+import net.paoding.rose.jade.statement.SelectQuerier;
+import net.paoding.rose.jade.statement.Statement;
+import net.paoding.rose.jade.statement.StatementMetaData;
+import net.paoding.rose.jade.statement.StatementWrapperProvider;
+import net.paoding.rose.jade.statement.UpdateQuerier;
+import net.paoding.rose.jade.statement.cached.CacheProvider;
+import net.paoding.rose.jade.statement.cached.CachedStatement;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public class JadeInvocationHandler implements InvocationHandler {
+
+    private static final Log logger = LogFactory.getLog(JadeInvocationHandler.class);
+
+    private final ConcurrentHashMap<Method, Statement> statements = new ConcurrentHashMap<Method, Statement>();
+
+    protected final DAOMetaData daoMetaData;
+
+    protected final DataAccessFactory dataAccessFactory;
+
+    protected final RowMapperFactory rowMapperFactory;
+
+    protected final InterpreterFactory interpreterFactory;
+
+    protected final CacheProvider cacheProvider;
+    
+    protected final StatementWrapperProvider statementWrapperProvider;
+    
+    public JadeInvocationHandler(//
+            DAOMetaData daoMetaData,//
+            InterpreterFactory interpreterFactory, //
+            RowMapperFactory rowMapperFactory,//
+            DataAccessFactory dataAccessFactory,//
+            CacheProvider cacheProvider, //
+            StatementWrapperProvider statementWrapperProvider) {
+        this.daoMetaData = daoMetaData;
+        this.rowMapperFactory = rowMapperFactory;
+        this.dataAccessFactory = dataAccessFactory;
+        this.interpreterFactory = interpreterFactory;
+        this.cacheProvider = cacheProvider;
+        this.statementWrapperProvider = statementWrapperProvider;
+    }
+
+    private static final String[] INDEX_NAMES = new String[] { ":1", ":2", ":3", ":4", ":5", ":6",
+            ":7", ":8", ":9", ":10", ":11", ":12", ":13", ":14", ":15", ":16", ":17", ":18", ":19",
+            ":20", ":21", ":22", ":23", ":24", ":25", ":26", ":27", ":28", ":29", ":30", };
+
+    @Override
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+        final boolean debugEnabled = logger.isDebugEnabled();
+        if (debugEnabled) {
+            logger.debug("invoking " + daoMetaData.getDAOClass().getName() + "#" + method.getName());
+        }
+
+        // 调用object的方法
+        if (method.getDeclaringClass() == Object.class) {
+            return invokeObjectMethod(proxy, method, args);
+        }
+        // 获取当前DAO方法对应的Statement对象
+        Statement statement = getStatement(method);
+        //
+        // 将参数放入  Map
+        Map<String, Object> parameters;
+        StatementMetaData statemenetMetaData = statement.getMetaData();
+        if (args == null || args.length == 0) {
+            parameters = new HashMap<String, Object>(4);
+        } else {
+            parameters = new HashMap<String, Object>(args.length * 2 + 4);
+            for (int i = 0; i < args.length; i++) {
+                parameters.put(INDEX_NAMES[i], args[i]);
+                SQLParam sqlParam = statemenetMetaData.getSQLParamAt(i);
+                if (sqlParam != null) {
+                    parameters.put(sqlParam.value(), args[i]);
+                }
+            }
+        }
+        // logging
+        StringBuilder invocationInfo = null;
+        if (debugEnabled) {
+            invocationInfo = getInvocationInfo(statemenetMetaData, parameters);
+            logger.debug("invoking " + invocationInfo.toString());
+        }
+
+        // executing
+        long begin = System.currentTimeMillis();
+        final Object result = statement.execute(parameters);
+        long cost = System.currentTimeMillis() - begin;
+
+        // logging
+        if (logger.isInfoEnabled()) {
+            if (invocationInfo == null) {
+                invocationInfo = getInvocationInfo(statemenetMetaData, parameters);
+            }
+            logger.info("cost " + cost + "ms: " + invocationInfo);
+        }
+        return result;
+    }
+
+    private StringBuilder getInvocationInfo(StatementMetaData metaData,
+            Map<String, Object> parameters) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(metaData).append("\n");
+        sb.append("\tsql: ").append(metaData.getSQL()).append("\n");
+        sb.append("\tparameters: ");
+        ArrayList<String> keys = new ArrayList<String>(parameters.keySet());
+        Collections.sort(keys);
+        for (String key : keys) {
+            sb.append(key).append("='").append(parameters.get(key)).append("'  ");
+        }
+        return sb;
+    }
+
+    private Statement getStatement(Method method) {
+        Statement statement = statements.get(method);
+        if (statement == null) {
+            synchronized (method) {
+                statement = statements.get(method);
+                if (statement == null) {
+                    StatementMetaData smd = wrap(new StatementMetaData(daoMetaData, method));
+                    SQLType sqlType = smd.getSQLType();
+                    Querier querier;
+                    if (sqlType == SQLType.READ) {
+                        RowMapper rowMapper = rowMapperFactory.getRowMapper(smd);
+                        querier = new SelectQuerier(dataAccessFactory, smd, rowMapper);
+                    } else {
+                        querier = new UpdateQuerier(dataAccessFactory, smd);
+                    }
+                    Interpreter[] interpreters = interpreterFactory.getInterpreters(smd);
+                    statement = new JdbcStatement(smd, sqlType, interpreters, querier);
+                    if (cacheProvider != null) {
+                        statement = new CachedStatement(cacheProvider, statement);
+                    }
+                    statements.put(method, wrap(statement));
+                }
+            }
+        }
+        return statement;
+    }
+    
+    private Statement wrap(Statement statement) {
+        if (statementWrapperProvider != null) {
+            return statementWrapperProvider.wrap(statement);
+        }
+        return statement;
+    }
+
+    private StatementMetaData wrap(StatementMetaData smd) {
+        if (statementWrapperProvider != null) {
+            return statementWrapperProvider.wrap(smd);
+        }
+        return smd;
+    }
+
+    private Object invokeObjectMethod(Object proxy, Method method, Object[] args)
+            throws CloneNotSupportedException {
+        String methodName = method.getName();
+        if (methodName.equals("toString")) {
+            return JadeInvocationHandler.this.toString();
+        }
+        if (methodName.equals("hashCode")) {
+            return daoMetaData.getDAOClass().hashCode() * 13 + this.hashCode();
+        }
+        if (methodName.equals("equals")) {
+            return args[0] == proxy;
+        }
+        if (methodName.equals("clone")) {
+            throw new CloneNotSupportedException("clone is not supported for jade dao.");
+        }
+        throw new UnsupportedOperationException(daoMetaData.getDAOClass().getName() + "#"
+                + method.getName());
+    }
+
+    @Override
+    public String toString() {
+        DAO dao = daoMetaData.getDAOClass().getAnnotation(DAO.class);
+        String toString = daoMetaData.getDAOClass().getName()//
+                + "[catalog=" + dao.catalog() + "]";
+        return toString;
+    }
+
+}

+ 113 - 0
rose-jade/src/main/java/net/paoding/rose/jade/context/application/JadeFactory.java

@@ -0,0 +1,113 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.context.application;
+
+import java.lang.reflect.Proxy;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.context.JadeInvocationHandler;
+import net.paoding.rose.jade.dataaccess.DataAccessFactoryAdapter;
+import net.paoding.rose.jade.dataaccess.DataSourceFactory;
+import net.paoding.rose.jade.dataaccess.datasource.SimpleDataSourceFactory;
+import net.paoding.rose.jade.rowmapper.DefaultRowMapperFactory;
+import net.paoding.rose.jade.rowmapper.RowMapperFactory;
+import net.paoding.rose.jade.statement.DAOMetaData;
+import net.paoding.rose.jade.statement.DefaultInterpreterFactory;
+import net.paoding.rose.jade.statement.Interpreter;
+import net.paoding.rose.jade.statement.StatementWrapperProvider;
+import net.paoding.rose.jade.statement.cached.CacheProvider;
+
+import org.springframework.util.ClassUtils;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+//BUG: @SQL中的 :1.create_date应该抛出错误而非返回null
+public class JadeFactory {
+
+    private RowMapperFactory rowMapperFactory = new DefaultRowMapperFactory();
+
+    private DefaultInterpreterFactory interpreterFactory = new DefaultInterpreterFactory();
+
+    private DataAccessFactoryAdapter dataAccessFactory;
+
+    private CacheProvider cacheProvider;
+    
+    // 可选的
+    private StatementWrapperProvider statementWrapperProvider;
+
+    public JadeFactory() {
+    }
+
+    public JadeFactory(DataSource defaultDataSource) {
+        setDataSourceFactory(new SimpleDataSourceFactory(defaultDataSource));
+    }
+
+    public JadeFactory(DataSourceFactory dataSourceFactory) {
+        setDataSourceFactory(dataSourceFactory);
+    }
+
+    public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
+        this.dataAccessFactory = new DataAccessFactoryAdapter(dataSourceFactory);
+    }
+
+    public void setCacheProvider(CacheProvider cacheProvider) {
+        this.cacheProvider = cacheProvider;
+    }
+
+    public DataSourceFactory getDataSourceFactory() {
+        if (this.dataAccessFactory == null) {
+            return null;
+        }
+        return this.dataAccessFactory.getDataSourceFactory();
+    }
+
+    public void setRowMapperFactory(RowMapperFactory rowMapperFactory) {
+        this.rowMapperFactory = rowMapperFactory;
+    }
+    
+    public StatementWrapperProvider getStatementWrapperProvider() {
+        return statementWrapperProvider;
+    }
+    
+    public void setStatementWrapperProvider(StatementWrapperProvider statementWrapperProvider) {
+        this.statementWrapperProvider = statementWrapperProvider;
+    }
+
+    public void addInterpreter(Interpreter[] interpreters) {
+        for (Interpreter interpreter : interpreters) {
+            interpreterFactory.addInterpreter(interpreter);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T create(Class<?> daoClass) {
+        try {
+            DAOMetaData daoMetaData = new DAOMetaData(daoClass);
+            JadeInvocationHandler handler = new JadeInvocationHandler(
+                    //
+                    daoMetaData, interpreterFactory, rowMapperFactory, dataAccessFactory,
+                    cacheProvider, statementWrapperProvider);
+            ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
+            return (T) Proxy.newProxyInstance(classLoader, new Class[] { daoClass }, handler);
+        } catch (RuntimeException e) {
+            throw new IllegalStateException("failed to create bean for " + daoClass.getName(), e);
+        }
+    }
+}

+ 471 - 0
rose-jade/src/main/java/net/paoding/rose/jade/context/spring/JadeBeanFactoryPostProcessor.java

@@ -0,0 +1,471 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.context.spring;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.dataaccess.DataAccessFactory;
+import net.paoding.rose.jade.dataaccess.DataAccessFactoryAdapter;
+import net.paoding.rose.jade.dataaccess.DataSourceFactory;
+import net.paoding.rose.jade.rowmapper.DefaultRowMapperFactory;
+import net.paoding.rose.jade.rowmapper.RowMapperFactory;
+import net.paoding.rose.jade.statement.Interpreter;
+import net.paoding.rose.jade.statement.InterpreterFactory;
+import net.paoding.rose.jade.statement.StatementWrapperProvider;
+import net.paoding.rose.jade.statement.cached.CacheProvider;
+import net.paoding.rose.scanning.ResourceRef;
+import net.paoding.rose.scanning.RoseScanner;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.ApplicationContextException;
+import org.springframework.context.annotation.ScannedGenericBeanDefinition;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.io.Resource;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * {@link JadeBeanFactoryPostProcessor}
+ * 配置在发布包下的applicationContext-jade.xml中,Spring容器完成其内部的标准初始化工作后将调用本处理器,识别
+ * 符合Jade规范的 DAO 接口并将之配置为Spring容器的Bean定义,加入到Spring容器中。
+ * <p>
+ * 
+ * <h1>=开关属性的设置=</h1>
+ * <p>
+ * 
+ * jade在发布jar包时,将在发布包下的 applicationContext-jade.xml
+ * 文件配置本处理器(也就说本类一定会被Spring容器执行),为使jade能够适应不同的应用环境和业务需要,jade特
+ * 提供了一些系统属性设定约定,使得您可灵活地控制本处理器的行为,甚至将本处理器视为一个空处理器:
+ * <p>
+ * 
+ * <strong>jade.context.spring</strong><br>
+ * 如果设置了一个非空属性值(非空时应该填写什么值Jade不做规定),表示jade的spring初始化工作不由本类负责。<br>
+ * 所以,如果您觉得jade默认去自动扫描DAO接口并注册到Spring容器的行为是您不愿接受,您可以设置一个非空值给该属性,
+ * 从而叫停jade的这个行为。
+ * <p>
+ * 
+ * <strong>jade.context.spring.com.yourcompany.dao.UserDAO</strong><br>
+ * 合法的值:0表示忽略,1表示肯定;除0和1外的设置(包括空值)都是非法的。<br>
+ * 在jade的spring初始化工作由本类负责的前提下,将该属性设置为0表示该DAO不由本类负责读取并放到Spring容器中(即忽略之);
+ * 设置为1则表示该DAO由本类负责读取并放到Spring容器中(即肯定之)。<br>
+ * 如果没有该系统属性,jade则读取它的上一级属性:jade.context.spring.com.yourcompany.dao
+ * 并以此类推,直至 jade.context.spring.*。这类属性在Jade统称为开关属性。
+ * <p>
+ * 
+ * <strong>jade.context.spring.*</strong><br>
+ * 这个属性是所有开关属性的根,即类似 jade.context.spring.com 和 jade.context.spring.cn
+ * 之类的开关属性,它的父亲是 jade.context.spring.*, 而非 jade.context.spring<br>
+ * 如果没有设置这个根属性,jade 将等价于其被设置为1。您可以将之设置为0,
+ * 这样就表示只有那些明确设置了开关属性为1的package或接口的类才由本处理器负责读取并放到Spring容器中。
+ * <p>
+ * 
+ * <h1>=DAO的发现=</h1>
+ * <p>
+ * 首先,本处理器会调用 {@link RoseScanner#getJarOrClassesFolderResources()}
+ * 获取类路径下的classes目录以及那些设置了rose标帜的jar包地址。
+ * 为了使jar包中的DAO能够被本处理器识别,其设置的rose标识中必须含有dao或DAO。
+ * <p>
+ * 然后,本处理器将从classes目录或jar包中识别那些符合jade规范的DAO接口:
+ * <ul>
+ * <li>
+ * DAO接口的package必须含有dao目录,如:dao.UserDAO、myapp.dao.UserDAO、myapp.dao.blog
+ * .BlogDAO</li>
+ * <li>DAO接口必须以大写DAO结尾,如:UserDAO、BlogDAO</li>
+ * <li>DAO接口上必须标注@DAO注解(Jade在实现上通过读取二进制文件来进行判断,而非Class.forName)</li>
+ * </ul>
+ * <p>
+ * 通过这两个步骤,本处理器完成了对DAO接口的发现,并最后将这些接口封装为 {@link JadeFactoryBean}
+ * 的形式注册到Spring容器中。
+ * 
+ * <h1>=数据源=</h1>
+ * <p>
+ * 数据源 {@link DataSource} 提供了数据库的访问接口,jade通过{@link DataSourceFactory}
+ * 接口为DAO方法提供数据源,在本处利器所初始化的spring容器中,数据源的设置有两种方式:
+ * 
+ * <h2>==定制方式==</h2><br>
+ * <ul>
+ * <li>当spring容器配置了一个id/name 为 "jade.dataSourceFactory"
+ * 对象,jade将把这个bean取出来,作为 {@link DataSourceFactory}为DAO提供数据源;</li>
+ * <li>当spring容器没有id/name 为 "jade.dataSourceFactory"的对象,但是配置其它名字的
+ * {@link DataSourceFactory},jade将把这个bean 取出来,为DAO提供数据源;</li>
+ * <li>当spring容器没有id/name 为 "jade.dataSourceFactory"的对象,但其中存在
+ * {@link DataSourceFactory}的个数超过1个,此时系统初始化的时侯不会跑出异常,但一旦开始进行进行DAO操作时,将抛出
+ * IllegalStateException 异常。(参见 {@link SpringDataSourceFactoryDelegate})</li>
+ * </ul>
+ * 
+ * <h2>==默认方式==</h2><br>
+ * 当spring容器没有配置任何 {@link DataSourceFactory} 时,jade将启用默认方式为DAO配置数据源,即使用
+ * {@link SpringDataSourceFactory}
+ * 为DAO提供数据源,从spring容器中寻找对应的数据源。对于给定的一个DAO接口,如
+ * com.mycompany.myapp.dao.UserDAO, 其规则如下:
+ * <p>
+ * <ul>
+ * <li>如果存在id/name为jade.dataSource.com.mycompany.myapp.dao.
+ * UserDAO的数据源,则使用它作为这个DAO的数据源,否则逐级询问配置,直到顶一级包名:jade.dataSource.com</li>
+ * <li>如果以上仍未能确定UserDAO的数据源,且UserDAO接口上的<code>@DAO</code>
+ * 的catalog属性非空(假设其值为myteam.myapp),则视myteam.myapp等同于package名,执行前一个步骤的问询</li>
+ * <ul>
+ * <li>即按此顺序问询Spring容器的配置:jade.dataSource.myteam.myapp.UserDAO,...,jade.
+ * dataSource.myteam</li>
+ * </ul>
+ * <li>
+ * 如果以上仍未能确定UserDAO的数据源,则判断是否存在id/name为jade.dataSource、dataSource的数据源</li>
+ * <li>
+ * 如果以上仍未能确定UserDAO的数据源,则最终就是没有数据源,运行时将会有异常抛出</li>
+ * </ul> <br>
+ * 
+ * <h1>=SQL解析器=</h1>
+ * <p>
+ * 当DAO方法被调用,执行数据库访问前,jade总是会先调用相应的SQL解析器,解析/改写SQL、设置相应的参数或运行时状态。<br>
+ * Jade使用 {@link InterpreterFactory} 为每个DAO方法配置对应的解析器。 本处理器使用的
+ * {@link InterpreterFactory} 是 {@link SpringInterpreterFactory}。<br>
+ * {@link SpringInterpreterFactory}将获取配置在Spring容器中的 {@link Interpreter}
+ * ,按照标注在其上的{@link Order}排序,设置给各个DAO方法。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class JadeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+
+    /**
+     * 开关属性前缀常量
+     */
+    private static final String propertyPrefix = "jade.context.spring";;
+
+    /**
+     * 日志记录器
+     */
+    private final Log logger = LogFactory.getLog(JadeBeanFactoryPostProcessor.class);
+
+    /**
+     * 数据存取器工厂,通过它可以得到一个对数据库进行操作的实现
+     * <p>
+     * 
+     * @see #getDataAccessFactory(ConfigurableListableBeanFactory)
+     */
+    private DataAccessFactory dataAccessFactory;
+
+    /**
+     * 行映射器工厂,通过它可以得到一个数据库表的行映射器实现,使得从数据库读取的记录可以映射为一个对象
+     * <p>
+     * 
+     * @see #getRowMapperFactory()
+     */
+    private RowMapperFactory rowMapperFactory;
+
+    /**
+     * 解释器工厂,通过它可以或得一个DAO方法对应的解析器数组,这些解析器数组将解析每一次DAO操作,进行SQL解析或设置运行时状态
+     * <p>
+     * 
+     * @see #getInterpreterFactory(ConfigurableListableBeanFactory)
+     */
+    private InterpreterFactory interpreterFactory;
+    
+    /**
+     * StatmentWrapper提供者的bean名称,为“none”等价于null
+     */
+    private String statmentWrapperProviderName;
+
+    /**
+     * 缓存提供者的bean名称,为“none”等价于null
+     */
+    private String cacheProviderName;
+
+    // ------------------------------
+
+    public DataAccessFactory getDataAccessFactory(ConfigurableListableBeanFactory beanFactory) {
+        if (this.dataAccessFactory == null) {
+            dataAccessFactory = new DataAccessFactoryAdapter(//
+                    new SpringDataSourceFactoryDelegate(beanFactory));
+        }
+        return dataAccessFactory;
+    }
+
+    public InterpreterFactory getInterpreterFactory(ConfigurableListableBeanFactory beanFactory) {
+        if (interpreterFactory == null) {
+            interpreterFactory = new SpringInterpreterFactory(beanFactory);
+        }
+        return interpreterFactory;
+    }
+
+    public RowMapperFactory getRowMapperFactory() {
+        if (rowMapperFactory == null) {
+            rowMapperFactory = new DefaultRowMapperFactory();
+        }
+        return rowMapperFactory;
+    }
+
+    public String getCacheProviderName(ConfigurableListableBeanFactory beanFactory) {
+        if (cacheProviderName == null) {
+            String[] names = beanFactory.getBeanNamesForType(CacheProvider.class);
+            if (names.length == 0) {
+                cacheProviderName = "none";
+            } else if (names.length == 1) {
+                cacheProviderName = names[0];
+            } else {
+                String topPriority = "jade.cacheProvider";
+                if (ArrayUtils.contains(names, topPriority)) {
+                    cacheProviderName = topPriority;
+                } else {
+                    throw new IllegalStateException(
+                            "required not more than 1 CacheProvider, but found " + names.length);
+                }
+            }
+        }
+        return "none".equals(cacheProviderName) ? null : cacheProviderName;
+    }
+
+    public String getStatementWrapperProvider(ConfigurableListableBeanFactory beanFactory) {
+        if (statmentWrapperProviderName == null) {
+            String[] names = beanFactory.getBeanNamesForType(StatementWrapperProvider.class);
+            if (names.length == 0) {
+                statmentWrapperProviderName = "none";
+            } else if (names.length == 1) {
+                statmentWrapperProviderName = names[0];
+            } else {
+                String topPriority = "jade.statmentWrapperProvider";
+                if (ArrayUtils.contains(names, topPriority)) {
+                    statmentWrapperProviderName = topPriority;
+                } else {
+                    throw new IllegalStateException(
+                            "required not more than 1 StatmentWrapperProvider, but found " + names.length);
+                }
+            }
+        }
+        return "none".equals(statmentWrapperProviderName) ? null : statmentWrapperProviderName;
+    }
+
+    // ------------------------------
+
+    /**
+     * 本方法将在Spring容器完成内部的标准初始化工作后被调用,在此识别 Jade DAO
+     * 接口并将配置为Spring容器的Bean定义,加入到Spring容器中。
+     * <p>
+     * 
+     * 因为本类将配置在发布包的 applicationContext-jade.xml 文件中,所以在rose环境中,本类一定会生效!
+     * 为了适应不同的应用环境,这里提供了一些机制使有更灵活的控制,请参考类级别的JavaDoc说明。
+     * <p>
+     * 
+     * @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)
+     */
+    @Override
+    public final void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
+            throws BeansException {
+        String springFlag = System.getProperty(propertyPrefix);
+
+        // 对于配置了jade.context.spring 系统属性的,表示jade的spring初始化工作不由本类负责。
+        if (springFlag != null && springFlag.length() > 0) {
+            logger.info("found " + propertyPrefix + "=" + springFlag);
+            return;
+        }
+
+        // 其它情况则按既定的规则执行
+        doPostProcessBeanFactory(beanFactory);
+    }
+
+    private void doPostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
+        // 记录开始
+        if (logger.isInfoEnabled()) {
+            logger.info("[jade] starting ...");
+        }
+
+        // 1、获取标注rose标志的资源(ResourceRef),即classes目录、在/META-INF/rose.properties或/META-INF/MENIFEST.MF配置了rose属性的jar包
+        final List<ResourceRef> resources = findRoseResources();
+
+        // 2、从获取的资源(resources)中,把rose=*、rose=DAO、rose=dao的筛选出来,并以URL的形式返回
+        List<String> urls = findJadeResources(resources);
+
+        // 3、从每个URL中找出符合规范的DAO接口,并将之以JadeFactoryBean的形式注册到Spring容器中
+        findJadeDAODefinitions(beanFactory, urls);
+
+        // 记录结束
+        if (logger.isInfoEnabled()) {
+            logger.info("[jade] exits");
+        }
+    }
+
+    /*
+     * 找出含有rose标帜的目录或jar包
+     */
+    private List<ResourceRef> findRoseResources() {
+        final List<ResourceRef> resources;
+        try {
+            resources = RoseScanner.getInstance().getJarOrClassesFolderResources();
+        } catch (IOException e) {
+            throw new ApplicationContextException(
+                    "error on getJarResources/getClassesFolderResources", e);
+        }
+        return resources;
+    }
+
+    /*
+     * 找出含有dao、DAO标识的url
+     */
+    private List<String> findJadeResources(final List<ResourceRef> resources) {
+        List<String> urls = new LinkedList<String>();
+        for (ResourceRef ref : resources) {
+            if (ref.hasModifier("dao") || ref.hasModifier("DAO")) {
+                try {
+                    Resource resource = ref.getResource();
+                    File resourceFile = resource.getFile();
+                    if (resourceFile.isFile()) {
+                        urls.add("jar:file:" + resourceFile.toURI().getPath()
+                                + ResourceUtils.JAR_URL_SEPARATOR);
+                    } else if (resourceFile.isDirectory()) {
+                        urls.add(resourceFile.toURI().toString());
+                    }
+                } catch (IOException e) {
+                    throw new ApplicationContextException("error on resource.getFile", e);
+                }
+            }
+        }
+        if (logger.isInfoEnabled()) {
+            logger.info("[jade] found " + urls.size() + " jade urls: " + urls);
+        }
+        return urls;
+    }
+
+    /*
+     * 从获得的目录或jar包中寻找出符合规范的DAO接口,并注册到Spring容器中
+     */
+    private void findJadeDAODefinitions(ConfigurableListableBeanFactory beanFactory,
+            List<String> urls) {
+        JadeComponentProvider provider = new JadeComponentProvider();
+        Set<String> daoClassNames = new HashSet<String>();
+
+        for (String url : urls) {
+            if (logger.isInfoEnabled()) {
+                logger.info("[jade] call 'jade/find'");
+            }
+
+            Set<BeanDefinition> dfs = provider.findCandidateComponents(url);
+            if (logger.isInfoEnabled()) {
+                logger.info("[jade] found " + dfs.size() + " beanDefinition from '" + url + "'");
+            }
+
+            for (BeanDefinition beanDefinition : dfs) {
+                String daoClassName = beanDefinition.getBeanClassName();
+                if (getDisableFlag(daoClassName)) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("[jade] ignored disabled jade dao class: " + daoClassName
+                                + "  [" + url + "]");
+                    }
+                    continue;
+                }
+                if (daoClassNames.contains(daoClassName)) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("[jade] ignored replicated jade dao class: " + daoClassName
+                                + "  [" + url + "]");
+                    }
+                    continue;
+                }
+                daoClassNames.add(daoClassName);
+
+                registerDAODefinition(beanFactory, beanDefinition);
+            }
+        }
+    }
+
+    /*
+     * 将找到的一个DAO接口注册到Spring容器中
+     */
+    private void registerDAODefinition(ConfigurableListableBeanFactory beanFactory,
+            BeanDefinition beanDefinition) {
+        final String daoClassName = beanDefinition.getBeanClassName();
+        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
+        /*
+         * 属性及其设置要按 JadeFactoryBean 的要求来办
+         */
+        propertyValues.addPropertyValue("objectType", daoClassName);
+        propertyValues.addPropertyValue("dataAccessFactory", getDataAccessFactory(beanFactory));
+        propertyValues.addPropertyValue("rowMapperFactory", getRowMapperFactory());
+        propertyValues.addPropertyValue("interpreterFactory", getInterpreterFactory(beanFactory));
+        String cacheProviderName = getCacheProviderName(beanFactory);
+        if (cacheProviderName != null) {
+            RuntimeBeanReference beanRef = new RuntimeBeanReference(cacheProviderName);
+            propertyValues.addPropertyValue("cacheProvider", beanRef);
+        }
+        String statementWrapperProvider = getStatementWrapperProvider(beanFactory);
+        if (statementWrapperProvider != null) {
+            RuntimeBeanReference beanRef = new RuntimeBeanReference(statementWrapperProvider);
+            propertyValues.addPropertyValue("statementWrapperProvider", beanRef);
+        }
+        ScannedGenericBeanDefinition scannedBeanDefinition = (ScannedGenericBeanDefinition) beanDefinition;
+        scannedBeanDefinition.setPropertyValues(propertyValues);
+        scannedBeanDefinition.setBeanClass(JadeFactoryBean.class);
+
+        DefaultListableBeanFactory defaultBeanFactory = (DefaultListableBeanFactory) beanFactory;
+        defaultBeanFactory.registerBeanDefinition(daoClassName, beanDefinition);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("[jade] register DAO: " + daoClassName);
+        }
+    }
+
+    /*
+     * 获取给定dao类的开关属性
+     */
+    protected boolean getDisableFlag(String daoType) {
+        String name = daoType;
+        while (true) {
+            String flag;
+            if (name.length() == 0) {
+                flag = System.getProperty(propertyPrefix + ".*");
+            } else {
+                flag = System.getProperty(propertyPrefix + "." + name);
+            }
+            if (flag == null) {
+                int index = name.lastIndexOf('.');
+                if (index == -1) {
+                    if (name.length() == 0) {
+                        return false;
+                    } else {
+                        name = "";
+                    }
+                } else {
+                    name = name.substring(0, index);
+                }
+                continue;
+            }
+            if ("0".equals(flag)) {
+                return true;
+            } else if (flag == null || "1".equals(flag)) {
+                return false;
+            } else {
+                if (name.length() == 0) {
+                    name = "*";
+                }
+                throw new IllegalArgumentException("illegal value of property: " + propertyPrefix
+                        + "." + name + "='" + flag + "'");
+            }
+        }
+    }
+}

+ 231 - 0
rose-jade/src/main/java/net/paoding/rose/jade/context/spring/JadeComponentProvider.java

@@ -0,0 +1,231 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.context.spring;
+
+import java.io.IOException;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import net.paoding.rose.jade.annotation.DAO;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.BeanDefinitionStoreException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.context.annotation.ScannedGenericBeanDefinition;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.core.type.filter.TypeFilter;
+import org.springframework.util.Assert;
+
+/**
+ * {@link JadeComponentProvider}用于查找一个目录或jar包下符合Jade规范的DAO接口。
+ * 
+ * @author Mark Fisher
+ * @author Juergen Hoeller
+ * @author Ramnivas Laddad
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ * 
+ * @see JadeComponentProvider
+ * @see org.springframework.core.type.classreading.MetadataReaderFactory
+ * @see org.springframework.core.type.AnnotationMetadata
+ * @see ScannedGenericBeanDefinition
+ */
+public class JadeComponentProvider implements ResourceLoaderAware {
+
+    /**
+     * 日志记录器
+     */
+    private final Log logger = LogFactory.getLog(JadeComponentProvider.class);
+
+    /**
+     * 
+     */
+    private String resourcePattern = "**/*DAO.class";
+
+    /**
+     * 
+     */
+    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
+
+    /**
+     * 
+     */
+    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
+            resourcePatternResolver);
+
+    /**
+     * 
+     */
+    private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
+
+    /**
+     * 
+     */
+    private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
+
+    /**
+     * 
+     */
+    public JadeComponentProvider() {
+        includeFilters.add(new AnnotationTypeFilter(DAO.class));
+    }
+
+    /**
+     * Set the ResourceLoader to use for resource locations. This will
+     * typically be a ResourcePatternResolver implementation.
+     * <p>
+     * Default is PathMatchingResourcePatternResolver, also capable of
+     * resource pattern resolving through the ResourcePatternResolver
+     * interface.
+     * 
+     * @see org.springframework.core.io.support.ResourcePatternResolver
+     * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
+     */
+    public void setResourceLoader(ResourceLoader resourceLoader) {
+        this.resourcePatternResolver = ResourcePatternUtils
+                .getResourcePatternResolver(resourceLoader);
+        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
+    }
+
+    /**
+     * Return the ResourceLoader that this component provider uses.
+     */
+    public final ResourceLoader getResourceLoader() {
+        return this.resourcePatternResolver;
+    }
+
+    /**
+     * Set the resource pattern to use when scanning the classpath. This
+     * value will be appended to each base package name.
+     * 
+     * @see #findCandidateComponents(String)
+     */
+    public void setResourcePattern(String resourcePattern) {
+        Assert.notNull(resourcePattern, "'resourcePattern' must not be null");
+        this.resourcePattern = resourcePattern;
+    }
+
+    /**
+     * Add an exclude type filter to the <i>front</i> of the exclusion
+     * list.
+     */
+    public void addExcludeFilter(TypeFilter excludeFilter) {
+        this.excludeFilters.add(0, excludeFilter);
+    }
+
+    /**
+     * 查找并返回一个目录或jar包下符合Jade规范的DAO接口。
+     * <p>
+     * 所返回的每一个BeanDefinition代表一个符合规范的DAO接口,我们可以通过
+     * {@link BeanDefinition#getBeanClassName()} 得到对应的DAO接口的类名
+     * <p>
+     * 所返回的BeanDefinition代表的是一个接口,不能直接注册到Spring容器中,必须先做额外的转化!
+     */
+    public Set<BeanDefinition> findCandidateComponents(String uriPrefix) {
+        if (!uriPrefix.endsWith("/")) {
+            uriPrefix = uriPrefix + "/";
+        }
+        Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
+        try {
+            String packageSearchPath = uriPrefix + this.resourcePattern;
+            boolean traceEnabled = logger.isDebugEnabled();
+            boolean debugEnabled = logger.isDebugEnabled();
+            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
+            if (debugEnabled) {
+                logger.debug("[jade/find] find " + resources.length + " resources for "
+                        + packageSearchPath);
+            }
+            for (int i = 0; i < resources.length; i++) {
+                Resource resource = resources[i];
+                if (traceEnabled) {
+                    logger.trace("[jade/find] scanning " + resource);
+                }
+                // resourcePatternResolver.getResources出来的classPathResources,metadataReader对其进行getInputStream的时候为什么返回null呢?
+                // 不得不做一个exists判断
+                if (!resource.exists()) {
+                    if (debugEnabled) {
+                        logger.debug("Ignored because not exists:" + resource);
+                    }
+                } else if (resource.isReadable()) {
+                    MetadataReader metadataReader = metadataReaderFactory
+                            .getMetadataReader(resource);
+                    if (isCandidateComponent(metadataReader)) {
+                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(
+                                metadataReader);
+                        sbd.setResource(resource);
+                        sbd.setSource(resource);
+                        if (sbd.getMetadata().isInterface() && sbd.getMetadata().isIndependent()) {
+                            if (debugEnabled) {
+                                logger.debug("Identified candidate component class: " + resource);
+                            }
+                            candidates.add(sbd);
+                        } else {
+                            if (traceEnabled) {
+                                logger.trace("Ignored because not a interface top-level class: "
+                                        + resource);
+                            }
+                        }
+                    } else {
+                        if (traceEnabled) {
+                            logger.trace("Ignored because not matching any filter: " + resource);
+                        }
+                    }
+                } else {
+                    if (traceEnabled) {
+                        logger.trace("Ignored because not readable: " + resource);
+                    }
+                }
+            }
+        } catch (IOException ex) {
+            throw new BeanDefinitionStoreException("I/O failure during jade scanning", ex);
+        }
+        return candidates;
+    }
+
+    /**
+     * Determine whether the given class does not match any exclude filter
+     * and does match at least one include filter.
+     * 
+     * @param metadataReader the ASM ClassReader for the class
+     * @return whether the class qualifies as a candidate component
+     */
+    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
+        for (TypeFilter tf : this.excludeFilters) {
+            if (tf.match(metadataReader, this.metadataReaderFactory)) {
+                return false;
+            }
+        }
+        for (TypeFilter tf : this.includeFilters) {
+            if (tf.match(metadataReader, this.metadataReaderFactory)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 167 - 0
rose-jade/src/main/java/net/paoding/rose/jade/context/spring/JadeFactoryBean.java

@@ -0,0 +1,167 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.context.spring;
+
+import java.lang.reflect.Proxy;
+
+import net.paoding.rose.jade.context.JadeInvocationHandler;
+import net.paoding.rose.jade.dataaccess.DataAccessFactory;
+import net.paoding.rose.jade.rowmapper.RowMapperFactory;
+import net.paoding.rose.jade.statement.DAOMetaData;
+import net.paoding.rose.jade.statement.InterpreterFactory;
+import net.paoding.rose.jade.statement.StatementWrapperProvider;
+import net.paoding.rose.jade.statement.cached.CacheProvider;
+
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+import com.wzzx.rose.jade.transaction.Transaction;
+import com.wzzx.rose.jade.transaction.TransactionHandler;
+
+/**
+ * 
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ * 
+ */
+public class JadeFactoryBean implements FactoryBean, InitializingBean {
+
+    protected Class<?> objectType;
+
+    protected DataAccessFactory dataAccessFactory;
+
+    protected RowMapperFactory rowMapperFactory;
+
+    protected InterpreterFactory interpreterFactory;
+
+    protected CacheProvider cacheProvider;
+
+    protected Object daoObject;
+    
+    // 可选的
+    private StatementWrapperProvider statementWrapperProvider;
+
+    public JadeFactoryBean() {
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return objectType;
+    }
+
+    public void setObjectType(Class<?> objectType) {
+        this.objectType = objectType;
+    }
+
+    /**
+     * 
+     * @param dataAccessFactory
+     */
+    public void setDataAccessFactory(DataAccessFactory dataAccessFactory) {
+        this.dataAccessFactory = dataAccessFactory;
+    }
+
+    public DataAccessFactory getDataAccessFactory() {
+        return dataAccessFactory;
+    }
+
+    /**
+     * 
+     * @param rowMapperFactory
+     */
+    public void setRowMapperFactory(RowMapperFactory rowMapperFactory) {
+        this.rowMapperFactory = rowMapperFactory;
+    }
+
+    public RowMapperFactory getRowMapperFactory() {
+        return rowMapperFactory;
+    }
+
+    /**
+     * 
+     * @param interpreterFactory
+     */
+    public void setInterpreterFactory(InterpreterFactory interpreterFactory) {
+        this.interpreterFactory = interpreterFactory;
+    }
+
+    public InterpreterFactory getInterpreterFactory() {
+        return interpreterFactory;
+    }
+
+    public void setCacheProvider(CacheProvider cacheProvider) {
+        this.cacheProvider = cacheProvider;
+    }
+
+    public CacheProvider getCacheProvider() {
+        return cacheProvider;
+    }
+    
+    public StatementWrapperProvider getStatementWrapperProvider() {
+        return statementWrapperProvider;
+    }
+    
+    public void setStatementWrapperProvider(StatementWrapperProvider statementWrapperProvider) {
+        this.statementWrapperProvider = statementWrapperProvider;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        Assert.isTrue(objectType.isInterface(), "not a interface class: " + objectType.getName());
+        Assert.notNull(dataAccessFactory);
+        Assert.notNull(rowMapperFactory);
+        Assert.notNull(interpreterFactory);
+        // cacheProvider可以null,不做assert.notNull判断
+    }
+
+    @Override
+    public Object getObject() {
+        if (daoObject == null) {
+            daoObject = createDAO();
+            Assert.notNull(daoObject);
+        }
+        return daoObject;
+    }
+
+    protected Object createDAO() {
+        try {
+            DAOMetaData daoMetaData = new DAOMetaData(objectType);
+            JadeInvocationHandler handler;
+            if (Transaction.class.isAssignableFrom(objectType)) {
+                handler = new TransactionHandler (
+                        daoMetaData, interpreterFactory, rowMapperFactory, dataAccessFactory,
+                        cacheProvider, statementWrapperProvider);
+            } else {
+                handler = new JadeInvocationHandler(
+                    daoMetaData, interpreterFactory, rowMapperFactory, dataAccessFactory,
+                    cacheProvider, statementWrapperProvider);
+            }
+            return Proxy.newProxyInstance(objectType.getClassLoader(),
+                    new Class[] { objectType }, handler);
+        } catch (RuntimeException e) {
+            throw new IllegalStateException("failed to create bean for "
+                    + this.objectType.getName(), e);
+        }
+    }
+
+}

+ 126 - 0
rose-jade/src/main/java/net/paoding/rose/jade/context/spring/SpringDataSourceFactory.java

@@ -0,0 +1,126 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.context.spring;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.annotation.DAO;
+import net.paoding.rose.jade.dataaccess.DataSourceFactory;
+import net.paoding.rose.jade.dataaccess.DataSourceHolder;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.apache.commons.lang.IllegalClassException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/**
+ * 
+ * @author qieqie.wang
+ */
+public class SpringDataSourceFactory implements DataSourceFactory, ApplicationContextAware {
+
+    private Log logger = LogFactory.getLog(getClass());
+
+    private ListableBeanFactory applicationContext;
+
+    private ConcurrentHashMap<Class<?>, DataSourceHolder> cachedDataSources = new ConcurrentHashMap<Class<?>, DataSourceHolder>();
+
+    public SpringDataSourceFactory() {
+    }
+
+    public SpringDataSourceFactory(ListableBeanFactory applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    @Override
+    public DataSourceHolder getHolder(StatementMetaData metaData,
+            Map<String, Object> runtimeProperties) {
+        Class<?> daoClass = metaData.getDAOMetaData().getDAOClass();
+        DataSourceHolder holder = cachedDataSources.get(daoClass);
+        if (holder != null) {
+            return holder;
+        }
+
+        holder = getDataSourceByDirectory(daoClass, daoClass.getName());
+        if (holder != null) {
+            cachedDataSources.put(daoClass, holder);
+            return holder;
+        }
+        String catalog = daoClass.getAnnotation(DAO.class).catalog();
+        if (catalog.length() > 0) {
+            holder = getDataSourceByDirectory(daoClass, catalog + "." + daoClass.getSimpleName());
+        }
+        if (holder != null) {
+            cachedDataSources.put(daoClass, holder);
+            return holder;
+        }
+        holder = getDataSourceByKey(daoClass, "jade.dataSource");
+        if (holder != null) {
+            cachedDataSources.put(daoClass, holder);
+            return holder;
+        }
+        holder = getDataSourceByKey(daoClass, "dataSource");
+        if (holder != null) {
+            cachedDataSources.put(daoClass, holder);
+            return holder;
+        }
+        return null;
+    }
+
+    private DataSourceHolder getDataSourceByDirectory(Class<?> daoClass, String catalog) {
+        String tempCatalog = catalog;
+        DataSourceHolder dataSource;
+        while (tempCatalog != null && tempCatalog.length() > 0) {
+            dataSource = getDataSourceByKey(daoClass, "jade.dataSource." + tempCatalog);
+            if (dataSource != null) {
+                return dataSource;
+            }
+            int index = tempCatalog.lastIndexOf('.');
+            if (index == -1) {
+                tempCatalog = null;
+            } else {
+                tempCatalog = tempCatalog.substring(0, index);
+            }
+        }
+        return null;
+    }
+
+    private DataSourceHolder getDataSourceByKey(Class<?> daoClass, String key) {
+        if (applicationContext.containsBean(key)) {
+            Object dataSource = applicationContext.getBean(key);
+            if (!(dataSource instanceof DataSource) && !(dataSource instanceof DataSourceFactory)) {
+                throw new IllegalClassException("expects DataSource or DataSourceFactory, but a "
+                        + dataSource.getClass().getName());
+            }
+            if (logger.isDebugEnabled()) {
+                logger.debug("found dataSource: " + key + " for DAO " + daoClass.getName());
+            }
+            return new DataSourceHolder(dataSource);
+        }
+        return null;
+    }
+}

+ 43 - 0
rose-jade/src/main/java/net/paoding/rose/jade/context/spring/SpringDataSourceFactoryDelegate.java

@@ -0,0 +1,43 @@
+package net.paoding.rose.jade.context.spring;
+
+import java.util.Map;
+
+import net.paoding.rose.jade.dataaccess.DataSourceFactory;
+import net.paoding.rose.jade.dataaccess.DataSourceHolder;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.springframework.beans.factory.ListableBeanFactory;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+public class SpringDataSourceFactoryDelegate implements DataSourceFactory {
+
+    private ListableBeanFactory beanFactory;
+
+    private DataSourceFactory dataSourceFactory;
+
+    public SpringDataSourceFactoryDelegate(ListableBeanFactory beanFactory) {
+        this.beanFactory = beanFactory;
+    }
+
+    @Override
+    public DataSourceHolder getHolder(StatementMetaData metaData, Map<String, Object> runtimeProperties) {
+        if (dataSourceFactory == null) {
+            ListableBeanFactory beanFactory = this.beanFactory;
+            if (beanFactory != null) {
+                if (beanFactory.containsBeanDefinition("jade.dataSourceFactory")) {
+                    dataSourceFactory = (DataSourceFactory) beanFactory.getBean(
+                            "jade.dataSourceFactory", DataSourceFactory.class);
+                } else {
+                    dataSourceFactory = new SpringDataSourceFactory(beanFactory);
+                }
+                this.beanFactory = null;
+            }
+        }
+        return dataSourceFactory.getHolder(metaData, runtimeProperties);
+    }
+
+}

+ 76 - 0
rose-jade/src/main/java/net/paoding/rose/jade/context/spring/SpringInterpreterFactory.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.context.spring;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+
+import net.paoding.rose.jade.statement.DefaultInterpreterFactory;
+import net.paoding.rose.jade.statement.Interpreter;
+import net.paoding.rose.jade.statement.InterpreterComparator;
+import net.paoding.rose.jade.statement.InterpreterFactory;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+public class SpringInterpreterFactory implements InterpreterFactory, ApplicationContextAware {
+
+    private DefaultInterpreterFactory interpreterFactory;
+
+    private ListableBeanFactory beanFactory;
+
+    public SpringInterpreterFactory() {
+    }
+
+    public SpringInterpreterFactory(ListableBeanFactory beanFactory) {
+        this.beanFactory = beanFactory;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.beanFactory = applicationContext;
+    }
+
+    @Override
+    public Interpreter[] getInterpreters(StatementMetaData metaData) {
+        if (interpreterFactory == null) {
+            init();
+        }
+        return interpreterFactory.getInterpreters(metaData);
+    }
+
+    private void init() {
+        synchronized (this) {
+            if (interpreterFactory == null) {
+                @SuppressWarnings("unchecked")
+                Map<String, Interpreter> map = beanFactory.getBeansOfType(Interpreter.class);
+                ArrayList<Interpreter> interpreters = new ArrayList<Interpreter>(map.values());
+                Collections.sort(interpreters, new InterpreterComparator());
+                interpreterFactory = new DefaultInterpreterFactory(interpreters);
+            }
+        }
+    }
+
+}

+ 87 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataAccess.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.support.KeyHolder;
+
+/**
+ * {@link DataAccess} 分隔了DAO接口层和数据访问层。
+ * <p>
+ * 数据访问层规范定义了所支持的数据访问接口
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public interface DataAccess {
+
+    /**
+     * 返回所使用的DataSource
+     * 
+     * @return
+     */
+    DataSource getDataSource();
+    
+    /**
+     * 返回所使用的JdbcTemplate
+     * 
+     * @return
+     */
+    JdbcTemplate getJdbcTemplate();
+    
+    /**
+     * 读访问
+     * 
+     * @param sql 所要执行的实际SQL语句
+     * @param args 伴随该SQL语句的参数
+     * @param rowMapper 行映射器
+     * @return
+     */
+    List<?> select(String sql, Object[] args, RowMapper rowMapper);
+
+    /**
+     * 写访问(更新或插入)
+     * 
+     * @param sql 所要执行的实际SQL语句
+     * @param args 伴随该SQL语句的参数
+     * @param generatedKeyHolder 是否要读取该SQL生成的key
+     * @return
+     */
+    int update(String sql, Object[] args, KeyHolder generatedKeyHolder);
+    
+    /**
+     * oracle 写访问 (更新或插入)
+     * @param sql
+     * @param args
+     * @param generatedKeyHolder
+     * @return
+     */
+    int oracleUpdate(String sql, Object[] args, KeyHolder generatedKeyHolder);
+
+    /**
+     * 批量写访问(更新或插入)
+     * 
+     * @param sql 所要执行的实际SQL语句
+     * @param argsList 伴随该SQL语句的参数
+     * @return
+     */
+    int[] batchUpdate(String sql, List<Object[]> argsList);
+}

+ 34 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataAccessFactory.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess;
+
+import java.util.Map;
+
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+/**
+ * 这是框架的内部接口,{@link DataAccess}的工厂类。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public interface DataAccessFactory {
+
+    /**
+     * 运行时为框架提供一个 {@link DataAccess} 实例
+     */
+    DataAccess getDataAccess(StatementMetaData metaData, Map<String, Object> runtime);
+}

+ 58 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataAccessFactoryAdapter.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess;
+
+import java.util.Map;
+
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException;
+
+/**
+ * 框架内部使用的 {@link DataAccessFactory}实现,适配到 {@link DataSourceFactory}
+ * ,由后者提供最终的数据源
+ * 
+ * @see DataSourceFactory
+ * 
+ * @author qieqie
+ * 
+ */
+public class DataAccessFactoryAdapter implements DataAccessFactory {
+
+    protected final DataSourceFactory dataSourceFactory;
+
+    public DataAccessFactoryAdapter(DataSourceFactory dataSourceFactory) {
+        this.dataSourceFactory = dataSourceFactory;
+    }
+
+    public DataSourceFactory getDataSourceFactory() {
+        return dataSourceFactory;
+    }
+
+    @Override
+    public DataAccess getDataAccess(StatementMetaData metaData, Map<String, Object> runtime) {
+        DataSourceHolder holder = dataSourceFactory.getHolder(metaData, runtime);
+        while (holder != null && holder.isFactory()) {
+            holder = holder.getFactory().getHolder(metaData, runtime);
+        }
+        if (holder == null || holder.getDataSource() == null) {
+            throw new DataSourceLookupFailureException("cannot found a dataSource for: " + metaData);
+        }
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(holder.getDataSource());
+        return new DataAccessImpl(jdbcTemplate);
+    }
+}

+ 269 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataAccessImpl.java

@@ -0,0 +1,269 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.PreparedStatementCreator;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.RowMapperResultSetExtractor;
+import org.springframework.jdbc.core.SqlParameterValue;
+import org.springframework.jdbc.core.SqlTypeValue;
+import org.springframework.jdbc.core.StatementCreatorUtils;
+import org.springframework.jdbc.support.KeyHolder;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class DataAccessImpl implements DataAccess {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    public DataAccessImpl(JdbcTemplate jdbcTemplate) {
+        this.jdbcTemplate = jdbcTemplate;
+    }
+
+    // ------------------------------------------------
+
+    public javax.sql.DataSource getDataSource() {
+        return this.jdbcTemplate.getDataSource();
+    }
+
+    public JdbcTemplate getJdbcTemplate() {
+		return jdbcTemplate;
+	}
+
+	@Override
+    public List<?> select(String sql, Object[] args, RowMapper rowMapper) {
+        PreparedStatementCreator csc = getPreparedStatementCreator(sql, args, false,null);
+        return (List<?>) jdbcTemplate.query(csc, new RowMapperResultSetExtractor(rowMapper));
+    }
+
+    @Override
+    public int update(String sql, Object[] args, KeyHolder generatedKeyHolder) {
+        boolean returnKeys = generatedKeyHolder != null;
+        PreparedStatementCreator psc = getPreparedStatementCreator(sql, args, returnKeys,null);
+        if (generatedKeyHolder == null) {
+            return jdbcTemplate.update(psc);
+        } else {
+            return jdbcTemplate.update(psc, generatedKeyHolder);
+        }
+    }
+
+    @Override
+	public int oracleUpdate(String sql, Object[] args,
+			KeyHolder generatedKeyHolder) {
+		// TODO Auto-generated method stub
+    	boolean returnKeys = generatedKeyHolder != null;
+        PreparedStatementCreator psc = getPreparedStatementCreator(sql, args, returnKeys,"oracle");
+        if (generatedKeyHolder == null) {
+            return jdbcTemplate.update(psc);
+        } else {
+            return jdbcTemplate.update(psc, generatedKeyHolder);
+        }
+	}
+    
+    // TODO: 批量处理
+    @Override
+    public int[] batchUpdate(String sql, List<Object[]> argsList) {
+        int[] updated = new int[argsList.size()];
+        int i = 0;
+        for (Object[] args : argsList) {
+            updated[i++] = update(sql, args, null);
+        }
+        return updated;
+    }
+
+    private PreparedStatementCreator getPreparedStatementCreator(//
+            final String sql, final Object[] args, final boolean returnKeys,final String productName) {
+        PreparedStatementCreator creator = new PreparedStatementCreator() {
+
+            @Override
+            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
+                PreparedStatement ps = con.prepareStatement(sql);
+                if (returnKeys) {
+                    if(StringUtils.isNotBlank(productName) && "oracle".equalsIgnoreCase(productName)){
+                    	ps = con.prepareStatement(sql, new int[]{1});
+                    }else{
+                    	ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
+                    }
+                } else {
+                    ps = con.prepareStatement(sql);
+                }
+
+                if (args != null) {
+                    for (int i = 0; i < args.length; i++) {
+                        Object arg = args[i];
+                        if (arg instanceof SqlParameterValue) {
+                            SqlParameterValue paramValue = (SqlParameterValue) arg;
+                            StatementCreatorUtils.setParameterValue(ps, i + 1, paramValue,
+                                    paramValue.getValue());
+                        } else {
+                            StatementCreatorUtils.setParameterValue(ps, i + 1,
+                                    SqlTypeValue.TYPE_UNKNOWN, arg);
+                        }
+                    }
+                }
+                return ps;
+            }
+        };
+        return creator;
+    }
+
+
+    //    //TODO: 实现批量更新
+    //    @Override
+    //    public int[] batchUpdate(String sql, StatementMetaData modifier,
+    //            List<Map<String, Object>> parametersList) {
+    //        // 以com.xiaonei.in.dao为试点测试真正的批量插入、更新,不支持返回可能的自增主键
+    //        // 2010-10-20
+    //        //        if (modifier.getDefinition().getDAOClazz().getName().startsWith("com.xiaonei.in.dao")) {
+    //        //return batchUpdate2(sql, modifier, parametersList);
+    //        //        } else {
+    //        return batchUpdate1(sql, modifier, parametersList);
+    //        //        }
+    //    }
+
+    //    private int[] batchUpdate1(String sql, StatementMetaData modifier,
+    //            List<Map<String, Object>> parametersList) {
+    //        int[] updated = new int[parametersList.size()];
+    //        for (int i = 0; i < updated.length; i++) {
+    //            Map<String, Object> parameters = parametersList.get(i);
+    //            SQLThreadLocal.set(SQLType.WRITE, sql, modifier, parameters);
+    //            updated[i] = update(sql, modifier, parameters);
+    //            SQLThreadLocal.remove();
+    //        }
+    //        return updated;
+    //    }
+
+    //    private int[] batchUpdate2(String sql, Modifier modifier,
+    //            List<Map<String, Object>> parametersList) {
+    //        if (parametersList.size() == 0) {
+    //            return new int[0];
+    //        }
+    //        // sql --> args[]
+    //        HashMap<String, List<Object[]>> batches = new HashMap<String, List<Object[]>>();
+    //        // sql --> named args
+    //        HashMap<String, List<Map<String, Object>>> batches2 = new HashMap<String, List<Map<String, Object>>>();
+    //        // sql --> [2,3,6,9] positions of parametersList
+    //        Map<String, List<Integer>> positions = new HashMap<String, List<Integer>>();
+    //
+    //        for (int i = 0; i < parametersList.size(); i++) {
+    //            SQLInterpreterResult ir = interpret(sql, modifier, parametersList.get(i));
+    //            List<Object[]> args = batches.get(ir.getSQL());
+    //            List<Integer> position = positions.get(ir.getSQL());
+    //            List<Map<String, Object>> maplist = batches2.get(ir.getSQL());
+    //            if (args == null) {
+    //                args = new LinkedList<Object[]>();
+    //                batches.put(ir.getSQL(), args);
+    //                position = new LinkedList<Integer>();
+    //                positions.put(ir.getSQL(), position);
+    //                maplist = new LinkedList<Map<String, Object>>();
+    //                batches2.put(ir.getSQL(), maplist);
+    //            }
+    //            position.add(i);
+    //            args.add(ir.getParameters());
+    //            maplist.add(parametersList.get(i));
+    //        }
+    //        if (batches.size() == 1) {
+    //            SQLThreadLocal.set(SQLType.WRITE, sql, modifier, parametersList);
+    //            int[] updated = jdbc.batchUpdate(modifier, batches.keySet().iterator().next(), batches
+    //                    .values().iterator().next());
+    //            SQLThreadLocal.remove();
+    //            return updated;
+    //        }
+    //        int[] batchUpdated = new int[parametersList.size()];
+    //        for (Map.Entry<String, List<Object[]>> batch : batches.entrySet()) {
+    //            String batchSQL = batch.getKey();
+    //            List<Object[]> values = batch.getValue();
+    //            List<Map<String, Object>> map = batches2.get(batchSQL);
+    //            SQLThreadLocal.set(SQLType.WRITE, sql, modifier, map);
+    //            int[] updated = jdbc.batchUpdate(modifier, batchSQL, values);
+    //            SQLThreadLocal.remove();
+    //            List<Integer> position = positions.get(batchSQL);
+    //            int i = 0;
+    //            for (Integer p : position) {
+    //                batchUpdated[p] = updated[i++];
+    //            }
+    //        }
+    //        return batchUpdated;
+    //
+    //    }
+
+    //    protected InterpreterOutput interpret(String jadeSQL, StatementMetaData modifier,
+    //            Map<String, Object> parametersAsMap) {
+    //
+    //        //
+    //        StatementRuntimeImpl result = null;
+    //        // 
+    //        for (Interpreter interpreter : interpreters) {
+    //            String sql = (result == null) ? jadeSQL : result.getSQL();
+    //            Object[] parameters = (result == null) ? null : result.getParameters();
+    //            InterpreterOutput t = interpreter.interpret(dataSource, sql, modifier, parametersAsMap,
+    //                    parameters);
+    //            if (t != null) {
+    //                if (result == null) {
+    //                    result = new StatementRuntimeImpl();
+    //                }
+    //                if (t.getSQL() != null) {
+    //                    result.setSQL(t.getSQL());
+    //                }
+    //                if (t.getParameters() != null) {
+    //                    result.setParameters(t.getParameters());
+    //                }
+    //                if (t.getClientInfo() != null) {
+    //                    result.setClientInfo(t.getClientInfo());
+    //                }
+    //            }
+    //        }
+    //        // path、catalog、node
+    //        Method daoMethod = modifier.getMethod();
+    //        Class<?> daoClass = daoMethod.getClass();
+    //        DAO dao = daoClass.getAnnotation(DAO.class);
+    //
+    //        result.setClientInfo(RoutingConnection.PATH, daoClass.getName());
+    //
+    //        // catalog
+    //        if (result.getClientInfo(RoutingConnection.CATALOG) == null) {
+    //            if (dao.catalog() != null && dao.catalog().length() > 0) {
+    //                result.setClientInfo(RoutingConnection.CATALOG, dao.catalog());
+    //            }
+    //        }
+    //
+    //        // node
+    //        if (result.getClientInfo(RoutingConnection.NODE) == null) {
+    //            UseMaster useMaster = daoMethod.getAnnotation(UseMaster.class);
+    //            if (useMaster != null) {
+    //                if (useMaster.value()) {
+    //                    result.setClientInfo(RoutingConnection.NODE, "master");
+    //                } else {
+    //                    result.setClientInfo(RoutingConnection.NODE, "slave");
+    //                }
+    //            }
+    //        }
+    //        //
+    //        return result;
+    //    }
+
+}

+ 51 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataSourceFactory.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess;
+
+import java.util.Map;
+
+import net.paoding.rose.jade.context.spring.SpringDataSourceFactory;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+/**
+ * Jade框架的数据源接口,应用程序通过配置合适的DataSourceFactory实现,框架才能将SQL语句发送到正确的数据库上。
+ * <p>
+ * 
+ * @see SpringDataSourceFactory
+ * @author qieqie.wang
+ */
+public interface DataSourceFactory {
+
+    /**
+     * 框架调用此方法为一次DAO执行设置相应的数据源,当你的代码调用一次DAO方法时,本方法将对应被框架调用一次。
+     * <p>
+     * 
+     * 框架把每一个不同的DAO方法抽象为一个唯一的metaData对象,当本方法被调用时候,
+     * 框架会传入DAO方法对应的metaData参数到本方法中 。
+     * 同时框架也会将调用此DAO方法时候的参数传入到本方法中,以让你有机会了解到当前的具体运行时。
+     * 当然,你如果可以不理会metadata和runtime参数,如果你不敢兴趣的话。有些系统可能只有一个数据源,此时就是如此。
+     * <p>
+     * 参数说明:<br>
+     * runtime默认包含调用DAO方法时的参数,你可以通过该参数的名称获取之(如<code>@SQLParam("id")</code>
+     * 中的"id") ,同时也支持使用":1"、":2"的方式获取第一个、第二个参数。
+     * <p>
+     * 
+     * @param metaData 正在执行的DAO方法
+     * @param runtime DAO方法执行时参数
+     * @return
+     */
+    DataSourceHolder getHolder(StatementMetaData metaData, Map<String, Object> runtime);
+}

+ 84 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/DataSourceHolder.java

@@ -0,0 +1,84 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess;
+
+import javax.sql.DataSource;
+
+/**
+ * 用于表示一个 {@link DataSource} 或 {@link DataSourceFactory} 类。一个
+ * {@link DataSourceHolder} 有且只能表示这两种类型其中之一
+ * <p>
+ * 
+ * @see DataSourceFactory
+ * @author qieqie.wang
+ * 
+ */
+public class DataSourceHolder {
+
+    private final DataSource dataSource;
+
+    private final DataSourceFactory dataSourceFactory;
+
+    /**
+     * 构造一个holder实例,所提供的参数必须是 {@link DataSource} 或
+     * {@link DataSourceFactory}类型
+     * 
+     * @throws IllegalArgumentException
+     * 
+     * @param dataSourceOrItsFactory
+     */
+    public DataSourceHolder(Object dataSourceOrItsFactory) {
+        if (dataSourceOrItsFactory instanceof DataSource) {
+            this.dataSource = (DataSource) dataSourceOrItsFactory;
+            this.dataSourceFactory = null;
+            return;
+        }
+        if (dataSourceOrItsFactory instanceof DataSourceFactory) {
+            this.dataSource = null;
+            this.dataSourceFactory = (DataSourceFactory) dataSourceOrItsFactory;
+            return;
+        }
+        throw new IllegalArgumentException("" + dataSourceOrItsFactory);
+    }
+
+    //------------------
+
+    /**
+     * 包含的是一个DataSourceFactory?
+     */
+    public boolean isFactory() {
+        return this.dataSourceFactory != null;
+    }
+
+    /**
+     * 返回所代表的 {@link DataSource},如不是返回null
+     * 
+     * @return
+     */
+    public DataSource getDataSource() {
+        return dataSource;
+    }
+
+    /**
+     * 返回所代表的 {@link DataSourceFactory},如不是返回null
+     * 
+     * @return
+     */
+    public DataSourceFactory getFactory() {
+        return dataSourceFactory;
+    }
+
+}

+ 124 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/datasource/HierarchicalDataSourceFactory.java

@@ -0,0 +1,124 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess.datasource;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.dataaccess.DataSourceFactory;
+import net.paoding.rose.jade.dataaccess.DataSourceHolder;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * package层级式的数据源工厂。
+ * <p>
+ * 
+ * 本工厂实现可以注册多个不同的数据源,使用者需要为每个数据源设定一个以点号分隔的名字,形同一个package名称或class全名称。
+ * 当框架要求本工厂为某个DAO方法提供数据源时,本工厂会从class全名开始逐级寻找是否有相应数据源被注册,如果有则返回。
+ * 
+ * @author qieqie.wang@gmail.com
+ * 
+ */
+public class HierarchicalDataSourceFactory implements DataSourceFactory {
+
+    /**
+     * 注册的数据源,key为注册的名字
+     */
+    private ConcurrentHashMap<String, DataSourceHolder> dataSources = new ConcurrentHashMap<String, DataSourceHolder>();
+
+    /**
+     * 默认数据源,逐级没有找到相应的数据源时候返回默认数据源
+     */
+    private DataSourceHolder defaultDataSource;
+
+    public HierarchicalDataSourceFactory() {
+    }
+
+    public HierarchicalDataSourceFactory(DataSource defaultDataSource) {
+        this.defaultDataSource = new DataSourceHolder(defaultDataSource);
+    }
+
+    /**
+     * 注册一个数据源,名称以点号为分隔分成多级。如果名称星号,表示默认数据源。
+     * 
+     * @param name
+     * @param dataSource
+     */
+    public void registerDataSource(String name, DataSource dataSource) {
+        if (dataSource == null) {
+            throw new NullPointerException("dataSource");
+        }
+        if (StringUtils.isBlank(name)) {
+            throw new IllegalArgumentException("blank name");
+        }
+        if (name.equals("*")) {
+            defaultDataSource = new DataSourceHolder(dataSource);
+        } else {
+            dataSources.putIfAbsent(name, new DataSourceHolder(dataSource));
+        }
+    }
+
+    /**
+     * 注册一个数据源工厂,名称以点号为分隔分成多级。如果名称星号,表示默认数据源工厂。
+     * 
+     * @param name
+     * @param dataSource
+     */
+
+    public void registerDataSource(String name, DataSourceFactory dataSource) {
+        if (dataSource == null) {
+            throw new NullPointerException("dataSource");
+        }
+        if (StringUtils.isBlank(name)) {
+            throw new IllegalArgumentException("blank name");
+        }
+        if (name.equals("*")) {
+            defaultDataSource = new DataSourceHolder(dataSource);
+        } else {
+            dataSources.putIfAbsent(name, new DataSourceHolder(dataSource));
+        }
+    }
+
+    /**
+     * 根据DAO的类全名、以点号为分隔逐级从注册的数据源中寻找存在的数据源,如果有则立即返回之
+     */
+    @Override
+    public DataSourceHolder getHolder(StatementMetaData metaData, Map<String, Object> runtime) {
+        String daoName = metaData.getDAOMetaData().getDAOClass().getName();
+        String name = daoName;
+        DataSourceHolder dataSource = dataSources.get(name);
+        if (dataSource != null) {
+            return dataSource;
+        }
+        while (true) {
+            int index = name.lastIndexOf('.');
+            if (index == -1) {
+                dataSources.putIfAbsent(daoName, defaultDataSource);
+                return defaultDataSource;
+            }
+            name = name.substring(0, index);
+            dataSource = dataSources.get(name);
+            if (dataSource != null) {
+                dataSources.putIfAbsent(daoName, dataSource);
+                return dataSource;
+            }
+        }
+    }
+}

+ 109 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/datasource/MasterSlaveDataSourceFactory.java

@@ -0,0 +1,109 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess.datasource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.annotation.SQLType;
+import net.paoding.rose.jade.dataaccess.DataSourceFactory;
+import net.paoding.rose.jade.dataaccess.DataSourceHolder;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.springframework.util.CollectionUtils;
+
+/**
+ * 在master-slave模式下的应用程序可以使用,即所有写操作使用master,所有读操作使用slave
+ * <p>
+ * 
+ * 以下演示了使用他的Java代码(Spring配置文件的配置也可以参考以下代码并进行等价转化)
+ * 
+ * <pre>
+ * MasterSlaveDataSourceFactory mainFactory = new MasterSlaveDataSourceFactory();
+ * 
+ * DataSource master = getMasterDataSource();
+ * mainFactory.setMasters(new SimpleDataSourceFactory(master));
+ * 
+ * List&lt;DataSource&gt; slaves = getSlaveDataSources();
+ * if (queryFromMaster) {
+ *     slaves = new ArrayList&lt;DataSource&gt;(slaves);
+ *     slaves.add(master);
+ * }
+ * mainFactory.setSlaves(new RandomDataSourceFactory(slaves));
+ * </pre>
+ * 
+ * @author qieqie
+ * 
+ */
+public class MasterSlaveDataSourceFactory implements DataSourceFactory {
+
+    private DataSourceFactory masters = new RandomDataSourceFactory();
+
+    private DataSourceFactory slaves = new RandomDataSourceFactory();
+
+    public MasterSlaveDataSourceFactory() {
+    }
+
+    /**
+     * 
+     * @param master
+     * @param slaves
+     * @param queryFromMaster true代表允许从master数据源查询数据
+     */
+    public MasterSlaveDataSourceFactory(DataSource master, List<DataSource> slaves,
+            boolean queryFromMaster) {
+        if (queryFromMaster && !CollectionUtils.containsInstance(slaves, master)) {
+            slaves = new ArrayList<DataSource>(slaves);
+            slaves.add(master);
+        }
+        setSlaves(new RandomDataSourceFactory(slaves));
+        setMasters(new SimpleDataSourceFactory(master));
+    }
+
+    //------------------
+
+    /**
+     * 
+     * @param masters
+     * @see RandomDataSourceFactory
+     * @see SimpleDataSourceFactory
+     */
+    public void setMasters(DataSourceFactory masters) {
+        this.masters = masters;
+    }
+
+    /**
+     * 
+     * @param slaves
+     * @see RandomDataSourceFactory
+     */
+    public void setSlaves(DataSourceFactory slaves) {
+        this.slaves = slaves;
+    }
+
+    @Override
+    public DataSourceHolder getHolder(StatementMetaData metaData,
+            Map<String, Object> runtimeProperties) {
+        if (metaData.getSQLType() != SQLType.READ) {
+            return masters.getHolder(metaData, runtimeProperties);
+        } else {
+            return slaves.getHolder(metaData, runtimeProperties);
+        }
+    }
+}

+ 73 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/datasource/RandomDataSourceFactory.java

@@ -0,0 +1,73 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess.datasource;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.dataaccess.DataSourceFactory;
+import net.paoding.rose.jade.dataaccess.DataSourceHolder;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+/**
+ * 从预设的一系列的DataSource随机提供一个
+ * 
+ * @author qieqie
+ * 
+ */
+public class RandomDataSourceFactory implements DataSourceFactory {
+
+    private Random random = new Random();
+
+    private List<DataSourceHolder> dataSources = Collections.emptyList();
+
+    public RandomDataSourceFactory() {
+    }
+
+    public void addDataSource(DataSource dataSource) {
+        if (this.dataSources.size() == 0) {
+            this.dataSources = new ArrayList<DataSourceHolder>(dataSources);
+        }
+        this.dataSources.add(new DataSourceHolder(dataSource));
+    }
+
+    public RandomDataSourceFactory(List<DataSource> dataSources) {
+        this.setDataSources(dataSources);
+    }
+
+    public void setDataSources(List<DataSource> dataSources) {
+        this.dataSources = new ArrayList<DataSourceHolder>(dataSources.size());
+        for (DataSource dataSource : dataSources) {
+            this.dataSources.add(new DataSourceHolder(dataSource));
+        }
+    }
+
+    @Override
+    public DataSourceHolder getHolder(StatementMetaData metaData,
+            Map<String, Object> runtimeProperties) {
+        if (dataSources.size() == 0) {
+            return null;
+        }
+        int index = random.nextInt(dataSources.size()); // 0.. size
+        return dataSources.get(index);
+    }
+
+}

+ 71 - 0
rose-jade/src/main/java/net/paoding/rose/jade/dataaccess/datasource/SimpleDataSourceFactory.java

@@ -0,0 +1,71 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.dataaccess.datasource;
+
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.dataaccess.DataSourceFactory;
+import net.paoding.rose.jade.dataaccess.DataSourceHolder;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+/**
+ * 当你的应用程序只需要一个DataSource时候使用这个实现即可!
+ * 
+ * @author qieqie
+ * 
+ */
+public class SimpleDataSourceFactory implements DataSourceFactory {
+
+    private DataSourceHolder dataSource;
+
+    /**
+     * 构造候还得继续调用 {@link #setDataSource(DataSource)} 设置数据源,谢谢
+     */
+    public SimpleDataSourceFactory() {
+    }
+
+    /**
+     * 提供的数据源要求非null
+     * 
+     * @param dataSource
+     */
+    public SimpleDataSourceFactory(DataSource dataSource) {
+        setDataSource(dataSource);
+    }
+
+    /**
+     * 设置数据源。
+     * <p>
+     * 提供的数据源要求非null
+     * 
+     * @param dataSource
+     */
+    public void setDataSource(DataSource dataSource) {
+        if (dataSource == null) {
+            throw new NullPointerException("dataSource");
+        }
+        this.dataSource = new DataSourceHolder(dataSource);
+    }
+
+    @Override
+    public DataSourceHolder getHolder(StatementMetaData metaData,
+            Map<String, Object> runtimeProperties) {
+        return dataSource;
+    }
+
+}

+ 66 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/AbstractCollectionRowMapper.java

@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * 将SQL结果集的一行映射为某种集合
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public abstract class AbstractCollectionRowMapper implements RowMapper {
+
+    private Class<?> elementType;
+
+    public AbstractCollectionRowMapper(StatementMetaData modifier) {
+        Class<?>[] genericTypes = modifier.getGenericReturnTypes();
+        if (genericTypes.length < 1) {
+            throw new IllegalArgumentException("Collection generic");
+        }
+        elementType = genericTypes[0];
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
+        int columnSize = rs.getMetaData().getColumnCount();
+        Collection collection = createCollection(columnSize);
+        // columnIndex从1开始
+        for (int columnIndex = 1; columnIndex <= columnSize; columnIndex++) {
+            collection.add(JdbcUtils.getResultSetValue(rs, columnIndex, elementType));
+        }
+        return collection;
+    }
+
+    /**
+     * 由子类覆盖此方法,提供一个空的具体集合实现类
+     * 
+     * @param columnSize
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    protected abstract Collection createCollection(int columnSize);
+
+}

+ 49 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/ArrayRowMapper.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+import java.lang.reflect.Array;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * 将SQL结果集的一行映射为一个数组
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class ArrayRowMapper implements RowMapper {
+
+    private Class<?> componentType;
+
+    public ArrayRowMapper(Class<?> returnType) {
+        this.componentType = returnType.getComponentType();
+    }
+
+    @Override
+    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
+        int columnSize = rs.getMetaData().getColumnCount();
+        Object array = Array.newInstance(componentType, columnSize);
+        for (int i = 0; i < columnSize; i++) {
+            Object value = JdbcUtils.getResultSetValue(rs, (i + 1), componentType);
+            Array.set(array, i, value);
+        }
+        return array;
+    }
+}

+ 267 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/BeanPropertyRowMapper.java

@@ -0,0 +1,267 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 net.paoding.rose.jade.rowmapper;
+
+import java.beans.PropertyDescriptor;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeanInstantiationException;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
+import org.springframework.beans.NotWritablePropertyException;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.support.JdbcUtils;
+import org.springframework.util.Assert;
+
+/**
+ * {@link RowMapper} implementation that converts a row into a new instance
+ * of the specified mapped target class. The mapped target class must be a
+ * top-level class and it must have a default or no-arg constructor.
+ * 
+ * <p>
+ * Column values are mapped based on matching the column name as obtained
+ * from result set metadata to public setters for the corresponding
+ * properties. The names are matched either directly or by transforming a
+ * name separating the parts with underscores to the same name using
+ * "camel" case.
+ * 
+ * <p>
+ * Mapping is provided for fields in the target class for many common
+ * types, e.g.: String, boolean, Boolean, byte, Byte, short, Short, int,
+ * Integer, long, Long, float, Float, double, Double, BigDecimal,
+ * <code>java.util.Date</code>, etc.
+ * 
+ * <p>
+ * To facilitate mapping between columns and fields that don't have
+ * matching names, try using column aliases in the SQL statement like
+ * "select fname as first_name from customer".
+ * 
+ * <p>
+ * Please note that this class is designed to provide convenience rather
+ * than high performance. For best performance consider using a custom
+ * RowMapper.
+ * 
+ * @author Thomas Risberg
+ * @author Juergen Hoeller
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @since 2.5
+ */
+public class BeanPropertyRowMapper implements RowMapper {
+
+    /** Logger available to subclasses */
+    protected final Log logger = LogFactory.getLog(getClass());
+
+    /** The class we are mapping to */
+    private final Class<?> mappedClass;
+
+    /** Map of the fields we provide mapping for */
+    private Map<String, PropertyDescriptor> mappedFields;
+
+    private final boolean checkColumns;
+
+    private final boolean checkProperties;
+
+    /** Set of bean properties we provide mapping for */
+    private Set<String> mappedProperties;
+
+    /**
+     * Create a new BeanPropertyRowMapper, accepting unpopulated properties
+     * in the target bean.
+     * 
+     * @param mappedClass the class that each row should be mapped to
+     */
+    public BeanPropertyRowMapper(Class<?> mappedClass, boolean checkColumns, boolean checkProperties) {
+        this.mappedClass = mappedClass;
+        Assert.state(this.mappedClass != null, "Mapped class was not specified");
+        this.checkProperties = checkProperties;
+        this.checkColumns = checkColumns;
+        initialize();
+    }
+
+    /**
+     * Initialize the mapping metadata for the given class.
+     * 
+     * @param mappedClass the mapped class.
+     */
+    protected void initialize() {
+        this.mappedFields = new HashMap<String, PropertyDescriptor>();
+        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
+        if (checkProperties) {
+            mappedProperties = new HashSet<String>();
+        }
+        for (int i = 0; i < pds.length; i++) {
+            PropertyDescriptor pd = pds[i];
+            if (pd.getWriteMethod() != null) {
+                if (checkProperties) {
+                    this.mappedProperties.add(pd.getName());
+                }
+                this.mappedFields.put(pd.getName().toLowerCase(), pd);
+                for (String underscoredName : underscoreName(pd.getName())) {
+                    if (underscoredName != null
+                            && !pd.getName().toLowerCase().equals(underscoredName)) {
+                        this.mappedFields.put(underscoredName, pd);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Convert a name in camelCase to an underscored name in lower case.
+     * Any upper case letters are converted to lower case with a preceding
+     * underscore.
+     * 
+     * @param camelCaseName the string containing original name
+     * @return the converted name
+     */
+    private String[] underscoreName(String camelCaseName) {
+        StringBuilder result = new StringBuilder();
+        if (camelCaseName != null && camelCaseName.length() > 0) {
+            result.append(camelCaseName.substring(0, 1).toLowerCase());
+            for (int i = 1; i < camelCaseName.length(); i++) {
+                char ch = camelCaseName.charAt(i);
+                if (Character.isUpperCase(ch)) {
+                    result.append("_");
+                    result.append(Character.toLowerCase(ch));
+                } else {
+                    result.append(ch);
+                }
+            }
+        }
+        String name = result.toString();
+        // 当name为user1_name2时,使name2为user_1_name_2
+        // 这使得列user_1_name_2的列能映射到user1Name2属性
+        String name2 = null;
+        boolean digitFound = false;
+        for (int i = name.length() - 1; i >= 0; i--) {
+            if (Character.isDigit(name.charAt(i))) {
+            	// 遇到数字就做一个标识并continue,直到不是时才不continue
+                digitFound = true;
+                continue;
+            }
+            // 只有上一个字符是数字才做下划线
+            if (digitFound && i < name.length() - 1 && i > 0) {
+                if (name2 == null) {
+                    name2 = name;
+                }
+                name2 = name2.substring(0, i + 1) + "_" + name2.substring(i + 1);
+            }
+            digitFound = false;
+        }
+        return new String[] { name, name2 };
+    }
+
+    /**
+     * Extract the values for all columns in the current row.
+     * <p>
+     * Utilizes public setters and result set metadata.
+     * 
+     * @see java.sql.ResultSetMetaData
+     */
+    public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
+        // spring's : Object mappedObject = BeanUtils.instantiateClass(this.mappedClass);
+        // jade's : private Object instantiateClass(this.mappedClass);
+        // why: 经过简单的笔记本测试,mappedClass.newInstrance性能比BeanUtils.instantiateClass(mappedClass)快1个数量级
+        Object mappedObject = instantiateClass(this.mappedClass);
+        BeanWrapper bw = new BeanWrapperImpl(mappedObject);
+
+        ResultSetMetaData rsmd = rs.getMetaData();
+        int columnCount = rsmd.getColumnCount();
+
+        boolean warnEnabled = logger.isWarnEnabled();
+        boolean debugEnabled = logger.isDebugEnabled();
+        Set<String> populatedProperties = (checkProperties ? new HashSet<String>() : null);
+
+        for (int index = 1; index <= columnCount; index++) {
+            String column = JdbcUtils.lookupColumnName(rsmd, index).toLowerCase();
+            PropertyDescriptor pd = this.mappedFields.get(column);
+            if (pd != null) {
+                try {
+                    Object value = JdbcUtils.getResultSetValue(rs, index, pd.getPropertyType());
+                    if (debugEnabled && rowNumber == 0) {
+                        logger.debug("Mapping column '" + column + "' to property '" + pd.getName()
+                                + "' of type " + pd.getPropertyType());
+                    }
+                    bw.setPropertyValue(pd.getName(), value);
+                    if (populatedProperties != null) {
+                        populatedProperties.add(pd.getName());
+                    }
+                } catch (NotWritablePropertyException ex) {
+                    throw new DataRetrievalFailureException("Unable to map column " + column
+                            + " to property " + pd.getName(), ex);
+                }
+            } else {
+                if (checkColumns) {
+                    throw new InvalidDataAccessApiUsageException("Unable to map column '" + column
+                            + "' to any properties of bean " + this.mappedClass.getName());
+                }
+                if (warnEnabled && rowNumber == 0) {
+                    logger.warn("Unable to map column '" + column + "' to any properties of bean "
+                            + this.mappedClass.getName());
+                }
+            }
+        }
+
+        if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {
+            throw new InvalidDataAccessApiUsageException(
+                    "Given ResultSet does not contain all fields "
+                            + "necessary to populate object of class [" + this.mappedClass + "]: "
+                            + this.mappedProperties);
+        }
+
+        return mappedObject;
+    }
+
+    /**
+     * 
+     * @param clazz
+     * @return
+     * @throws BeanInstantiationException
+     * @see {@link BeanUtils#instantiateClass(Class)}
+     */
+    private static Object instantiateClass(Class<?> clazz) throws BeanInstantiationException {
+        /*- spring's BeanUtils.instantiateClass()
+         Assert.notNull(clazz, "Class must not be null");
+        if (clazz.isInterface()) {
+            throw new BeanInstantiationException(clazz, "Specified class is an interface");
+        }
+        try {
+            return instantiateClass(clazz.getDeclaredConstructor((Class[]) null), null);
+        }
+        catch (NoSuchMethodException ex) {
+            throw new BeanInstantiationException(clazz, "No default constructor found", ex);
+        }*/
+
+        try {
+            return clazz.newInstance();
+        } catch (Exception ex) {
+            throw new BeanInstantiationException(clazz, ex.getMessage(), ex);
+        }
+    }
+
+}

+ 192 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/DefaultRowMapperFactory.java

@@ -0,0 +1,192 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+import java.lang.reflect.Constructor;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.paoding.rose.jade.annotation.RowHandler;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.apache.commons.lang.ClassUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeanInstantiationException;
+import org.springframework.jdbc.core.ColumnMapRowMapper;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.SingleColumnRowMapper;
+
+/**
+ * 支持DAO方法返回类型:
+ * <p>
+ * <ul>
+ * <li>int、long等primitive:期望返回单列、1行</li>
+ * <li>Integer、Long等包装类型的:期望返回单列、0行或1行</li>
+ * <li>String、BigDecimal:期望返回单列,0行或1行</li>
+ * <li>java.util.Date及其子类:期望返回单列,0行或1行</li>
+ * <li>byte[]:期望返回单行、单列;列类型可转化为byte[]类型(比如blob类型)</li>
+ * <li>Blob、Clob:期望返回单,0行或1行</li>
+ * <li><code>数组(int[]、String[]等):期望返回单列,多行;</li>
+ * <li>数组(User[]等):期望返回多列,多行;</li>
+ * <li>集合(List&lt;Integer&gt;、Set&lt;String&gt等): 期望返回单列,多行;</li>
+ * <li>集合(List&lt;User&gt;、Set&lt;User&gt等): 期望返回单列,多行;</li>
+ * <li>映射(Map&lt;String, Date&gt): 期望返回2列,多行</li>
+ * <li>映射(Map&lt;String, User&gt): 期望返回多列,多行</li>
+ * <li>映射(Map&lt;String, String[]&gt): 期望返回多列,多行</li>
+ * <ul>
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+@SuppressWarnings("rawtypes")
+public class DefaultRowMapperFactory implements RowMapperFactory {
+
+    private static Log logger = LogFactory.getLog(RowMapperFactory.class);
+
+    private Map<String, RowMapper> rowMappers = new HashMap<String, RowMapper>();
+
+    @Override
+    public RowMapper getRowMapper(StatementMetaData modifier) {
+        RowHandler rowHandler = modifier.getAnnotation(RowHandler.class);
+        if (rowHandler != null) {
+            if (rowHandler.rowMapper() != RowHandler.ByDefault.class) {
+                try {
+                    RowMapper rowMapper = rowHandler.rowMapper().newInstance();
+                    if (logger.isInfoEnabled()) {
+                        logger.info("using rowMapper " + rowMapper + " for " + modifier);
+                    }
+
+                    return rowMapper;
+                } catch (Exception ex) {
+                    throw new BeanInstantiationException(rowHandler.rowMapper(), ex.getMessage(),
+                            ex);
+                }
+            }
+        }
+        //
+
+        Class<?> returnClassType = modifier.getMethod().getReturnType();
+        Class<?> rowType = getRowType(modifier);
+        // TODO rowType == null 
+
+        // BUGFIX: SingleColumnRowMapper 处理  Primitive Type 抛异常
+        if (rowType.isPrimitive()) {
+            rowType = ClassUtils.primitiveToWrapper(rowType);
+        }
+
+        // 根据类型创建  RowMapper
+        RowMapper rowMapper;
+
+        // 返回单列的查询的(或者返回只有2列的Map类型查询的)
+        if (TypeUtils.isColumnType(rowType)) {
+            if (Map.class.isAssignableFrom(returnClassType)) {
+                rowMapper = new MapEntryColumnRowMapper(modifier, rowType);
+            } else {
+                rowMapper = new SingleColumnRowMapper(rowType);
+            }
+        }
+        // 返回单行,用Map表示的, rowType == null ?  Map<String, ?>
+        else if (rowType == Object.class && Map.class.isAssignableFrom(returnClassType)) {
+            rowMapper = new ColumnMapRowMapper();
+        }
+        // 返回多列的,用Bean对象、集合、映射、数组来表示每一行的
+        else {
+            if (rowType == Map.class) {
+                rowMapper = new ColumnMapRowMapper();
+            } else if (rowType.isArray()) {
+                rowMapper = new ArrayRowMapper(rowType);
+            } else if ((rowType == List.class) || (rowType == Collection.class)) {
+                rowMapper = new ListRowMapper(modifier);
+            } else if (rowType == Set.class) {
+                rowMapper = new SetRowMapper(modifier);
+            } else {
+                boolean checkColumns = (rowHandler == null) ? true : rowHandler.checkColumns();
+                boolean checkProperties = (rowHandler == null) ? false : rowHandler
+                        .checkProperties();
+                String key = rowType.getName() + "[checkColumns=" + checkColumns
+                        + "&checkProperties=" + checkProperties + "]";
+                rowMapper = rowMappers.get(key);
+                if (rowMapper == null) {
+                    rowMapper = new BeanPropertyRowMapper(rowType, checkColumns, checkProperties); // jade's BeanPropertyRowMapper here
+                    rowMappers.put(key, rowMapper);
+                }
+            }
+            // 如果DAO方法最终返回的是Map,rowMapper要返回Map.Entry对象
+            if (Map.class.isAssignableFrom(returnClassType)) {
+                rowMapper = new MapEntryRowMapper(modifier, rowMapper);
+            }
+        }
+
+        if (logger.isInfoEnabled()) {
+            logger.info("using rowMapper " + rowMapper + " for " + modifier);
+        }
+
+        return rowMapper;
+    }
+
+    // 获得返回的集合元素类型
+    private static Class<?> getRowType(StatementMetaData statementMetaData) {
+        Class<?> returnClassType = statementMetaData.getMethod().getReturnType();
+        if (Collection.class.isAssignableFrom(returnClassType)) {
+            return getRowTypeFromCollectionType(statementMetaData, returnClassType);
+        } else if (Map.class.isAssignableFrom(returnClassType)) {
+            return getRowTypeFromMapType(statementMetaData, returnClassType);
+        } else if (returnClassType.isArray() && returnClassType != byte[].class) {
+            // 数组类型, 支持多重数组
+            return returnClassType.getComponentType();
+        }
+
+        // 此时代表整个DAO方法只关心结果集第一行
+        return returnClassType;
+    }
+
+    private static Class<?> getRowTypeFromMapType(StatementMetaData modifier,
+            Class<?> returnClassType) {
+        Class<?> rowType;
+        // 获取  Map<K, V> 值元素类型
+        Class<?>[] genericTypes = modifier.getGenericReturnTypes();
+        if (genericTypes.length != 2) {
+            throw new IllegalArgumentException("the returned generic type '"
+                    + returnClassType.getName() + "' should has two actual type parameters.");
+        }
+        rowType = genericTypes[1]; // 取  V 类型
+        return rowType;
+    }
+
+    private static Class<?> getRowTypeFromCollectionType(StatementMetaData modifier,
+            Class<?> returnClassType) {
+        Class<?> rowType;
+        // 仅支持  List / Collection / Set
+        if ((returnClassType != List.class) && (returnClassType != Collection.class)
+                && (returnClassType != Set.class)) {
+            throw new IllegalArgumentException("error collection type " + returnClassType.getName()
+                    + "; only support List, Set, Collection");
+        }
+        // 获取集合元素类型
+        Class<?>[] genericTypes = modifier.getGenericReturnTypes();
+        if (genericTypes.length != 1) {
+            throw new IllegalArgumentException("the returned generic type '"
+                    + returnClassType.getName() + "' should has a actual type parameter.");
+        }
+        rowType = genericTypes[0];
+        return rowType;
+    }
+
+}

+ 40 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/ListRowMapper.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+/**
+ * 将SQL结果集的一行映射为List
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class ListRowMapper extends AbstractCollectionRowMapper {
+
+    public ListRowMapper(StatementMetaData modifier) {
+        super(modifier);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected Collection createCollection(int columnSize) {
+        return new ArrayList(columnSize);
+    }
+}

+ 121 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/MapEntryColumnRowMapper.java

@@ -0,0 +1,121 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import net.paoding.rose.jade.annotation.KeyColumnOfMap;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.dao.TypeMismatchDataAccessException;
+import org.springframework.jdbc.IncorrectResultSetColumnCountException;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * 在将SQL结果集某一行的两列,一列作为key,一列作为value,形成一个key-value映射对。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ * 
+ */
+public class MapEntryColumnRowMapper implements RowMapper {
+
+    private static Log logger = LogFactory.getLog(MapEntryColumnRowMapper.class);
+
+    private String keyColumn;
+
+    private int keyColumnIndex = 1;
+
+    private int valueColumnIndex = 2;
+
+    private Class<?> keyType, valueType;
+
+    private StatementMetaData modifier;
+
+    public MapEntryColumnRowMapper(StatementMetaData modifier, Class<?> requiredType) {
+        this.modifier = modifier;
+        Class<?>[] genericTypes = modifier.getGenericReturnTypes();
+        if (genericTypes.length < 2) {
+            throw new IllegalArgumentException("please set map generic parameters in method: "
+                    + modifier.getMethod());
+        }
+
+        // 获取 Key 类型与列
+        KeyColumnOfMap mapKey = modifier.getAnnotation(KeyColumnOfMap.class);
+        // 设置 Key 类型与列
+        this.keyColumn = (mapKey != null) ? mapKey.value() : null;
+        this.keyType = genericTypes[0];
+        this.valueType = genericTypes[1];
+    }
+
+    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
+
+        // 验证列的数目
+        if (rowNum == 0) {
+            ResultSetMetaData rsmd = rs.getMetaData();
+            int nrOfColumns = rsmd.getColumnCount();
+            if (nrOfColumns != 2) {
+                throw new IncorrectResultSetColumnCountException(2, nrOfColumns);
+            }
+
+            if (StringUtils.isNotEmpty(keyColumn)) {
+                keyColumnIndex = rs.findColumn(keyColumn);
+                if (keyColumnIndex == 1) {
+                    valueColumnIndex = 2;
+                } else if (keyColumnIndex == 2) {
+                    valueColumnIndex = 1;
+                } else {
+                    throw new IllegalArgumentException(String.format(
+                            "wrong key name %s for method: %s ", keyColumn, modifier.getMethod()));
+                }
+                keyColumn = null;
+            }
+            if (logger.isDebugEnabled()) {
+                logger.debug(String.format("keyIndex=%s; valueIndex=%s; for method: %s ",
+                        keyColumnIndex, valueColumnIndex, modifier.getMethod()));
+            }
+        }
+
+        // 从  JDBC ResultSet 获取  Key
+        Object key = JdbcUtils.getResultSetValue(rs, keyColumnIndex, keyType);
+        if (key != null && !keyType.isInstance(key)) {
+            ResultSetMetaData rsmd = rs.getMetaData();
+            throw new TypeMismatchDataAccessException( // NL
+                    "Type mismatch affecting row number " + rowNum + " and column type '"
+                            + rsmd.getColumnTypeName(keyColumnIndex) + "' expected type is '"
+                            + keyType + "'");
+        }
+
+        // 从  JDBC ResultSet 获取  Value
+        Object value = JdbcUtils.getResultSetValue(rs, valueColumnIndex, valueType);
+        if (value != null && !valueType.isInstance(value)) {
+            ResultSetMetaData rsmd = rs.getMetaData();
+            throw new TypeMismatchDataAccessException( // NL
+                    "Type mismatch affecting row number " + rowNum + " and column type '"
+                            + rsmd.getColumnTypeName(valueColumnIndex) + "' expected type is '"
+                            + valueType + "'");
+        }
+
+        // key有可能为null,不过我们还是做进去
+        return new MapEntryImpl<Object, Object>(key, value);
+    }
+}

+ 74 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/MapEntryImpl.java

@@ -0,0 +1,74 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+import java.util.Map;
+
+/**
+ * 封装一对Key-Value值。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class MapEntryImpl<K, V> implements Map.Entry<K, V> {
+
+    private final K key;
+
+    private V value;
+
+    /**
+     * Creates new entry.
+     */
+    public MapEntryImpl(K k, V v) {
+        value = v;
+        key = k;
+    }
+
+    public final K getKey() {
+        return key;
+    }
+
+    public final V getValue() {
+        return value;
+    }
+
+    public final V setValue(V newValue) {
+        V oldValue = value;
+        value = newValue;
+        return oldValue;
+    }
+
+    public final boolean equals(Object o) {
+        if (!(o instanceof Map.Entry)) return false;
+        Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+        Object k1 = getKey();
+        Object k2 = e.getKey();
+        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
+            Object v1 = getValue();
+            Object v2 = e.getValue();
+            if (v1 == v2 || (v1 != null && v1.equals(v2))) return true;
+        }
+        return false;
+    }
+
+    public final int hashCode() {
+        return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
+    }
+
+    public final String toString() {
+        return getKey() + "=" + getValue();
+    }
+}

+ 96 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/MapEntryRowMapper.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+import net.paoding.rose.jade.annotation.KeyColumnOfMap;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.dao.TypeMismatchDataAccessException;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.support.JdbcUtils;
+
+/**
+ * 在将SQL结果集的一行映射为某种对象后,再提取出一个列作为key,形成key-value映射对。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ * 
+ */
+public class MapEntryRowMapper implements RowMapper {
+
+    private static Log logger = LogFactory.getLog(MapEntryRowMapper.class);
+
+    private final RowMapper mapper;
+
+    private String keyColumn;
+
+    private int keyColumnIndex = 1;
+
+    private Class<?> keyType;
+
+    private StatementMetaData modifier;
+
+    public MapEntryRowMapper(StatementMetaData modifier, RowMapper mapper) {
+        this.modifier = modifier;
+        Class<?>[] genericTypes = modifier.getGenericReturnTypes();
+        if (genericTypes.length < 2) {
+            throw new IllegalArgumentException("please set map generic parameters in method: "
+                    + modifier.getMethod());
+        }
+        KeyColumnOfMap mapKey = modifier.getAnnotation(KeyColumnOfMap.class);
+        this.keyColumn = (mapKey != null) ? mapKey.value() : null;
+        this.keyType = genericTypes[0];
+        this.mapper = mapper;
+    }
+
+    @Override
+    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
+        if (rowNum == 0) {
+            if (StringUtils.isNotEmpty(keyColumn)) {
+                keyColumnIndex = rs.findColumn(keyColumn);
+                if (keyColumnIndex <= 0) {
+                    throw new IllegalArgumentException(String.format(
+                            "wrong key name %s for method: %s ", keyColumn, modifier.getMethod()));
+                }
+                keyColumn = null;
+            }
+
+            if (logger.isDebugEnabled()) {
+                logger.debug(String.format("keyIndex=%s; for method: %s ", keyColumnIndex, modifier
+                        .getMethod()));
+            }
+        }
+
+        // 从  JDBC ResultSet 获取 Key
+        Object key = JdbcUtils.getResultSetValue(rs, keyColumnIndex, keyType);
+        if (key != null && !keyType.isInstance(key)) {
+            ResultSetMetaData rsmd = rs.getMetaData();
+            throw new TypeMismatchDataAccessException( // NL
+                    "Type mismatch affecting row number " + rowNum + " and column type '"
+                            + rsmd.getColumnTypeName(keyColumnIndex) + "' expected type is '"
+                            + keyType + "'");
+        }
+
+        return new MapEntryImpl<Object, Object>(key, mapper.mapRow(rs, rowNum));
+    }
+}

+ 44 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/RowMapperFactory.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+
+
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * {@link RowMapperFactory}可以为每一个查询类型DAO方法创建对应的行映射器{@link RowMapper}
+ * 对象,用来把SQL结果集中的每一行转化为DAO方法定义的返回类型所要求的组件类型。
+ * <p>
+ * 对于DAO方法返回类型是List<User>或User的,RowMapper用来将结果集的每一行转化为User对象。<br>
+ * 如果返回类型是String[]或String的,RowMapper用来将结果集的每一行转化为一个字符串对象。<br>
+ * 。。。
+ * <p>
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public interface RowMapperFactory {
+
+    /**
+     * 根据DAO方法的返回类型定义,解析可能存在的泛型,创建对应的行映射器对象。
+     * 
+     * @return 如果无法解析时可返回null
+     */
+    public RowMapper getRowMapper(StatementMetaData metaData);
+}

+ 40 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/SetRowMapper.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.rowmapper;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+/**
+ * 将SQL结果集的一行映射为Set
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class SetRowMapper extends AbstractCollectionRowMapper {
+
+    public SetRowMapper(StatementMetaData modifier) {
+        super(modifier);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected Collection createCollection(int columnSize) {
+        return new HashSet(columnSize * 2);
+    }
+}

+ 21 - 0
rose-jade/src/main/java/net/paoding/rose/jade/rowmapper/TypeUtils.java

@@ -0,0 +1,21 @@
+package net.paoding.rose.jade.rowmapper;
+
+import java.math.BigDecimal;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.util.Date;
+
+import org.springframework.util.ClassUtils;
+
+public class TypeUtils {
+
+    public static boolean isColumnType(Class<?> columnTypeCandidate) {
+        return String.class == columnTypeCandidate // NL
+                || ClassUtils.isPrimitiveOrWrapper(columnTypeCandidate)// NL
+                || Date.class.isAssignableFrom(columnTypeCandidate) // NL
+                || columnTypeCandidate == byte[].class // NL
+                || columnTypeCandidate == BigDecimal.class // NL
+                || columnTypeCandidate == Blob.class // NL
+                || columnTypeCandidate == Clob.class;
+    }
+}

+ 81 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/DAOMetaData.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.util.Collections;
+import java.util.Map;
+
+
+/**
+ * {@link DAOMetaData} 封装缓存一个DAO接口类本身的一些信息,比如类对象、类常量等等
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class DAOMetaData {
+
+    /**
+     * DAO接类
+     */
+    private final Class<?> daoClass;
+
+    /**
+     * 定义在DAO接口上的常量(包含父接口的)
+     */
+    private final Map<String, ?> constants;
+
+    /**
+     * 
+     * @param daoClass
+     */
+    public DAOMetaData(Class<?> daoClass) {
+        this.daoClass = daoClass;
+        this.constants = Collections.unmodifiableMap(//
+                GenericUtils.getConstantFrom(daoClass, true, true));
+    }
+
+    public Class<?> getDAOClass() {
+        return daoClass;
+    }
+
+    public Map<String, ?> getConstants() {
+        return constants;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T getConstant(String fieldName) {
+        return (T) constants.get(fieldName);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof DAOMetaData) {
+            DAOMetaData other = (DAOMetaData) obj;
+            return daoClass.equals(other.daoClass);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return daoClass.hashCode() * 13;
+    }
+
+    @Override
+    public String toString() {
+        return daoClass.getName();
+    }
+}

+ 62 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/DefaultInterpreterFactory.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.lang.ArrayUtils;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+public class DefaultInterpreterFactory implements InterpreterFactory {
+
+    private Interpreter[] interpreters = new Interpreter[] { new SystemInterpreter() };
+
+    public DefaultInterpreterFactory() {
+    }
+
+    public DefaultInterpreterFactory(Interpreter[] interpreters) {
+        for (Interpreter interpreter : interpreters) {
+            this.addInterpreter(interpreter);
+        }
+    }
+
+    public DefaultInterpreterFactory(List<Interpreter> interpreters) {
+        for (Interpreter interpreter : interpreters) {
+            this.addInterpreter(interpreter);
+        }
+    }
+
+    public synchronized void addInterpreter(Interpreter interpreter) {
+        if (!ArrayUtils.contains(this.interpreters, interpreter)) {
+            Interpreter[] interpreters = Arrays.copyOf(this.interpreters,
+                    this.interpreters.length + 1);
+            interpreters[this.interpreters.length] = interpreter;
+            Arrays.sort(interpreters, new InterpreterComparator());
+            this.interpreters = interpreters;
+        }
+    }
+
+    @Override
+    public Interpreter[] getInterpreters(StatementMetaData metaData) {
+        return interpreters;
+    }
+
+}

+ 141 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/GenericUtils.java

@@ -0,0 +1,141 @@
+package net.paoding.rose.jade.statement;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 实现工具类,检查参数化类型的参数类型。
+ * 
+ * @author han.liao
+ */
+public class GenericUtils {
+
+    private static final Class<?>[] EMPTY_CLASSES = new Class<?>[0];
+
+    /**
+     * 从参数, 返回值, 基类的: Generic 类型信息获取传入的实际类信息。
+     * 
+     * @param genericType - Generic 类型信息
+     * 
+     * @return 实际类信息
+     */
+    public static Class<?>[] getActualClass(Type genericType) {
+
+        if (genericType instanceof ParameterizedType) {
+
+            Type[] actualTypes = ((ParameterizedType) genericType).getActualTypeArguments();
+            Class<?>[] actualClasses = new Class<?>[actualTypes.length];
+
+            for (int i = 0; i < actualTypes.length; i++) {
+                Type actualType = actualTypes[i];
+                if (actualType instanceof Class<?>) {
+                    actualClasses[i] = (Class<?>) actualType;
+                } else if (actualType instanceof GenericArrayType) {
+                    Type componentType = ((GenericArrayType) actualType).getGenericComponentType();
+                    actualClasses[i] = Array.newInstance((Class<?>) componentType, 0).getClass();
+                }
+            }
+
+            return actualClasses;
+        }
+
+        return EMPTY_CLASSES;
+    }
+
+    /**
+     * 收集类的所有常量。
+     * 
+     * @param clazz - 收集目标
+     * @param findAncestor - 是否查找父类
+     * @param findInterfaces - 是否查找接口
+     * 
+     * @return {@link Map} 包含类的所有常量
+     */
+    public static Map<String, ?> getConstantFrom(Class<?> clazz, // NL
+            boolean findAncestor, boolean findInterfaces) {
+
+        HashMap<String, Object> map = new HashMap<String, Object>();
+
+        if (findInterfaces) {
+            for (Class<?> interfaceClass : clazz.getInterfaces()) {
+                fillConstantFrom(interfaceClass, map);
+            }
+        }
+
+        if (findAncestor) {
+            Class<?> superClass = clazz;
+            while (superClass != null) {
+                fillConstantFrom(superClass, map);
+                superClass = superClass.getSuperclass();
+            }
+        }
+
+        fillConstantFrom(clazz, map);
+
+        return map;
+    }
+
+    // 填充静态常量
+    protected static void fillConstantFrom(Class<?> clazz, HashMap<String, Object> map) {
+
+        Field fields[] = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            if (field.isSynthetic()) {
+                continue; // 忽略系统常量
+            }
+
+            int modifiers = field.getModifiers();
+            if (!Modifier.isStatic(modifiers)) {
+                continue; // 忽略非静态常量
+            }
+
+            try {
+                if (field.isAccessible()) {
+                    field.setAccessible(true);
+                }
+                map.put(field.getName(), field.get(null));
+
+            } catch (SecurityException e) {
+                // Do nothing
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            }
+        }
+    }
+
+    // 测试代码
+    public static void main(String... args) {
+
+        // 输出所有常量
+        Map<String, ?> constants = getConstantFrom(Character.class, true, true);
+        System.out.println(constants);
+
+        // 输出方法的返回类型
+        for (Method method : ClassLoader.class.getMethods()) {
+            Class<?>[] classes = getActualClass(method.getGenericReturnType());
+            System.out.print(method.getName() + " = ");
+            System.out.println(Arrays.toString(classes));
+        }
+
+        // 输出超类的类型
+        Type genericSuperclassType = java.util.Properties.class.getGenericSuperclass();
+        System.out.print(genericSuperclassType + " = ");
+        System.out.println(Arrays.toString( // NL
+                getActualClass(genericSuperclassType)));
+
+        for (Type genericInterfaceType : java.util.Properties.class.getGenericInterfaces()) {
+            // 输出派生类的类型
+            Class<?>[] classes = getActualClass(genericInterfaceType);
+            System.out.print(genericInterfaceType + " = ");
+            System.out.println(Arrays.toString(classes));
+        }
+    }
+}

+ 39 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/Interpreter.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import org.springframework.core.annotation.Order;
+
+/**
+ * 可用 {@link Order}来调节优先级,根据 {@link Order} 语义,值越小越有效;
+ * <p>
+ * 如果没有标注 {@link Order} 使用默认值0。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ * 
+ */
+//按Spring语义规定,Order值越高该解释器越后执行
+@Order(0)
+public interface Interpreter {
+
+    /**
+     * 
+     * @param runtime
+     */
+    void interpret(StatementRuntime runtime);
+
+}

+ 37 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/InterpreterComparator.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.util.Comparator;
+
+import org.springframework.core.annotation.Order;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+public class InterpreterComparator implements Comparator<Interpreter> {
+
+    @Override
+    public int compare(Interpreter thees, Interpreter that) {
+        Order thessOrder = thees.getClass().getAnnotation(Order.class);
+        Order thatOrder = that.getClass().getAnnotation(Order.class);
+        int thessValue = thessOrder == null ? 0 : thessOrder.value();
+        int thatValue = thatOrder == null ? 0 : thatOrder.value();
+        return thessValue - thatValue;
+    }
+}

+ 26 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/InterpreterFactory.java

@@ -0,0 +1,26 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * 
+ */
+public interface InterpreterFactory {
+
+    Interpreter[] getInterpreters(StatementMetaData metaData);
+}

+ 141 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/JdbcStatement.java

@@ -0,0 +1,141 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.paoding.rose.jade.annotation.SQLType;
+
+import org.apache.commons.lang.ClassUtils;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+public class JdbcStatement implements Statement {
+
+    private final StatementMetaData metaData;
+
+    private final Interpreter[] interpreters;
+
+    private final Querier querier;
+
+    private final boolean batchUpdate;
+
+    private final SQLType sqlType;
+
+    public JdbcStatement(StatementMetaData statementMetaData, SQLType sqlType,
+            Interpreter[] interpreters, Querier querier) {
+        this.metaData = statementMetaData;
+        this.interpreters = (interpreters == null) ? new Interpreter[0] : interpreters;
+        this.querier = querier;
+        this.sqlType = sqlType;
+        if (sqlType == SQLType.WRITE) {
+            Method method = statementMetaData.getMethod();
+            Class<?>[] types = method.getParameterTypes();
+            Class<?> returnType = method.getReturnType();
+            if (returnType.isPrimitive()) {
+                returnType = ClassUtils.primitiveToWrapper(returnType);
+            }
+            if (types.length > 0 && List.class.isAssignableFrom(types[0])) {
+                this.batchUpdate = true;
+				if (returnType != void.class && returnType != int[].class
+						&& returnType != Integer[].class
+						&& returnType != Integer.class) {
+					throw new IllegalArgumentException("error return type:"
+							+ method.getDeclaringClass().getName() + "#"
+							+ method.getName() + "-->" + returnType);
+				}
+            } else {
+                this.batchUpdate = false;
+				if (returnType != void.class && returnType != Boolean.class
+						&& !Number.class.isAssignableFrom(returnType)) {
+					throw new IllegalArgumentException("error return type:"
+							+ method.getDeclaringClass().getName() + "#"
+							+ method.getName() + "-->" + returnType);
+				}
+            }
+        } else {
+            this.batchUpdate = false;
+        }
+    }
+
+    @Override
+    public StatementMetaData getMetaData() {
+        return metaData;
+    }
+
+    // TODO: 批量的处理!
+    @Override
+    public Object execute(Map<String, Object> parameters) {
+        if (batchUpdate) {
+            //
+            List<?> list = (List<?>) parameters.get(":1");
+            StatementRuntime[] runtimes = new StatementRuntime[list.size()];
+            for (int i = 0; i < list.size(); i++) {
+                Object arg = list.get(i);
+                HashMap<String, Object> clone = new HashMap<String, Object>(parameters);
+                // 更新执行参数
+                clone.put(":1", arg);
+                if (metaData.getSQLParamAt(0) != null) {
+                    clone.put(metaData.getSQLParamAt(0).value(), arg);
+                }
+                StatementRuntime runtime = new StatementRuntimeImpl(metaData, clone);
+                for (Interpreter interpreter : interpreters) {
+                    interpreter.interpret(runtime);
+                }
+                runtimes[i] = runtime;
+            }
+            return querier.execute(sqlType, runtimes);
+        } else {
+            StatementRuntime runtime = new StatementRuntimeImpl(metaData, parameters);
+            for (Interpreter interpreter : interpreters) {
+                interpreter.interpret(runtime);
+            }
+            return querier.execute(sqlType, runtime);
+        }
+        //        
+        //        // path、catalog、node
+        //        Class<?> daoClass = metaData.getClassMetaData().getDAOClass();
+        //        DAO dao = daoClass.getAnnotation(DAO.class);
+        //
+        //        // catalog
+        //        if (result.getClientInfo(RoutingConnection.CATALOG) == null) {
+        //            if (dao.catalog() != null && dao.catalog().length() > 0) {
+        //                result.setClientInfo(RoutingConnection.CATALOG, dao.catalog());
+        //            }
+        //        }
+        //
+        //        // node
+        //        if (result.getClientInfo(RoutingConnection.NODE) == null) {
+        //            UseMaster useMaster = daoMethod.getAnnotation(UseMaster.class);
+        //            if (useMaster != null) {
+        //                if (useMaster.value()) {
+        //                    result.setClientInfo(RoutingConnection.NODE, "master");
+        //                } else {
+        //                    result.setClientInfo(RoutingConnection.NODE, "slave");
+        //                }
+        //            }
+        //        }
+        //        //
+        //        return null;
+    }
+
+}

+ 29 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/Querier.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import net.paoding.rose.jade.annotation.SQLType;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+public interface Querier {
+
+    Object execute(SQLType sqlType, StatementRuntime... runtimes);
+
+}

+ 173 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/SelectQuerier.java

@@ -0,0 +1,173 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import net.paoding.rose.jade.annotation.SQLType;
+import net.paoding.rose.jade.dataaccess.DataAccess;
+import net.paoding.rose.jade.dataaccess.DataAccessFactory;
+
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * 实现 SELECT 查询。
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class SelectQuerier implements Querier {
+
+    private final RowMapper rowMapper;
+
+    private final Class<?> returnType;
+
+    private final DataAccessFactory dataAccessProvider;
+
+    public SelectQuerier(DataAccessFactory dataAccessProvider, StatementMetaData metaData,
+            RowMapper rowMapper) {
+        this.dataAccessProvider = dataAccessProvider;
+        this.returnType = metaData.getMethod().getReturnType();
+        this.rowMapper = rowMapper;
+    }
+
+    @Override
+    public Object execute(SQLType sqlType, StatementRuntime... runtimes) {
+        return execute(sqlType, (StatementRuntime) runtimes[0]);
+    }
+
+    public Object execute(SQLType sqlType, StatementRuntime runtime) {
+        DataAccess dataAccess = dataAccessProvider.getDataAccess(//
+                runtime.getMetaData(), runtime.getProperties());
+        // 执行查询
+        List<?> listResult = dataAccess.select(runtime.getSQL(), runtime.getArgs(), rowMapper);
+        final int sizeResult = listResult.size();
+
+        // 将 Result 转成方法的返回类型
+        if (returnType.isAssignableFrom(List.class)) {
+
+            // 返回  List 集合
+            return listResult;
+
+        } else if (returnType.isArray() && byte[].class != returnType) {
+            Object array = Array.newInstance(returnType.getComponentType(), sizeResult);
+            if (returnType.getComponentType().isPrimitive()) {
+                int len = listResult.size();
+                for (int i = 0; i < len; i++) {
+                    Array.set(array, i, listResult.get(i));
+                }
+            } else {
+                listResult.toArray((Object[]) array);
+            }
+            return array;
+
+        } else if (Map.class.isAssignableFrom(returnType)) {
+            // 将返回的  KeyValuePair 转换成  Map 对象
+            // 因为entry.key可能为null,所以使用HashMap
+            Map<Object, Object> map = null;
+            int initialCapacity = listResult.size() * 2;
+            if (returnType.isAssignableFrom(HashMap.class)) {
+
+                map = new HashMap<Object, Object>(initialCapacity);
+
+            } else if (returnType.isAssignableFrom(Hashtable.class)) {
+
+                map = new Hashtable<Object, Object>(initialCapacity);
+
+            } else if (! returnType.isInterface()) {
+                @SuppressWarnings("unchecked")
+                Class<Map<Object, Object>> mapType = (Class<Map<Object, Object>>)returnType;
+                try {
+                    Constructor<Map<Object, Object>> constructor = mapType.getConstructor(int.class);
+                    map = constructor.newInstance(initialCapacity);
+                } catch (Exception e) {
+                }
+
+                if (map == null) {
+                    try {
+                        map = mapType.newInstance();
+                    } catch (Exception e) {
+                    }
+                }
+            }
+
+            if (map == null)
+                throw new Error(returnType.toString());
+
+            if (listResult.size() == 1) {
+	            // return only one row
+	            Object obj = listResult.get(0);
+	            if (obj instanceof Map) {
+		            map.putAll((Map<?, ?>)obj);
+		            return map;
+                }
+            }
+
+            for (Object obj : listResult) {
+                if (obj == null) {
+                    continue;
+                }
+
+                Map.Entry<?, ?> entry = (Map.Entry<?, ?>) obj;
+
+                if (map.getClass() == Hashtable.class && entry.getKey() == null) {
+                    continue;
+                }
+
+                map.put(entry.getKey(), entry.getValue());
+            }
+
+            return map;
+
+        } else if (returnType.isAssignableFrom(HashSet.class)) {
+
+            // 返回  Set 集合
+            return new HashSet<Object>(listResult);
+
+        } else {
+
+            if (sizeResult == 1) {
+                // 返回单个  Bean、Boolean等类型对象
+                return listResult.get(0);
+
+            } else if (sizeResult == 0) {
+
+                // 基础类型的抛异常,其他的返回null
+                if (returnType.isPrimitive()) {
+                    String msg = "Incorrect result size: expected 1, actual " + sizeResult + ": "
+                            + runtime.getMetaData();
+                    throw new EmptyResultDataAccessException(msg, 1);
+                } else {
+                    return null;
+                }
+
+            } else {
+                // IncorrectResultSizeDataAccessException
+                String msg = "Incorrect result size: expected 0 or 1, actual " + sizeResult + ": "
+                        + runtime.getMetaData();
+                throw new IncorrectResultSizeDataAccessException(msg, 1, sizeResult);
+            }
+        }
+    }
+}

+ 121 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/SimpleInterpreter.java

@@ -0,0 +1,121 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+import org.apache.commons.lang.math.NumberUtils;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
+
+/**
+ * 
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+//NOTE: 这是一个最简单的解释器(没有表达式功能的解释器),实际并未启用
+public class SimpleInterpreter implements Interpreter {
+
+    private static final Pattern NAMED_PARAM_PATTERN = Pattern.compile("(\\:([a-zA-Z0-9_\\.]+))");
+
+    @Override
+    public void interpret(StatementRuntime runtime) {
+
+        final List<Object> parametersAsList = new LinkedList<Object>();
+
+        // 匹配符合  :name 格式的参数
+        Matcher matcher = NAMED_PARAM_PATTERN.matcher(runtime.getSQL());
+        if (!matcher.find()) {
+            return;
+        }
+
+        final StringBuilder builder = new StringBuilder();
+
+        int index = 0;
+
+        do {
+            // 提取参数名称
+            String name = matcher.group(1);
+            if (NumberUtils.isDigits(name)) {
+                name = matcher.group();//把冒号包含进去
+            }
+
+            Object value = null;
+
+            // 解析  a.b.c 类型的名称 
+            int find = name.indexOf('.');
+            if (find >= 0) {
+
+                // 用  BeanWrapper 获取属性值
+                Object bean = runtime.getParameters().get(name.substring(0, find));
+                if (bean != null) {
+                    BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
+                    value = beanWrapper.getPropertyValue(name.substring(find + 1));
+                }
+
+            } else {
+                // 直接获取值
+                value = runtime.getParameters().get(name);
+            }
+
+            // 拼装查询语句
+            builder.append(runtime.getSQL().substring(index, matcher.start()));
+
+            if (value instanceof Collection<?>) {
+
+                // 拼装 IN (...) 的查询条件
+                builder.append('(');
+
+                Collection<?> collection = (Collection<?>) value;
+
+                if (collection.isEmpty()) {
+                    builder.append("NULL");
+                } else {
+                    builder.append('?');
+                }
+
+                for (int i = 1; i < collection.size(); i++) {
+                    builder.append(", ?");
+                }
+
+                builder.append(')');
+
+                // 保存参数值
+                parametersAsList.addAll(collection);
+
+            } else {
+                // 拼装普通的查询条件
+                builder.append('?');
+
+                // 保存参数值
+                parametersAsList.add(value);
+            }
+
+            index = matcher.end();
+
+        } while (matcher.find());
+
+        // 拼装查询语句
+        builder.append(runtime.getSQL().substring(index));
+        runtime.setSQL(builder.toString());
+        runtime.setArgs(parametersAsList.toArray());
+    }
+
+}

+ 47 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/Statement.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.util.Map;
+
+/**
+ * {@link Statement} 表示一个符合规范的DAO方法,代表一个对数据库进行检索或更新的语句。
+ * <p>
+ * 
+ * 这是一个内部接口
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ */
+public interface Statement {
+
+    /**
+     * 所属的DAO方法及其相关的信息
+     * 
+     * @return
+     */
+    public StatementMetaData getMetaData();
+
+    /**
+     * 按照给定的方法参数值,执行一次数据库检索或更新
+     * <p>
+     * 您可以通过parameters.get(":1")、parameters.get(":2")获得方法中的第1、第2个参数(从1开始)<br>
+     * 如果DAO方法中的参数设置了<code>@SQLParam(name)</code>
+     * 注解,您还可以从parameters.get(name)取得该参数。
+     * 
+     * @return
+     */
+    public Object execute(Map<String, Object> parameters);
+}

+ 231 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementMetaData.java

@@ -0,0 +1,231 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import net.paoding.rose.jade.annotation.DAO;
+import net.paoding.rose.jade.annotation.SQL;
+import net.paoding.rose.jade.annotation.SQLParam;
+import net.paoding.rose.jade.annotation.SQLType;
+import net.paoding.rose.jade.annotation.ShardBy;
+
+import com.wzzx.rose.jade.shard.Table;
+
+/**
+ * {@link StatementMetaData} 封装、缓存了一个DAO方法的相关信息
+ * <p>
+ * 
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ */
+public class StatementMetaData {
+
+    /**
+     * 所属的DAO类的classMetaData
+     */
+    private final DAOMetaData daoMetaData;
+
+    /**
+     * 所在的DAO方法
+     */
+    private final Method method;
+
+    /**
+     * DAO方法上的原始SQL语句
+     */
+    private final String sql;
+
+    /**
+     * 方法返回参数的范型类型(不支持多级)-从method中获取并缓存
+     */
+    private final Class<?>[] genericReturnTypes;
+
+    /**
+     * {@link SQLParam} 注解数组-从method中获取并缓存
+     * <p>
+     * 此数组的长度为方法的参数个数,如果对应位置的方法参数没有注解 {@link SQLParam},该位置的元素值为null
+     */
+    private final SQLParam[] sqlParams;
+
+    /**
+     * <code>@{@link ShardBy}</code>标注在哪个参数上?(从0开始,负数代表无)-从method中获取并缓存
+     */
+    private final int shardByIndex;
+
+    private final int parameterCount;
+
+    // --------------------------------------------
+
+    public StatementMetaData(DAOMetaData daoMetaData, Method method) {
+        this.daoMetaData = daoMetaData;
+        this.method = method;
+        this.sql = method.getAnnotation(SQL.class).value();
+
+        this.genericReturnTypes = GenericUtils.getActualClass(method.getGenericReturnType());
+
+        Annotation[][] annotations = method.getParameterAnnotations();
+        this.parameterCount = annotations.length;
+        this.sqlParams = new SQLParam[annotations.length];
+        int shardByIndex = -1;
+        for (int index = 0; index < annotations.length; index++) {
+            for (Annotation annotation : annotations[index]) {
+                if (annotation instanceof ShardBy) {
+                    if (shardByIndex >= 0) {
+                        throw new IllegalArgumentException("duplicated @" + ShardBy.class.getName());
+                    }
+                    shardByIndex = index;
+                } else if (annotation instanceof SQLParam) {
+                    this.sqlParams[index] = (SQLParam) annotation;
+                }
+            }
+        }
+        this.shardByIndex = shardByIndex;
+    }
+
+    public DAOMetaData getDAOMetaData() {
+        return daoMetaData;
+    }
+
+    public Method getMethod() {
+        return method;
+    }
+
+    public String getSQL() {
+        return sql;
+    }
+
+    public int getParameterCount() {
+        return parameterCount;
+    }
+
+    public SQLParam getSQLParamAt(int argIndex) {
+        return sqlParams[argIndex];
+    }
+
+    public int getShardByIndex() {
+        return shardByIndex;
+    }
+
+    public Class<?>[] getGenericReturnTypes() {
+        return genericReturnTypes;
+    }
+
+    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+        return method.getAnnotation(annotationClass);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof StatementMetaData) {
+            StatementMetaData modifier = (StatementMetaData) obj;
+            return daoMetaData.equals(modifier.daoMetaData) && method.equals(modifier.method);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return daoMetaData.hashCode() ^ method.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return daoMetaData.getDAOClass().getName() + '#' + method.getName();
+    }
+
+    private static Pattern[] SELECT_PATTERNS = new Pattern[] {
+            //
+            Pattern.compile("^\\s*SELECT\\s+", Pattern.CASE_INSENSITIVE), //
+            Pattern.compile("^\\s*SHOW\\s+", Pattern.CASE_INSENSITIVE), //
+            Pattern.compile("^\\s*DESC\\s+", Pattern.CASE_INSENSITIVE), //
+            Pattern.compile("^\\s*DESCRIBE\\s+", Pattern.CASE_INSENSITIVE), //
+    };
+
+    private SQLType sqlType;
+
+    public SQLType getSQLType() {
+        if (sqlType == null) {
+            SQL sql = method.getAnnotation(SQL.class);
+            SQLType sqlType = sql.type();
+            if (sqlType == SQLType.AUTO_DETECT) {
+                for (int i = 0; i < SELECT_PATTERNS.length; i++) {
+                    // 用正则表达式匹配  SELECT 语句
+                    if (SELECT_PATTERNS[i].matcher(getSQL()).find()) {
+                        sqlType = SQLType.READ;
+                        break;
+                    }
+                }
+                if (sqlType == SQLType.AUTO_DETECT) {
+                    sqlType = SQLType.WRITE;
+                }
+            }
+            this.sqlType = sqlType;
+        }
+        return sqlType;
+    }
+
+    /**
+     * Copy Constructor
+     */
+    public StatementMetaData(StatementMetaData meta) {
+        this(meta, meta.sql);
+    }
+
+    public StatementMetaData(StatementMetaData meta, String sql) {
+        this.sql = sql;
+        daoMetaData = meta.daoMetaData;
+        method = meta.method;
+        genericReturnTypes = meta.genericReturnTypes;
+        parameterCount = meta.parameterCount;
+        sqlParams = meta.sqlParams;
+        shardByIndex = meta.shardByIndex;
+    }
+
+    /**
+     * 支持父接口
+     */
+    public String getCatalog() {
+        // DAO dao = method.getDeclaringClass().getAnnotation(DAO.class);
+        DAO dao = daoMetaData.getDAOClass().getAnnotation(DAO.class);
+        return dao == null ? "" : dao.catalog();
+    }
+
+    public Object getShardByParam(Map<String, Object> params) {
+        int index = getShardByIndex() + 1;
+        if (index <= 0)
+            index = 1;	// 默认为第一个参数
+        return index>0 && params != null ? params.get(":" + index) : null;
+    }
+
+    /**
+     * 配置Table的catelog
+     */
+    public Map<String, ?> getConstants() {
+        if (constants == null) {
+            constants = daoMetaData.getConstants();
+            for (Object value : constants.values()) {
+                if (value instanceof Table)
+                    ((Table) value).setCatelog(getCatalog());
+            }
+        }
+        return constants;
+    }
+    protected Map<String, ?> constants;
+}

+ 45 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementRuntime.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.util.Map;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+public interface StatementRuntime {
+
+    StatementMetaData getMetaData();
+
+    String getSQL();
+
+    // 一个Statement完全可以被解出多个runtime,他们的sql不同,同时一个对应的args可能有几个(即批量更新)
+    Object[] getArgs();
+
+    Map<String, Object> getParameters();
+
+    void setSQL(String sql);
+
+    void setArgs(Object[] args);
+
+    Map<String, Object> getProperties();
+
+    void setProperty(String name, Object value);
+
+    <T> T getProperty(String name);
+}

+ 97 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementRuntimeImpl.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 
+ * @author qieqie
+ * 
+ */
+public class StatementRuntimeImpl implements StatementRuntime {
+
+    private final StatementMetaData metaData;
+
+    private final Map<String, Object> parameters;
+
+    private String sql;
+
+    private Object[] args;
+
+    private Map<String, Object> properties;
+
+    public StatementRuntimeImpl(StatementMetaData metaData, Map<String, Object> parameters) {
+        this.metaData = metaData;
+        this.parameters = parameters;
+        this.sql = metaData.getSQL();
+    }
+
+    @Override
+    public StatementMetaData getMetaData() {
+        return metaData;
+    }
+
+    @Override
+    public Map<String, Object> getParameters() {
+        return this.parameters;
+    }
+
+    @Override
+    public void setSQL(String sql) {
+        this.sql = sql;
+    }
+
+    @Override
+    public String getSQL() {
+        return sql;
+    }
+
+    @Override
+    public Object[] getArgs() {
+        return args;
+    }
+
+    @Override
+    public void setArgs(Object[] args) {
+        this.args = args;
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        if (properties == null) {
+            return Collections.emptyMap();
+        }
+        return Collections.unmodifiableMap(this.properties);
+    }
+
+    @Override
+    public void setProperty(String name, Object value) {
+        if (properties == null) {
+            properties = new HashMap<String, Object>();
+        }
+        this.properties.put(name, value);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T getProperty(String name) {
+        return (T) (properties == null ? null : properties.get(name));
+    }
+
+}

+ 36 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementWrapper.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+/**
+ * 
+ * @author qieqie
+ *
+ */
+public interface StatementWrapper extends Statement {
+
+    /**
+     * 
+     * @param statement
+     */
+    void setStatement(Statement statement);
+
+    /**
+     * 
+     * @return
+     */
+    Statement getStatement();
+}

+ 28 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/StatementWrapperProvider.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+/**
+ * 
+ * @author qieqie
+ *
+ */
+public interface StatementWrapperProvider {
+
+    Statement wrap(Statement statement);
+
+    StatementMetaData wrap(StatementMetaData smd);
+}

+ 93 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/SystemInterpreter.java

@@ -0,0 +1,93 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.sql.SQLSyntaxErrorException;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.paoding.rose.jade.statement.expression.ExqlPattern;
+import net.paoding.rose.jade.statement.expression.impl.ExqlContextImpl;
+import net.paoding.rose.jade.statement.expression.impl.ExqlPatternImpl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.jdbc.BadSqlGrammarException;
+
+import com.wzzx.rose.jade.shard.Table;
+
+/**
+ * 
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class SystemInterpreter implements Interpreter {
+
+	private static final Log logger = LogFactory.getLog(SystemInterpreter.class);
+	
+    @Override
+    public synchronized void interpret(StatementRuntime runtime) {
+        // 转换语句中的表达式
+        ExqlPattern pattern = ExqlPatternImpl.compile(runtime.getSQL());
+        ExqlContextImpl context = new ExqlContextImpl(runtime.getSQL().length() + 32);
+        try {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> constants = (Map<String, Object>) runtime.getMetaData().getConstants();
+            Map<String, Object> params = runtime.getParameters();
+            for (Object obj : params.values()) {
+                if (obj instanceof Enum) {
+                    if (! (constants instanceof HashMap))
+                        constants = new HashMap<String, Object>(constants);
+                    String name = "Enum" + obj.getClass().getSimpleName();
+                    constants.put(name, ((Enum<?>) obj).name());
+                }
+            }
+
+            Table.setShardByParam(runtime.getMetaData().getShardByParam(params));
+
+            pattern.execute(context, params, constants);
+            runtime.setArgs(context.getParams());
+            String sql = context.flushOut();
+            runtime.setSQL(sql);
+            if(logger.isDebugEnabled()){
+            	logger.debug("interpreted sql : "+sql+" params: " +params);
+            }
+        } catch (Exception e) {
+            String daoInfo = runtime.getMetaData().toString();
+            throw new BadSqlGrammarException(daoInfo, runtime.getSQL(),
+                    new SQLSyntaxErrorException(daoInfo + " @SQL('" + runtime.getSQL() + "')", e));
+        } finally {
+            Table.clearShardByParam();
+        }
+
+    }
+
+    public static void main(String[] args) throws Exception {
+        // 转换语句中的表达式
+        String sql = "insert ignore into table_name "
+                + "(`id`,`uid`,`favable_id`,`addtime`,`ranking`) "//
+                + "values (:1.id,:1.uid,:1.a,:1.b)";
+        ExqlPattern pattern = ExqlPatternImpl.compile(sql);
+        ExqlContextImpl context = new ExqlContextImpl(sql.length() + 32);
+
+        Map<String, Object> parametersAsMap = new HashMap<String, Object>();
+        parametersAsMap.put(":1", "p1");
+        parametersAsMap.put(":2", "p2");
+
+        String result = pattern.execute(context, parametersAsMap);
+        System.out.println(result+",params:"+parametersAsMap);
+    }
+
+}

+ 210 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/UpdateQuerier.java

@@ -0,0 +1,210 @@
+/*
+ * Copyright 2009-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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement;
+
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import net.paoding.rose.jade.annotation.ReturnGeneratedKeys;
+import net.paoding.rose.jade.annotation.SQLType;
+import net.paoding.rose.jade.dataaccess.DataAccess;
+import net.paoding.rose.jade.dataaccess.DataAccessFactory;
+
+import org.apache.commons.lang.ClassUtils;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.jdbc.support.KeyHolder;
+
+/**
+ * 
+ * @author 王志亮 [qieqie.wang@gmail.com]
+ * @author 廖涵 [in355hz@gmail.com]
+ */
+public class UpdateQuerier implements Querier {
+
+    private final DataAccessFactory dataAccessProvider;
+
+    private final Class<?> returnType;
+
+    private final boolean returnGeneratedKeys;
+
+    public UpdateQuerier(DataAccessFactory dataAccessProvider, StatementMetaData metaData) {
+        this.dataAccessProvider = dataAccessProvider;
+        Method method = metaData.getMethod();
+        // 转换基本类型
+        Class<?> returnType = method.getReturnType();
+        if (returnType.isPrimitive()) {
+            returnType = ClassUtils.primitiveToWrapper(returnType);
+        }
+        this.returnType = returnType;
+        if (returnType != void.class && (method
+                        .isAnnotationPresent(ReturnGeneratedKeys.class))) {
+            returnGeneratedKeys = true;
+        } else {
+            returnGeneratedKeys = false;
+        }
+    }
+
+    @Override
+    public Object execute(SQLType sqlType, StatementRuntime... runtimes) {
+        return runtimes.length > 1 ? executeBatch(runtimes)
+                : executeSingle(runtimes[0], returnType);
+    }
+
+    private Object executeSingle(StatementRuntime runtime, Class<?> returnType) {
+        Number result = null;
+        DataAccess dataAccess = dataAccessProvider.getDataAccess(//
+                runtime.getMetaData(), runtime.getProperties());
+        if (returnGeneratedKeys) {
+            String databaseProductName = null;
+            DataSource dataSource = dataAccess.getDataSource();
+            Connection con = DataSourceUtils.getConnection(dataSource);
+            try {
+				databaseProductName = con.getMetaData().getDatabaseProductName();
+			} catch (SQLException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+				DataSourceUtils.releaseConnection(con, dataSource);
+				con = null;
+			}
+            finally {
+    			DataSourceUtils.releaseConnection(con, dataSource);
+    			con = null;
+    		}
+            //oracle
+            if(StringUtils.isNotBlank(databaseProductName) && StringUtils.equalsIgnoreCase("oracle", databaseProductName)){
+            	KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
+            	dataAccess.oracleUpdate(runtime.getSQL(), runtime.getArgs(), generatedKeyHolder);
+            	result = generatedKeyHolder.getKey();
+            }else{
+            	ArrayList<Map<String, Object>> keys = new ArrayList<Map<String, Object>>(1);
+            	KeyHolder generatedKeyHolder = new GeneratedKeyHolder(keys);
+            	dataAccess.update(runtime.getSQL(), runtime.getArgs(), generatedKeyHolder);
+            	if (keys.size() > 0) {
+            		result = generatedKeyHolder.getKey();
+            	}else{
+            		result = null;
+            	}
+            }
+        } else {
+            result = new Integer(dataAccess.update(runtime.getSQL(), runtime.getArgs(), null));
+        }
+        //
+        if (result == null || returnType == void.class) {
+            return null;
+        }
+        if (returnType == result.getClass()) {
+            return result;
+        }
+
+        // 将结果转成方法的返回类型
+        if (returnType == Integer.class) {
+            return result.intValue();
+        } else if (returnType == Long.class) {
+            return result.longValue();
+        } else if (returnType == Boolean.class) {
+            return result.intValue() > 0 ? Boolean.TRUE : Boolean.FALSE;
+        } else if (returnType == Double.class) {
+            return result.doubleValue();
+        } else if (returnType == Float.class) {
+            return result.floatValue();
+        } else if (Number.class.isAssignableFrom(returnType)) {
+            return result;
+        } else {
+            throw new DataRetrievalFailureException(
+                    "The generated key is not of a supported numeric type. " + "Unable to cast ["
+                            + result.getClass().getName() + "] to [" + Number.class.getName() + "]");
+        }
+    }
+
+    private Object executeBatch(StatementRuntime... runtimes) {
+        int[] updatedArray = new int[runtimes.length];
+        for (int i = 0; i < updatedArray.length; i++) {
+            StatementRuntime runtime = runtimes[i];
+            updatedArray[i] = (Integer) executeSingle(runtime, Integer.class);
+        }
+        return updatedArray;
+        //        Map<String, Object> parameters = runtime.getParameters();
+        //        List<?> list = (List<?>) parameters.get(":1");
+        //
+        //        int[] updatedArray;
+        //
+        //        if (true) {
+        //            List<Map<String, Object>> parametersList = new ArrayList<Map<String, Object>>(
+        //                    list.size());
+        //            for (Object arg : list) {
+        //
+        //                HashMap<String, Object> clone = new HashMap<String, Object>(parameters);
+        //
+        //                // 更新执行参数
+        //                clone.put(":1", arg);
+        //                if (runtime.getMetaData().getSQLParamAt(0) != null) {
+        //                    clone.put(runtime.getMetaData().getSQLParamAt(0).value(), arg);
+        //                }
+        //                parametersList.add(clone);
+        //            }
+        //            updatedArray = dataAccess.batchUpdate(runtime.getSQL(), parametersList);
+        //        } else {
+        //            // 批量执行查询
+        //            int index = 0;
+        //            updatedArray = new int[list.size()];
+        //            for (Object arg : list) {
+        //
+        //                HashMap<String, Object> clone = new HashMap<String, Object>(parameters);
+        //
+        //                // 更新执行参数
+        //                clone.put(":1", arg);
+        //                if (this.metaData.getSQLParamAt(0) != null) {
+        //                    clone.put(this.metaData.getSQLParamAt(0).value(), arg);
+        //                }
+        //                updatedArray[index] = (Integer) executeSignle(dataAccess, clone, int.class);
+        //
+        //                index++;
+        //            }
+        //        }
+        //        Class<?> batchReturnClazz = metaData.getMethod().getReturnType();
+        //        if (batchReturnClazz == int[].class) {
+        //            return updatedArray;
+        //        }
+        //        if (batchReturnClazz == Integer[].class) {
+        //            Integer[] ret = new Integer[updatedArray.length];
+        //            for (int i = 0; i < ret.length; i++) {
+        //                ret[i] = updatedArray[i];
+        //            }
+        //            return updatedArray;
+        //        }
+        //        if (batchReturnClazz == void.class) {
+        //            return null;
+        //        }
+        //        if (batchReturnClazz == int.class || batchReturnClazz == Integer.class) {
+        //            int updated = 0;
+        //            for (int i = 0; i < updatedArray.length; i++) {
+        //                updated += updatedArray[i];
+        //            }
+        //            return updated;
+        //        }
+        //
+        //        return null;
+    }
+
+}

+ 45 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/CacheInterface.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement.cached;
+
+/**
+ * {@link CacheInterface} 抽象DAO方法所使用的缓存接口
+ * 
+ * @author han.liao [in355hz@gmail.com]
+ */
+public interface CacheInterface {
+
+    /**
+     * 从缓存取出给定key对应的对象,如果没有则返回null
+     * 
+     */
+    Object get(String key);
+
+    /**
+     * 将某个对象和给定的key绑定起来存储在缓存中
+     * 
+     * @param expiryInSecond - 缓存过期时间,单位为秒
+     * 
+     */
+    boolean set(String key, Object value, int expiryInSecond);
+
+    /**
+     * 从 Cache 缓存池删除对象。
+     * 
+     * @param key - 缓存关键字
+     */
+    boolean delete(String key);
+}

+ 35 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/CacheProvider.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement.cached;
+
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+/**
+ * 定义 CacheProvider 接口从缓存池名称获取实例。
+ * 
+ * @author han.liao
+ */
+public interface CacheProvider {
+
+    /**
+     * 从缓存池的名称获取实例。
+     * 
+     * @param poolName - 缓存池的名称
+     * 
+     * @return 缓存池实例
+     */
+    CacheInterface getCacheByPool(StatementMetaData metaData, String poolName);
+}

+ 180 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/CachedStatement.java

@@ -0,0 +1,180 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement.cached;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import net.paoding.rose.jade.annotation.Cache;
+import net.paoding.rose.jade.annotation.CacheDelete;
+import net.paoding.rose.jade.annotation.SQLType;
+import net.paoding.rose.jade.statement.Statement;
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeanWrapperImpl;
+
+/**
+ * {@link CachedStatement} 封装了支持Cache的逻辑
+ * 
+ * @author qieqie.wang
+ * 
+ */
+public class CachedStatement implements Statement {
+
+    /**
+     * 实际底层Statemenet,比如就是数据库操作的实际执行语句
+     */
+    private final Statement realStatement;
+
+    /**
+     * 注解在DAO方法上的 {@link Cache},如果没有则为null
+     * <p>
+     * 仅在该语句为查询语句时侯此有效,即如果某个更新语句的DAO方法上也注解了 {@link Cache} 在这里也仍保留null
+     */
+    private final Cache cacheAnnotation;
+
+    /**
+     * 注解在该语句上的 {@link CacheDelete},如果没有则为null
+     * <p>
+     * 
+     * 可使用在各种语句上,甚至事查询语句上(很悲剧的:我也不知道为何有这种用法,执行一次查询还会附带删除神马Cache的啊?真的有吗?)
+     */
+    private final CacheDelete cacheDeleteAnnotation;
+
+    /**
+     * cache服务接口
+     */
+    private final CacheProvider cacheProvider;
+
+    /**
+     * 
+     * @param cacheProvider
+     * @param realStatement
+     */
+    public CachedStatement(CacheProvider cacheProvider, Statement realStatement) {
+        this.realStatement = realStatement;
+        this.cacheProvider = cacheProvider;
+        StatementMetaData metaData = realStatement.getMetaData();
+        SQLType sqlType = metaData.getSQLType();
+        cacheDeleteAnnotation = metaData.getMethod().getAnnotation(CacheDelete.class);
+        Cache cacheAnnotation = metaData.getMethod().getAnnotation(Cache.class);
+        if (sqlType == SQLType.READ) {
+            this.cacheAnnotation = cacheAnnotation;
+        } else {
+            this.cacheAnnotation = null;
+            if (cacheAnnotation != null) {
+                Log logger = LogFactory.getLog(CachedStatement.class);
+                logger.warn("@" + Cache.class.getName() + " is invalid for a " //
+                        + sqlType + " SQL:" + metaData.getSQL());
+            }
+        }
+    }
+
+    @Override
+    public StatementMetaData getMetaData() {
+        return realStatement.getMetaData();
+    }
+
+    @Override
+    public Object execute(Map<String, Object> parameters) {
+        Object value = null;
+        if (cacheAnnotation == null) {
+            value = realStatement.execute(parameters);
+        } else {
+            CacheInterface cache = cacheProvider.getCacheByPool(//
+                    getMetaData(), cacheAnnotation.pool());
+            String cacheKey = buildKey(cacheAnnotation.key(), parameters);
+            value = cache.get(cacheKey);
+            if (value == null) {
+                value = realStatement.execute(parameters);
+                cache.set(cacheKey, value, cacheAnnotation.expiry());
+            }
+        }
+        if (cacheDeleteAnnotation != null) {
+            CacheInterface cache = cacheProvider.getCacheByPool(//
+                    getMetaData(), cacheDeleteAnnotation.pool());
+            for (String key : cacheDeleteAnnotation.key()) {
+                String cacheKey = buildKey(key, parameters);
+                cache.delete(cacheKey);
+            }
+        }
+        return value;
+    }
+
+    // 参数的模板
+    private static final Pattern PATTERN = Pattern.compile("\\:([a-zA-Z0-9_\\.]*)");
+
+    /**
+     * 查找模板 KEY 中所有的 :name, :name.property 参数替换成实际值。
+     * 
+     * @param key - 作为模板的 KEY
+     * @param parameters - 传入的参数
+     * 
+     * @return 最终的缓存 KEY
+     * @author 廖涵 in355hz@gmail.com
+     */
+    private static String buildKey(String key, Map<String, Object> parameters) {
+        // 匹配符合  :name 格式的参数
+        Matcher matcher = PATTERN.matcher(key);
+        if (matcher.find()) {
+
+            StringBuilder builder = new StringBuilder();
+
+            int index = 0;
+
+            do {
+                // 提取参数名称
+                final String name = matcher.group(1).trim();
+
+                Object value = null;
+
+                // 解析  a.b.c 类型的名称 
+                int find = name.indexOf('.');
+                if (find >= 0) {
+
+                    // 用  BeanWrapper 获取属性值
+                    Object bean = parameters.get(name.substring(0, find));
+                    if (bean != null) {
+                        value = new BeanWrapperImpl(bean)
+                                .getPropertyValue(name.substring(find + 1));
+                    }
+
+                } else {
+
+                    // 获取参数值
+                    value = parameters.get(name);
+                }
+
+                // 拼装参数值
+                builder.append(key.substring(index, matcher.start()));
+                builder.append(value);
+
+                index = matcher.end();
+
+            } while (matcher.find());
+
+            // 拼装最后一段
+            builder.append(key.substring(index));
+
+            return builder.toString();
+        }
+
+        return key;
+    }
+}

+ 92 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/MockCache.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement.cached;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * 提供 ConcurrentHashMap 缓存池的 {@link CacheInterface} 实现。
+ * 
+ * @author han.liao
+ */
+public class MockCache implements CacheInterface {
+
+    private static Log logger = LogFactory.getLog(MockCache.class);
+
+    private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
+
+    private String poolName; // 缓存名称
+
+    private int maxSize = 100; // 默认值
+
+    public MockCache(String poolName) {
+        this.poolName = poolName;
+    }
+
+    public MockCache(String poolName, int maxSize) {
+        this.poolName = poolName;
+        this.maxSize = maxSize;
+    }
+
+    public int getMaxSize() {
+        return maxSize;
+    }
+
+    public void setMaxSize(int maxSize) {
+        this.maxSize = maxSize;
+    }
+
+    @Override
+    public Object get(String key) {
+
+        Object value = map.get(key);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Get cache \'" + key + "\' from pool \'" + poolName + "\': " + value);
+        }
+
+        return value;
+    }
+
+    @Override
+    public boolean set(String key, Object value, int expiry) {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Set cache \'" + key + "\' to pool \'" + poolName + "\': " + value);
+        }
+
+        if (map.size() >= maxSize) {
+            map.remove(map.keys().nextElement());
+        }
+
+        map.put(key, value);
+        return true;
+    }
+
+    @Override
+    public boolean delete(String key) {
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Remove cache \'" + key + "\' from pool \'" + poolName + "\'.");
+        }
+
+        map.remove(key);
+        return true;
+    }
+}

+ 45 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/cached/MockCacheProvider.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2009-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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 i distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.paoding.rose.jade.statement.cached;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.paoding.rose.jade.statement.StatementMetaData;
+
+/**
+ * 提供 ConcurrentHashMap 缓存池的 {@link CacheProvider} 实现。
+ * 
+ * @author han.liao
+ */
+public class MockCacheProvider implements CacheProvider {
+
+    private ConcurrentHashMap<String, MockCache> caches = new ConcurrentHashMap<String, MockCache>();
+
+    @Override
+    public CacheInterface getCacheByPool(StatementMetaData metaData, String poolName) {
+        MockCache cache = caches.get(poolName);
+        if (cache == null) {
+            cache = new MockCache(poolName);
+
+            MockCache cacheExist = caches.putIfAbsent(poolName, cache);
+            if (cacheExist != null) {
+                cache = cacheExist;
+            }
+        }
+
+        return cache;
+    }
+}

+ 38 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/expression/ExprResolver.java

@@ -0,0 +1,38 @@
+package net.paoding.rose.jade.statement.expression;
+
+/**
+ * 定义处理表达式的接口, 以兼容不同的表达式语法。
+ * 
+ * @author han.liao
+ */
+public interface ExprResolver {
+
+    /**
+     * 返回变量的内容。
+     * 
+     * @param variant - 变量的名称
+     * 
+     * @return 变量的内容
+     */
+    Object getVar(String variant);
+
+    /**
+     * 设置变量的内容。
+     * 
+     * @param variant - 变量的名称
+     * 
+     * @param value - 变量的内容
+     */
+    void setVar(String variant, Object value);
+
+    /**
+     * 返回在语句输出的内容。
+     * 
+     * @param expression - 解释的表达式
+     * 
+     * @return 输出的内容
+     * 
+     * @throws Exception
+     */
+    Object executeExpr(String expression) throws Exception;
+}

+ 44 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/expression/ExqlContext.java

@@ -0,0 +1,44 @@
+package net.paoding.rose.jade.statement.expression;
+
+/**
+ * 定义输出的上下文接口。
+ * 
+ * @author han.liao
+ */
+public interface ExqlContext {
+
+    /**
+     * 输出字符到语句内容。
+     * 
+     * @param ch - 输出的字符
+     */
+    void fillChar(char ch);
+
+    /**
+     * 输出字符串到语句内容, 字符串的内容是未经转义的。
+     * 
+     * @param string - 输出的字符串
+     */
+    void fillText(String string);
+
+    /**
+     * 输出对象到语句内容, 字符串的内容将被转义。
+     * 
+     * @param obj - 输出的对象
+     */
+    void fillValue(Object obj);
+
+    /**
+     * 返回所有参数的数组, 按参数出现的顺序。
+     * 
+     * @return 所有参数的数组
+     */
+    Object[] getParams();
+
+    /**
+     * 得到输出的语句内容。
+     * 
+     * @return 语句内容
+     */
+    String flushOut();
+}

+ 0 - 0
rose-jade/src/main/java/net/paoding/rose/jade/statement/expression/ExqlPattern.java


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov