/
GraphService.java
343 lines (292 loc) · 11 KB
/
GraphService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
package org.jboss.windup.graph.service;
import com.syncleus.ferma.Traversable;
import com.syncleus.ferma.VertexFrame;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.structure.Transaction;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.janusgraph.core.attribute.Text;
import org.janusgraph.util.datastructures.IterablesUtil;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.graph.service.exception.NonUniqueResultException;
import org.jboss.windup.util.ExecutionStatistics;
import org.jboss.windup.util.FilteredIterator;
import org.jboss.windup.util.exception.WindupException;
import static org.jboss.windup.util.Util.NL;
public class GraphService<T extends WindupVertexFrame> implements Service<T>
{
private final Class<T> type;
private final GraphContext context;
public GraphService(GraphContext context, Class<T> type)
{
this.context = context;
this.type = type;
}
@SuppressWarnings("unchecked")
public static <T extends WindupVertexFrame> T refresh(GraphContext context, T frame)
{
Vertex v = context.getGraph().traversal().V((Long)frame.getId()).next();
return (T) context.getFramed().frameElement(v, WindupVertexFrame.class);
}
@Override
public void commit()
{
ExecutionStatistics.performBenchmarked("GraphService.commit", () ->
{
getGraphContext().getGraph().tx().commit();
return null;
});
}
/**
* Create a new instance of the given {@link WindupVertexFrame} type. The ID is generated by the underlying graph database.
*/
@Override
public T create()
{
return ExecutionStatistics.performBenchmarked("GraphService.create", () -> context.getFramed().addFramedVertex(type));
}
@Override
public T addTypeToModel(final WindupVertexFrame model)
{
return ExecutionStatistics.performBenchmarked("GraphService.addTypeToModel", () -> GraphService.addTypeToModel(getGraphContext(), model, type));
}
protected Traversable<?, ?> findAllQuery()
{
return getGraphContext().getQuery(type);
}
@Override
public List<T> findAll()
{
return (List<T>)findAllQuery().toList(type);
}
@Override
public Iterable<T> findAllByProperties(final String[] keys, final String[] vals)
{
return ExecutionStatistics.performBenchmarked("GraphService.findAllByProperties(" + Arrays.asList(keys) + ")", () ->
{
Traversable<?, ?> traversable = findAllQuery();
for (int i = 0, j = keys.length; i < j; i++)
{
final String key = keys[i];
final String val = vals[i];
traversable = traversable.traverse(g ->
g.has(key, val)
);
}
return (List<T>)traversable.toList(type);
});
}
@Override
public Iterable<T> findAllByProperty(final String key, final Object value)
{
return findAllByProperty(key, value, false);
}
/**
* Allows optional filtering by model type, because getVertices(prop, value, type)
* does not filter by model type (it only frames the vertex as that type).
* That can be used to prevent collisions if multiple models use the same property name (e.g. "name").
*
* @param filterByType If true, the items returned from the graph are further filtered by this service's model type.
*/
public Iterable<T> findAllByProperty(final String key, final Object value, final boolean filterByType)
{
return ExecutionStatistics.performBenchmarked("GraphService.findAllByProperty(" + key + ")", () -> {
Class<?> typeSearch = filterByType ? WindupVertexFrame.class : type;
final Iterator verticesIterator = context.getFramed().getFramedVertices(key, value, typeSearch);
final List<T> vertices = new ArrayList<>();
verticesIterator.forEachRemaining(v -> vertices.add((T)v));
if (!filterByType)
return vertices;
return (Iterable<T>) () -> {
final FilteredIterator.Filter<T> filter = new ModelTypeFilter<>(GraphService.this.type);
return new FilteredIterator<T>(vertices.iterator(), filter);
};
});
}
@Override
public List<T> findAllWithoutProperty(final String key, final Object value)
{
return ExecutionStatistics.performBenchmarked("GraphService.findAllWithoutProperty(" + key + ")", () -> {
//return (List<T>)findAllQuery().traverse(g -> g.hasNot(key).or(g.has(key, P.neq(value)))).toList(type);
return findAllQuery().getRawTraversal().not(__.has(key, P.eq(value))).toList()
.stream()
.map(v -> frame((Vertex)v))
.collect(Collectors.toList());
});
}
@Override
public List<T> findAllWithoutProperty(final String key)
{
return ExecutionStatistics.performBenchmarked("GraphService.findAllWithoutProperty(" + key + ")", () -> {
return (List<T>)findAllQuery().traverse(g -> g.hasNot(key)).toList(type);
});
}
@Override
public Iterable<T> findAllByPropertyMatchingRegex(final String key, final String... regex)
{
return ExecutionStatistics.performBenchmarked("GraphService.findAllByPropertyMatchingRegex(" + key + ")", () ->
{
if (regex.length == 0)
return IterablesUtil.emptyIterable();
final String regexFinal;
if (regex.length == 1)
{
regexFinal = regex[0];
}
else
{
StringBuilder builder = new StringBuilder();
builder.append("\\b(");
int i = 0;
for (String value : regex)
{
if (i > 0)
builder.append("|");
builder.append(value);
i++;
}
builder.append(")\\b");
regexFinal = builder.toString();
}
return (List<T>)findAllQuery().traverse(g -> g.has(key, Text.textRegex(regexFinal))).toList(type);
});
}
/**
* Returns the vertex with given ID framed into given interface.
*/
@Override
public T getById(Object id)
{
return context.getFramed().getFramedVertex(this.type, id);
}
@Override
public T frame(Vertex vertex)
{
return getGraphContext().getFramed().frameElement(vertex, this.getType());
}
@Override
public Class<T> getType()
{
return this.type;
}
protected Traversable<?, ?> getQuery()
{
return getGraphContext().getQuery(this.getType());
}
@Override
public T getUnique() throws NonUniqueResultException
{
Iterable<T> results = findAll();
if (!results.iterator().hasNext())
{
return null;
}
Iterator<T> iterator = results.iterator();
T result = iterator.next();
if (iterator.hasNext())
{
throw new NonUniqueResultException("Expected unique value, but returned non-unique.");
}
return result;
}
@Override
public T getUniqueByProperty(String property, Object value) throws NonUniqueResultException
{
return getUniqueByProperty(property, value, false);
}
public T getUniqueByProperty(String property, Object value, boolean enforceType) throws NonUniqueResultException
{
Iterable<T> results = findAllByProperty(property, value, enforceType);
T result = null;
for (WindupVertexFrame item : results)
{
// There can be other types using the same property name.
if (!type.isInstance(item))
continue;
if (result != null)
{
throw new NonUniqueResultException("Expected unique value, but returned non-unique: " + property + " Conflicting models:"
+ NL + "\t" + StringUtils.join(item.getClass().getInterfaces(), ", ") + NL + "\t\t" + item.toPrettyString()
+ NL + "\t" + StringUtils.join(result.getClass().getInterfaces(), ", ") + NL + "\t\t" + result.toPrettyString());
}
result = (T) item;
}
return result;
}
protected T getUnique(Traversal<?, ?> query)
{
List<?> results = query.toList();
if (!results.iterator().hasNext())
{
return null;
}
Iterator<?> iter = results.iterator();
Object resultObj = iter.next();
if (iter.hasNext())
{
throw new NonUniqueResultException("Expected unique value, but returned non-unique.");
}
if (!(resultObj instanceof Vertex))
throw new WindupException("Unrecognized type returned by framed query: " + resultObj);
return frame((Vertex)resultObj);
}
protected GraphContext getGraphContext()
{
return context;
}
@Override
public Transaction newTransaction()
{
return context.getGraph().tx();
}
/**
* Adds the specified type to this frame, and returns a new object that implements this type.
*/
public static <T extends WindupVertexFrame> T addTypeToModel(GraphContext graphContext, WindupVertexFrame frame, Class<T> type)
{
Vertex vertex = frame.getElement();
graphContext.getGraphTypeManager().addTypeToElement(type, vertex);
return graphContext.getFramed().frameElement(vertex, type);
}
/**
* Removes the specified type from the frame.
*/
public static <T extends WindupVertexFrame> WindupVertexFrame removeTypeFromModel(GraphContext graphContext, WindupVertexFrame frame, Class<T> type)
{
Vertex vertex = frame.getElement();
graphContext.getGraphTypeManager().removeTypeFromElement(type, vertex);
return graphContext.getFramed().frameElement(vertex, WindupVertexFrame.class);
}
@Override
public void remove(final T model)
{
ExecutionStatistics.performBenchmarked("GraphService.commit", () ->
{
model.getElement().remove();
return null;
});
}
/**
* Only accepts vertices of given type and it's subtypes.
*/
static class ModelTypeFilter<E extends VertexFrame> implements FilteredIterator.Filter<E>
{
Class<E> type;
public ModelTypeFilter(Class<E> type)
{
this.type = type;
}
public boolean accept(E item)
{
return this.type.isInstance(item);
}
}
}