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

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

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

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

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

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

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

Mainly intended for internal use within the framework. * * @author Thomas Risberg + * @author Tareq Abedrabbo * @since 1.0 */ -public class CouchDbUtils { +public abstract class CouchUtils { /** * Convert the given runtime exception to an appropriate exception from the @@ -41,17 +41,38 @@ public class CouchDbUtils { */ public static DataAccessException translateCouchExceptionIfPossible(RuntimeException ex) { - // Check for well-known MongoException subclasses. - - // All other MongoExceptions - if (ex instanceof CouchDBException) { - return new UncategorizedDocumentStoreException(ex.getMessage(), ex); - } - - // If we get here, we have an exception that resulted from user code, - // rather than the persistence provider, so we return null to indicate - // that translation should not occur. return null; } + + /** + * Adds an id variable to a URL + * @param url the URL to modify + * @return the modified URL + */ + public static String addId(String url) { + return ensureTrailingSlash(url) + "{id}"; + } + + + /** + * Adds a 'changes since' variable to a URL + * @param url + * @return + */ + public static String addChangesSince(String url) { + return ensureTrailingSlash(url) + "_changes?since={seq}"; + } + + /** + * Ensures that a URL ends with a slash. + * @param url the URL to modify + * @return the modified URL + */ + public static String ensureTrailingSlash(String url) { + if (!url.endsWith("/")) { + url += "/"; + } + return url; + } } diff --git a/spring-data-couchdb/src/main/resources/META-INF/spring.handlers b/spring-data-couchdb/src/main/resources/META-INF/spring.handlers new file mode 100644 index 000000000..e8486b631 --- /dev/null +++ b/spring-data-couchdb/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/data/couch=org.springframework.data.document.couchdb.config.CouchNamespaceHandler diff --git a/spring-data-couchdb/src/main/resources/META-INF/spring.schemas b/spring-data-couchdb/src/main/resources/META-INF/spring.schemas new file mode 100644 index 000000000..7ca5ca1c2 --- /dev/null +++ b/spring-data-couchdb/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/data/couch/spring-couch-1.0.xsd=org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd +http\://www.springframework.org/schema/data/couch/spring-couch.xsd=org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd diff --git a/spring-data-couchdb/src/main/resources/META-INF/spring.tooling b/spring-data-couchdb/src/main/resources/META-INF/spring.tooling new file mode 100644 index 000000000..5859f57de --- /dev/null +++ b/spring-data-couchdb/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the Couch DB namespace +http\://www.springframework.org/schema/data/couch@name=Couch Namespace +http\://www.springframework.org/schema/data/couch@prefix=couch +http\://www.springframework.org/schema/data/couch@icon=org/springframework/jdbc/config/spring-jdbc.gif diff --git a/spring-data-couchdb/src/main/resources/org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd b/spring-data-couchdb/src/main/resources/org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd new file mode 100644 index 000000000..d4e21345b --- /dev/null +++ b/spring-data-couchdb/src/main/resources/org/springframework/data/document/couchdb/config/spring-couch-1.0.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/DummyDocument.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/DummyDocument.java new file mode 100644 index 000000000..d43677696 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/DummyDocument.java @@ -0,0 +1,77 @@ +/* + * Copyright 2011 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.couchdb; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +import java.util.Date; + +/** + * @author Tareq Abedrabbo (tareq.abedrabbo@opencredo.com) + * @since 13/01/2011 + */ +@JsonIgnoreProperties(ignoreUnknown=true) +public class DummyDocument { + + private String message; + + private String timestamp = new Date().toString(); + + public DummyDocument() { + } + + public DummyDocument(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getTimestamp() { + return timestamp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DummyDocument document = (DummyDocument) o; + + if (message != null ? !message.equals(document.message) : document.message != null) return false; + + return true; + } + + @Override + public int hashCode() { + return message != null ? message.hashCode() : 0; + } + + @Override + public String toString() { + return "DummyDocument{" + + "message='" + message + '\'' + + ", timestamp=" + timestamp + + '}'; + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/IsBodyEqual.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/IsBodyEqual.java new file mode 100644 index 000000000..8f690bcc2 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/IsBodyEqual.java @@ -0,0 +1,51 @@ +/* + * Copyright 2011 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.couchdb; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.springframework.http.HttpEntity; + +/** + * Matches the content of the body of an HttpEntity. + * @author Tareq Abedrabbo + * @since 31/01/2011 + */ +public class IsBodyEqual extends TypeSafeMatcher { + + private Object object; + + public IsBodyEqual(Object object) { + this.object = object; + } + + @Override + public boolean matchesSafely(HttpEntity httpEntity) { + return httpEntity.getBody().equals(object); + } + + public void describeTo(Description description) { + description.appendText("body equals ").appendValue(object); + } + + @Factory + public static Matcher bodyEqual(Object object) { + return new IsBodyEqual(object); + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/admin/CouchAdminIntegrationTests.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/admin/CouchAdminIntegrationTests.java new file mode 100644 index 000000000..6054a3742 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/admin/CouchAdminIntegrationTests.java @@ -0,0 +1,37 @@ +/* + * 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.Test; +import org.springframework.data.document.couchdb.core.CouchConstants; + +public class CouchAdminIntegrationTests { + + @Test + public void dbLifecycle() { + + CouchAdmin admin = new CouchAdmin(CouchConstants.COUCHDB_URL); + admin.deleteDatabase("foo"); + List dbs = admin.listDatabases(); + admin.createDatabase("foo"); + List newDbs = admin.listDatabases(); + Assert.assertEquals(dbs.size()+1, newDbs.size()); + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/AbstractCouchTemplateIntegrationTests.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/AbstractCouchTemplateIntegrationTests.java new file mode 100644 index 000000000..98fa48750 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/AbstractCouchTemplateIntegrationTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2011 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.couchdb.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.UUID; + +import static org.junit.Assume.assumeNoException; +import static org.junit.Assume.assumeTrue; +import static org.springframework.http.HttpStatus.OK; + +/** + * Base class for CouchDB integration tests. Checks whether CouchDB is available before running each test, + * in which case the test is executed. If CouchDB is not available, tests are ignored. + * + * @author Tareq Abedrabbo (tareq.abedrabbo@opencredo.com) + * @since 13/01/2011 + */ + +public abstract class AbstractCouchTemplateIntegrationTests { + + + protected static final Log log = LogFactory.getLog(AbstractCouchTemplateIntegrationTests.class); + + protected static final RestTemplate restTemplate = new RestTemplate(); + + /** + * This methods ensures that the database is running. Otherwise, the test is ignored. + */ + @BeforeClass + public static void assumeDatabaseIsUpAndRunning() { + try { + ResponseEntity responseEntity = restTemplate.getForEntity(CouchConstants.COUCHDB_URL, String.class); + assumeTrue(responseEntity.getStatusCode().equals(OK)); + log.debug("CouchDB is running on " + CouchConstants.COUCHDB_URL + + " with status " + responseEntity.getStatusCode()); + } catch (RestClientException e) { + log.debug("CouchDB is not running on " + CouchConstants.COUCHDB_URL); + assumeNoException(e); + } + } + + @Before + public void setUpTestDatabase() throws Exception { + RestTemplate template = new RestTemplate(); + template.setErrorHandler(new DefaultResponseErrorHandler(){ + @Override + public void handleError(ClientHttpResponse response) throws IOException { + // do nothing, error status will be handled in the switch statement + } + }); + ResponseEntity response = template.getForEntity(CouchConstants.TEST_DATABASE_URL, String.class); + HttpStatus statusCode = response.getStatusCode(); + switch (statusCode) { + case NOT_FOUND: + createNewTestDatabase(); + break; + case OK: + deleteExisitingTestDatabase(); + createNewTestDatabase(); + break; + default: + throw new IllegalStateException("Unsupported http status [" + statusCode + "]"); + } + } + + private void deleteExisitingTestDatabase() { + restTemplate.delete(CouchConstants.TEST_DATABASE_URL); + } + + private void createNewTestDatabase() { + restTemplate.put(CouchConstants.TEST_DATABASE_URL, null); + } + + /** + * Reads a CouchDB document and converts it to the expected type. + */ + protected T getDocument(String id, Class expectedType) { + String url = CouchConstants.TEST_DATABASE_URL + "{id}"; + return restTemplate.getForObject(url, expectedType, id); + } + + /** + * Writes a CouchDB document + */ + protected String putDocument(Object document) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity request = new HttpEntity(document, headers); + String id = UUID.randomUUID().toString(); + restTemplate.put(CouchConstants.TEST_DATABASE_URL + "{id}", request, id); + return id; + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchConstants.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchConstants.java new file mode 100644 index 000000000..14412b947 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchConstants.java @@ -0,0 +1,27 @@ +/* + * Copyright 2011 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.document.couchdb.core; + +public abstract class CouchConstants { + + public static final String COUCHDB_URL = "http://127.0.0.1:5984/"; + public static final String TEST_DATABASE_URL = COUCHDB_URL + "si_couchdb_test/"; + + public CouchConstants() { + // TODO Auto-generated constructor stub + } + +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateIntegrationTests.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateIntegrationTests.java new file mode 100644 index 000000000..33411243b --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateIntegrationTests.java @@ -0,0 +1,41 @@ +/* + * 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.Test; +import org.junit.runner.RunWith; +import org.springframework.data.document.couchdb.DummyDocument; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.Assert.*; + +public class CouchTemplateIntegrationTests extends AbstractCouchTemplateIntegrationTests { + + + @Test + public void saveAndFindTest() { + CouchTemplate template = new CouchTemplate(CouchConstants.TEST_DATABASE_URL); + DummyDocument document = new DummyDocument("hello"); + String id = UUID.randomUUID().toString(); + template.save(id, document); + DummyDocument foundDocument = template.findOne(id, DummyDocument.class); + Assert.assertEquals(document.getMessage(), foundDocument.getMessage()); + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateTests.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateTests.java new file mode 100644 index 000000000..b0163aa43 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/core/CouchTemplateTests.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.couchdb.core; + +import org.junit.Test; + +/** + * Unit tests for CouchTemplate with mocks + * + * + */ +public class CouchTemplateTests { + + @Test + public void foo() { + + } +} diff --git a/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/monitor/JmxServer.java b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/monitor/JmxServer.java new file mode 100644 index 000000000..07cf47222 --- /dev/null +++ b/spring-data-couchdb/src/test/java/org/springframework/data/document/couchdb/monitor/JmxServer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.document.couchdb.monitor; + +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Server application to test JMX functionality. + * + * @author Mark Pollack + */ +public class JmxServer { + + public static void main(String[] args) { + new JmxServer().run(); + } + + public void run() { + new ClassPathXmlApplicationContext(new String[] {"server-jmx.xml"} ); + } + +} diff --git a/spring-data-couchdb/src/test/resources/log4j.properties b/spring-data-couchdb/src/test/resources/log4j.properties new file mode 100644 index 000000000..6d5422d74 --- /dev/null +++ b/spring-data-couchdb/src/test/resources/log4j.properties @@ -0,0 +1,13 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.category.org.apache.activemq=ERROR +log4j.category.org.springframework.batch=DEBUG +log4j.category.org.springframework.transaction=INFO + +log4j.category.org.hibernate.SQL=DEBUG +# for debugging datasource initialization +# log4j.category.test.jdbc=DEBUG diff --git a/spring-data-couchdb/src/test/resources/server-jmx.xml b/spring-data-couchdb/src/test/resources/server-jmx.xml new file mode 100644 index 000000000..30c1933fe --- /dev/null +++ b/spring-data-couchdb/src/test/resources/server-jmx.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-couchdb/template.mf b/spring-data-couchdb/template.mf index a03fa6ca7..af4bc48ff 100644 --- a/spring-data-couchdb/template.mf +++ b/spring-data-couchdb/template.mf @@ -8,10 +8,15 @@ Import-Template: org.springframework.beans.*;version="[3.0.0, 4.0.0)", org.springframework.core.*;version="[3.0.0, 4.0.0)", org.springframework.dao.*;version="[3.0.0, 4.0.0)", + org.springframework.http.*;version="[3.0.0, 4.0.0)", + org.springframework.web.*;version="[3.0.0, 4.0.0)", org.springframework.util.*;version="[3.0.0, 4.0.0)", + org.springframework.context.*;version="[3.0.0, 4.0.0)", + org.springframework.jmx.*;version="[3.0.0, 4.0.0)", + org.springframework.remoting.*;version="[3.0.0, 4.0.0)", org.springframework.data.core.*;version="[1.0.0, 2.0.0)", org.springframework.data.document.*;version="[1.0.0, 2.0.0)", - org.jcouchdb.*;version="0", + org.codehaus.jackson.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", org.w3c.dom.*;version="0" diff --git a/spring-data-document-parent/pom.xml b/spring-data-document-parent/pom.xml index 077bf9455..e3d4eead3 100644 --- a/spring-data-document-parent/pom.xml +++ b/spring-data-document-parent/pom.xml @@ -15,6 +15,7 @@ 1.2.15 1.8.4 1.5.10 + 1.6.1 3.0.5.RELEASE 1.0.0.BUILD-SNAPSHOT 1.6.11.M2 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/analytics/MvcAnalyticsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/analytics/MvcAnalyticsTests.java index f69d2bc35..b2863a503 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/analytics/MvcAnalyticsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/analytics/MvcAnalyticsTests.java @@ -47,6 +47,8 @@ public class MvcAnalyticsTests { @Test public void loadMvcEventData() { + mongoTemplate.dropCollection("mvc"); + mongoTemplate.createCollection("mvc"); // datasize, favoriteRestId createAndStoreMvcEvent(5, 1); createAndStoreMvcEvent(6, 2);