13
13
14
14
import java .io .StringWriter ;
15
15
import java .lang .invoke .MethodHandles ;
16
- import java .util .ArrayList ;
17
- import java .util .Collection ;
18
- import java .util .List ;
16
+ import java .util .*;
19
17
import java .util .function .Consumer ;
20
18
import java .util .function .Function ;
21
19
22
20
import org .apache .commons .lang3 .ObjectUtils ;
23
- import org .eclipse .rdf4j .model .BNode ;
24
- import org .eclipse .rdf4j .model .IRI ;
25
- import org .eclipse .rdf4j .model .Model ;
26
- import org .eclipse .rdf4j .model .Namespace ;
27
- import org .eclipse .rdf4j .model .Resource ;
28
- import org .eclipse .rdf4j .model .Statement ;
21
+ import org .eclipse .rdf4j .model .*;
22
+ import org .eclipse .rdf4j .model .base .AbstractStatement ;
23
+ import org .eclipse .rdf4j .model .impl .LinkedHashModel ;
29
24
import org .eclipse .rdf4j .model .util .ModelBuilder ;
25
+ import org .eclipse .rdf4j .query .Operation ;
30
26
import org .eclipse .rdf4j .repository .RepositoryConnection ;
31
27
import org .eclipse .rdf4j .rio .RDFFormat ;
32
28
import org .eclipse .rdf4j .rio .Rio ;
29
+ import org .eclipse .rdf4j .spring .support .RDF4JTemplate ;
33
30
import org .slf4j .Logger ;
34
31
import org .slf4j .LoggerFactory ;
35
32
36
33
/**
34
+ * <p>
35
+ * An {@link Operation} that holds a {@link Model} internally and exposes a {@link ModelBuilder} for adding to it.
36
+ * Moreover it allows for deleting statements.
37
+ * </p>
38
+ * <p>
39
+ * Thus, the class provides a way of configuring an update to the repository incrementally, and no repository access
40
+ * happens until {@link #execute()} is called. (unless the client uses {@link #applyToConnection(Function)} and accesses
41
+ * the repository that way.)
42
+ * </p>
43
+ * Removing statements via {@link #remove} will remove them from the repository when {@link #execute()} is called;
44
+ * moreover, the statements will also be removed from the model at the time of the {@link #remove} call, such that a
45
+ * subsequent creation of some of the deleted statements to the model will result in those triples being first deleted
46
+ * and then added to the repository when {@link #execute()} is called.
47
+ *
37
48
* @author Florian Kleedorfer
38
49
* @since 4.0.0
39
50
*/
@@ -42,11 +53,55 @@ public class UpdateWithModelBuilder {
42
53
private static final Logger logger = LoggerFactory .getLogger (MethodHandles .lookup ().lookupClass ());
43
54
44
55
private final RepositoryConnection con ;
56
+
57
+ /** the model builder being exposed to clients */
45
58
private final ModelBuilder modelBuilder ;
59
+ /** the model being built by the modelBuilder, and that is going to be added to the repository eventually */
60
+ private final Model addModel ;
61
+
62
+ /**
63
+ * Set of Statements to be removed from the repository eventually. The Statement implementation used here is the
64
+ * {@link WildcardAllowingStatement}, which allows for using wildcards for deletion
65
+ */
66
+ private final Set <Statement > removeStatements ;
46
67
47
68
public UpdateWithModelBuilder (RepositoryConnection con ) {
48
69
this .con = con ;
49
- this .modelBuilder = new ModelBuilder ();
70
+ this .addModel = new LinkedHashModel ();
71
+ this .removeStatements = new HashSet <>();
72
+ this .modelBuilder = new ModelBuilder (addModel );
73
+ }
74
+
75
+ public static UpdateWithModelBuilder fromTemplate (RDF4JTemplate template ) {
76
+ return template .applyToConnection (con -> new UpdateWithModelBuilder (con ));
77
+ }
78
+
79
+ /**
80
+ * Will remove statements upon update execution, before processing any additions. Statements that are removed here
81
+ * are also removed from the #addModel at the time of this call (not upon update execution)
82
+ *
83
+ * <p>
84
+ * The semantics of {@link RepositoryConnection#remove(Iterable, Resource...)} apply, i.e. the resource(s) specified
85
+ * here are used there, if any.
86
+ *
87
+ * @param subject the subject, or null to match any resource
88
+ * @param predicate the predicate, or null to match any IRI
89
+ * @param object the object, or null to match any value
90
+ * @param resources the context(s), if any
91
+ * @return this builder
92
+ */
93
+ public UpdateWithModelBuilder remove (
94
+ Resource subject , IRI predicate , Value object , Resource ... resources ) {
95
+ addModel .remove (subject , predicate , object , resources );
96
+ if (resources .length == 0 ) {
97
+ removeStatements .add (new WildcardAllowingStatement (subject , predicate , object , null ));
98
+ } else {
99
+ for (int i = 0 ; i < resources .length ; i ++) {
100
+ removeStatements .add (
101
+ new WildcardAllowingStatement (subject , predicate , object , resources [i ]));
102
+ }
103
+ }
104
+ return this ;
50
105
}
51
106
52
107
public UpdateWithModelBuilder setNamespace (Namespace ns ) {
@@ -171,9 +226,63 @@ public void execute() {
171
226
Model model = modelBuilder .build ();
172
227
if (logger .isDebugEnabled ()) {
173
228
StringWriter sw = new StringWriter ();
229
+ Rio .write (this .removeStatements , sw , RDFFormat .TURTLE );
230
+ logger .debug ("removing the following triples:\n {}" , sw .toString ());
231
+ sw = new StringWriter ();
174
232
Rio .write (model , sw , RDFFormat .TURTLE );
175
233
logger .debug ("adding the following triples:\n {}" , sw .toString ());
176
234
}
177
- con .add (model );
235
+ con .remove (this .removeStatements );
236
+ con .add (this .addModel );
237
+ }
238
+
239
+ static class WildcardAllowingStatement extends AbstractStatement {
240
+ private static final long serialVersionUID = -4116676621136121342L ;
241
+ private final Resource subject ;
242
+ private final IRI predicate ;
243
+ private final Value object ;
244
+ private final Resource context ;
245
+
246
+ WildcardAllowingStatement (Resource subject , IRI predicate , Value object , Resource context ) {
247
+ this .subject = subject ;
248
+ this .predicate = predicate ;
249
+ this .object = object ;
250
+ this .context = context ;
251
+ }
252
+
253
+ public Resource getSubject () {
254
+ return this .subject ;
255
+ }
256
+
257
+ public IRI getPredicate () {
258
+ return this .predicate ;
259
+ }
260
+
261
+ public Value getObject () {
262
+ return this .object ;
263
+ }
264
+
265
+ public Resource getContext () {
266
+ return this .context ;
267
+ }
268
+
269
+ @ Override
270
+ public boolean equals (Object o ) {
271
+ if (this == o )
272
+ return true ;
273
+ if (o == null || getClass () != o .getClass ())
274
+ return false ;
275
+ WildcardAllowingStatement that = (WildcardAllowingStatement ) o ;
276
+ return Objects .equals (getSubject (), that .getSubject ())
277
+ && Objects .equals (getPredicate (), that .getPredicate ())
278
+ && Objects .equals (getObject (), that .getObject ())
279
+ && Objects .equals (getContext (), that .getContext ());
280
+ }
281
+
282
+ @ Override
283
+ public int hashCode () {
284
+ return Objects .hash (
285
+ super .hashCode (), getSubject (), getPredicate (), getObject (), getContext ());
286
+ }
178
287
}
179
288
}
0 commit comments