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-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/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/neo4j/Neo4jRepositoriesAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java new file mode 100644 index 0000000000..8eb7e4751b --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfiguration.java @@ -0,0 +1,64 @@ +/* + * 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.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.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; + +/** + * {@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 + * @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) +@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..5793b10072 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigureRegistrar.java @@ -0,0 +1,55 @@ +/* + * 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.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 Michael Hunger + */ +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/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 fba5f4e02c..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 @@ -90,6 +90,18 @@ "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.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", @@ -330,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 811f7d78f3..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,6 +31,7 @@ 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.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ @@ -68,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 new file mode 100644 index 0000000000..30fc7667ca --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/alt/neo4j/CityNeo4jRepository.java @@ -0,0 +1,23 @@ +/* + * 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.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..7f774d1b4f --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/MixedNeo4jRepositoriesAutoConfigurationTests.java @@ -0,0 +1,167 @@ +/* + * 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.data.neo4j; + +import java.util.ArrayList; +import java.util.List; + +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.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.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 Neo4jRepositoriesAutoConfiguration}. + * + * @author Dave Syer + * @author Oliver Gierke + * @author Michael Hunger + * @author Vince Bickers + * @author Stephane Nicoll + */ +public class MixedNeo4jRepositoriesAutoConfigurationTests { + + private AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(); + + @After + public void close() { + this.context.close(); + } + + @Test + public void testDefaultRepositoryConfiguration() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); + this.context.register(TestConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(CountryRepository.class)).isNotNull(); + } + + @Test + public void testMixedRepositoryConfiguration() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); + this.context.register(MixedConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(CountryRepository.class)).isNotNull(); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + } + + @Test + public void testJpaRepositoryConfigurationWithNeo4jTemplate() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); + this.context.register(JpaConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + } + + @Test + @Ignore + public void testJpaRepositoryConfigurationWithNeo4jOverlap() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, "spring.datasource.initialize:false"); + this.context.register(OverlapConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + } + + @Test + public void testJpaRepositoryConfigurationWithNeo4jOverlapDisabled() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:false", + "spring.data.neo4j.repositories.enabled:false"); + this.context.register(OverlapConfiguration.class, BaseConfiguration.class); + this.context.refresh(); + 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 auto-configuration 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/Neo4jDataAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java new file mode 100644 index 0000000000..7b8fa27709 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java @@ -0,0 +1,69 @@ +/* + * 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.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.springframework.boot.autoconfigure.AutoConfigurationPackages; +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; + +/** + * 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 = new AnnotationConfigApplicationContext(); + + @After + public void close() { + this.context.close(); + } + + @Test + public void usesAutoConfigurationPackageToPickUpDomainTypes() { + 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/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..a1a514f831 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jRepositoriesAutoConfigurationTests.java @@ -0,0 +1,126 @@ +/* + * 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.data.neo4j; + +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 Neo4jRepositoriesAutoConfiguration}. + * + * @author Dave Syer + * @author Oliver Gierke + * @author Michael Hunger + * @author Vince Bickers + * @author Stephane Nicoll + */ +public class Neo4jRepositoriesAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + this.context.close(); + } + + @Test + public void testDefaultRepositoryConfiguration() throws Exception { + prepareApplicationContext(TestConfiguration.class); + + assertThat(this.context.getBean(CityRepository.class)).isNotNull(); + Neo4jMappingContext mappingContext = this.context.getBean(Neo4jMappingContext.class); + assertThat(mappingContext.getPersistentEntity(City.class)).isNotNull(); + + } + + @Test + public void testNoRepositoryConfiguration() throws Exception { + prepareApplicationContext(EmptyConfiguration.class); + + assertThat(this.context.getBean(SessionFactory.class)).isNotNull(); + } + + @Test + public void doesNotTriggerDefaultRepositoryDetectionIfCustomized() { + prepareApplicationContext(CustomizedConfiguration.class); + + 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(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.data.neo4j.uri=http://localhost:9797"); + 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..98e225c0c4 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/City.java @@ -0,0 +1,71 @@ +/* + * 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.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..67b7f4b860 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/city/CityRepository.java @@ -0,0 +1,27 @@ +/* + * 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.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); + +} 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..b9baccf1a4 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/Country.java @@ -0,0 +1,50 @@ +/* + * 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.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..a3180af6d8 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/country/CountryRepository.java @@ -0,0 +1,23 @@ +/* + * 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.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..a00bbf59b9 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jAutoConfigurationTests.java @@ -0,0 +1,125 @@ +/* + * 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 org.junit.After; +import org.junit.Test; +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.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 can't use the embedded driver + * as we use lucene 4 and Neo4j still requires 3. + * + * @author Stephane Nicoll + * @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 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 new file mode 100644 index 0000000000..be38fab978 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/neo4j/Neo4jPropertiesTests.java @@ -0,0 +1,186 @@ +/* + * 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.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.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Neo4jProperties}. + * + * @author Stephane Nicoll + */ +public class Neo4jPropertiesTests { + + private AnnotationConfigApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void defaultUseEmbeddedInMemoryIfAvailable() { + Neo4jProperties properties = load(true); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.EMBEDDED_DRIVER, null); + } + + @Test + public void defaultUseHttpDriverIfEmbeddedDriverIsNotAvailable() { + Neo4jProperties properties = load(false); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.HTTP_DRIVER, + Neo4jProperties.DEFAULT_HTTP_URI); + } + + @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"); + } + + @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"); + } + + @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 embeddedModeDisabledUseHttpUri() { + Neo4jProperties properties = load(true, + "spring.data.neo4j.embedded.enabled=false"); + Configuration configuration = properties.createConfiguration(); + assertDriver(configuration, Neo4jProperties.HTTP_DRIVER, + Neo4jProperties.DEFAULT_HTTP_URI); + } + + @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"); + } + + 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); + } + + 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); + } + + @org.springframework.context.annotation.Configuration + @EnableConfigurationProperties(Neo4jProperties.class) + static class TestConfiguration { + + } + + private static class TestEmbeddedDriver { + + } + +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index d6570f51fb..7c7a99d7a4 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -55,6 +55,7 @@ 2.1.9 1.9.2 3.2.2 + 1.10 1.4 2.1.1 2.1 @@ -122,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 @@ -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,6 +775,11 @@ commons-collections ${commons-collections.version} + + commons-codec + commons-codec + ${commons-codec.version} + commons-dbcp commons-dbcp @@ -1760,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 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/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 7c88a8e2e1..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]. @@ -3009,6 +3009,122 @@ 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 `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`: + +[source,java,indent=0] +---- + @Component + public class MyBean { + + private final Neo4jTemplate neo4jTemplate; + + @Autowired + public MyBean(Neo4jTemplate neo4jTemplate) { + this.neo4jTemplate = neo4jTemplate; + } + + // ... + + } +---- + +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. + +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 +---- + +[[boot-features-connecting-to-neo4j-embedded]] +==== Using the embedded mode + +NOTE: Neo4j's embedded mode is subject to a different licensing, make sure to review it +before integrating the dependency in your application. + +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: + +---- + 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 +---- + + + +[[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. + +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] +---- + @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/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 new file mode 100644 index 0000000000..f6a28a52f3 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/pom.xml @@ -0,0 +1,41 @@ + + + 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-data-neo4j + + + + org.springframework.boot + spring-boot-starter-test + 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..986f9b2902 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/main/java/sample/data/neo4j/SampleNeo4jApplication.java @@ -0,0 +1,63 @@ +/* + * 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; + +@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..e69de29bb2 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..15f808b403 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-data-neo4j/src/test/java/sample/data/neo4j/SampleNeo4jApplicationTests.java @@ -0,0 +1,61 @@ +/* + * 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 java.net.ConnectException; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.boot.test.rule.OutputCapture; + +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link SampleNeo4jApplication}. + * + * @author Stephane Nicoll + */ +public class SampleNeo4jApplicationTests { + + @Rule + public OutputCapture outputCapture = new OutputCapture(); + + @Test + public void testDefaultSettings() throws Exception { + 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-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 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 {