Merging upstream changes

This commit is contained in:
J. Brisbin
2011-03-07 15:45:51 -06:00
67 changed files with 2644 additions and 670 deletions

View File

@@ -11,6 +11,7 @@
<module>spring-data-document-parent</module>
<module>spring-data-document-core</module>
<module>spring-data-mongodb</module>
<module>spring-data-mongodb-cross-store</module>
<module>spring-data-couchdb</module>
</modules>

View File

@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" path="src/test/java"/>
<classpathentry kind="src" path="src/test/resources"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>

View File

@@ -10,6 +10,43 @@
<artifactId>spring-data-couchdb</artifactId>
<packaging>jar</packaging>
<name>Spring Data CouchDB Support</name>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>tareq.abedrabbo</id>
<name>Tareq Abedrabbo</name>
<email>tareq.abedrabbo@opencredo.com</email>
<organization>OpenCredo</organization>
<organizationUrl>http://www.opencredo.org</organizationUrl>
<roles>
<role>Project Admin</role>
<role>Developer</role>
</roles>
<timezone>+0</timezone>
</developer>
<developer>
<id>tomas.lukosius</id>
<name>Tomas Lukosius</name>
<email>tomas.lukosius@opencredo.com</email>
<organization>OpenCredo</organization>
<organizationUrl>http://www.opencredo.org</organizationUrl>
<roles>
<role>Project Admin</role>
<role>Developer</role>
</roles>
<timezone>+0</timezone>
</developer>
</developers>
<dependencies>
<!-- Spring -->
@@ -21,6 +58,12 @@
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Data -->
<dependency>
@@ -28,6 +71,18 @@
<artifactId>spring-data-document-core</artifactId>
</dependency>
<!-- Jackson JSON -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.6.1</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
@@ -82,16 +137,18 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- Couch DB -->
<dependency>
<groupId>com.google.code.jcouchdb</groupId>
<artifactId>jcouchdb</artifactId>
<version>0.11.0-1</version>
</dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<!--
<build>
<plugins>
<plugin>
@@ -100,4 +157,5 @@
</plugin>
</plugins>
</build>
-->
</project>

View File

@@ -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);
}
}

View File

@@ -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<Database>, 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<? extends Database> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<Database> {
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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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<String> 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);
}
}

View File

@@ -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<String> listDatabases();
void createDatabase(String name);
void deleteDatabase(String name);
DbInfo getDatabaseInfo(String name);
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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.
* </p>
* 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> T findOne(String id, Class<T> 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> T findOne(URI uri, Class<T> targetClass);
/**
* Maps a Java object to JSON and writes it to the database
* </p>
* 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);
}

View File

@@ -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> T findOne(String id, Class<T> 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> T findOne(URI uri, Class<T> 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<Map> 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<Map> 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<Object> httpEntity = new HttpEntity<Object>(document, httpHeaders);
return httpEntity;
}
}

View File

@@ -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<Object> {
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.
* <p>
* 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.
* <p>
* 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.
*
* <p>
* 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:
*
* <pre class="code">
* protected JavaType getJavaType(Class&lt;?&gt; clazz) {
* if (List.class.isAssignableFrom(clazz)) {
* return TypeFactory.collectionType(ArrayList.class, MyBean.class);
* } else {
* return super.getJavaType(clazz);
* }
* }
* </pre>
*
* @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<String, Integer> 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<String, Integer> readHeaderFields(JsonParser jp)
throws JsonParseException, IOException {
Map<String, Integer> map = new HashMap<String, Integer>();
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 = "";
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,4 @@
/**
* CouchDB specific JMX monitoring support.
*/
package org.springframework.data.document.couchdb.monitor;

View File

@@ -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.
*
* <p>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;
}
}

View File

