1313
1414import java .io .StringWriter ;
1515import java .lang .invoke .MethodHandles ;
16- import java .util .ArrayList ;
17- import java .util .Collection ;
18- import java .util .List ;
16+ import java .util .*;
1917import java .util .function .Consumer ;
2018import java .util .function .Function ;
2119
2220import 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 ;
2924import org .eclipse .rdf4j .model .util .ModelBuilder ;
25+ import org .eclipse .rdf4j .query .Operation ;
3026import org .eclipse .rdf4j .repository .RepositoryConnection ;
3127import org .eclipse .rdf4j .rio .RDFFormat ;
3228import org .eclipse .rdf4j .rio .Rio ;
29+ import org .eclipse .rdf4j .spring .support .RDF4JTemplate ;
3330import org .slf4j .Logger ;
3431import org .slf4j .LoggerFactory ;
3532
3633/**
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+ *
3748 * @author Florian Kleedorfer
3849 * @since 4.0.0
3950 */
@@ -42,11 +53,55 @@ public class UpdateWithModelBuilder {
4253 private static final Logger logger = LoggerFactory .getLogger (MethodHandles .lookup ().lookupClass ());
4354
4455 private final RepositoryConnection con ;
56+
57+ /** the model builder being exposed to clients */
4558 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 ;
4667
4768 public UpdateWithModelBuilder (RepositoryConnection con ) {
4869 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 ;
50105 }
51106
52107 public UpdateWithModelBuilder setNamespace (Namespace ns ) {
@@ -171,9 +226,63 @@ public void execute() {
171226 Model model = modelBuilder .build ();
172227 if (logger .isDebugEnabled ()) {
173228 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 ();
174232 Rio .write (model , sw , RDFFormat .TURTLE );
175233 logger .debug ("adding the following triples:\n {}" , sw .toString ());
176234 }
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+ }
178287 }
179288}
0 commit comments