Decouple schema and mapping context.

This commit is contained in:
Michael Simons
2019-03-27 18:27:52 +00:00
parent d310d83279
commit 5ab8facba3
5 changed files with 159 additions and 98 deletions

View File

@@ -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<PropertyDescription> 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<RelationshipDescription> relationships = new ArrayList<>();
entity.doWithAssociations(new SimpleAssociationHandler() {
@Override
public void doWithAssociation(Association<? extends PersistentProperty<?>> 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<Id> 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();
}
}

View File

@@ -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<Neo4jPersistentEntity<?>, 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<Neo4jPersistentE
return new Neo4jPersistentPropertyImpl(property, neo4jPersistentProperties, simpleTypeHolder);
}
@Override
public void initialize() {
super.initialize();
super.getPersistentEntities().forEach(m ->
schema.registerNodeDescription(describeAsNode(m))
);
}
public Schema getSchema() {
return schema;
}
private NodeDescription describeAsNode(Neo4jPersistentEntity<?> entity) {
List<PropertyDescription> 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<RelationshipDescription> relationships = new ArrayList<>();
entity.doWithAssociations(new SimpleAssociationHandler() {
@Override
public void doWithAssociation(Association<? extends PersistentProperty<?>> 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<Id> 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();
}
}

View File

@@ -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();
}

View File

@@ -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<String, NodeDescription> 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.
*

View File

@@ -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<NodeDescription> optionalUserNodeDescription = schema.getNodeDescription("User");
assertThat(optionalUserNodeDescription)