@@ -0,0 +1 @@
http\://www.springframework.org/schema/data/couch=org.springframework.data.document.couchdb.config.CouchNamespaceHandler

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.springframework.org/schema/data/couch"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:repository="http://www.springframework.org/schema/data/repository"
targetNamespace="http://www.springframework.org/schema/data/couch"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/context"
schemaLocation="http://www.springframework.org/schema/context/spring-context.xsd" />
<xsd:import namespace="http://www.springframework.org/schema/data/repository"
schemaLocation="http://www.springframework.org/schema/data/repository/spring-repository.xsd" />
<xsd:element name="jmx">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines a JMX Model MBeans for monitoring a CouchDB server'.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="database-url" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The database URL of the CouchDB]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -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 +
'}';
}
}

View File

@@ -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<HttpEntity> {
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<HttpEntity> bodyEqual(Object object) {
return new IsBodyEqual(object);
}
}

View File

@@ -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<String> dbs = admin.listDatabases();
admin.createDatabase("foo");
List<String> newDbs = admin.listDatabases();
Assert.assertEquals(dbs.size()+1, newDbs.size());
}
}

View File

@@ -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<String> 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<String> 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> T getDocument(String id, Class<T> 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;
}
}

View File

@@ -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
}
}

View File

@@ -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());
}
}

View File

@@ -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() {
}
}

View File

@@ -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"} );
}
}

View File

@@ -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

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:couch="http://www.springframework.org/schema/data/couch"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/data/couch http://www.springframework.org/schema/data/couch/spring-couch-1.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<couch:jmx/>
<context:mbean-export/>
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean"
p:port="1099" />
<!-- Expose JMX over RMI -->
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean" depends-on="registry"
p:objectName="connector:name=rmi"
p:serviceUrl="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector" />
</beans>

View File

@@ -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"

View File

