/*
* Copyright (c) [2011-2017] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
*
*/
package org.springframework.data.neo4j.mapping;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.neo4j.ogm.annotation.EndNode;
import org.neo4j.ogm.annotation.Relationship;
import org.neo4j.ogm.annotation.StartNode;
import org.neo4j.ogm.exception.MappingException;
import org.neo4j.ogm.metadata.ClassInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.neo4j.annotation.QueryResult;
/**
* This class implements Spring Data's PersistentProperty interface, scavenging the required data from the
* OGM's mapping classes in order to for SDN to play nicely with Spring Data REST.
* The main thing to note is that this class is effectively a shim for FieldInfo. We don't reload
* all the mapping information again.
* We do not yet support getter/setter access to entity properties.
* <p>
* These attributes do not appear to be used/needed for SDN 4 to inter-operate correctly with SD-REST:
* </p>
* <ul>
* <li>mapValueType</li>
* <li>typeInformation</li>
* <li>isVersionProperty (there is no SDN versioning at this stage)</li>
* <li>isTransient (we never supply transient classes to the Spring mapping context)</li>
* <li>isWritable (we don't currently support read-only fields)</li>
* </ul>
* Consequently their associated getter methods always return default values of null or [true|false]
* However, because these method calls are not expected, we also log a warning message if they get invoked
*
* @author Vince Bickers
* @author Adam George
* @author Luanne Misquitta
* @author Mark Angrish
* @author Mark Paluch
* @since 4.0.0
*/
public class Neo4jPersistentProperty extends AnnotationBasedPersistentProperty<Neo4jPersistentProperty> {
private static final Logger logger = LoggerFactory.getLogger(Neo4jPersistentProperty.class);
private final boolean isIdProperty;
/**
* Constructs a new {@link Neo4jPersistentProperty} based on the given arguments.
*
* @param owningClassInfo The {@link ClassInfo} of the object of which the property field is a member
* @param property The property
* @param owner The owning {@link PersistentEntity} that corresponds to the given {@code ClassInfo}
* @param simpleTypeHolder The {@link SimpleTypeHolder} that dictates whether the type of this property is considered simple
* or not
*/
public Neo4jPersistentProperty(ClassInfo owningClassInfo, Property property,
PersistentEntity<?, Neo4jPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
super(property, owner, simpleTypeHolder);
if (owningClassInfo == null) {
logger.warn("Owning ClassInfo is null for property: {}", property);
}
if ((owningClassInfo !=null && owningClassInfo.getUnderlyingClass()!=null && simpleTypeHolder.isSimpleType(owningClassInfo.getUnderlyingClass()))
|| owner.getType().isEnum()) { //TODO refactor all these null checks
this.isIdProperty = false;
}
else {
this.isIdProperty = resolveWhetherIdProperty(owningClassInfo, property);
}
}
private static boolean resolveWhetherIdProperty(ClassInfo owningClassInfo, Property property) {
if (owningClassInfo == null || owningClassInfo.isInterface() || owningClassInfo.annotationsInfo().get(QueryResult.class.getName()) != null || owningClassInfo.isEnum()) {
// no ID properties on @QueryResult or non-concrete objects
return false;
} else {
try {
return property.getField() //
.filter(field ->owningClassInfo.getField(owningClassInfo.identityField()).equals(field)) //
.isPresent();}
catch (MappingException noIdentityField) {
logger.warn("No identity field found for class of type: {} when creating persistent property for : {}",
owningClassInfo.name(), property);
return false;
}
}
}
@Override
public boolean isIdProperty() {
logger.debug("[property].isIdProperty() returns {}", this.isIdProperty);
return this.isIdProperty;
}
@Override
public boolean isVersionProperty() {
logger.debug("[property].isVersionProperty() returns false"); // by design
return false;
}
/**
* Overridden to force field access as opposed to getter method access for simplicity.
*
* @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#usePropertyAccess()
*/
@Override
public boolean usePropertyAccess() {
logger.debug("[property].usePropertyAccess() returns false");
return false;
}
/**
* Determines whether or not this property should be considered an association to another entity or whether it's
* just a simple property that should be shown as a value.
* <p>
* This implementation works by looking for non-transient members annotated with <code>@Relationship</code>.
* </p>
*
* @return <code>true</code> if this property is an association to another entity, <code>false</code> if not
*/
@Override
public boolean isAssociation() {
return !isTransient() &&
(isAnnotationPresent(Relationship.class)
|| isAnnotationPresent(StartNode.class)
|| isAnnotationPresent(EndNode.class));
}
@Override
protected Association<Neo4jPersistentProperty> createAssociation() {
return new Association<Neo4jPersistentProperty>(this, null);
}
}