diff --git a/pom.xml b/pom.xml index 667c3c198..10907b79f 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ spring-data-document-parent spring-data-document-core spring-data-mongodb + spring-data-mongodb-cross-store spring-data-couchdb diff --git a/spring-data-couchdb/.classpath b/spring-data-couchdb/.classpath index 0bb7ad5ca..2064dbb9b 100644 --- a/spring-data-couchdb/.classpath +++ b/spring-data-couchdb/.classpath @@ -1,6 +1,9 @@ + + + diff --git a/spring-data-couchdb/pom.xml b/spring-data-couchdb/pom.xml index e9fbd9cbb..6914e22c0 100644 --- a/spring-data-couchdb/pom.xml +++ b/spring-data-couchdb/pom.xml @@ -10,6 +10,43 @@ spring-data-couchdb jar Spring Data CouchDB Support + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + tareq.abedrabbo + Tareq Abedrabbo + tareq.abedrabbo@opencredo.com + OpenCredo + http://www.opencredo.org + + Project Admin + Developer + + +0 + + + tomas.lukosius + Tomas Lukosius + tomas.lukosius@opencredo.com + OpenCredo + http://www.opencredo.org + + Project Admin + Developer + + +0 + + + @@ -21,6 +58,12 @@ org.springframework spring-tx + + + org.springframework + spring-test + test + @@ -28,6 +71,18 @@ spring-data-document-core + + + org.codehaus.jackson + jackson-core-asl + 1.6.1 + + + org.codehaus.jackson + jackson-mapper-asl + 1.6.1 + + org.slf4j @@ -82,16 +137,18 @@ junit junit + test - - + - com.google.code.jcouchdb - jcouchdb - 0.11.0-1 - + org.hamcrest + hamcrest-all + 1.1 + test + + diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CannotGetCouchDbConnectionException.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CannotGetCouchDbConnectionException.java deleted file mode 100644 index a2fb40054..000000000 --- a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CannotGetCouchDbConnectionException.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.springframework.data.document.couchdb; - -import org.springframework.dao.DataAccessResourceFailureException; - -public class CannotGetCouchDbConnectionException extends DataAccessResourceFailureException { - - public CannotGetCouchDbConnectionException(String msg, Throwable cause) { - super(msg, cause); - } - -} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchDbFactoryBean.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchDbFactoryBean.java deleted file mode 100644 index e243dee4c..000000000 --- a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchDbFactoryBean.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.document.couchdb; - -import org.jcouchdb.db.Database; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.support.PersistenceExceptionTranslator; -import org.springframework.util.Assert; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * Convenient factory for configuring MongoDB. - * - * @author Thomas Risberg - * @since 1.0 - */ -public class CouchDbFactoryBean implements FactoryBean, InitializingBean, - PersistenceExceptionTranslator { - - /** - * Logger, available to subclasses. - */ - protected final Log logger = LogFactory.getLog(getClass()); - - private String host; - private Integer port; - private String databaseName; - - public void setDatabaseName(String databaseName) { - this.databaseName = databaseName; - } - - public void setHost(String host) { - this.host = host; - } - - public void setPort(int port) { - this.port = port; - } - - public Database getObject() throws Exception { - Assert.hasText(host, "Host must not be empty"); - Assert.hasText(databaseName, "Database name must not be empty"); - if (port == null) { - return new Database(host, databaseName); - } - else { - return new Database(host, port, databaseName); - } - } - - public Class getObjectType() { - return Database.class; - } - - public boolean isSingleton() { - return false; - } - - public void afterPropertiesSet() throws Exception { - // apply defaults - convenient when used to configure for tests - // in an application context - if (host == null) { - logger.warn("Property host not specified. Using default 'localhost'"); - databaseName = "localhost"; - } - if (databaseName == null) { - logger.warn("Property databaseName not specified. Using default name 'test'"); - databaseName = "test"; - } - } - - public DataAccessException translateExceptionIfPossible(RuntimeException ex) { - logger.debug("Translating " + ex); - return CouchDbUtils.translateCouchExceptionIfPossible(ex); - } - -} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchServerResourceUsageException.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchServerResourceUsageException.java new file mode 100644 index 000000000..ce8dee340 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchServerResourceUsageException.java @@ -0,0 +1,33 @@ +/* + * Copyright 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.couchdb; + +import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.web.client.HttpServerErrorException; + +public class CouchServerResourceUsageException extends InvalidDataAccessResourceUsageException { + + /** + * Create a new CouchServerResourceUsageException, + * wrapping an arbitrary HttpServerErrorException. + * @param cause the HttpServerErrorException thrown + */ + public CouchServerResourceUsageException(HttpServerErrorException cause) { + super(cause != null ? cause.getMessage() : null, cause); + } + +} + diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchTemplate.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchTemplate.java deleted file mode 100644 index 0e646ab02..000000000 --- a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchTemplate.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.document.couchdb; - - -import org.jcouchdb.db.Database; -import org.jcouchdb.document.BaseDocument; -import org.springframework.data.document.AbstractDocumentStoreTemplate; - - public class CouchTemplate extends AbstractDocumentStoreTemplate { - - private Database database; - - public CouchTemplate() { - super(); - } - - public CouchTemplate(String host, String databaseName) { - super(); - database = new Database(host, databaseName); - } - - public CouchTemplate(Database database) { - super(); - this.database = database; - } - - public void save(BaseDocument document) { - getConnection().createDocument(document); - } - - @Override - public Database getConnection() { - return database; - } - - -} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchUsageException.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchUsageException.java new file mode 100644 index 000000000..95f17ef92 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchUsageException.java @@ -0,0 +1,35 @@ +/* + * Copyright 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.couchdb; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; + +public class CouchUsageException extends InvalidDataAccessApiUsageException + { + + /** + * Create a new CouchUsageException, + * wrapping an arbitrary HttpServerErrorException. + * @param cause the HttpServerErrorException thrown + */ + public CouchUsageException(HttpClientErrorException cause) { + super(cause != null ? cause.getMessage() : null, cause); + } + +} + diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/DocumentExistsException.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/DocumentExistsException.java new file mode 100644 index 000000000..d564211a8 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/DocumentExistsException.java @@ -0,0 +1,34 @@ +/* + * Copyright 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.couchdb; + +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.web.client.HttpStatusCodeException; + +public class DocumentExistsException extends DataIntegrityViolationException + { + + /** + * Create a new DocumentExistsException, + * wrapping an arbitrary HttpServerErrorException. + * @param cause the HttpServerErrorException thrown + */ + public DocumentExistsException(String documentId, HttpStatusCodeException cause) { + super(cause != null ? cause.getMessage() : null, cause); + } + +} + diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/DocumentRetrievalFailureException.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/DocumentRetrievalFailureException.java new file mode 100644 index 000000000..c35a1ff6d --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/DocumentRetrievalFailureException.java @@ -0,0 +1,38 @@ +/* + * Copyright 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.couchdb; + +import org.springframework.dao.DataRetrievalFailureException; + +public class DocumentRetrievalFailureException extends + DataRetrievalFailureException { + + private String documentPath; + + public DocumentRetrievalFailureException(String documentPath) { + super("Could not find document at path = " + documentPath); + this.documentPath = documentPath; + } + + public String getDocumentPath() { + return documentPath; + } + + + + + +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/UncategorizedCouchDataAccessException.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/UncategorizedCouchDataAccessException.java new file mode 100644 index 000000000..d951e5353 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/UncategorizedCouchDataAccessException.java @@ -0,0 +1,33 @@ +/* + * Copyright 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.couchdb; + +import org.springframework.dao.UncategorizedDataAccessException; +import org.springframework.web.client.RestClientException; + +public class UncategorizedCouchDataAccessException extends UncategorizedDataAccessException { + + /** + * Create a new HibernateSystemException, + * wrapping an arbitrary HibernateException. + * @param cause the HibernateException thrown + */ + public UncategorizedCouchDataAccessException(RestClientException cause) { + super(cause != null ? cause.getMessage() : null, cause); + } + +} + diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/CouchAdmin.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/CouchAdmin.java new file mode 100644 index 000000000..f9c0eb62d --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/CouchAdmin.java @@ -0,0 +1,64 @@ +/* + * Copyright 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.couchdb.admin; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.springframework.data.document.couchdb.support.CouchUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + +public class CouchAdmin implements CouchAdminOperations { + + private String databaseUrl; + private RestOperations restOperations = new RestTemplate(); + + public CouchAdmin(String databaseUrl) { + + if (!databaseUrl.trim().endsWith("/")) { + this.databaseUrl = databaseUrl.trim() + "/"; + } else { + this.databaseUrl = databaseUrl.trim(); + } + } + + public List listDatabases() { + String dbs = restOperations.getForObject(databaseUrl + "_all_dbs", String.class); + return Arrays.asList(StringUtils.commaDelimitedListToStringArray(dbs)); + } + + public void createDatabase(String dbName) { + org.springframework.util.Assert.hasText(dbName); + restOperations.put(databaseUrl + dbName, null); + + } + + public void deleteDatabase(String dbName) { + org.springframework.util.Assert.hasText(dbName); + restOperations.delete(CouchUtils.ensureTrailingSlash(databaseUrl + dbName)); + + } + + public DbInfo getDatabaseInfo(String dbName) { + String url = CouchUtils.ensureTrailingSlash(databaseUrl + dbName); + Map dbInfoMap = (Map) restOperations.getForObject(url, Map.class); + return new DbInfo(dbInfoMap); + } + +} \ No newline at end of file diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/CouchAdminOperations.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/CouchAdminOperations.java new file mode 100644 index 000000000..4c00dd68e --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/CouchAdminOperations.java @@ -0,0 +1,34 @@ +/* + * Copyright 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.couchdb.admin; + +import java.util.List; + +public interface CouchAdminOperations { + + + // functionality for /_special - replication, logs, UUIDs + + List listDatabases(); + + void createDatabase(String name); + + void deleteDatabase(String name); + + DbInfo getDatabaseInfo(String name); + + +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/DbInfo.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/DbInfo.java new file mode 100644 index 000000000..d8aa62b42 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/admin/DbInfo.java @@ -0,0 +1,77 @@ +/* + * Copyright 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.couchdb.admin; + +import java.util.Collections; +import java.util.Map; + +public class DbInfo { + + private Map dbInfoMap; + + public DbInfo(Map dbInfoMap) { + super(); + this.dbInfoMap = dbInfoMap; + } + + public boolean isCompactRunning() { + return (Boolean) this.dbInfoMap.get("compact_running"); + } + + public String getDbName() { + return (String) this.dbInfoMap.get("db_name"); + } + + public long getDiskFormatVersion() { + return (Long) this.dbInfoMap.get("disk_format_version"); + } + + public long getDiskSize() { + return (Long) this.dbInfoMap.get("disk_size"); + } + + public long getDocCount() { + return (Long) this.dbInfoMap.get("doc_count"); + } + + public long getDocDeleteCount() { + return (Long) this.dbInfoMap.get("doc_del_count"); + } + + public long getInstanceStartTime() { + return (Long) this.dbInfoMap.get("instance_start_time"); + } + + public long getPurgeSequence() { + return (Long) this.dbInfoMap.get("purge_seq"); + } + + public long getUpdateSequence() { + return (Long) this.dbInfoMap.get("update_seq"); + } + + public Map getDbInfoMap() { + return Collections.unmodifiableMap(dbInfoMap); + } + + + + + + + +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/config/CouchJmxParser.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/config/CouchJmxParser.java new file mode 100644 index 000000000..4521dc838 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/config/CouchJmxParser.java @@ -0,0 +1,70 @@ +/* + * Copyright 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.couchdb.config; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.parsing.CompositeComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.data.document.couchdb.monitor.ServerInfo; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +public class CouchJmxParser implements BeanDefinitionParser { + + public BeanDefinition parse(Element element, ParserContext parserContext) { + String databaseUrl = element.getAttribute("database-url"); + if (!StringUtils.hasText(databaseUrl)) { + databaseUrl = "http://localhost:5984"; + } + registerJmxComponents(databaseUrl, element, parserContext); + return null; + } + + protected void registerJmxComponents(String databaseUrl, Element element, ParserContext parserContext) { + Object eleSource = parserContext.extractSource(element); + + CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); + + /* + createBeanDefEntry(AssertMetrics.class, compositeDef, mongoRefName, eleSource, parserContext); + createBeanDefEntry(BackgroundFlushingMetrics.class, compositeDef, mongoRefName, eleSource, parserContext); + createBeanDefEntry(BtreeIndexCounters.class, compositeDef, mongoRefName, eleSource, parserContext); + createBeanDefEntry(ConnectionMetrics.class, compositeDef, mongoRefName, eleSource, parserContext); + createBeanDefEntry(GlobalLockMetrics.class, compositeDef, mongoRefName, eleSource, parserContext); + createBeanDefEntry(MemoryMetrics.class, compositeDef, mongoRefName, eleSource, parserContext); + createBeanDefEntry(OperationCounters.class, compositeDef, mongoRefName, eleSource, parserContext); + */ + createBeanDefEntry(ServerInfo.class, compositeDef, databaseUrl, eleSource, parserContext); + //createBeanDefEntry(MongoAdmin.class, compositeDef, mongoRefName, eleSource, parserContext); + + + parserContext.registerComponent(compositeDef); + + } + + protected void createBeanDefEntry(Class clazz, CompositeComponentDefinition compositeDef, String databaseUrl, Object eleSource, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); + builder.getRawBeanDefinition().setSource(eleSource); + builder.addConstructorArg(databaseUrl); + BeanDefinition assertDef = builder.getBeanDefinition(); + String assertName = parserContext.getReaderContext().registerWithGeneratedName(assertDef); + compositeDef.addNestedComponent(new BeanComponentDefinition(assertDef, assertName)); + } + +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/config/CouchNamespaceHandler.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/config/CouchNamespaceHandler.java new file mode 100644 index 000000000..d47e1c76b --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/config/CouchNamespaceHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 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.couchdb.config; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + + +/** + * {@link org.springframework.beans.factory.xml.NamespaceHandler} for Couch DB + * based repositories. + * + * @author Oliver Gierke + */ +public class CouchNamespaceHandler extends NamespaceHandlerSupport { + + /* + * (non-Javadoc) + * + * @see org.springframework.beans.factory.xml.NamespaceHandler#init() + */ + public void init() { + + /* + registerBeanDefinitionParser("repositories", + new MongoRepositoryConfigDefinitionParser()); + */ + + registerBeanDefinitionParser("jmx", new CouchJmxParser()); + } +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/CouchOperations.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/CouchOperations.java new file mode 100644 index 000000000..b1573dc00 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/CouchOperations.java @@ -0,0 +1,63 @@ +/* + * Copyright 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.couchdb.core; + +import java.net.URI; + + +public interface CouchOperations { + + /** + * Reads a document from the database and maps it a Java object. + *