@@ -1,92 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-document-parent</artifactId>
<name>Spring Data Document Parent</name>
<url>http://www.springsource.org/spring-data/data-document</url>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- versions for commonly-used dependencies -->
<junit.version>4.8.1</junit.version>
<log4j.version>1.2.15</log4j.version>
<org.mockito.version>1.8.4</org.mockito.version>
<org.slf4j.version>1.5.10</org.slf4j.version>
<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
<data.commons.version>1.0.0.BUILD-SNAPSHOT</data.commons.version>
<aspectj.version>1.6.11.M2</aspectj.version>
</properties>
<profiles>
<profile>
<id>strict</id>
<properties>
<maven.test.failure.ignore>false</maven.test.failure.ignore>
</properties>
</profile>
<profile>
<id>fast</id>
<properties>
<maven.test.skip>true</maven.test.skip>
<maven.javadoc.skip>true</maven.javadoc.skip>
</properties>
</profile>
<profile>
<id>staging</id>
<distributionManagement>
<site>
<id>spring-site-staging</id>
<url>file:///${java.io.tmpdir}/spring-data/data-document/docs</url>
</site>
<repository>
<id>spring-milestone-staging</id>
<url>file:///${java.io.tmpdir}/spring-data/data-document/milestone</url>
</repository>
<snapshotRepository>
<id>spring-snapshot-staging</id>
<url>file:///${java.io.tmpdir}/spring-data/data-document/snapshot</url>
</snapshotRepository>
</distributionManagement>
</profile>
<profile>
<id>bootstrap</id>
<!-- TODO: move the repositories in here before release -->
</profile>
</profiles>
<distributionManagement>
<!-- see 'staging' profile for dry-run deployment settings -->
<downloadUrl>http://www.springsource.com/download/community
</downloadUrl>
<site>
<id>static.springframework.org</id>
<url>
scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-data/data-document/docs/${project.version}
</url>
</site>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone Repository</name>
<url>s3://maven.springframework.org/milestone</url>
</repository>
<snapshotRepository>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>s3://maven.springframework.org/snapshot</url>
</snapshotRepository>
</distributionManagement>
<dependencyManagement>
<!--
inheritable <dependency> declarations for child poms. children still
must explicitly declare the groupId/artifactId of these dependencies
in order for them to show up on the classpath, but metadata like
<version> and <scope> are inherited, which cuts down on verbosity.
see
http://www.sonatype.com/books/mvnref-book/reference/pom-relationships-sect-dep-manage.html
-->
<dependencies>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-document-parent</artifactId>
<name>Spring Data Document Parent</name>
<url>http://www.springsource.org/spring-data/data-document</url>
<version>1.0.0.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- versions for commonly-used dependencies -->
<junit.version>4.8.1</junit.version>
<log4j.version>1.2.15</log4j.version>
<org.mockito.version>1.8.4</org.mockito.version>
<org.slf4j.version>1.5.10</org.slf4j.version>
<org.codehaus.jackson.version>1.6.1</org.codehaus.jackson.version>
<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
<data.commons.version>1.0.0.BUILD-SNAPSHOT</data.commons.version>
<aspectj.version>1.6.11.M2</aspectj.version>
</properties>
<profiles>
<profile>
<id>strict</id>
<properties>
<maven.test.failure.ignore>false</maven.test.failure.ignore>
</properties>
</profile>
<profile>
<id>fast</id>
<properties>
<maven.test.skip>true</maven.test.skip>
<maven.javadoc.skip>true</maven.javadoc.skip>
</properties>
</profile>
<profile>
<id>staging</id>
<distributionManagement>
<site>
<id>spring-site-staging</id>
<url>file:///${java.io.tmpdir}/spring-data/data-document/docs</url>
</site>
<repository>
<id>spring-milestone-staging</id>
<url>file:///${java.io.tmpdir}/spring-data/data-document/milestone</url>
</repository>
<snapshotRepository>
<id>spring-snapshot-staging</id>
<url>file:///${java.io.tmpdir}/spring-data/data-document/snapshot</url>
</snapshotRepository>
</distributionManagement>
</profile>
<profile>
<id>bootstrap</id>
<!-- TODO: move the repositories in here before release -->
</profile>
</profiles>
<distributionManagement>
<!-- see 'staging' profile for dry-run deployment settings -->
<downloadUrl>http://www.springsource.com/download/community
</downloadUrl>
<site>
<id>static.springframework.org</id>
<url>scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-data/data-document/docs/${project.version}
</url>
</site>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone Repository</name>
<url>s3://maven.springframework.org/milestone</url>
</repository>
<snapshotRepository>
<id>spring-snapshot</id>
<name>Spring Snapshot Repository</name>
<url>s3://maven.springframework.org/snapshot</url>
</snapshotRepository>
</distributionManagement>
<dependencyManagement>
<!--
inheritable <dependency> declarations for child poms. children still
must explicitly declare the groupId/artifactId of these dependencies
in order for them to show up on the classpath, but metadata like
<version> and <scope> are inherited, which cuts down on verbosity.
see
http://www.sonatype.com/books/mvnref-book/reference/pom-relationships-sect-dep-manage.html
-->
<dependencies>
<!-- Spring -->
<dependency>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java"/>
<classpathentry including="**/*.aj|**/*.java" kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry including="**/*.java" kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry including="**/*.aj|**/*.java" kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -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

View File

@@ -189,10 +189,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
<!--aspectLibrary>
<aspectLibrary>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons-aspects</artifactId>
</aspectLibrary-->
</aspectLibrary>
</aspectLibraries>
<source>1.6</source>
<target>1.6</target>

View File

@@ -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<Object> {
protected final Log log = LogFactory.getLog(getClass());
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private ConversionService conversionService;
@Override
public void getPersistentState(Class<? extends ChangeSetBacked> 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<? extends ChangeSetBacked> 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<? extends ChangeSetBacked> 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;
}
}

View File

@@ -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<DocumentEntity> {
}

View File

