From 0658cc8aee30d5e6bb7bc55f760e4e3e02897e5b Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Wed, 2 Sep 2015 08:57:37 +0200 Subject: [PATCH 1/2] Add Neo4j support See gh-5458 --- spring-boot-actuator/pom.xml | 5 + spring-boot-autoconfigure/pom.xml | 12 ++ .../MongoRepositoriesAutoConfiguration.java | 3 +- .../data/neo4j/Neo4jAutoConfiguration.java | 83 +++++++++ .../data/neo4j/Neo4jProperties.java | 105 +++++++++++ .../Neo4jRepositoriesAutoConfiguration.java | 66 +++++++ ...o4jRepositoriesAutoConfigureRegistrar.java | 55 ++++++ .../data/neo4j/package-info.java | 21 +++ ...itional-spring-configuration-metadata.json | 6 + .../main/resources/META-INF/spring.factories | 2 + .../data/alt/neo4j/CityNeo4jRepository.java | 23 +++ ...o4jRepositoriesAutoConfigurationTests.java | 175 ++++++++++++++++++ ...o4jRepositoriesAutoConfigurationTests.java | 125 +++++++++++++ .../autoconfigure/data/neo4j/city/City.java | 70 +++++++ .../data/neo4j/city/CityRepository.java | 34 ++++ .../data/neo4j/country/Country.java | 49 +++++ .../data/neo4j/country/CountryRepository.java | 23 +++ .../neo4j/Neo4jAutoConfigurationTests.java | 55 ++++++ .../Neo4jDataAutoConfigurationTests.java | 96 ++++++++++ .../neo4j/Neo4jPropertiesTests.java | 84 +++++++++ spring-boot-dependencies/pom.xml | 14 ++ spring-boot-docs/pom.xml | 5 + .../main/asciidoc/spring-boot-features.adoc | 173 +++++++++++++++++ .../src/main/asciidoc/using-spring-boot.adoc | 3 + .../spring-boot-sample-data-neo4j/pom.xml | 55 ++++++ .../main/java/sample/data/neo4j/Customer.java | 45 +++++ .../sample/data/neo4j/CustomerRepository.java | 29 +++ .../data/neo4j/SampleNeo4jApplication.java | 65 +++++++ .../src/main/resources/application.properties | 3 + .../neo4j/SampleNeo4jApplicationTests.java | 50 +++++ spring-boot-starters/pom.xml | 2 + .../spring-boot-starter-data-neo4j/pom.xml | 30 +++ .../main/resources/META-INF/spring.provides | 1 + 33 files changed, 1565 insertions(+), 2 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/package-info.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jDataAutoConfigurationTests.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java create mode 100644 spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/Customer.java create mode 100644 spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/CustomerRepository.java create mode 100644 spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-data-neo4j/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java create mode 100644 spring-boot-starters/spring-boot-starter-data-neo4j/pom.xml create mode 100644 spring-boot-starters/spring-boot-starter-data-neo4j/src/main/resources/META-INF/spring.provides diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index d4c7b547e5..cf323298cb 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -166,6 +166,11 @@ spring-data-mongodb true + + org.springframework.data + spring-data-neo4j + true + org.springframework.data spring-data-redis diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 9951e96cb1..b6de2d4b7b 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -379,6 +379,17 @@ + + org.springframework.data + spring-data-neo4j + true + + + jcl-over-slf4j + org.slf4j + + + org.springframework.data spring-data-redis @@ -635,5 +646,6 @@ tomcat-embed-jasper test + diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java index 1afa03ff98..7a8630d260 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java @@ -53,8 +53,7 @@ import org.springframework.data.mongodb.repository.support.MongoRepositoryFactor */ @Configuration @ConditionalOnClass({ Mongo.class, MongoRepository.class }) -@ConditionalOnMissingBean({ MongoRepositoryFactoryBean.class, - MongoRepositoryConfigurationExtension.class }) +@ConditionalOnMissingBean({ MongoRepositoryFactoryBean.class, MongoRepositoryConfigurationExtension.class }) @ConditionalOnProperty(prefix = "spring.data.mongodb.repositories", name = "enabled", havingValue = "true", matchIfMissing = true) @Import(MongoRepositoriesAutoConfigureRegistrar.class) @AutoConfigureAfter(MongoDataAutoConfiguration.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jAutoConfiguration.java new file mode 100644 index 0000000000..b5b9b69f1b --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jAutoConfiguration.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2015 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.boot.autoconfigure.data.neo4j; + +import org.neo4j.ogm.session.Neo4jSession; +import org.neo4j.ogm.session.Session; +import org.neo4j.ogm.session.SessionFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; + +import org.springframework.data.neo4j.config.Neo4jConfiguration; +import org.springframework.data.neo4j.template.Neo4jOperations; +import org.springframework.data.neo4j.template.Neo4jTemplate; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Neo4j support. + *

+ * Registers a {@link Neo4jTemplate} bean if no other bean of + * the same type is configured. + * + * @author Michael Hunger + * @author Josh Long + * @author Vince Bickers + * @since 1.3.0 + */ +@Configuration +@EnableConfigurationProperties(Neo4jProperties.class) +@ConditionalOnMissingBean(type = "org.springframework.data.neo4j.template.Neo4jOperations") +@ConditionalOnClass({ Neo4jSession.class, Neo4jOperations.class }) +public class Neo4jAutoConfiguration extends Neo4jConfiguration { + + @Autowired + private Neo4jProperties properties; + + @Value("${spring.data.neo4j.domain.packages:null}") + private String[] domainPackages; + + @Bean + @ConditionalOnMissingBean(org.neo4j.ogm.config.Configuration.class) + public org.neo4j.ogm.config.Configuration configuration() { + return this.properties.configure(); + } + + @Override + @ConditionalOnMissingBean(SessionFactory.class) + public SessionFactory getSessionFactory() { + return new SessionFactory(configuration(), this.domainPackages); + } + + @Bean + @ConditionalOnMissingBean(Session.class) + @Scope(value = "${spring.data.neo4j.session.lifetime:session}", proxyMode = ScopedProxyMode.TARGET_CLASS) + public Session getSession() throws Exception { + return getSessionFactory().openSession(); + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java new file mode 100644 index 0000000000..73b3974883 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2015 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.boot.autoconfigure.data.neo4j; + +import org.neo4j.ogm.config.Configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for Neo4j. + * + * @author Dave Syer + * @author Phillip Webb + * @author Josh Long + * @author Andy Wilkinson + * @author Eddú Meléndez + * @author Michael Hunger + * @author Vince Bickers + */ +@ConfigurationProperties(prefix = "spring.data.neo4j") +public class Neo4jProperties { + + // if you don't set this up somewhere, this is what we'll use by default + private String driver = "org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver"; + private String compiler; + private String URI; + private String username; + private String password; + + public String getDriver() { + return this.driver; + } + + public void setDriver(String driver) { + this.driver = driver; + } + + public String getCompiler() { + return this.compiler; + } + + public void setCompiler(String compiler) { + this.compiler = compiler; + } + + public String getURI() { + return this.URI; + } + + public void setURI(String URI) { + this.URI = URI; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Configuration configure() { + Configuration configuration = new Configuration(); + + if (this.driver != null) { + configuration.driverConfiguration().setDriverClassName(this.driver); + } + + if (this.URI != null) { + configuration.driverConfiguration().setURI(this.URI); + } + + if (this.username != null && this.password != null) { + configuration.driverConfiguration().setCredentials(this.username, this.password); + } + + if (this.compiler != null) { + configuration.compilerConfiguration().setCompilerClassName(this.compiler); + } + + return configuration; + } +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java new file mode 100644 index 0000000000..2cc223d8c7 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2014 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.boot.autoconfigure.data.neo4j; + +import org.neo4j.ogm.session.Neo4jSession; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import org.springframework.data.neo4j.repository.GraphRepository; + +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import org.springframework.data.neo4j.repository.config.Neo4jRepositoryConfigurationExtension; +import org.springframework.data.neo4j.repository.support.GraphRepositoryFactoryBean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Neo4j + * Repositories. + *

+ * Activates when there is no bean of type + * {@link org.springframework.data.neo4j.repository.support.GraphRepositoryFactoryBean} + * configured in the context, the Spring Data Neo4j + * {@link org.springframework.data.neo4j.repository.GraphRepository} type is on the + * classpath, the Neo4j client driver API is on the classpath, and there is no other + * configured {@link org.springframework.data.neo4j.repository.GraphRepository}. + *

+ * Once in effect, the auto-configuration is the equivalent of enabling Neo4j repositories + * using the + * {@link org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories} + * annotation. + * + * @author Dave Syer + * @author Oliver Gierke + * @author Josh Long + * @see EnableNeo4jRepositories + */ +@Configuration +@ConditionalOnClass({ Neo4jSession.class, GraphRepository.class }) +@ConditionalOnMissingBean({ GraphRepositoryFactoryBean.class, Neo4jRepositoryConfigurationExtension.class }) +@ConditionalOnProperty(prefix = "spring.data.neo4j.repositories", name = "enabled", havingValue = "true", matchIfMissing = true) +@Import({Neo4jRepositoriesAutoConfigureRegistrar.class}) +@AutoConfigureAfter(Neo4jAutoConfiguration.class) +public class Neo4jRepositoriesAutoConfiguration { + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java new file mode 100644 index 0000000000..1651010535 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2014 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.boot.autoconfigure.data.neo4j; + +import java.lang.annotation.Annotation; + +import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import org.springframework.data.neo4j.repository.config.Neo4jRepositoryConfigurationExtension; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +/** + * {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data Neo4j + * Repositories. + * + * @author Dave Syer + */ +class Neo4jRepositoriesAutoConfigureRegistrar extends + AbstractRepositoryConfigurationSourceSupport { + + @Override + protected Class getAnnotation() { + return EnableNeo4jRepositories.class; + } + + @Override + protected Class getConfiguration() { + return EnableNeo4jRepositoriesConfiguration.class; + } + + @Override + protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() { + return new Neo4jRepositoryConfigurationExtension(); + } + + @EnableNeo4jRepositories + private static class EnableNeo4jRepositoriesConfiguration { + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/package-info.java new file mode 100644 index 0000000000..a681b2a7d5 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2015 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. + */ + +/** + * Auto-configuration for Spring Data Neo4j. + */ +package org.springframework.boot.autoconfigure.data.neo4j; + diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index fba5f4e02c..6b720f28ab 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -90,6 +90,12 @@ "description": "Enable Mongo repositories.", "defaultValue": true }, + { + "name": "spring.data.neo4j.repositories.enabled", + "type": "java.lang.Boolean", + "description": "Enable Neo4j repositories.", + "defaultValue": true + }, { "name": "spring.data.redis.repositories.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 811f7d78f3..90463d5960 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -31,6 +31,8 @@ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositor org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ +org.springframework.boot.autoconfigure.data.neo4j.Neo4jAutoConfiguration,\ +org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java new file mode 100644 index 0000000000..722bc7a823 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-2014 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.boot.autoconfigure.data.alt.neo4j; + +import org.springframework.boot.autoconfigure.data.neo4j.city.City; +import org.springframework.data.neo4j.repository.GraphRepository; + +public interface CityNeo4jRepository extends GraphRepository { +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java new file mode 100644 index 0000000000..6741bb0cb5 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java @@ -0,0 +1,175 @@ +/* + * Copyright 2012-2014 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.boot.autoconfigure.data.neo4j; + +import java.util.ArrayList; +import java.util.List; + +import org.assertj.core.api.Assertions; + +import org.junit.After; +import org.junit.Ignore; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; +import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.data.jpa.city.City; +import org.springframework.boot.autoconfigure.data.jpa.city.CityRepository; +import org.springframework.boot.autoconfigure.data.neo4j.country.Country; +import org.springframework.boot.autoconfigure.data.neo4j.country.CountryRepository; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfigurationTests; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; + +import org.springframework.boot.orm.jpa.EntityScan; + +import org.springframework.boot.test.EnvironmentTestUtils; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; + +import org.springframework.core.type.AnnotationMetadata; + +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; + +/** + * Tests for {@link org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration}. + * + * @author Dave Syer + * @author Oliver Gierke + * @author Michael Hunger + * @author Vince Bickers + */ +public class MixedNeo4jRepositoriesAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + this.context.close(); + } + + @Test + public void testDefaultRepositoryConfiguration() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); + this.context.register(TestConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + Assertions.assertThat(this.context.getBean(CountryRepository.class)).isNotNull(); + } + + @Test + public void testMixedRepositoryConfiguration() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); + this.context.register(MixedConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + Assertions.assertThat(this.context.getBean(CountryRepository.class)).isNotNull(); + Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + } + + @Test + public void testJpaRepositoryConfigurationWithNeo4jTemplate() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); + this.context.register(JpaConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + } + + @Test + @Ignore + public void testJpaRepositoryConfigurationWithNeo4jOverlap() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); + this.context.register(OverlapConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + } + + @Test + public void testJpaRepositoryConfigurationWithNeo4jOverlapDisabled() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false", + "spring.data.neo4j.repositories.enabled:false"); + this.context.register(OverlapConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + } + + @Configuration + @TestAutoConfigurationPackage(Neo4jAutoConfigurationTests.class) + // Not this package or its parent + @EnableNeo4jRepositories(basePackageClasses = Country.class) + protected static class TestConfiguration { + + } + + @Configuration + @TestAutoConfigurationPackage(Neo4jAutoConfigurationTests.class) + @EnableNeo4jRepositories(basePackageClasses = Country.class) + @EntityScan(basePackageClasses = City.class) + @EnableJpaRepositories(basePackageClasses = CityRepository.class) + protected static class MixedConfiguration { + + } + + @Configuration + @TestAutoConfigurationPackage(Neo4jAutoConfigurationTests.class) + @EntityScan(basePackageClasses = City.class) + @EnableJpaRepositories(basePackageClasses = CityRepository.class) + protected static class JpaConfiguration { + + } + + // In this one the Jpa repositories and the autoconfiguration packages overlap, so + // Neo4j will try and configure the same repositories + @Configuration + @TestAutoConfigurationPackage(CityRepository.class) + @EnableJpaRepositories(basePackageClasses = CityRepository.class) + protected static class OverlapConfiguration { + + } + + @Configuration + @Import(Registrar.class) + protected static class BaseConfiguration { + + } + + protected static class Registrar implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + List names = new ArrayList(); + for (Class type : new Class[] { DataSourceAutoConfiguration.class, + HibernateJpaAutoConfiguration.class, + JpaRepositoriesAutoConfiguration.class, + Neo4jAutoConfiguration.class, + Neo4jRepositoriesAutoConfiguration.class }) { + names.add(type.getName()); + } + return names.toArray(new String[names.size()]); + } + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java new file mode 100644 index 0000000000..5afdb1d86e --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2012-2014 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.boot.autoconfigure.data.neo4j; + +import org.assertj.core.api.Assertions; + +import org.junit.After; +import org.junit.Test; + +import org.neo4j.ogm.session.SessionFactory; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; + +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; +import org.springframework.boot.autoconfigure.data.alt.neo4j.CityNeo4jRepository; +import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage; +import org.springframework.boot.autoconfigure.data.neo4j.city.City; +import org.springframework.boot.autoconfigure.data.neo4j.city.CityRepository; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +import org.springframework.data.neo4j.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; + +/** + * Tests for {@link org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration}. + * + * @author Dave Syer + * @author Oliver Gierke + * @author Michael Hunger + * @author Vince Bickers + */ +public class Neo4jRepositoriesAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + this.context.close(); + } + + @Test + public void testDefaultRepositoryConfiguration() throws Exception { + + prepareApplicationContext(TestConfiguration.class); + + Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + + Neo4jMappingContext mappingContext = this.context.getBean(Neo4jMappingContext.class); + Assertions.assertThat(mappingContext.getPersistentEntity(City.class)).isNotNull(); + + } + + @Test + public void testNoRepositoryConfiguration() throws Exception { + prepareApplicationContext(EmptyConfiguration.class); + Assertions.assertThat(this.context.getBean(SessionFactory.class)).isNotNull(); + } + + @Test + public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() { + prepareApplicationContext(CustomizedConfiguration.class); + + Assertions.assertThat(this.context.getBean(CityNeo4jRepository.class)).isNotNull(); + } + + @Test(expected = NoSuchBeanDefinitionException.class) + public void autoConfigurationShouldNotKickInEvenIfManualConfigDidNotCreateAnyRepositories() { + prepareApplicationContext(SortOfInvalidCustomConfiguration.class); + + this.context.getBean(CityRepository.class); + } + + private void prepareApplicationContext(Class... configurationClasses) { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(configurationClasses); + this.context.register(Neo4jAutoConfiguration.class, + Neo4jRepositoriesAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + } + + @Configuration + @TestAutoConfigurationPackage(City.class) + protected static class TestConfiguration { + + } + + @Configuration + @TestAutoConfigurationPackage(EmptyDataPackage.class) + protected static class EmptyConfiguration { + + } + + @Configuration + @TestAutoConfigurationPackage(Neo4jRepositoriesAutoConfigurationTests.class) + @EnableNeo4jRepositories(basePackageClasses = CityNeo4jRepository.class) + protected static class CustomizedConfiguration { + + } + + @Configuration + // To not find any repositories + @EnableNeo4jRepositories("foo.bar") + @TestAutoConfigurationPackage(Neo4jRepositoriesAutoConfigurationTests.class) + protected static class SortOfInvalidCustomConfiguration { + + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java new file mode 100644 index 0000000000..df7694538f --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2013 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.boot.autoconfigure.data.neo4j.city; + +import java.io.Serializable; + +import org.neo4j.ogm.annotation.GraphId; +import org.neo4j.ogm.annotation.NodeEntity; + +import org.springframework.boot.autoconfigure.data.neo4j.country.Country; + +@NodeEntity +public class City implements Serializable { + + private static final long serialVersionUID = 1L; + + @GraphId + private Long id; + + private String name; + + private String state; + + private Country country; + + private String map; + + public City() { + } + + public City(String name, Country country) { + this.name = name; + this.country = country; + } + + public String getName() { + return this.name; + } + + public String getState() { + return this.state; + } + + public Country getCountry() { + return this.country; + } + + public String getMap() { + return this.map; + } + + @Override + public String toString() { + return getName() + "," + getState() + "," + getCountry(); + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java new file mode 100644 index 0000000000..a89e07909f --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2013 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.boot.autoconfigure.data.neo4j.city; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.neo4j.repository.GraphRepository; + +public interface CityRepository extends GraphRepository { + + Page findAll(Pageable pageable); + +// TODO: cannot resolve queries like this at the moment. +// +// Page findByNameLikeAndCountryLikeAllIgnoringCase(String name, String country, +// Pageable pageable); +// +// City findByNameAndCountry(String name, String country); + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java new file mode 100644 index 0000000000..012ddd1e54 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2013 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.boot.autoconfigure.data.neo4j.country; + +import java.io.Serializable; + +import org.neo4j.ogm.annotation.GraphId; +import org.neo4j.ogm.annotation.NodeEntity; + +@NodeEntity +public class Country implements Serializable { + + private static final long serialVersionUID = 1L; + + @GraphId + private Long id; + + private String name; + + public Country() { + } + + public Country(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java new file mode 100644 index 0000000000..1814c642da --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-2013 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.boot.autoconfigure.data.neo4j.country; + +import org.springframework.data.neo4j.repository.GraphRepository; + +public interface CountryRepository extends GraphRepository { + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java new file mode 100644 index 0000000000..d4c10bee39 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2014 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.boot.autoconfigure.neo4j; + +import org.assertj.core.api.Assertions; + +import org.junit.After; +import org.junit.Test; + +import org.neo4j.ogm.config.Configuration; + +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.data.neo4j.Neo4jAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * Tests for {@link Neo4jAutoConfiguration}. + * + * @author Dave Syer + * @author Michael Hunger + * @author Vince Bickers + */ +public class Neo4jAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void configurationExists() { + this.context = new AnnotationConfigApplicationContext( + PropertyPlaceholderAutoConfiguration.class, Neo4jAutoConfiguration.class); + Assertions.assertThat(this.context.getBeanNamesForType(Configuration.class).length).isEqualTo(1); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jDataAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jDataAutoConfigurationTests.java new file mode 100644 index 0000000000..8227b24f7c --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jDataAutoConfigurationTests.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2015 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.boot.autoconfigure.neo4j; + +import org.assertj.core.api.Assertions; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; + +import org.junit.rules.ExpectedException; + +import org.neo4j.ogm.session.SessionFactory; + +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.data.neo4j.Neo4jAutoConfiguration; +import org.springframework.boot.autoconfigure.data.neo4j.city.City; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import org.springframework.data.neo4j.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.template.Neo4jOperations; + +/** + * Tests for {@link Neo4jAutoConfiguration}. + * + * @author Josh Long + * @author Oliver Gierke + * @author Vince Bickers + */ +public class Neo4jDataAutoConfigurationTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void templateExists() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(PropertyPlaceholderAutoConfiguration.class, Neo4jAutoConfiguration.class); + this.context.refresh(); + Assertions.assertThat(this.context.getBeanNamesForType(Neo4jOperations.class).length).isEqualTo(1); + } + + @Test + public void sessionFactoryExists() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(PropertyPlaceholderAutoConfiguration.class, Neo4jAutoConfiguration.class); + this.context.refresh(); + Assertions.assertThat(this.context.getBeanNamesForType(SessionFactory.class).length).isEqualTo(1); + } + + @Test + public void usesAutoConfigurationPackageToPickUpDomainTypes() { + this.context = new AnnotationConfigApplicationContext(); + String cityPackage = City.class.getPackage().getName(); + AutoConfigurationPackages.register(this.context, cityPackage); + this.context.register(Neo4jAutoConfiguration.class); + this.context.refresh(); + assertDomainTypesDiscovered(this.context.getBean(Neo4jMappingContext.class), + City.class); + } + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static void assertDomainTypesDiscovered(Neo4jMappingContext mappingContext, + Class... types) { + for (Class type : types) { + Assertions.assertThat(mappingContext.getPersistentEntity(type)).isNotNull(); + } + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java new file mode 100644 index 0000000000..e619acbf95 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2015 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.boot.autoconfigure.neo4j; + +import org.assertj.core.api.Assertions; + +import org.junit.Test; + +import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; + +/** + * Tests for {@link Neo4jProperties}. + * + * @author Phillip Webb + * @author Andy Wilkinson + * @author Vince Bickers + */ + +public class Neo4jPropertiesTests { + + @Test + public void shouldHaveCorrectDefaultDriver() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Conf.class); + context.refresh(); + + Neo4jProperties neo4jProperties = context.getBean(Neo4jProperties.class); + + Assertions.assertThat(neo4jProperties.getDriver()).isEqualTo("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver"); + } + + @Test + public void shouldConfigureFromDefaults() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.register(Conf.class); + context.refresh(); + + Neo4jProperties neo4jProperties = context.getBean(Neo4jProperties.class); + + org.neo4j.ogm.config.Configuration configuration = neo4jProperties.configure(); + Assertions.assertThat(configuration.driverConfiguration().getDriverClassName()) + .isEqualTo("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver"); + + } + + @Test + public void shouldBeCustomisable() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(context, "spring.data.neo4j.driver:CustomDriver"); + context.register(Conf.class); + context.refresh(); + + Neo4jProperties neo4jProperties = context.getBean(Neo4jProperties.class); + + Assertions.assertThat(neo4jProperties.getDriver()).isEqualTo("CustomDriver"); + + } + + @Configuration + @EnableConfigurationProperties(Neo4jProperties.class) + static class Conf { + } +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index d6570f51fb..39156dd5f8 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -55,9 +55,11 @@ 2.1.9 1.9.2 3.2.2 + 1.10 1.4 2.1.1 2.1 + 3.4 1.6 2.4.2 2.2.3 @@ -325,6 +327,11 @@ spring-boot-starter-data-redis 1.4.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-starter-data-neo4j + 1.4.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter-data-rest @@ -768,11 +775,17 @@ commons-collections ${commons-collections.version} + + commons-codec + commons-codec + ${commons-codec.version} + commons-dbcp commons-dbcp ${commons-dbcp.version} + commons-digester commons-digester @@ -2149,6 +2162,7 @@ wsdl4j ${wsdl4j.version} + diff --git a/spring-boot-docs/pom.xml b/spring-boot-docs/pom.xml index 3500b5bd58..b36c2a3d56 100644 --- a/spring-boot-docs/pom.xml +++ b/spring-boot-docs/pom.xml @@ -488,6 +488,11 @@ spring-data-mongodb true + + org.springframework.data + spring-data-neo4j + true + org.springframework.data spring-data-redis diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 7c88a8e2e1..0568ede96b 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3009,6 +3009,179 @@ Mongo instance's configuration and logging routing. +[[boot-features-neo4j]] +=== Neo4j +http://neo4j.com/[Neo4j] is an open-source NoSQL graph database that uses a +rich data model of nodes related by first class relationships which is better +suited for connected big data than traditional rdbms approaches. +Spring Boot offers several conveniences for working with Neo4j, including the +`spring-boot-starter-data-neo4j` '`Starter POM`'. + +[[boot-features-connecting-to-neo4j]] +==== Connecting to a Neo4j database +You can inject an auto-configured `org.neo4j.ogm.session.Neo4jSession` to +access Neo4j databases. + +In your `application properties`, you can supply any domain packages to be scanned by the OGM at startup +as well as the lifetime of the OGM session that will be established for web clients. + +By default your application will be configured to use an in-process embedded instance of Neo4j that will not persist any data when your application shuts down. You can also connect to a remote Neo4j server, or to an embedded instance that persists data between restarts of your application. + +The following sections show how you can configure your application for each of these scenarios. + +[[boot-features-neo4j-embedded]] +==== Connecting to an embedded database +[source,properties,indent=0] +---- + # embedded driver (optional: default is embedded driver) + spring.data.neo4j.driver=org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver + + # database path (optional: default is in-memory) + spring.data.neo4j.URI=file://var/tmp/graph.db + + # declare the domain packages for the OGM to scan at startup + # note: if you don't need to do any object mapping, you can omit this property + spring.data.neo4.domain.packages=my.app.domain.core, my.app.domain.external, ... + + # OGM session lifetime for web clients + # options: session (httpSession), request (httpRequest) + # default: session + spring.data.neo4j.session.lifetime=session +---- + +[[boot-features-neo4j-remote]] +==== Connecting to a remote database +[source,properties,indent=0] +---- + # http driver + spring.data.neo4j.driver=org.neo4j.ogm.drivers.http.driver.HttpDriver + + # database uri + spring.data.neo4j.URI=http://user:password@localhost:7474 + + # declare the domain packages for the OGM to scan at startup + # note: if you don't need to do any object mapping, you can omit this property + spring.data.neo4.domain.packages=my.app.domain.core, my.app.domain.external, ... + + # OGM session lifetime for web clients + # options: session (httpSession), request (httpRequest) + # default: session + spring.data.neo4j.session.lifetime=session +---- + +[[boot-features-spring-data-neo4j-application]] +==== Application +[source,java,indent=0]] +---- + @SpringBootApplication + @Import(Neo4jAutoConfiguration.class) + public class Application { + + public static void main(String[] args) { + new SpringApplication(Application.class).run(args); + } + + } +---- + +[[boot-features-neo4j-ogm-session]] +==== Neo4jSession +[source,java,indent=0] +---- + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Component; + + import org.neo4j.ogm.session.Neo4jSession; + + @Component + public class MyBean { + + private final Session session; + + @Autowired + public MyBean(Session session) { + this.session = session; + } + + // ... + public void example() { + Iterable result = session.query("MATCH (c:Customer) RETURN count(*)",null); + // ... + } + + } +---- + +[[boot-features-spring-data-neo4j-template]] +==== Neo4jTemplate +Spring Data Neo4j provides a +{spring-data-neo4j-javadoc}/core/Neo4jTemplate.html[`Neo4jTemplate`] class that is very +similar in its design to Spring's `JdbcTemplate`. As with `JdbcTemplate` Spring Boot +auto-configures a bean for you to simply inject: + +[source,java,indent=0] +---- + import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.stereotype.Component; + + import org.springframework.data.neo4j.template.Neo4jTemplate; + + @Component + public class MyBean { + + private final Neo4jTemplate neo4jTemplate; + + @Autowired + public MyBean(Neo4jTemplate neo4jTemplate) { + this.neo4jTemplate = neo4jTemplate; + } + + // ... + + } +---- + +See the `Neo4jOperations` Javadoc for complete details. + +[[boot-features-spring-data-neo4j-repositories]] +==== Spring Data Neo4j repositories +Spring Data includes repository support for Neo4j. + +In fact, both Spring Data JPA and Spring Data Neo4j share the same common +infrastructure; so you could take the JPA example from earlier and, assuming that `City` +is now a Neo4j OGM `@NodeEntity` rather than a JPA `@Entity`, it will work in the same way. + +To enable repository support (and optionally support for `@Transactional`), add the following two annotations to +your Spring configuration: + +[source,java,indent=0] +---- + @EnableNeo4jRepositories(basePackages = "com.example.myapp.repository") + @EnableTransactionManagement +---- + +==== Repository example +[source,java,indent=0] +---- + package com.example.myapp.domain; + + import org.springframework.data.domain.*; + import org.springframework.data.repository.*; + + public interface CityRepository extends GraphRepository { + + Page findAll(Pageable pageable); + + City findByNameAndCountry(String name, String country); + + } +---- + +TIP: For complete details of Spring Data Neo4j, including its rich object mapping +technologies, refer to their http://projects.spring.io/spring-data-neo4j/[reference +documentation]. + + [[boot-features-gemfire]] === Gemfire https://github.com/spring-projects/spring-data-gemfire[Spring Data Gemfire] provides diff --git a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 01db16519e..d040454f67 100644 --- a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -420,6 +420,9 @@ and Hibernate. |`spring-boot-starter-data-redis` |Support for the REDIS key-value data store, including `spring-data-redis`. +|`spring-boot-starter-data-neo4j` +|Support for the Neo4j Graph Database, including `spring-data-neo4j`. + |`spring-boot-starter-data-rest` |Support for exposing Spring Data repositories over REST via `spring-data-rest-webmvc`. diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml b/spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml new file mode 100644 index 0000000000..e87954e532 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.4.0.BUILD-SNAPSHOT + + spring-boot-sample-data-neo4j + Spring Boot Data Neo4j Sample + Spring Boot Data Neo4j Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-neo4j + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.neo4j + neo4j-ogm-embedded-driver + 2.0.0-M04 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/Customer.java b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/Customer.java new file mode 100644 index 0000000000..4267e34b46 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/Customer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2013 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 sample.data.neo4j; + +import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.annotation.GraphId; + +@NodeEntity +public class Customer { + + @GraphId + private Long id; + + private String firstName; + private String lastName; + + public Customer() { + } + + public Customer(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + @Override + public String toString() { + return String.format("Customer[id=%s, firstName='%s', lastName='%s']", id, + firstName, lastName); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/CustomerRepository.java b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/CustomerRepository.java new file mode 100644 index 0000000000..6c809b901f --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/CustomerRepository.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2013 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 sample.data.neo4j; + +import java.util.List; + +import org.springframework.data.neo4j.repository.GraphRepository; + +public interface CustomerRepository extends GraphRepository { + + public Customer findByFirstName(String firstName); + + public List findByLastName(String lastName); + +} diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java new file mode 100644 index 0000000000..20b2c8ef46 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2015 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 sample.data.neo4j; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import org.springframework.context.annotation.Import; + +@SpringBootApplication +public class SampleNeo4jApplication implements CommandLineRunner { + + @Autowired + private CustomerRepository repository; + + @Override + public void run(String... args) throws Exception { + this.repository.deleteAll(); + + // save a couple of customers + this.repository.save(new Customer("Alice", "Smith")); + this.repository.save(new Customer("Bob", "Smith")); + + // fetch all customers + System.out.println("Customers found with findAll():"); + System.out.println("-------------------------------"); + for (Customer customer : this.repository.findAll()) { + System.out.println(customer); + } + System.out.println(); + + // fetch an individual customer + System.out.println("Customer found with findByFirstName('Alice'):"); + System.out.println("--------------------------------"); + System.out.println(this.repository.findByFirstName("Alice")); + + System.out.println("Customers found with findByLastName('Smith'):"); + System.out.println("--------------------------------"); + for (Customer customer : this.repository.findByLastName("Smith")) { + System.out.println(customer); + } + } + + public static void main(String[] args) throws Exception { + SpringApplication.run(SampleNeo4jApplication.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/resources/application.properties new file mode 100644 index 0000000000..b56c18a6b3 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.data.neo4j.driver=org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver +spring.data.neo4j.domain.packages=sample.data.neo4j +spring.data.neo4j.session.lifetime=prototype \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java b/spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java new file mode 100644 index 0000000000..a7d445800c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2015 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 sample.data.neo4j; + +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.OutputCapture; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link SampleNeo4jApplication}. + * + * @author Dave Syer + * @author Andy Wilkinson + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(SampleNeo4jApplication.class) +@IntegrationTest +public class SampleNeo4jApplicationTests { + + @ClassRule + public static OutputCapture outputCapture = new OutputCapture(); + + @Test + public void testDefaultSettings() throws Exception { + String output = SampleNeo4jApplicationTests.outputCapture.toString(); + assertTrue("Wrong output: " + output, + output.contains("firstName='Alice', lastName='Smith'")); + } + +} diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index e5667e3c3a..57c9258cdc 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -33,6 +33,7 @@ spring-boot-starter-data-gemfire spring-boot-starter-data-jpa spring-boot-starter-data-mongodb + spring-boot-starter-data-neo4j spring-boot-starter-data-redis spring-boot-starter-data-rest spring-boot-starter-data-solr @@ -69,6 +70,7 @@ spring-boot-starter-websocket spring-boot-starter-ws + diff --git a/spring-boot-starters/spring-boot-starter-data-neo4j/pom.xml b/spring-boot-starters/spring-boot-starter-data-neo4j/pom.xml new file mode 100644 index 0000000000..3a753b0c3e --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-data-neo4j/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starters + 1.4.0.BUILD-SNAPSHOT + + spring-boot-starter-data-neo4j + Spring Boot Data Neo4j Starter + Spring Boot Data Neo4j Starter + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.data + spring-data-neo4j + + + diff --git a/spring-boot-starters/spring-boot-starter-data-neo4j/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-data-neo4j/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000000..38cc6363c5 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-data-neo4j/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: spring-data-neo4j \ No newline at end of file From fd437797b6c44a6dd77bd74d3729838ab7769b35 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 22 Mar 2016 16:03:14 +0100 Subject: [PATCH 2/2] Polish contribution This commit polihes the original Neo4j contribution in several areas. Rather than providing the packages to scan, this commit rearranges the `EntityScan` and `EntityScanRegistrar` so that the logic can be shared for other components. If no package is provided, scanning now defaults to the "auto-configured" package(s) and a `@NodeEntityScan` annotation allows to override that. The configuration has also been updated to detect the driver based on the `uri` property. If the embedded driver is available we use that by default. If it is not available, we're trying to connect to a Neo4j server running on localhost. It is possible to disable the embedded mode or set the `uri` parameter explicitly to deviate from these defaults. The sample no longer relies on the embedded driver for licensing reason: rather it expects an instance running on localhost (like other data-related samples) and gracefully ignore any connection error. A README has been added in the sample to further explain the available options; Closes gh-5458 --- .../SpringApplicationHierarchyTests.java | 3 + .../MongoRepositoriesAutoConfiguration.java | 3 +- .../data/neo4j/Neo4jAutoConfiguration.java | 83 -------- .../data/neo4j/Neo4jProperties.java | 105 ---------- .../Neo4jRepositoriesAutoConfiguration.java | 10 +- ...o4jRepositoriesAutoConfigureRegistrar.java | 4 +- .../neo4j/Neo4jAutoConfiguration.java | 142 ++++++++++++++ .../autoconfigure/neo4j/Neo4jProperties.java | 179 ++++++++++++++++++ .../autoconfigure/neo4j/package-info.java | 20 ++ ...itional-spring-configuration-metadata.json | 36 ++++ .../main/resources/META-INF/spring.factories | 2 +- .../data/alt/neo4j/CityNeo4jRepository.java | 2 +- ...o4jRepositoriesAutoConfigurationTests.java | 44 ++--- .../Neo4jDataAutoConfigurationTests.java | 39 +--- ...o4jRepositoriesAutoConfigurationTests.java | 29 +-- .../autoconfigure/data/neo4j/city/City.java | 3 +- .../data/neo4j/city/CityRepository.java | 9 +- .../data/neo4j/country/Country.java | 3 +- .../data/neo4j/country/CountryRepository.java | 2 +- .../neo4j/Neo4jAutoConfigurationTests.java | 94 +++++++-- .../neo4j/Neo4jPropertiesTests.java | 170 +++++++++++++---- spring-boot-dependencies/pom.xml | 24 ++- .../appendix-application-properties.adoc | 9 + .../main/asciidoc/spring-boot-features.adoc | 159 +++++----------- spring-boot-samples/pom.xml | 1 + .../spring-boot-sample-data-neo4j/README.adoc | 23 +++ .../spring-boot-sample-data-neo4j/pom.xml | 14 -- .../data/neo4j/SampleNeo4jApplication.java | 2 - .../src/main/resources/application.properties | 3 - .../neo4j/SampleNeo4jApplicationTests.java | 39 ++-- spring-boot/pom.xml | 5 + .../AbstractEntityScanBeanPostProcessor.java | 59 ++++++ .../scan/AbstractEntityScanRegistrar.java} | 118 +++++------- .../boot/context/scan/package-info.java | 20 ++ .../boot/neo4j/NodeEntityScan.java | 83 ++++++++ .../boot/neo4j/NodeEntityScanRegistrar.java | 73 +++++++ .../boot/neo4j/SessionFactoryProvider.java | 56 ++++++ .../boot/neo4j/package-info.java | 20 ++ .../boot/orm/jpa/EntityScan.java | 2 +- .../boot/orm/jpa/JpaEntityScanRegistrar.java | 75 ++++++++ .../boot/context/scan/TestEntityScan.java | 44 +++++ .../context/scan/TestEntityScanRegistrar.java | 65 +++++++ .../context/scan/TestEntityScanTests.java | 158 ++++++++++++++++ .../boot/neo4j/NodeEntityScanTests.java | 107 +++++++++++ .../boot/orm/jpa/EntityScanTests.java | 93 +-------- 45 files changed, 1607 insertions(+), 627 deletions(-) delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jAutoConfiguration.java delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jProperties.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/package-info.java rename spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/{ => data}/neo4j/Neo4jDataAutoConfigurationTests.java (60%) create mode 100644 spring-boot-samples/spring-boot-sample-data-neo4j/README.adoc create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/scan/AbstractEntityScanBeanPostProcessor.java rename spring-boot/src/main/java/org/springframework/boot/{orm/jpa/EntityScanRegistrar.java => context/scan/AbstractEntityScanRegistrar.java} (57%) create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/scan/package-info.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/neo4j/NodeEntityScan.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/neo4j/NodeEntityScanRegistrar.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/neo4j/SessionFactoryProvider.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/neo4j/package-info.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaEntityScanRegistrar.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScan.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScanRegistrar.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScanTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/neo4j/NodeEntityScanTests.java diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java index 2eafa5a520..81f8bb7377 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SpringApplicationHierarchyTests.java @@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDa import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.util.ApplicationContextTestUtils; import org.springframework.context.ConfigurableApplicationContext; @@ -63,6 +64,7 @@ public class SpringApplicationHierarchyTests { @EnableAutoConfiguration(exclude = { ElasticsearchDataAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, + Neo4jAutoConfiguration.class, RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class }, excludeName = { "org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration" }) @@ -75,6 +77,7 @@ public class SpringApplicationHierarchyTests { ElasticsearchDataAutoConfiguration.class, ElasticsearchRepositoriesAutoConfiguration.class, CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, + Neo4jAutoConfiguration.class, RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class }, excludeName = { "org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration" }) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java index 7a8630d260..1afa03ff98 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/mongo/MongoRepositoriesAutoConfiguration.java @@ -53,7 +53,8 @@ import org.springframework.data.mongodb.repository.support.MongoRepositoryFactor */ @Configuration @ConditionalOnClass({ Mongo.class, MongoRepository.class }) -@ConditionalOnMissingBean({ MongoRepositoryFactoryBean.class, MongoRepositoryConfigurationExtension.class }) +@ConditionalOnMissingBean({ MongoRepositoryFactoryBean.class, + MongoRepositoryConfigurationExtension.class }) @ConditionalOnProperty(prefix = "spring.data.mongodb.repositories", name = "enabled", havingValue = "true", matchIfMissing = true) @Import(MongoRepositoriesAutoConfigureRegistrar.class) @AutoConfigureAfter(MongoDataAutoConfiguration.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jAutoConfiguration.java deleted file mode 100644 index b5b9b69f1b..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jAutoConfiguration.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2012-2015 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.boot.autoconfigure.data.neo4j; - -import org.neo4j.ogm.session.Neo4jSession; -import org.neo4j.ogm.session.Session; -import org.neo4j.ogm.session.SessionFactory; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; - -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Scope; -import org.springframework.context.annotation.ScopedProxyMode; - -import org.springframework.data.neo4j.config.Neo4jConfiguration; -import org.springframework.data.neo4j.template.Neo4jOperations; -import org.springframework.data.neo4j.template.Neo4jTemplate; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Neo4j support. - *

- * Registers a {@link Neo4jTemplate} bean if no other bean of - * the same type is configured. - * - * @author Michael Hunger - * @author Josh Long - * @author Vince Bickers - * @since 1.3.0 - */ -@Configuration -@EnableConfigurationProperties(Neo4jProperties.class) -@ConditionalOnMissingBean(type = "org.springframework.data.neo4j.template.Neo4jOperations") -@ConditionalOnClass({ Neo4jSession.class, Neo4jOperations.class }) -public class Neo4jAutoConfiguration extends Neo4jConfiguration { - - @Autowired - private Neo4jProperties properties; - - @Value("${spring.data.neo4j.domain.packages:null}") - private String[] domainPackages; - - @Bean - @ConditionalOnMissingBean(org.neo4j.ogm.config.Configuration.class) - public org.neo4j.ogm.config.Configuration configuration() { - return this.properties.configure(); - } - - @Override - @ConditionalOnMissingBean(SessionFactory.class) - public SessionFactory getSessionFactory() { - return new SessionFactory(configuration(), this.domainPackages); - } - - @Bean - @ConditionalOnMissingBean(Session.class) - @Scope(value = "${spring.data.neo4j.session.lifetime:session}", proxyMode = ScopedProxyMode.TARGET_CLASS) - public Session getSession() throws Exception { - return getSessionFactory().openSession(); - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java deleted file mode 100644 index 73b3974883..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2012-2015 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.boot.autoconfigure.data.neo4j; - -import org.neo4j.ogm.config.Configuration; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for Neo4j. - * - * @author Dave Syer - * @author Phillip Webb - * @author Josh Long - * @author Andy Wilkinson - * @author Eddú Meléndez - * @author Michael Hunger - * @author Vince Bickers - */ -@ConfigurationProperties(prefix = "spring.data.neo4j") -public class Neo4jProperties { - - // if you don't set this up somewhere, this is what we'll use by default - private String driver = "org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver"; - private String compiler; - private String URI; - private String username; - private String password; - - public String getDriver() { - return this.driver; - } - - public void setDriver(String driver) { - this.driver = driver; - } - - public String getCompiler() { - return this.compiler; - } - - public void setCompiler(String compiler) { - this.compiler = compiler; - } - - public String getURI() { - return this.URI; - } - - public void setURI(String URI) { - this.URI = URI; - } - - public String getUsername() { - return this.username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.password = password; - } - - public Configuration configure() { - Configuration configuration = new Configuration(); - - if (this.driver != null) { - configuration.driverConfiguration().setDriverClassName(this.driver); - } - - if (this.URI != null) { - configuration.driverConfiguration().setURI(this.URI); - } - - if (this.username != null && this.password != null) { - configuration.driverConfiguration().setCredentials(this.username, this.password); - } - - if (this.compiler != null) { - configuration.compilerConfiguration().setCompilerClassName(this.compiler); - } - - return configuration; - } -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java index 2cc223d8c7..8eb7e4751b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -20,16 +20,13 @@ import org.neo4j.ogm.session.Neo4jSession; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; - import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; - +import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; - import org.springframework.data.neo4j.repository.GraphRepository; - import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.data.neo4j.repository.config.Neo4jRepositoryConfigurationExtension; import org.springframework.data.neo4j.repository.support.GraphRepositoryFactoryBean; @@ -53,13 +50,14 @@ import org.springframework.data.neo4j.repository.support.GraphRepositoryFactoryB * @author Dave Syer * @author Oliver Gierke * @author Josh Long + * @since 1.4.0 * @see EnableNeo4jRepositories */ @Configuration @ConditionalOnClass({ Neo4jSession.class, GraphRepository.class }) @ConditionalOnMissingBean({ GraphRepositoryFactoryBean.class, Neo4jRepositoryConfigurationExtension.class }) @ConditionalOnProperty(prefix = "spring.data.neo4j.repositories", name = "enabled", havingValue = "true", matchIfMissing = true) -@Import({Neo4jRepositoriesAutoConfigureRegistrar.class}) +@Import(Neo4jRepositoriesAutoConfigureRegistrar.class) @AutoConfigureAfter(Neo4jAutoConfiguration.class) public class Neo4jRepositoriesAutoConfiguration { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java index 1651010535..5793b10072 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -28,7 +28,7 @@ import org.springframework.data.repository.config.RepositoryConfigurationExtensi * {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data Neo4j * Repositories. * - * @author Dave Syer + * @author Michael Hunger */ class Neo4jRepositoriesAutoConfigureRegistrar extends AbstractRepositoryConfigurationSourceSupport { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java new file mode 100644 index 0000000000..e8d2a814f3 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfiguration.java @@ -0,0 +1,142 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.neo4j; + +import java.util.List; + +import org.neo4j.ogm.session.Neo4jSession; +import org.neo4j.ogm.session.Session; +import org.neo4j.ogm.session.SessionFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.neo4j.SessionFactoryProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.data.neo4j.config.Neo4jConfiguration; +import org.springframework.data.neo4j.template.Neo4jOperations; +import org.springframework.data.neo4j.template.Neo4jTemplate; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Neo4j support. + *

+ * Registers a {@link Neo4jTemplate} bean if no other bean of + * the same type is configured. + * + * @author Michael Hunger + * @author Josh Long + * @author Vince Bickers + * @author Stephane Nicoll + * @since 1.4.0 + */ +@Configuration +@ConditionalOnClass({Neo4jSession.class, Neo4jOperations.class}) +@ConditionalOnMissingBean(Neo4jOperations.class) +@EnableConfigurationProperties(Neo4jProperties.class) +public class Neo4jAutoConfiguration { + + @Configuration + @Import(SessionFactoryProviderConfiguration.class) + public static class SpringBootNeo4jConfiguration extends Neo4jConfiguration { + + private final ObjectProvider sessionFactoryProvider; + + public SpringBootNeo4jConfiguration(ObjectProvider sessionFactoryProvider) { + this.sessionFactoryProvider = sessionFactoryProvider; + } + + @Override + public SessionFactory getSessionFactory() { + SessionFactoryProvider provider = this.sessionFactoryProvider.getObject(); + return provider.getSessionFactory(); + } + + @Bean + @Scope(scopeName = "${spring.data.neo4j.session.scope:singleton}", + proxyMode = ScopedProxyMode.TARGET_CLASS) + @Override + public Session getSession() throws Exception { + return getSessionFactory().openSession(); + } + + } + + @Configuration + @Import(Neo4jConfigurationConfiguration.class) + static class SessionFactoryProviderConfiguration implements BeanFactoryAware { + + private final org.neo4j.ogm.config.Configuration configuration; + + private ConfigurableListableBeanFactory beanFactory; + + SessionFactoryProviderConfiguration(org.neo4j.ogm.config.Configuration configuration) { + this.configuration = configuration; + } + + @Bean + @ConditionalOnMissingBean + public SessionFactoryProvider sessionFactoryProvider() { + SessionFactoryProvider provider = new SessionFactoryProvider(); + provider.setConfiguration(this.configuration); + provider.setPackagesToScan(getPackagesToScan()); + return provider; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + + protected String[] getPackagesToScan() { + if (AutoConfigurationPackages.has(this.beanFactory)) { + List basePackages = AutoConfigurationPackages.get(this.beanFactory); + return basePackages.toArray(new String[basePackages.size()]); + } + return new String[0]; + } + + } + + @Configuration + static class Neo4jConfigurationConfiguration { + + private final Neo4jProperties properties; + + Neo4jConfigurationConfiguration(Neo4jProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean + public org.neo4j.ogm.config.Configuration configuration() { + return this.properties.createConfiguration(); + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jProperties.java new file mode 100644 index 0000000000..1331310ac4 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/Neo4jProperties.java @@ -0,0 +1,179 @@ +/* + * Copyright 2012-2016 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.boot.autoconfigure.neo4j; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.neo4j.ogm.config.Configuration; + +import org.springframework.beans.BeansException; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.ClassUtils; + +/** + * Configuration properties for Neo4j. + * + * @author Stephane Nicoll + * @author Michael Hunger + * @author Vince Bickers + * @since 1.4.0 + */ +@ConfigurationProperties(prefix = "spring.data.neo4j") +public class Neo4jProperties implements ApplicationContextAware { + + static final String EMBEDDED_DRIVER = "org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver"; + + static final String HTTP_DRIVER = "org.neo4j.ogm.drivers.http.driver.HttpDriver"; + + static final String DEFAULT_HTTP_URI = "http://localhost:7474"; + + /** + * URI used by the driver. Auto-detected by default. + */ + private String uri; + + /** + * Login user of the server. + */ + private String username; + + /** + * Login password of the server. + */ + private String password; + + /** + * Compiler to use. + */ + private String compiler; + + private final Embedded embedded = new Embedded(); + + private ClassLoader classLoader = Neo4jProperties.class.getClassLoader(); + + public String getUri() { + return this.uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getCompiler() { + return this.compiler; + } + + public void setCompiler(String compiler) { + this.compiler = compiler; + } + + public Embedded getEmbedded() { + return this.embedded; + } + + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + this.classLoader = ctx.getClassLoader(); + } + + /** + * Create a {@link Configuration} based on the state of this instance. + * @return a configuration + */ + public Configuration createConfiguration() { + Configuration configuration = new Configuration(); + if (this.uri == null) { + if (getEmbedded().isEnabled() + && ClassUtils.isPresent(EMBEDDED_DRIVER, this.classLoader)) { + configuration.driverConfiguration().setDriverClassName(EMBEDDED_DRIVER); + } + else { + configuration.driverConfiguration().setDriverClassName(HTTP_DRIVER); + configuration.driverConfiguration().setURI(DEFAULT_HTTP_URI); + } + } + else { + configuration.driverConfiguration().setDriverClassName(deduceDriverFromUri()); + configuration.driverConfiguration().setURI(this.uri); + } + + if (this.username != null && this.password != null) { + configuration.driverConfiguration().setCredentials(this.username, this.password); + } + if (this.compiler != null) { + configuration.compilerConfiguration().setCompilerClassName(this.compiler); + } + return configuration; + } + + private String deduceDriverFromUri() { + try { + URI uri = new URI(this.uri); + String scheme = uri.getScheme(); + if (scheme == null || scheme.equals("file")) { + return EMBEDDED_DRIVER; + } + else if ("http".equals(scheme)) { + return HTTP_DRIVER; + } + else { + throw new IllegalArgumentException("Could not deduce driver to use based on URI '" + uri + "'"); + } + } + catch (URISyntaxException ex) { + throw new IllegalArgumentException("Invalid URI for spring.data.neo4j.uri '" + this.uri + "'", ex); + } + } + + + public static class Embedded { + + /** + * Enable embedded mode if the embedded driver is available. + */ + private boolean enabled = true; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/package-info.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/package-info.java new file mode 100644 index 0000000000..e53e6c5214 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/neo4j/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2016 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. + */ + +/** + * Auto-configuration for Neo4j. + */ +package org.springframework.boot.autoconfigure.neo4j; diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 6b720f28ab..e16833216b 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -96,6 +96,12 @@ "description": "Enable Neo4j repositories.", "defaultValue": true }, + { + "name": "spring.data.neo4j.session.scope", + "type": "java.lang.String", + "description": "Scope (lifetime) of the session.", + "defaultValue": "singleton" + }, { "name": "spring.data.redis.repositories.enabled", "type": "java.lang.Boolean", @@ -336,6 +342,36 @@ } ] }, + { + "name": "spring.data.neo4j.compiler", + "providers": [ + { + "name": "class-reference", + "parameters": { + "target": "org.neo4j.ogm.compiler.Compiler" + } + } + ] + }, + { + "name": "spring.data.neo4j.session.scope", + "values": [ + { + "value": "singleton" + }, + { + "value": "session" + }, + { + "value": "request" + } + ], + "providers": [ + { + "name": "any" + } + ] + }, { "name": "spring.datasource.driver-class-name", "providers": [ diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 90463d5960..5afd06d3ff 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -31,7 +31,6 @@ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositor org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ -org.springframework.boot.autoconfigure.data.neo4j.Neo4jAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ @@ -70,6 +69,7 @@ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ +org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java index 722bc7a823..30fc7667ca 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java index 6741bb0cb5..7f774d1b4f 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -19,8 +19,6 @@ package org.springframework.boot.autoconfigure.data.neo4j; import java.util.ArrayList; import java.util.List; -import org.assertj.core.api.Assertions; - import org.junit.After; import org.junit.Ignore; import org.junit.Test; @@ -32,35 +30,34 @@ import org.springframework.boot.autoconfigure.data.jpa.city.CityRepository; import org.springframework.boot.autoconfigure.data.neo4j.country.Country; import org.springframework.boot.autoconfigure.data.neo4j.country.CountryRepository; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfigurationTests; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; - import org.springframework.boot.orm.jpa.EntityScan; - -import org.springframework.boot.test.EnvironmentTestUtils; - +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; - import org.springframework.core.type.AnnotationMetadata; - import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import static org.assertj.core.api.Assertions.assertThat; + /** - * Tests for {@link org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration}. + * Tests for {@link Neo4jRepositoriesAutoConfiguration}. * * @author Dave Syer * @author Oliver Gierke * @author Michael Hunger * @author Vince Bickers + * @author Stephane Nicoll */ public class MixedNeo4jRepositoriesAutoConfigurationTests { - private AnnotationConfigApplicationContext context; + private AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(); @After public void close() { @@ -69,51 +66,46 @@ public class MixedNeo4jRepositoriesAutoConfigurationTests { @Test public void testDefaultRepositoryConfiguration() throws Exception { - this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); this.context.register(TestConfiguration.class, BaseConfiguration.class); this.context.refresh(); - Assertions.assertThat(this.context.getBean(CountryRepository.class)).isNotNull(); + assertThat(this.context.getBean(CountryRepository.class)).isNotNull(); } @Test public void testMixedRepositoryConfiguration() throws Exception { - this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); this.context.register(MixedConfiguration.class, BaseConfiguration.class); this.context.refresh(); - Assertions.assertThat(this.context.getBean(CountryRepository.class)).isNotNull(); - Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + assertThat(this.context.getBean(CountryRepository.class)).isNotNull(); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); } @Test public void testJpaRepositoryConfigurationWithNeo4jTemplate() throws Exception { - this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); this.context.register(JpaConfiguration.class, BaseConfiguration.class); this.context.refresh(); - Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); } @Test @Ignore public void testJpaRepositoryConfigurationWithNeo4jOverlap() throws Exception { - this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); this.context.register(OverlapConfiguration.class, BaseConfiguration.class); this.context.refresh(); - Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); } @Test public void testJpaRepositoryConfigurationWithNeo4jOverlapDisabled() throws Exception { - this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false", "spring.data.neo4j.repositories.enabled:false"); this.context.register(OverlapConfiguration.class, BaseConfiguration.class); this.context.refresh(); - Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); } @Configuration @@ -141,7 +133,7 @@ public class MixedNeo4jRepositoriesAutoConfigurationTests { } - // In this one the Jpa repositories and the autoconfiguration packages overlap, so + // In this one the Jpa repositories and the auto-configuration packages overlap, so // Neo4j will try and configure the same repositories @Configuration @TestAutoConfigurationPackage(CityRepository.class) @@ -161,11 +153,11 @@ public class MixedNeo4jRepositoriesAutoConfigurationTests { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { List names = new ArrayList(); - for (Class type : new Class[] { DataSourceAutoConfiguration.class, + for (Class type : new Class[] {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class, Neo4jAutoConfiguration.class, - Neo4jRepositoriesAutoConfiguration.class }) { + Neo4jRepositoriesAutoConfiguration.class}) { names.add(type.getName()); } return names.toArray(new String[names.size()]); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jDataAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java similarity index 60% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jDataAutoConfigurationTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java index 8227b24f7c..7b8fa27709 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jDataAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -14,27 +14,19 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.neo4j; +package org.springframework.boot.autoconfigure.data.neo4j; import org.assertj.core.api.Assertions; - import org.junit.After; import org.junit.Rule; import org.junit.Test; - import org.junit.rules.ExpectedException; -import org.neo4j.ogm.session.SessionFactory; - import org.springframework.boot.autoconfigure.AutoConfigurationPackages; -import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.data.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.data.neo4j.city.City; - +import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; - import org.springframework.data.neo4j.mapping.Neo4jMappingContext; -import org.springframework.data.neo4j.template.Neo4jOperations; /** * Tests for {@link Neo4jAutoConfiguration}. @@ -48,34 +40,15 @@ public class Neo4jDataAutoConfigurationTests { @Rule public final ExpectedException thrown = ExpectedException.none(); - private AnnotationConfigApplicationContext context; + private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @After public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void templateExists() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(PropertyPlaceholderAutoConfiguration.class, Neo4jAutoConfiguration.class); - this.context.refresh(); - Assertions.assertThat(this.context.getBeanNamesForType(Neo4jOperations.class).length).isEqualTo(1); - } - - @Test - public void sessionFactoryExists() { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(PropertyPlaceholderAutoConfiguration.class, Neo4jAutoConfiguration.class); - this.context.refresh(); - Assertions.assertThat(this.context.getBeanNamesForType(SessionFactory.class).length).isEqualTo(1); + this.context.close(); } @Test public void usesAutoConfigurationPackageToPickUpDomainTypes() { - this.context = new AnnotationConfigApplicationContext(); String cityPackage = City.class.getPackage().getName(); AutoConfigurationPackages.register(this.context, cityPackage); this.context.register(Neo4jAutoConfiguration.class); @@ -85,7 +58,7 @@ public class Neo4jDataAutoConfigurationTests { } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) private static void assertDomainTypesDiscovered(Neo4jMappingContext mappingContext, Class... types) { for (Class type : types) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java index 5afdb1d86e..a1a514f831 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -16,35 +16,34 @@ package org.springframework.boot.autoconfigure.data.neo4j; -import org.assertj.core.api.Assertions; - import org.junit.After; import org.junit.Test; - import org.neo4j.ogm.session.SessionFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; - import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.data.alt.neo4j.CityNeo4jRepository; import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage; import org.springframework.boot.autoconfigure.data.neo4j.city.City; import org.springframework.boot.autoconfigure.data.neo4j.city.CityRepository; - +import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; - import org.springframework.data.neo4j.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; +import static org.assertj.core.api.Assertions.assertThat; + /** - * Tests for {@link org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration}. + * Tests for {@link Neo4jRepositoriesAutoConfiguration}. * * @author Dave Syer * @author Oliver Gierke * @author Michael Hunger * @author Vince Bickers + * @author Stephane Nicoll */ public class Neo4jRepositoriesAutoConfigurationTests { @@ -57,27 +56,26 @@ public class Neo4jRepositoriesAutoConfigurationTests { @Test public void testDefaultRepositoryConfiguration() throws Exception { - prepareApplicationContext(TestConfiguration.class); - Assertions.assertThat(this.context.getBean(CityRepository.class)).isNotNull(); - + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); Neo4jMappingContext mappingContext = this.context.getBean(Neo4jMappingContext.class); - Assertions.assertThat(mappingContext.getPersistentEntity(City.class)).isNotNull(); + assertThat(mappingContext.getPersistentEntity(City.class)).isNotNull(); } @Test public void testNoRepositoryConfiguration() throws Exception { prepareApplicationContext(EmptyConfiguration.class); - Assertions.assertThat(this.context.getBean(SessionFactory.class)).isNotNull(); + + assertThat(this.context.getBean(SessionFactory.class)).isNotNull(); } @Test public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() { prepareApplicationContext(CustomizedConfiguration.class); - Assertions.assertThat(this.context.getBean(CityNeo4jRepository.class)).isNotNull(); + assertThat(this.context.getBean(CityNeo4jRepository.class)).isNotNull(); } @Test(expected = NoSuchBeanDefinitionException.class) @@ -89,6 +87,8 @@ public class Neo4jRepositoriesAutoConfigurationTests { private void prepareApplicationContext(Class... configurationClasses) { this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.data.neo4j.uri=http://localhost:9797"); this.context.register(configurationClasses); this.context.register(Neo4jAutoConfiguration.class, Neo4jRepositoriesAutoConfiguration.class, @@ -122,4 +122,5 @@ public class Neo4jRepositoriesAutoConfigurationTests { protected static class SortOfInvalidCustomConfiguration { } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java index df7694538f..98e225c0c4 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2016 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. @@ -67,4 +67,5 @@ public class City implements Serializable { public String toString() { return getName() + "," + getState() + "," + getCountry(); } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java index a89e07909f..67b7f4b860 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2016 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. @@ -24,11 +24,4 @@ public interface CityRepository extends GraphRepository { Page findAll(Pageable pageable); -// TODO: cannot resolve queries like this at the moment. -// -// Page findByNameLikeAndCountryLikeAllIgnoringCase(String name, String country, -// Pageable pageable); -// -// City findByNameAndCountry(String name, String country); - } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java index 012ddd1e54..b9baccf1a4 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2016 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. @@ -46,4 +46,5 @@ public class Country implements Serializable { public String toString() { return getName(); } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java index 1814c642da..a3180af6d8 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java index d4c10bee39..a00bbf59b9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -16,21 +16,27 @@ package org.springframework.boot.autoconfigure.neo4j; -import org.assertj.core.api.Assertions; - import org.junit.After; import org.junit.Test; - -import org.neo4j.ogm.config.Configuration; +import org.neo4j.ogm.drivers.http.driver.HttpDriver; +import org.neo4j.ogm.session.Session; +import org.neo4j.ogm.session.SessionFactory; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; -import org.springframework.boot.autoconfigure.data.neo4j.Neo4jAutoConfiguration; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.neo4j.template.Neo4jOperations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** - * Tests for {@link Neo4jAutoConfiguration}. + * Tests for {@link Neo4jAutoConfiguration}. Tests can't use the embedded driver + * as we use lucene 4 and Neo4j still requires 3. * - * @author Dave Syer + * @author Stephane Nicoll * @author Michael Hunger * @author Vince Bickers */ @@ -46,10 +52,74 @@ public class Neo4jAutoConfigurationTests { } @Test - public void configurationExists() { - this.context = new AnnotationConfigApplicationContext( - PropertyPlaceholderAutoConfiguration.class, Neo4jAutoConfiguration.class); - Assertions.assertThat(this.context.getBeanNamesForType(Configuration.class).length).isEqualTo(1); + public void defaultConfiguration() { + load(null, "spring.data.neo4j.uri=http://localhost:8989"); + assertThat(this.context.getBeansOfType(Neo4jOperations.class)).hasSize(1); + assertThat(this.context.getBeansOfType(org.neo4j.ogm.config.Configuration.class)).hasSize(1); + assertThat(this.context.getBeansOfType(SessionFactory.class)).hasSize(1); + assertThat(this.context.getBeanDefinition("scopedTarget.getSession").getScope()).isEqualTo("singleton"); + } + + @Test + public void customScope() { + load(null, "spring.data.neo4j.uri=http://localhost:8989", + "spring.data.neo4j.session.scope=prototype"); + assertThat(this.context.getBeanDefinition("scopedTarget.getSession").getScope()).isEqualTo("prototype"); + } + + @Test + public void customNeo4jOperations() { + load(CustomNeo4jOperations.class); + assertThat(this.context.getBean(Neo4jOperations.class)) + .isSameAs(this.context.getBean("myNeo4jOperations")); + assertThat(this.context.getBeansOfType(org.neo4j.ogm.config.Configuration.class)).hasSize(0); + assertThat(this.context.getBeansOfType(SessionFactory.class)).hasSize(0); + assertThat(this.context.getBeansOfType(Session.class)).hasSize(0); + } + + @Test + public void customConfiguration() { + load(CustomConfiguration.class); + assertThat(this.context.getBean(org.neo4j.ogm.config.Configuration.class)) + .isSameAs(this.context.getBean("myConfiguration")); + assertThat(this.context.getBeansOfType(Neo4jOperations.class)).hasSize(1); + assertThat(this.context.getBeansOfType(org.neo4j.ogm.config.Configuration.class)).hasSize(1); + assertThat(this.context.getBeansOfType(SessionFactory.class)).hasSize(1); + } + + public void load(Class config, String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(ctx, environment); + if (config != null) { + ctx.register(config); + } + ctx.register(PropertyPlaceholderAutoConfiguration.class, + Neo4jAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; + } + + @Configuration + static class CustomNeo4jOperations { + + @Bean + public Neo4jOperations myNeo4jOperations() { + return mock(Neo4jOperations.class); + } + + } + + + @Configuration + static class CustomConfiguration { + + @Bean + public org.neo4j.ogm.config.Configuration myConfiguration() { + org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration(); + configuration.driverConfiguration().setDriverClassName(HttpDriver.class.getName()); + return configuration; + } + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java index e619acbf95..be38fab978 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -16,69 +16,171 @@ package org.springframework.boot.autoconfigure.neo4j; -import org.assertj.core.api.Assertions; +import java.net.URL; +import java.net.URLClassLoader; +import com.hazelcast.util.Base64; +import org.junit.After; import org.junit.Test; +import org.neo4j.ogm.authentication.Credentials; +import org.neo4j.ogm.config.Configuration; +import org.neo4j.ogm.config.DriverConfiguration; -import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link Neo4jProperties}. * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Vince Bickers + * @author Stephane Nicoll */ - public class Neo4jPropertiesTests { - @Test - public void shouldHaveCorrectDefaultDriver() { - - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(Conf.class); - context.refresh(); + private AnnotationConfigApplicationContext context; - Neo4jProperties neo4jProperties = context.getBean(Neo4jProperties.class); + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } - Assertions.assertThat(neo4jProperties.getDriver()).isEqualTo("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver"); + @Test + public void defaultUseEmbeddedInMemoryIfAvailable() { + Neo4jProperties properties = load(true); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.EMBEDDED_DRIVER, null); } @Test - public void shouldConfigureFromDefaults() { + public void defaultUseHttpDriverIfEmbeddedDriverIsNotAvailable() { + Neo4jProperties properties = load(false); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.HTTP_DRIVER, + Neo4jProperties.DEFAULT_HTTP_URI); + } - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(Conf.class); - context.refresh(); + @Test + public void httpUriUseHttpServer() { + Neo4jProperties properties = load(true, + "spring.data.neo4j.uri=http://localhost:7474"); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.HTTP_DRIVER, + "http://localhost:7474"); + } - Neo4jProperties neo4jProperties = context.getBean(Neo4jProperties.class); + @Test + public void fileUriUseEmbeddedServer() { + Neo4jProperties properties = load(true, + "spring.data.neo4j.uri=file://var/tmp/graph.db"); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.EMBEDDED_DRIVER, + "file://var/tmp/graph.db"); + } - org.neo4j.ogm.config.Configuration configuration = neo4jProperties.configure(); - Assertions.assertThat(configuration.driverConfiguration().getDriverClassName()) - .isEqualTo("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver"); + @Test + public void credentialsAreSet() { + Neo4jProperties properties = load(true, + "spring.data.neo4j.uri=http://localhost:7474", + "spring.data.neo4j.username=user", + "spring.data.neo4j.password=secret"); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.HTTP_DRIVER, + "http://localhost:7474"); + assertCredentials(configuration, "user", "secret"); + } + @Test + public void credentialsAreSetFromUri() { + Neo4jProperties properties = load(true, + "spring.data.neo4j.uri=http://user:secret@my-server:7474"); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.HTTP_DRIVER, + "http://user:secret@my-server:7474"); + assertCredentials(configuration, "user", "secret"); } @Test - public void shouldBeCustomisable() { + public void embeddedModeDisabledUseHttpUri() { + Neo4jProperties properties = load(true, + "spring.data.neo4j.embedded.enabled=false"); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.HTTP_DRIVER, + Neo4jProperties.DEFAULT_HTTP_URI); + } - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - EnvironmentTestUtils.addEnvironment(context, "spring.data.neo4j.driver:CustomDriver"); - context.register(Conf.class); - context.refresh(); + @Test + public void embeddedModeWithRelativeLocation() { + Neo4jProperties properties = load(true, + "spring.data.neo4j.uri=target/neo4j/my.db"); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.EMBEDDED_DRIVER, + "target/neo4j/my.db"); + } - Neo4jProperties neo4jProperties = context.getBean(Neo4jProperties.class); + private static void assertDriver(Configuration actual, String driver, + String uri) { + assertThat(actual).isNotNull(); + DriverConfiguration driverConfig = actual.driverConfiguration(); + assertThat(driverConfig.getDriverClassName()).isEqualTo(driver); + assertThat(driverConfig.getURI()).isEqualTo(uri); + } - Assertions.assertThat(neo4jProperties.getDriver()).isEqualTo("CustomDriver"); + private static void assertCredentials(Configuration actual, String username, String password) { + Credentials credentials = actual.driverConfiguration().getCredentials(); + if (username == null & password == null) { + assertThat(credentials).isNull(); + } + else { + assertThat(credentials).isNotNull(); + Object content = credentials.credentials(); + assertThat(content).isInstanceOf(String.class); + String[] auth = new String(Base64.decode(((String) content) + .getBytes())).split(":"); + assertThat(auth[0]).isEqualTo(username); + assertThat(auth[1]).isEqualTo(password); + assertThat(auth).hasSize(2); + } + } + public Neo4jProperties load(final boolean embeddedAvailable, String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.setClassLoader( + new URLClassLoader(new URL[0], getClass().getClassLoader()) { + + @Override + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + if (name.equals(Neo4jProperties.EMBEDDED_DRIVER)) { + if (embeddedAvailable) { + return TestEmbeddedDriver.class; + } + else { + throw new ClassNotFoundException(); + } + } + return super.loadClass(name, resolve); + } + + }); + EnvironmentTestUtils.addEnvironment(ctx, environment); + ctx.register(TestConfiguration.class); + ctx.refresh(); + this.context = ctx; + return this.context.getBean(Neo4jProperties.class); } - @Configuration + @org.springframework.context.annotation.Configuration @EnableConfigurationProperties(Neo4jProperties.class) - static class Conf { + static class TestConfiguration { + + } + + private static class TestEmbeddedDriver { + } + } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 39156dd5f8..7c7a99d7a4 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -59,7 +59,6 @@ 1.4 2.1.1 2.1 - 3.4 1.6 2.4.2 2.2.3 @@ -124,6 +123,7 @@ 2.14.1 5.1.38 1.9.22 + 2.0.0-M04 9.4.1208.jre7 2.0.7.RELEASE 2.0.7.RELEASE @@ -785,7 +785,6 @@ commons-dbcp ${commons-dbcp.version} - commons-digester commons-digester @@ -1773,6 +1772,26 @@ mongo-java-driver ${mongodb.version} + + org.neo4j + neo4j-ogm-api + ${neo4j-ogm.version} + + + org.neo4j + neo4j-ogm-compiler + ${neo4j-ogm.version} + + + org.neo4j + neo4j-ogm-core + ${neo4j-ogm.version} + + + org.neo4j + neo4j-ogm-http-driver + ${neo4j-ogm.version} + org.postgresql postgresql @@ -2162,7 +2181,6 @@ wsdl4j ${wsdl4j.version} - diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 8f22283ea2..fa31ea2ee9 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -536,6 +536,15 @@ content into your application; rather pick only the properties that you need. # DATA REDIS spring.data.redis.repositories.enabled=true # Enable Redis repositories. + # NEO4J ({sc-spring-boot-autoconfigure}/neo4j/Neo4jProperties.{sc-ext}[Neo4jProperties]) + spring.data.neo4j.compiler= # Compiler to use. + spring.data.neo4j.embedded.enabled=true # Enable embedded mode if the embedded driver is available. + spring.data.neo4j.password= # Login password of the server. + spring.data.neo4j.repositories.enabled=true # Enable Neo4j repositories. + spring.data.neo4j.session.scope=singleton # Scope (lifetime) of the session. + spring.data.neo4j.uri= # URI used by the driver. Auto-detected by default. + spring.data.neo4j.username= # Login user of the server. + # DATA REST ({sc-spring-boot-autoconfigure}/data/rest/RepositoryRestProperties.{sc-ext}[RepositoryRestProperties]) spring.data.rest.base-path= # Base path to be used by Spring Data REST to expose repository resources. spring.data.rest.default-page-size= # Default size of pages. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 0568ede96b..9871bbba53 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2805,9 +2805,9 @@ http://projects.spring.io/spring-data-redis/[Redis], http://projects.spring.io/spring-data-gemfire/[Gemfire], http://projects.spring.io/spring-data-couchbase/[Couchbase] and http://projects.spring.io/spring-data-cassandra/[Cassandra]. -Spring Boot provides auto-configuration for Redis, MongoDB, Elasticsearch, Solr and -Cassandra; you can make use of the other projects, but you will need to configure them -yourself. Refer to the appropriate reference documentation at +Spring Boot provides auto-configuration for Redis, MongoDB, Neo4j, Elasticsearch, Solr +and Cassandra; you can make use of the other projects, but you will need to configure +them yourself. Refer to the appropriate reference documentation at http://projects.spring.io/spring-data[projects.spring.io/spring-data]. @@ -3011,137 +3011,77 @@ Mongo instance's configuration and logging routing. [[boot-features-neo4j]] === Neo4j -http://neo4j.com/[Neo4j] is an open-source NoSQL graph database that uses a -rich data model of nodes related by first class relationships which is better -suited for connected big data than traditional rdbms approaches. -Spring Boot offers several conveniences for working with Neo4j, including the -`spring-boot-starter-data-neo4j` '`Starter POM`'. +http://neo4j.com/[Neo4j] is an open-source NoSQL graph database that uses a rich data +model of nodes related by first class relationships which is better suited for connected +big data than traditional rdbms approaches. Spring Boot offers several conveniences for +working with Neo4j, including the `spring-boot-starter-data-neo4j` '`Starter POM`'. -[[boot-features-connecting-to-neo4j]] -==== Connecting to a Neo4j database -You can inject an auto-configured `org.neo4j.ogm.session.Neo4jSession` to -access Neo4j databases. - -In your `application properties`, you can supply any domain packages to be scanned by the OGM at startup -as well as the lifetime of the OGM session that will be established for web clients. - -By default your application will be configured to use an in-process embedded instance of Neo4j that will not persist any data when your application shuts down. You can also connect to a remote Neo4j server, or to an embedded instance that persists data between restarts of your application. - -The following sections show how you can configure your application for each of these scenarios. - -[[boot-features-neo4j-embedded]] -==== Connecting to an embedded database -[source,properties,indent=0] ----- - # embedded driver (optional: default is embedded driver) - spring.data.neo4j.driver=org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver - - # database path (optional: default is in-memory) - spring.data.neo4j.URI=file://var/tmp/graph.db - - # declare the domain packages for the OGM to scan at startup - # note: if you don't need to do any object mapping, you can omit this property - spring.data.neo4.domain.packages=my.app.domain.core, my.app.domain.external, ... - - # OGM session lifetime for web clients - # options: session (httpSession), request (httpRequest) - # default: session - spring.data.neo4j.session.lifetime=session ----- - -[[boot-features-neo4j-remote]] -==== Connecting to a remote database -[source,properties,indent=0] ----- - # http driver - spring.data.neo4j.driver=org.neo4j.ogm.drivers.http.driver.HttpDriver - - # database uri - spring.data.neo4j.URI=http://user:password@localhost:7474 - # declare the domain packages for the OGM to scan at startup - # note: if you don't need to do any object mapping, you can omit this property - spring.data.neo4.domain.packages=my.app.domain.core, my.app.domain.external, ... - # OGM session lifetime for web clients - # options: session (httpSession), request (httpRequest) - # default: session - spring.data.neo4j.session.lifetime=session ----- - -[[boot-features-spring-data-neo4j-application]] -==== Application -[source,java,indent=0]] ----- - @SpringBootApplication - @Import(Neo4jAutoConfiguration.class) - public class Application { - - public static void main(String[] args) { - new SpringApplication(Application.class).run(args); - } - - } ----- +[[boot-features-connecting-to-neo4j]] +==== Connecting to a Neo4j database +You can inject an auto-configured `Neo4jSession`, `Session` or `Neo4jOperations` instance +as you would any other Spring Bean. By default the instance will attempt to connect to a +Neo4j server using `localhost:7474`: -[[boot-features-neo4j-ogm-session]] -==== Neo4jSession [source,java,indent=0] ---- - import org.springframework.beans.factory.annotation.Autowired; - import org.springframework.stereotype.Component; - - import org.neo4j.ogm.session.Neo4jSession; - @Component public class MyBean { - private final Session session; + private final Neo4jTemplate neo4jTemplate; @Autowired - public MyBean(Session session) { - this.session = session; + public MyBean(Neo4jTemplate neo4jTemplate) { + this.neo4jTemplate = neo4jTemplate; } // ... - public void example() { - Iterable result = session.query("MATCH (c:Customer) RETURN count(*)",null); - // ... - } } ---- -[[boot-features-spring-data-neo4j-template]] -==== Neo4jTemplate -Spring Data Neo4j provides a -{spring-data-neo4j-javadoc}/core/Neo4jTemplate.html[`Neo4jTemplate`] class that is very -similar in its design to Spring's `JdbcTemplate`. As with `JdbcTemplate` Spring Boot -auto-configures a bean for you to simply inject: +You can take full control of the configuration by adding a +`org.neo4j.ogm.config.Configuration` `@Bean` of your own. Also, adding a `@Bean` of type +`Neo4jOperations` disables the auto-configuration. -[source,java,indent=0] +You can configure the user and credentials to use via the `spring.data.couchbase.*` +properties: + +[source,properties,indent=0] +---- + spring.data.neo4j.uri=http://my-server:7474 + spring.data.neo4j.username=neo4j + spring.data.neo4j.password=secret ---- - import org.springframework.beans.factory.annotation.Autowired; - import org.springframework.stereotype.Component; - import org.springframework.data.neo4j.template.Neo4jTemplate; +[[boot-features-connecting-to-neo4j-embedded]] +==== Using the embedded mode - @Component - public class MyBean { +NOTE: Neo4j's embedded mode is subject to a different licensing, make sure to review it +before integrating the dependency in your application. - private final Neo4jTemplate neo4jTemplate; +If you add `org.neo4j:neo4j-ogm-embedded-driver` to the dependencies of your application, +Spring Boot will automatically configure an in-process embedded instance of Neo4j that +will not persist any data when your application shuts down. You can explicitly disable +that mode using `spring.data.neo4j.embedded.enabled=false`. You can also enable +persistence for the embedded mode: - @Autowired - public MyBean(Neo4jTemplate neo4jTemplate) { - this.neo4jTemplate = neo4jTemplate; - } +---- + spring.data.neo4j.uri=file://var/tmp/graph.db +---- - // ... +[[boot-features-neo4j-ogm-session]] +==== Neo4jSession - } +By default, the lifetime of the session is scope to the application. If you are running a +web application you can change it to scope or request easily: + +---- + spring.data.neo4j.session.scope=session ---- -See the `Neo4jOperations` Javadoc for complete details. + [[boot-features-spring-data-neo4j-repositories]] ==== Spring Data Neo4j repositories @@ -3151,8 +3091,10 @@ In fact, both Spring Data JPA and Spring Data Neo4j share the same common infrastructure; so you could take the JPA example from earlier and, assuming that `City` is now a Neo4j OGM `@NodeEntity` rather than a JPA `@Entity`, it will work in the same way. -To enable repository support (and optionally support for `@Transactional`), add the following two annotations to -your Spring configuration: +TIP: You can customize entity scanning locations using the `@NodeEntityScan` annotation. + +To enable repository support (and optionally support for `@Transactional`), add the following +two annotations to your Spring configuration: [source,java,indent=0] ---- @@ -3182,6 +3124,7 @@ technologies, refer to their http://projects.spring.io/spring-data-neo4j/[refere documentation]. + [[boot-features-gemfire]] === Gemfire https://github.com/spring-projects/spring-data-gemfire[Spring Data Gemfire] provides diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 4ad586d157..3fc8a3cfe3 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -38,6 +38,7 @@ spring-boot-sample-data-gemfire spring-boot-sample-data-jpa spring-boot-sample-data-mongodb + spring-boot-sample-data-neo4j spring-boot-sample-data-redis spring-boot-sample-data-rest spring-boot-sample-data-solr diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/README.adoc b/spring-boot-samples/spring-boot-sample-data-neo4j/README.adoc new file mode 100644 index 0000000000..96c1922e71 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/README.adoc @@ -0,0 +1,23 @@ += Spring Boot Neo4j Sample + +This sample demonstrates the integration of Neo4j with a simple entity. It +expects a Neo4j instance running on `localhost`. If your neo4j instance +requires authentication, update `application.properties` with your credentials: + +``` +spring.data.neo4j.username=neo4j +spring.data.neo4j.password=secret +``` + +You can also locally add the embedded driver to embed Neo4j instead. Note +that Spring Boot does not provide dependency management for that GPL-licensed +library: + +``` + + org.neo4j + neo4j-ogm-embedded-driver + ${neo4j-ogm.version} + +``` + diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml b/spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml index e87954e532..f6a28a52f3 100644 --- a/spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml @@ -19,12 +19,6 @@ ${basedir}/../.. - - - org.springframework.boot - spring-boot-starter - - org.springframework.boot spring-boot-starter-data-neo4j @@ -35,14 +29,6 @@ spring-boot-starter-test test - - - org.neo4j - neo4j-ogm-embedded-driver - 2.0.0-M04 - test - - diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java index 20b2c8ef46..986f9b2902 100644 --- a/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java @@ -22,8 +22,6 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Import; - @SpringBootApplication public class SampleNeo4jApplication implements CommandLineRunner { diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/resources/application.properties index b56c18a6b3..e69de29bb2 100644 --- a/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/resources/application.properties @@ -1,3 +0,0 @@ -spring.data.neo4j.driver=org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver -spring.data.neo4j.domain.packages=sample.data.neo4j -spring.data.neo4j.session.lifetime=prototype \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java b/spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java index a7d445800c..15f808b403 100644 --- a/spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java @@ -16,35 +16,46 @@ package sample.data.neo4j; -import org.junit.ClassRule; +import java.net.ConnectException; + +import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.OutputCapture; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import org.springframework.boot.test.rule.OutputCapture; import static org.junit.Assert.assertTrue; /** * Tests for {@link SampleNeo4jApplication}. * - * @author Dave Syer - * @author Andy Wilkinson + * @author Stephane Nicoll */ -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(SampleNeo4jApplication.class) -@IntegrationTest public class SampleNeo4jApplicationTests { - @ClassRule - public static OutputCapture outputCapture = new OutputCapture(); + @Rule + public OutputCapture outputCapture = new OutputCapture(); @Test public void testDefaultSettings() throws Exception { - String output = SampleNeo4jApplicationTests.outputCapture.toString(); + try { + SampleNeo4jApplication.main(new String[0]); + } + catch (Exception ex) { + if (!neo4jServerRunning(ex)) { + return; + } + } + String output = this.outputCapture.toString(); assertTrue("Wrong output: " + output, output.contains("firstName='Alice', lastName='Smith'")); } + private boolean neo4jServerRunning(Throwable ex) { + System.out.println(ex.getMessage()); + if (ex instanceof ConnectException) { + return false; + } + return (ex.getCause() == null || neo4jServerRunning(ex.getCause())); + } + } diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 291535ccf7..c853b19759 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -189,6 +189,11 @@ liquibase-core true + + org.neo4j + neo4j-ogm-core + true + org.slf4j slf4j-api diff --git a/spring-boot/src/main/java/org/springframework/boot/context/scan/AbstractEntityScanBeanPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/scan/AbstractEntityScanBeanPostProcessor.java new file mode 100644 index 0000000000..06885ef909 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/scan/AbstractEntityScanBeanPostProcessor.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2016 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.boot.context.scan; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; + +/** + * A base {@link BeanPostProcessor} implementation that holds the packages to + * use for a given component. An implementation must implement + * {@link #postProcessBeforeInitialization(Object, String)} and update the + * component responsible to manage the packages to scan. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +public abstract class AbstractEntityScanBeanPostProcessor implements BeanPostProcessor, Ordered { + + private final String[] packagesToScan; + + protected AbstractEntityScanBeanPostProcessor(String[] packagesToScan) { + this.packagesToScan = packagesToScan; + } + + /** + * Return the packages to use. + * @return the packages to use. + */ + protected String[] getPackagesToScan() { + return this.packagesToScan; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public int getOrder() { + return 0; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityScanRegistrar.java b/spring-boot/src/main/java/org/springframework/boot/context/scan/AbstractEntityScanRegistrar.java similarity index 57% rename from spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityScanRegistrar.java rename to spring-boot/src/main/java/org/springframework/boot/context/scan/AbstractEntityScanRegistrar.java index b633de5478..5d9cf355f4 100644 --- a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityScanRegistrar.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/scan/AbstractEntityScanRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -14,44 +14,69 @@ * limitations under the License. */ -package org.springframework.boot.orm.jpa; +package org.springframework.boot.context.scan; +import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; /** - * {@link ImportBeanDefinitionRegistrar} used by {@link EntityScan}. + * A base {@link ImportBeanDefinitionRegistrar} used to collect the packages to + * scan for a given component. + *

+ * Expect to process an annotation type that defines a {@code basePackage} and + * {@code basePackageClasses} attributes as well as a {@code value} alias of + * {@code basePackage}. + *

+ * The {@link ImportBeanDefinitionRegistrar} registers a single + * {@link AbstractEntityScanBeanPostProcessor} implementation with the packages + * to use. * - * @author Phillip Webb - * @author Oliver Gierke + * @author Stephane Nicoll + * @since 1.4.0 + * @see AbstractEntityScanBeanPostProcessor */ -class EntityScanRegistrar implements ImportBeanDefinitionRegistrar { +public abstract class AbstractEntityScanRegistrar implements ImportBeanDefinitionRegistrar { + + private final Class annotationType; + + private final String beanPostProcessorName; + + private final Class beanPostProcessorType; + + /** + * Create an instance. + * @param annotationType the annotation to inspect + * @param beanPostProcessorName the name of the bean post processor + * @param beanPostProcessorType the type of the bean post processor implementation + */ + protected AbstractEntityScanRegistrar(Class annotationType, + String beanPostProcessorName, + Class beanPostProcessorType) { + this.beanPostProcessorName = beanPostProcessorName; + this.annotationType = annotationType; + this.beanPostProcessorType = beanPostProcessorType; + } - private static final String BEAN_NAME = "entityScanBeanPostProcessor"; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Set packagesToScan = getPackagesToScan(importingClassMetadata); - if (!registry.containsBeanDefinition(BEAN_NAME)) { + if (!registry.containsBeanDefinition(this.beanPostProcessorName)) { addEntityScanBeanPostProcessor(registry, packagesToScan); } else { @@ -59,15 +84,16 @@ class EntityScanRegistrar implements ImportBeanDefinitionRegistrar { } } - private Set getPackagesToScan(AnnotationMetadata metadata) { + protected Set getPackagesToScan(AnnotationMetadata metadata) { AnnotationAttributes attributes = AnnotationAttributes - .fromMap(metadata.getAnnotationAttributes(EntityScan.class.getName())); + .fromMap(metadata.getAnnotationAttributes(this.annotationType.getName())); String[] value = attributes.getStringArray("value"); String[] basePackages = attributes.getStringArray("basePackages"); Class[] basePackageClasses = attributes.getClassArray("basePackageClasses"); if (!ObjectUtils.isEmpty(value)) { - Assert.state(ObjectUtils.isEmpty(basePackages), - "@EntityScan basePackages and value attributes are mutually exclusive"); + Assert.state(ObjectUtils.isEmpty(basePackages), String.format( + "@%s basePackages and value attributes are mutually exclusive", + this.annotationType.getSimpleName())); } Set packagesToScan = new LinkedHashSet(); packagesToScan.addAll(Arrays.asList(value)); @@ -85,20 +111,20 @@ class EntityScanRegistrar implements ImportBeanDefinitionRegistrar { private void addEntityScanBeanPostProcessor(BeanDefinitionRegistry registry, Set packagesToScan) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); - beanDefinition.setBeanClass(EntityScanBeanPostProcessor.class); + beanDefinition.setBeanClass(this.beanPostProcessorType); beanDefinition.getConstructorArgumentValues() .addGenericArgumentValue(toArray(packagesToScan)); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // We don't need this one to be post processed otherwise it can cause a // cascade of bean instantiation that we would rather avoid. beanDefinition.setSynthetic(true); - registry.registerBeanDefinition(BEAN_NAME, beanDefinition); + registry.registerBeanDefinition(this.beanPostProcessorName, beanDefinition); } private void updateEntityScanBeanPostProcessor(BeanDefinitionRegistry registry, Set packagesToScan) { - BeanDefinition definition = registry.getBeanDefinition(BEAN_NAME); - ValueHolder constructorArguments = definition.getConstructorArgumentValues() + BeanDefinition definition = registry.getBeanDefinition(this.beanPostProcessorName); + ConstructorArgumentValues.ValueHolder constructorArguments = definition.getConstructorArgumentValues() .getGenericArgumentValue(String[].class); Set mergedPackages = new LinkedHashSet(); mergedPackages.addAll(Arrays.asList((String[]) constructorArguments.getValue())); @@ -110,52 +136,4 @@ class EntityScanRegistrar implements ImportBeanDefinitionRegistrar { return set.toArray(new String[set.size()]); } - /** - * {@link BeanPostProcessor} to set - * {@link LocalContainerEntityManagerFactoryBean#setPackagesToScan(String...)} based - * on an {@link EntityScan} annotation. - */ - static class EntityScanBeanPostProcessor - implements BeanPostProcessor, SmartInitializingSingleton, Ordered { - - private final String[] packagesToScan; - - private boolean processed; - - EntityScanBeanPostProcessor(String[] packagesToScan) { - this.packagesToScan = packagesToScan; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - if (bean instanceof LocalContainerEntityManagerFactoryBean) { - LocalContainerEntityManagerFactoryBean factoryBean = (LocalContainerEntityManagerFactoryBean) bean; - factoryBean.setPackagesToScan(this.packagesToScan); - this.processed = true; - } - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } - - @Override - public void afterSingletonsInstantiated() { - Assert.state(this.processed, - "Unable to configure " - + "LocalContainerEntityManagerFactoryBean from @EntityScan, " - + "ensure an appropriate bean is registered."); - } - - @Override - public int getOrder() { - return 0; - } - - } - } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/scan/package-info.java b/spring-boot/src/main/java/org/springframework/boot/context/scan/package-info.java new file mode 100644 index 0000000000..c6a135a597 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/scan/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2016 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. + */ + +/** + * Support for component scanning. + */ +package org.springframework.boot.context.scan; diff --git a/spring-boot/src/main/java/org/springframework/boot/neo4j/NodeEntityScan.java b/spring-boot/src/main/java/org/springframework/boot/neo4j/NodeEntityScan.java new file mode 100644 index 0000000000..149234fa99 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/neo4j/NodeEntityScan.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2016 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.boot.neo4j; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.neo4j.ogm.session.SessionFactory; + +import org.springframework.context.annotation.Import; + +/** + * Configures the {@link SessionFactory} to scan for node entity + * classes in the classpath. This annotation provides an alternative to manually setting + * {@link SessionFactoryProvider#setPackagesToScan(String...)} and is + * particularly useful if you want to configure entity scanning in a type-safe way, or if + * your {@link SessionFactory} is auto-configured. + *

+ * A {@link SessionFactoryProvider} must be configured within your Spring + * ApplicationContext in order to use entity scanning. Furthermore, any existing + * {@code packagesToScan} setting will be replaced. + *

+ * One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias + * {@link #value()} may be specified to define specific packages to scan. If specific + * packages are not defined scanning will occur from the package of the class with this + * annotation. + * + * @author Phillip Webb + * @author Stephane Nicoll + * @since 1.4.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(NodeEntityScanRegistrar.class) +public @interface NodeEntityScan { + + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation + * declarations e.g.: {@code @NodeEntityScan("org.my.pkg")} instead of + * {@code @NodeEntityScan(basePackages="org.my.pkg")}. + * @return the base packages to scan + */ + String[] value() default {}; + + /** + * Base packages to scan for node entities. {@link #value()} is an alias for (and + * mutually exclusive with) this attribute. + *

+ * Use {@link #basePackageClasses()} for a type-safe alternative to String-based + * package names. + * @return the base packages to scan + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to + * scan for node entities. The package of each class specified will be scanned. + *

+ * Consider creating a special no-op marker class or interface in each package that + * serves no purpose other than being referenced by this attribute. + * @return classes from the base packages to scan + */ + Class[] basePackageClasses() default {}; + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/neo4j/NodeEntityScanRegistrar.java b/spring-boot/src/main/java/org/springframework/boot/neo4j/NodeEntityScanRegistrar.java new file mode 100644 index 0000000000..64c2cdfbdc --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/neo4j/NodeEntityScanRegistrar.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2016 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.boot.neo4j; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.context.scan.AbstractEntityScanBeanPostProcessor; +import org.springframework.boot.context.scan.AbstractEntityScanRegistrar; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.util.Assert; + +/** + * {@link ImportBeanDefinitionRegistrar} used by {@link NodeEntityScan}. + * + * @author Stephane Nicoll + */ +class NodeEntityScanRegistrar extends AbstractEntityScanRegistrar { + + NodeEntityScanRegistrar() { + super(NodeEntityScan.class, "nodeEntityScanBeanPostProcessor", NodeEntityScanBeanPostProcessor.class); + } + + /** + * {@link BeanPostProcessor} to set + * {@link SessionFactoryProvider#setPackagesToScan(String...)} based + * on an {@link NodeEntityScan} annotation. + */ + static class NodeEntityScanBeanPostProcessor extends AbstractEntityScanBeanPostProcessor + implements SmartInitializingSingleton { + + private boolean processed; + + NodeEntityScanBeanPostProcessor(String[] packagesToScan) { + super(packagesToScan); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof SessionFactoryProvider) { + SessionFactoryProvider provider = (SessionFactoryProvider) bean; + provider.setPackagesToScan(getPackagesToScan()); + this.processed = true; + } + return bean; + } + + @Override + public void afterSingletonsInstantiated() { + Assert.state(this.processed, + "Unable to configure " + + "SessionFactoryFactoryBean from @NodeEntityScan, " + + "ensure an appropriate bean is registered."); + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/neo4j/SessionFactoryProvider.java b/spring-boot/src/main/java/org/springframework/boot/neo4j/SessionFactoryProvider.java new file mode 100644 index 0000000000..7ab1793bb8 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/neo4j/SessionFactoryProvider.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2016 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.boot.neo4j; + +import org.neo4j.ogm.config.Configuration; +import org.neo4j.ogm.session.SessionFactory; + +/** + * Provide a a Neo4j {@link SessionFactory} instance based on a + * configurable {@link Configuration} and custom packages to scan. + * + * @author Stephane Nicoll + * @since 1.4.0 + * @see NodeEntityScan + */ +public class SessionFactoryProvider { + + private Configuration configuration; + + private String[] packagesToScan; + + /** + * Set the configuration to use. + * @param configuration the configuration + */ + public void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } + + /** + * Set the packages to scan. + * @param packagesToScan the packages to scan + */ + public void setPackagesToScan(String[] packagesToScan) { + this.packagesToScan = packagesToScan; + } + + public SessionFactory getSessionFactory() { + return new SessionFactory(this.configuration, this.packagesToScan); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/neo4j/package-info.java b/spring-boot/src/main/java/org/springframework/boot/neo4j/package-info.java new file mode 100644 index 0000000000..b588d49c29 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/neo4j/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2016 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. + */ + +/** + * Neo4j support classes. + */ +package org.springframework.boot.neo4j; diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityScan.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityScan.java index 4c37d96f87..9e231c556e 100644 --- a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityScan.java +++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/EntityScan.java @@ -46,7 +46,7 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented -@Import(EntityScanRegistrar.class) +@Import(JpaEntityScanRegistrar.class) public @interface EntityScan { /** diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaEntityScanRegistrar.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaEntityScanRegistrar.java new file mode 100644 index 0000000000..b73e0e08aa --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaEntityScanRegistrar.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2015 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.boot.orm.jpa; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.context.scan.AbstractEntityScanBeanPostProcessor; +import org.springframework.boot.context.scan.AbstractEntityScanRegistrar; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.util.Assert; + +/** + * {@link ImportBeanDefinitionRegistrar} used by {@link EntityScan}. + * + * @author Phillip Webb + * @author Oliver Gierke + */ +class JpaEntityScanRegistrar extends AbstractEntityScanRegistrar { + + JpaEntityScanRegistrar() { + super(EntityScan.class, "entityScanBeanPostProcessor", JpaEntityScanBeanPostProcessor.class); + } + + /** + * {@link BeanPostProcessor} to set + * {@link LocalContainerEntityManagerFactoryBean#setPackagesToScan(String...)} based + * on an {@link EntityScan} annotation. + */ + static class JpaEntityScanBeanPostProcessor extends AbstractEntityScanBeanPostProcessor + implements SmartInitializingSingleton { + + private boolean processed; + + JpaEntityScanBeanPostProcessor(String[] packagesToScan) { + super(packagesToScan); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof LocalContainerEntityManagerFactoryBean) { + LocalContainerEntityManagerFactoryBean factoryBean = (LocalContainerEntityManagerFactoryBean) bean; + factoryBean.setPackagesToScan(getPackagesToScan()); + this.processed = true; + } + return bean; + } + + @Override + public void afterSingletonsInstantiated() { + Assert.state(this.processed, + "Unable to configure " + + "LocalContainerEntityManagerFactoryBean from @EntityScan, " + + "ensure an appropriate bean is registered."); + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScan.java b/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScan.java new file mode 100644 index 0000000000..af45607ccc --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScan.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2016 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.boot.context.scan; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +/** + * EntityScan test annotation. + * + * @author Stephane Nicoll + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(TestEntityScanRegistrar.class) +public @interface TestEntityScan { + + String[] value() default {}; + + String[] basePackages() default {}; + + Class[] basePackageClasses() default {}; + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScanRegistrar.java b/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScanRegistrar.java new file mode 100644 index 0000000000..7976c19eea --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScanRegistrar.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2016 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.boot.context.scan; + +import org.springframework.beans.BeansException; + +/** + * Test implementation of {@link AbstractEntityScanRegistrar}. + * + * @author Stephane Nicoll + */ +class TestEntityScanRegistrar extends AbstractEntityScanRegistrar { + + static final String BEAN_NAME = "testEntityScanBeanPostProcessor"; + + TestEntityScanRegistrar() { + super(TestEntityScan.class, BEAN_NAME, TestEntityScanBeanPostProcessor.class); + } + + static class TestFactoryBean { + private String[] packagesToScan; + + public void setPackagesToScan(String... packagesToScan) { + this.packagesToScan = packagesToScan; + } + + public String[] getPackagesToScan() { + return this.packagesToScan; + } + + } + + static class TestEntityScanBeanPostProcessor extends AbstractEntityScanBeanPostProcessor { + + TestEntityScanBeanPostProcessor(String[] packagesToScan) { + super(packagesToScan); + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof TestFactoryBean) { + TestFactoryBean factoryBean = (TestFactoryBean) bean; + factoryBean.setPackagesToScan(getPackagesToScan()); + } + return bean; + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScanTests.java b/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScanTests.java new file mode 100644 index 0000000000..c21377cc34 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/scan/TestEntityScanTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2012-2016 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.boot.context.scan; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.context.scan.TestEntityScanRegistrar.TestFactoryBean; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link TestEntityScan}. + * + * @author Phillip Webb + * @author Stephane Nicoll + */ +public class TestEntityScanTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private AnnotationConfigApplicationContext context; + + @After + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testValue() throws Exception { + this.context = new AnnotationConfigApplicationContext(ValueConfig.class); + assertSetPackagesToScan("com.mycorp.entity"); + } + + @Test + public void basePackages() throws Exception { + this.context = new AnnotationConfigApplicationContext(BasePackagesConfig.class); + assertSetPackagesToScan("com.mycorp.entity2"); + } + + @Test + public void basePackageClasses() throws Exception { + this.context = new AnnotationConfigApplicationContext( + BasePackageClassesConfig.class); + assertSetPackagesToScan(getClass().getPackage().getName()); + } + + @Test + public void fromConfigurationClass() throws Exception { + this.context = new AnnotationConfigApplicationContext(FromConfigConfig.class); + assertSetPackagesToScan(getClass().getPackage().getName()); + } + + @Test + public void valueAndBasePackagesThrows() throws Exception { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("@TestEntityScan basePackages and value " + + "attributes are mutually exclusive"); + this.context = new AnnotationConfigApplicationContext(ValueAndBasePackages.class); + } + + @Test + public void valueAndBasePackageClassesMerges() throws Exception { + this.context = new AnnotationConfigApplicationContext( + ValueAndBasePackageClasses.class); + assertSetPackagesToScan("com.mycorp.entity", getClass().getPackage().getName()); + } + + @Test + public void basePackageAndBasePackageClassesMerges() throws Exception { + this.context = new AnnotationConfigApplicationContext( + BasePackagesAndBasePackageClasses.class); + assertSetPackagesToScan("com.mycorp.entity2", getClass().getPackage().getName()); + } + + @Test + public void considersMultipleAnnotations() { + this.context = new AnnotationConfigApplicationContext(MultiScanFirst.class, + MultiScanSecond.class); + assertSetPackagesToScan("foo", "bar"); + } + + private void assertSetPackagesToScan(String... expected) { + String[] actual = this.context + .getBean(TestFactoryBean.class) + .getPackagesToScan(); + assertThat(actual).isEqualTo(expected); + } + + @Configuration + static class BaseConfig { + + @Bean + public TestFactoryBean testFactoryBean() { + return new TestFactoryBean(); + } + + } + + @TestEntityScan("com.mycorp.entity") + static class ValueConfig extends BaseConfig { + } + + @TestEntityScan(basePackages = "com.mycorp.entity2") + static class BasePackagesConfig extends BaseConfig { + } + + @TestEntityScan(basePackageClasses = TestEntityScanTests.class) + static class BasePackageClassesConfig extends BaseConfig { + } + + @TestEntityScan + static class FromConfigConfig extends BaseConfig { + } + + @TestEntityScan(value = "com.mycorp.entity", basePackages = "com.mycorp") + static class ValueAndBasePackages extends BaseConfig { + } + + @TestEntityScan(value = "com.mycorp.entity", basePackageClasses = TestEntityScanTests.class) + static class ValueAndBasePackageClasses extends BaseConfig { + } + + @TestEntityScan(basePackages = "com.mycorp.entity2", basePackageClasses = TestEntityScanTests.class) + static class BasePackagesAndBasePackageClasses extends BaseConfig { + } + + @TestEntityScan(basePackages = "foo") + static class MultiScanFirst extends BaseConfig { + } + + @TestEntityScan(basePackages = "bar") + static class MultiScanSecond extends BaseConfig { + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/neo4j/NodeEntityScanTests.java b/spring-boot/src/test/java/org/springframework/boot/neo4j/NodeEntityScanTests.java new file mode 100644 index 0000000000..bb24396be7 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/neo4j/NodeEntityScanTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2012-2016 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.boot.neo4j; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link NodeEntityScan}. + * + * @author Stephane Nicoll + */ +public class NodeEntityScanTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private AnnotationConfigApplicationContext context; + + @After + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void simpleValue() throws Exception { + this.context = new AnnotationConfigApplicationContext(ValueConfig.class); + assertSetPackagesToScan("com.mycorp.entity"); + } + + @Test + public void needsSessionFactoryFactory() throws Exception { + this.thrown.expect(IllegalStateException.class); + this.thrown.expectMessage("Unable to configure " + + "SessionFactoryFactoryBean from @NodeEntityScan, " + + "ensure an appropriate bean is registered."); + this.context = new AnnotationConfigApplicationContext(MissingSessionFactory.class); + } + + private void assertSetPackagesToScan(String... expected) { + String[] actual = this.context + .getBean(TestSessionFactoryProvider.class) + .getPackagesToScan(); + assertThat(actual).isEqualTo(expected); + } + + @Configuration + static class BaseConfig { + + @Bean + public SessionFactoryProvider sessionFactoryFactoryBean() { + return new TestSessionFactoryProvider(); + } + + } + + @NodeEntityScan("com.mycorp.entity") + static class ValueConfig extends BaseConfig { + } + + @Configuration + @NodeEntityScan("com.mycorp.entity") + static class MissingSessionFactory { + } + + + private static class TestSessionFactoryProvider + extends SessionFactoryProvider { + + private String[] packagesToScan; + + @Override + public void setPackagesToScan(String... packagesToScan) { + this.packagesToScan = packagesToScan; + } + + public String[] getPackagesToScan() { + return this.packagesToScan; + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/orm/jpa/EntityScanTests.java b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/EntityScanTests.java index 01cdcfdb39..93101b2d1f 100644 --- a/spring-boot/src/test/java/org/springframework/boot/orm/jpa/EntityScanTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/orm/jpa/EntityScanTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.orm.jpa; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceException; +import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -37,6 +38,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link EntityScan}. * * @author Phillip Webb + * @author Stephane Nicoll */ public class EntityScanTests { @@ -45,51 +47,17 @@ public class EntityScanTests { private AnnotationConfigApplicationContext context; - @Test - public void testValue() throws Exception { - this.context = new AnnotationConfigApplicationContext(ValueConfig.class); - assertSetPackagesToScan("com.mycorp.entity"); - } - - @Test - public void basePackages() throws Exception { - this.context = new AnnotationConfigApplicationContext(BasePackagesConfig.class); - assertSetPackagesToScan("com.mycorp.entity2"); - } - - @Test - public void basePackageClasses() throws Exception { - this.context = new AnnotationConfigApplicationContext( - BasePackageClassesConfig.class); - assertSetPackagesToScan(getClass().getPackage().getName()); - } - - @Test - public void fromConfigurationClass() throws Exception { - this.context = new AnnotationConfigApplicationContext(FromConfigConfig.class); - assertSetPackagesToScan(getClass().getPackage().getName()); - } - - @Test - public void valueAndBasePackagesThrows() throws Exception { - this.thrown.expect(IllegalStateException.class); - this.thrown.expectMessage("@EntityScan basePackages and value " - + "attributes are mutually exclusive"); - this.context = new AnnotationConfigApplicationContext(ValueAndBasePackages.class); - } - - @Test - public void valueAndBasePackageClassesMerges() throws Exception { - this.context = new AnnotationConfigApplicationContext( - ValueAndBasePackageClasses.class); - assertSetPackagesToScan("com.mycorp.entity", getClass().getPackage().getName()); + @After + public void closeContext() { + if (this.context != null) { + this.context.close(); + } } @Test - public void basePackageAndBasePackageClassesMerges() throws Exception { - this.context = new AnnotationConfigApplicationContext( - BasePackagesAndBasePackageClasses.class); - assertSetPackagesToScan("com.mycorp.entity2", getClass().getPackage().getName()); + public void simpleValue() throws Exception { + this.context = new AnnotationConfigApplicationContext(ValueConfig.class); + assertSetPackagesToScan("com.mycorp.entity"); } @Test @@ -108,13 +76,6 @@ public class EntityScanTests { assertSetPackagesToScan("com.mycorp.entity"); } - @Test - public void considersMultipleEntityScanAnnotations() { - this.context = new AnnotationConfigApplicationContext(MultiScanFirst.class, - MultiScanSecond.class); - assertSetPackagesToScan("foo", "bar"); - } - private void assertSetPackagesToScan(String... expected) { String[] actual = this.context .getBean(TestLocalContainerEntityManagerFactoryBean.class) @@ -136,30 +97,6 @@ public class EntityScanTests { static class ValueConfig extends BaseConfig { } - @EntityScan(basePackages = "com.mycorp.entity2") - static class BasePackagesConfig extends BaseConfig { - } - - @EntityScan(basePackageClasses = EntityScanTests.class) - static class BasePackageClassesConfig extends BaseConfig { - } - - @EntityScan - static class FromConfigConfig extends BaseConfig { - } - - @EntityScan(value = "com.mycorp.entity", basePackages = "com.mycorp") - static class ValueAndBasePackages extends BaseConfig { - } - - @EntityScan(value = "com.mycorp.entity", basePackageClasses = EntityScanTests.class) - static class ValueAndBasePackageClasses extends BaseConfig { - } - - @EntityScan(basePackages = "com.mycorp.entity2", basePackageClasses = EntityScanTests.class) - static class BasePackagesAndBasePackageClasses extends BaseConfig { - } - @Configuration @EntityScan("com.mycorp.entity") static class MissingEntityManager { @@ -195,16 +132,6 @@ public class EntityScanTests { } } - @EntityScan(basePackages = "foo") - static class MultiScanFirst extends BaseConfig { - - } - - @EntityScan(basePackages = "bar") - static class MultiScanSecond extends BaseConfig { - - } - private static class TestLocalContainerEntityManagerFactoryBean extends LocalContainerEntityManagerFactoryBean {