diff --git a/spring-boot-project/spring-boot-autoconfigure/pom.xml b/spring-boot-project/spring-boot-autoconfigure/pom.xml index 497e4d8188..b81e317cc7 100755 --- a/spring-boot-project/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-autoconfigure/pom.xml @@ -782,7 +782,7 @@ org.neo4j - neo4j-ogm-http-driver + neo4j-ogm-bolt-native-types test @@ -790,6 +790,11 @@ neo4j-ogm-embedded-driver test + + org.neo4j + neo4j-ogm-http-driver + test + org.springframework spring-test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java index 620dbfb0d3..ccf0c99b9f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,9 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.neo4j.ogm.driver.NativeTypesException; +import org.neo4j.ogm.driver.NativeTypesNotAvailableException; +import org.neo4j.ogm.driver.NativeTypesNotSupportedException; import org.neo4j.ogm.session.SessionFactory; import org.neo4j.ogm.session.event.EventListener; @@ -34,12 +37,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.autoconfigure.domain.EntityScanPackages; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.neo4j.transaction.Neo4jTransactionManager; import org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor; +import org.springframework.lang.Nullable; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.StringUtils; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -53,6 +58,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * @author Vince Bickers * @author Stephane Nicoll * @author Kazuki Shimizu + * @author Michael Simons * @since 1.4.0 */ @Configuration @@ -73,10 +79,44 @@ public class Neo4jDataAutoConfiguration { public SessionFactory sessionFactory(org.neo4j.ogm.config.Configuration configuration, ApplicationContext applicationContext, ObjectProvider eventListeners) { - SessionFactory sessionFactory = new SessionFactory(configuration, - getPackagesToScan(applicationContext)); - eventListeners.stream().forEach(sessionFactory::register); - return sessionFactory; + try { + SessionFactory sessionFactory = new SessionFactory(configuration, + getPackagesToScan(applicationContext)); + eventListeners.forEach(sessionFactory::register); + return sessionFactory; + } + catch (NativeTypesException ex) { + InvalidConfigurationPropertyValueException translatedMessage = translateNativeTypesException( + ex); + throw (translatedMessage != null) ? translatedMessage : ex; + } + } + + @Nullable + private static InvalidConfigurationPropertyValueException translateNativeTypesException( + NativeTypesException cause) { + + String propertyName = Neo4jProperties.CONFIGURATION_PREFIX + ".use-native-types"; + boolean propertyValue = true; + + if (cause instanceof NativeTypesNotAvailableException) { + String message = String.format( + "The native type module for your Neo4j-OGM driver is not available. " + + "Please add the following dependency to your build:%n'%s'.", + ((NativeTypesNotAvailableException) cause).getRequiredModule()); + return new InvalidConfigurationPropertyValueException(propertyName, + propertyValue, message); + } + if (cause instanceof NativeTypesNotSupportedException) { + String message = String.format( + "The configured Neo4j-OGM driver %s does not support Neo4j native types. " + + "Please consider one of the drivers that support Neo4js native types like the Bolt or the embedded driver.", + cause.getDriverClassName()); + return new InvalidConfigurationPropertyValueException(propertyName, + propertyValue, message); + } + + return null; } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java index d1f8f8fecd..48408be386 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2019 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. @@ -33,11 +33,14 @@ import org.springframework.util.ClassUtils; * @author Michael Hunger * @author Vince Bickers * @author Aurélien Leboulanger + * @author Michael Simons * @since 1.4.0 */ -@ConfigurationProperties(prefix = "spring.data.neo4j") +@ConfigurationProperties(prefix = Neo4jProperties.CONFIGURATION_PREFIX) public class Neo4jProperties implements ApplicationContextAware { + static final String CONFIGURATION_PREFIX = "spring.data.neo4j"; + static final String EMBEDDED_DRIVER = "org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver"; static final String HTTP_DRIVER = "org.neo4j.ogm.drivers.http.driver.HttpDriver"; @@ -72,6 +75,12 @@ public class Neo4jProperties implements ApplicationContextAware { */ private Boolean openInView; + /** + * Disables the conversion of java.time.* and spatial types in Neo4j-OGM entities and + * uses Neo4j native types wherever possible. + */ + private boolean useNativeTypes = false; + private final Embedded embedded = new Embedded(); private ClassLoader classLoader = Neo4jProperties.class.getClassLoader(); @@ -116,6 +125,14 @@ public class Neo4jProperties implements ApplicationContextAware { this.openInView = openInView; } + public boolean isUseNativeTypes() { + return this.useNativeTypes; + } + + public void setUseNativeTypes(boolean useNativeTypes) { + this.useNativeTypes = useNativeTypes; + } + public Embedded getEmbedded() { return this.embedded; } @@ -146,6 +163,9 @@ public class Neo4jProperties implements ApplicationContextAware { builder.credentials(this.username, this.password); } builder.autoIndex(this.getAutoIndex().getName()); + if (this.useNativeTypes) { + builder.useNativeTypes(); + } } private void configureUriWithDefaults(Builder builder) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java index 44ac066639..43a67c7409 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jDataAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.data.neo4j.city.City; import org.springframework.boot.autoconfigure.data.neo4j.country.Country; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; +import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -46,13 +47,14 @@ import org.springframework.data.neo4j.web.support.OpenSessionInViewInterceptor; import org.springframework.web.context.WebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** - * Tests for {@link Neo4jDataAutoConfiguration}. Tests can't use the embedded driver as we - * use Lucene 4 and Neo4j still requires 3. + * Tests for {@link Neo4jDataAutoConfiguration}. Tests should not use the embedded driver + * as it requires the complete Neo4j-Kernel and server to function properly. * * @author Stephane Nicoll * @author Michael Hunger @@ -116,7 +118,6 @@ public class Neo4jDataAutoConfigurationTests { assertThat(context) .hasSingleBean(org.neo4j.ogm.config.Configuration.class); }); - } @Test @@ -144,6 +145,50 @@ public class Neo4jDataAutoConfigurationTests { .doesNotHaveBean(OpenSessionInViewInterceptor.class)); } + @Test + public void shouldBeAbleToUseNativeTypesWithBolt() { + this.contextRunner + .withPropertyValues("spring.data.neo4j.uri=bolt://localhost:7687", + "spring.data.neo4j.use-native-types:true") + .withConfiguration(AutoConfigurations.of(Neo4jDataAutoConfiguration.class, + TransactionAutoConfiguration.class)) + .run((context) -> assertThat(context) + .getBean(org.neo4j.ogm.config.Configuration.class) + .hasFieldOrPropertyWithValue("useNativeTypes", true)); + } + + @Test + public void shouldDealWithNativeTypesNotAvailableException() { + assertThatIllegalStateException().isThrownBy(() -> { + this.contextRunner + .withClassLoader( + new FilteredClassLoader("org.neo4j.ogm.drivers.bolt.types")) + .withPropertyValues("spring.data.neo4j.uri=bolt://localhost:7687", + "spring.data.neo4j.use-native-types:true") + .withConfiguration( + AutoConfigurations.of(Neo4jDataAutoConfiguration.class, + TransactionAutoConfiguration.class)) + .run((context) -> context.getBean(SessionFactory.class)); + }).withRootCauseInstanceOf(InvalidConfigurationPropertyValueException.class) + .withStackTraceContaining( + "The native type module for your Neo4j-OGM driver is not available. Please add the following dependency to your build:"); + } + + @Test + public void shouldDealWithNativeTypesNotSupportedException() { + assertThatIllegalStateException().isThrownBy(() -> { + this.contextRunner + .withPropertyValues("spring.data.neo4j.uri=http://localhost:7474", + "spring.data.neo4j.use-native-types:true") + .withConfiguration( + AutoConfigurations.of(Neo4jDataAutoConfiguration.class, + TransactionAutoConfiguration.class)) + .run((context) -> context.getBean(SessionFactory.class)); + }).withRootCauseInstanceOf(InvalidConfigurationPropertyValueException.class) + .withStackTraceContaining( + " The configured Neo4j-OGM driver org.neo4j.ogm.drivers.http.driver.HttpDriver does not support Neo4j native types."); + } + @Test public void eventListenersAreAutoRegistered() { this.contextRunner.withUserConfiguration(EventListenerConfiguration.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jPropertiesTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jPropertiesTests.java index 827b574194..2fbb627739 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jPropertiesTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/neo4j/Neo4jPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link Neo4jProperties}. * * @author Stephane Nicoll + * @author Michael Simons */ public class Neo4jPropertiesTests { @@ -147,6 +148,21 @@ public class Neo4jPropertiesTests { "file:relative/path/to/my.db"); } + @Test + public void nativeTypesAreSetToFalseByDefault() { + Neo4jProperties properties = load(true); + Configuration configuration = properties.createConfiguration(); + assertThat(configuration.getUseNativeTypes()).isFalse(); + } + + @Test + public void nativeTypesCanBeConfigured() { + Neo4jProperties properties = load(true, + "spring.data.neo4j.use-native-types=true"); + Configuration configuration = properties.createConfiguration(); + assertThat(configuration.getUseNativeTypes()).isTrue(); + } + private static void assertDriver(Configuration actual, String driver, String uri) { assertThat(actual).isNotNull(); assertThat(actual.getDriverClassName()).isEqualTo(driver); diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index 450b917e2c..7caf4a42aa 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -2527,6 +2527,11 @@ neo4j-ogm-bolt-driver ${neo4j-ogm.version} + + org.neo4j + neo4j-ogm-bolt-native-types + ${neo4j-ogm.version} + org.neo4j neo4j-ogm-core @@ -2537,6 +2542,11 @@ neo4j-ogm-embedded-driver ${neo4j-ogm.version} + + org.neo4j + neo4j-ogm-embedded-native-types + ${neo4j-ogm.version} + org.neo4j neo4j-ogm-http-driver diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 4aaf614cd6..d36069ae00 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -4440,6 +4440,25 @@ in your configuration, e.g. `spring.data.neo4j.uri=file://var/tmp/graph.db`. +[[boot-features-neo4j-ogm-native-types]] +==== Using native types +Neo4j-OGM can map some types, like `java.time.*` to `String` based properties or +use one of the various native types that Neo4j provides. +For backwards compatibility reasons the default for Neo4j-OGM is using a `String` based representation. +To change that behaviour, please add one of `org.neo4j:neo4j-ogm-bolt-native-types` or `org.neo4j:neo4j-ogm-embedded-native-types` +to the dependencies of your application +and use the following property in your Spring Boot configuration: + +[source,properties,indent=0] +---- + spring.data.neo4j.use-native-types=true +---- + +Please refer to the Neo4j-OGM reference for more information about +https://neo4j.com/docs/ogm-manual/current/reference/#reference:native-property-types[native property types]. + + + [[boot-features-neo4j-ogm-session]] ==== Neo4jSession By default, if you are running a web application, the session is bound to the thread for