@@ -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<Object, ChangeSetBacked> {
@Autowired
private DB mongoDb;
@Autowired
private MongoChangeSetPersister changeSetPersister;
@Override
public boolean cacheInEntity() {
return true;
}
@Override
public ChangeSetBacked findEntity(Class<ChangeSetBacked> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<Object> changeSetPersister() {
return new MongoConfigurationHolder().changeSetConfig.getChangeSetPersister();
}
private static ChangeSetSynchronizer<ChangeSetBacked> changeSetManager() {
return new MongoConfigurationHolder().changeSetConfig.getChangeSetManager();
}
@Configurable
public static class MongoConfigurationHolder {
@Autowired
@Qualifier("mongoChangeSetConfiguration")
public ChangeSetConfiguration<Object> 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;
}
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="test" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>org.springframework.persistence.document.test.Account</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<!--value='create' to build a new database on each run; value='update' to modify an existing database; value='create-drop' means the same as 'create' but also drops tables when Hibernate closes; value='validate' makes no changes to the database-->
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/>
</properties>
</persistence-unit>
</persistence>

View File

@@ -1,19 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
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">
<context:spring-configured/>
<context:component-scan base-package="org.springframework.persistence.test">
<context:exclude-filter expression=".*_Roo_.*" type="regex"/>
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<!-- Mongo config -->
<bean id="mongo" class="org.springframework.data.document.mongodb.MongoFactoryBean">
<property name="host" value="localhost" />
<property name="port" value="27017" />
</bean>
<bean id="mongoTemplate" class="org.springframework.data.document.mongodb.MongoTemplate">
<constructor-arg ref="mongo" />
<constructor-arg value="database" />
<property name="defaultCollectionName" value="springdata" />
<constructor-arg name="mongo" ref="mongo" />
<constructor-arg name="databaseName" value="test" />
</bean>
<bean class="org.springframework.data.document.mongodb.MongoExceptionTranslator" />
<!-- Mongo aspect config -->
<bean class="org.springframework.persistence.document.MongoDocumentBacking"
factory-method="aspectOf">
<property name="changeSetConfiguration" ref="mongoChangeSetConfiguration"/>
</bean>
<bean id ="mongoChangeSetPersister" class="org.springframework.persistence.document.MongoChangeSetPersister" />
<bean id="mongoChangeSetSynchronizer" class="org.springframework.persistence.support.SimpleReflectiveChangeSetSynchronizer" />
<bean id="mongoChangeSetConfiguration" class="org.springframework.persistence.support.ChangeSetConfiguration">
<property name="changeSetPersister" ref="mongoChangeSetPersister"/>
<property name="changeSetManager" ref="mongoChangeSetSynchronizer"/>
</bean>
<!-- Needed for ChangeSet persistence -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean" />
<jdbc:embedded-database id="dataSource" type="HSQL">
</jdbc:embedded-database>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" >
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
<property name="persistenceUnitName" value="test"/>
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
<property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect"/>
</bean>
</property>
</bean>
</beans>

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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<DBCursor>() {
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<DBCursor>() {
/*
* (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);
}
}
}

View File

@@ -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<T extends Object> extends AbstractEntityMetadata<T> {
class MongoEntityInformation<T extends Object> extends AbstractEntityInformation<T> {
private static final List<String> 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<T> domainClass) {
public MongoEntityInformation(Class<T> domainClass) {
super(domainClass);
@@ -61,16 +62,16 @@ class MongoEntityMetadata<T extends Object> extends AbstractEntityMetadata<T> {
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";
}

View File

@@ -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<Query, Criteria> {
class MongoQueryCreator extends AbstractQueryCreator<Query, Query> {
private static final Logger LOG = LoggerFactory.getLogger(MongoQueryCreator.class);
@@ -64,10 +63,12 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
* @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<Object> iterator) {
protected Query create(Part part, Iterator<Object> 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<Query, Criteria> {
* @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<Object> 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<Query, Criteria> {
* #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<Query, Criteria> {
* #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());

View File

@@ -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.

View File

@@ -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<Mon
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
return new MongoRepositoryFactory(template);
MongoRepositoryFactory factory = new MongoRepositoryFactory(template);
factory.addQueryCreationListener(new IndexEnsuringQueryCreationListener(template));
return factory;
}
/*
@@ -95,7 +105,8 @@ public class MongoRepositoryFactoryBean extends RepositoryFactoryBeanSupport<Mon
@SuppressWarnings("unchecked")
protected Object getTargetRepository(RepositoryMetadata metadata) {
EntityMetadata<Object> info = new MongoEntityMetadata<Object>((Class<Object>) metadata.getDomainClass());
MongoEntityInformation<Object> info = new MongoEntityInformation<Object>(
(Class<Object>) metadata.getDomainClass());
return new SimpleMongoRepository<Object, Serializable>(info, template);
}
@@ -152,4 +163,55 @@ public class MongoRepositoryFactoryBean extends RepositoryFactoryBeanSupport<Mon
super.validate(metadata, customImplementation);
}
}
/**
* {@link QueryCreationListener} inspecting {@link PartTreeMongoQuery}s and creating an index for the properties it
* refers to.
*
* @author Oliver Gierke
*/
private static class IndexEnsuringQueryCreationListener implements QueryCreationListener<PartTreeMongoQuery> {
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;
}
}
}

View File

@@ -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;
}
/*

View File

@@ -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());
}
}

View File

@@ -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<T, ID extends Serializable> implements PagingAndSortingRepository<T, ID> {
private final MongoTemplate template;
private final EntityMetadata<T> entityInformation;
private final MongoTemplate template;
private final MongoEntityInformation<T> entityInformation;
/**
* Creates a ew {@link SimpleMongoRepository} for the given {@link MongoInformation} and {@link MongoTemplate}.
*
* @param metadata
* @param template
*/
public SimpleMongoRepository(MongoEntityInformation<T> metadata, MongoTemplate template) {
/**
* Creates a ew {@link SimpleMongoRepository} for the given domain class and
* {@link MongoTemplate}.
*
* @param domainClass
* @param template
*/
public SimpleMongoRepository(EntityMetadata<T> 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<T> 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<T> save(Iterable<? extends T> entities) {
List<T> result = new ArrayList<T>();
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<? extends T> 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<T> findAll() {
return findAll(new Query());
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll
* (org.springframework.data.domain.Pageable)
*/
public Page<T> findAll(final Pageable pageable) {
Long count = count();
List<T> list = findAll(QueryUtils.applyPagination(new Query(), pageable));
return new PageImpl<T>(list, pageable, count);
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.repository.PagingAndSortingRepository#findAll
* (org.springframework.data.domain.Sort)
*/
public List<T> 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<T> findAll(Iterable<ID> 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<T> save(Iterable<? extends T> entities) {
private List<T> findAll(Query query) {
List<T> result = new ArrayList<T>();
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<T> 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<? extends T> 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<T> findAll(final Pageable pageable) {
Long count = count();
Query spec = new Query();
List<T> list =
template.find(getCollectionName(getDomainClass()),
QueryUtils.applyPagination(spec, pageable),
getDomainClass());
return new PageImpl<T>(list, pageable, count);
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.data.repository.PagingAndSortingRepository#findAll
* (org.springframework.data.domain.Sort)
*/
public List<T> 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());
}
}

View File

@@ -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);

View File

@@ -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<Person> result = repository.findByLastnameLikeAndAgeBetween("*e*", 44, 50);
assertThat(result.size(), is(2));
assertThat(result, hasItems(carter, boyd));
}
@Test
public void findsPeopleWithAndAndOr() throws Exception {
List<Person> result = repository.findByAgeOrLastnameLikeAndFirstnameLike(45, "*ss*", "*a*");
assertThat(result.size(), is(2));
assertThat(result, hasItems(boyd, stefan));
}
}

View File

@@ -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<Person> isNewAware =
new MongoEntityMetadata<Person>(Person.class);
MongoEntityInformation<Person> isNewAware =
new MongoEntityInformation<Person>(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>(InvalidPerson.class);
new MongoEntityInformation<InvalidPerson>(InvalidPerson.class);
}
class Person {

View File

@@ -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);
}
}

View File

@@ -113,4 +113,8 @@ public interface PersonRepository extends MongoRepository<Person, String> {
List<Person> findByAddressZipCode(String zipCode);
List<Person> findByLastnameLikeAndAgeBetween(String lastname, int from, int to);
List<Person> findByAgeOrLastnameLikeAndFirstnameLike(int age, String lastname, String firstname);
}

View File

@@ -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)
------------------------------------------------