Skip to content
This repository was archived by the owner on Apr 16, 2022. It is now read-only.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for
* license information.
*/
package com.microsoft.spring.data.gremlin.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.data.annotation.QueryAnnotation;

/**
* Annotation to declare Parameterized queries to be defined as String.
* Inspired from Spring Neo4j implementation
*
* @author Ganesh Guttikonda
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@QueryAnnotation
@Documented
public @interface Query {

/**
* Defines the Gremlin query to be executed when the annotated method is called.
*/
String value() default "";

}
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ private <T> T recoverDomain(@NonNull GremlinSource<T> source, @NonNull List<Resu
return domain;
}

private <T> List<T> recoverDomainList(@NonNull GremlinSource<T> source, @NonNull List<Result> results) {
public <T> List<T> recoverDomainList(@NonNull GremlinSource<T> source, @NonNull List<Result> results) {
return results.stream().map(r -> recoverDomain(source, Collections.singletonList(r))).collect(toList());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for
* license information.
*/
package com.microsoft.spring.data.gremlin.query.query;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import org.apache.tinkerpop.gremlin.driver.Client;
import org.apache.tinkerpop.gremlin.driver.Result;
import org.apache.tinkerpop.gremlin.driver.ResultSet;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.lang.NonNull;

import com.microsoft.spring.data.gremlin.common.GremlinUtils;
import com.microsoft.spring.data.gremlin.conversion.source.GremlinSource;
import com.microsoft.spring.data.gremlin.query.GremlinOperations;
import com.microsoft.spring.data.gremlin.query.GremlinTemplate;
import com.microsoft.spring.data.gremlin.query.paramerter.GremlinParameterAccessor;
import com.microsoft.spring.data.gremlin.query.paramerter.GremlinParametersParameterAccessor;

public class GraphRepositoryGremlinQuery extends AbstractGremlinQuery {

private final GremlinQueryMethod method;
private final GremlinOperations operations;
private final Client gremlinClient;

public GraphRepositoryGremlinQuery(@NonNull Client gremlinClient, @NonNull GremlinQueryMethod method,
@NonNull GremlinOperations operations) {
super(method, operations);
this.gremlinClient = gremlinClient;
this.method = method;
this.operations = operations;
}

@Override
protected GremlinQuery createQuery(GremlinParameterAccessor accessor) {
throw new UnsupportedOperationException("Not implemented yet");
}

@Override
public Object execute(@NonNull Object[] parameters) {
final String query = method.getQuery();
final Map<String, Object> params = this.resolveParams(this.method.getParameters(), parameters);
final GremlinParameterAccessor accessor = new GremlinParametersParameterAccessor(this.method, parameters);
final ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
final Class<?> methodReturnType = processor.getReturnedType().getDomainType();
final ResultSet rs = this.gremlinClient.submit(query, params);

if (ResultSet.class.equals(methodReturnType)) {
return rs;
}

final GremlinSource<?> source = GremlinUtils.toGremlinSource(methodReturnType);
if (GremlinTemplate.class.equals(this.operations.getClass())) {
try {
final List<Result> gremlinResults = rs.all().get();
final List<?> results = ((GremlinTemplate) this.operations).recoverDomainList(source, gremlinResults);
if (results == null || results.isEmpty()) {
//return null for not found results
return null;
}

if (results.size() == 1) {
// return pojo instead of list
return results.get(0);
}
return results;
} catch (InterruptedException e) {
throw new IllegalStateException(e);
} catch (ExecutionException e) {
throw new IllegalStateException(e);
}
}

throw new UnsupportedOperationException(methodReturnType + " is not handled by deserializer!");
}

@SuppressWarnings({ "unchecked", "rawtypes" })
protected Map<String, Object> resolveParams(Parameters<?, ?> methodParameters, Object[] parameters) {

final Map<String, Object> resolvedParameters = new HashMap<>();

for (final Parameter parameter : methodParameters) {
final int parameterIndex = parameter.getIndex();
final Object parameterValue = parameters[parameterIndex];
// Convenience! Client can simply pass Map<String, Object> params,
// we automatically resolve them to individual parameters.
// this is to allow the pass through for GremlinClient
if (parameterValue instanceof Map) {
resolvedParameters.putAll((Map) parameterValue);
}
parameter.getName().ifPresent(parameterName -> resolvedParameters.put(parameterName, parameterValue));

}
return resolvedParameters;
}

@Override
@NonNull
public GremlinQueryMethod getQueryMethod() {
return this.method;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@
*/
package com.microsoft.spring.data.gremlin.query.query;

import com.microsoft.spring.data.gremlin.annotation.Query;
import com.microsoft.spring.data.gremlin.query.GremlinEntityMetadata;
import com.microsoft.spring.data.gremlin.query.SimpleGremlinEntityMetadata;

import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.EntityMetadata;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;

public class GremlinQueryMethod extends QueryMethod {

private GremlinEntityMetadata<?> metadata;
private final Query queryAnnotation;
private final Method method;

public GremlinQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
super(method, metadata, factory);
this.queryAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
this.method = method;
}

@Override
Expand All @@ -30,4 +39,22 @@ public EntityMetadata<?> getEntityInformation() {

return this.metadata;
}

public String getQuery() {
return queryAnnotation.value();
}

public boolean hasAnnotatedQuery() {
return getAnnotatedQuery() != null;
}

private String getAnnotatedQuery() {

final String query = (String) AnnotationUtils.getValue(getQueryAnnotation());
return StringUtils.hasText(query) ? query : null;
}

private Query getQueryAnnotation() {
return AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
*/
package com.microsoft.spring.data.gremlin.repository.support;

import com.microsoft.spring.data.gremlin.common.GremlinFactory;
import com.microsoft.spring.data.gremlin.query.GremlinOperations;
import com.microsoft.spring.data.gremlin.query.query.GraphRepositoryGremlinQuery;
import com.microsoft.spring.data.gremlin.query.query.GremlinQueryMethod;
import com.microsoft.spring.data.gremlin.query.query.PartTreeGremlinQuery;
import org.springframework.context.ApplicationContext;
Expand Down Expand Up @@ -55,14 +57,16 @@ public <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> domainClas
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(
QueryLookupStrategy.Key key, QueryMethodEvaluationContextProvider provider) {
return Optional.of(new GremlinQueryLookupStrategy(this.operations));
return Optional.of(new GremlinQueryLookupStrategy(this.context, this.operations));
}

private static class GremlinQueryLookupStrategy implements QueryLookupStrategy {

private final GremlinOperations operations;
private final ApplicationContext context;

public GremlinQueryLookupStrategy(@NonNull GremlinOperations operations) {
public GremlinQueryLookupStrategy(@NonNull ApplicationContext context, @NonNull GremlinOperations operations) {
this.context = context;
this.operations = operations;
}

Expand All @@ -73,7 +77,12 @@ public RepositoryQuery resolveQuery(@NonNull Method method, RepositoryMetadata m

Assert.notNull(queryMethod, "queryMethod should not be null");
Assert.notNull(this.operations, "operations should not be null");

if (queryMethod.hasAnnotatedQuery()) {
final GremlinFactory gremlinFactory = context.getBean(GremlinFactory.class);
Assert.notNull(gremlinFactory, "gremlinFactory bean should not be null");
return new GraphRepositoryGremlinQuery(gremlinFactory.getGremlinClient(), queryMethod, operations);
}

return new PartTreeGremlinQuery(queryMethod, this.operations);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@
*/
package com.microsoft.spring.data.gremlin.common.repository;

import com.microsoft.spring.data.gremlin.annotation.Query;
import com.microsoft.spring.data.gremlin.common.domain.Person;
import com.microsoft.spring.data.gremlin.repository.GremlinRepository;

import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends GremlinRepository<Person, String> {

@Query("g.V().has('name', name)")
public Person findPersonByName(@Param("name") String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class PersonRepositoryIT {

private final Person person = new Person(TestConstants.VERTEX_PERSON_ID, TestConstants.VERTEX_PERSON_NAME);
private final Person person0 = new Person(TestConstants.VERTEX_PERSON_0_ID, TestConstants.VERTEX_PERSON_0_NAME);
private final Person person1 = new Person(TestConstants.VERTEX_PERSON_1_ID, TestConstants.VERTEX_PERSON_1_NAME);
private final Project project = new Project(TestConstants.VERTEX_PROJECT_ID, TestConstants.VERTEX_PROJECT_NAME,
TestConstants.VERTEX_PROJECT_URI);

Expand Down Expand Up @@ -231,5 +232,17 @@ public void testFindAll() {
this.repository.deleteAll();
Assert.assertFalse(this.repository.findAll().iterator().hasNext());
}


@Test
public void testQueryAnnotation() {
final Person result = this.repository.save(this.person1);
Assert.assertNotNull(result);
final Person foundPerson = this.repository.findPersonByName(this.person1.getName());

Assert.assertNotNull(foundPerson);
Assert.assertEquals(foundPerson.getId(), this.person1.getId());
Assert.assertEquals(foundPerson.getName(), this.person1.getName());
}
}