+ * This method is intended to work when a default database + * is set on the CouchDbDocumentOperations instance. + * + * @param id the id of the CouchDB document to read + * @param targetClass the target type to map to + * @return the mapped object + */ + T findOne(String id, Class targetClass); + + /** + * Reads a document from the database and maps it a Java object. + * + * @param uri the full URI of the document to read + * @param targetClass the target type to map to + * @return the mapped object + */ + T findOne(URI uri, Class targetClass); + + + /** + * Maps a Java object to JSON and writes it to the database + *

+ * This method is intended to work when a default database + * is set on the CouchDbDocumentOperations instance. + * + * @param id the id of the document to write + * @param document the object to write + */ + void save(String id, Object document); + + /** + * Maps a Java object to JSON and writes it to the database + * + * @param uri the full URI of the document to write + * @param document the object to write + */ + void save(URI uri, Object document); +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/CouchTemplate.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/CouchTemplate.java new file mode 100644 index 000000000..8cb7014ec --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/CouchTemplate.java @@ -0,0 +1,153 @@ +/* + * Copyright 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.couchdb.core; + +import java.net.URI; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.data.document.couchdb.CouchServerResourceUsageException; +import org.springframework.data.document.couchdb.CouchUsageException; +import org.springframework.data.document.couchdb.DocumentRetrievalFailureException; +import org.springframework.data.document.couchdb.UncategorizedCouchDataAccessException; +import org.springframework.data.document.couchdb.support.CouchUtils; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; + + +public class CouchTemplate implements CouchOperations { + + protected final Log logger = LogFactory.getLog(this.getClass()); + + private String defaultDocumentUrl; + + private RestOperations restOperations = new RestTemplate(); + + /** + * Constructs an instance of CouchDbDocumentTemplate with a default database + * @param defaultDatabaseUrl the default database to connect to + */ + public CouchTemplate(String defaultDatabaseUrl) { + Assert.hasText(defaultDatabaseUrl, "defaultDatabaseUrl must not be empty"); + defaultDocumentUrl = CouchUtils.addId(defaultDatabaseUrl); + } + + /** + * Constructs an instance of CouchDbDocumentTemplate with a default database + * @param defaultDatabaseUrl the default database to connect to + */ + public CouchTemplate(String defaultDatabaseUrl, RestOperations restOperations) { + this(defaultDatabaseUrl); + Assert.notNull(restOperations, "restOperations must not be null"); + this.restOperations = restOperations; + } + + + + + + public T findOne(String id, Class targetClass) { + Assert.state(defaultDocumentUrl != null, "defaultDatabaseUrl must be set to use this method"); + try { + return restOperations.getForObject(defaultDocumentUrl, targetClass, id); + //TODO check this exception translation and centralize. + } catch (HttpClientErrorException clientError) { + if (clientError.getStatusCode() == HttpStatus.NOT_FOUND) { + throw new DocumentRetrievalFailureException(defaultDocumentUrl + "/" + id); + } + throw new CouchUsageException(clientError); + } catch (HttpServerErrorException serverError) { + throw new CouchServerResourceUsageException(serverError); + } catch (RestClientException otherError) { + throw new UncategorizedCouchDataAccessException(otherError); + } + } + + public T findOne(URI uri, Class targetClass) { + Assert.state(uri != null, "uri must be set to use this method"); + try { + return restOperations.getForObject(uri, targetClass); + //TODO check this exception translation and centralize. + } catch (HttpClientErrorException clientError) { + if (clientError.getStatusCode() == HttpStatus.NOT_FOUND) { + throw new DocumentRetrievalFailureException(uri.getPath()); + } + throw new CouchUsageException(clientError); + } catch (HttpServerErrorException serverError) { + throw new CouchServerResourceUsageException(serverError); + } catch (RestClientException otherError) { + throw new UncategorizedCouchDataAccessException(otherError); + } + } + + public void save(String id, Object document) { + Assert.notNull(document, "document must not be null for save"); + HttpEntity httpEntity = createHttpEntity(document); + try { + ResponseEntity response = restOperations.exchange(defaultDocumentUrl, HttpMethod.PUT, httpEntity, Map.class, id); + //TODO update the document revision id on the object from the returned value + //TODO better exception translation + } catch (RestClientException e) { + throw new UncategorizedCouchDataAccessException(e); + } + + } + + public void save(URI uri, Object document) { + Assert.notNull(document, "document must not be null for save"); + Assert.notNull(uri, "URI must not be null for save"); + HttpEntity httpEntity = createHttpEntity(document); + try { + ResponseEntity response = restOperations.exchange(uri, HttpMethod.PUT, httpEntity, Map.class); + //TODO update the document revision id on the object from the returned value + //TODO better exception translation + } catch (RestClientException e) { + throw new UncategorizedCouchDataAccessException(e); + } + + + } + + private HttpEntity createHttpEntity(Object document) { + + if (document instanceof HttpEntity) { + HttpEntity httpEntity = (HttpEntity) document; + Assert.isTrue(httpEntity.getHeaders().getContentType().equals(MediaType.APPLICATION_JSON), + "HttpEntity payload with non application/json content type found."); + return httpEntity; + } + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + HttpEntity httpEntity = new HttpEntity(document, httpHeaders); + + return httpEntity; + } + + +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/support/CouchDbMappingJacksonHttpMessageConverter.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/support/CouchDbMappingJacksonHttpMessageConverter.java new file mode 100644 index 000000000..b49bfa09e --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/core/support/CouchDbMappingJacksonHttpMessageConverter.java @@ -0,0 +1,326 @@ +/* + * Copyright 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.couchdb.core.support; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.codehaus.jackson.JsonEncoding; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.JsonToken; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.type.TypeFactory; +import org.codehaus.jackson.type.JavaType; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.util.Assert; + +public class CouchDbMappingJacksonHttpMessageConverter extends + AbstractHttpMessageConverter { + + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private static final String ROWS_FIELD_NAME = "rows"; + private static final String VALUE_FIELD_NAME = "value"; + private static final String INCLUDED_DOC_FIELD_NAME = "doc"; + private static final String TOTAL_ROWS_FIELD_NAME = "total_rows"; + + private ObjectMapper objectMapper = new ObjectMapper(); + + private boolean prefixJson = false; + + /** + * Construct a new {@code BindingJacksonHttpMessageConverter}. + */ + public CouchDbMappingJacksonHttpMessageConverter() { + super(new MediaType("application", "json", DEFAULT_CHARSET)); + } + + /** + * Sets the {@code ObjectMapper} for this view. If not set, a default + * {@link ObjectMapper#ObjectMapper() ObjectMapper} is used. + *

+ * Setting a custom-configured {@code ObjectMapper} is one way to take + * further control of the JSON serialization process. For example, an + * extended {@link org.codehaus.jackson.map.SerializerFactory} can be + * configured that provides custom serializers for specific types. The other + * option for refining the serialization process is to use Jackson's + * provided annotations on the types to be serialized, in which case a + * custom-configured ObjectMapper is unnecessary. + */ + public void setObjectMapper(ObjectMapper objectMapper) { + Assert.notNull(objectMapper, "'objectMapper' must not be null"); + this.objectMapper = objectMapper; + } + + /** + * Indicates whether the JSON output by this view should be prefixed with + * "{} &&". Default is false. + *

+ * Prefixing the JSON string in this manner is used to help prevent JSON + * Hijacking. The prefix renders the string syntactically invalid as a + * script so that it cannot be hijacked. This prefix does not affect the + * evaluation of JSON, but if JSON validation is performed on the string, + * the prefix would need to be ignored. + */ + public void setPrefixJson(boolean prefixJson) { + this.prefixJson = prefixJson; + } + + @Override + public boolean canRead(Class clazz, MediaType mediaType) { + JavaType javaType = getJavaType(clazz); + return this.objectMapper.canDeserialize(javaType) && canRead(mediaType); + } + + /** + * Returns the Jackson {@link JavaType} for the specific class. + * + *

+ * Default implementation returns + * {@link TypeFactory#type(java.lang.reflect.Type)}, but this can be + * overridden in subclasses, to allow for custom generic collection + * handling. For instance: + * + *

+	 * protected JavaType getJavaType(Class<?> clazz) {
+	 * 	if (List.class.isAssignableFrom(clazz)) {
+	 * 		return TypeFactory.collectionType(ArrayList.class, MyBean.class);
+	 * 	} else {
+	 * 		return super.getJavaType(clazz);
+	 * 	}
+	 * }
+	 * 
+ * + * @param clazz + * the class to return the java type for + * @return the java type + */ + protected JavaType getJavaType(Class clazz) { + return TypeFactory.type(clazz); + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return this.objectMapper.canSerialize(clazz) && canWrite(mediaType); + } + + @Override + protected boolean supports(Class clazz) { + // should not be called, since we override canRead/Write instead + throw new UnsupportedOperationException(); + } + + @Override + protected Object readInternal(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + JavaType javaType = getJavaType(clazz); + try { + return success(clazz, inputMessage); + + // return this.objectMapper.readValue(inputMessage.getBody(), + // javaType); + } catch (Exception ex) { + throw new HttpMessageNotReadableException("Could not read JSON: " + + ex.getMessage(), ex); + } + } + + private Object success(Class clazz, HttpInputMessage inputMessage) + throws JsonParseException, IOException { + + //Note, parsing code used from ektorp project + JsonParser jp = objectMapper.getJsonFactory().createJsonParser( + inputMessage.getBody()); + if (jp.nextToken() != JsonToken.START_OBJECT) { + throw new RuntimeException("Expected data to start with an Object"); + } + Map fields = readHeaderFields(jp); + + List result; + if (fields.containsKey(TOTAL_ROWS_FIELD_NAME)) { + int totalRows = fields.get(TOTAL_ROWS_FIELD_NAME); + if (totalRows == 0) { + return Collections.emptyList(); + } + result = new ArrayList(totalRows); + } else { + result = new ArrayList(); + } + + ParseState state = new ParseState(); + + Object first = parseFirstRow(jp, state, clazz); + if (first == null) { + return Collections.emptyList(); + } else { + result.add(first); + } + + while (jp.getCurrentToken() != null) { + skipToField(jp, state.docFieldName, state); + if (atEndOfRows(jp)) { + return result; + } + result.add(jp.readValueAs(clazz)); + endRow(jp, state); + } + return result; + } + + private Object parseFirstRow(JsonParser jp, ParseState state, Class clazz) + throws JsonParseException, IOException, JsonProcessingException, + JsonMappingException { + skipToField(jp, VALUE_FIELD_NAME, state); + JsonNode value = null; + if (atObjectStart(jp)) { + value = jp.readValueAsTree(); + jp.nextToken(); + if (isEndOfRow(jp)) { + state.docFieldName = VALUE_FIELD_NAME; + Object doc = objectMapper.readValue(value, clazz); + endRow(jp, state); + return doc; + } + } + skipToField(jp, INCLUDED_DOC_FIELD_NAME, state); + if (atObjectStart(jp)) { + state.docFieldName = INCLUDED_DOC_FIELD_NAME; + Object doc = jp.readValueAs(clazz); + endRow(jp, state); + return doc; + } + return null; + } + + + private boolean isEndOfRow(JsonParser jp) { + return jp.getCurrentToken() == JsonToken.END_OBJECT; + } + + private void endRow(JsonParser jp, ParseState state) throws IOException, JsonParseException { + state.inRow = false; + jp.nextToken(); + } + + private boolean atObjectStart(JsonParser jp) { + return jp.getCurrentToken() == JsonToken.START_OBJECT; + } + + private boolean atEndOfRows(JsonParser jp) { + return jp.getCurrentToken() != JsonToken.START_OBJECT; + } + private void skipToField(JsonParser jp, String fieldName, ParseState state) throws JsonParseException, IOException { + String lastFieldName = null; + while (jp.getCurrentToken() != null) { + switch (jp.getCurrentToken()) { + case FIELD_NAME: + lastFieldName = jp.getCurrentName(); + jp.nextToken(); + break; + case START_OBJECT: + if (!state.inRow) { + state.inRow = true; + jp.nextToken(); + } else { + if (isInField(fieldName, lastFieldName)) { + return; + } else { + jp.skipChildren(); + } + } + break; + default: + if (isInField(fieldName, lastFieldName)) { + jp.nextToken(); + return; + } + jp.nextToken(); + break; + } + } + } + + private boolean isInField(String fieldName, String lastFieldName) { + return lastFieldName != null && lastFieldName.equals(fieldName); + } + + + private Map readHeaderFields(JsonParser jp) + throws JsonParseException, IOException { + Map map = new HashMap(); + jp.nextToken(); + String nextFieldName = jp.getCurrentName(); + while (!nextFieldName.equals(ROWS_FIELD_NAME)) { + jp.nextToken(); + map.put(nextFieldName, Integer.valueOf(jp.getIntValue())); + jp.nextToken(); + nextFieldName = jp.getCurrentName(); + } + return map; + } + + @Override + protected void writeInternal(Object o, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + + JsonEncoding encoding = getEncoding(outputMessage.getHeaders() + .getContentType()); + JsonGenerator jsonGenerator = this.objectMapper.getJsonFactory() + .createJsonGenerator(outputMessage.getBody(), encoding); + try { + if (this.prefixJson) { + jsonGenerator.writeRaw("{} && "); + } + this.objectMapper.writeValue(jsonGenerator, o); + } catch (JsonGenerationException ex) { + throw new HttpMessageNotWritableException("Could not write JSON: " + + ex.getMessage(), ex); + } + } + + private JsonEncoding getEncoding(MediaType contentType) { + if (contentType != null && contentType.getCharSet() != null) { + Charset charset = contentType.getCharSet(); + for (JsonEncoding encoding : JsonEncoding.values()) { + if (charset.name().equals(encoding.getJavaName())) { + return encoding; + } + } + } + return JsonEncoding.UTF8; + } + + private static class ParseState { + boolean inRow; + String docFieldName = ""; + } +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/AbstractMonitor.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/AbstractMonitor.java new file mode 100644 index 000000000..fc9d66c90 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/AbstractMonitor.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-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.couchdb.monitor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.client.RestTemplate; + +/** + * Base class to encapsulate common configuration settings when connecting to a CouchDB database + * + * @author Mark Pollack + * + */ +public abstract class AbstractMonitor { + + + protected RestTemplate restTemplate; + protected String databaseUrl; + + /** + * Gets the databaseUrl used to connect to CouchDB + * @return + */ + public String getDatabaseUrl() { + return this.databaseUrl; + } + + + + + +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/ServerInfo.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/ServerInfo.java new file mode 100644 index 000000000..80159a669 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/ServerInfo.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-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.couchdb.monitor; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map; + +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.web.client.RestTemplate; + +/** + * Expose basic server information via JMX + * + * @author Mark Pollack + * + */ +@ManagedResource(description="Server Information") +public class ServerInfo extends AbstractMonitor { + + + public ServerInfo(String databaseUrl) { + this.databaseUrl = databaseUrl; + this.restTemplate = new RestTemplate(); + } + + + @ManagedOperation(description="Server host name") + public String getHostName() throws UnknownHostException { + return InetAddress.getLocalHost().getHostName(); + } + + + @ManagedOperation(description="CouchDB Server Version") + public String getVersion() { + return (String) getRoot().get("version"); + } + + @ManagedOperation(description="Message of the day") + public String getMotd() { + return (String) getRoot().get("greeting"); + } + + public Map getRoot() { + Map map = restTemplate.getForObject(getDatabaseUrl(),Map.class); + return map; + } + +} diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/package-info.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/package-info.java new file mode 100644 index 000000000..574efceb3 --- /dev/null +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/monitor/package-info.java @@ -0,0 +1,4 @@ +/** + * CouchDB specific JMX monitoring support. + */ +package org.springframework.data.document.couchdb.monitor; diff --git a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchDbUtils.java b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/support/CouchUtils.java similarity index 61% rename from spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchDbUtils.java rename to spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/support/CouchUtils.java index 65c7084d3..823454ca9 100644 --- a/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/CouchDbUtils.java +++ b/spring-data-couchdb/src/main/java/org/springframework/data/document/couchdb/support/CouchUtils.java @@ -14,21 +14,21 @@ * limitations under the License. */ -package org.springframework.data.document.couchdb; +package org.springframework.data.document.couchdb.support; -import org.jcouchdb.exception.CouchDBException; import org.springframework.dao.DataAccessException; import org.springframework.data.document.UncategorizedDocumentStoreException; /** - * Helper class featuring helper methods for internal MongoDb classes. + * Helper class featuring helper methods for internal CouchDB classes. * *

Mainly intended for internal use within the framework. * * @author Thomas Risberg + * @author Tareq Abedrabbo * @since 1.0 */ -public class CouchDbUtils { +public abstract class CouchUtils { /** * Convert the given runtime exception to an appropriate exception from the @@ -41,17 +41,38 @@ public class CouchDbUtils { */ public static DataAccessException translateCouchExceptionIfPossible(RuntimeException ex) { - // Check for well-known MongoException subclasses. - - // All other MongoExceptions - if (ex instanceof CouchDBException) { - return new UncategorizedDocumentStoreException(ex.getMessage(), ex); - } - - // If we get here, we have an exception that resulted from user code, - // rather than the persistence provider, so we return null to indicate - // that translation should not occur. return null; } + + /** + * Adds an id variable to a URL + * @param url the URL to modify + * @return the modified URL + */ + public static String addId(String url) { + return ensureTrailingSlash(url) + "{id}"; + } + + + /** + * Adds a 'changes since' variable to a URL + * @param url + * @return + */ + public static String addChangesSince(String url) { + return ensureTrailingSlash(url) + "_changes?since={seq}"; + } + + /** + * Ensures that a URL ends with a slash. + * @param url the URL to modify + * @return the modified URL + */ + public static String ensureTrailingSlash(String url) { + if (!url.endsWith("/")) { + url += "/"; + } + return url; + } } diff --git a/spring-data-couchdb/src/main/resources/META-INF/spring.handlers b/spring-data-couchdb/src/main/resources/META-INF/spring.handlers new file mode 100644 index 000000000..e8486b631 --- /dev/null +++ b/spring-data-couchdb/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/data/couch=org.springframework.data.document.couchdb.config.CouchNamespaceHandler diff --git a/spring-data-couchdb/src/main/resources/META-INF/spring.schemas b/spring-data-couchdb/src/main/resources/META-INF/spring.schemas new file mode 100644 index 000000000..7ca5ca1c2 --- /dev/null +++ b/spring-data-couchdb/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/data/couch/spring-couch-1.0.xsd=org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd +http\://www.springframework.org/schema/data/couch/spring-couch.xsd=org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd diff --git a/spring-data-couchdb/src/main/resources/META-INF/spring.tooling b/spring-data-couchdb/src/main/resources/META-INF/spring.tooling new file mode 100644 index 000000000..5859f57de --- /dev/null +++ b/spring-data-couchdb/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the Couch DB namespace +http\://www.springframework.org/schema/data/couch@name=Couch Namespace +http\://www.springframework.org/schema/data/couch@prefix=couch +http\://www.springframework.org/schema/data/couch@icon=org/springframework/jdbc/config/spring-jdbc.gif diff --git a/spring-data-couchdb/src/main/resources/org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd b/spring-data-couchdb/src/main/resources/org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd new file mode 100644 index 000000000..d4e21345b --- /dev/null +++ b/spring-data-couchdb/src/main/resources/org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/DummyDocument.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/DummyDocument.java new file mode 100644 index 000000000..d43677696 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/DummyDocument.java @@ -0,0 +1,77 @@ +/* + * Copyright 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.couchdb; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +import java.util.Date; + +/** + * @author Tareq Abedrabbo (tareq.abedrabbo@opencredo.com) + * @since 13/01/2011 + */ +@JsonIgnoreProperties(ignoreUnknown=true) +public class DummyDocument { + + private String message; + + private String timestamp = new Date().toString(); + + public DummyDocument() { + } + + public DummyDocument(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getTimestamp() { + return timestamp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DummyDocument document = (DummyDocument) o; + + if (message != null ? !message.equals(document.message) : document.message != null) return false; + + return true; + } + + @Override + public int hashCode() { + return message != null ? message.hashCode() : 0; + } + + @Override + public String toString() { + return "DummyDocument{" + + "message='" + message + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/IsBodyEqual.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/IsBodyEqual.java new file mode 100644 index 000000000..8f690bcc2 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/IsBodyEqual.java @@ -0,0 +1,51 @@ +/* + * Copyright 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.couchdb; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.springframework.http.HttpEntity; + +/** + * Matches the content of the body of an HttpEntity. + * @author Tareq Abedrabbo + * @since 31/01/2011 + */ +public class IsBodyEqual extends TypeSafeMatcher { + + private Object object; + + public IsBodyEqual(Object object) { + this.object = object; + } + + @Override + public boolean matchesSafely(HttpEntity httpEntity) { + return httpEntity.getBody().equals(object); + } + + public void describeTo(Description description) { + description.appendText("body equals ").appendValue(object); + } + + @Factory + public static Matcher bodyEqual(Object object) { + return new IsBodyEqual(object); + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/admin/CouchAdminIntegrationTests.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/admin/CouchAdminIntegrationTests.java new file mode 100644 index 000000000..93c13de54 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/admin/CouchAdminIntegrationTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 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.couchdb.admin; + +import java.util.List; + +import junit.framework.Assert; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.data.document.couchdb.core.CouchConstants; + +public class CouchAdminIntegrationTests { + + @Test + @Ignore("until CI has couch server running") + public void dbLifecycle() { + + CouchAdmin admin = new CouchAdmin(CouchConstants.COUCHDB_URL); + admin.deleteDatabase("foo"); + List dbs = admin.listDatabases(); + admin.createDatabase("foo"); + List newDbs = admin.listDatabases(); + Assert.assertEquals(dbs.size()+1, newDbs.size()); + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/AbstractCouchTemplateIntegrationTests.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/AbstractCouchTemplateIntegrationTests.java new file mode 100644 index 000000000..98fa48750 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/AbstractCouchTemplateIntegrationTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 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.couchdb.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.UUID; + +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeTrue; +import static org.springframework.http.HttpStatus.OK; + +/** + * Base class for CouchDB integration tests. Checks whether CouchDB is available before running each test, + * in which case the test is executed. If CouchDB is not available, tests are ignored. + * + * @author Tareq Abedrabbo (tareq.abedrabbo@opencredo.com) + * @since 13/01/2011 + */ + +public abstract class AbstractCouchTemplateIntegrationTests { + + + protected static final Log log = LogFactory.getLog(AbstractCouchTemplateIntegrationTests.class); + + protected static final RestTemplate restTemplate = new RestTemplate(); + + /** + * This methods ensures that the database is running. Otherwise, the test is ignored. + */ + @BeforeClass + public static void assumeDatabaseIsUpAndRunning() { + try { + ResponseEntity responseEntity = restTemplate.getForEntity(CouchConstants.COUCHDB_URL, String.class); + assumeTrue(responseEntity.getStatusCode().equals(OK)); + log.debug("CouchDB is running on " + CouchConstants.COUCHDB_URL + + " with status " + responseEntity.getStatusCode()); + } catch (RestClientException e) { + log.debug("CouchDB is not running on " + CouchConstants.COUCHDB_URL); + assumeNoException(e); + } + } + + @Before + public void setUpTestDatabase() throws Exception { + RestTemplate template = new RestTemplate(); + template.setErrorHandler(new DefaultResponseErrorHandler(){ + @Override + public void handleError(ClientHttpResponse response) throws IOException { + // do nothing, error status will be handled in the switch statement + } + }); + ResponseEntity response = template.getForEntity(CouchConstants.TEST_DATABASE_URL, String.class); + HttpStatus statusCode = response.getStatusCode(); + switch (statusCode) { + case NOT_FOUND: + createNewTestDatabase(); + break; + case OK: + deleteExisitingTestDatabase(); + createNewTestDatabase(); + break; + default: + throw new IllegalStateException("Unsupported http status [" + statusCode + "]"); + } + } + + private void deleteExisitingTestDatabase() { + restTemplate.delete(CouchConstants.TEST_DATABASE_URL); + } + + private void createNewTestDatabase() { + restTemplate.put(CouchConstants.TEST_DATABASE_URL, null); + } + + /** + * Reads a CouchDB document and converts it to the expected type. + */ + protected T getDocument(String id, Class expectedType) { + String url = CouchConstants.TEST_DATABASE_URL + "{id}"; + return restTemplate.getForObject(url, expectedType, id); + } + + /** + * Writes a CouchDB document + */ + protected String putDocument(Object document) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity request = new HttpEntity(document, headers); + String id = UUID.randomUUID().toString(); + restTemplate.put(CouchConstants.TEST_DATABASE_URL + "{id}", request, id); + return id; + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchConstants.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchConstants.java new file mode 100644 index 000000000..14412b947 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchConstants.java @@ -0,0 +1,27 @@ +/* + * Copyright 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.couchdb.core; + +public abstract class CouchConstants { + + public static final String COUCHDB_URL = "http://127.0.0.1:5984/"; + public static final String TEST_DATABASE_URL = COUCHDB_URL + "si_couchdb_test/"; + + public CouchConstants() { + // TODO Auto-generated constructor stub + } + +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateIntegrationTests.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateIntegrationTests.java new file mode 100644 index 000000000..893f32471 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 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.couchdb.core; + +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.data.document.couchdb.DummyDocument; + +public class CouchTemplateIntegrationTests extends AbstractCouchTemplateIntegrationTests { + + + @Test + @Ignore("until CI has couch server running") + public void saveAndFindTest() { + CouchTemplate template = new CouchTemplate(CouchConstants.TEST_DATABASE_URL); + DummyDocument document = new DummyDocument("hello"); + String id = UUID.randomUUID().toString(); + template.save(id, document); + DummyDocument foundDocument = template.findOne(id, DummyDocument.class); + Assert.assertEquals(document.getMessage(), foundDocument.getMessage()); + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateTests.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateTests.java new file mode 100644 index 000000000..b0163aa43 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 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.couchdb.core; + +import org.junit.Test; + +/** + * Unit tests for CouchTemplate with mocks + * + * + */ +public class CouchTemplateTests { + + @Test + public void foo() { + + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/monitor/JmxServer.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/monitor/JmxServer.java new file mode 100644 index 000000000..07cf47222 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/monitor/JmxServer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.couchdb.monitor; + +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Server application to test JMX functionality. + * + * @author Mark Pollack + */ +public class JmxServer { + + public static void main(String[] args) { + new JmxServer().run(); + } + + public void run() { + new ClassPathXmlApplicationContext(new String[] {"server-jmx.xml"} ); + } + +} diff --git a/spring-data-couchdb/src/test/resources/log4j.properties b/spring-data-couchdb/src/test/resources/log4j.properties new file mode 100644 index 000000000..6d5422d74 --- /dev/null +++ b/spring-data-couchdb/src/test/resources/log4j.properties @@ -0,0 +1,13 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.category.org.apache.activemq=ERROR +log4j.category.org.springframework.batch=DEBUG +log4j.category.org.springframework.transaction=INFO + +log4j.category.org.hibernate.SQL=DEBUG +# for debugging datasource initialization +# log4j.category.test.jdbc=DEBUG diff --git a/spring-data-couchdb/src/test/resources/server-jmx.xml b/spring-data-couchdb/src/test/resources/server-jmx.xml new file mode 100644 index 000000000..30c1933fe --- /dev/null +++ b/spring-data-couchdb/src/test/resources/server-jmx.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-couchdb/template.mf b/spring-data-couchdb/template.mf index a03fa6ca7..af4bc48ff 100644 --- a/spring-data-couchdb/template.mf +++ b/spring-data-couchdb/template.mf @@ -8,10 +8,15 @@ Import-Template: org.springframework.beans.*;version="[3.0.0, 4.0.0)", org.springframework.core.*;version="[3.0.0, 4.0.0)", org.springframework.dao.*;version="[3.0.0, 4.0.0)", + org.springframework.http.*;version="[3.0.0, 4.0.0)", + org.springframework.web.*;version="[3.0.0, 4.0.0)", org.springframework.util.*;version="[3.0.0, 4.0.0)", + org.springframework.context.*;version="[3.0.0, 4.0.0)", + org.springframework.jmx.*;version="[3.0.0, 4.0.0)", + org.springframework.remoting.*;version="[3.0.0, 4.0.0)", org.springframework.data.core.*;version="[1.0.0, 2.0.0)", org.springframework.data.document.*;version="[1.0.0, 2.0.0)", - org.jcouchdb.*;version="0", + org.codehaus.jackson.*;version="[1.0.0, 2.0.0)";resolution:=optional, 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" diff --git a/spring-data-document-parent/pom.xml b/spring-data-document-parent/pom.xml index 3b93fde57..bbdab5f08 100644 --- a/spring-data-document-parent/pom.xml +++ b/spring-data-document-parent/pom.xml @@ -1,92 +1,91 @@ - - 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 + 1.6.1 + 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 + + + + + diff --git a/spring-data-mongodb-cross-store/.classpath b/spring-data-mongodb-cross-store/.classpath index 399cc9b91..b1ef66a27 100644 --- a/spring-data-mongodb-cross-store/.classpath +++ b/spring-data-mongodb-cross-store/.classpath @@ -1,10 +1,10 @@ - + - - - + + + diff --git a/spring-data-mongodb-cross-store/.settings/org.eclipse.jdt.core.prefs b/spring-data-mongodb-cross-store/.settings/org.eclipse.jdt.core.prefs index 2bafcc110..644302d5c 100644 --- a/spring-data-mongodb-cross-store/.settings/org.eclipse.jdt.core.prefs +++ b/spring-data-mongodb-cross-store/.settings/org.eclipse.jdt.core.prefs @@ -1,6 +1,9 @@ -#Mon Feb 28 16:26:01 EST 2011 +#Tue Mar 01 09:48:37 EST 2011 eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 -org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.5 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index ae18b6747..d80a94777 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -189,10 +189,10 @@ org.springframework spring-aspects - + 1.6 1.6 diff --git a/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoChangeSetPersister.java b/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoChangeSetPersister.java new file mode 100644 index 000000000..e36f94a85 --- /dev/null +++ b/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoChangeSetPersister.java @@ -0,0 +1,124 @@ +package org.springframework.persistence.document; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionService; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.persistence.support.ChangeSet; +import org.springframework.persistence.support.ChangeSetBacked; +import org.springframework.persistence.support.ChangeSetPersister; +import org.springframework.util.ClassUtils; + +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.mongodb.Mongo; +import com.mongodb.MongoException; + +//import edu.emory.mathcs.backport.java.util.Arrays; + +public class MongoChangeSetPersister implements ChangeSetPersister { + + protected final Log log = LogFactory.getLog(getClass()); + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private ConversionService conversionService; + + @Override + public void getPersistentState(Class entityClass, Object id, ChangeSet changeSet) + throws DataAccessException, NotFoundException { + String collection = ClassUtils.getShortName(entityClass).toLowerCase(); + DBObject q = new BasicDBObject(); + q.put("_id", id); + try { + DBObject dbo = mongoTemplate.getCollection(collection).findOne(q); + log.debug("Found DBObject: " + dbo); + if (dbo == null) { + throw new NotFoundException(); + } + String classShortName = ClassUtils.getShortName(entityClass); + for (Object property : dbo.toMap().keySet()) { + String propertyKey = (String) property; + String propertyName = propertyKey.startsWith(classShortName) ? propertyKey.substring(propertyKey.indexOf(classShortName) + + classShortName.length() + 1) : propertyKey; + // System.err.println("Mongo persisted property [" + propertyName + "] :: " + propertyKey + " = " + dbo.get(propertyKey)); + if (propertyKey.startsWith("_")) { + // Id or class + changeSet.set(propertyName, dbo.get(propertyKey)); + } else { + //throw new IllegalStateException("Unknown property [" + propertyName + "] found in MongoDB store"); + changeSet.set(propertyName, dbo.get(propertyKey)); + } + } + } catch (MongoException ex) { + throw new DataAccessResourceFailureException("Can't read from Mongo", ex); + } + } + + @Override + public Object getPersistentId(Class entityClass, + ChangeSet cs) throws DataAccessException { + log.debug("getPersistentId called on " + entityClass); + if (cs == null) { + return null; + } + if (cs.getValues().get(ChangeSetPersister.ID_KEY) == null) { + // Not yet persistent + return null; + } + Object o = cs.getValues().get(ChangeSetPersister.ID_KEY); + return o; + } + + @Override + public Object persistState(Class entityClass, ChangeSet cs) throws DataAccessException { + log.info("PERSIST::"+cs); + cs.set(CLASS_KEY, entityClass.getName()); + String idstr = cs.get(ID_KEY, String.class, this.conversionService); + Object id = null; + if (idstr != null) { + id = idstr; + } + if (id == null) { + log.info("Flush: entity make persistent; data store will assign id"); + cs.set("_class", entityClass.getName()); + String collection = entityClass.getSimpleName().toLowerCase(); + DBCollection dbc = mongoTemplate.getCollection(collection); + DBObject dbo = mapChangeSetToDbObject(cs); + if (dbc == null) { + dbc = mongoTemplate.createCollection(collection); + } + dbc.save(dbo); + id = dbo.get(ID_KEY); + log.info("Data store assigned id: " + id); + } else { + log.info("Flush: entity already persistent with id=" + id); + String collection = entityClass.getName(); + DBCollection dbc = mongoTemplate.getCollection(collection); + DBObject dbo = mapChangeSetToDbObject(cs); + if (dbc == null) { + throw new DataAccessResourceFailureException("Expected to find a collection named '" + collection +"'. It was not found, so ChangeSet can't be persisted."); + } + dbc.save(dbo); + } + + return 0L; + } + + private DBObject mapChangeSetToDbObject(ChangeSet cs) { + BasicDBObject dbo = new BasicDBObject(); + for (String property : cs.getValues().keySet()) { + dbo.put(property, cs.getValues().get(property)); + } + return dbo; + } +} diff --git a/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoDocumentBacking.aj b/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoDocumentBacking.aj new file mode 100644 index 000000000..914c1efcf --- /dev/null +++ b/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoDocumentBacking.aj @@ -0,0 +1,12 @@ +package org.springframework.persistence.document; + +import org.springframework.persistence.support.AbstractDeferredUpdateMixinFields; + +/** + * Aspect to turn an object annotated with DocumentEntity into a document entity using Mongo. + * + * @author Thomas Risberg + */ +public aspect MongoDocumentBacking extends AbstractDeferredUpdateMixinFields { + +} diff --git a/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoEntityOperations.java b/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoEntityOperations.java new file mode 100644 index 000000000..c812358da --- /dev/null +++ b/spring-data-mongodb-cross-store/src/main/java/org/springframework/persistence/document/MongoEntityOperations.java @@ -0,0 +1,58 @@ +package org.springframework.persistence.document; + +import java.lang.reflect.Field; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.persistence.OrderedEntityOperations; +import org.springframework.persistence.RelatedEntity; +import org.springframework.persistence.support.ChangeSetBacked; + +import com.mongodb.DB; + +public class MongoEntityOperations extends OrderedEntityOperations { + + @Autowired + private DB mongoDb; + + @Autowired + private MongoChangeSetPersister changeSetPersister; + + @Override + public boolean cacheInEntity() { + return true; + } + + @Override + public ChangeSetBacked findEntity(Class entityClass, Object pk) throws DataAccessException { + throw new UnsupportedOperationException(); + } + + @Override + public Object findUniqueKey(ChangeSetBacked entity) throws DataAccessException { + return entity.getId(); + } + + @Override + public boolean isTransactional() { + // TODO + return false; + } + + @Override + public boolean isTransient(ChangeSetBacked entity) throws DataAccessException { + return entity.getId() == null; + } + + @Override + public Object makePersistent(Object owner, ChangeSetBacked entity, Field f, RelatedEntity fs) throws DataAccessException { + changeSetPersister.persistState(entity.getClass(), entity.getChangeSet()); + return entity.getId(); + } + + @Override + public boolean supports(Class entityClass, RelatedEntity fs) { + return entityClass.isAnnotationPresent(DocumentEntity.class); + } + +} diff --git a/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/CrossStoreMongoTests.java b/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/CrossStoreMongoTests.java index 6cc76521d..8de689735 100644 --- a/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/CrossStoreMongoTests.java +++ b/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/CrossStoreMongoTests.java @@ -4,12 +4,18 @@ import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.persistence.document.test.MongoPerson; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.transaction.annotation.Transactional; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; import com.mongodb.Mongo; +import com.mongodb.MongoException; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:/META-INF/spring/applicationContext.xml") @@ -18,16 +24,39 @@ public class CrossStoreMongoTests { @Autowired private Mongo mongo; + @Autowired + private MongoTemplate mongoTemplate; + + @BeforeTransaction + public void setUp() { + DBCollection col = this.mongoTemplate.getCollection(MongoPerson.class.getSimpleName().toLowerCase()); + if (col != null) { + this.mongoTemplate.dropCollection(MongoPerson.class.getName()); + } + } + @Test -// @Transactional -// @Rollback(false) + @Transactional + @Rollback(false) public void testUserConstructor() { int age = 33; MongoPerson p = new MongoPerson("Thomas", age); - //Assert.assertEquals(p.getRedisValue().getString("RedisPerson.name"), p.getName()); Assert.assertEquals(age, p.getAge()); p.birthday(); Assert.assertEquals(1 + age, p.getAge()); } + @Test + @Transactional + public void testInstantiatedFinder() throws MongoException { + String key = MongoPerson.class.getSimpleName().toLowerCase(); + DBCollection col = this.mongoTemplate.getCollection(key); + DBObject dbo = col.findOne(); + Object id1 = dbo.get("_id"); + MongoPerson found = MongoPerson.findPerson(id1); + Assert.assertNotNull(found); + Assert.assertEquals(id1, found.getId()); + System.out.println("Loaded MongoPerson data: " + found); + } + } diff --git a/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/Account.java b/spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/Account.java similarity index 86% rename from spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/Account.java rename to spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/Account.java index 26edd39f3..f73a6586e 100644 --- a/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/Account.java +++ b/spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/Account.java @@ -1,9 +1,12 @@ -package org.springframework.data.document.persistence; +package org.springframework.persistence.document.test; import javax.persistence.Entity; +import javax.persistence.Id; @Entity public class Account { + + @Id private Long id; private String name; diff --git a/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/MongoPerson.java b/spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/MongoPerson.java similarity index 95% rename from spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/MongoPerson.java rename to spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/MongoPerson.java index c1a32ee02..0603beb86 100644 --- a/spring-data-mongodb-cross-store/src/test/java/org/springframework/data/document/persistence/MongoPerson.java +++ b/spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/MongoPerson.java @@ -1,4 +1,4 @@ -package org.springframework.data.document.persistence; +package org.springframework.persistence.document.test; import org.springframework.persistence.RelatedEntity; import org.springframework.persistence.document.DocumentEntity; diff --git a/spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/MongoPerson_Roo_Mongo_Entity.aj b/spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/MongoPerson_Roo_Mongo_Entity.aj new file mode 100644 index 000000000..3cd14ee18 --- /dev/null +++ b/spring-data-mongodb-cross-store/src/test/java/org/springframework/persistence/document/test/MongoPerson_Roo_Mongo_Entity.aj @@ -0,0 +1,60 @@ +package org.springframework.persistence.document.test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.persistence.support.ChangeSet; +import org.springframework.persistence.support.ChangeSetBacked; +import org.springframework.persistence.support.ChangeSetConfiguration; +import org.springframework.persistence.support.ChangeSetPersister; +import org.springframework.persistence.support.ChangeSetSynchronizer; +import org.springframework.persistence.support.HashMapChangeSet; +import org.springframework.persistence.support.ChangeSetPersister.NotFoundException; + +/** + * EXAMPLE OF CODE THAT SHOULD BE GENERATED BY ROO BESIDES EACH MONGOENTITY CLASS + * + * Note: Combines X_Roo_Entity with X_Roo_Finder, as + * we need only a single aspect for entities. + * + * @author Thomas Risberg + * + */ +privileged aspect MongoPerson_Roo_Mongo_Entity { + + private static ChangeSetPersister changeSetPersister() { + return new MongoConfigurationHolder().changeSetConfig.getChangeSetPersister(); + } + + private static ChangeSetSynchronizer changeSetManager() { + return new MongoConfigurationHolder().changeSetConfig.getChangeSetManager(); + } + + @Configurable + public static class MongoConfigurationHolder { + @Autowired + @Qualifier("mongoChangeSetConfiguration") + public ChangeSetConfiguration changeSetConfig; + } + + /** + * Add constructor that takes ChangeSet. + * @param ChangeSet + */ + public MongoPerson.new(ChangeSet cs) { + super(); + setChangeSet(cs); + } + + public static MongoPerson MongoPerson.findPerson(Object id) { + ChangeSet rv = new HashMapChangeSet(); + try { + changeSetPersister().getPersistentState(MongoPerson.class, id, rv); + return new MongoPerson(rv); + } + catch (NotFoundException ex) { + return null; + } + } + +} diff --git a/spring-data-mongodb-cross-store/src/test/resources/META-INF/persistence.xml b/spring-data-mongodb-cross-store/src/test/resources/META-INF/persistence.xml new file mode 100644 index 000000000..232536c0d --- /dev/null +++ b/spring-data-mongodb-cross-store/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,13 @@ + + + + org.hibernate.ejb.HibernatePersistence + org.springframework.persistence.document.test.Account + + + + + + + + diff --git a/spring-data-mongodb-cross-store/src/test/resources/META-INF/spring/applicationContext.xml b/spring-data-mongodb-cross-store/src/test/resources/META-INF/spring/applicationContext.xml index 0e0d85184..bffad856c 100644 --- a/spring-data-mongodb-cross-store/src/test/resources/META-INF/spring/applicationContext.xml +++ b/spring-data-mongodb-cross-store/src/test/resources/META-INF/spring/applicationContext.xml @@ -1,19 +1,67 @@ + xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:jdbc="http://www.springframework.org/schema/jdbc" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-data-mongodb-cross-store/src/test/resources/log4j.properties b/spring-data-mongodb-cross-store/src/test/resources/log4j.properties new file mode 100644 index 000000000..292bb1d4d --- /dev/null +++ b/spring-data-mongodb-cross-store/src/test/resources/log4j.properties @@ -0,0 +1,13 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.category.org.springframework=INFO +log4j.category.org.springframework.data=DEBUG +log4j.category.org.springframework.persistence=DEBUG + +log4j.category.org.hibernate.SQL=DEBUG +# for debugging datasource initialization +# log4j.category.test.jdbc=DEBUG diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Query.java index 3bf36ec7a..6e8756e71 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Query.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Query.java @@ -44,7 +44,7 @@ public class Query { return this; } - protected Query or(Query... queries) { + public Query or(Query... queries) { this.criteria.put("$or", new OrCriteria(queries)); return this; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/AbstractMongoQuery.java index f312d3049..617f538ba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/AbstractMongoQuery.java @@ -26,7 +26,6 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; -import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.util.Assert; @@ -34,7 +33,6 @@ import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; - /** * Base class for {@link RepositoryQuery} implementations for Mongo. * @@ -42,167 +40,159 @@ import com.mongodb.DBObject; */ public abstract class AbstractMongoQuery implements RepositoryQuery { - private final MongoQueryMethod method; - private final MongoTemplate template; + private final MongoQueryMethod method; + private final MongoTemplate template; + /** + * Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoTemplate}. + * + * @param method + * @param template + */ + public AbstractMongoQuery(MongoQueryMethod method, MongoTemplate template) { - /** - * Creates a new {@link AbstractMongoQuery} from the given {@link QueryMethod} and - * {@link MongoTemplate}. - * - * @param method - * @param template - */ - public AbstractMongoQuery(MongoQueryMethod method, MongoTemplate template) { + Assert.notNull(template); + Assert.notNull(method); - Assert.notNull(template); - Assert.notNull(method); + this.method = method; + this.template = template; + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() + */ + public MongoQueryMethod getQueryMethod() { + + return method; + } - this.method = method; - this.template = template; - } + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.RepositoryQuery#execute(java .lang.Object[]) + */ + public Object execute(Object[] parameters) { + ParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), parameters); + Query query = createQuery(new ConvertingParameterAccessor(template.getConverter(), accessor)); - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.query.RepositoryQuery#execute(java - * .lang.Object[]) - */ - public Object execute(Object[] parameters) { + switch (method.getType()) { + case COLLECTION: + return new CollectionExecution().execute(query); + case PAGING: + return new PagedExecution(accessor.getPageable()).execute(query); + default: + return new SingleEntityExecution().execute(query); + } + } - ParameterAccessor accessor = - new ParametersParameterAccessor(method.getParameters(), parameters); - Query query = createQuery(new ConvertingParameterAccessor(template.getConverter(), accessor)); + /** + * Create a {@link Query} instance using the given {@link ParameterAccessor} + * @param accessor + * @param converter + * @return + */ + protected abstract Query createQuery(ConvertingParameterAccessor accessor); - if (method.isCollectionQuery()) { - return new CollectionExecution().execute(query); - } else if (method.isPageQuery()) { - return new PagedExecution(accessor.getPageable()).execute(query); - } else { - return new SingleEntityExecution().execute(query); - } - } - - /** - * Create a {@link Query} instance using the given {@link ParameterAccessor} - * @param accessor - * @param converter - * @return - */ - protected abstract Query createQuery(ConvertingParameterAccessor accessor); - + private abstract class Execution { - private abstract class Execution { + abstract Object execute(Query query); - abstract Object execute(Query query); + protected List readCollection(Query query) { + MongoEntityInformation metadata = method.getEntityMetadata(); - protected List readCollection(Query query) { + String collectionName = metadata.getCollectionName(); + return template.find(collectionName, query, metadata.getJavaType()); + } + } - String collectionName = getCollectionName(method.getDomainClass()); - return template - .find(collectionName, query, method.getReturnedDomainClass()); - } - } + /** + * {@link Execution} for collection returning queries. + * + * @author Oliver Gierke + */ + class CollectionExecution extends Execution { - /** - * {@link Execution} for collection returning queries. - * - * @author Oliver Gierke - */ - class CollectionExecution extends Execution { + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.repository.MongoQuery.Execution #execute(com.mongodb.DBObject) + */ + @Override + public Object execute(Query query) { - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.document.mongodb.repository.MongoQuery.Execution - * #execute(com.mongodb.DBObject) - */ - @Override - public Object execute(Query query) { + return readCollection(query); + } + } - return readCollection(query); - } - } + /** + * {@link Execution} for pagination queries. + * + * @author Oliver Gierke + */ + class PagedExecution extends Execution { - /** - * {@link Execution} for pagination queries. - * - * @author Oliver Gierke - */ - class PagedExecution extends Execution { + private final Pageable pageable; - private final Pageable pageable; + /** + * Creates a new {@link PagedExecution}. + * + * @param pageable + */ + public PagedExecution(Pageable pageable) { + Assert.notNull(pageable); + this.pageable = pageable; + } - /** - * Creates a new {@link PagedExecution}. - * - * @param pageable - */ - public PagedExecution(Pageable pageable) { + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.repository.MongoQuery.Execution #execute(com.mongodb.DBObject) + */ + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + Object execute(Query query) { - Assert.notNull(pageable); - this.pageable = pageable; - } + MongoEntityInformation metadata = method.getEntityMetadata(); + int count = getCollectionCursor(metadata.getCollectionName(), query.getQueryObject()).count(); + List result = template.find(metadata.getCollectionName(), applyPagination(query, pageable), + metadata.getJavaType()); - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.document.mongodb.repository.MongoQuery.Execution - * #execute(com.mongodb.DBObject) - */ - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - Object execute(Query query) { + return new PageImpl(result, pageable, count); + } - String collectionName = getCollectionName(method.getDomainClass()); - int count = getCollectionCursor(collectionName, query.getQueryObject()).count(); + private DBCursor getCollectionCursor(String collectionName, final DBObject query) { - List result = - template.find(collectionName, applyPagination(query, pageable), - method.getReturnedDomainClass()); + return template.execute(collectionName, new CollectionCallback() { - return new PageImpl(result, pageable, count); - } + public DBCursor doInCollection(DBCollection collection) { + return collection.find(query); + } + }); + } + } - private DBCursor getCollectionCursor(String collectionName, final DBObject query) { + /** + * {@link Execution} to return a single entity. + * + * @author Oliver Gierke + */ + class SingleEntityExecution extends Execution { - return template.execute(collectionName, new CollectionCallback() { + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.repository.MongoQuery.Execution #execute(com.mongodb.DBObject) + */ + @Override + Object execute(Query query) { - public DBCursor doInCollection(DBCollection collection) { - - return collection.find(query); - } - }); - } - } - - /** - * {@link Execution} to return a single entity. - * - * @author Oliver Gierke - */ - class SingleEntityExecution extends Execution { - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.document.mongodb.repository.MongoQuery.Execution - * #execute(com.mongodb.DBObject) - */ - @Override - Object execute(Query query) { - - List result = readCollection(query); - return result.isEmpty() ? null : result.get(0); - } - } + List result = readCollection(query); + return result.isEmpty() ? null : result.get(0); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoEntityMetadata.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoEntityInformation.java similarity index 81% rename from spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoEntityMetadata.java rename to spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoEntityInformation.java index 439f6c2e2..1ace73e3e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoEntityMetadata.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoEntityInformation.java @@ -19,8 +19,9 @@ import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; -import org.springframework.data.repository.support.AbstractEntityMetadata; +import org.springframework.data.repository.support.AbstractEntityInformation; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; /** @@ -29,18 +30,18 @@ import org.springframework.util.ReflectionUtils; * * @author Oliver Gierke */ -class MongoEntityMetadata extends AbstractEntityMetadata { +class MongoEntityInformation extends AbstractEntityInformation { private static final List FIELD_NAMES = Arrays.asList("ID", "id", "_id"); private Field field; /** - * Creates a new {@link MongoEntityMetadata}. + * Creates a new {@link MongoEntityInformation}. * * @param domainClass */ - public MongoEntityMetadata(Class domainClass) { + public MongoEntityInformation(Class domainClass) { super(domainClass); @@ -61,16 +62,16 @@ class MongoEntityMetadata extends AbstractEntityMetadata { domainClass.getName())); } } - - - /** - * Returns the actual field name containing the id. - * - * @return - */ - public String getFieldName() { - - return field.getName(); + + + public String getCollectionName() { + + return StringUtils.uncapitalize(getJavaType().getSimpleName()); + } + + public String getIdAttribute() { + + return "_id"; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java index f5478b68d..eef11343f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryCreator.java @@ -18,7 +18,6 @@ package org.springframework.data.document.mongodb.repository; import static org.springframework.data.document.mongodb.query.Criteria.*; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; import java.util.regex.Pattern; @@ -41,7 +40,7 @@ import org.springframework.data.repository.query.parser.PartTree; * * @author Oliver Gierke */ -class MongoQueryCreator extends AbstractQueryCreator { +class MongoQueryCreator extends AbstractQueryCreator { private static final Logger LOG = LoggerFactory.getLogger(MongoQueryCreator.class); @@ -64,10 +63,12 @@ class MongoQueryCreator extends AbstractQueryCreator { * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator) */ @Override - protected Criteria create(Part part, Iterator iterator) { + protected Query create(Part part, Iterator iterator) { - return from(part.getType(), + Criteria criteria = from(part.getType(), where(part.getProperty().toDotPath()), iterator); + + return new Query(criteria); } @@ -76,11 +77,12 @@ class MongoQueryCreator extends AbstractQueryCreator { * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator) */ @Override - protected Criteria and(Part part, Criteria base, + protected Query and(Part part, Query base, Iterator iterator) { - return from(part.getType(), where(part.getProperty().toDotPath()), + Criteria criteria = from(part.getType(), where(part.getProperty().toDotPath()), iterator); + return base.and(criteria); } @@ -92,10 +94,9 @@ class MongoQueryCreator extends AbstractQueryCreator { * #or(java.lang.Object, java.lang.Object) */ @Override - protected Criteria or(Criteria base, Criteria criteria) { - - base.or(Collections.singletonList(new Query(criteria))); - return base; + protected Query or(Query base, Query query) { + + return new Query().or(base, query); } @@ -107,9 +108,7 @@ class MongoQueryCreator extends AbstractQueryCreator { * #complete(java.lang.Object, org.springframework.data.domain.Sort) */ @Override - protected Query complete(Criteria criteria, Sort sort) { - - Query query = new Query(criteria); + protected Query complete(Query query, Sort sort) { if (LOG.isDebugEnabled()) { LOG.debug("Created query " + query.getQueryObject()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryMethod.java index ec6777e0a..9d6da84b6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryMethod.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoQueryMethod.java @@ -31,7 +31,7 @@ import org.springframework.util.StringUtils; class MongoQueryMethod extends QueryMethod { private final Method method; - private final Class domainClass; + private final MongoEntityInformation entityInformation; /** * Creates a new {@link MongoQueryMethod} from the given {@link Method}. @@ -41,27 +41,9 @@ class MongoQueryMethod extends QueryMethod { public MongoQueryMethod(Method method, Class domainClass) { super(method); this.method = method; - this.domainClass = domainClass; + this.entityInformation = new MongoEntityInformation(ClassUtils.getReturnedDomainClass(method)); } - - /* (non-Javadoc) - * @see org.springframework.data.repository.query.QueryMethod#getDomainClass() - */ - @Override - public Class getDomainClass() { - return this.domainClass; - } - - - /** - * Returns the type that will be returned by the query method. - * - * @return - */ - public Class getReturnedDomainClass() { - return ClassUtils.getReturnedDomainClass(method); - } - + /** * Returns whether the method has an annotated query. @@ -95,6 +77,15 @@ class MongoQueryMethod extends QueryMethod { return StringUtils.hasText(value) ? value : null; } + + /* (non-Javadoc) + * @see org.springframework.data.repository.query.QueryMethod#getEntityMetadata() + */ + @Override + public MongoEntityInformation getEntityMetadata() { + + return entityInformation; + } /** * Returns the {@link Query} annotation that is applied to the method or {@code null} if none available. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java index b03370f32..ca4be6d09 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/MongoRepositoryFactoryBean.java @@ -18,12 +18,20 @@ package org.springframework.data.document.mongodb.repository; import java.io.Serializable; import java.lang.reflect.Method; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.document.mongodb.MongoOperations; import org.springframework.data.document.mongodb.MongoPropertyDescriptors.MongoPropertyDescriptor; import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.document.mongodb.query.Index; +import org.springframework.data.document.mongodb.query.Order; +import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.support.EntityMetadata; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.data.repository.support.QueryCreationListener; import org.springframework.data.repository.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.support.RepositoryFactorySupport; import org.springframework.data.repository.support.RepositoryMetadata; @@ -57,7 +65,9 @@ public class MongoRepositoryFactoryBean extends RepositoryFactoryBeanSupport info = new MongoEntityMetadata((Class) metadata.getDomainClass()); + MongoEntityInformation info = new MongoEntityInformation( + (Class) metadata.getDomainClass()); return new SimpleMongoRepository(info, template); } @@ -152,4 +163,55 @@ public class MongoRepositoryFactoryBean extends RepositoryFactoryBeanSupport { + + private static final Logger LOG = LoggerFactory.getLogger(IndexEnsuringQueryCreationListener.class); + private final MongoOperations operations; + + public IndexEnsuringQueryCreationListener(MongoOperations operations) { + this.operations = operations; + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.repository.support.QueryCreationListener#onCreation(org.springframework.data.repository + * .query.RepositoryQuery) + */ + public void onCreation(PartTreeMongoQuery query) { + + PartTree tree = query.getTree(); + Index index = new Index(); + index.named(query.getQueryMethod().getName()); + Sort sort = tree.getSort(); + + for (Part part : tree.getParts()) { + String property = part.getProperty().toDotPath(); + Order order = toOrder(sort, property); + index.on(property, order); + } + + MongoEntityInformation metadata = query.getQueryMethod().getEntityMetadata(); + operations.ensureIndex(metadata.getCollectionName(), index); + LOG.debug(String.format("Created index %s!", index.toString())); + } + + private static Order toOrder(Sort sort, String property) { + + if (sort == null) { + return Order.DESCENDING; + } + + org.springframework.data.domain.Sort.Order order = sort.getOrderFor(property); + return order == null ? Order.DESCENDING : order.isAscending() ? Order.ASCENDING : Order.DESCENDING; + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/PartTreeMongoQuery.java index a9914874a..1ea20789e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/PartTreeMongoQuery.java @@ -39,7 +39,14 @@ public class PartTreeMongoQuery extends AbstractMongoQuery { public PartTreeMongoQuery(MongoQueryMethod method, MongoTemplate template) { super(method, template); - this.tree = new PartTree(method.getName(), method.getDomainClass()); + this.tree = new PartTree(method.getName(), method.getEntityMetadata().getJavaType()); + } + + /** + * @return the tree + */ + public PartTree getTree() { + return tree; } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryUtils.java index 3ca3eae66..39e6ba567 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/repository/QueryUtils.java @@ -19,7 +19,6 @@ import org.springframework.data.document.mongodb.query.Query; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; -import org.springframework.util.StringUtils; import com.mongodb.DBCursor; @@ -84,10 +83,4 @@ abstract class QueryUtils { return query; } - - - public static String getCollectionName(Class domainClass) { - - return StringUtils.uncapitalize(domainClass.getSimpleName()); - } } 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 12640021a..7f0eee2bf 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 @@ -15,218 +15,207 @@ */ package org.springframework.data.document.mongodb.repository; -import static org.springframework.data.document.mongodb.query.Criteria.*; -import static org.springframework.data.document.mongodb.repository.QueryUtils.*; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - import org.bson.types.ObjectId; -import org.springframework.data.document.mongodb.convert.MongoConverter; import org.springframework.data.document.mongodb.MongoTemplate; +import org.springframework.data.document.mongodb.query.Criteria; import org.springframework.data.document.mongodb.query.Query; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.data.repository.support.EntityMetadata; import org.springframework.util.Assert; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.springframework.data.document.mongodb.query.Criteria.where; /** * Repository base implementation for Mongo. - * + * * @author Oliver Gierke */ public class SimpleMongoRepository implements PagingAndSortingRepository { - private final MongoTemplate template; - private final EntityMetadata entityInformation; + private final MongoTemplate template; + private final MongoEntityInformation entityInformation; + /** + * Creates a ew {@link SimpleMongoRepository} for the given {@link MongoInformation} and {@link MongoTemplate}. + * + * @param metadata + * @param template + */ + public SimpleMongoRepository(MongoEntityInformation metadata, MongoTemplate template) { - /** - * Creates a ew {@link SimpleMongoRepository} for the given domain class and - * {@link MongoTemplate}. - * - * @param domainClass - * @param template - */ - public SimpleMongoRepository(EntityMetadata entityInformation, MongoTemplate template) { + Assert.notNull(template); + Assert.notNull(metadata); + this.entityInformation = metadata; + this.template = template; + } - Assert.notNull(entityInformation); - Assert.notNull(template); - this.entityInformation = entityInformation; - this.template = template; - } - - private Class getDomainClass() { - return entityInformation.getJavaType(); + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#save(java.lang.Object) + */ + public T save(T entity) { + + template.save(entityInformation.getCollectionName(), entity); + return entity; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#save(java.lang.Iterable) + */ + public List save(Iterable entities) { + + List result = new ArrayList(); + + for (T entity : entities) { + save(entity); + result.add(entity); } + return result; + } - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.Repository#save(java.lang.Object) - */ - public T save(T entity) { + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#findById(java.io.Serializable ) + */ + public T findById(ID id) { - template.save(getCollectionName(getDomainClass()), entity); - return entity; + return template.findOne(entityInformation.getCollectionName(), getIdQuery(id), entityInformation.getJavaType()); + } + + private Query getIdQuery(Object id) { + + return new Query(getIdCriteria(id)); + } + + private Criteria getIdCriteria(Object id) { + ObjectId objectId = template.getConverter().convertObjectId(id); + return where(entityInformation.getIdAttribute()).is(objectId); + } + + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#exists(java.io.Serializable ) + */ + public boolean exists(ID id) { + + return findById(id) != null; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#count() + */ + public Long count() { + + return template.getCollection(entityInformation.getCollectionName()).count(); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#delete(java.lang.Object) + */ + public void delete(T entity) { + + template.remove(entityInformation.getCollectionName(), getIdQuery(entityInformation.getId(entity))); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#delete(java.lang.Iterable) + */ + public void delete(Iterable entities) { + + for (T entity : entities) { + delete(entity); + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#deleteAll() + */ + public void deleteAll() { + + template.dropCollection(entityInformation.getCollectionName()); + } + + /* (non-Javadoc) + * @see org.springframework.data.repository.Repository#findAll() + */ + public List findAll() { + return findAll(new Query()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.PagingAndSortingRepository#findAll + * (org.springframework.data.domain.Pageable) + */ + public Page findAll(final Pageable pageable) { + + Long count = count(); + List list = findAll(QueryUtils.applyPagination(new Query(), pageable)); + + return new PageImpl(list, pageable, count); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.PagingAndSortingRepository#findAll + * (org.springframework.data.domain.Sort) + */ + public List findAll(final Sort sort) { + + return findAll(QueryUtils.applySorting(new Query(), sort)); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.repository.Repository#findAll(java.lang.Iterable) + */ + public List findAll(Iterable ids) { + + Query query = null; + + for (ID id : ids) { + if (query == null) { + query = getIdQuery(id); + } else { + query = new Query().or(getIdQuery(id)); + } } + return findAll(query); + } - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.Repository#save(java.lang.Iterable) - */ - public List save(Iterable entities) { + private List findAll(Query query) { - List result = new ArrayList(); - - for (T entity : entities) { - save(entity); - result.add(entity); - } - - return result; + if (query == null) { + return Collections.emptyList(); } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.Repository#findById(java.io.Serializable - * ) - */ - public T findById(ID id) { - - MongoConverter converter = template.getConverter(); - ObjectId objectId = converter.convertObjectId(id); - - return template.findOne(getCollectionName(getDomainClass()), new Query( - where("_id").is(objectId)), getDomainClass()); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.Repository#exists(java.io.Serializable - * ) - */ - public boolean exists(ID id) { - - return findById(id) != null; - } - - - /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#findAll() - */ - public List findAll() { - - return template.getCollection(getCollectionName(getDomainClass()), - getDomainClass()); - } - - - /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#count() - */ - public Long count() { - - return template.getCollection(getCollectionName(getDomainClass())) - .count(); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.Repository#delete(java.lang.Object) - */ - public void delete(T entity) { - - Object id = entityInformation.getId(entity); - ObjectId objectId = template.getConverter().convertObjectId(id); - - Query query = - new Query(where("_id").is( - objectId)); - template.remove(getCollectionName(getDomainClass()), query); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.Repository#delete(java.lang.Iterable) - */ - public void delete(Iterable entities) { - - for (T entity : entities) { - delete(entity); - } - } - - - /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.Repository#deleteAll() - */ - public void deleteAll() { - - template.dropCollection(getCollectionName(getDomainClass())); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.PagingAndSortingRepository#findAll - * (org.springframework.data.domain.Pageable) - */ - public Page findAll(final Pageable pageable) { - - Long count = count(); - Query spec = new Query(); - - List list = - template.find(getCollectionName(getDomainClass()), - QueryUtils.applyPagination(spec, pageable), - getDomainClass()); - - return new PageImpl(list, pageable, count); - } - - - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.PagingAndSortingRepository#findAll - * (org.springframework.data.domain.Sort) - */ - public List findAll(final Sort sort) { - - Query query = QueryUtils.applySorting(new Query(), sort); - return template.find(getCollectionName(getDomainClass()), query, - getDomainClass()); - } + return template.find(entityInformation.getCollectionName(), query, entityInformation.getJavaType()); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/analytics/MvcAnalyticsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/analytics/MvcAnalyticsTests.java index f69d2bc35..b2863a503 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/analytics/MvcAnalyticsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/analytics/MvcAnalyticsTests.java @@ -47,6 +47,8 @@ public class MvcAnalyticsTests { @Test public void loadMvcEventData() { + mongoTemplate.dropCollection("mvc"); + mongoTemplate.createCollection("mvc"); // datasize, favoriteRestId createAndStoreMvcEvent(5, 1); createAndStoreMvcEvent(6, 2); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index f4c3be616..aafac0e8f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -179,4 +179,20 @@ public abstract class AbstractPersonRepositoryIntegrationTests { assertThat(result.size(), is(3)); assertThat(result, hasItems(dave, leroi, stefan)); } + + @Test + public void findsPeopleByLastnameLikeAndAgeIn() throws Exception { + + List result = repository.findByLastnameLikeAndAgeBetween("*e*", 44, 50); + assertThat(result.size(), is(2)); + assertThat(result, hasItems(carter, boyd)); + } + + @Test + public void findsPeopleWithAndAndOr() throws Exception { + + List result = repository.findByAgeOrLastnameLikeAndFirstnameLike(45, "*ss*", "*a*"); + assertThat(result.size(), is(2)); + assertThat(result, hasItems(boyd, stefan)); + } } \ No newline at end of file diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoEntityMetadataUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoEntityMetadataUnitTests.java index 4ee90c0c8..7446377ab 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoEntityMetadataUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/MongoEntityMetadataUnitTests.java @@ -22,7 +22,7 @@ import org.junit.Test; /** - * Unit test for {@link MongoEntityMetadata}. + * Unit test for {@link MongoEntityInformation}. * * @author Oliver Gierke */ @@ -31,8 +31,8 @@ public class MongoEntityMetadataUnitTests { @Test public void findsIdField() throws Exception { - MongoEntityMetadata isNewAware = - new MongoEntityMetadata(Person.class); + MongoEntityInformation isNewAware = + new MongoEntityInformation(Person.class); Person person = new Person(); assertThat(isNewAware.isNew(person), is(true)); @@ -44,7 +44,7 @@ public class MongoEntityMetadataUnitTests { @Test(expected = IllegalArgumentException.class) public void rejectsClassIfNoIdField() throws Exception { - new MongoEntityMetadata(InvalidPerson.class); + new MongoEntityInformation(InvalidPerson.class); } class Person { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java index 0f9b73153..b0872dd3e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/Person.java @@ -192,4 +192,12 @@ public class Person { return id.hashCode(); } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("%s %s", firstname, lastname); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java index 9eb934c69..232d40af7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/repository/PersonRepository.java @@ -113,4 +113,8 @@ public interface PersonRepository extends MongoRepository { List findByAddressZipCode(String zipCode); + + List findByLastnameLikeAndAgeBetween(String lastname, int from, int to); + + List findByAgeOrLastnameLikeAndFirstnameLike(int age, String lastname, String firstname); } diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 19ef84cf4..3dd377881 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -7,6 +7,7 @@ Changes in version 1.0.0.M2 MongoDB Repository * Adapted new metamodel API (DATADOC-47, DATACMNS-17) * Added support for 'In' and 'NotIn' keyword (DATADOC-46) +* Fixed 'And' and 'Or' keywords Changes in version 1.0.0.M1 MongoDB (2011-02-14) ------------------------------------------------