From af21b847a0faa3f3e57a74e48a7c5b71f1d234d4 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Mon, 17 Dec 2018 19:41:52 +0100 Subject: [PATCH] Add opt-in support for Neo4j-OGM native types This includes tests for the autoconfiguration using that new property. The test require the native types for Bolt and embedded in the test scope, so the Neo4j-OGM native types have been added to managed dependencies. The enhanced autoconfiguration throws an InvalidConfigurationPropertyValueException when native types cannot be used due to missing dependencies or wrong transport mode. See gh-15637 --- .../spring-boot-autoconfigure/pom.xml | 7 ++- .../neo4j/Neo4jDataAutoConfiguration.java | 50 +++++++++++++++-- .../data/neo4j/Neo4jProperties.java | 24 ++++++++- .../Neo4jDataAutoConfigurationTests.java | 53 +++++++++++++++++-- .../data/neo4j/Neo4jPropertiesTests.java | 18 ++++++- .../spring-boot-dependencies/pom.xml | 10 ++++ .../main/asciidoc/spring-boot-features.adoc | 19 +++++++ 7 files changed, 168 insertions(+), 13 deletions(-) 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