From 15768b3a441753b91e989f653a1139e9ef17c6b1 Mon Sep 17 00:00:00 2001 From: "J. Brisbin" Date: Mon, 7 Mar 2011 15:35:30 -0600 Subject: [PATCH] First stab at mapping --- spring-data-document-core/pom.xml | 256 +++--- spring-data-document-core/template.mf | 8 +- spring-data-document-parent/pom.xml | 787 +++++++++--------- spring-data-mongodb/pom.xml | 238 +++--- .../mongodb/MongoPropertyDescriptors.java | 371 ++++----- .../data/document/mongodb/MongoReader.java | 24 +- .../data/document/mongodb/MongoTemplate.java | 6 +- .../mongodb/SimpleMongoConverter.java | 557 ------------- .../config/MongoMappingConverterParser.java | 53 ++ .../SimpleMongoRepositoryConfiguration.java | 285 ++++--- .../convert/MappingMongoConverter.java | 487 +++++++++++ .../{ => convert}/MongoBeanWrapper.java | 3 +- .../mongodb/{ => convert}/MongoConverter.java | 40 +- .../mongodb/convert/SimpleMongoConverter.java | 546 ++++++++++++ .../convert/SimplePojoDBObjectConverter.java | 177 ++++ .../document/mongodb/index/CompoundIndex.java | 43 + .../mongodb/index/CompoundIndexes.java | 33 + .../mongodb/index/IndexDefinition.java | 28 + .../mongodb/index/IndexDirection.java | 25 + .../mongodb/index/IndexPredicate.java | 51 ++ .../data/document/mongodb/index/Indexed.java | 41 + .../mongodb/mapping/MappingException.java | 35 + .../mongodb/mapping/MappingIntrospector.java | 417 ++++++++++ .../mongodb/mapping/MongoClassMapping.java | 33 + .../mongodb/mapping/MongoCollection.java | 37 + .../MongoMappingConfigurationStrategy.java | 457 ++++++++++ .../mongodb/mapping/MongoMappingContext.java | 59 ++ .../mongodb/mapping/MongoMappingFactory.java | 43 + .../mapping/MongoPersistentEntity.java | 43 + .../mongodb/mapping/MongoProperty.java | 38 + .../mapping/index/MongoIndexDefinition.java | 116 +++ .../repository/SimpleMongoRepository.java | 2 +- .../repository/StringBasedMongoQuery.java | 2 +- .../data/mapping/annotation/Aliases.java | 26 + .../data/mapping/annotation/Id.java | 14 + .../data/mapping/annotation/IdentifiedBy.java | 12 + .../data/mapping/annotation/ManyToMany.java | 17 + .../data/mapping/annotation/OneToMany.java | 17 + .../data/mapping/annotation/OneToOne.java | 14 + .../annotation/PersistenceConstructor.java | 30 + .../annotation/PersistenceStrategy.java | 11 + .../data/mapping/annotation/Persistent.java | 37 + .../data/mapping/annotation/Transient.java | 14 + .../data/mapping/annotation/Value.java | 37 + .../mapping/model/AbstractClassMapping.java | 42 + .../mapping/model/AbstractMappingContext.java | 156 ++++ .../model/AbstractPersistentEntity.java | 193 +++++ .../model/AbstractPersistentProperty.java | 77 ++ .../data/mapping/model/ClassMapping.java | 31 + .../DatastoreConfigurationException.java | 18 + .../model/EntityInstantiationException.java | 30 + .../data/mapping/model/IdentityMapping.java | 31 + .../model/IllegalMappingException.java | 28 + .../model/MappingConfigurationStrategy.java | 70 ++ .../data/mapping/model/MappingContext.java | 169 ++++ .../data/mapping/model/MappingFactory.java | 278 +++++++ .../data/mapping/model/PersistentEntity.java | 167 ++++ .../mapping/model/PersistentProperty.java | 51 ++ .../data/mapping/model/PropertyMapping.java | 25 + .../data/mapping/model/types/Association.java | 146 ++++ .../data/mapping/model/types/Basic.java | 65 ++ .../types/BasicTypeConverterRegistrar.java | 133 +++ .../data/mapping/model/types/Embedded.java | 43 + .../data/mapping/model/types/Identity.java | 36 + .../data/mapping/model/types/ManyToMany.java | 36 + .../data/mapping/model/types/ManyToOne.java | 37 + .../data/mapping/model/types/OneToMany.java | 36 + .../data/mapping/model/types/OneToOne.java | 36 + .../data/mapping/model/types/Simple.java | 37 + .../data/mapping/model/types/ToOne.java | 47 ++ .../mapping/reflect/ClassPropertyFetcher.java | 372 +++++++++ .../data/mapping/reflect/ReflectionUtils.java | 209 +++++ .../mongodb/MongoOperationsUnitTests.java | 1 + .../document/mongodb/MongoTemplateTests.java | 1 + .../mongodb/MongoTemplateUnitTests.java | 1 + .../mongodb/SimpleMongoConverterTests.java | 1 + .../document/mongodb/mapping/Account.java | 39 + .../document/mongodb/mapping/Address.java | 71 ++ .../mongodb/mapping/MappingTests.java | 95 +++ .../data/document/mongodb/mapping/Person.java | 121 +++ .../MongoQueryCreatorUnitTests.java | 2 +- .../StringBasedMongoQueryUnitTests.java | 4 +- .../src/test/resources/infrastructure.xml | 26 +- .../src/test/resources/mapping.xml | 29 + spring-data-mongodb/template.mf | 8 +- 85 files changed, 6997 insertions(+), 1571 deletions(-) delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/SimpleMongoConverter.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/config/MongoMappingConverterParser.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java rename spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/{ => convert}/MongoBeanWrapper.java (95%) rename spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/{ => convert}/MongoConverter.java (50%) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimpleMongoConverter.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimplePojoDBObjectConverter.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/CompoundIndex.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/CompoundIndexes.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexDefinition.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexDirection.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexPredicate.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/Indexed.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingException.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingIntrospector.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoClassMapping.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoCollection.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationStrategy.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingContext.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingFactory.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntity.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoProperty.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/index/MongoIndexDefinition.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Aliases.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Id.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/IdentifiedBy.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/ManyToMany.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/OneToMany.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/OneToOne.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/PersistenceConstructor.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/PersistenceStrategy.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Persistent.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Transient.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Value.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractClassMapping.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractMappingContext.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentEntity.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/ClassMapping.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/DatastoreConfigurationException.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiationException.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IdentityMapping.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IllegalMappingException.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingConfigurationStrategy.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingContext.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingFactory.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PropertyMapping.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Association.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Basic.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/BasicTypeConverterRegistrar.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Embedded.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Identity.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ManyToMany.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ManyToOne.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/OneToMany.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/OneToOne.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Simple.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ToOne.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/reflect/ClassPropertyFetcher.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/reflect/ReflectionUtils.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Account.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Address.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Person.java create mode 100644 spring-data-mongodb/src/test/resources/mapping.xml diff --git a/spring-data-document-core/pom.xml b/spring-data-document-core/pom.xml index ac325d868..a07b9b599 100644 --- a/spring-data-document-core/pom.xml +++ b/spring-data-document-core/pom.xml @@ -1,132 +1,136 @@ - 4.0.0 - - org.springframework.data - spring-data-document-parent - 1.0.0.BUILD-SNAPSHOT - ../spring-data-document-parent/pom.xml - - spring-data-document-core - jar - Spring Data Document Support - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + + org.springframework.data + spring-data-document-parent + 1.0.0.BUILD-SNAPSHOT + ../spring-data-document-parent/pom.xml + + spring-data-document-core + jar + Spring Data Document Support + - - - org.springframework - spring-beans - - - org.springframework - spring-tx - + + + org.springframework + spring-beans + + + org.springframework + spring-tx + + + org.springframework + spring-web + - - - org.slf4j - slf4j-api - - - org.slf4j - jcl-over-slf4j - compile - - - org.slf4j - slf4j-log4j12 - runtime - - - log4j - log4j - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - - runtime - - - - javax.annotation - jsr250-api - true - + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + compile + + + org.slf4j + slf4j-log4j12 + runtime + + + log4j + log4j + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + - - org.mockito - mockito-all - test - + + javax.annotation + jsr250-api + true + - - junit - junit - - - - - - javax.servlet - servlet-api - 2.5 - provided - - - - org.springframework - spring-core - ${org.springframework.version} - - - commons-logging - commons-logging - - - - - org.springframework - spring-web - ${org.springframework.version} - - - commons-logging - commons-logging - - - - - org.springframework - spring-webmvc - ${org.springframework.version} - - - commons-logging - commons-logging - - - - - - - - com.springsource.bundlor - com.springsource.bundlor.maven - - - + + org.mockito + mockito-all + test + + + + junit + junit + + + + + + javax.servlet + servlet-api + 2.5 + provided + + + + org.springframework + spring-core + ${org.springframework.version} + + + commons-logging + commons-logging + + + + + org.springframework + spring-web + ${org.springframework.version} + + + commons-logging + commons-logging + + + + + org.springframework + spring-webmvc + ${org.springframework.version} + + + commons-logging + commons-logging + + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + diff --git a/spring-data-document-core/template.mf b/spring-data-document-core/template.mf index f8e76f43e..46fa79d44 100644 --- a/spring-data-document-core/template.mf +++ b/spring-data-document-core/template.mf @@ -24,9 +24,15 @@ Import-Template: org.springframework.core.*;version="[3.0.0, 4.0.0)", org.springframework.dao.*;version="[3.0.0, 4.0.0)", org.springframework.util.*;version="[3.0.0, 4.0.0)", + org.springframework.expression.*;version="[3.0.0, 4.0.0)", + org.springframework.expression.common.*;version="[3.0.0, 4.0.0)", + org.springframework.expression.spel.standard.*;version="[3.0.0, 4.0.0)", + org.springframework.expression.spel.support.*;version="[3.0.0, 4.0.0)", + org.springframework.validation.*;version="[3.0.0, 4.0.0)", org.springframework.data.core.*;version="[1.0.0, 2.0.0)", org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", - org.w3c.dom.*;version="0" + org.w3c.dom.*;version="0", + javax.persistence.*;version="[1.0, 2.0)" diff --git a/spring-data-document-parent/pom.xml b/spring-data-document-parent/pom.xml index 077bf9455..3b93fde57 100644 --- a/spring-data-document-parent/pom.xml +++ b/spring-data-document-parent/pom.xml @@ -1,400 +1,407 @@ - - 4.0.0 - org.springframework.data - spring-data-document-parent - Spring Data Document Parent - http://www.springsource.org/spring-data/data-document - 1.0.0.BUILD-SNAPSHOT - pom - - UTF-8 - - 4.8.1 - 1.2.15 - 1.8.4 - 1.5.10 - 3.0.5.RELEASE - 1.0.0.BUILD-SNAPSHOT - 1.6.11.M2 - - - - strict - - false - - - - fast - - true - true - - - - staging - - - spring-site-staging - file:///${java.io.tmpdir}/spring-data/data-document/docs - - - spring-milestone-staging - file:///${java.io.tmpdir}/spring-data/data-document/milestone - - - spring-snapshot-staging - file:///${java.io.tmpdir}/spring-data/data-document/snapshot - - - - - bootstrap - - - - - - http://www.springsource.com/download/community - - - static.springframework.org - scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-data/data-document/docs/${project.version} - - - - spring-milestone - Spring Milestone Repository - s3://maven.springframework.org/milestone - - - spring-snapshot - Spring Snapshot Repository - s3://maven.springframework.org/snapshot - - - - - + + 4.0.0 + org.springframework.data + spring-data-document-parent + Spring Data Document Parent + http://www.springsource.org/spring-data/data-document + 1.0.0.BUILD-SNAPSHOT + pom + + UTF-8 + + 4.8.1 + 1.2.15 + 1.8.4 + 1.5.10 + 3.0.5.RELEASE + 1.0.0.BUILD-SNAPSHOT + 1.6.11.M2 + + + + strict + + false + + + + fast + + true + true + + + + staging + + + spring-site-staging + file:///${java.io.tmpdir}/spring-data/data-document/docs + + + spring-milestone-staging + file:///${java.io.tmpdir}/spring-data/data-document/milestone + + + spring-snapshot-staging + file:///${java.io.tmpdir}/spring-data/data-document/snapshot + + + + + bootstrap + + + + + + http://www.springsource.com/download/community + + + static.springframework.org + + scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-data/data-document/docs/${project.version} + + + + spring-milestone + Spring Milestone Repository + s3://maven.springframework.org/milestone + + + spring-snapshot + Spring Snapshot Repository + s3://maven.springframework.org/snapshot + + + + + - - - org.springframework - spring-aop - ${org.springframework.version} - - - org.springframework - spring-beans - ${org.springframework.version} - - - org.springframework - spring-core - ${org.springframework.version} - - - commons-logging - commons-logging - - - - - org.springframework - spring-tx - ${org.springframework.version} - - - org.springframework - spring-orm - ${org.springframework.version} - - - org.springframework - spring-test - ${org.springframework.version} - test - + + + org.springframework + spring-aop + ${org.springframework.version} + + + org.springframework + spring-beans + ${org.springframework.version} + + + org.springframework + spring-core + ${org.springframework.version} + + + commons-logging + commons-logging + + + + + org.springframework + spring-tx + ${org.springframework.version} + + + org.springframework + spring-orm + ${org.springframework.version} + + + org.springframework + spring-web + ${org.springframework.version} + + + org.springframework + spring-expression + ${org.springframework.version} + + + org.springframework + spring-test + ${org.springframework.version} + test + - - - org.springframework.data - spring-data-commons-core - ${data.commons.version} - - - org.springframework.data - spring-data-commons-aspects - ${data.commons.version} - - - org.springframework.data - spring-data-document-core - ${project.version} - - - org.springframework.data - spring-data-couchdb - ${project.version} - - - org.springframework.data - spring-data-mongodb - ${project.version} - + + + org.springframework.data + spring-data-commons-core + ${data.commons.version} + + + org.springframework.data + spring-data-commons-aspects + ${data.commons.version} + + + org.springframework.data + spring-data-couchdb + ${project.version} + + + org.springframework.data + spring-data-mongodb + ${project.version} + - - - org.slf4j - slf4j-api - ${org.slf4j.version} - - - org.slf4j - jcl-over-slf4j - ${org.slf4j.version} - runtime - - - org.slf4j - slf4j-log4j12 - ${org.slf4j.version} - runtime - - - log4j - log4j - ${log4j.version} - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - - runtime - + + + org.slf4j + slf4j-api + ${org.slf4j.version} + + + org.slf4j + jcl-over-slf4j + ${org.slf4j.version} + runtime + + + org.slf4j + slf4j-log4j12 + ${org.slf4j.version} + runtime + + + log4j + log4j + ${log4j.version} + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + - - javax.annotation - jsr250-api - 1.0 - true - + + javax.annotation + jsr250-api + 1.0 + true + - - org.mockito - mockito-all - ${org.mockito.version} - test - + + org.mockito + mockito-all + ${org.mockito.version} + test + - - junit - junit - ${junit.version} - test - + + junit + junit + ${junit.version} + test + - - - - - - log4j - log4j - ${log4j.version} - test - - + + + + + + log4j + log4j + ${log4j.version} + test + + - - - - - org.springframework.build.aws - org.springframework.build.aws.maven - 3.1.0.RELEASE - - - - - ${project.basedir}/src/main/java - - **/* - - - **/*.java - - - - ${project.basedir}/src/main/resources - - **/* - - - - - - ${project.basedir}/src/test/java - - **/* - - - **/*.java - - - - ${project.basedir}/src/test/resources - - **/* - - - **/*.java - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.5 - 1.5 - -Xlint:all - true - false - - - - org.apache.maven.plugins - maven-surefire-plugin - - false - - **/*Tests.java - - - **/Abstract*.java - - junit:junit - - - - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - - com.springsource.bundlor - com.springsource.bundlor.maven - 1.0.0.RELEASE - - true - - - - bundlor - - bundlor - - - - - - - - - - - repository.plugin.springsource.release - SpringSource Maven Repository - http://repository.springsource.com/maven/bundles/release - - - - - repository.springframework.maven.release - Spring Framework Maven Release Repository - http://maven.springframework.org/release - - - repository.springframework.maven.milestone - Spring Framework Maven Milestone Repository - http://maven.springframework.org/milestone - - - repository.springframework.maven.snapshot - Spring Framework Maven Snapshot Repository - http://maven.springframework.org/snapshot - - - - - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.1 - - false - - - - + + + + + org.springframework.build.aws + org.springframework.build.aws.maven + 3.1.0.RELEASE + + + + + ${project.basedir}/src/main/java + + **/* + + + **/*.java + + + + ${project.basedir}/src/main/resources + + **/* + + + + + + ${project.basedir}/src/test/java + + **/* + + + **/*.java + + + + ${project.basedir}/src/test/resources + + **/* + + + **/*.java + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + -Xlint:all + true + false + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + **/*Tests.java + + + **/Abstract*.java + + junit:junit + + + + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + 1.0.0.RELEASE + + true + + + + bundlor + + bundlor + + + + + + + + + + + repository.plugin.springsource.release + SpringSource Maven Repository + http://repository.springsource.com/maven/bundles/release + + + + + repository.springframework.maven.release + Spring Framework Maven Release Repository + http://maven.springframework.org/release + + + repository.springframework.maven.milestone + Spring Framework Maven Milestone Repository + http://maven.springframework.org/milestone + + + repository.springframework.maven.snapshot + Spring Framework Maven Snapshot Repository + http://maven.springframework.org/snapshot + + + + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.1 + + false + + + + diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 1fa14284d..5b8f79776 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -1,120 +1,132 @@ - - 4.0.0 - - org.springframework.data - spring-data-document-parent - 1.0.0.BUILD-SNAPSHOT - ../spring-data-document-parent/pom.xml - - spring-data-mongodb - jar - Spring Data MongoDB Support - + + 4.0.0 + + org.springframework.data + spring-data-document-parent + 1.0.0.BUILD-SNAPSHOT + ../spring-data-document-parent/pom.xml + + spring-data-mongodb + jar + Spring Data MongoDB Support + - - - org.springframework - spring-beans - - - org.springframework - spring-tx - - - org.springframework - spring-test - test - + + + org.springframework + spring-beans + + + org.springframework + spring-tx + + + org.springframework + spring-expression + + + org.springframework + spring-test + test + - - - org.springframework.data - spring-data-document-core - - - org.springframework.data - spring-data-commons-core - + + + org.springframework.data + spring-data-document-core + 1.0.0.BUILD-SNAPSHOT + + + org.springframework.data + spring-data-commons-core + - - - org.slf4j - slf4j-api - - - org.slf4j - jcl-over-slf4j - compile - - - org.slf4j - slf4j-log4j12 - runtime - - - log4j - log4j - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - - runtime - - - - javax.annotation - jsr250-api - true - + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + compile + + + org.slf4j + slf4j-log4j12 + runtime + + + log4j + log4j + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + runtime + - - org.mockito - mockito-all - test - - - - org.hamcrest - hamcrest-all - 1.1 - test - + + javax.annotation + jsr250-api + true + - - junit - junit - - - - - - org.mongodb - mongo-java-driver - 2.3 - + + javax.persistence + persistence-api + 1.0 + - - - - - com.springsource.bundlor - com.springsource.bundlor.maven - - - + + org.mockito + mockito-all + test + + + + org.hamcrest + hamcrest-all + 1.1 + test + + + + junit + junit + + + + + + org.mongodb + mongo-java-driver + 2.3 + + + + + + + com.springsource.bundlor + com.springsource.bundlor.maven + + + diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoPropertyDescriptors.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoPropertyDescriptors.java index 94105721e..0192f1eb6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoPropertyDescriptors.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoPropertyDescriptors.java @@ -15,225 +15,222 @@ */ package org.springframework.data.document.mongodb; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.math.BigInteger; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - import org.bson.types.ObjectId; import org.springframework.beans.BeanUtils; import org.springframework.util.Assert; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.math.BigInteger; +import java.util.*; + /** * An iterable of {@link MongoPropertyDescriptor}s that allows dedicated access to the {@link MongoPropertyDescriptor} * that captures the id-property. - * + * * @author Oliver Gierke */ public class MongoPropertyDescriptors implements Iterable { - private final Collection descriptors; - private final MongoPropertyDescriptors.MongoPropertyDescriptor idDescriptor; + private final Collection descriptors; + private final MongoPropertyDescriptors.MongoPropertyDescriptor idDescriptor; - /** - * Creates the {@link MongoPropertyDescriptors} for the given type. - * - * @param type - */ - public MongoPropertyDescriptors(Class type) { + /** + * Creates the {@link MongoPropertyDescriptors} for the given type. + * + * @param type + */ + public MongoPropertyDescriptors(Class type) { - Assert.notNull(type); - Set descriptors = new HashSet(); - MongoPropertyDescriptors.MongoPropertyDescriptor idDesciptor = null; + Assert.notNull(type); + Set descriptors = new HashSet(); + MongoPropertyDescriptors.MongoPropertyDescriptor idDesciptor = null; - for (PropertyDescriptor candidates : BeanUtils.getPropertyDescriptors(type)) { - MongoPropertyDescriptor descriptor = new MongoPropertyDescriptors.MongoPropertyDescriptor(candidates); - descriptors.add(descriptor); - if (descriptor.isIdProperty()) { - idDesciptor = descriptor; - } - } + for (PropertyDescriptor candidates : BeanUtils.getPropertyDescriptors(type)) { + MongoPropertyDescriptor descriptor = new MongoPropertyDescriptors.MongoPropertyDescriptor(candidates); + descriptors.add(descriptor); + if (descriptor.isIdProperty()) { + idDesciptor = descriptor; + } + } - this.descriptors = Collections.unmodifiableSet(descriptors); - this.idDescriptor = idDesciptor; - } + this.descriptors = Collections.unmodifiableSet(descriptors); + this.idDescriptor = idDesciptor; + } - /** - * Returns the {@link MongoPropertyDescriptor} for the id property. - * - * @return the idDescriptor - */ - public MongoPropertyDescriptors.MongoPropertyDescriptor getIdDescriptor() { - return idDescriptor; - } + /** + * Returns the {@link MongoPropertyDescriptor} for the id property. + * + * @return the idDescriptor + */ + public MongoPropertyDescriptors.MongoPropertyDescriptor getIdDescriptor() { + return idDescriptor; + } - /* - * (non-Javadoc) - * - * @see java.lang.Iterable#iterator() - */ - public Iterator iterator() { - return descriptors.iterator(); - } + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() { + return descriptors.iterator(); + } - /** - * Simple value object to have a more suitable abstraction for MongoDB specific property handling. - * - * @author Oliver Gierke - */ - public static class MongoPropertyDescriptor { - - public static Collection> SUPPORTED_ID_CLASSES; - - static { - Set> classes = new HashSet>(); - classes.add(ObjectId.class); - classes.add(String.class); - classes.add(BigInteger.class); - SUPPORTED_ID_CLASSES = Collections.unmodifiableCollection(classes); - } + /** + * Simple value object to have a more suitable abstraction for MongoDB specific property handling. + * + * @author Oliver Gierke + */ + public static class MongoPropertyDescriptor { - private static final String ID_PROPERTY = "id"; - static final String ID_KEY = "_id"; + public static Collection> SUPPORTED_ID_CLASSES; - private final PropertyDescriptor delegate; + static { + Set> classes = new HashSet>(); + classes.add(ObjectId.class); + classes.add(String.class); + classes.add(BigInteger.class); + SUPPORTED_ID_CLASSES = Collections.unmodifiableCollection(classes); + } - /** - * Creates a new {@link MongoPropertyDescriptor} for the given {@link PropertyDescriptor}. - * - * @param descriptor - */ - public MongoPropertyDescriptor(PropertyDescriptor descriptor) { - Assert.notNull(descriptor); - this.delegate = descriptor; - } + private static final String ID_PROPERTY = "id"; + static final String ID_KEY = "_id"; - /** - * Returns whether the property is the id-property. Will be identified by name for now ({@value #ID_PROPERTY}). - * - * @return - */ - boolean isIdProperty() { - return ID_PROPERTY.equals(delegate.getName()) || ID_KEY.equals(delegate.getName()); - } + private final PropertyDescriptor delegate; - /** - * Returns whether the property is of one of the supported id types. Currently we support {@link String}, - * {@link ObjectId} and {@link BigInteger}. - * - * @return - */ - public boolean isOfIdType() { - return SUPPORTED_ID_CLASSES.contains(delegate.getPropertyType()); - } + /** + * Creates a new {@link MongoPropertyDescriptor} for the given {@link PropertyDescriptor}. + * + * @param descriptor + */ + public MongoPropertyDescriptor(PropertyDescriptor descriptor) { + Assert.notNull(descriptor); + this.delegate = descriptor; + } - /** - * Returns the key that shall be used for mapping. Will return {@value #ID_KEY} for the id property and the - * plain name for all other ones. - * - * @return - */ - public String getKeyToMap() { - return isIdProperty() ? ID_KEY : delegate.getName(); - } + /** + * Returns whether the property is the id-property. Will be identified by name for now ({@value #ID_PROPERTY}). + * + * @return + */ + public boolean isIdProperty() { + return ID_PROPERTY.equals(delegate.getName()) || ID_KEY.equals(delegate.getName()); + } - /** - * Returns the name of the property. - * - * @return - */ - public String getName() { - return delegate.getName(); - } + /** + * Returns whether the property is of one of the supported id types. Currently we support {@link String}, + * {@link ObjectId} and {@link BigInteger}. + * + * @return + */ + public boolean isOfIdType() { + return SUPPORTED_ID_CLASSES.contains(delegate.getPropertyType()); + } - /** - * Returns whether the underlying property is actually mappable. By default this will exclude the - * {@literal class} property and only include properties with a getter. - * - * @return - */ - public boolean isMappable() { - return !delegate.getName().equals("class") && delegate.getReadMethod() != null; - } + /** + * Returns the key that shall be used for mapping. Will return {@value #ID_KEY} for the id property and the + * plain name for all other ones. + * + * @return + */ + public String getKeyToMap() { + return isIdProperty() ? ID_KEY : delegate.getName(); + } - /** - * Returns the plain property type. - * - * @return - */ - public Class getPropertyType() { - return delegate.getPropertyType(); - } + /** + * Returns the name of the property. + * + * @return + */ + public String getName() { + return delegate.getName(); + } - /** - * Returns the type type to be set. Will return the setter method's type and fall back to the getter method's - * return type in case no setter is available. Useful for further (generics) inspection. - * - * @return - */ - public Type getTypeToSet() { + /** + * Returns whether the underlying property is actually mappable. By default this will exclude the + * {@literal class} property and only include properties with a getter. + * + * @return + */ + public boolean isMappable() { + return !delegate.getName().equals("class") && delegate.getReadMethod() != null; + } - Method method = delegate.getWriteMethod(); - return method == null ? delegate.getReadMethod().getGenericReturnType() - : method.getGenericParameterTypes()[0]; - } + /** + * Returns the plain property type. + * + * @return + */ + public Class getPropertyType() { + return delegate.getPropertyType(); + } - /** - * Returns whther we describe a {@link Map}. - * @return - */ - public boolean isMap() { - return Map.class.isAssignableFrom(getPropertyType()); - } + /** + * Returns the type type to be set. Will return the setter method's type and fall back to the getter method's + * return type in case no setter is available. Useful for further (generics) inspection. + * + * @return + */ + public Type getTypeToSet() { - /** - * Returns whether the descriptor is for a collection. - * @return - */ - public boolean isCollection() { - return Collection.class.isAssignableFrom(getPropertyType()); - } + Method method = delegate.getWriteMethod(); + return method == null ? delegate.getReadMethod().getGenericReturnType() + : method.getGenericParameterTypes()[0]; + } - /** - * Returns whether the descriptor is for an {@link Enum}. - * - * @return - */ - public boolean isEnum() { - return Enum.class.isAssignableFrom(getPropertyType()); - } + /** + * Returns whther we describe a {@link Map}. + * + * @return + */ + public boolean isMap() { + return Map.class.isAssignableFrom(getPropertyType()); + } - /* - * (non-Javadoc) - * - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || !getClass().equals(obj.getClass())) { - return false; - } - MongoPropertyDescriptor that = (MongoPropertyDescriptor) obj; - return that.delegate.equals(this.delegate); - } + /** + * Returns whether the descriptor is for a collection. + * + * @return + */ + public boolean isCollection() { + return Collection.class.isAssignableFrom(getPropertyType()); + } - /* - * (non-Javadoc) - * - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return delegate.hashCode(); - } - } + /** + * Returns whether the descriptor is for an {@link Enum}. + * + * @return + */ + public boolean isEnum() { + return Enum.class.isAssignableFrom(getPropertyType()); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || !getClass().equals(obj.getClass())) { + return false; + } + MongoPropertyDescriptor that = (MongoPropertyDescriptor) obj; + return that.delegate.equals(this.delegate); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return delegate.hashCode(); + } + } } \ No newline at end of file diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoReader.java index 3d1341af8..5af012be5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoReader.java @@ -19,22 +19,22 @@ import com.mongodb.DBObject; /** * A MongoWriter is responsible for converting a native MongoDB DBObject to an object of type T. - * + * + * @param the type of the object to convert from a DBObject * @author Mark Pollack * @author Thomas Risberg * @author Oliver Gierke - * - * @param the type of the object to convert from a DBObject */ public interface MongoReader { - /** - * Ready from the native MongoDB DBObject representation to an instance of the class T. The given type has to be the - * starting point for marshalling the {@link DBObject} into it. So in case there's no real valid data inside - * {@link DBObject} for the given type, just return an empty instance of the given type. - * @param clazz the type of the return value - * @param dbo theDBObject - * @return the converted object - */ - S read(Class clazz, DBObject dbo); + /** + * Ready from the native MongoDB DBObject representation to an instance of the class T. The given type has to be the + * starting point for marshalling the {@link DBObject} into it. So in case there's no real valid data inside + * {@link DBObject} for the given type, just return an empty instance of the given type. + * + * @param clazz the type of the return value + * @param dbo theDBObject + * @return the converted object + */ + S read(Class clazz, DBObject dbo); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java index 54bb9ea5f..0e781b43a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java @@ -36,6 +36,8 @@ import org.springframework.data.document.mongodb.MongoPropertyDescriptors.MongoP import org.springframework.data.document.mongodb.query.IndexDefinition; import org.springframework.data.document.mongodb.query.Query; import org.springframework.data.document.mongodb.query.Update; +import org.springframework.data.document.mongodb.convert.MongoConverter; +import org.springframework.data.document.mongodb.convert.SimpleMongoConverter; import org.springframework.jca.cci.core.ConnectionCallback; import org.springframework.util.Assert; @@ -130,7 +132,7 @@ public class MongoTemplate implements InitializingBean, MongoOperations { } /** - * Constructor used for a template configuration with a default collection name and a custom {@link MongoConverter} + * Constructor used for a template configuration with a default collection name and a custom {@link org.springframework.data.document.mongodb.convert.MongoConverter} * @param mongo * @param databaseName * @param defaultCollectionName @@ -205,7 +207,7 @@ public class MongoTemplate implements InitializingBean, MongoOperations { } /** - * Returns the default {@link MongoConverter}. + * Returns the default {@link org.springframework.data.document.mongodb.convert.MongoConverter}. * * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/SimpleMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/SimpleMongoConverter.java deleted file mode 100644 index 7a9fdc9e7..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/SimpleMongoConverter.java +++ /dev/null @@ -1,557 +0,0 @@ -/* - * Copyright 2010-2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.document.mongodb; - -import java.lang.reflect.Array; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.regex.Pattern; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.bson.types.CodeWScope; -import org.bson.types.ObjectId; -import org.springframework.beans.BeanUtils; -import org.springframework.core.CollectionFactory; -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.support.ConversionServiceFactory; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.data.document.mongodb.MongoPropertyDescriptors.MongoPropertyDescriptor; -import org.springframework.util.Assert; -import org.springframework.util.comparator.CompoundComparator; - -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; -import com.mongodb.DBRef; - -/** - * Basic {@link MongoConverter} implementation to convert between domain classes and {@link DBObject}s. - * - * @author Mark Pollack - * @author Thomas Risberg - * @author Oliver Gierke - */ -public class SimpleMongoConverter implements MongoConverter { - - private static final Log LOG = LogFactory.getLog(SimpleMongoConverter.class); - private static final Set SIMPLE_TYPES; - - static { - Set basics = new HashSet(); - basics.add(boolean.class.getName()); - basics.add(long.class.getName()); - basics.add(short.class.getName()); - basics.add(int.class.getName()); - basics.add(byte.class.getName()); - basics.add(float.class.getName()); - basics.add(double.class.getName()); - basics.add(char.class.getName()); - basics.add(Boolean.class.getName()); - basics.add(Long.class.getName()); - basics.add(Short.class.getName()); - basics.add(Integer.class.getName()); - basics.add(Byte.class.getName()); - basics.add(Float.class.getName()); - basics.add(Double.class.getName()); - basics.add(Character.class.getName()); - basics.add(String.class.getName()); - basics.add(java.util.Date.class.getName()); - // basics.add(Time.class.getName()); - // basics.add(Timestamp.class.getName()); - // basics.add(java.sql.Date.class.getName()); - // basics.add(BigDecimal.class.getName()); - // basics.add(BigInteger.class.getName()); - basics.add(Locale.class.getName()); - // basics.add(Calendar.class.getName()); - // basics.add(GregorianCalendar.class.getName()); - // basics.add(java.util.Currency.class.getName()); - // basics.add(TimeZone.class.getName()); - // basics.add(Object.class.getName()); - basics.add(Class.class.getName()); - // basics.add(byte[].class.getName()); - // basics.add(Byte[].class.getName()); - // basics.add(char[].class.getName()); - // basics.add(Character[].class.getName()); - // basics.add(Blob.class.getName()); - // basics.add(Clob.class.getName()); - // basics.add(Serializable.class.getName()); - // basics.add(URI.class.getName()); - // basics.add(URL.class.getName()); - basics.add(DBRef.class.getName()); - basics.add(Pattern.class.getName()); - basics.add(CodeWScope.class.getName()); - basics.add(ObjectId.class.getName()); - // TODO check on enums.. - basics.add(Enum.class.getName()); - SIMPLE_TYPES = Collections.unmodifiableSet(basics); - } - - private final GenericConversionService conversionService; - - /** - * Creates a {@link SimpleMongoConverter}. - */ - public SimpleMongoConverter() { - this.conversionService = ConversionServiceFactory.createDefaultConversionService(); - initializeConverters(); - } - - /** - * Creates a new {@link SimpleMongoConverter} for the given {@link ConversionService}. - * - * @param conversionService - */ - public SimpleMongoConverter(GenericConversionService conversionService) { - Assert.notNull(conversionService); - this.conversionService = conversionService; - initializeConverters(); - } - - /** - * Initializes additional converters that handle {@link ObjectId} conversion. Will register converters for supported - * id types if none are registered for those conversion already. {@link GenericConversionService} is configured. - */ - protected void initializeConverters() { - - if (!conversionService.canConvert(ObjectId.class, String.class)) { - conversionService.addConverter(ObjectIdToStringConverter.INSTANCE); - conversionService.addConverter(StringToObjectIdConverter.INSTANCE); - } - - if (!conversionService.canConvert(ObjectId.class, BigInteger.class)) { - conversionService.addConverter(ObjectIdToBigIntegerConverter.INSTANCE); - conversionService.addConverter(BigIntegerToIdConverter.INSTANCE); - } - } - - /* - * (non-Javadoc) - * - * @see org.springframework.data.document.mongodb.MongoWriter#write(java.lang.Object, com.mongodb.DBObject) - */ - @SuppressWarnings("rawtypes") - public void write(Object obj, DBObject dbo) { - - MongoBeanWrapper beanWrapper = createWrapper(obj, false); - for (MongoPropertyDescriptor descriptor : beanWrapper.getDescriptors()) { - if (descriptor.isMappable()) { - Object value = beanWrapper.getValue(descriptor); - - if (value == null) { - continue; - } - - String keyToUse = descriptor.getKeyToMap(); - // TODO validate Enums... - if (descriptor.isEnum()) { - writeValue(dbo, keyToUse, ((Enum) value).name()); - } else if (descriptor.isIdProperty() && descriptor.isOfIdType()) { - if (value instanceof String && ObjectId.isValid((String)value)) { - try { - writeValue(dbo, keyToUse, conversionService.convert(value, ObjectId.class)); - } catch (ConversionFailedException iae) { - LOG.warn("Unable to convert the String " + value + " to an ObjectId"); - writeValue(dbo, keyToUse, value); - } - } - else { - // we can't convert this id - use as is - writeValue(dbo, keyToUse, value); - } - } else { - writeValue(dbo, keyToUse, value); - } - } else { - if (!"class".equals(descriptor.getName())) { - LOG.warn("Unable to map property " + descriptor.getName() + ". Skipping."); - } - } - } - } - - /** - * Writes the given value to the given {@link DBObject}. Will skip {@literal null} values. - * - * @param dbo - * @param keyToUse - * @param value - */ - private void writeValue(DBObject dbo, String keyToUse, Object value) { - - if (!isSimpleType(value.getClass())) { - writeCompoundValue(dbo, keyToUse, value); - } else { - dbo.put(keyToUse, value); - } - } - - /** - * Writes the given {@link CompoundComparator} value to the given {@link DBObject}. - * - * @param dbo - * @param keyToUse - * @param value - */ - @SuppressWarnings("unchecked") - private void writeCompoundValue(DBObject dbo, String keyToUse, Object value) { - if (value instanceof Map) { - writeMap(dbo, keyToUse, (Map) value); - return; - } - if (value instanceof Collection) { - // Should write a collection! - writeArray(dbo, keyToUse, ((Collection) value).toArray()); - return; - } - if (value instanceof Object[]) { - // Should write an array! - writeArray(dbo, keyToUse, (Object[]) value); - return; - } - DBObject nestedDbo = new BasicDBObject(); - write(value, nestedDbo); - dbo.put(keyToUse, nestedDbo); - - } - - /** - * Writes the given {@link Map} to the given {@link DBObject}. - * - * @param dbo - * @param mapKey - * @param map - */ - protected void writeMap(DBObject dbo, String mapKey, Map map) { - // TODO support non-string based keys as long as there is a Spring Converter obj->string and (optionally) - // string->obj - DBObject dboToPopulate = null; - - // TODO - Does that make sense? If we create a new object here it's content will never make it out of this - // method - if (mapKey != null) { - dboToPopulate = new BasicDBObject(); - } else { - dboToPopulate = dbo; - } - if (map != null) { - for (Entry entry : map.entrySet()) { - - Object entryValue = entry.getValue(); - String entryKey = entry.getKey(); - - if (!isSimpleType(entryValue.getClass())) { - writeCompoundValue(dboToPopulate, entryKey, entryValue); - } else { - dboToPopulate.put(entryKey, entryValue); - } - } - dbo.put(mapKey, dboToPopulate); - } - } - - /** - * Writes the given array to the given {@link DBObject}. - * - * @param dbo - * @param keyToUse - * @param array - */ - protected void writeArray(DBObject dbo, String keyToUse, Object[] array) { - // TODO - Object[] dboValues; - if (array != null) { - dboValues = new Object[array.length]; - int i = 0; - for (Object o : array) { - if (!isSimpleType(o.getClass())) { - DBObject dboValue = new BasicDBObject(); - write(o, dboValue); - dboValues[i] = dboValue; - } else { - dboValues[i] = o; - } - i++; - } - dbo.put(keyToUse, dboValues); - } - } - - /* - * (non-Javadoc) - * - * @see org.springframework.data.document.mongodb.MongoReader#read(java.lang.Class, com.mongodb.DBObject) - */ - public S read(Class clazz, DBObject source) { - - if (source == null) { - return null; - } - - Assert.notNull(clazz, "Mapped class was not specified"); - S target = BeanUtils.instantiateClass(clazz); - MongoBeanWrapper bw = new MongoBeanWrapper(target, conversionService, true); - - for (MongoPropertyDescriptor descriptor : bw.getDescriptors()) { - String keyToUse = descriptor.getKeyToMap(); - if (source.containsField(keyToUse)) { - if (descriptor.isMappable()) { - Object value = source.get(keyToUse); - if (!isSimpleType(value.getClass())) { - if (value instanceof Object[]) { - bw.setValue(descriptor, readCollection(descriptor, Arrays.asList((Object[]) value)) - .toArray()); - } else if (value instanceof BasicDBList) { - bw.setValue(descriptor, readCollection(descriptor, (BasicDBList) value)); - } else if (value instanceof DBObject) { - bw.setValue(descriptor, readCompoundValue(descriptor, (DBObject) value)); - } else { - LOG.warn("Unable to map compound DBObject field " + keyToUse + " to property " - + descriptor.getName() - + ". The field value should have been a 'DBObject.class' but was " - + value.getClass().getName()); - } - } else { - bw.setValue(descriptor, value); - } - } else { - LOG.warn("Unable to map DBObject field " + keyToUse + " to property " + descriptor.getName() - + ". Skipping."); - } - } - } - - return target; - } - - /** - * Reads the given collection values (that are {@link DBObject}s potentially) into a {@link Collection} of domain - * objects. - * - * @param descriptor - * @param values - * @return - */ - private Collection readCollection(MongoPropertyDescriptor descriptor, Collection values) { - - Class targetCollectionType = descriptor.getPropertyType(); - boolean targetIsArray = targetCollectionType.isArray(); - - @SuppressWarnings("unchecked") - Collection result = targetIsArray ? new ArrayList(values.size()) : CollectionFactory - .createCollection(targetCollectionType, values.size()); - - for (Object o : values) { - if (o instanceof DBObject) { - Class type; - if (targetIsArray) { - type = targetCollectionType.getComponentType(); - } else { - type = getGenericParameters(descriptor.getTypeToSet()).get(0); - } - result.add(read(type, (DBObject) o)); - } else { - result.add(o); - } - } - - return result; - } - - /** - * Reads a compound value from the given {@link DBObject} for the given property. - * - * @param pd - * @param dbo - * @return - */ - private Object readCompoundValue(MongoPropertyDescriptors.MongoPropertyDescriptor pd, DBObject dbo) { - - Assert.isTrue(!pd.isCollection(), "Collections not supported!"); - - if (pd.isMap()) { - return readMap(pd, dbo, getGenericParameters(pd.getTypeToSet()).get(1)); - } else { - return read(pd.getPropertyType(), dbo); - } - } - - /** - * Create a {@link Map} instance. Will return a {@link HashMap} by default. Subclasses might want to override this - * method to use a custom {@link Map} implementation. - * - * @return - */ - protected Map createMap() { - return new HashMap(); - } - - /** - * Reads every key/value pair from the {@link DBObject} into a {@link Map} instance. - * - * @param pd - * @param dbo - * @param targetType - * @return - */ - protected Map readMap(MongoPropertyDescriptors.MongoPropertyDescriptor pd, DBObject dbo, Class targetType) { - Map map = createMap(); - for (String key : dbo.keySet()) { - Object value = dbo.get(key); - if (!isSimpleType(value.getClass())) { - map.put(key, read(targetType, (DBObject) value)); - // Can do some reflection tricks here - - // throw new RuntimeException("User types not supported yet as values for Maps"); - } else { - map.put(key, conversionService.convert(value, targetType)); - } - } - return map; - } - - protected static boolean isSimpleType(Class propertyType) { - if (propertyType == null) { - return false; - } - if (propertyType.isArray()) { - return isSimpleType(propertyType.getComponentType()); - } - return SIMPLE_TYPES.contains(propertyType.getName()); - } - - /** - * Callback to allow customizing creation of a {@link MongoBeanWrapper}. - * - * @param target the target object to wrap - * @param fieldAccess whether to use field access or property access - * @return - */ - protected MongoBeanWrapper createWrapper(Object target, boolean fieldAccess) { - - return new MongoBeanWrapper(target, conversionService, fieldAccess); - } - - List> getGenericParameters(Type genericParameterType) { - - List> actualGenericParameterTypes = new ArrayList>(); - - if (genericParameterType instanceof ParameterizedType) { - ParameterizedType aType = (ParameterizedType) genericParameterType; - Type[] parameterArgTypes = aType.getActualTypeArguments(); - for (Type parameterArgType : parameterArgTypes) { - if (parameterArgType instanceof GenericArrayType) { - Class arrayType = (Class) ((GenericArrayType) parameterArgType).getGenericComponentType(); - actualGenericParameterTypes.add(Array.newInstance(arrayType, 0).getClass()); - } else { - if (parameterArgType instanceof ParameterizedType) { - ParameterizedType paramTypeArgs = (ParameterizedType) parameterArgType; - actualGenericParameterTypes.add((Class) paramTypeArgs.getRawType()); - } else { - if (parameterArgType instanceof TypeVariable) { - throw new RuntimeException("Can not map " + ((TypeVariable) parameterArgType).getName()); - } else { - if (parameterArgType instanceof Class) { - actualGenericParameterTypes.add((Class) parameterArgType); - } else { - throw new RuntimeException("Can not map " + parameterArgType); - } - } - } - } - } - } - - return actualGenericParameterTypes; - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoConverter#convertObjectId(org.bson.types.ObjectId, java.lang.Class) - */ - public T convertObjectId(ObjectId id, Class targetType) { - return conversionService.convert(id, targetType); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoConverter#convertObjectId(java.lang.Object) - */ - public ObjectId convertObjectId(Object id) { - return conversionService.convert(id, ObjectId.class); - } - - /** - * Simple singleton to convert {@link ObjectId}s to their {@link String} representation. - * - * @author Oliver Gierke - */ - public static enum ObjectIdToStringConverter implements Converter { - INSTANCE; - public String convert(ObjectId id) { - return id.toString(); - } - } - - /** - * Simple singleton to convert {@link String}s to their {@link ObjectId} representation. - * - * @author Oliver Gierke - */ - public static enum StringToObjectIdConverter implements Converter { - INSTANCE; - public ObjectId convert(String source) { - return new ObjectId(source); - } - } - - /** - * Simple singleton to convert {@link ObjectId}s to their {@link BigInteger} representation. - * - * @author Oliver Gierke - */ - public static enum ObjectIdToBigIntegerConverter implements Converter { - INSTANCE; - public BigInteger convert(ObjectId source) { - return new BigInteger(source.toString(), 16); - } - } - - /** - * Simple singleton to convert {@link BigInteger}s to their {@link ObjectId} representation. - * - * @author Oliver Gierke - */ - public static enum BigIntegerToIdConverter implements Converter { - INSTANCE; - public ObjectId convert(BigInteger source) { - return new ObjectId(source.toString(16)); - } - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/config/MongoMappingConverterParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/config/MongoMappingConverterParser.java new file mode 100644 index 000000000..da0d6830b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/config/MongoMappingConverterParser.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.config; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * Created by IntelliJ IDEA. + * User: jbrisbin + * Date: 2/28/11 + * Time: 9:26 AM + * To change this template use File | Settings | File Templates. + */ +public class MongoMappingConverterParser extends AbstractSingleBeanDefinitionParser { + + private static final String BASE_PACKAGE = "base-package"; + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { + String id = super.resolveId(element, definition, parserContext); + if (!StringUtils.hasText(id)) { + return "mongoMappingConverter"; + } + return id; + } + + @Override + protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { + super.doParse(element, parserContext, builder); + + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/config/SimpleMongoRepositoryConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/config/SimpleMongoRepositoryConfiguration.java index ca1d079be..3e8d21620 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/config/SimpleMongoRepositoryConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/config/SimpleMongoRepositoryConfiguration.java @@ -29,167 +29,164 @@ import org.w3c.dom.Element; * {@link RepositoryConfig} implementation to create * {@link MongoRepositoryConfiguration} instances for both automatic and manual * configuration. - * + * * @author Oliver Gierke */ -public class SimpleMongoRepositoryConfiguration - extends - RepositoryConfig { +public class SimpleMongoRepositoryConfiguration extends RepositoryConfig { - private static final String MONGO_TEMPLATE_REF = "mongo-template-ref"; - private static final String DEFAULT_MONGO_TEMPLATE_REF = "mongoTemplate"; + private static final String MONGO_TEMPLATE_REF = "mongo-template-ref"; + private static final String DEFAULT_MONGO_TEMPLATE_REF = "mongoTemplate"; + /** + * Creates a new {@link SimpleMongoRepositoryConfiguration} for the given + * {@link Element}. + * + * @param repositoriesElement + */ + protected SimpleMongoRepositoryConfiguration(Element repositoriesElement) { + + super(repositoriesElement, MongoRepositoryFactoryBean.class.getName()); + } + + + /** + * Returns the bean name of the {@link org.springframework.data.document.mongodb.MongoTemplate} to be referenced. + * + * @return + */ + public String getMongoTemplateRef() { + + String templateRef = getSource().getAttribute(MONGO_TEMPLATE_REF); + return StringUtils.hasText(templateRef) ? templateRef + : DEFAULT_MONGO_TEMPLATE_REF; + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.config.GlobalRepositoryConfigInformation + * #getAutoconfigRepositoryInformation(java.lang.String) + */ + public MongoRepositoryConfiguration getAutoconfigRepositoryInformation( + String interfaceName) { + + return new AutomaticMongoRepositoryConfiguration(interfaceName, this); + } + + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.config.GlobalRepositoryConfigInformation + * #getRepositoryBaseInterface() + */ + public Class getRepositoryBaseInterface() { + + return MongoRepository.class; + } + + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.config.RepositoryConfig# + * createSingleRepositoryConfigInformationFor(org.w3c.dom.Element) + */ + @Override + protected MongoRepositoryConfiguration createSingleRepositoryConfigInformationFor( + Element element) { + + return new ManualMongoRepositoryConfiguration(element, this); + } + + /** + * Simple interface for configuration values specific to Mongo repositories. + * + * @author Oliver Gierke + */ + public interface MongoRepositoryConfiguration + extends + SingleRepositoryConfigInformation { + + String getMongoTemplateRef(); + } + + /** + * Implements manual lookup of the additional attributes. + * + * @author Oliver Gierke + */ + private static class ManualMongoRepositoryConfiguration + extends + ManualRepositoryConfigInformation + implements MongoRepositoryConfiguration { + /** - * Creates a new {@link SimpleMongoRepositoryConfiguration} for the given - * {@link Element}. - * - * @param repositoriesElement - * @param defaultRepositoryFactoryBeanClassName + * Creates a new {@link ManualMongoRepositoryConfiguration} for the + * given {@link Element} and parent. + * + * @param element + * @param parent */ - protected SimpleMongoRepositoryConfiguration(Element repositoriesElement) { + public ManualMongoRepositoryConfiguration(Element element, + SimpleMongoRepositoryConfiguration parent) { - super(repositoriesElement, MongoRepositoryFactoryBean.class.getName()); + super(element, parent); } - /** - * Returns the bean name of the {@link org.springframework.data.document.mongodb.MongoTemplate} to be referenced. - * - * @return - */ + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.repository.config. + * SimpleMongoRepositoryConfiguration + * .MongoRepositoryConfiguration#getMongoTemplateRef() + */ public String getMongoTemplateRef() { - String templateRef = getSource().getAttribute(MONGO_TEMPLATE_REF); - return StringUtils.hasText(templateRef) ? templateRef - : DEFAULT_MONGO_TEMPLATE_REF; + return getAttribute(MONGO_TEMPLATE_REF); + } + } + + /** + * Implements the lookup of the additional attributes during automatic + * configuration. + * + * @author Oliver Gierke + */ + private static class AutomaticMongoRepositoryConfiguration + extends + AutomaticRepositoryConfigInformation + implements MongoRepositoryConfiguration { + + /** + * Creates a new {@link AutomaticMongoRepositoryConfiguration} for the + * given interface and parent. + * + * @param interfaceName + * @param parent + */ + public AutomaticMongoRepositoryConfiguration(String interfaceName, + SimpleMongoRepositoryConfiguration parent) { + + super(interfaceName, parent); } /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.GlobalRepositoryConfigInformation - * #getAutoconfigRepositoryInformation(java.lang.String) - */ - public MongoRepositoryConfiguration getAutoconfigRepositoryInformation( - String interfaceName) { + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.repository.config. + * SimpleMongoRepositoryConfiguration + * .MongoRepositoryConfiguration#getMongoTemplateRef() + */ + public String getMongoTemplateRef() { - return new AutomaticMongoRepositoryConfiguration(interfaceName, this); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.config.GlobalRepositoryConfigInformation - * #getRepositoryBaseInterface() - */ - public Class getRepositoryBaseInterface() { - - return MongoRepository.class; - } - - - /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.config.RepositoryConfig# - * createSingleRepositoryConfigInformationFor(org.w3c.dom.Element) - */ - @Override - protected MongoRepositoryConfiguration createSingleRepositoryConfigInformationFor( - Element element) { - - return new ManualMongoRepositoryConfiguration(element, this); - } - - /** - * Simple interface for configuration values specific to Mongo repositories. - * - * @author Oliver Gierke - */ - public interface MongoRepositoryConfiguration - extends - SingleRepositoryConfigInformation { - - String getMongoTemplateRef(); - } - - /** - * Implements manual lookup of the additional attributes. - * - * @author Oliver Gierke - */ - private static class ManualMongoRepositoryConfiguration - extends - ManualRepositoryConfigInformation - implements MongoRepositoryConfiguration { - - /** - * Creates a new {@link ManualMongoRepositoryConfiguration} for the - * given {@link Element} and parent. - * - * @param element - * @param parent - */ - public ManualMongoRepositoryConfiguration(Element element, - SimpleMongoRepositoryConfiguration parent) { - - super(element, parent); - } - - - /* - * (non-Javadoc) - * - * @see org.springframework.data.document.mongodb.repository.config. - * SimpleMongoRepositoryConfiguration - * .MongoRepositoryConfiguration#getMongoTemplateRef() - */ - public String getMongoTemplateRef() { - - return getAttribute(MONGO_TEMPLATE_REF); - } - } - - /** - * Implements the lookup of the additional attributes during automatic - * configuration. - * - * @author Oliver Gierke - */ - private static class AutomaticMongoRepositoryConfiguration - extends - AutomaticRepositoryConfigInformation - implements MongoRepositoryConfiguration { - - /** - * Creates a new {@link AutomaticMongoRepositoryConfiguration} for the - * given interface and parent. - * - * @param interfaceName - * @param parent - */ - public AutomaticMongoRepositoryConfiguration(String interfaceName, - SimpleMongoRepositoryConfiguration parent) { - - super(interfaceName, parent); - } - - - /* - * (non-Javadoc) - * - * @see org.springframework.data.document.mongodb.repository.config. - * SimpleMongoRepositoryConfiguration - * .MongoRepositoryConfiguration#getMongoTemplateRef() - */ - public String getMongoTemplateRef() { - - return getParent().getMongoTemplateRef(); - } + return getParent().getMongoTemplateRef(); } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java new file mode 100644 index 000000000..e4ea704cd --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java @@ -0,0 +1,487 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.convert; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.DBRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bson.types.CodeWScope; +import org.bson.types.ObjectId; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.document.mongodb.mapping.MappingException; +import org.springframework.data.document.mongodb.mapping.MappingIntrospector; +import org.springframework.data.document.mongodb.mapping.MongoMappingContext; +import org.springframework.data.mapping.annotation.Id; +import org.springframework.data.mapping.annotation.Persistent; +import org.springframework.data.mapping.annotation.Transient; +import org.springframework.data.mapping.annotation.Value; +import org.springframework.data.mapping.model.PersistentEntity; +import org.springframework.data.mapping.model.PersistentProperty; +import org.springframework.data.mapping.model.types.Association; +import org.springframework.data.mapping.model.types.Simple; +import org.springframework.data.mapping.reflect.ClassPropertyFetcher; +import org.springframework.data.mapping.reflect.ReflectionUtils; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.math.BigInteger; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; + +/** + * @author Jon Brisbin + */ +@SuppressWarnings({"unchecked"}) +public class MappingMongoConverter implements MongoConverter, ApplicationContextAware { + + protected static final Log log = LogFactory.getLog(MappingMongoConverter.class); + protected static final Set SIMPLE_TYPES; + protected static final ConcurrentMap, Map> fieldsByName = new ConcurrentHashMap, Map>(); + + static { + Set basics = new HashSet(); + basics.add(boolean.class.getName()); + basics.add(long.class.getName()); + basics.add(short.class.getName()); + basics.add(int.class.getName()); + basics.add(byte.class.getName()); + basics.add(float.class.getName()); + basics.add(double.class.getName()); + basics.add(char.class.getName()); + basics.add(Boolean.class.getName()); + basics.add(Long.class.getName()); + basics.add(Short.class.getName()); + basics.add(Integer.class.getName()); + basics.add(Byte.class.getName()); + basics.add(Float.class.getName()); + basics.add(Double.class.getName()); + basics.add(Character.class.getName()); + basics.add(String.class.getName()); + basics.add(java.util.Date.class.getName()); + basics.add(Locale.class.getName()); + basics.add(Class.class.getName()); + basics.add(DBRef.class.getName()); + basics.add(Pattern.class.getName()); + basics.add(CodeWScope.class.getName()); + basics.add(ObjectId.class.getName()); + // TODO check on enums.. + basics.add(Enum.class.getName()); + SIMPLE_TYPES = Collections.unmodifiableSet(basics); + } + + protected GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); + protected MongoMappingContext mappingContext; + protected ApplicationContext applicationContext; + protected boolean autowirePersistentBeans = false; + + public MappingMongoConverter() { + initializeConverters(); + } + + public MappingMongoConverter(MongoMappingContext mappingContext) { + this.mappingContext = mappingContext; + initializeConverters(); + } + + public MappingMongoConverter(MongoMappingContext mappingContext, List> converters) { + this.mappingContext = mappingContext; + if (null != converters) { + for (Converter c : converters) { + conversionService.addConverter(c); + } + } + initializeConverters(); + } + + public MongoMappingContext getMappingContext() { + return mappingContext; + } + + public void setMappingContext(MongoMappingContext mappingContext) { + this.mappingContext = mappingContext; + } + + public boolean isAutowirePersistentBeans() { + return autowirePersistentBeans; + } + + public void setAutowirePersistentBeans(boolean autowirePersistentBeans) { + this.autowirePersistentBeans = autowirePersistentBeans; + } + + public T convertObjectId(ObjectId id, Class targetType) { + return conversionService.convert(id, targetType); + } + + public ObjectId convertObjectId(Object id) { + return conversionService.convert(id, ObjectId.class); + } + + public S read(Class clazz, final DBObject dbo) { + final StandardEvaluationContext ctx = new StandardEvaluationContext(); + if (null != applicationContext) { + ctx.setBeanResolver(new BeanFactoryResolver(applicationContext)); + } + String[] keySet = dbo.keySet().toArray(new String[]{}); + for (String key : keySet) { + ctx.setVariable(key, dbo.get(key)); + } + + try { + if ((clazz.isArray() || clazz.isAssignableFrom(Collection.class)) && dbo instanceof BasicDBList) { + List l = new ArrayList(); + BasicDBList dbList = (BasicDBList) dbo; + for (Object o : dbList) { + if (o instanceof DBObject) { + Object newObj = read(clazz.getComponentType(), (DBObject) o); + if (newObj.getClass().isAssignableFrom(clazz.getComponentType())) { + l.add(newObj); + } else { + l.add(conversionService.convert(newObj, clazz.getComponentType())); + } + } else { + l.add(o); + } + } + return conversionService.convert(l, clazz); + } + + final MappingIntrospector introspector = MappingIntrospector.getInstance(clazz); + final List cparamNames = new ArrayList(); + final S instance = introspector.createInstance(new MappingIntrospector.ParameterValueProvider() { + public T getParameterValue(String name, Class type, Expression spelExpr) { + Object o = getValueInternal(name, type, dbo, ctx, spelExpr); + if (null != o && o.getClass().isAssignableFrom(type)) { + cparamNames.add(name); + return (T) o; + } else { + cparamNames.add(name); + return conversionService.convert(o, type); + } + } + }); + introspector.doWithProperties(new MappingIntrospector.PropertyHandler() { + public void doWithProperty(PropertyDescriptor descriptor, Field field, Expression spelExpr) { + String name = descriptor.getName(); + if (!cparamNames.contains(name)) { + Object o = getValueInternal(name, descriptor.getPropertyType(), dbo, ctx, spelExpr); + try { + introspector.setValue(name, instance, o); + } catch (MappingException e) { + log.error(e.getMessage(), e); + } + } + } + }); + + introspector.maybeAutowire(instance, applicationContext, autowirePersistentBeans); + + return instance; + } catch (MappingException e) { + throw new RuntimeException(e); + } + } + + public void write(final Object o, final DBObject dbo) { + try { + final MappingIntrospector introspector = MappingIntrospector.getInstance(o.getClass()); + + Field idFld = introspector.getIdField(); + if (null != idFld) { + Object idObj = introspector.getFieldValue(idFld.getName(), o); + if (null != idObj) { + dbo.put("_id", conversionService.convert(idObj, ObjectId.class)); + } + } + + introspector.doWithProperties(new MappingIntrospector.PropertyHandler() { + public void doWithProperty(PropertyDescriptor descriptor, Field field, Expression spelExpr) { + String name = descriptor.getName(); + try { + Object newObj = introspector.getFieldValue(name, o); + if (null != newObj) { + if (MappingIntrospector.isSimpleType(newObj.getClass())) { + dbo.put(name, newObj); + } else { + if (newObj.getClass().isAssignableFrom(Collection.class)) { + BasicDBList dbList = new BasicDBList(); + write(newObj, dbList); + dbo.put(name, dbList); + } else { + DBObject newDbObj = new BasicDBObject(); + write(newObj, newDbObj); + dbo.put(name, newDbObj); + } + } + } + } catch (MappingException e) { + log.error(e.getMessage(), e); + } + } + }); + + // Handle mapping-specific stuff + if (introspector.isMappable()) { + introspector.doWithAssociations(new MappingIntrospector.AssociationHandler() { + public void doWithAssociation(MappingIntrospector.Association association) { + log.info("HANDLE ASSOCIATION: " + association); + } + }); + } + + } catch (MappingException e) { + log.error(e.getMessage(), e); + } + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + protected boolean isTransientField(Field f) { + return (Modifier.isTransient(f.getModifiers()) || null != f.getAnnotation(Transient.class) || null != f.getAnnotation(Autowired.class)); + } + + protected boolean isPersistentProperty(Object obj, PropertyDescriptor descriptor) { + try { + Field f = obj.getClass().getDeclaredField(descriptor.getName()); + + return false; + } catch (NoSuchFieldException e) { + return false; + } + } + + protected void initializeConverters() { + if (!conversionService.canConvert(ObjectId.class, String.class)) { + conversionService.addConverter(ObjectIdToStringConverter.INSTANCE); + conversionService.addConverter(StringToObjectIdConverter.INSTANCE); + } + if (!conversionService.canConvert(ObjectId.class, BigInteger.class)) { + conversionService.addConverter(ObjectIdToBigIntegerConverter.INSTANCE); + conversionService.addConverter(BigIntegerToIdConverter.INSTANCE); + } + if (!conversionService.canConvert(ObjectId.class, Integer.class)) { + conversionService.addConverter(IntegerToIdConverter.INSTANCE); + conversionService.addConverter(IdToIntegerConverter.INSTANCE); + } + if (!conversionService.canConvert(Object.class, DBObject.class)) { + conversionService.addConverter(new ObjectToDBObjectConverter()); + } + } + + protected Object getValueInternal(String name, Class type, DBObject dbo, StandardEvaluationContext ctx, Expression spelExpr) { + Object o; + if (null != spelExpr) { + o = spelExpr.getValue(ctx); + } else { + Object dbObj = dbo.get(name); + if (dbObj instanceof DBObject) { + // It's a complex object, have to read it in + o = read(type, (DBObject) dbObj); + } else { + o = dbObj; + } + } + return o; + } + + protected static boolean isSimpleType(Class propertyType) { + if (propertyType == null) { + return false; + } + if (propertyType.isArray()) { + return isSimpleType(propertyType.getComponentType()); + } + return SIMPLE_TYPES.contains(propertyType.getName()); + } + + /** + * Simple singleton to convert {@link ObjectId}s to their {@link String} representation. + * + * @author Oliver Gierke + */ + public static enum ObjectIdToStringConverter implements Converter { + INSTANCE; + + public String convert(ObjectId id) { + return id.toString(); + } + } + + /** + * Simple singleton to convert {@link String}s to their {@link ObjectId} representation. + * + * @author Oliver Gierke + */ + public static enum StringToObjectIdConverter implements Converter { + INSTANCE; + + public ObjectId convert(String source) { + return new ObjectId(source); + } + } + + /** + * Simple singleton to convert {@link ObjectId}s to their {@link java.math.BigInteger} representation. + * + * @author Oliver Gierke + */ + public static enum ObjectIdToBigIntegerConverter implements Converter { + INSTANCE; + + public BigInteger convert(ObjectId source) { + return new BigInteger(source.toString(), 16); + } + } + + /** + * Simple singleton to convert {@link BigInteger}s to their {@link ObjectId} representation. + * + * @author Oliver Gierke + */ + public static enum BigIntegerToIdConverter implements Converter { + INSTANCE; + + public ObjectId convert(BigInteger source) { + return new ObjectId(source.toString(16)); + } + } + + public static enum IntegerToIdConverter implements Converter { + INSTANCE; + + public ObjectId convert(Integer source) { + String s = String.valueOf(source); + StringBuffer buff = new StringBuffer(); + for (int i = 0; i < (16 - s.length()); i++) { + buff.append("0"); + } + buff.append(s); + return new ObjectId(buff.toString()); + } + } + + public static enum IdToIntegerConverter implements Converter { + INSTANCE; + + public Integer convert(ObjectId source) { + return Integer.parseInt(source.toString()); + } + } + + protected class ObjectToDBObjectConverter implements Converter { + + private final DBObject dbo; + + public ObjectToDBObjectConverter() { + this.dbo = null; + } + + public ObjectToDBObjectConverter(DBObject dbo) { + this.dbo = dbo; + } + + public DBObject convert(Object source) { + DBObject dbo; + if (source instanceof Collection) { + Collection c = (Collection) source; + dbo = (null == this.dbo ? new BasicDBList() : this.dbo); + for (Object o : c) { + if (isSimpleType(o.getClass())) { + ((BasicDBList) dbo).add(o); + } else { + ((BasicDBList) dbo).add(convert(o)); + } + } + } else if (source instanceof Map) { + Map m = (Map) source; + dbo = (null == this.dbo ? new BasicDBObject() : this.dbo); + for (Map.Entry entry : m.entrySet()) { + String key = (entry.getKey() instanceof String ? entry.getKey().toString() : conversionService.convert(entry.getKey(), String.class)); + if (isSimpleType(entry.getValue().getClass())) { + dbo.put(key, entry.getValue()); + } else { + dbo.put(key, convert(entry.getValue())); + } + } + } else { + dbo = (null == this.dbo ? new BasicDBObject() : this.dbo); + if (!fieldsByName.containsKey(source.getClass())) { + Map fields = new HashMap(); + for (Field f : source.getClass().getDeclaredFields()) { + if (!"class".equals(f.getName())) { + ReflectionUtils.makeAccessible(f); + fields.put(f.getName(), f); + } + } + fieldsByName.put(source.getClass(), fields); + } + try { + for (PropertyDescriptor descriptor : Introspector.getBeanInfo(source.getClass()).getPropertyDescriptors()) { + Field f = fieldsByName.get(source.getClass()).get(descriptor.getName()); + if (null != f && !isTransientField(f)) { + try { + Object o; + if (null != descriptor.getReadMethod()) { + o = descriptor.getReadMethod().invoke(source); + } else { + o = f.get(source); + } + if (null != o && isSimpleType(o.getClass())) { + dbo.put(descriptor.getName(), o); + } else if (null != o) { + dbo.put(descriptor.getName(), convert(o)); + } else { + // Value was NULL, skip it + } + } catch (InvocationTargetException e) { + throw new RuntimeException("Error converting " + source + " to DBObject"); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error converting " + source + " to DBObject"); + } + } + } + } catch (IntrospectionException e) { + throw new RuntimeException("Error converting " + source + " to DBObject"); + } + } + return dbo; + } + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoBeanWrapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoBeanWrapper.java similarity index 95% rename from spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoBeanWrapper.java rename to spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoBeanWrapper.java index 1c28e8664..f676332b1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoBeanWrapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoBeanWrapper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.document.mongodb; +package org.springframework.data.document.mongodb.convert; import static org.springframework.beans.PropertyAccessorFactory.*; @@ -21,6 +21,7 @@ import org.springframework.beans.BeanWrapper; import org.springframework.beans.ConfigurablePropertyAccessor; import org.springframework.beans.NotWritablePropertyException; import org.springframework.core.convert.ConversionService; +import org.springframework.data.document.mongodb.MongoPropertyDescriptors; import org.springframework.data.document.mongodb.MongoPropertyDescriptors.MongoPropertyDescriptor; import org.springframework.util.Assert; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoConverter.java similarity index 50% rename from spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoConverter.java rename to spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoConverter.java index 45c70c287..47daa9a46 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoConverter.java @@ -13,29 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.document.mongodb; +package org.springframework.data.document.mongodb.convert; import org.bson.types.ObjectId; +import org.springframework.data.document.mongodb.MongoReader; +import org.springframework.data.document.mongodb.MongoWriter; public interface MongoConverter extends MongoWriter, MongoReader { - /** - * Converts the given {@link ObjectId} to the given target type. - * - * @param the actual type to create - * @param id the source {@link ObjectId} - * @param targetType the target type to convert the {@link ObjectId} to - * @return - */ - public T convertObjectId(ObjectId id, Class targetType); - - - /** - * Returns the {@link ObjectId} instance for the given id. - * - * @param id - * @return - */ - public ObjectId convertObjectId(Object id); + /** + * Converts the given {@link ObjectId} to the given target type. + * + * @param the actual type to create + * @param id the source {@link ObjectId} + * @param targetType the target type to convert the {@link ObjectId} to + * @return + */ + public T convertObjectId(ObjectId id, Class targetType); + + + /** + * Returns the {@link ObjectId} instance for the given id. + * + * @param id + * @return + */ + public ObjectId convertObjectId(Object id); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimpleMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimpleMongoConverter.java new file mode 100644 index 000000000..84f4529f6 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimpleMongoConverter.java @@ -0,0 +1,546 @@ +/* + * Copyright 2010-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.document.mongodb.convert; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.DBRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bson.types.CodeWScope; +import org.bson.types.ObjectId; +import org.springframework.beans.BeanUtils; +import org.springframework.core.CollectionFactory; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.document.mongodb.MongoPropertyDescriptors.MongoPropertyDescriptor; +import org.springframework.util.Assert; +import org.springframework.util.comparator.CompoundComparator; + +import java.lang.reflect.*; +import java.math.BigInteger; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Pattern; + +/** + * Basic {@link MongoConverter} implementation to convert between domain classes and {@link DBObject}s. + * + * @author Mark Pollack + * @author Thomas Risberg + * @author Oliver Gierke + */ +public class SimpleMongoConverter implements MongoConverter { + + private static final Log LOG = LogFactory.getLog(SimpleMongoConverter.class); + private static final Set SIMPLE_TYPES; + + static { + Set basics = new HashSet(); + basics.add(boolean.class.getName()); + basics.add(long.class.getName()); + basics.add(short.class.getName()); + basics.add(int.class.getName()); + basics.add(byte.class.getName()); + basics.add(float.class.getName()); + basics.add(double.class.getName()); + basics.add(char.class.getName()); + basics.add(Boolean.class.getName()); + basics.add(Long.class.getName()); + basics.add(Short.class.getName()); + basics.add(Integer.class.getName()); + basics.add(Byte.class.getName()); + basics.add(Float.class.getName()); + basics.add(Double.class.getName()); + basics.add(Character.class.getName()); + basics.add(String.class.getName()); + basics.add(java.util.Date.class.getName()); + // basics.add(Time.class.getName()); + // basics.add(Timestamp.class.getName()); + // basics.add(java.sql.Date.class.getName()); + // basics.add(BigDecimal.class.getName()); + // basics.add(BigInteger.class.getName()); + basics.add(Locale.class.getName()); + // basics.add(Calendar.class.getName()); + // basics.add(GregorianCalendar.class.getName()); + // basics.add(java.util.Currency.class.getName()); + // basics.add(TimeZone.class.getName()); + // basics.add(Object.class.getName()); + basics.add(Class.class.getName()); + // basics.add(byte[].class.getName()); + // basics.add(Byte[].class.getName()); + // basics.add(char[].class.getName()); + // basics.add(Character[].class.getName()); + // basics.add(Blob.class.getName()); + // basics.add(Clob.class.getName()); + // basics.add(Serializable.class.getName()); + // basics.add(URI.class.getName()); + // basics.add(URL.class.getName()); + basics.add(DBRef.class.getName()); + basics.add(Pattern.class.getName()); + basics.add(CodeWScope.class.getName()); + basics.add(ObjectId.class.getName()); + // TODO check on enums.. + basics.add(Enum.class.getName()); + SIMPLE_TYPES = Collections.unmodifiableSet(basics); + } + + private final GenericConversionService conversionService; + + /** + * Creates a {@link SimpleMongoConverter}. + */ + public SimpleMongoConverter() { + this.conversionService = ConversionServiceFactory.createDefaultConversionService(); + initializeConverters(); + } + + /** + * Creates a new {@link SimpleMongoConverter} for the given {@link ConversionService}. + * + * @param conversionService + */ + public SimpleMongoConverter(GenericConversionService conversionService) { + Assert.notNull(conversionService); + this.conversionService = conversionService; + initializeConverters(); + } + + /** + * Initializes additional converters that handle {@link ObjectId} conversion. Will register converters for supported + * id types if none are registered for those conversion already. {@link GenericConversionService} is configured. + */ + protected void initializeConverters() { + + if (!conversionService.canConvert(ObjectId.class, String.class)) { + conversionService.addConverter(ObjectIdToStringConverter.INSTANCE); + conversionService.addConverter(StringToObjectIdConverter.INSTANCE); + } + + if (!conversionService.canConvert(ObjectId.class, BigInteger.class)) { + conversionService.addConverter(ObjectIdToBigIntegerConverter.INSTANCE); + conversionService.addConverter(BigIntegerToIdConverter.INSTANCE); + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.MongoWriter#write(java.lang.Object, com.mongodb.DBObject) + */ + @SuppressWarnings("rawtypes") + public void write(Object obj, DBObject dbo) { + + MongoBeanWrapper beanWrapper = createWrapper(obj, false); + for (MongoPropertyDescriptor descriptor : beanWrapper.getDescriptors()) { + if (descriptor.isMappable()) { + Object value = beanWrapper.getValue(descriptor); + + if (value == null) { + continue; + } + + String keyToUse = descriptor.getKeyToMap(); + // TODO validate Enums... + if (descriptor.isEnum()) { + writeValue(dbo, keyToUse, ((Enum) value).name()); + } else if (descriptor.isIdProperty() && descriptor.isOfIdType()) { + if (value instanceof String && ObjectId.isValid((String) value)) { + try { + writeValue(dbo, keyToUse, conversionService.convert(value, ObjectId.class)); + } catch (ConversionFailedException iae) { + LOG.warn("Unable to convert the String " + value + " to an ObjectId"); + writeValue(dbo, keyToUse, value); + } + } else { + // we can't convert this id - use as is + writeValue(dbo, keyToUse, value); + } + } else { + writeValue(dbo, keyToUse, value); + } + } else { + if (!"class".equals(descriptor.getName())) { + LOG.warn("Unable to map property " + descriptor.getName() + ". Skipping."); + } + } + } + } + + /** + * Writes the given value to the given {@link DBObject}. Will skip {@literal null} values. + * + * @param dbo + * @param keyToUse + * @param value + */ + private void writeValue(DBObject dbo, String keyToUse, Object value) { + + if (!isSimpleType(value.getClass())) { + writeCompoundValue(dbo, keyToUse, value); + } else { + dbo.put(keyToUse, value); + } + } + + /** + * Writes the given {@link CompoundComparator} value to the given {@link DBObject}. + * + * @param dbo + * @param keyToUse + * @param value + */ + @SuppressWarnings("unchecked") + private void writeCompoundValue(DBObject dbo, String keyToUse, Object value) { + if (value instanceof Map) { + writeMap(dbo, keyToUse, (Map) value); + return; + } + if (value instanceof Collection) { + // Should write a collection! + writeArray(dbo, keyToUse, ((Collection) value).toArray()); + return; + } + if (value instanceof Object[]) { + // Should write an array! + writeArray(dbo, keyToUse, (Object[]) value); + return; + } + DBObject nestedDbo = new BasicDBObject(); + write(value, nestedDbo); + dbo.put(keyToUse, nestedDbo); + + } + + /** + * Writes the given {@link Map} to the given {@link DBObject}. + * + * @param dbo + * @param mapKey + * @param map + */ + protected void writeMap(DBObject dbo, String mapKey, Map map) { + // TODO support non-string based keys as long as there is a Spring Converter obj->string and (optionally) + // string->obj + DBObject dboToPopulate = null; + + // TODO - Does that make sense? If we create a new object here it's content will never make it out of this + // method + if (mapKey != null) { + dboToPopulate = new BasicDBObject(); + } else { + dboToPopulate = dbo; + } + if (map != null) { + for (Entry entry : map.entrySet()) { + + Object entryValue = entry.getValue(); + String entryKey = entry.getKey(); + + if (!isSimpleType(entryValue.getClass())) { + writeCompoundValue(dboToPopulate, entryKey, entryValue); + } else { + dboToPopulate.put(entryKey, entryValue); + } + } + dbo.put(mapKey, dboToPopulate); + } + } + + /** + * Writes the given array to the given {@link DBObject}. + * + * @param dbo + * @param keyToUse + * @param array + */ + protected void writeArray(DBObject dbo, String keyToUse, Object[] array) { + // TODO + Object[] dboValues; + if (array != null) { + dboValues = new Object[array.length]; + int i = 0; + for (Object o : array) { + if (!isSimpleType(o.getClass())) { + DBObject dboValue = new BasicDBObject(); + write(o, dboValue); + dboValues[i] = dboValue; + } else { + dboValues[i] = o; + } + i++; + } + dbo.put(keyToUse, dboValues); + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.MongoReader#read(java.lang.Class, com.mongodb.DBObject) + */ + public S read(Class clazz, DBObject source) { + + if (source == null) { + return null; + } + + Assert.notNull(clazz, "Mapped class was not specified"); + S target = BeanUtils.instantiateClass(clazz); + MongoBeanWrapper bw = new MongoBeanWrapper(target, conversionService, true); + + for (MongoPropertyDescriptor descriptor : bw.getDescriptors()) { + String keyToUse = descriptor.getKeyToMap(); + if (source.containsField(keyToUse)) { + if (descriptor.isMappable()) { + Object value = source.get(keyToUse); + if (!isSimpleType(value.getClass())) { + if (value instanceof Object[]) { + bw.setValue(descriptor, readCollection(descriptor, Arrays.asList((Object[]) value)) + .toArray()); + } else if (value instanceof BasicDBList) { + bw.setValue(descriptor, readCollection(descriptor, (BasicDBList) value)); + } else if (value instanceof DBObject) { + bw.setValue(descriptor, readCompoundValue(descriptor, (DBObject) value)); + } else { + LOG.warn("Unable to map compound DBObject field " + keyToUse + " to property " + + descriptor.getName() + + ". The field value should have been a 'DBObject.class' but was " + + value.getClass().getName()); + } + } else { + bw.setValue(descriptor, value); + } + } else { + LOG.warn("Unable to map DBObject field " + keyToUse + " to property " + descriptor.getName() + + ". Skipping."); + } + } + } + + return target; + } + + /** + * Reads the given collection values (that are {@link DBObject}s potentially) into a {@link Collection} of domain + * objects. + * + * @param descriptor + * @param values + * @return + */ + private Collection readCollection(MongoPropertyDescriptor descriptor, Collection values) { + + Class targetCollectionType = descriptor.getPropertyType(); + boolean targetIsArray = targetCollectionType.isArray(); + + @SuppressWarnings("unchecked") + Collection result = targetIsArray ? new ArrayList(values.size()) : CollectionFactory + .createCollection(targetCollectionType, values.size()); + + for (Object o : values) { + if (o instanceof DBObject) { + Class type; + if (targetIsArray) { + type = targetCollectionType.getComponentType(); + } else { + type = getGenericParameters(descriptor.getTypeToSet()).get(0); + } + result.add(read(type, (DBObject) o)); + } else { + result.add(o); + } + } + + return result; + } + + /** + * Reads a compound value from the given {@link DBObject} for the given property. + * + * @param pd + * @param dbo + * @return + */ + private Object readCompoundValue(MongoPropertyDescriptor pd, DBObject dbo) { + + Assert.isTrue(!pd.isCollection(), "Collections not supported!"); + + if (pd.isMap()) { + return readMap(pd, dbo, getGenericParameters(pd.getTypeToSet()).get(1)); + } else { + return read(pd.getPropertyType(), dbo); + } + } + + /** + * Create a {@link Map} instance. Will return a {@link HashMap} by default. Subclasses might want to override this + * method to use a custom {@link Map} implementation. + * + * @return + */ + protected Map createMap() { + return new HashMap(); + } + + /** + * Reads every key/value pair from the {@link DBObject} into a {@link Map} instance. + * + * @param pd + * @param dbo + * @param targetType + * @return + */ + protected Map readMap(MongoPropertyDescriptor pd, DBObject dbo, Class targetType) { + Map map = createMap(); + for (String key : dbo.keySet()) { + Object value = dbo.get(key); + if (!isSimpleType(value.getClass())) { + map.put(key, read(targetType, (DBObject) value)); + // Can do some reflection tricks here - + // throw new RuntimeException("User types not supported yet as values for Maps"); + } else { + map.put(key, conversionService.convert(value, targetType)); + } + } + return map; + } + + protected static boolean isSimpleType(Class propertyType) { + if (propertyType == null) { + return false; + } + if (propertyType.isArray()) { + return isSimpleType(propertyType.getComponentType()); + } + return SIMPLE_TYPES.contains(propertyType.getName()); + } + + /** + * Callback to allow customizing creation of a {@link MongoBeanWrapper}. + * + * @param target the target object to wrap + * @param fieldAccess whether to use field access or property access + * @return + */ + protected MongoBeanWrapper createWrapper(Object target, boolean fieldAccess) { + + return new MongoBeanWrapper(target, conversionService, fieldAccess); + } + + public List> getGenericParameters(Type genericParameterType) { + + List> actualGenericParameterTypes = new ArrayList>(); + + if (genericParameterType instanceof ParameterizedType) { + ParameterizedType aType = (ParameterizedType) genericParameterType; + Type[] parameterArgTypes = aType.getActualTypeArguments(); + for (Type parameterArgType : parameterArgTypes) { + if (parameterArgType instanceof GenericArrayType) { + Class arrayType = (Class) ((GenericArrayType) parameterArgType).getGenericComponentType(); + actualGenericParameterTypes.add(Array.newInstance(arrayType, 0).getClass()); + } else { + if (parameterArgType instanceof ParameterizedType) { + ParameterizedType paramTypeArgs = (ParameterizedType) parameterArgType; + actualGenericParameterTypes.add((Class) paramTypeArgs.getRawType()); + } else { + if (parameterArgType instanceof TypeVariable) { + throw new RuntimeException("Can not map " + ((TypeVariable) parameterArgType).getName()); + } else { + if (parameterArgType instanceof Class) { + actualGenericParameterTypes.add((Class) parameterArgType); + } else { + throw new RuntimeException("Can not map " + parameterArgType); + } + } + } + } + } + } + + return actualGenericParameterTypes; + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.convert.MongoConverter#convertObjectId(org.bson.types.ObjectId, java.lang.Class) + */ + public T convertObjectId(ObjectId id, Class targetType) { + return conversionService.convert(id, targetType); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.convert.MongoConverter#convertObjectId(java.lang.Object) + */ + public ObjectId convertObjectId(Object id) { + return conversionService.convert(id, ObjectId.class); + } + + /** + * Simple singleton to convert {@link ObjectId}s to their {@link String} representation. + * + * @author Oliver Gierke + */ + public static enum ObjectIdToStringConverter implements Converter { + INSTANCE; + + public String convert(ObjectId id) { + return id.toString(); + } + } + + /** + * Simple singleton to convert {@link String}s to their {@link ObjectId} representation. + * + * @author Oliver Gierke + */ + public static enum StringToObjectIdConverter implements Converter { + INSTANCE; + + public ObjectId convert(String source) { + return new ObjectId(source); + } + } + + /** + * Simple singleton to convert {@link ObjectId}s to their {@link BigInteger} representation. + * + * @author Oliver Gierke + */ + public static enum ObjectIdToBigIntegerConverter implements Converter { + INSTANCE; + + public BigInteger convert(ObjectId source) { + return new BigInteger(source.toString(), 16); + } + } + + /** + * Simple singleton to convert {@link BigInteger}s to their {@link ObjectId} representation. + * + * @author Oliver Gierke + */ + public static enum BigIntegerToIdConverter implements Converter { + INSTANCE; + + public ObjectId convert(BigInteger source) { + return new ObjectId(source.toString(16)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimplePojoDBObjectConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimplePojoDBObjectConverter.java new file mode 100644 index 000000000..79394b8e2 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimplePojoDBObjectConverter.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.convert; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.DBRef; +import org.bson.types.CodeWScope; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.mapping.annotation.Transient; +import org.springframework.data.mapping.reflect.ReflectionUtils; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; + +/** + * @author Jon Brisbin + */ +public class SimplePojoDBObjectConverter { + + protected static final Set SIMPLE_TYPES; + + static { + Set basics = new HashSet(); + basics.add(boolean.class.getName()); + basics.add(long.class.getName()); + basics.add(short.class.getName()); + basics.add(int.class.getName()); + basics.add(byte.class.getName()); + basics.add(float.class.getName()); + basics.add(double.class.getName()); + basics.add(char.class.getName()); + basics.add(Boolean.class.getName()); + basics.add(Long.class.getName()); + basics.add(Short.class.getName()); + basics.add(Integer.class.getName()); + basics.add(Byte.class.getName()); + basics.add(Float.class.getName()); + basics.add(Double.class.getName()); + basics.add(Character.class.getName()); + basics.add(String.class.getName()); + basics.add(java.util.Date.class.getName()); + basics.add(Locale.class.getName()); + basics.add(Class.class.getName()); + basics.add(DBRef.class.getName()); + basics.add(Pattern.class.getName()); + basics.add(CodeWScope.class.getName()); + basics.add(ObjectId.class.getName()); + // TODO check on enums.. + basics.add(Enum.class.getName()); + SIMPLE_TYPES = Collections.unmodifiableSet(basics); + } + + protected static final ConcurrentMap, Map> fieldsByName = new ConcurrentHashMap, Map>(); + + protected GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); + + public SimplePojoDBObjectConverter() { + } + + public GenericConversionService getConversionService() { + return conversionService; + } + + public void setConversionService(GenericConversionService conversionService) { + this.conversionService = conversionService; + } + + public static boolean isSimpleType(Class propertyType) { + if (propertyType == null) { + return false; + } + if (propertyType.isArray()) { + return isSimpleType(propertyType.getComponentType()); + } + return SIMPLE_TYPES.contains(propertyType.getName()); + } + + public static boolean isTransientField(Field f) { + return (Modifier.isTransient(f.getModifiers()) || null != f.getAnnotation(Transient.class) || null != f.getAnnotation(Autowired.class)); + } + + public DBObject convert(Object source, DBObject existing) throws Exception { + DBObject dbo; + if (source instanceof Collection) { + Collection c = (Collection) source; + dbo = (null == existing ? new BasicDBList() : existing); + for (Object o : c) { + if (isSimpleType(o.getClass())) { + ((BasicDBList) dbo).add(o); + } else { + ((BasicDBList) dbo).add(convert(o, null)); + } + } + } else if (source instanceof Map) { + Map m = (Map) source; + dbo = (null == existing ? new BasicDBObject() : existing); + for (Map.Entry entry : m.entrySet()) { + String key = (entry.getKey() instanceof String ? entry.getKey().toString() : conversionService.convert(entry.getKey(), String.class)); + if (isSimpleType(entry.getValue().getClass())) { + dbo.put(key, entry.getValue()); + } else { + dbo.put(key, convert(entry.getValue(), null)); + } + } + } else { + dbo = (null == existing ? new BasicDBObject() : existing); + if (!fieldsByName.containsKey(source.getClass())) { + Map fields = new HashMap(); + for (Field f : source.getClass().getDeclaredFields()) { + if (!"class".equals(f.getName())) { + ReflectionUtils.makeAccessible(f); + fields.put(f.getName(), f); + } + } + fieldsByName.put(source.getClass(), fields); + } + try { + for (PropertyDescriptor descriptor : Introspector.getBeanInfo(source.getClass()).getPropertyDescriptors()) { + Field f = fieldsByName.get(source.getClass()).get(descriptor.getName()); + if (null != f && !isTransientField(f)) { + try { + Object o; + if (null != descriptor.getReadMethod()) { + o = descriptor.getReadMethod().invoke(source); + } else { + o = f.get(source); + } + if (null != o && isSimpleType(o.getClass())) { + dbo.put(descriptor.getName(), o); + } else if (null != o) { + dbo.put(descriptor.getName(), convert(o, null)); + } else { + // Value was NULL, skip it + } + } catch (InvocationTargetException e) { + throw new RuntimeException("Error converting " + source + " to DBObject"); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error converting " + source + " to DBObject"); + } + } + } + } catch (IntrospectionException e) { + throw new RuntimeException("Error converting " + source + " to DBObject"); + } + } + return dbo; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/CompoundIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/CompoundIndex.java new file mode 100644 index 000000000..e5304f450 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/CompoundIndex.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.index; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Jon Brisbin + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CompoundIndex { + + String def(); + + boolean unique() default false; + + boolean sparse() default false; + + boolean dropDups() default true; + + String name() default ""; + + String collection() default ""; + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/CompoundIndexes.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/CompoundIndexes.java new file mode 100644 index 000000000..72ac63adb --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/CompoundIndexes.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.index; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Jon Brisbin + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CompoundIndexes { + + CompoundIndex[] value(); + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexDefinition.java new file mode 100644 index 000000000..3165ed9e8 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexDefinition.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.index; + +/** + * @author Jon Brisbin + */ +public interface IndexDefinition { + + T getIndexDefinition(); + + T getIndexOptions(); + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexDirection.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexDirection.java new file mode 100644 index 000000000..45fcd503f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexDirection.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.index; + +/** + * @author Jon Brisbin + */ +public enum IndexDirection { + ASCENDING, + DESCENDING; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexPredicate.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexPredicate.java new file mode 100644 index 000000000..41f64cc65 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/IndexPredicate.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.index; + +/** + * @author Jon Brisbin + */ +public abstract class IndexPredicate { + + private String name; + private IndexDirection direction = IndexDirection.ASCENDING; + private boolean unique = false; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public IndexDirection getDirection() { + return direction; + } + + public void setDirection(IndexDirection direction) { + this.direction = direction; + } + + public boolean isUnique() { + return unique; + } + + public void setUnique(boolean unique) { + this.unique = unique; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/Indexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/Indexed.java new file mode 100644 index 000000000..909fb292a --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/index/Indexed.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.index; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Jon Brisbin + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Indexed { + boolean unique() default false; + + IndexDirection direction() default IndexDirection.ASCENDING; + + boolean sparse() default false; + + boolean dropDups() default true; + + String name() default ""; + + String collection() default ""; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingException.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingException.java new file mode 100644 index 000000000..565044d4d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +/** + * @author Jon Brisbin + */ +public class MappingException extends Throwable { + + private final Object source; + + public MappingException(Throwable throwable, Object source) { + super(String.format("Error encountered mapping object: %s", source), throwable); + this.source = source; + } + + public Object getSource() { + return source; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingIntrospector.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingIntrospector.java new file mode 100644 index 000000000..02cd1aac5 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingIntrospector.java @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import com.mongodb.DBRef; +import com.sun.org.apache.xalan.internal.extensions.ExpressionContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bson.types.CodeWScope; +import org.bson.types.ObjectId; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.data.mapping.annotation.*; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.Assert; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.math.BigInteger; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; + +/** + * @author Jon Brisbin + */ +public class MappingIntrospector { + + private static final Log log = LogFactory.getLog(MappingIntrospector.class); + private static final ConcurrentMap, MappingIntrospector> introspectors = new ConcurrentHashMap, MappingIntrospector>(); + private static final Set SIMPLE_TYPES; + + static { + Set basics = new HashSet(); + basics.add(boolean.class.getName()); + basics.add(long.class.getName()); + basics.add(short.class.getName()); + basics.add(int.class.getName()); + basics.add(byte.class.getName()); + basics.add(float.class.getName()); + basics.add(double.class.getName()); + basics.add(char.class.getName()); + basics.add(Boolean.class.getName()); + basics.add(Long.class.getName()); + basics.add(Short.class.getName()); + basics.add(Integer.class.getName()); + basics.add(Byte.class.getName()); + basics.add(Float.class.getName()); + basics.add(Double.class.getName()); + basics.add(Character.class.getName()); + basics.add(String.class.getName()); + basics.add(java.util.Date.class.getName()); + basics.add(Locale.class.getName()); + basics.add(Class.class.getName()); + basics.add(DBRef.class.getName()); + basics.add(Pattern.class.getName()); + basics.add(CodeWScope.class.getName()); + basics.add(ObjectId.class.getName()); + // TODO check on enums.. + basics.add(Enum.class.getName()); + SIMPLE_TYPES = Collections.unmodifiableSet(basics); + } + + private final Class clazz; + private final BeanInfo beanInfo; + private final Map properties; + private final Map fields; + private final Set associations; + private final Map setters; + private final Map getters; + private Field idField = null; + private List ignoredProperties = new ArrayList() {{ + add("class"); + }}; + private SpelExpressionParser parser = new SpelExpressionParser(); + private Map expressions = new HashMap(); + private PreferredConstructor preferredConstructor = null; + + @SuppressWarnings({"unchecked"}) + private MappingIntrospector(Class clazz) throws MappingException { + this.clazz = clazz; + try { + this.beanInfo = Introspector.getBeanInfo(clazz); + } catch (IntrospectionException e) { + throw new MappingException(e, clazz); + } + Map properties = new HashMap(); + Map fields = new HashMap(); + Set associations = new HashSet(); + Map setters = new HashMap(); + Map getters = new HashMap(); + for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) { + String name = descriptor.getName(); + if (!ignoredProperties.contains(name)) { + try { + Field fld = clazz.getDeclaredField(name); + Class fldType = fld.getType(); + fld.setAccessible(true); + if (!isTransientField(fld)) { + if (fld.isAnnotationPresent(Id.class)) { + if (null != idField) { + throw new IllegalStateException("You cannot have two fields in a domain object annotated with Id! " + clazz); + } + idField = fld; + continue; + } else if (null == idField && fldType.equals(ObjectId.class)) { + // Respect fields of the MongoDB ObjectId type + idField = fld; + continue; + } else if (null == idField + && (fldType.equals(String.class) || fldType.equals(BigInteger.class)) + && ("id".equals(name) || "_id".equals(name))) { + // Strings and BigIntegers named "id"|"_id" are also valid ID fields + idField = fld; + continue; + } else if (fld.isAnnotationPresent(OneToMany.class) + || fld.isAnnotationPresent(OneToOne.class) + || fld.isAnnotationPresent(ManyToMany.class)) { + associations.add(new Association(descriptor, fld)); + continue; + } else if (fld.isAnnotationPresent(Value.class)) { + // @Value fields are evaluated at runtime and are the same transient fields + continue; + } + fields.put(name, fld); + properties.put(name, descriptor); + setters.put(name, descriptor.getWriteMethod()); + getters.put(name, descriptor.getReadMethod()); + if (fld.isAnnotationPresent(Value.class)) { + expressions.put(name, parser.parseExpression(fld.getAnnotation(Value.class).value())); + } + } + } catch (NoSuchFieldException e) { + log.warn(e.getMessage()); + } + } + } + this.properties = Collections.unmodifiableMap(properties); + this.fields = Collections.unmodifiableMap(fields); + this.associations = Collections.unmodifiableSet(associations); + this.setters = Collections.unmodifiableMap(setters); + this.getters = Collections.unmodifiableMap(getters); + + // Find the right constructor + for (Constructor constructor : clazz.getConstructors()) { + if (constructor.getParameterTypes().length != 0) { + // Non-no-arg constructor + if (null == preferredConstructor) { + String[] paramNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(constructor); + Type[] paramTypes = constructor.getGenericParameterTypes(); + Class[] paramClassTypes = new Class[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + Class targetType; + if (paramTypes[i] instanceof ParameterizedType) { + ParameterizedType ptype = (ParameterizedType) paramTypes[i]; + Type[] types = ptype.getActualTypeArguments(); + if (types.length == 1) { + targetType = (Class) types[0]; + } else { + targetType = (Class) ptype.getRawType(); + } + } else { + targetType = (Class) paramTypes[i]; + } + paramClassTypes[i] = targetType; + } + preferredConstructor = new PreferredConstructor((Constructor) constructor, paramNames, paramClassTypes); + } + } + } + } + + + @SuppressWarnings({"unchecked"}) + public static MappingIntrospector getInstance(Class clazz) throws MappingException { + MappingIntrospector introspector = (MappingIntrospector) introspectors.get(clazz); + if (null == introspector) { + introspector = new MappingIntrospector(clazz); + introspectors.put(clazz, introspector); + } + return introspector; + } + + public Class getTargetClass() { + return clazz; + } + + public Field getField(String name) { + return fields.get(name); + } + + public PropertyDescriptor getPropertyDescriptor(String name) { + return properties.get(name); + } + + public boolean isMappable() { + return (null != clazz.getAnnotation(Persistent.class)); + } + + public Field getIdField() { + return idField; + } + + public T createInstance() throws MappingException { + return createInstance(null); + } + + public T createInstance(ParameterValueProvider provider) throws MappingException { + try { + if (null == preferredConstructor || null == provider) { + return clazz.newInstance(); + } else { + List params = new LinkedList(); + for (ConstructorParameter p : preferredConstructor.parameters) { + Expression x = null; + Value v = p.getValueAnnotation(); + if (null != v) { + x = parser.parseExpression(v.value()); + } + params.add(provider.getParameterValue(p.name, p.type, x)); + } + return preferredConstructor.constructor.newInstance(params.toArray()); + } + } catch (InvocationTargetException e) { + throw new MappingException(e, clazz); + } catch (InstantiationException e) { + throw new MappingException(e, clazz); + } catch (IllegalAccessException e) { + throw new MappingException(e, clazz); + } + } + + public Object getFieldValue(PropertyDescriptor descriptor, Object from) throws MappingException { + try { + Method getter = descriptor.getReadMethod(); + if (null != getter) { + return getter.invoke(from); + } else { + Field f = fields.get(descriptor.getName()); + return f.get(from); + } + } catch (IllegalAccessException e) { + throw new MappingException(e, from); + } catch (InvocationTargetException e) { + throw new MappingException(e, from); + } + } + + public Object getFieldValue(String name, Object from) throws MappingException { + PropertyDescriptor descriptor = properties.get(name); + if (null != descriptor) { + return getFieldValue(descriptor, from); + } + return null; + } + + public void setValue(String name, Object on, Object value) throws MappingException { + Field f = fields.get(name); + if (null != f) { + Method setter = setters.get(name); + try { + if (null != setter) { + setter.invoke(on, value); + } else { + f.set(on, value); + } + } catch (IllegalAccessException e) { + throw new MappingException(e, value); + } catch (InvocationTargetException e) { + throw new MappingException(e, value); + } + } + } + + public void maybeAutowire(Object obj, ApplicationContext applicationContext, boolean autowire) throws MappingException { + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(obj); + evaluationContext.setBeanResolver(new BeanFactoryResolver(applicationContext)); + + if (autowire) { + applicationContext.getAutowireCapableBeanFactory().autowireBean(obj); + if (obj instanceof InitializingBean) { + try { + ((InitializingBean) obj).afterPropertiesSet(); + } catch (Exception e) { + throw new MappingException(e, obj); + } + } + } + + for (Map.Entry entry : expressions.entrySet()) { + setValue(entry.getKey(), obj, entry.getValue().getValue(evaluationContext)); + } + } + + public void doWithProperties(PropertyHandler handler) { + for (Map.Entry entry : properties.entrySet()) { + String name = entry.getKey(); + handler.doWithProperty(entry.getValue(), fields.get(name), expressions.get(name)); + } + } + + public void doWithAssociations(AssociationHandler handler) { + for (Association association : associations) { + handler.doWithAssociation(association); + } + } + + public static boolean isSimpleType(Class propertyType) { + if (propertyType == null) { + return false; + } + if (propertyType.isArray()) { + return isSimpleType(propertyType.getComponentType()); + } + return SIMPLE_TYPES.contains(propertyType.getName()); + } + + public static boolean isTransientField(Field f) { + return (Modifier.isTransient(f.getModifiers()) || null != f.getAnnotation(Transient.class) || null != f.getAnnotation(Autowired.class)); + } + + public static class Association { + + private final PropertyDescriptor descriptor; + private final Field field; + + public Association(PropertyDescriptor descriptor, Field field) { + this.descriptor = descriptor; + this.field = field; + } + + public PropertyDescriptor getDescriptor() { + return descriptor; + } + + public Field getField() { + return field; + } + + } + + private class PreferredConstructor { + + Constructor constructor; + ConstructorParameter[] parameters; + + @SuppressWarnings({"unchecked"}) + public PreferredConstructor(Constructor constructor, String[] names, Class[] types) { + this.constructor = constructor; + Annotation[][] annos = constructor.getParameterAnnotations(); + parameters = new ConstructorParameter[names.length]; + for (int i = 0; i < names.length; i++) { + parameters[i] = new ConstructorParameter(names[i], types[i], annos[i]); + } + } + } + + private class ConstructorParameter { + String name; + Class type; + Annotation[] annotations; + + private ConstructorParameter(String name, Class type, Annotation[] annotations) { + this.name = name; + this.type = type; + this.annotations = annotations; + } + + private Value getValueAnnotation() { + for (Annotation anno : annotations) { + if (anno instanceof Value) { + return (Value) anno; + } + } + return null; + } + } + + public interface AssociationHandler { + void doWithAssociation(Association association); + } + + public interface PropertyHandler { + void doWithProperty(PropertyDescriptor descriptor, Field field, Expression spelExpr); + } + + public interface ParameterValueProvider { + T getParameterValue(String name, Class type, Expression spelExpr); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoClassMapping.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoClassMapping.java new file mode 100644 index 000000000..0665bdb77 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoClassMapping.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import org.springframework.data.mapping.model.AbstractClassMapping; +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +public class MongoClassMapping extends AbstractClassMapping { + + public MongoClassMapping(PersistentEntity entity, MappingContext context) { + super(entity, context); + } + + @Override + public MongoCollection getMappedForm() { + return (MongoCollection) context.getMappingFactory().createMappedForm(entity); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoCollection.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoCollection.java new file mode 100644 index 000000000..dd7599986 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoCollection.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +/** + * @author Jon Brisbin + */ +public class MongoCollection { + + private String name; + + public MongoCollection(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationStrategy.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationStrategy.java new file mode 100644 index 000000000..18c008f3e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationStrategy.java @@ -0,0 +1,457 @@ +package org.springframework.data.document.mongodb.mapping; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.mongodb.MongoException; +import com.mongodb.util.JSON; +import org.springframework.dao.DataAccessException; +import org.springframework.data.document.mongodb.index.CompoundIndex; +import org.springframework.data.document.mongodb.index.CompoundIndexes; +import org.springframework.data.document.mongodb.index.IndexDirection; +import org.springframework.data.document.mongodb.index.Indexed; +import org.springframework.data.document.mongodb.CollectionCallback; +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.mapping.annotation.*; +import org.springframework.data.mapping.model.*; +import org.springframework.data.mapping.model.types.Association; +import org.springframework.data.mapping.reflect.ClassPropertyFetcher; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.Assert; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author J. Brisbin + */ +public class MongoMappingConfigurationStrategy implements MappingConfigurationStrategy { + + protected MongoTemplate mongo; + protected MongoMappingFactory mappingFactory; + protected SpelExpressionParser expressionParser = new SpelExpressionParser(); + protected TemplateParserContext templateParserContext = new TemplateParserContext(); + protected Map, PersistenceDescriptor> descriptors = new ConcurrentHashMap, PersistenceDescriptor>(); + protected Map, Set> owners = new LinkedHashMap, Set>(); + + public MongoMappingConfigurationStrategy(MongoTemplate mongo) { + this.mongo = mongo; + } + + public MongoMappingConfigurationStrategy(MongoTemplate mongo, MongoMappingFactory mappingFactory) { + this.mongo = mongo; + this.mappingFactory = mappingFactory; + } + + public SpelExpressionParser getExpressionParser() { + return expressionParser; + } + + public void setExpressionParser(SpelExpressionParser expressionParser) { + this.expressionParser = expressionParser; + } + + public TemplateParserContext getTemplateParserContext() { + return templateParserContext; + } + + public void setTemplateParserContext(TemplateParserContext templateParserContext) { + this.templateParserContext = templateParserContext; + } + + @SuppressWarnings({"unchecked"}) + public boolean isPersistentEntity(Class aClass) { + if (null != aClass) { + return (null != aClass.getAnnotation(Persistent.class)); + } + return false; + } + + public List getPersistentProperties(Class aClass, MappingContext mappingContext) { + return getPersistentProperties(aClass, mappingContext, null); + } + + public List getPersistentProperties(Class aClass, + MappingContext mappingContext, + ClassMapping classMapping) { + PersistenceDescriptor pd = getPersistenceDescriptor(aClass, mappingContext, classMapping); + return (null != pd ? pd.getProperties() : null); + } + + public PersistentProperty getIdentity(Class aClass, MappingContext mappingContext) { + PersistenceDescriptor idPd = getPersistenceDescriptor(aClass, mappingContext, null); + return (null != idPd ? idPd.getIdProperty() : null); + } + + public IdentityMapping getDefaultIdentityMapping(final ClassMapping classMapping) { + final PersistentProperty prop = getPersistenceDescriptor(classMapping.getEntity().getJavaClass(), + classMapping.getEntity().getMappingContext(), + classMapping).getIdProperty(); + + return new IdentityMapping() { + public String[] getIdentifierName() { + return new String[]{prop.getName()}; + } + + public ClassMapping getClassMapping() { + return classMapping; + } + + public Object getMappedForm() { + return classMapping.getMappedForm(); + } + }; + } + + public Set getOwningEntities(Class aClass, MappingContext mappingContext) { + return owners.get(aClass); + } + + protected PersistenceDescriptor getPersistenceDescriptor(Class javaClass, + MappingContext context, + ClassMapping mapping) { + PersistenceDescriptor descriptor = descriptors.get(javaClass); + if (null == descriptor) { + ClassPropertyFetcher fetcher = ClassPropertyFetcher.forClass(javaClass); + PersistentEntity entity = getPersistentEntity(javaClass, context, mapping); + + String collection = javaClass.getSimpleName().toLowerCase(); + for (Annotation anno : javaClass.getAnnotations()) { + if (anno instanceof Persistent) { + + } else if (anno instanceof CompoundIndexes) { + CompoundIndexes idxs = (CompoundIndexes) anno; + for (CompoundIndex idx : idxs.value()) { + String idxColl = collection; + if (!"".equals(idx.collection())) { + idxColl = idx.collection(); + } + String name = null; + if (!"".equals(idx.name())) { + name = idx.name(); + } + ensureIndex(idxColl, name, idx.def(), null, idx.unique(), idx.dropDups(), idx.sparse()); + } + } + } + + EvaluationContext elContext = createElContext(); + elContext.setVariable("class", javaClass); + + PersistentProperty id = extractIdProperty(entity, context, elContext); + + List properties = new LinkedList(); + for (PropertyDescriptor propertyDescriptor : fetcher.getPropertyDescriptors()) { + if (null == id || !propertyDescriptor.getName().equals(id.getName())) { + PersistentProperty p = createPersistentProperty(entity, context, propertyDescriptor, mapping); + if (null != p) { + properties.add(p); + for (Annotation anno : fetcher.getDeclaredField(p.getName()).getDeclaredAnnotations()) { + if (anno instanceof Indexed) { + Indexed idx = (Indexed) anno; + String idxColl = collection; + if (!"".equals(idx.collection())) { + idxColl = idx.collection(); + } + String name = p.getName(); + if (!"".equals(idx.name())) { + name = idx.name(); + } + ensureIndex(idxColl, name, null, idx.direction(), idx.unique(), idx.dropDups(), idx.sparse()); + } + } + } + } + } + descriptor = new PersistenceDescriptor(entity, id, properties); + descriptors.put(javaClass, descriptor); + } + return descriptor; + } + + protected PersistentEntity getPersistentEntity(Class javaClass, MappingContext context, ClassMapping mapping) { + Assert.notNull(javaClass); + if (null != mapping) { + return mapping.getEntity(); + } else { + return context.getPersistentEntity(javaClass.getName()); + } + } + + protected PersistentProperty createPersistentProperty(PersistentEntity entity, + MappingContext mappingContext, + PropertyDescriptor descriptor, + ClassMapping mapping) { + ClassPropertyFetcher fetcher = ClassPropertyFetcher.forClass(entity.getJavaClass()); + Field f = fetcher.getDeclaredField(descriptor.getName()); + + if (null != f) { + // Handle associations and persistent types + PersistentProperty prop = extractChildType(entity, mappingContext, descriptor, mapping); + if (null == prop) { + // Must be a simple type + prop = mappingFactory.createSimple(entity, mappingContext, descriptor); + } + return prop; + } + + return null; + } + + protected PersistentProperty extractChildType(PersistentEntity entity, + MappingContext mappingContext, + PropertyDescriptor descriptor, + ClassMapping mapping) { + + ClassPropertyFetcher fetcher = ClassPropertyFetcher.forClass(entity.getJavaClass()); + Field f = fetcher.getDeclaredField(descriptor.getName()); + + Class childClass = null; + Association assoc = null; + if (f.getType().isAssignableFrom(List.class)) { + if (null != f.getAnnotation(org.springframework.data.mapping.annotation.OneToMany.class)) { + org.springframework.data.mapping.annotation.OneToMany otm = f.getAnnotation( + org.springframework.data.mapping.annotation.OneToMany.class); + if (Object.class != otm.targetClass()) { + childClass = otm.targetClass(); + } else { + childClass = extractType(f); + } + assoc = mappingFactory.createOneToMany(entity, mappingContext, descriptor); + } else if (null != f.getAnnotation(org.springframework.data.mapping.annotation.ManyToMany.class)) { + org.springframework.data.mapping.annotation.ManyToMany mtm = f.getAnnotation( + org.springframework.data.mapping.annotation.ManyToMany.class); + if (Object.class != mtm.targetClass()) { + childClass = mtm.targetClass(); + } else { + childClass = extractType(f); + } + assoc = mappingFactory.createManyToMany(entity, mappingContext, descriptor); + } + } else { + if (null != f.getAnnotation(OneToOne.class) || null != f.getType().getAnnotation(Persistent.class)) { + childClass = f.getType(); + assoc = mappingFactory.createOneToOne(entity, mappingContext, descriptor); + } + } + if (null != childClass && null != assoc) { + if (childClass != entity.getJavaClass()) { + PersistentEntity childEntity = getPersistentEntity(childClass, mappingContext, mapping); + addOwner(entity.getJavaClass(), childEntity); + assoc.setAssociatedEntity(childEntity); + } else { + addOwner(entity.getJavaClass(), entity); + assoc.setAssociatedEntity(entity); + } + } + + return assoc; + } + + protected Class extractType(Field f) { + Type t = f.getGenericType(); + if (null != t && t instanceof ParameterizedType) { + Type[] types = ((ParameterizedType) t).getActualTypeArguments(); + return ((Class) types[0]); + } + return f.getType(); + } + + protected IdentifiedBy extractIdentifiedBy(Class clazz, Field field) { + Persistent cAnno = clazz.getAnnotation(Persistent.class); + Persistent fldAnno = (null != field ? field.getAnnotation(Persistent.class) : null); + return (null != fldAnno ? fldAnno.identifiedBy() : cAnno.identifiedBy()); + } + + protected PersistenceStrategy extractPersistenceStrategy(Class clazz, Field field) { + Persistent cAnno = clazz.getAnnotation(Persistent.class); + Persistent fldAnno = (null != field ? field.getAnnotation(Persistent.class) : null); + return (null != fldAnno ? fldAnno.strategy() : cAnno.strategy()); + } + + protected String extractId(Class clazz, Field field) { + Persistent cAnno = clazz.getAnnotation(Persistent.class); + Persistent fldAnno = (null != field ? field.getAnnotation(Persistent.class) : null); + return (null != fldAnno ? fldAnno.id() : cAnno.id()); + } + + protected void addOwner(Class javaClass, PersistentEntity parent) { + if (!owners.containsKey(javaClass)) { + Set owningEntities = new HashSet(); + owningEntities.add(parent); + owners.put(javaClass, owningEntities); + } + } + + protected PersistentProperty extractIdProperty(PersistentEntity entity, + MappingContext mappingContext, + EvaluationContext elContext) { + ClassPropertyFetcher fetcher = ClassPropertyFetcher.forClass(entity.getJavaClass()); + + // Let field annotation override that on the class + IdentifiedBy idBy = extractIdentifiedBy(fetcher.getJavaClass(), null); + String id = extractId(fetcher.getJavaClass(), null); + Assert.notNull(id); + if (id.indexOf("#") > -1) { + id = expressionParser.parseExpression(id).getValue(elContext, String.class); + } + + PropertyDescriptor idPropDesc = null; + switch (idBy) { + case DEFAULT: + idPropDesc = findIdByAnnotation(fetcher); + if (null == idPropDesc) { + idPropDesc = fetcher.getPropertyDescriptor(id); + } + break; + case ANNOTATION: + idPropDesc = findIdByAnnotation(fetcher); + break; + case PROPERTY: + idPropDesc = fetcher.getPropertyDescriptor(id); + break; + case VALUE: + try { + idPropDesc = new ValuePropertyDescriptor("id", entity.getJavaClass(), id); + } catch (IntrospectionException e) { + throw new IllegalStateException(e.getMessage(), e); + } + break; + } + //Assert.notNull(idPropDesc, String.format("No ID property could be found on the entity %s", entity)); + + return (null != idPropDesc ? mappingFactory.createIdentity(entity, mappingContext, idPropDesc) : null); + } + + protected PropertyDescriptor findIdByAnnotation(ClassPropertyFetcher fetcher) { + for (PropertyDescriptor descriptor : fetcher.getPropertyDescriptors()) { + Field f = fetcher.getDeclaredField(descriptor.getName()); + if (null != f && null != f.getAnnotation(Id.class)) { + return descriptor; + } + } + return null; + } + + protected EvaluationContext createElContext() { + StandardEvaluationContext elContext = new StandardEvaluationContext(System.getProperties()); + for (String prop : System.getProperties().stringPropertyNames()) { + elContext.setVariable(prop, System.getProperty(prop)); + } + return elContext; + } + + protected void ensureIndex(String collection, + final String name, + final String def, + final IndexDirection direction, + final boolean unique, + final boolean dropDups, + final boolean sparse) { + mongo.execute(collection, new CollectionCallback() { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { + DBObject defObj; + if (null != def) { + defObj = (DBObject) JSON.parse(def); + } else { + defObj = new BasicDBObject(); + defObj.put(name, (direction == IndexDirection.ASCENDING ? 1 : -1)); + } + DBObject opts = new BasicDBObject(); + if (!"".equals(name)) { + opts.put("name", name); + } + opts.put("dropDups", dropDups); + opts.put("sparse", sparse); + opts.put("unique", unique); + collection.ensureIndex(defObj, opts); + return null; + } + }); + } + + protected class PersistenceDescriptor { + + PersistentEntity entity; + PersistentProperty idProperty = null; + List properties = null; + + PersistenceDescriptor(PersistentEntity entity, PersistentProperty idProperty, List properties) { + this.entity = entity; + this.idProperty = idProperty; + this.properties = properties; + } + + public PersistentEntity getEntity() { + return entity; + } + + public PersistentProperty getIdProperty() { + return idProperty; + } + + public List getProperties() { + return properties; + } + + } + + protected class ValuePropertyDescriptor extends PropertyDescriptor { + + Object value; + + ValuePropertyDescriptor(String s, Class aClass, Object value) throws IntrospectionException { + super(s, aClass); + this.value = value; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + @Override + public Method getReadMethod() { + try { + return getClass().getMethod("getValue", null); + } catch (NoSuchMethodException e) { + // IGNORED + } + return super.getReadMethod(); + } + + @Override + public void setReadMethod(Method method) throws IntrospectionException { + // IGNORED + } + + @Override + public Method getWriteMethod() { + try { + return getClass().getMethod("setValue", Object.class); + } catch (NoSuchMethodException e) { + // IGNORED + } + return super.getWriteMethod(); + } + + @Override + public void setWriteMethod(Method method) throws IntrospectionException { + // IGNORED + } + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingContext.java new file mode 100644 index 000000000..b725209e9 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingContext.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.mapping.model.AbstractMappingContext; +import org.springframework.data.mapping.model.MappingConfigurationStrategy; +import org.springframework.data.mapping.model.MappingFactory; +import org.springframework.data.mapping.model.PersistentEntity; + +/** + * @author Jon Brisbin + */ +public class MongoMappingContext extends AbstractMappingContext { + + private MongoTemplate mongo; + private MongoMappingFactory mappingFactory = null; + private MappingConfigurationStrategy strategy = null; + + public MongoMappingContext(MongoTemplate mongo) { + this.mongo = mongo; + this.mappingFactory = new MongoMappingFactory(); + strategy = new MongoMappingConfigurationStrategy(mongo, mappingFactory); + } + + public MongoMappingContext(MongoTemplate mongo, MongoMappingFactory mappingFactory) { + this.mongo = mongo; + this.mappingFactory = mappingFactory; + strategy = new MongoMappingConfigurationStrategy(mongo, mappingFactory); + } + + @Override + protected PersistentEntity createPersistentEntity(Class javaClass) { + MongoPersistentEntity entity = new MongoPersistentEntity(javaClass, this); + return entity; + } + + public MappingConfigurationStrategy getMappingSyntaxStrategy() { + return strategy; + } + + public MappingFactory getMappingFactory() { + return mappingFactory; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingFactory.java new file mode 100644 index 000000000..2ab43468b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.mapping.model.MappingFactory; +import org.springframework.data.mapping.model.PersistentEntity; +import org.springframework.data.mapping.model.PersistentProperty; +import org.springframework.data.mapping.reflect.ClassPropertyFetcher; + +import java.lang.reflect.Field; + +/** + * @author Jon Brisbin + */ +public class MongoMappingFactory extends MappingFactory { + + @Override + public MongoCollection createMappedForm(PersistentEntity entity) { + String name = entity.getJavaClass().getSimpleName().toLowerCase(); + return new MongoCollection(name); + } + + @Override + public MongoProperty createMappedForm(PersistentProperty property) { + String name = property.getName(); + return new MongoProperty(name, false); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntity.java new file mode 100644 index 000000000..e871b641c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntity.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import org.springframework.data.mapping.model.AbstractPersistentEntity; +import org.springframework.data.mapping.model.ClassMapping; +import org.springframework.data.mapping.model.MappingContext; + +/** + * @author Jon Brisbin + */ +public class MongoPersistentEntity extends AbstractPersistentEntity { + + @SuppressWarnings({"unchecked"}) + public MongoPersistentEntity(Class javaClass, MappingContext context) { + super(javaClass, context); + } + + @SuppressWarnings({"unchecked"}) + public ClassMapping getMapping() { + return new MongoClassMapping(this, getMappingContext()); + } + + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoProperty.java new file mode 100644 index 000000000..90172e434 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoProperty.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +/** + * @author Jon Brisbin + */ +public class MongoProperty { + + private String name; + + public MongoProperty(String name, boolean indexed) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/index/MongoIndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/index/MongoIndexDefinition.java new file mode 100644 index 000000000..1e02b7f5f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/index/MongoIndexDefinition.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping.index; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.util.JSON; +import org.springframework.data.document.mongodb.index.IndexDefinition; +import org.springframework.data.document.mongodb.index.IndexDirection; + +/** + * @author Jon Brisbin + */ +public class MongoIndexDefinition implements IndexDefinition { + + private String collection = null; + private String name = null; + private IndexDirection direction = IndexDirection.ASCENDING; + private boolean unique = false; + private boolean dropDups = true; + private boolean sparse = false; + private String definition = null; + + public MongoIndexDefinition() { + } + + public String getCollection() { + return collection; + } + + public void setCollection(String collection) { + this.collection = collection; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public IndexDirection getDirection() { + return direction; + } + + public void setDirection(IndexDirection direction) { + this.direction = direction; + } + + public boolean isUnique() { + return unique; + } + + public void setUnique(boolean unique) { + this.unique = unique; + } + + public boolean isDropDups() { + return dropDups; + } + + public void setDropDups(boolean dropDups) { + this.dropDups = dropDups; + } + + public boolean isSparse() { + return sparse; + } + + public void setSparse(boolean sparse) { + this.sparse = sparse; + } + + public String getDefinition() { + return definition; + } + + public void setDefinition(String definition) { + this.definition = definition; + } + + public DBObject getIndexDefinition() { + DBObject dbo; + if (null != definition) { + dbo = (DBObject) JSON.parse(definition); + } else { + dbo = new BasicDBObject(); + dbo.put(name, (direction == IndexDirection.ASCENDING ? 1 : -1)); + } + return dbo; + } + + public DBObject getIndexOptions() { + DBObject dbo = new BasicDBObject(); + dbo.put("dropDups", dropDups); + dbo.put("sparse", sparse); + dbo.put("unique", unique); + return dbo; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java index 56d107360..12640021a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/SimpleMongoRepository.java @@ -23,7 +23,7 @@ import java.util.ArrayList; import java.util.List; import org.bson.types.ObjectId; -import org.springframework.data.document.mongodb.MongoConverter; +import org.springframework.data.document.mongodb.convert.MongoConverter; import org.springframework.data.document.mongodb.MongoTemplate; import org.springframework.data.document.mongodb.query.Query; import org.springframework.data.domain.Page; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQuery.java index 5af669064..5e9202297 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQuery.java @@ -55,7 +55,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { * * @see * org.springframework.data.document.mongodb.repository.AbstractMongoQuery#createQuery(org.springframework.data. - * repository.query.SimpleParameterAccessor, org.springframework.data.document.mongodb.MongoConverter) + * repository.query.SimpleParameterAccessor, org.springframework.data.document.mongodb.support.convert.MongoConverter) */ @Override protected Query createQuery(ConvertingParameterAccessor accessor) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Aliases.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Aliases.java new file mode 100644 index 000000000..4c30070a1 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Aliases.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.mapping.annotation; + +/** + * @author Jon Brisbin + */ +public @interface Aliases { + + String[] value(); + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Id.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Id.java new file mode 100644 index 000000000..f78664265 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Id.java @@ -0,0 +1,14 @@ +package org.springframework.data.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author J. Brisbin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.FIELD}) +public @interface Id { +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/IdentifiedBy.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/IdentifiedBy.java new file mode 100644 index 000000000..d5f3803f9 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/IdentifiedBy.java @@ -0,0 +1,12 @@ +package org.springframework.data.mapping.annotation; + +/** + * @author J. Brisbin + */ +public enum IdentifiedBy { + DEFAULT, + ANNOTATION, + PROPERTY, + VALUE, + PARENT +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/ManyToMany.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/ManyToMany.java new file mode 100644 index 000000000..4be53ae69 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/ManyToMany.java @@ -0,0 +1,17 @@ +package org.springframework.data.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author J. Brisbin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.FIELD}) +public @interface ManyToMany { + + Class targetClass() default Object.class; + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/OneToMany.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/OneToMany.java new file mode 100644 index 000000000..43eafa4ad --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/OneToMany.java @@ -0,0 +1,17 @@ +package org.springframework.data.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author J. Brisbin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.FIELD}) +public @interface OneToMany { + + Class targetClass() default Object.class; + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/OneToOne.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/OneToOne.java new file mode 100644 index 000000000..cd212c36d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/OneToOne.java @@ -0,0 +1,14 @@ +package org.springframework.data.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author J. Brisbin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.FIELD}) +public @interface OneToOne { +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/PersistenceConstructor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/PersistenceConstructor.java new file mode 100644 index 000000000..ddfbbf694 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/PersistenceConstructor.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Jon Brisbin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.CONSTRUCTOR) +public @interface PersistenceConstructor { +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/PersistenceStrategy.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/PersistenceStrategy.java new file mode 100644 index 000000000..af06aca39 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/PersistenceStrategy.java @@ -0,0 +1,11 @@ +package org.springframework.data.mapping.annotation; + +/** + * @author J. Brisbin + */ +public enum PersistenceStrategy { + DEFAULT, + KEY_VALUE, + DOCUMENT, + SERIALIZED +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Persistent.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Persistent.java new file mode 100644 index 000000000..3312ff8d9 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Persistent.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author J. Brisbin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = {ElementType.TYPE, ElementType.FIELD}) +public @interface Persistent { + + PersistenceStrategy strategy() default PersistenceStrategy.DOCUMENT; + + IdentifiedBy identifiedBy() default IdentifiedBy.DEFAULT; + + String id() default "id"; + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Transient.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Transient.java new file mode 100644 index 000000000..a8d5cd0ad --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Transient.java @@ -0,0 +1,14 @@ +package org.springframework.data.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author J. Brisbin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Transient { +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Value.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Value.java new file mode 100644 index 000000000..73502dc68 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Value.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.mapping.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Jon Brisbin + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ + ElementType.PARAMETER, + ElementType.FIELD, + ElementType.ANNOTATION_TYPE +}) +public @interface Value { + + String value(); + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractClassMapping.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractClassMapping.java new file mode 100644 index 000000000..d51b2d022 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractClassMapping.java @@ -0,0 +1,42 @@ +/* Copyright (C) 2010 SpringSource + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +/** + * Abstract implementation of the ClassMapping interface + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class AbstractClassMapping implements ClassMapping{ + protected PersistentEntity entity; + protected MappingContext context; + + public AbstractClassMapping(PersistentEntity entity, MappingContext context) { + super(); + this.entity = entity; + this.context = context; + } + + public PersistentEntity getEntity() { + return this.entity; + } + + public abstract T getMappedForm(); + + public IdentityMapping getIdentifier() { + return context.getMappingSyntaxStrategy().getDefaultIdentityMapping(this); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractMappingContext.java new file mode 100644 index 000000000..452f1ded4 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractMappingContext.java @@ -0,0 +1,156 @@ +/* Copyright (C) 2010 SpringSource + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.validation.Validator; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Abstract implementation of the MappingContext interface + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class AbstractMappingContext implements MappingContext { + + protected Collection persistentEntities = new ConcurrentLinkedQueue(); + protected Map persistentEntitiesByName = new ConcurrentHashMap(); + protected Map> persistentEntitiesByDiscriminator = new ConcurrentHashMap>(); + protected Map entityValidators = new ConcurrentHashMap(); + protected Collection eventListeners = new ConcurrentLinkedQueue(); + protected GenericConversionService conversionService = new GenericConversionService(); + + public ConversionService getConversionService() { + return conversionService; + } + + public ConverterRegistry getConverterRegistry() { + return conversionService; + } + + public void addMappingContextListener(Listener listener) { + if (listener != null) + eventListeners.add(listener); + } + + public void addTypeConverter(Converter converter) { + conversionService.addConverter(converter); + } + + public Validator getEntityValidator(PersistentEntity entity) { + if (entity != null) { + return entityValidators.get(entity); + } + return null; + } + + public void addEntityValidator(PersistentEntity entity, Validator validator) { + if (entity != null && validator != null) { + entityValidators.put(entity, validator); + } + } + + public PersistentEntity addExternalPersistentEntity(Class javaClass) { + if (javaClass == null) throw new IllegalArgumentException("PersistentEntity class cannot be null"); + + PersistentEntity entity = persistentEntitiesByName.get(javaClass.getName()); + + if (entity == null) { + entity = addPersistentEntityInternal(javaClass, true); + } + + return entity; + } + + public final PersistentEntity addPersistentEntity(Class javaClass) { + if (javaClass == null) throw new IllegalArgumentException("PersistentEntity class cannot be null"); + + PersistentEntity entity = persistentEntitiesByName.get(javaClass.getName()); + + if (entity == null) { + entity = addPersistentEntityInternal(javaClass, false); + } + + return entity; + } + + private PersistentEntity addPersistentEntityInternal(Class javaClass, boolean isExternal) { + PersistentEntity entity; + entity = createPersistentEntity(javaClass); + entity.setExternal(isExternal); + + persistentEntities.remove(entity); + persistentEntities.add(entity); + persistentEntitiesByName.put(entity.getName(), entity); + try { + entity.afterPropertiesSet(); + } catch (Exception e) { + throw new RuntimeException(String.format("Error initializing PersistentEntity: %s", entity), e); + } + if (!entity.isRoot()) { + PersistentEntity root = entity.getRootEntity(); + Map children = persistentEntitiesByDiscriminator.get(root); + if (children == null) { + children = new ConcurrentHashMap(); + persistentEntitiesByDiscriminator.put(root, children); + } + children.put(entity.getDiscriminator(), entity); + } + for (Listener eventListener : eventListeners) { + eventListener.persistentEntityAdded(entity); + } + return entity; + } + + public PersistentEntity getChildEntityByDiscriminator(PersistentEntity root, String discriminator) { + final Map children = persistentEntitiesByDiscriminator.get(root); + if (children != null) { + return children.get(discriminator); + } + return null; + } + + protected abstract PersistentEntity createPersistentEntity(Class javaClass); + + public Collection getPersistentEntities() { + return persistentEntities; + } + + public boolean isPersistentEntity(Class type) { + return type != null && getPersistentEntity(type.getName()) != null; + + } + + public boolean isPersistentEntity(Object value) { + return value != null && isPersistentEntity(value.getClass()); + } + + public PersistentEntity getPersistentEntity(String name) { + final int proxyIndicator = name.indexOf("_$$_"); + if (proxyIndicator > -1) { + name = name.substring(0, proxyIndicator); + } + + return persistentEntitiesByName.get(name); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentEntity.java new file mode 100644 index 000000000..4a51134df --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentEntity.java @@ -0,0 +1,193 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import org.springframework.beans.BeanUtils; +import org.springframework.data.mapping.model.types.Association; +import org.springframework.data.mapping.model.types.OneToMany; + +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Modifier; +import java.util.*; + +/** + * Abstract implementation to be subclasses on a per datastore basis + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class AbstractPersistentEntity implements PersistentEntity { + + protected Class javaClass; + protected List persistentProperties; + protected List associations; + protected Map propertiesByName = new HashMap(); + protected MappingContext context; + protected PersistentProperty identity; + protected List persistentPropertyNames; + private String decapitalizedName; + protected Set owners; + private PersistentEntity parentEntity = null; + private boolean external; + + public AbstractPersistentEntity(Class javaClass, MappingContext context) { + if (javaClass == null) throw new IllegalArgumentException("The argument [javaClass] cannot be null"); + this.javaClass = javaClass; + this.context = context; + this.decapitalizedName = Introspector.decapitalize(javaClass.getSimpleName()); + } + + public boolean isExternal() { + return external; + } + + + public void setExternal(boolean external) { + this.external = external; + } + + public MappingContext getMappingContext() { + return this.context; + } + + public void afterPropertiesSet() throws Exception { + this.identity = context.getMappingSyntaxStrategy().getIdentity(javaClass, context); + this.owners = context.getMappingSyntaxStrategy().getOwningEntities(javaClass, context); + this.persistentProperties = context.getMappingSyntaxStrategy().getPersistentProperties(javaClass, context); + persistentPropertyNames = new ArrayList(); + associations = new ArrayList(); + for (PersistentProperty persistentProperty : persistentProperties) { + if (!(persistentProperty instanceof OneToMany)) + persistentPropertyNames.add(persistentProperty.getName()); + if (persistentProperty instanceof Association) { + associations.add((Association) persistentProperty); + } + } + for (PersistentProperty persistentProperty : persistentProperties) { + propertiesByName.put(persistentProperty.getName(), persistentProperty); + } + + Class superClass = javaClass.getSuperclass(); + if (superClass != null + && !superClass.equals(Object.class) + && !Modifier.isAbstract(superClass.getModifiers())) { + this.parentEntity = context.addPersistentEntity(superClass); + } + + + getMapping().getMappedForm(); // initialize mapping + } + + public boolean hasProperty(String name, Class type) { + final PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(getJavaClass(), name); + return pd != null && pd.getPropertyType().equals(type); + } + + public boolean isIdentityName(String propertyName) { + return getIdentity().getName().equals(propertyName); + } + + public PersistentEntity getParentEntity() { + return parentEntity; + } + + public String getDiscriminator() { + return getJavaClass().getSimpleName(); + } + + public PersistentEntity getRootEntity() { + PersistentEntity root = this; + PersistentEntity parent = getParentEntity(); + while (parent != null) { + root = parent; + parent = parent.getParentEntity(); + } + return root; + } + + public boolean isRoot() { + return getParentEntity() == null; + } + + public boolean isOwningEntity(PersistentEntity owner) { + return owner != null && owners.contains(owner.getJavaClass()); + } + + public String getDecapitalizedName() { + return this.decapitalizedName; + } + + public List getPersistentPropertyNames() { + return this.persistentPropertyNames; + } + + public T newInstance() { + try { + return getJavaClass().newInstance(); + } catch (InstantiationException e) { + throw new EntityInstantiationException("Unable to create entity of type [" + getJavaClass() + "]: " + e.getMessage(), e); + } catch (IllegalAccessException e) { + throw new EntityInstantiationException("Unable to create entity of type [" + getJavaClass() + "]: " + e.getMessage(), e); + } + } + + public String getName() { + return javaClass.getName(); + } + + public PersistentProperty getIdentity() { + return this.identity; + } + + public Class getJavaClass() { + return this.javaClass; + } + + public boolean isInstance(Object obj) { + return getJavaClass().isInstance(obj); + } + + public List getPersistentProperties() { + return persistentProperties; + } + + public List getAssociations() { + return associations; + } + + public PersistentProperty getPropertyByName(String name) { + return propertiesByName.get(name); + } + + @Override + public int hashCode() { + return javaClass.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof PersistentEntity)) return false; + if (this == o) return true; + + PersistentEntity other = (PersistentEntity) o; + return javaClass.equals(other.getJavaClass()); + } + + @Override + public String toString() { + return "PersistentEntity[" + javaClass.getName() + "]"; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java new file mode 100644 index 000000000..b3778124c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java @@ -0,0 +1,77 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import java.beans.PropertyDescriptor; + +/** + * Abstract implementation of the PersistentProperty interface that + * uses the PropertyDescriptor instance to establish name and type + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class AbstractPersistentProperty implements PersistentProperty { + protected PersistentEntity owner; + protected MappingContext context; + protected String name; + protected Class type; + private boolean nullable = false; + + public AbstractPersistentProperty(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + this.owner = owner; + this.context = context; + this.name = descriptor.getName(); + this.type = descriptor.getPropertyType(); + } + + public AbstractPersistentProperty(PersistentEntity owner, MappingContext context, String name, Class type) { + this.owner = owner; + this.context = context; + this.name = name; + this.type = type; + } + + + public String getName() { + return name; + } + + public String getCapitilizedName() { + String name = getName(); + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + + public Class getType() { + return type; + } + + public PersistentEntity getOwner() { + return owner; + } + + @Override + public String toString() { + return getName() + ":" + getType().getName(); + } + + public boolean isNullable() { + return nullable; + } + + public void setNullable(boolean nullable) { + this.nullable = nullable; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/ClassMapping.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/ClassMapping.java new file mode 100644 index 000000000..7b6a8596e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/ClassMapping.java @@ -0,0 +1,31 @@ +package org.springframework.data.mapping.model; + +/** + * A class mapping is a mapping between a class and some external + * form such as a table, column family, or document (depending on the underlying data store) + * + * @author Graeme Rocher + * @since 1.0 + */ +public interface ClassMapping { + /** + * Obtains the PersistentEntity for this class mapping + * + * @return The PersistentEntity + */ + PersistentEntity getEntity(); + + /** + * Returns the mapped form of the class such as a Table, a Key Space, Document etc. + * + * @return The mapped representation + */ + T getMappedForm(); + + /** + * Returns details of the identifier used for this class + * + * @return The Identity + */ + IdentityMapping getIdentifier(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/DatastoreConfigurationException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/DatastoreConfigurationException.java new file mode 100644 index 000000000..b27406168 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/DatastoreConfigurationException.java @@ -0,0 +1,18 @@ +package org.springframework.data.mapping.model; + +/** + * Exception thrown when something goes wrong configuring a datastore + * + * @author Graeme Rocher + * @since 1.0 + */ +public class DatastoreConfigurationException extends RuntimeException{ + + public DatastoreConfigurationException(String message) { + super(message); + } + + public DatastoreConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiationException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiationException.java new file mode 100644 index 000000000..c3b368a19 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiationException.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.mapping.model; + +/** + * Created by IntelliJ IDEA. + * User: jbrisbin + * Date: 2/25/11 + * Time: 9:07 AM + * To change this template use File | Settings | File Templates. + */ +public class EntityInstantiationException extends RuntimeException { + public EntityInstantiationException(String s, Throwable throwable) { + super(s, throwable); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IdentityMapping.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IdentityMapping.java new file mode 100644 index 000000000..851ab3ccd --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IdentityMapping.java @@ -0,0 +1,31 @@ +/* Copyright (C) 2010 SpringSource + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +/** + * @author Graeme Rocher + * @since 1.0 + */ +public interface IdentityMapping extends PropertyMapping{ + + /** + * The identifier property name(s) as an array. Usually there is just one identifier + * name, however in the case of a composite or natural identifier there + * may be serveral names + * + * @return A String[] of identifier names that make up the key + */ + String[] getIdentifierName(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IllegalMappingException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IllegalMappingException.java new file mode 100644 index 000000000..035583cb1 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IllegalMappingException.java @@ -0,0 +1,28 @@ +/* Copyright (C) 2010 SpringSource + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +/** + * Thrown when an error occurs reading the mapping between object and datastore + * + * @author Graeme Rocher + * @since 1.0 + */ +public class IllegalMappingException extends RuntimeException { + + public IllegalMappingException(String s) { + super(s); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingConfigurationStrategy.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingConfigurationStrategy.java new file mode 100644 index 000000000..ca6ab7485 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingConfigurationStrategy.java @@ -0,0 +1,70 @@ +package org.springframework.data.mapping.model; + +import java.util.List; +import java.util.Set; + +/** + *

This interface defines a strategy for reading how + * persistent properties are defined in a persistent entity.

+ *

+ *

Subclasses can implement this interface in order to provide + * a different mechanism for mapping entities such as annotations or XML.

+ * + * @author Graeme Rocher + * @since 1.0 + */ +public interface MappingConfigurationStrategy { + + /** + * Tests whether the given class is a persistent entity + * + * @param javaClass The java class + * @return true if it is a persistent entity + */ + boolean isPersistentEntity(Class javaClass); + + /** + * @see #getPersistentProperties(Class, MappingContext, ClassMapping) + */ + List getPersistentProperties(Class javaClass, MappingContext context); + + /** + * Obtains a List of PersistentProperty instances for the given Mapped class + * + * @param javaClass The Java class + * @param context The MappingContext instance + * @param mapping The mapping for this class + * @return The PersistentProperty instances + */ + List getPersistentProperties(Class javaClass, MappingContext context, ClassMapping mapping); + + /** + * Obtains the identity of a persistent entity + * + * @param javaClass The Java class + * @param context The MappingContext + * @return A PersistentProperty instance + */ + PersistentProperty getIdentity(Class javaClass, MappingContext context); + + /** + * Obtains the default manner in which identifiers are mapped. In GORM + * this is just using a property called 'id', but in other frameworks this + * may differ. For example JPA expects an annotated @Id property + * + * @param classMapping The ClassMapping instance + * @return The default identifier mapping + */ + IdentityMapping getDefaultIdentityMapping(ClassMapping classMapping); + + /** + * Returns a set of entities that "own" the given entity. Ownership + * dictates default cascade strategies. So if entity A owns entity B + * then saves, updates and deletes will cascade from A to B + * + * @param javaClass The Java class + * @param context The MappingContext + * @return A Set of owning classes + */ + Set getOwningEntities(Class javaClass, MappingContext context); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingContext.java new file mode 100644 index 000000000..332aef7a8 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingContext.java @@ -0,0 +1,169 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.validation.Validator; + +import java.util.Collection; + +/** + *

This interface defines the overall context including all known + * PersistentEntity instances and methods to obtain instances on demand

+ *

+ *

This interface is used internally to establish associations + * between entities and also at runtime to obtain entities by name

+ *

+ *

The generic type parameters T & R are used to specify the + * mapped form of a class (example Table) and property (example Column) respectively.

+ *

+ * + * @author Graeme Rocher + * @since 1.0 + */ +public interface MappingContext { + + + /** + * Obtains a list of PersistentEntity instances + * + * @return A list of PersistentEntity instances + */ + Collection getPersistentEntities(); + + /** + * Obtains a PersistentEntity by name + * + * @param name The name of the entity + * @return The entity or null + */ + PersistentEntity getPersistentEntity(String name); + + /** + * Obtains a child of the given root entity using the given discriminator + * + * @param root The root entity + * @param discriminator The discriminator + * @return The child entity or null if non exists + */ + PersistentEntity getChildEntityByDiscriminator(PersistentEntity root, String discriminator); + + /** + * Adds a PersistentEntity instance + * + * @param javaClass The Java class representing the entity + * @return The PersistentEntity instance + */ + PersistentEntity addPersistentEntity(Class javaClass); + + /** + * Adds a persistent entity that is not mapped by this MappingContext instance. + * Used for cross store persistence + * + * @param javaClass The Java class + * @return The persistent entity + */ + PersistentEntity addExternalPersistentEntity(Class javaClass); + + /** + * Adds a validator to be used by the entity for validation + * + * @param entity The PersistentEntity + * @param validator The validator + */ + void addEntityValidator(PersistentEntity entity, Validator validator); + + /** + * Add a converter used to convert property values to and from the datastore + * + * @param converter The converter to add + */ + void addTypeConverter(Converter converter); + + /** + * Obtains the ConversionService instance to use for type conversion + * + * @return The conversion service instance + */ + ConversionService getConversionService(); + + /** + * Obtains the converter registry + * + * @return The converter registry used for type conversion + */ + ConverterRegistry getConverterRegistry(); + + /** + * Obtains a validator for the given entity + * + * @param entity The entity + * @return A validator or null if none exists for the given entity + */ + Validator getEntityValidator(PersistentEntity entity); + + /** + * Returns the syntax reader used to interpret the entity + * mapping syntax + * + * @return The SyntaxReader + */ + MappingConfigurationStrategy getMappingSyntaxStrategy(); + + /** + * Obtains the MappingFactory instance + * + * @return The mapping factory instance + */ + MappingFactory getMappingFactory(); + + /** + * Returns whether the specified class is a persistent entity + * + * @param type The type to check + * @return True if it is + */ + boolean isPersistentEntity(Class type); + + /** + * Returns whether the specified value is a persistent entity + * + * @param value The value to check + * @return True if it is + */ + boolean isPersistentEntity(Object value); + + /** + * Adds a new mapping context listener instance + * + * @param listener The listener + */ + void addMappingContextListener(Listener listener); + + /** + * Implementors can register for events when the mapping context changes + */ + public static interface Listener { + + /** + * Fired when a new entity is added + * + * @param entity The entity + */ + void persistentEntityAdded(PersistentEntity entity); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingFactory.java new file mode 100644 index 000000000..a5796605b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingFactory.java @@ -0,0 +1,278 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import org.springframework.data.mapping.model.types.*; + +import java.beans.PropertyDescriptor; +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.net.URL; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.*; + +/** + *

An abstract factory for creating persistent property instances.

+ *

+ *

Subclasses should implement the createMappedForm method in order to + * provide a mechanisms for representing the property in a form appropriate + * for mapping to the underlying datastore. Example:

+ *

+ *

+ *  
+ *      class RelationalPropertyFactory extends PropertyFactory {
+ *            public Column createMappedForm(PersistentProperty mpp) {
+ *                return new Column(mpp)
+ *            }
+ *      }
+ *  
+ * 
+ * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class MappingFactory { + public static final Set SIMPLE_TYPES; + + static { + Set basics = new HashSet(); + basics.add(boolean.class.getName()); + basics.add(long.class.getName()); + basics.add(short.class.getName()); + basics.add(int.class.getName()); + basics.add(byte.class.getName()); + basics.add(float.class.getName()); + basics.add(double.class.getName()); + basics.add(char.class.getName()); + basics.add(Boolean.class.getName()); + basics.add(Long.class.getName()); + basics.add(Short.class.getName()); + basics.add(Integer.class.getName()); + basics.add(Byte.class.getName()); + basics.add(Float.class.getName()); + basics.add(Double.class.getName()); + basics.add(Character.class.getName()); + basics.add(String.class.getName()); + basics.add(java.util.Date.class.getName()); + basics.add(Time.class.getName()); + basics.add(Timestamp.class.getName()); + basics.add(java.sql.Date.class.getName()); + basics.add(BigDecimal.class.getName()); + basics.add(BigInteger.class.getName()); + basics.add(Locale.class.getName()); + basics.add(Calendar.class.getName()); + basics.add(GregorianCalendar.class.getName()); + basics.add(java.util.Currency.class.getName()); + basics.add(TimeZone.class.getName()); + basics.add(Object.class.getName()); + basics.add(Class.class.getName()); + basics.add(byte[].class.getName()); + basics.add(Byte[].class.getName()); + basics.add(char[].class.getName()); + basics.add(Character[].class.getName()); + basics.add(Blob.class.getName()); + basics.add(Clob.class.getName()); + basics.add(Serializable.class.getName()); + basics.add(URI.class.getName()); + basics.add(URL.class.getName()); + + SIMPLE_TYPES = Collections.unmodifiableSet(basics); + } + + + public MappingFactory() { + super(); + } + + public static boolean isSimpleType(Class propType) { + if (propType == null) return false; + if (propType.isArray()) { + return isSimpleType(propType.getComponentType()); + } + final String typeName = propType.getName(); + return isSimpleType(typeName); + } + + public static boolean isSimpleType(final String typeName) { + return SIMPLE_TYPES.contains(typeName); + } + + /** + * Creates the mapped form of a persistent entity + * + * @param entity The entity + * @return The mapped form + */ + public abstract R createMappedForm(PersistentEntity entity); + + /** + * Creates the mapped form of a PersistentProperty instance + * + * @param mpp The PersistentProperty instance + * @return The mapped form + */ + public abstract T createMappedForm(PersistentProperty mpp); + + /** + * Creates an identifier property + * + * @param owner The owner + * @param context The context + * @param pd The PropertyDescriptor + * @return An Identity instance + */ + public Identity createIdentity(PersistentEntity owner, MappingContext context, PropertyDescriptor pd) { + return new Identity(owner, context, pd) { + public PropertyMapping getMapping() { + return createPropertyMapping(this, owner); + } + }; + } + + /** + * Creates a simple property type used for mapping basic types such as String, long, integer etc. + * + * @param owner The owner + * @param context The MappingContext + * @param pd The PropertyDescriptor + * @return A Simple property type + */ + public Simple createSimple(PersistentEntity owner, MappingContext context, PropertyDescriptor pd) { + return new Simple(owner, context, pd) { + public PropertyMapping getMapping() { + return createPropertyMapping(this, owner); + } + }; + } + + protected PropertyMapping createPropertyMapping(final PersistentProperty property, final PersistentEntity owner) { + return new PropertyMapping() { + public ClassMapping getClassMapping() { + return owner.getMapping(); + } + + public T getMappedForm() { + return createMappedForm(property); + } + }; + } + + /** + * Creates a one-to-one association type used for mapping a one-to-one association between entities + * + * @param entity The entity + * @param context The context + * @param property The property + * @return The ToOne instance + */ + public ToOne createOneToOne(PersistentEntity entity, MappingContext context, PropertyDescriptor property) { + return new OneToOne(entity, context, property) { + public PropertyMapping getMapping() { + return createPropertyMapping(this, owner); + } + }; + } + + /** + * Creates a many-to-one association type used for a mapping a many-to-one association between entities + * + * @param entity The entity + * @param context The context + * @param property The property + * @return The ToOne instance + */ + public ToOne createManyToOne(PersistentEntity entity, MappingContext context, PropertyDescriptor property) { + return new ManyToOne(entity, context, property) { + public PropertyMapping getMapping() { + return createPropertyMapping(this, owner); + } + }; + + } + + /** + * Creates a {@link OneToMany} type used to model a one-to-many association between entities + * + * @param entity The entity + * @param context The context + * @param property The property + * @return The {@link OneToMany} instance + */ + public OneToMany createOneToMany(PersistentEntity entity, MappingContext context, PropertyDescriptor property) { + return new OneToMany(entity, context, property) { + public PropertyMapping getMapping() { + return createPropertyMapping(this, owner); + } + }; + + } + + /** + * Creates a {@link ManyToMany} type used to model a many-to-many association between entities + * + * @param entity The entity + * @param context The context + * @param property The property + * @return The {@link ManyToMany} instance + */ + public ManyToMany createManyToMany(PersistentEntity entity, MappingContext context, PropertyDescriptor property) { + return new ManyToMany(entity, context, property) { + public PropertyMapping getMapping() { + return createPropertyMapping(this, owner); + } + }; + } + + /** + * Creates an {@link Embedded} type used to model an embedded association (composition) + * + * @param entity The entity + * @param context The context + * @param property The property + * @return The {@link Embedded} instance + */ + public Embedded createEmbedded(PersistentEntity entity, + MappingContext context, PropertyDescriptor property) { + return new Embedded(entity, context, property) { + public PropertyMapping getMapping() { + return createPropertyMapping(this, owner); + } + }; + } + + /** + * Creates a {@link Basic} collection type + * + * @param entity The entity + * @param context The context + * @param property The property + * @return The Basic collection type + */ + public Basic createBasicCollection(PersistentEntity entity, + MappingContext context, PropertyDescriptor property) { + return new Basic(entity, context, property) { + public PropertyMapping getMapping() { + return createPropertyMapping(this, owner); + } + + }; + } +} + diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java new file mode 100644 index 000000000..cc2171f8c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentEntity.java @@ -0,0 +1,167 @@ +package org.springframework.data.mapping.model; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.mapping.model.types.Association; + +import java.util.List; + +/** + * Represents a persistent entity + * + * @author Graeme Rocher + * @since 1.0 + */ +public interface PersistentEntity extends InitializingBean { + + /** + * The entity name including any package prefix + * + * @return The entity name + */ + String getName(); + + /** + * Whether this PersistentEntity is mapped using a different store. Used for cross store persistence + * + * @return True if this entity is externally mapped + */ + boolean isExternal(); + + /** + * Whether this PersistentEntity is mapped using a different store. Used for cross store persistence + * + * @return True if this entity is externally mapped + */ + void setExternal(boolean external); + + /** + * Returns the identity of the instance + * + * @return The identity + */ + PersistentProperty getIdentity(); + + /** + * A list of properties to be persisted + * + * @return A list of PersistentProperty instances + */ + List getPersistentProperties(); + + /** + * A list of the associations for this entity. This is typically + * a subset of the list returned by {@link #getPersistentProperties()} + * + * @return A list of associations + */ + List getAssociations(); + + /** + * Obtains a PersistentProperty instance by name + * + * @param name The name of the property + * @return The PersistentProperty or null if it doesn't exist + */ + PersistentProperty getPropertyByName(String name); + + /** + * @return The underlying Java class for this entity + */ + Class getJavaClass(); + + /** + * Tests whether the given instance is an instance of this persistent entity + * + * @param obj The object + * @return True if it is + */ + boolean isInstance(Object obj); + + /** + * Defines the mapping between this persistent entity + * and an external form + * + * @return The ClassMapping instance + */ + ClassMapping getMapping(); + + /** + * Constructs a new instance + * + * @return The new instnace + */ + T newInstance(); + + /** + * A list of property names that a persistent + * + * @return A List of strings + */ + List getPersistentPropertyNames(); + + /** + * @return Returns the name of the class decapitalized form + */ + String getDecapitalizedName(); + + /** + * Returns whether the specified entity asserts ownership over this + * entity + * + * @param owner The owning entity + * @return True if it does own this entity + */ + boolean isOwningEntity(PersistentEntity owner); + + /** + * Returns the parent entity of this entity + * + * @return The ParentEntity instance + */ + PersistentEntity getParentEntity(); + + /** + * Obtains the root entity of an inheritance hierarchy + * + * @return The root entity + */ + PersistentEntity getRootEntity(); + + /** + * Whether this entity is a root entity + * + * @return True if it is a root entity + */ + boolean isRoot(); + + /** + * The discriminator used when persisting subclasses of an inheritance hierarchy + * + * @return The discriminator + */ + String getDiscriminator(); + + /** + * Obtains the MappingContext where this PersistentEntity is defined + * + * @return The MappingContext instance + */ + MappingContext getMappingContext(); + + /** + * Checks whether an entity has a bean property of the given name and type + * + * @param name The name + * @param type The type + * @return True if it does + */ + boolean hasProperty(String name, Class type); + + /** + * True if the given property is the identifier + * + * @param propertyName the property name + * @return True if it is the identifier + */ + boolean isIdentityName(String propertyName); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java new file mode 100644 index 000000000..b9591cca4 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentProperty.java @@ -0,0 +1,51 @@ +package org.springframework.data.mapping.model; + +/** + * @author Graeme Rocher + * @since 1.0 + */ +public interface PersistentProperty { + + /** + * The name of the property + * + * @return The property name + */ + String getName(); + + /** + * The name with the first letter in upper case as per Java bean conventions + * + * @return The capitilized name + */ + String getCapitilizedName(); + + /** + * The type of the property + * + * @return The property type + */ + Class getType(); + + /** + * Specifies the mapping between this property and an external form + * such as a column, key/value pair etc. + * + * @return The PropertyMapping instance + */ + PropertyMapping getMapping(); + + /** + * Obtains the owner of this persistent property + * + * @return The owner + */ + PersistentEntity getOwner(); + + /** + * Whether the property can be set to null + * + * @return True if it can + */ + boolean isNullable(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PropertyMapping.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PropertyMapping.java new file mode 100644 index 000000000..45f27ecf5 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PropertyMapping.java @@ -0,0 +1,25 @@ +package org.springframework.data.mapping.model; + +/** + * A marker interface for a property mapping which specifies + * what or where a particular property is mapped to + * + * @author Graeme Rocher + * @since 1.0 + */ +public interface PropertyMapping { + + /** + * Retrieves the ClassMapping instance of the owning class + * + * @return The ClassMapping instance + */ + ClassMapping getClassMapping(); + + /** + * Returns the mapped form of the property such as a Column, a Key/Value pair, attribute etc. + * @return The mapped representation + */ + T getMappedForm(); + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Association.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Association.java new file mode 100644 index 000000000..7829d336b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Association.java @@ -0,0 +1,146 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.*; + +import javax.persistence.CascadeType; +import javax.persistence.FetchType; +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.List; + +/** + * Models an association between one class and another + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class Association extends AbstractPersistentProperty { + + public static final List DEFAULT_OWNER_CASCADE = new ArrayList() {{ + add(CascadeType.ALL); + }}; + + public static final List DEFAULT_CHILD_CASCADE = new ArrayList() {{ + add(CascadeType.PERSIST); + }}; + + private PersistentEntity associatedEntity; + private String referencedPropertyName; + private boolean owningSide; + private List cascadeOperations = new ArrayList(); + private FetchType fetchStrategy = FetchType.EAGER; + + public Association(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public Association(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } + + public FetchType getFetchStrategy() { + return fetchStrategy; + } + + public void setFetchStrategy(FetchType fetchStrategy) { + this.fetchStrategy = fetchStrategy; + } + + public boolean isBidirectional() { + return getInverseSide() != null; + } + + public Association getInverseSide() { + final PersistentProperty associatedProperty = associatedEntity.getPropertyByName(referencedPropertyName); + if(associatedProperty == null) return null; + if(associatedProperty instanceof Association) { + return (Association) associatedProperty; + } + else { + throw new IllegalMappingException("The inverse side ["+associatedEntity.getName()+"." + associatedProperty.getName() +"] of the association ["+getOwner().getName()+"." + this.getName() +"] is not valid. Associations can only map to other entities and collection types."); + } + + } + + /** + * Returns true if the this association cascade for the given cascade operation + * + * @param cascadeOperation The cascadeOperation + * @return True if it does + */ + public boolean doesCascade(CascadeType cascadeOperation) { + List cascades = getCascadeOperations(); + return cascadeOperation != null && (cascades.contains(CascadeType.ALL) || cascades.contains(cascadeOperation)); + } + + protected List getCascadeOperations() { + List cascades; + if(cascadeOperations.isEmpty()) { + if(isOwningSide()) cascades = DEFAULT_OWNER_CASCADE; + else { + cascades = DEFAULT_CHILD_CASCADE; + } + } + else { + cascades = this.cascadeOperations; + } + return cascades; + } + + /** + * Returns whether this side owns the relationship. This controls + * the default cascading behavior if none is specified + * + * @return True if this property is the owning side + */ + public boolean isOwningSide() { + return owningSide; + } + + public void setOwningSide(boolean owningSide) { + this.owningSide = owningSide; + } + + public void setAssociatedEntity(PersistentEntity associatedEntity) { + this.associatedEntity = associatedEntity; + } + + public PersistentEntity getAssociatedEntity() { + return associatedEntity; + } + + public void setReferencedPropertyName(String referencedPropertyName) { + this.referencedPropertyName = referencedPropertyName; + } + + public String getReferencedPropertyName() { + return referencedPropertyName; + } + + @Override + public String toString() { + return getOwner().getName() + "->" + getName(); + } + + public boolean isList() { + return List.class.isAssignableFrom(getType()); + } + + public boolean isCircular() { + return isBidirectional() && getAssociatedEntity().equals(getOwner()); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Basic.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Basic.java new file mode 100644 index 000000000..e99738075 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Basic.java @@ -0,0 +1,65 @@ +/* Copyright (C) 2011 SpringSource + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.springframework.data.mapping.model.types; + +import java.beans.PropertyDescriptor; + +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; +import org.springframework.data.mapping.model.PropertyMapping; + +/** + * Models a basic collection type such as a list of Strings + * + * @author Graeme Rocher + * @since 1.0 + * + */ +public abstract class Basic extends Association { + + public Basic(PersistentEntity owner, MappingContext context, + PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public Basic(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + + } + + @Override + public Association getInverseSide() { + return null; // basic collection types have no inverse side + } + + @Override + public boolean isOwningSide() { + return true; + } + + @Override + public void setOwningSide(boolean owningSide) { + // noop + } + + @Override + public PersistentEntity getAssociatedEntity() { + return null; // basic collection types have no associated entity + } + + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/BasicTypeConverterRegistrar.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/BasicTypeConverterRegistrar.java new file mode 100644 index 000000000..069714d12 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/BasicTypeConverterRegistrar.java @@ -0,0 +1,133 @@ +/* Copyright (C) 2010 SpringSource + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.core.convert.converter.ConverterRegistry; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * A registrar that registers basic type converters + * + * @author Graeme Rocher + * @since 1.0 + */ +public class BasicTypeConverterRegistrar { + + public void register(ConverterRegistry registry) { + registry.addConverter(new Converter() { + public String convert(Date date) { + return String.valueOf(date.getTime()); + } + }); + registry.addConverter(new Converter() { + public Calendar convert(Date date) { + final GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(date); + return calendar; + } + }); + + registry.addConverter(new Converter() { + public Long convert(Integer integer) { + return integer.longValue(); + } + }); + + registry.addConverter(new Converter() { + public Double convert(Integer integer) { + return integer.doubleValue(); + } + }); + + + registry.addConverter(new Converter() { + + public Date convert(CharSequence s) { + try { + final Long time = Long.valueOf(s.toString()); + return new Date(time); + } catch (NumberFormatException e) { + // ignore + } + return null; + } + }); + + registry.addConverter(new Converter() { + + public Double convert(CharSequence s) { + try { + return Double.valueOf(s.toString()); + } catch (NumberFormatException e) { + return (double) 0; + } + } + }); + + registry.addConverter(new Converter() { + + public Integer convert(CharSequence s) { + try { + return Integer.valueOf(s.toString()); + } catch (NumberFormatException e) { + // ignore + } + return 0; + } + }); + registry.addConverter(new Converter() { + + public Long convert(CharSequence s) { + try { + return Long.valueOf(s.toString()); + } catch (NumberFormatException e) { + // ignore + } + return 0L; + } + }); + + + registry.addConverter(new Converter() { + public String convert(Object o) { + return o.toString(); + } + }); + + registry.addConverter(new Converter() { + public String convert(Calendar calendar) { + return String.valueOf(calendar.getTime().getTime()); + } + }); + + registry.addConverter(new Converter() { + + public Calendar convert(CharSequence s) { + try { + Date date = new Date(Long.valueOf(s.toString())); + Calendar c = new GregorianCalendar(); + c.setTime(date); + return c; + } catch (NumberFormatException e) { + return null; + } + } + }); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Embedded.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Embedded.java new file mode 100644 index 000000000..30a5ba191 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Embedded.java @@ -0,0 +1,43 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +import java.beans.PropertyDescriptor; + +/** + * Models an embedded component + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class Embedded extends ToOne { + + public Embedded(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public Embedded(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } + + @Override + public boolean isOwningSide() { + return true; // embedded instances are always owned + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Identity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Identity.java new file mode 100644 index 000000000..ba36e292a --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Identity.java @@ -0,0 +1,36 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.AbstractPersistentProperty; +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +import java.beans.PropertyDescriptor; + +/** + * Represents the identity of a persistent entity + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class Identity extends AbstractPersistentProperty { + public Identity(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + public Identity(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ManyToMany.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ManyToMany.java new file mode 100644 index 000000000..ff5252921 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ManyToMany.java @@ -0,0 +1,36 @@ +/* Copyright (C) 2010 SpringSource + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +import java.beans.PropertyDescriptor; + +/** + * Models a many-to-many association between one class and another + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class ManyToMany extends Association { + public ManyToMany(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public ManyToMany(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ManyToOne.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ManyToOne.java new file mode 100644 index 000000000..29836a98c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ManyToOne.java @@ -0,0 +1,37 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +import java.beans.PropertyDescriptor; + +/** + * Models a many-to-one association + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class ManyToOne extends ToOne { + public ManyToOne(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public ManyToOne(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/OneToMany.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/OneToMany.java new file mode 100644 index 000000000..75d5126e2 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/OneToMany.java @@ -0,0 +1,36 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +import java.beans.PropertyDescriptor; + +/** + * Models a one-to-many association + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class OneToMany extends Association { + public OneToMany(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public OneToMany(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/OneToOne.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/OneToOne.java new file mode 100644 index 000000000..01b065349 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/OneToOne.java @@ -0,0 +1,36 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +import java.beans.PropertyDescriptor; + +/** + * Models a one-to-one association + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class OneToOne extends ToOne { + public OneToOne(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public OneToOne(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Simple.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Simple.java new file mode 100644 index 000000000..2ad2d21ab --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/Simple.java @@ -0,0 +1,37 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.AbstractPersistentProperty; +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +import java.beans.PropertyDescriptor; + +/** + * Models a simple property type + * + * @author Graeme Rocher + * @since 1.0 + */ +public abstract class Simple extends AbstractPersistentProperty { + public Simple(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public Simple(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ToOne.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ToOne.java new file mode 100644 index 000000000..673f047be --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/types/ToOne.java @@ -0,0 +1,47 @@ +/* Copyright (C) 2010 SpringSource + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model.types; + +import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.mapping.model.PersistentEntity; + +import java.beans.PropertyDescriptor; + +/** + * @author Graeme Rocher + * @since 1.1 + */ +public abstract class ToOne extends Association { + + private boolean foreignKeyInChild; + + public ToOne(PersistentEntity owner, MappingContext context, PropertyDescriptor descriptor) { + super(owner, context, descriptor); + } + + public ToOne(PersistentEntity owner, MappingContext context, String name, Class type) { + super(owner, context, name, type); + } + + public void setForeignKeyInChild(boolean foreignKeyInChild) { + this.foreignKeyInChild = foreignKeyInChild; + } + + public boolean isForeignKeyInChild() { + return foreignKeyInChild; + } + + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/reflect/ClassPropertyFetcher.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/reflect/ClassPropertyFetcher.java new file mode 100644 index 000000000..0b3d5c669 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/reflect/ClassPropertyFetcher.java @@ -0,0 +1,372 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.reflect; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.data.mapping.annotation.PersistenceConstructor; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.*; +import java.util.*; + +/** + * Reads the properties of a class in an optimized manner avoiding + * exceptions + * + * @author Graeme Rocher + * @since 1.0 + */ +public class ClassPropertyFetcher { + + private static final Log log = LogFactory.getLog(ClassPropertyFetcher.class); + + private final Class clazz; + final Map staticFetchers = new HashMap(); + final Map instanceFetchers = new HashMap(); + private final ReferenceInstanceCallback callback; + private PropertyDescriptor[] propertyDescriptors; + private Constructor preferredConstructor; + private Map propertyDescriptorsByName = new HashMap(); + private Map fieldsByName = new HashMap(); + private Map> typeToPropertyMap = new HashMap>(); + + private static Map cachedClassPropertyFetchers = new WeakHashMap(); + + public static ClassPropertyFetcher forClass(final Class c) { + ClassPropertyFetcher cpf = cachedClassPropertyFetchers.get(c); + if (cpf == null) { + cpf = new ClassPropertyFetcher(c); + cachedClassPropertyFetchers.put(c, cpf); + } + return cpf; + } + + ClassPropertyFetcher(final Class clazz) { + this.clazz = clazz; + this.callback = new ReferenceInstanceCallback() { + public Object getReferenceInstance() { + try { + return ReflectionUtils.instantiate(clazz); + } catch (InstantiationException e) { + throw new IllegalStateException(e); + } + } + }; + init(); + } + + /** + * @return The Java that this ClassPropertyFetcher was constructor for + */ + public Class getJavaClass() { + return this.clazz; + } + + public Object getReference() { + if (callback != null) + return this.callback.getReferenceInstance(); + return null; + } + + public PropertyDescriptor[] getPropertyDescriptors() { + return propertyDescriptors; + } + + public boolean isReadableProperty(String name) { + return staticFetchers.containsKey(name) || instanceFetchers.containsKey(name); + } + + private void init() { + + List allClasses = resolveAllClasses(clazz); + for (Class c : allClasses) { + Field[] fields = c.getDeclaredFields(); + for (Field field : fields) { + processField(field); + } + Method[] methods = c.getDeclaredMethods(); + for (Method method : methods) { + processMethod(method); + } + } + + try { + this.propertyDescriptors = Introspector.getBeanInfo(clazz).getPropertyDescriptors(); + } catch (IntrospectionException e) { + // ignore + } + if (propertyDescriptors != null) { + for (PropertyDescriptor desc : propertyDescriptors) { + propertyDescriptorsByName.put(desc.getName(), desc); + final Class propertyType = desc.getPropertyType(); + List pds = typeToPropertyMap.get(propertyType); + if (pds == null) { + pds = new ArrayList(); + typeToPropertyMap.put(propertyType, pds); + } + pds.add(desc); + + Method readMethod = desc.getReadMethod(); + if (readMethod != null) { + boolean staticReadMethod = Modifier.isStatic(readMethod.getModifiers()); + if (staticReadMethod) { + staticFetchers.put(desc.getName(), new GetterPropertyFetcher(readMethod, staticReadMethod)); + } else { + instanceFetchers.put(desc.getName(), new GetterPropertyFetcher(readMethod, staticReadMethod)); + } + } + } + } + + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length == 1 && constructors[0].getGenericParameterTypes().length > 0) { + preferredConstructor = constructors[0]; + } else { + for (Constructor c : constructors) { + if (c.isAnnotationPresent(PersistenceConstructor.class)) { + preferredConstructor = c; + } + } + } + + } + + public Constructor getPreferredConstructor() { + return preferredConstructor; + } + + private void processMethod(Method method) { + if (method.isSynthetic()) + return; + if (!Modifier.isPublic(method.getModifiers())) + return; + if (Modifier.isStatic(method.getModifiers()) + && method.getReturnType() != Void.class) { + if (method.getParameterTypes().length == 0) { + String name = method.getName(); + if (name.indexOf('$') == -1) { + if (name.length() > 3 && name.startsWith("get") && Character.isUpperCase(name.charAt(3))) { + name = name.substring(3); + } else if (name.length() > 2 + && name.startsWith("is") + && Character.isUpperCase(name.charAt(2)) + && (method.getReturnType() == Boolean.class || method.getReturnType() == boolean.class)) { + name = name.substring(2); + } + PropertyFetcher fetcher = new GetterPropertyFetcher(method, true); + staticFetchers.put(name, fetcher); + staticFetchers.put(Introspector.decapitalize(name), fetcher); + } + } + } + } + + private void processField(Field field) { + if (field.isSynthetic()) + return; + final int modifiers = field.getModifiers(); + final String name = field.getName(); + if (!Modifier.isPublic(modifiers)) { + if (name.indexOf('$') == -1) { + fieldsByName.put(name, field); + } + } else { + if (name.indexOf('$') == -1) { + boolean staticField = Modifier.isStatic(modifiers); + if (staticField) { + staticFetchers.put(name, new FieldReaderFetcher(field, staticField)); + } else { + instanceFetchers.put(name, new FieldReaderFetcher(field, staticField)); + } + } + } + + } + + private List resolveAllClasses(Class c) { + List list = new ArrayList(); + Class currentClass = c; + while (currentClass != null) { + list.add(currentClass); + currentClass = currentClass.getSuperclass(); + } + Collections.reverse(list); + return list; + } + + public Object getPropertyValue(String name) { + return getPropertyValue(name, false); + } + + public Object getPropertyValue(String name, boolean onlyInstanceProperties) { + PropertyFetcher fetcher = resolveFetcher(name, onlyInstanceProperties); + return getPropertyValueWithFetcher(name, fetcher); + } + + private Object getPropertyValueWithFetcher(String name, PropertyFetcher fetcher) { + if (fetcher != null) { + try { + return fetcher.get(callback); + } catch (Exception e) { + log.warn("Error fetching property's " + name + " value from class " + clazz.getName(), e); + } + } + return null; + } + + public T getStaticPropertyValue(String name, Class c) { + PropertyFetcher fetcher = staticFetchers.get(name); + if (fetcher != null) { + Object v = getPropertyValueWithFetcher(name, fetcher); + return returnOnlyIfInstanceOf(v, c); + } + return null; + } + + public T getPropertyValue(String name, Class c) { + return returnOnlyIfInstanceOf(getPropertyValue(name, false), c); + } + + private T returnOnlyIfInstanceOf(Object value, Class type) { + if ((value != null) && (type == Object.class || ReflectionUtils.isAssignableFrom(type, value.getClass()))) { + return (T) value; + } else { + return null; + } + } + + + private PropertyFetcher resolveFetcher(String name, + boolean onlyInstanceProperties) { + PropertyFetcher fetcher = null; + if (!onlyInstanceProperties) { + fetcher = staticFetchers.get(name); + } + if (fetcher == null) { + fetcher = instanceFetchers.get(name); + } + return fetcher; + } + + public Class getPropertyType(String name) { + return getPropertyType(name, false); + } + + public Class getPropertyType(String name, boolean onlyInstanceProperties) { + PropertyFetcher fetcher = resolveFetcher(name, onlyInstanceProperties); + if (fetcher != null) { + return fetcher.getPropertyType(name); + } + return null; + } + + public PropertyDescriptor getPropertyDescriptor(String name) { + return propertyDescriptorsByName.get(name); + } + + public List getPropertiesOfType(Class javaClass) { + final List propertyDescriptorList = typeToPropertyMap.get(javaClass); + if (propertyDescriptorList == null) return Collections.emptyList(); + return propertyDescriptorList; + } + + public List getPropertiesAssignableToType(Class assignableType) { + List properties = new ArrayList(); + for (Class type : typeToPropertyMap.keySet()) { + if (assignableType.isAssignableFrom(type)) { + properties.addAll(typeToPropertyMap.get(type)); + } + } + return properties; + } + + public static interface ReferenceInstanceCallback { + public Object getReferenceInstance(); + } + + static interface PropertyFetcher { + public Object get(ReferenceInstanceCallback callback) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException; + + public Class getPropertyType(String name); + } + + static class GetterPropertyFetcher implements PropertyFetcher { + private final Method readMethod; + private final boolean staticMethod; + + GetterPropertyFetcher(Method readMethod, boolean staticMethod) { + this.readMethod = readMethod; + this.staticMethod = staticMethod; + ReflectionUtils.makeAccessible(readMethod); + } + + public Object get(ReferenceInstanceCallback callback) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + if (staticMethod) { + return readMethod.invoke(null, (Object[]) null); + } else { + if (callback != null) { + return readMethod.invoke(callback.getReferenceInstance(), + (Object[]) null); + } else { + return null; + } + } + } + + public Class getPropertyType(String name) { + return readMethod.getReturnType(); + } + } + + static class FieldReaderFetcher implements PropertyFetcher { + private final Field field; + private final boolean staticField; + + public FieldReaderFetcher(Field field, boolean staticField) { + this.field = field; + this.staticField = staticField; + ReflectionUtils.makeAccessible(field); + } + + public Object get(ReferenceInstanceCallback callback) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + if (staticField) { + return field.get(null); + } else { + if (callback != null) { + return field.get(callback.getReferenceInstance()); + } else { + return null; + } + } + } + + public Class getPropertyType(String name) { + return field.getType(); + } + } + + public Field getDeclaredField(String name) { + return fieldsByName.get(name); + } +} \ No newline at end of file diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/reflect/ReflectionUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/reflect/ReflectionUtils.java new file mode 100644 index 000000000..ebc3428df --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/reflect/ReflectionUtils.java @@ -0,0 +1,209 @@ +/* Copyright 2004-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.reflect; + +import org.springframework.beans.BeanUtils; + +import java.beans.PropertyDescriptor; +import java.lang.Boolean; +import java.lang.Byte; +import java.lang.Character; +import java.lang.Class; +import java.lang.Double; +import java.lang.Exception; +import java.lang.Float; +import java.lang.IllegalAccessException; +import java.lang.InstantiationException; +import java.lang.Integer; +import java.lang.Long; +import java.lang.NoSuchMethodException; +import java.lang.NullPointerException; +import java.lang.Object; +import java.lang.Short; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Provides methods to help with reflective operations + * + * @author Graeme Rocher + * @since 1.0 + */ +public class ReflectionUtils { + + public static final Map, Class> PRIMITIVE_TYPE_COMPATIBLE_CLASSES = new HashMap, Class>(); + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + /** + * Just add two entries to the class compatibility map + * + * @param left + * @param right + */ + private static void registerPrimitiveClassPair(Class left, Class right) { + PRIMITIVE_TYPE_COMPATIBLE_CLASSES.put(left, right); + PRIMITIVE_TYPE_COMPATIBLE_CLASSES.put(right, left); + } + + static { + registerPrimitiveClassPair(Boolean.class, boolean.class); + registerPrimitiveClassPair(Integer.class, int.class); + registerPrimitiveClassPair(Short.class, short.class); + registerPrimitiveClassPair(Byte.class, byte.class); + registerPrimitiveClassPair(Character.class, char.class); + registerPrimitiveClassPair(Long.class, long.class); + registerPrimitiveClassPair(Float.class, float.class); + registerPrimitiveClassPair(Double.class, double.class); + } + + + /** + * Make the given field accessible, explicitly setting it accessible if necessary. + * The setAccessible(true) method is only called when actually necessary, + * to avoid unnecessary conflicts with a JVM SecurityManager (if active). + *

+ * Based on the same method in Spring core. + * + * @param field the field to make accessible + * @see java.lang.reflect.Field#setAccessible + */ + public static void makeAccessible(Field field) { + if (!Modifier.isPublic(field.getModifiers()) || + !Modifier.isPublic(field.getDeclaringClass().getModifiers())) { + field.setAccessible(true); + } + } + + /** + * Make the given method accessible, explicitly setting it accessible if necessary. + * The setAccessible(true) method is only called when actually necessary, + * to avoid unnecessary conflicts with a JVM SecurityManager (if active). + *

+ * Based on the same method in Spring core. + * + * @param method the method to make accessible + * @see java.lang.reflect.Method#setAccessible + */ + public static void makeAccessible(Method method) { + if (!Modifier.isPublic(method.getModifiers()) || + !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { + method.setAccessible(true); + } + } + + /** + *

Tests whether or not the left hand type is compatible with the right hand type in Groovy + * terms, i.e. can the left type be assigned a value of the right hand type in Groovy.

+ *

This handles Java primitive type equivalence and uses isAssignableFrom for all other types, + * with a bit of magic for native types and polymorphism i.e. Number assigned an int. + * If either parameter is null an exception is thrown

+ * + * @param leftType The type of the left hand part of a notional assignment + * @param rightType The type of the right hand part of a notional assignment + * @return True if values of the right hand type can be assigned in Groovy to variables of the left hand type. + */ + public static boolean isAssignableFrom(final Class leftType, final Class rightType) { + if (leftType == null) { + throw new NullPointerException("Left type is null!"); + } + if (rightType == null) { + throw new NullPointerException("Right type is null!"); + } + if (leftType == Object.class) { + return true; + } + if (leftType == rightType) { + return true; + } + // check for primitive type equivalence + Class r = PRIMITIVE_TYPE_COMPATIBLE_CLASSES.get(leftType); + boolean result = r == rightType; + + if (!result) { + // If no primitive <-> wrapper match, it may still be assignable + // from polymorphic primitives i.e. Number -> int (AKA Integer) + if (rightType.isPrimitive()) { + // see if incompatible + r = PRIMITIVE_TYPE_COMPATIBLE_CLASSES.get(rightType); + if (r != null) { + result = leftType.isAssignableFrom(r); + } + } else { + // Otherwise it may just be assignable using normal Java polymorphism + result = leftType.isAssignableFrom(rightType); + } + } + return result; + } + + /** + * Instantiates an object catching any relevant exceptions and rethrowing as a runtime exception + * + * @param clazz The class + * @return The instantiated object or null if the class parameter was null + */ + public static Object instantiate(Class clazz) throws InstantiationException { + if (clazz == null) return null; + try { + return clazz.getConstructor(EMPTY_CLASS_ARRAY).newInstance(); + } catch (IllegalAccessException e) { + throw new InstantiationException(e.getClass().getName() + " error creating instance of class [" + e.getMessage() + "]: " + e.getMessage()); + } catch (InvocationTargetException e) { + throw new InstantiationException(e.getClass().getName() + " error creating instance of class [" + e.getMessage() + "]: " + e.getMessage()); + } catch (NoSuchMethodException e) { + throw new InstantiationException(e.getClass().getName() + " error creating instance of class [" + e.getMessage() + "]: " + e.getMessage()); + } catch (java.lang.InstantiationException e) { + throw new InstantiationException(e.getClass().getName() + " error creating instance of class [" + e.getMessage() + "]: " + e.getMessage()); + } + } + + /** + * Retrieves all the properties of the given class for the given type + * + * @param clazz The class to retrieve the properties from + * @param propertyType The type of the properties you wish to retrieve + * @return An array of PropertyDescriptor instances + */ + public static PropertyDescriptor[] getPropertiesOfType(Class clazz, Class propertyType) { + if (clazz == null || propertyType == null) { + return new PropertyDescriptor[0]; + } + + Set properties = new HashSet(); + try { + for (PropertyDescriptor descriptor : BeanUtils.getPropertyDescriptors(clazz)) { + Class currentPropertyType = descriptor.getPropertyType(); + if (isTypeInstanceOfPropertyType(propertyType, currentPropertyType)) { + properties.add(descriptor); + } + } + } catch (Exception e) { + // if there are any errors in instantiating just return null for the moment + return new PropertyDescriptor[0]; + } + return properties.toArray(new PropertyDescriptor[properties.size()]); + } + + private static boolean isTypeInstanceOfPropertyType(Class type, Class propertyType) { + return propertyType.isAssignableFrom(type) && !propertyType.equals(Object.class); + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java index 825591680..76c5a1ce5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java @@ -31,6 +31,7 @@ import org.springframework.dao.DataAccessException; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import org.springframework.data.document.mongodb.convert.MongoConverter; /** * Abstract base class for unit tests to specify behaviour we expect from {@link MongoOperations}. Subclasses return diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateTests.java index b96033a6c..18ec12d60 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateTests.java @@ -39,6 +39,7 @@ import org.springframework.data.document.mongodb.query.Index.Duplicates; import org.springframework.data.document.mongodb.query.Order; import org.springframework.data.document.mongodb.query.Query; import org.springframework.data.document.mongodb.query.Update; +import org.springframework.data.document.mongodb.convert.MongoConverter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateUnitTests.java index b3b0814e6..d0802ea48 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateUnitTests.java @@ -24,6 +24,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.dao.DataAccessException; +import org.springframework.data.document.mongodb.convert.SimpleMongoConverter; import org.springframework.test.util.ReflectionTestUtils; import com.mongodb.DB; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/SimpleMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/SimpleMongoConverterTests.java index 9ad81683e..1dd63e7f9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/SimpleMongoConverterTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/SimpleMongoConverterTests.java @@ -29,6 +29,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.data.document.mongodb.SomeEnumTest.NumberEnum; import org.springframework.data.document.mongodb.SomeEnumTest.StringEnum; +import org.springframework.data.document.mongodb.convert.SimpleMongoConverter; import org.springframework.util.ReflectionUtils; import com.mongodb.BasicDBObject; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Account.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Account.java new file mode 100644 index 000000000..cd53ae0eb --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Account.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import org.springframework.data.mapping.annotation.Id; +import org.springframework.data.mapping.annotation.Persistent; + +/** + * @author Jon Brisbin + */ +@Persistent +public class Account { + + @Id + private String id; + private Float balance; + + public Float getBalance() { + return balance; + } + + public void setBalance(Float balance) { + this.balance = balance; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Address.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Address.java new file mode 100644 index 000000000..354e8ea81 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Address.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import java.io.Serializable; + +/** + * @author Jon Brisbin + */ +public class Address implements Serializable { + + private String[] lines; + private String city; + private String provinceOrState; + private Integer postalCode; + private String country; + + public String[] getLines() { + return lines; + } + + public void setLines(String[] lines) { + this.lines = lines; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getProvinceOrState() { + return provinceOrState; + } + + public void setProvinceOrState(String provinceOrState) { + this.provinceOrState = provinceOrState; + } + + public Integer getPostalCode() { + return postalCode; + } + + public void setPostalCode(Integer postalCode) { + this.postalCode = postalCode; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java new file mode 100644 index 000000000..e9a64ec5d --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.document.mongodb.convert.MongoConverter; +import org.springframework.data.document.mongodb.query.Criteria; +import org.springframework.data.document.mongodb.query.Index; +import org.springframework.data.document.mongodb.query.IndexDefinition; +import org.springframework.data.document.mongodb.query.Query; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.endsWith; +import static org.junit.Assert.assertThat; + +/** + * @author Jon Brisbin + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:mapping.xml") +public class MappingTests { + + @Autowired + MongoTemplate template; + @Autowired + MongoMappingContext mappingContext; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void setUp() { + template.dropCollection(template.getDefaultCollectionName()); + mappingContext.addPersistentEntity(Person.class); + } + + @Test + public void testWrite() { + Person p = new Person(123456789, "John", "Doe", 37); + + Address addr = new Address(); + addr.setLines(new String[]{"1234 W. 1st Street", "Apt. 12"}); + addr.setCity("Anytown"); + addr.setPostalCode(12345); + addr.setCountry("USA"); + p.setAddress(addr); + + Account acct = new Account(); + acct.setBalance(1000.00f); + List accounts = new ArrayList(); + accounts.add(acct); + p.setAccounts(accounts); + + template.insert(p); + } + + @Test + public void testRead() { + MongoConverter converter = template.getConverter(); + + List result = template.find(new Query(Criteria.where("ssn").is(123456789)), Person.class); + assertThat(result.size(), is(1)); + assertThat(result.get(0).getAddress().getCountry(), is("USA")); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Person.java new file mode 100644 index 000000000..74900f4f5 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Person.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.mongodb.mapping; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.document.mongodb.index.CompoundIndex; +import org.springframework.data.document.mongodb.index.CompoundIndexes; +import org.springframework.data.document.mongodb.index.Indexed; +import org.springframework.data.mapping.annotation.*; + +import java.math.BigInteger; +import java.util.List; + +/** + * @author Jon Brisbin + */ +@Persistent +@CompoundIndexes({ + @CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}") +}) +public class Person { + + @Id + private String id; + @Indexed(unique = true) + private Integer ssn; + private String firstName; + @Indexed + private String lastName; + private Integer age; + @Transient + private Integer accountTotal; + @OneToMany + private List accounts; + private Address address; + + public Person(Integer ssn, String firstName, String lastName, Integer age) { + this.ssn = ssn; + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + } + + @PersistenceConstructor + public Person(Integer ssn, String firstName, String lastName, Integer age, List accounts, Address address) { + this.ssn = ssn; + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.accounts = accounts; + this.address = address; + } + + public Integer getSsn() { + return ssn; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Integer getAccountTotal() { + return accountTotal; + } + + public void setAccountTotal(Integer accountTotal) { + this.accountTotal = accountTotal; + } + + public List getAccounts() { + return accounts; + } + + public void setAccounts(List accounts) { + this.accounts = accounts; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoQueryCreatorUnitTests.java index bb92ca28f..9aeafec75 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoQueryCreatorUnitTests.java @@ -25,7 +25,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.data.document.mongodb.MongoConverter; +import org.springframework.data.document.mongodb.convert.MongoConverter; import org.springframework.data.document.mongodb.Person; import org.springframework.data.repository.query.parser.PartTree; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java index 2255b932f..b103088e7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/StringBasedMongoQueryUnitTests.java @@ -26,9 +26,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.data.document.mongodb.MongoConverter; +import org.springframework.data.document.mongodb.convert.MongoConverter; import org.springframework.data.document.mongodb.MongoTemplate; -import org.springframework.data.document.mongodb.SimpleMongoConverter; +import org.springframework.data.document.mongodb.convert.SimpleMongoConverter; import org.springframework.data.document.mongodb.query.BasicQuery; /** diff --git a/spring-data-mongodb/src/test/resources/infrastructure.xml b/spring-data-mongodb/src/test/resources/infrastructure.xml index 0e0d85184..0921d9a25 100644 --- a/spring-data-mongodb/src/test/resources/infrastructure.xml +++ b/spring-data-mongodb/src/test/resources/infrastructure.xml @@ -1,19 +1,19 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> - - - - + + + + - - - - - - - + + + + + + + diff --git a/spring-data-mongodb/src/test/resources/mapping.xml b/spring-data-mongodb/src/test/resources/mapping.xml new file mode 100644 index 000000000..2f1dbe0b0 --- /dev/null +++ b/spring-data-mongodb/src/test/resources/mapping.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-data-mongodb/template.mf b/spring-data-mongodb/template.mf index 7cc16ff69..7f879da16 100644 --- a/spring-data-mongodb/template.mf +++ b/spring-data-mongodb/template.mf @@ -15,11 +15,17 @@ Import-Template: org.springframework.data.domain.*;version="[1.0.0, 2.0.0)", org.springframework.data.document.*;version="[1.0.0, 2.0.0)", org.springframework.data.repository.*;version="[1.0.0, 2.0.0)", + org.springframework.expression.*;version="[3.0.0, 4.0.0)", + org.springframework.expression.common.*;version="[3.0.0, 4.0.0)", + org.springframework.expression.spel.standard.*;version="[3.0.0, 4.0.0)", + org.springframework.expression.spel.support.*;version="[3.0.0, 4.0.0)", + org.springframework.validation.*;version="[3.0.0, 4.0.0)", com.mongodb.*;version="0", org.bson.*;version="0", org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", org.slf4j.*;version="[1.5.0,1.6.0)", - org.w3c.dom.*;version="0" + org.w3c.dom.*;version="0", + javax.persistence.*;version="0"