DATAGRAPH-311: Initial progression towards a Serialization option for query results (and entities). Note: This relies on a change to Spring Data Commons as well ref PR https://github.com/SpringSource/spring-data-commons/pull/35
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -33,7 +33,7 @@
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-neo4j</dist.id>
|
||||
|
||||
<springdata.commons>1.6.0.RC1</springdata.commons>
|
||||
<springdata.commons>1.6.0.BUILD-SNAPSHOT</springdata.commons>
|
||||
<neo4j.version>1.9</neo4j.version>
|
||||
|
||||
<neo4j.spatial.version>0.11-neo4j-1.9</neo4j.spatial.version>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright 2011 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.
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark a POJO as being able to hold the results of a
|
||||
* SDN based query.
|
||||
*
|
||||
* @author Nicki Watt
|
||||
* @since 06.08.2013
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
public @interface POJOResult {
|
||||
String value() default "";
|
||||
}
|
||||
@@ -23,20 +23,28 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.DoReturn;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class provides a mechanism for managing and controlling access to
|
||||
* a Set based field on a SDN managed entity. The associated field typically
|
||||
* serves as a container for all the references to some other SDN entity(s).
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public class ManagedFieldAccessorSet<T> extends AbstractSet<T> {
|
||||
public class ManagedFieldAccessorSet<T> extends AbstractSet<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object entity;
|
||||
final Set<T> delegate;
|
||||
private final Neo4jPersistentProperty property;
|
||||
private final Neo4jTemplate ctx;
|
||||
private final FieldAccessor fieldAccessor;
|
||||
private final transient Neo4jPersistentProperty property;
|
||||
private final transient Neo4jTemplate ctx;
|
||||
private final transient FieldAccessor fieldAccessor;
|
||||
private final MappingPolicy mappingPolicy;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -29,10 +29,13 @@ import java.util.Map;
|
||||
* deleted.
|
||||
*/
|
||||
public class ManagedPrefixedDynamicProperties extends PrefixedDynamicProperties {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object entity;
|
||||
private final Neo4jTemplate template;
|
||||
private final FieldAccessor fieldAccessor;
|
||||
private final Neo4jPersistentProperty property;
|
||||
private transient final Neo4jTemplate template;
|
||||
private transient final FieldAccessor fieldAccessor;
|
||||
private transient final Neo4jPersistentProperty property;
|
||||
private boolean isNode;
|
||||
private MappingPolicy mappingPolicy;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.fieldaccess;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
@@ -27,7 +28,10 @@ import java.util.Set;
|
||||
* <p>
|
||||
* The methods *PrefixedProperty() allow to access the prefixed property key/values pairs directly.
|
||||
*/
|
||||
public class PrefixedDynamicProperties implements DynamicProperties {
|
||||
public class PrefixedDynamicProperties implements DynamicProperties , Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Map<String, Object> map;
|
||||
protected final String prefix;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.mapping;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
@@ -32,7 +33,10 @@ public interface MappingPolicy {
|
||||
boolean shouldLoad();
|
||||
MappingPolicy combineWith(MappingPolicy mappingPolicy);
|
||||
|
||||
public class DefaultMappingPolicy implements MappingPolicy {
|
||||
public class DefaultMappingPolicy implements MappingPolicy , Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Set<Option> options;
|
||||
|
||||
public DefaultMappingPolicy(Option... options) {
|
||||
|
||||
@@ -16,19 +16,24 @@
|
||||
|
||||
package org.springframework.data.neo4j.support.conversion;
|
||||
|
||||
import org.neo4j.helpers.collection.IteratorUtil;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.neo4j.annotation.MapResult;
|
||||
import org.springframework.data.neo4j.annotation.POJOResult;
|
||||
import org.springframework.data.neo4j.annotation.ResultColumn;
|
||||
import org.springframework.data.neo4j.conversion.DefaultConverter;
|
||||
import org.springframework.data.neo4j.conversion.ResultConverter;
|
||||
import org.springframework.data.neo4j.conversion.QueryResultBuilder;
|
||||
import org.springframework.data.neo4j.core.EntityPath;
|
||||
import org.springframework.data.neo4j.mapping.EntityPersister;
|
||||
import org.springframework.data.neo4j.mapping.MappingPolicy;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplateAware;
|
||||
import org.springframework.data.neo4j.support.path.ConvertingEntityPath;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -80,6 +85,65 @@ public class EntityResultConverter<T, R> extends DefaultConverter<T, R> implemen
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public R extractPOJOResult(Object value, Class returnType, MappingPolicy mappingPolicy) {
|
||||
String errorMessage = "Error extracting and setting value for POJO Result : " + returnType;
|
||||
if (!Map.class.isAssignableFrom(value.getClass())) {
|
||||
throw new RuntimeException("POJOResult can only be extracted from Map<String,Object>.");
|
||||
}
|
||||
|
||||
Object newThing = null;
|
||||
ResultColumnValueExtractor resultColumnValueExtractor = new ResultColumnValueExtractor((Map<String, Object>) value,mappingPolicy,this);
|
||||
try {
|
||||
newThing = returnType.newInstance();
|
||||
BeanWrapper wrapper = new BeanWrapperImpl( newThing );
|
||||
for (Field field: returnType.getDeclaredFields()) {
|
||||
extractAndSetValueOfField(wrapper, field, resultColumnValueExtractor);
|
||||
}
|
||||
} catch (IllegalAccessException e1) {
|
||||
throw new POJOResultBuildingException(errorMessage, e1);
|
||||
} catch (InstantiationException e2) {
|
||||
throw new POJOResultBuildingException(errorMessage, e2);
|
||||
} catch (InvocationTargetException e3) {
|
||||
throw new POJOResultBuildingException(errorMessage, e3);
|
||||
} catch (NoSuchMethodException e4) {
|
||||
throw new POJOResultBuildingException(errorMessage, e4);
|
||||
} catch (ClassNotFoundException e5) {
|
||||
throw new POJOResultBuildingException(errorMessage, e5);
|
||||
}
|
||||
|
||||
return (R) newThing;
|
||||
}
|
||||
|
||||
private void extractAndSetValueOfField(BeanWrapper wrapper, Field field,
|
||||
ResultColumnValueExtractor resultColumnValueExtractor)
|
||||
throws InvocationTargetException , NoSuchMethodException , ClassNotFoundException , IllegalAccessException{
|
||||
if (!isPOJOMappableField(field))
|
||||
return;
|
||||
|
||||
Object val = resultColumnValueExtractor.extractFromField(field);
|
||||
if (val != null) {
|
||||
if (val.getClass().getEnclosingClass() != null &&
|
||||
val.getClass().getEnclosingClass().equals(QueryResultBuilder.class)) {
|
||||
val = IteratorUtil.asCollection((Iterable) val);
|
||||
}
|
||||
wrapper.setPropertyValue( field.getName(), val );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* At present, the only fields which can be mapped to a POJO are those
|
||||
* annotated with the ResultColumn annotation
|
||||
*
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
private boolean isPOJOMappableField(Field field) {
|
||||
return field.getAnnotation(ResultColumn.class) != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public R extractMapResult(Object value, Class returnType, MappingPolicy mappingPolicy) {
|
||||
if (!Map.class.isAssignableFrom(value.getClass())) {
|
||||
@@ -95,6 +159,8 @@ public class EntityResultConverter<T, R> extends DefaultConverter<T, R> implemen
|
||||
public R convert(Object value, Class type, MappingPolicy mappingPolicy) {
|
||||
if (type.isAnnotationPresent(MapResult.class)) {
|
||||
return extractMapResult(value, type,mappingPolicy);
|
||||
} else if (type.isAnnotationPresent(POJOResult.class)) {
|
||||
return extractPOJOResult(value, type,mappingPolicy);
|
||||
} else
|
||||
return super.convert(value, type,mappingPolicy);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright 2011 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.
|
||||
* 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.support.conversion;
|
||||
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
|
||||
/**
|
||||
* Exception which occurs whilst trying to build and map a POJO
|
||||
* from a Query result.
|
||||
*
|
||||
* @author Nicki Watt
|
||||
* @since 06.08.2013
|
||||
*/
|
||||
public class POJOResultBuildingException extends MappingException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public POJOResultBuildingException(String message, Throwable t) {
|
||||
super( message , t );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,15 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.support.conversion;
|
||||
|
||||
import org.springframework.data.neo4j.annotation.ResultColumn;
|
||||
import org.springframework.data.neo4j.conversion.QueryResultBuilder;
|
||||
import org.springframework.data.neo4j.conversion.ResultConverter;
|
||||
import org.springframework.data.neo4j.mapping.MappingPolicy;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Map;
|
||||
@@ -36,11 +31,13 @@ public class QueryResultProxy implements InvocationHandler {
|
||||
private final Map<String, Object> map;
|
||||
private final MappingPolicy mappingPolicy;
|
||||
private final ResultConverter converter;
|
||||
private final ResultColumnValueExtractor resultColumnValueExtractor;
|
||||
|
||||
public QueryResultProxy(Map<String, Object> map, MappingPolicy mappingPolicy, ResultConverter converter) {
|
||||
this.map = map;
|
||||
this.mappingPolicy = mappingPolicy;
|
||||
this.converter = converter;
|
||||
this.resultColumnValueExtractor = new ResultColumnValueExtractor(map,mappingPolicy,converter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -51,57 +48,13 @@ public class QueryResultProxy implements InvocationHandler {
|
||||
}
|
||||
|
||||
if (method.getName().equals("hashCode") && (params==null || params.length == 0)) {
|
||||
return map.hashCode();
|
||||
return map.hashCode();
|
||||
}
|
||||
|
||||
ResultColumn column = method.getAnnotation(ResultColumn.class);
|
||||
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
|
||||
return resultColumnValueExtractor.extractFromMethod(method);
|
||||
|
||||
String columnName = column.value();
|
||||
if(!map.containsKey( columnName )) {
|
||||
throw new NoSuchColumnFoundException( columnName );
|
||||
}
|
||||
|
||||
Object columnValue = map.get( columnName );
|
||||
if(columnValue==null) return null;
|
||||
|
||||
// If the returned value is a Scala iterable, transform it to a Java iterable first
|
||||
Class iterableLikeInterface = implementsInterface("scala.collection.Iterable", columnValue.getClass());
|
||||
if (iterableLikeInterface!=null) {
|
||||
columnValue = transformScalaIterableToJavaIterable(columnValue, iterableLikeInterface);
|
||||
}
|
||||
|
||||
if (returnType.isCollectionLike())
|
||||
return new QueryResultBuilder((Iterable)columnValue, converter).to(returnType.getActualType().getType());
|
||||
else
|
||||
return converter.convert(columnValue, returnType.getType(), mappingPolicy);
|
||||
}
|
||||
public Object transformScalaIterableToJavaIterable(Object scalaIterable, Class iterableLikeIface) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
// This is equivalent to doing this:
|
||||
// JavaConversions.asJavaIterable(((IterableLike) columnValue).toIterable());
|
||||
|
||||
Class<?> javaConversions = iterableLikeIface.getClassLoader().loadClass("scala.collection.JavaConversions");
|
||||
Method asJavaIterable = javaConversions.getMethod("asJavaIterable", iterableLikeIface);
|
||||
Iterable<?> javaIterable = (Iterable<?>) asJavaIterable.invoke(null, scalaIterable);
|
||||
return javaIterable;
|
||||
}
|
||||
private Class implementsInterface(String interfaceName, Class clazz) {
|
||||
if(clazz.getCanonicalName().equals(interfaceName)) return clazz;
|
||||
|
||||
Class superclass = clazz.getSuperclass();
|
||||
if(superclass != null) {
|
||||
Class iface = implementsInterface(interfaceName, superclass);
|
||||
if (iface!= null) return iface;
|
||||
}
|
||||
|
||||
for(Class iface : clazz.getInterfaces()) {
|
||||
Class superIface = implementsInterface(interfaceName, iface);
|
||||
if(superIface!=null)
|
||||
return superIface;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean equalsInternal(Object me, Object other) {
|
||||
if (other == null) {
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.springframework.data.neo4j.support.conversion;
|
||||
|
||||
import org.springframework.data.neo4j.annotation.ResultColumn;
|
||||
import org.springframework.data.neo4j.conversion.QueryResultBuilder;
|
||||
import org.springframework.data.neo4j.conversion.ResultConverter;
|
||||
import org.springframework.data.neo4j.mapping.MappingPolicy;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Given a method or field annotated with the @ResultColumn, this class will
|
||||
* extract and return the associated value from the underlying query result.
|
||||
* Note: Quite a lot of the code originated from QueryResultProxy and
|
||||
* was moved into here.
|
||||
*
|
||||
* @author Nicki Watt
|
||||
* @since 06.08.2013
|
||||
*/
|
||||
public class ResultColumnValueExtractor {
|
||||
|
||||
private final Map<String, Object> map;
|
||||
private final MappingPolicy mappingPolicy;
|
||||
private final ResultConverter converter;
|
||||
|
||||
public ResultColumnValueExtractor(Map<String, Object> map, MappingPolicy mappingPolicy, ResultConverter converter) {
|
||||
this.map = map;
|
||||
this.mappingPolicy = mappingPolicy;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
public Object extractFromField(Field field) throws ClassNotFoundException,
|
||||
NoSuchMethodException,
|
||||
IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
ResultColumn column = field.getAnnotation(ResultColumn.class);
|
||||
TypeInformation<?> returnType = ClassTypeInformation.fromTypeOf(field);
|
||||
return extractFromAccessibleObject(column,returnType);
|
||||
}
|
||||
|
||||
public Object extractFromMethod(Method method) throws ClassNotFoundException,
|
||||
NoSuchMethodException,
|
||||
IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
ResultColumn column = method.getAnnotation(ResultColumn.class);
|
||||
TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
|
||||
return extractFromAccessibleObject(column,returnType);
|
||||
}
|
||||
|
||||
public Object extractFromAccessibleObject(ResultColumn column, TypeInformation<?> returnType)
|
||||
throws ClassNotFoundException,
|
||||
NoSuchMethodException,
|
||||
IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
|
||||
String columnName = column.value();
|
||||
if(!map.containsKey( columnName )) {
|
||||
throw new NoSuchColumnFoundException( columnName );
|
||||
}
|
||||
|
||||
Object columnValue = map.get(columnName);
|
||||
if(columnValue==null) return null;
|
||||
|
||||
// If the returned value is a Scala iterable, transform it to a Java iterable first
|
||||
Class iterableLikeInterface = implementsInterface("scala.collection.Iterable", columnValue.getClass());
|
||||
if (iterableLikeInterface!=null) {
|
||||
columnValue = transformScalaIterableToJavaIterable(columnValue, iterableLikeInterface);
|
||||
}
|
||||
|
||||
if (returnType.isCollectionLike()) {
|
||||
QueryResultBuilder qrb = new QueryResultBuilder((Iterable)columnValue, converter);
|
||||
return qrb.to(returnType.getActualType().getType());
|
||||
} else
|
||||
return converter.convert(columnValue, returnType.getType(), mappingPolicy);
|
||||
}
|
||||
|
||||
|
||||
public Object transformScalaIterableToJavaIterable(Object scalaIterable, Class iterableLikeIface) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
// This is equivalent to doing this:
|
||||
// JavaConversions.asJavaIterable(((IterableLike) columnValue).toIterable());
|
||||
|
||||
Class<?> javaConversions = iterableLikeIface.getClassLoader().loadClass("scala.collection.JavaConversions");
|
||||
Method asJavaIterable = javaConversions.getMethod("asJavaIterable", iterableLikeIface);
|
||||
Iterable<?> javaIterable = (Iterable<?>) asJavaIterable.invoke(null, scalaIterable);
|
||||
return javaIterable;
|
||||
}
|
||||
|
||||
private Class implementsInterface(String interfaceName, Class clazz) {
|
||||
if(clazz.getCanonicalName().equals(interfaceName)) return clazz;
|
||||
|
||||
Class superclass = clazz.getSuperclass();
|
||||
if(superclass != null) {
|
||||
Class iface = implementsInterface(interfaceName, superclass);
|
||||
if (iface!= null) return iface;
|
||||
}
|
||||
|
||||
for(Class iface : clazz.getInterfaces()) {
|
||||
Class superIface = implementsInterface(interfaceName, iface);
|
||||
if(superIface!=null)
|
||||
return superIface;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,10 +25,14 @@ import org.springframework.data.neo4j.annotation.RelationshipType;
|
||||
import org.springframework.data.neo4j.annotation.StartNode;
|
||||
import org.springframework.data.neo4j.fieldaccess.DynamicProperties;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@RelationshipEntity(type = "BEST_FRIEND")
|
||||
public class BestFriend {
|
||||
public class BestFriend implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@GraphId
|
||||
private Long id;
|
||||
|
||||
|
||||
@@ -20,10 +20,14 @@ package org.springframework.data.neo4j.model;
|
||||
import org.springframework.data.neo4j.annotation.*;
|
||||
import org.springframework.data.neo4j.fieldaccess.DynamicProperties;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@RelationshipEntity(useShortNames = false)
|
||||
public class Friendship {
|
||||
public class Friendship implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@GraphId
|
||||
private Long id;
|
||||
|
||||
@@ -56,7 +60,6 @@ public class Friendship {
|
||||
private Date firstMeetingDate;
|
||||
|
||||
private DynamicProperties personalProperties;
|
||||
|
||||
private transient String latestLocation;
|
||||
|
||||
public Friendship(Person start, Person end, String type) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.index.IndexType;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
@@ -43,7 +44,9 @@ import java.util.Set;
|
||||
|
||||
@NodeEntity
|
||||
@TypeAlias("g")
|
||||
public class Group implements IGroup {
|
||||
public class Group implements IGroup , Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public final static String OTHER_NAME_INDEX = "other_name";
|
||||
public static final String SEARCH_GROUPS_INDEX = "search_groups";
|
||||
@@ -59,7 +62,7 @@ public class Group implements IGroup {
|
||||
private Iterable<Person> readOnlyPersons;
|
||||
|
||||
@GraphTraversal(traversal = PeopleTraversalBuilder.class, params = "persons")
|
||||
private Iterable<Person> people;
|
||||
transient private Iterable<Person> people;
|
||||
|
||||
@GraphProperty
|
||||
@Indexed
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.data.neo4j.model;
|
||||
|
||||
import org.neo4j.graphdb.*;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.neo4j.annotation.*;
|
||||
import org.springframework.data.neo4j.fieldaccess.DynamicProperties;
|
||||
import org.springframework.data.neo4j.support.index.IndexType;
|
||||
@@ -24,11 +25,14 @@ import org.springframework.data.neo4j.support.index.IndexType;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
@NodeEntity
|
||||
public class Person implements Being {
|
||||
public class Person implements Being , Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String NAME_INDEX = "name-index";
|
||||
public static final org.neo4j.graphdb.RelationshipType KNOWS = DynamicRelationshipType.withName("knows");
|
||||
@@ -77,19 +81,23 @@ public class Person implements Being {
|
||||
|
||||
@Fetch
|
||||
@RelatedToVia(type = "knows", elementClass = Friendship.class)
|
||||
private Iterable<Friendship> friendships;
|
||||
private Iterable<Friendship> friendships;
|
||||
|
||||
@Query("start person=node({self}) match (person)<-[?:boss]-(boss) return boss")
|
||||
private Person bossByQuery;
|
||||
transient private Person bossByQuery;
|
||||
// NW - all queries should be transient
|
||||
|
||||
@Query("start person=node({self}) match (person)<-[?:boss]-(boss) return boss.name")
|
||||
private String bossName;
|
||||
transient private String bossName;
|
||||
// NW - all queries should be transient
|
||||
|
||||
@Query("start person=node({self}) match (person)<-[:persons]-(team)-[:persons]->(member) return member")
|
||||
private Iterable<Person> otherTeamMembers;
|
||||
transient private Iterable<Person> otherTeamMembers;
|
||||
// NW - all queries should be transient
|
||||
|
||||
@Query("start person=node({self}) match (person)<-[:persons]-(team)-[:persons]->(member) return member.name?, member.age?")
|
||||
private Iterable<Map<String,Object>> otherTeamMemberData;
|
||||
transient private Iterable<Map<String,Object>> otherTeamMemberData;
|
||||
// NW - all queries should be transient
|
||||
|
||||
@RelatedTo(elementClass = Group.class, type = "interface_test", direction = Direction.OUTGOING)
|
||||
private Set<IGroup> groups;
|
||||
|
||||
@@ -18,12 +18,17 @@ package org.springframework.data.neo4j.model;
|
||||
import org.springframework.data.neo4j.annotation.GraphId;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 23.12.11
|
||||
*/
|
||||
@NodeEntity
|
||||
public class RootEntity {
|
||||
public class RootEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@GraphId Long id;
|
||||
|
||||
String rootName;
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2011 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.
|
||||
* 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.repository;
|
||||
|
||||
import org.springframework.data.neo4j.annotation.POJOResult;
|
||||
import org.springframework.data.neo4j.annotation.ResultColumn;
|
||||
import org.springframework.data.neo4j.model.Group;
|
||||
import org.springframework.data.neo4j.model.Person;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@POJOResult
|
||||
public class MemberDataPOJO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ResultColumn("collect(team)")
|
||||
private Set<Group> teams;
|
||||
|
||||
@ResultColumn("boss")
|
||||
private Person boss;
|
||||
|
||||
@ResultColumn("someonesAge")
|
||||
private int anInt;
|
||||
|
||||
@ResultColumn("someonesName")
|
||||
private String aName;
|
||||
|
||||
public Set<Group> getTeams() {
|
||||
return teams;
|
||||
}
|
||||
|
||||
public void setTeams(Set<Group> teams) {
|
||||
this.teams = teams;
|
||||
}
|
||||
|
||||
public Person getBoss() {
|
||||
return boss;
|
||||
}
|
||||
|
||||
public void setBoss(Person boss) {
|
||||
this.boss = boss;
|
||||
}
|
||||
|
||||
public int getAnInt() {
|
||||
return anInt;
|
||||
}
|
||||
|
||||
public void setAnInt(int anInt) {
|
||||
this.anInt = anInt;
|
||||
}
|
||||
|
||||
public String getAName() {
|
||||
return aName;
|
||||
}
|
||||
|
||||
public void setAName(String name) {
|
||||
this.aName = name;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,9 @@ public interface PersonRepository extends GraphRepository<Person>, NamedIndexRep
|
||||
@Query("start member=node({p_person}) match team-[:persons]->member<-[?:boss]-boss return collect(team), boss")
|
||||
Iterable<MemberData> findMemberData(@Param("p_person") Person person);
|
||||
|
||||
@Query("start member=node({p_person}) match team-[:persons]->member<-[?:boss]-boss return collect(team), boss, boss.name as someonesName, boss.age as someonesAge ")
|
||||
MemberDataPOJO findMemberDataPojo(@Param("p_person") Person person);
|
||||
|
||||
@Query("start member=node({p_person}) match team-[:persons]->member<-[?:boss]-boss return member")
|
||||
Iterable<MemberData> nonWorkingQuery(@Param("p_person") Person person);
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright 2011 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.
|
||||
* 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.repository;
|
||||
|
||||
import org.neo4j.helpers.collection.MapUtil;
|
||||
import org.springframework.data.neo4j.fieldaccess.DynamicPropertiesContainer;
|
||||
import org.springframework.data.neo4j.model.Friendship;
|
||||
import org.springframework.data.neo4j.model.Group;
|
||||
import org.springframework.data.neo4j.model.Person;
|
||||
import org.springframework.data.neo4j.model.Personality;
|
||||
import org.springframework.data.neo4j.template.Neo4jOperations;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
* Creates a whole bunch of SDN entities for testing various aspects of the
|
||||
* Serialization process.
|
||||
*
|
||||
* @author Nicki Watt
|
||||
* @since 06.08.2013
|
||||
*/
|
||||
public class SerialTesters {
|
||||
|
||||
public SimpleDateFormat bdayFormatter;
|
||||
|
||||
public Person michael;
|
||||
public Person emil;
|
||||
public Person david;
|
||||
public Person tareq;
|
||||
public Person nicki;
|
||||
public Group serialTesterGroup;
|
||||
public Friendship friendShip;
|
||||
public Friendship friendShip2;
|
||||
public Friendship friendShip3;
|
||||
|
||||
public SerialTesters createUpgraderTeam(GraphRepository<Person> repo, GraphRepository<Group> groupRepo, GraphRepository<Friendship> friendshipRepository)
|
||||
{
|
||||
emil = new Person("Emil", 30);
|
||||
|
||||
michael = new Person("Michael", 36);
|
||||
michael.setBoss(emil);
|
||||
michael.setPersonality(Personality.EXTROVERT);
|
||||
michael.setLocation( "POINT(16 56)" );
|
||||
|
||||
david = new Person("David", 25);
|
||||
david.setBoss(emil);
|
||||
david.setLocation( 16.5, 56.5 );
|
||||
|
||||
tareq = new Person("Tareq", 36);
|
||||
nicki = new Person("Nicki", 36);
|
||||
bdayFormatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
|
||||
try {
|
||||
nicki.setBirthdate(bdayFormatter.parse("01 JAN 2013 00:00:00"));
|
||||
} catch (ParseException e) {
|
||||
throw new RuntimeException("Could not parse date ...");
|
||||
}
|
||||
nicki.setBoss(tareq);
|
||||
nicki.setDynamicProperty("What is this???");
|
||||
nicki.setHeight((short)100);
|
||||
nicki.setLocation(51.5, 0.1);
|
||||
nicki.setPersonalProperties(new DynamicPropertiesContainer());
|
||||
nicki.setProperty("addressLine1","Somewhere");
|
||||
nicki.setProperty("addressLine2","Over the rainbow");
|
||||
nicki.setNickname("Nicks");
|
||||
friendShip2 = nicki.knows(tareq);
|
||||
friendShip2.setYears(2);
|
||||
friendShip3 = nicki.knows(michael);
|
||||
friendShip3.setYears(0);
|
||||
|
||||
friendShip = michael.knows(david);
|
||||
friendShip.setYears(2);
|
||||
serialTesterGroup = new Group();
|
||||
serialTesterGroup.setName("SDN-upgraders");
|
||||
serialTesterGroup.addPerson(michael);
|
||||
serialTesterGroup.addPerson(nicki);
|
||||
serialTesterGroup.addPerson(david);
|
||||
|
||||
repo.save(asList(emil, david, michael, nicki, tareq));
|
||||
friendshipRepository.save(friendShip);
|
||||
friendshipRepository.save(friendShip2);
|
||||
friendshipRepository.save(friendShip3);
|
||||
|
||||
groupRepo.save(serialTesterGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright 2011 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.
|
||||
* 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.repository;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.neo4j.model.Person;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.node.Neo4jHelper;
|
||||
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.transaction.BeforeTransaction;
|
||||
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
|
||||
public class SerializableGraphQueryRepositoryTests {
|
||||
|
||||
protected final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Autowired
|
||||
private Neo4jTemplate neo4jTemplate;
|
||||
|
||||
@Autowired
|
||||
private PersonRepository personRepository;
|
||||
|
||||
@Autowired
|
||||
GroupRepository groupRepository;
|
||||
|
||||
@Autowired
|
||||
FriendshipRepository friendshipRepository;
|
||||
|
||||
private SerialTesters serialTesters;
|
||||
private Date expectedBirthDate;
|
||||
|
||||
|
||||
@BeforeTransaction
|
||||
public void cleanDb() {
|
||||
Neo4jHelper.cleanDb(neo4jTemplate);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
serialTesters = new SerialTesters();
|
||||
serialTesters.createUpgraderTeam(personRepository, groupRepository, friendshipRepository);
|
||||
expectedBirthDate = serialTesters.bdayFormatter.parse("01 JAN 2013 00:00:00");
|
||||
|
||||
}
|
||||
|
||||
@Test @Transactional
|
||||
public void shouldBeAbleToTurnQueryResultsIntoAPOJO() throws Exception {
|
||||
MemberDataPOJO nickisMemberData = personRepository.findMemberDataPojo(serialTesters.nicki);
|
||||
assertPOJOContainsExpectedData(nickisMemberData);
|
||||
}
|
||||
|
||||
@Test @Transactional
|
||||
public void shouldBeAbleToSerializeAndDeserializeEntity() throws Exception {
|
||||
Person anSDNUpgrader = personRepository.findOne(serialTesters.nicki.getId());
|
||||
assertEntityDetailsForPerson(anSDNUpgrader);
|
||||
Person aDeserializedSDNUpgrader = assertObjectCanBeSerializedAndDeserialized(anSDNUpgrader);
|
||||
assertEntityDetailsForPerson(aDeserializedSDNUpgrader);
|
||||
}
|
||||
|
||||
private void assertEntityDetailsForPerson(Person aPerson) {
|
||||
assertThat(aPerson.getAge(), is(equalTo(36)));
|
||||
assertThat(aPerson.getBoss(), is(serialTesters.tareq));
|
||||
assertThat(aPerson.getBirthdate(), is(equalTo(expectedBirthDate)));
|
||||
assertThat(aPerson.getName(), is(equalTo("Nicki")));
|
||||
assertThat(aPerson.getDynamicProperty(), is(equalTo((Object)"What is this???")));
|
||||
assertThat(aPerson.getFriendships(), hasItems(serialTesters.friendShip2, serialTesters.friendShip3)) ;
|
||||
assertThat(aPerson.getHeight(), is(equalTo((short)100)));
|
||||
assertThat(aPerson.getProperty("addressLine1"), is(equalTo((Object)"Somewhere")));
|
||||
assertThat(aPerson.getProperty("addressLine2"), is(equalTo((Object)"Over the rainbow")));
|
||||
}
|
||||
|
||||
@Test @Transactional
|
||||
public void shouldBeAbleToSerializedPOJOReturnedFromQueryResult() throws Exception {
|
||||
MemberDataPOJO nickisOrigMemberData = personRepository.findMemberDataPojo(serialTesters.nicki);
|
||||
assertPOJOContainsExpectedData(nickisOrigMemberData);
|
||||
MemberDataPOJO nickisDeserMemberData = assertObjectCanBeSerializedAndDeserialized(nickisOrigMemberData);
|
||||
assertPOJOContainsExpectedData(nickisDeserMemberData);
|
||||
}
|
||||
|
||||
public void assertPOJOContainsExpectedData(MemberDataPOJO pojo) throws Exception {
|
||||
assertNotNull(pojo);
|
||||
assertThat(pojo.getBoss(), is(serialTesters.tareq));
|
||||
assertThat(asCollection(pojo.getTeams()), hasItem(serialTesters.serialTesterGroup));
|
||||
assertThat(pojo.getAnInt(), is(serialTesters.tareq.getAge()));
|
||||
assertThat(pojo.getAName(), is(serialTesters.tareq.getName()));
|
||||
}
|
||||
|
||||
private <T> T assertObjectCanBeSerializedAndDeserialized(T someObject) throws Exception {
|
||||
assertThat(someObject, instanceOf(Serializable.class));
|
||||
byte[] bos = serializeIt(someObject);
|
||||
return deserializeIt(bos);
|
||||
}
|
||||
|
||||
private <T> byte[] serializeIt(T someObject) throws Exception {
|
||||
ObjectOutputStream out = null;
|
||||
try {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
out = new ObjectOutputStream(bos);
|
||||
out.writeObject(someObject);
|
||||
return bos.toByteArray();
|
||||
} finally {
|
||||
if (out != null) out.close();
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T deserializeIt(byte[] serializedBytes) throws Exception {
|
||||
ObjectInputStream in = null;
|
||||
try {
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(serializedBytes);
|
||||
in = new ObjectInputStream(bis);
|
||||
Object theNewObj = in.readObject();
|
||||
return (T)theNewObj;
|
||||
} finally {
|
||||
if (in != null) in.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
|
||||
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd">
|
||||
<context:annotation-config/>
|
||||
<neo4j:config graphDatabaseService="graphDatabaseService"/>
|
||||
<neo4j:repositories base-package="org.springframework.data.neo4j.repository"/>
|
||||
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>
|
||||
</beans>
|
||||
Reference in New Issue
Block a user