From 5ab8facba385bca3678ea9f42141c1bae3f3a723 Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Wed, 27 Mar 2019 18:27:52 +0000 Subject: [PATCH] Decouple schema and mapping context. --- .../MappingContextBasedScannerImpl.java | 116 ++++++++++++++++++ .../core/mapping/Neo4jMappingContext.java | 88 ------------- .../data/neo4j/core/schema/Scanner.java | 38 ++++++ .../data/neo4j/core/schema/Schema.java | 9 +- ...> MappingContextBasedScannerImplTest.java} | 6 +- 5 files changed, 159 insertions(+), 98 deletions(-) create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImpl.java create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Scanner.java rename spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/{Neo4jMappingContextTest.java => MappingContextBasedScannerImplTest.java} (93%) diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImpl.java new file mode 100644 index 000000000..0f11cfe20 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImpl.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core.mapping; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.SimpleAssociationHandler; +import org.springframework.data.mapping.SimplePropertyHandler; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.IdDescription; +import org.springframework.data.neo4j.core.schema.NodeDescription; +import org.springframework.data.neo4j.core.schema.PropertyDescription; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.schema.RelationshipDescription; +import org.springframework.data.neo4j.core.schema.Scanner; +import org.springframework.data.neo4j.core.schema.Schema; + +/** + * @author Michael J. Simons + */ +@API(status = API.Status.INTERNAL, since = "1.0") +public class MappingContextBasedScannerImpl implements Scanner { + + private final Neo4jMappingContext neo4jMappingContext; + + public MappingContextBasedScannerImpl(Neo4jMappingContext neo4jMappingContext) { + this.neo4jMappingContext = neo4jMappingContext; + } + + @Override + public Schema scan() { + + final Schema schema = new Schema(); + neo4jMappingContext.getPersistentEntities().forEach(m -> + schema.registerNodeDescription(describeAsNode(m)) + ); + return schema; + } + + private NodeDescription describeAsNode(Neo4jPersistentEntity entity) { + + List properties = new ArrayList<>(); + + // TODO break this up into separate methods. + entity.doWithProperties(new SimplePropertyHandler() { + @Override + public void doWithPersistentProperty(PersistentProperty persistentProperty) { + org.springframework.data.neo4j.core.schema.Property propertyAnnotation = + persistentProperty.findAnnotation(org.springframework.data.neo4j.core.schema.Property.class); + + String propertyName = persistentProperty.getName(); + if (propertyAnnotation != null && !propertyAnnotation.name().isEmpty() + && propertyAnnotation.name().trim().length() != 0) { + propertyName = propertyAnnotation.name().trim(); + } + + properties.add(new PropertyDescription(persistentProperty.getName(), propertyName)); + } + }); + + List relationships = new ArrayList<>(); + entity.doWithAssociations(new SimpleAssociationHandler() { + @Override + public void doWithAssociation(Association> association) { + + Neo4jPersistentEntity obverseOwner = neo4jMappingContext + .getPersistentEntity(association.getInverse().getAssociationTargetType()); + + Relationship outgoingRelationship = association.getInverse().findAnnotation(Relationship.class); + String type; + if (outgoingRelationship != null && outgoingRelationship.type() != null) { + type = outgoingRelationship.type(); + } else { + type = association.getInverse().getName(); + } + relationships.add(new RelationshipDescription(type, obverseOwner.getPrimaryLabel())); + } + }); + + final Neo4jPersistentProperty idProperty = entity.getRequiredIdProperty(); + final Optional optionalIdAnnotation = Optional + .ofNullable(AnnotatedElementUtils.findMergedAnnotation(idProperty.getField(), Id.class)); + final IdDescription idDescription = optionalIdAnnotation + .map(idAnnotation -> new IdDescription(idAnnotation.strategy(), idAnnotation.generator())) + .orElseGet(() -> new IdDescription()); + + return NodeDescription.builder() + .primaryLabel(entity.getPrimaryLabel()) + .idDescription(idDescription) + .properties(properties) + .relationships(relationships) + .build(); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java index 0fff776cb..6d8f0ef1a 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java @@ -18,25 +18,9 @@ */ package org.springframework.data.neo4j.core.mapping; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.data.mapping.Association; -import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.SimpleAssociationHandler; -import org.springframework.data.mapping.SimplePropertyHandler; import org.springframework.data.mapping.context.AbstractMappingContext; import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; -import org.springframework.data.neo4j.core.schema.Id; -import org.springframework.data.neo4j.core.schema.IdDescription; -import org.springframework.data.neo4j.core.schema.NodeDescription; -import org.springframework.data.neo4j.core.schema.PropertyDescription; -import org.springframework.data.neo4j.core.schema.Relationship; -import org.springframework.data.neo4j.core.schema.RelationshipDescription; -import org.springframework.data.neo4j.core.schema.Schema; import org.springframework.data.util.TypeInformation; /** @@ -46,8 +30,6 @@ import org.springframework.data.util.TypeInformation; */ public class Neo4jMappingContext extends AbstractMappingContext, Neo4jPersistentProperty> { - private final Schema schema = new Schema(); - /* * (non-Javadoc) * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation) @@ -68,74 +50,4 @@ public class Neo4jMappingContext extends AbstractMappingContext - schema.registerNodeDescription(describeAsNode(m)) - ); - } - - public Schema getSchema() { - return schema; - } - - private NodeDescription describeAsNode(Neo4jPersistentEntity entity) { - - List properties = new ArrayList<>(); - - // TODO break this up into separate methods. - entity.doWithProperties(new SimplePropertyHandler() { - @Override - public void doWithPersistentProperty(PersistentProperty persistentProperty) { - org.springframework.data.neo4j.core.schema.Property propertyAnnotation = - persistentProperty.findAnnotation(org.springframework.data.neo4j.core.schema.Property.class); - - String propertyName = persistentProperty.getName(); - if (propertyAnnotation != null && !propertyAnnotation.name().isEmpty() - && propertyAnnotation.name().trim().length() != 0) { - propertyName = propertyAnnotation.name().trim(); - } - - properties.add(new PropertyDescription(persistentProperty.getName(), propertyName)); - } - }); - - List relationships = new ArrayList<>(); - entity.doWithAssociations(new SimpleAssociationHandler() { - @Override - public void doWithAssociation(Association> association) { - - Neo4jPersistentEntity obverseOwner = Neo4jMappingContext.this - .getPersistentEntity(association.getInverse().getAssociationTargetType()); - - Relationship outgoingRelationship = association.getInverse().findAnnotation(Relationship.class); - String type; - if (outgoingRelationship != null && outgoingRelationship.type() != null) { - type = outgoingRelationship.type(); - } else { - type = association.getInverse().getName(); - } - relationships.add(new RelationshipDescription(type, obverseOwner.getPrimaryLabel())); - } - }); - - final Neo4jPersistentProperty idProperty = entity.getRequiredIdProperty(); - final Optional optionalIdAnnotation = Optional - .ofNullable(AnnotatedElementUtils.findMergedAnnotation(idProperty.getField(), Id.class)); - final IdDescription idDescription = optionalIdAnnotation - .map(idAnnotation -> new IdDescription(idAnnotation.strategy(), idAnnotation.generator())) - .orElseGet(() -> new IdDescription()); - - return NodeDescription.builder() - .primaryLabel(entity.getPrimaryLabel()) - .idDescription(idDescription) - .properties(properties) - .relationships(relationships) - .build(); - } - - } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Scanner.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Scanner.java new file mode 100644 index 000000000..c21a1aecb --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Scanner.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core.schema; + +import org.apiguardian.api.API; + +/** + * A scanner produces an instance of schema. The default scanner is based on all the information we can get from + * a Spring Mapping context (in our case, the {@link org.springframework.data.neo4j.core.mapping.Neo4jMappingContext}. + * + * @author Michael J. Simons + */ +@API(status = API.Status.STABLE, since = "1.0") +public interface Scanner { + + /** + * Scans the relevant classes and creates a schema. + * + * @return The new schema. + */ + Schema scan(); +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Schema.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Schema.java index 2de859b2e..c87fa0863 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Schema.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Schema.java @@ -22,8 +22,6 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apiguardian.api.API; @@ -32,16 +30,11 @@ import org.apiguardian.api.API; * * The schema is currently designed to be mutual. */ -@API(status = API.Status.INTERNAL, since = "1.0") +@API(status = API.Status.STABLE, since = "1.0") public final class Schema { private final Map nodeDescriptionsByPrimaryLabel = new HashMap<>(); - // Just added as a reminder. Add adaquate locks - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private final Lock read = lock.readLock(); - private final Lock write = lock.writeLock(); - /** * Registers a node description under it's primary label. * diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImplTest.java similarity index 93% rename from spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java rename to spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImplTest.java index ea9993e4b..017f53111 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImplTest.java @@ -33,12 +33,13 @@ import org.springframework.data.neo4j.core.schema.Property; import org.springframework.data.neo4j.core.schema.PropertyDescription; import org.springframework.data.neo4j.core.schema.Relationship; import org.springframework.data.neo4j.core.schema.RelationshipDescription; +import org.springframework.data.neo4j.core.schema.Scanner; import org.springframework.data.neo4j.core.schema.Schema; /** * @author Michael J. Simons */ -class Neo4jMappingContextTest { +class MappingContextBasedScannerImplTest { @Test void initializationOfSchemaShouldWork() { @@ -47,7 +48,8 @@ class Neo4jMappingContextTest { neo4jMappingContext.setInitialEntitySet(new HashSet<>(Arrays.asList(BikeNode.class, UserNode.class))); neo4jMappingContext.initialize(); - Schema schema = neo4jMappingContext.getSchema(); + final Scanner scanner = new MappingContextBasedScannerImpl(neo4jMappingContext); + Schema schema = scanner.scan(); Optional optionalUserNodeDescription = schema.getNodeDescription("User"); assertThat(optionalUserNodeDescription)