Modify traversal from outside the function that builds the traversal (Java)

Hi peeps, more of an OOP oriented question this time around. I'll start off by explaining what I'm trying to do first. In my app I have a bunch of tables that can have different types of filters applied to them. To make my life easier on the backend I've made an abstract Filter class which is extended by all the different types of filters, and the model mapper knows which subclass I want from a field sent in the request. A good way to apply the filtering to the traversal would be to have a dsl step that takes the list of filters, checks which instanceof each filter is and does it's stuff accordingly. I wanted to know if there would be any way to keep the logic in the filter classes, overriding the method in each subclass. Perhaps something like generating the steps in the filter and adding them to the admin traversal in the dsl, I'm not sure.
4 Replies
triggan
triggan2y ago
What you've described sounds really similar to how Gremlin DSLs are defined. Have you seen this portion of the docs? https://tinkerpop.apache.org/docs/current/reference/#gremlin-java-dsl
zendorphins
zendorphinsOP2y ago
I probably wasn't very clear in my intention, let me illustrate it with an example: Let's say the abstract class is called Filter and I have 2 subclasses, RangeFilter and ExactFilter, that filter some column given by a range of values and an exact value respectively. If I implement a dsl step like .applyFilters(List<Filter> filters) I believe I would require a for loop with a switch statement that checks what subclass the given Filter is, sort of like a visitor pattern, and return the steps i need for each filter implementation (filter(__.select(filter.getKey()).is(P.eq(filter.getValue()))) for the exact filter for example):
default GraphTraversal<S, E> applyFiltersInstaceOf(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal<S, E> traversal = identity();
for (Filter filter : filters) {
if (filter instanceof XFilter) {
traversal = traversal.filter(__.select(filter.getKey()).is(P.eq(filter.getValue())));
}
else if (filter instanceof YFilter) {
traversal = traversal.filter(__.select(filter.getKey()).is(P.eq(filter.getValue())));
}
return traversal;
}
default GraphTraversal<S, E> applyFiltersInstaceOf(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal<S, E> traversal = identity();
for (Filter filter : filters) {
if (filter instanceof XFilter) {
traversal = traversal.filter(__.select(filter.getKey()).is(P.eq(filter.getValue())));
}
else if (filter instanceof YFilter) {
traversal = traversal.filter(__.select(filter.getKey()).is(P.eq(filter.getValue())));
}
return traversal;
}
I was wandering if instead of checking what type the filter is, I could implement a method in each filter subclass that would return the steps I need and use that implementation directly in the dsl:
default GraphTraversal<S, E> applyFilters(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal<S, E> traversal = identity();
for (Filter filter : filters) {
traversal = traversal.filter(filter.getTraversal());
}
return traversal;
}
default GraphTraversal<S, E> applyFilters(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal<S, E> traversal = identity();
for (Filter filter : filters) {
traversal = traversal.filter(filter.getTraversal());
}
return traversal;
}
I know it's a pretty bad filtering method to only filter the final project step instead of filtering throughout the traversal, but the filters are the same for SQL queries and efficiency is not a priority currently. Any suggestions for further reading regarding the implementation of optional filters on queries is definitely appreciated however. I came up with this for now, though it would've been more flexible if I could have added the filter steps to the current traversal directly instead of giving an anonymous traversal to the filter() step
public class ValueFilter extends Filter {
private int value;

@Override
public CustomTraversal<Object, Object> getTraversal() {

return select(key).is(P.eq(value));
}

@GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, methodTypeParameters = {"A"})
default GraphTraversal<S, E> applyFilters(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal<S, E> traversal = identity();
for (Filter filter : filters) {
traversal.filter(filter.getTraversal());
}
return traversal;
}
public class ValueFilter extends Filter {
private int value;

@Override
public CustomTraversal<Object, Object> getTraversal() {

return select(key).is(P.eq(value));
}

@GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, methodTypeParameters = {"A"})
default GraphTraversal<S, E> applyFilters(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal<S, E> traversal = identity();
for (Filter filter : filters) {
traversal.filter(filter.getTraversal());
}
return traversal;
}
This seems to be the way forward, but its giving me a class casting error and I can't find a way to turn it back into a GraphTraversal
GraphTraversal.Admin traversal = new DefaultGraphTraversal<>().asAdmin();
for (Filter filter : filters) {
for (Step step : filter.getTraversalFilterSteps()) {
traversal.addStep(step);
}
}
return traversal;
GraphTraversal.Admin traversal = new DefaultGraphTraversal<>().asAdmin();
for (Filter filter : filters) {
for (Step step : filter.getTraversalFilterSteps()) {
traversal.addStep(step);
}
}
return traversal;
I'm a little bit in over my head right now, so if anyone has some documentation for how the dsl and admin traversal does it's magic I'd love to read it. This is the best I could come up with, which seems to not be the right way. While the steps do get added correctly to the main traversal, it seems like the bytecode does not.
@GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, methodTypeParameters = {"A"})
default GraphTraversal<S, E> applyFilters(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal.Admin<S, E> traversal = asAdmin();
for (Filter filter : filters) {
for (Step<S,E> step : filter.getTraversalFilterSteps()) {
traversal = traversal.addStep(step);
}
}
return traversal;
}
@GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, methodTypeParameters = {"A"})
default GraphTraversal<S, E> applyFilters(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal.Admin<S, E> traversal = asAdmin();
for (Filter filter : filters) {
for (Step<S,E> step : filter.getTraversalFilterSteps()) {
traversal = traversal.addStep(step);
}
}
return traversal;
}
spmallette
spmallette2y ago
sorry, but i've had some trouble following exactly what you want to do so it's been hard to respond quickly. couldn't your Filter just have a function that takes the current Traversal as an argument? For example if you had this on Filter:
void addFilter(GraphTraversal t)
void addFilter(GraphTraversal t)
you could then do this from the DSL:
@GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, methodTypeParameters = {"A"})
default GraphTraversal<S, E> applyFilters(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal.Admin<S, E> traversal = asAdmin();
for (Filter filter : filters) {
filter.addFilter(traversal);
}
return traversal;
}
@GremlinDsl.AnonymousMethod(returnTypeParameters = {"A", "A"}, methodTypeParameters = {"A"})
default GraphTraversal<S, E> applyFilters(List<Filter> filters) {
if (filters == null || filters.isEmpty()) {
return identity();
}
GraphTraversal.Admin<S, E> traversal = asAdmin();
for (Filter filter : filters) {
filter.addFilter(traversal);
}
return traversal;
}
in this way you'd be adding steps directly to the traversal itself rather than wrapping it in a filter() or trying to pick apart anonymous traversals into individual steps (which does mess up the bytecode as you have found).
zendorphins
zendorphinsOP2y ago
Yep I was really missing the forest for the trees. Thanks for the response I should've thought about doing it this way, I was a bit thrown off by this stack overflow post. Thought I had to reassign the traversal after adding steps for some reason, which wouldn't have worked for passing by reference. Your implementation works great.
Stack Overflow
Gremlin merge two traversals
I have 2 traversals: 1 to find vertex by label and another to find edges by label and I'm trying to merge them i.e. get all edgeLabel edges for vertices with vertexlabel label. vertex traversal: gr...
Want results from more Discord servers?
Add your server