1
2
3
4
5
6
7
8
9
10
11
12
13
14 package eu.fbk.rdfpro;
15
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Objects;
22 import java.util.Set;
23 import java.util.concurrent.ConcurrentHashMap;
24
25 import javax.annotation.Nullable;
26
27 import com.google.common.collect.ImmutableMap;
28 import com.google.common.collect.ImmutableSet;
29 import com.google.common.collect.Lists;
30 import com.google.common.collect.Ordering;
31 import com.google.common.collect.Sets;
32 import com.google.common.hash.BloomFilter;
33 import com.google.common.hash.Funnels;
34
35 import org.openrdf.model.Statement;
36 import org.openrdf.model.URI;
37 import org.openrdf.model.ValueFactory;
38 import org.openrdf.model.vocabulary.RDF;
39 import org.openrdf.query.BindingSet;
40 import org.openrdf.query.algebra.StatementPattern;
41 import org.openrdf.query.algebra.TupleExpr;
42 import org.openrdf.query.algebra.Var;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import eu.fbk.rdfpro.util.Algebra;
47 import eu.fbk.rdfpro.util.Environment;
48 import eu.fbk.rdfpro.util.IO;
49 import eu.fbk.rdfpro.util.Namespaces;
50 import eu.fbk.rdfpro.util.QuadModel;
51 import eu.fbk.rdfpro.util.Statements;
52 import eu.fbk.rdfpro.vocab.RR;
53
54
55
56
57 public final class Ruleset {
58
59 private static final Logger LOGGER = LoggerFactory.getLogger(Ruleset.class);
60
61 public static final Ruleset RHODF = fromRDF(Environment.getProperty("rdfpro.rules.rhodf"));
62
63 public static final Ruleset RDFS = fromRDF(Environment.getProperty("rdfpro.rules.rdfs"));
64
65 public static final Ruleset OWL2RL = fromRDF(Environment.getProperty("rdfpro.rules.owl2rl"));
66
67 private final Set<Rule> rules;
68
69 private final Set<URI> metaVocabularyTerms;
70
71 @Nullable
72 private transient Map<URI, Rule> ruleIndex;
73
74 @Nullable
75 private transient List<RuleSplit> ruleSplits;
76
77 private transient int hash;
78
79 @Nullable
80 private transient Boolean deletePossible;
81
82 @Nullable
83 private transient Boolean insertPossible;
84
85 @Nullable
86 private transient BloomFilter<Integer>[] filters;
87
88
89
90
91
92
93
94
95
96 public Ruleset(final Iterable<Rule> rules, @Nullable final Iterable<URI> metaVocabularyTerms) {
97
98 this.rules = ImmutableSet.copyOf(Ordering.natural().sortedCopy(rules));
99 this.metaVocabularyTerms = metaVocabularyTerms == null ? ImmutableSet.of()
100 : ImmutableSet.copyOf(Ordering.from(Statements.valueComparator()).sortedCopy(
101 metaVocabularyTerms));
102
103 this.ruleIndex = null;
104 this.ruleSplits = null;
105 this.hash = 0;
106 this.deletePossible = null;
107 this.insertPossible = null;
108 this.filters = null;
109 }
110
111
112
113
114
115
116 public Set<Rule> getRules() {
117 return this.rules;
118 }
119
120
121
122
123
124
125
126
127 @Nullable
128 public Rule getRule(final URI ruleID) {
129 if (this.ruleIndex == null) {
130 final ImmutableMap.Builder<URI, Rule> builder = ImmutableMap.builder();
131 for (final Rule rule : this.rules) {
132 builder.put(rule.getID(), rule);
133 }
134 this.ruleIndex = builder.build();
135 }
136 return this.ruleIndex.get(Objects.requireNonNull(ruleID));
137 }
138
139
140
141
142
143
144 public Set<URI> getMetaVocabularyTerms() {
145 return this.metaVocabularyTerms;
146 }
147
148
149
150
151
152
153
154 public boolean isDeletePossible() {
155 if (this.deletePossible == null) {
156 boolean deletePossible = false;
157 for (final Rule rule : this.rules) {
158 if (rule.getDeleteExpr() != null) {
159 deletePossible = true;
160 break;
161 }
162 }
163 this.deletePossible = deletePossible;
164 }
165 return this.deletePossible;
166 }
167
168
169
170
171
172
173
174 public boolean isInsertPossible() {
175 if (this.insertPossible == null) {
176 boolean insertPossible = false;
177 for (final Rule rule : this.rules) {
178 if (rule.getInsertExpr() != null) {
179 insertPossible = true;
180 break;
181 }
182 }
183 this.insertPossible = insertPossible;
184 }
185 return this.insertPossible;
186 }
187
188
189
190
191
192
193
194
195
196
197
198
199 @SuppressWarnings("unchecked")
200 public boolean isMatchable(final Statement stmt) {
201
202
203 if (this.filters == null) {
204 synchronized (this) {
205 if (this.filters == null) {
206
207 final List<StatementPattern> patterns = new ArrayList<>();
208 for (final Rule rule : this.rules) {
209 patterns.addAll(rule.getDeletePatterns());
210 patterns.addAll(rule.getWherePatterns());
211 }
212
213
214 BloomFilter<Integer>[] filters = new BloomFilter[] { null, null, null, null };
215 for (final StatementPattern p : patterns) {
216 if (!p.getSubjectVar().hasValue() && !p.getPredicateVar().hasValue()
217 && !p.getObjectVar().hasValue()
218 && (p.getContextVar() == null || !p.getContextVar().hasValue())) {
219 filters = new BloomFilter[0];
220 LOGGER.debug("Rules contain <?s ?p ?o ?c> pattern");
221 break;
222 }
223 }
224
225
226 final int[] counts = new int[4];
227 for (int i = 0; i < filters.length; ++i) {
228 final Set<Integer> hashes = Sets.newHashSet();
229 for (final StatementPattern pattern : patterns) {
230 final List<Var> vars = pattern.getVarList();
231 if (i < vars.size() && vars.get(i).hasValue()) {
232 hashes.add(vars.get(i).getValue().hashCode());
233 }
234 }
235 counts[i] = hashes.size();
236 if (!hashes.isEmpty()) {
237 final BloomFilter<Integer> filter = BloomFilter.create(
238 Funnels.integerFunnel(), hashes.size());
239 filters[i] = filter;
240 for (final Integer hash : hashes) {
241 filter.put(hash);
242 }
243
244 }
245 }
246
247
248 this.filters = filters;
249 if (LOGGER.isDebugEnabled()) {
250 LOGGER.debug("Number of constants in pattern components: "
251 + "s = {}, p = {}, o = {}, c = {}", counts[0], counts[1],
252 counts[2], counts[3]);
253 }
254 }
255 }
256 }
257
258
259 if (this.filters.length == 0) {
260 return true;
261 }
262
263
264 final BloomFilter<Integer> subjFilter = this.filters[0];
265 final BloomFilter<Integer> predFilter = this.filters[1];
266 final BloomFilter<Integer> objFilter = this.filters[2];
267 final BloomFilter<Integer> ctxFilter = this.filters[3];
268 return subjFilter != null && subjFilter.mightContain(stmt.getSubject().hashCode())
269 || predFilter != null && predFilter.mightContain(stmt.getPredicate().hashCode())
270 || objFilter != null && objFilter.mightContain(stmt.getObject().hashCode())
271 || ctxFilter != null && ctxFilter.mightContain(stmt.getContext() == null ? 0 :
272 stmt.getContext().hashCode());
273 }
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292 public Ruleset getABoxRuleset(final QuadModel tboxData) {
293
294
295 if (this.ruleSplits == null) {
296 final List<RuleSplit> splits = new ArrayList<>(this.rules.size());
297 for (final Rule rule : this.rules) {
298 splits.add(new RuleSplit(rule, this.metaVocabularyTerms));
299 }
300 this.ruleSplits = splits;
301 }
302
303
304 final Map<URI, List<BindingSet>> bindingsMap = new ConcurrentHashMap<>();
305 final List<Runnable> queries = new ArrayList<>();
306 int numABoxRules = 0;
307 for (final RuleSplit split : this.ruleSplits) {
308 if (split.aboxDeleteExpr != null || split.aboxInsertExpr != null) {
309 ++numABoxRules;
310 if (split.tboxWhereExpr != null) {
311 queries.add(new Runnable() {
312
313 @Override
314 public void run() {
315 final List<BindingSet> bindings = Lists.newArrayList(tboxData
316 .evaluate(split.tboxWhereExpr, null, null));
317 bindingsMap.put(split.rule.getID(), bindings);
318 }
319
320 });
321 }
322 }
323 }
324 Environment.run(queries);
325
326
327 final List<Rule> rules = new ArrayList<>();
328 for (final RuleSplit split : this.ruleSplits) {
329 if (split.aboxDeleteExpr != null || split.aboxInsertExpr != null) {
330 final URI id = split.rule.getID();
331 final boolean fixpoint = split.rule.isFixpoint();
332 final int phase = split.rule.getPhase();
333 if (split.tboxWhereExpr == null) {
334 final URI newID = Rule.newID(id.stringValue());
335 rules.add(new Rule(newID, fixpoint, phase, split.aboxDeleteExpr,
336 split.aboxInsertExpr, split.aboxWhereExpr));
337 } else {
338 final Iterable<? extends BindingSet> list = bindingsMap.get(id);
339 if (list != null) {
340 for (final BindingSet b : list) {
341 final TupleExpr delete = Algebra.normalize(
342 Algebra.rewrite(split.aboxDeleteExpr, b),
343 Statements.VALUE_NORMALIZER);
344 final TupleExpr insert = Algebra.normalize(
345 Algebra.rewrite(split.aboxInsertExpr, b),
346 Statements.VALUE_NORMALIZER);
347 final TupleExpr where = Algebra.normalize(
348 Algebra.rewrite(split.aboxWhereExpr, b),
349 Statements.VALUE_NORMALIZER);
350 if (!Objects.equals(insert, where) || delete != null) {
351 final URI newID = Rule.newID(id.stringValue());
352 rules.add(new Rule(newID, fixpoint, phase, delete, insert, where));
353 }
354 }
355 }
356 }
357 }
358 }
359 LOGGER.debug("{} ABox rules derived from {} TBox quads and {} original rules "
360 + "({} with ABox components, {} with TBox & ABox components)", rules.size(),
361 tboxData.size(), this.rules.size(), numABoxRules, queries.size());
362
363
364 return new Ruleset(rules, this.metaVocabularyTerms);
365 }
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380 public Ruleset rewriteGlobalGM(@Nullable final URI globalGraph) {
381 final List<Rule> rewrittenRules = new ArrayList<>();
382 for (final Rule rule : this.rules) {
383 rewrittenRules.add(rule.rewriteGlobalGM(globalGraph));
384 }
385 return new Ruleset(rewrittenRules, this.metaVocabularyTerms);
386 }
387
388
389
390
391
392
393
394
395
396 public Ruleset rewriteSeparateGM() {
397 final List<Rule> rewrittenRules = new ArrayList<>();
398 for (final Rule rule : this.rules) {
399 rewrittenRules.add(rule.rewriteSeparateGM());
400 }
401 return new Ruleset(rewrittenRules, this.metaVocabularyTerms);
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415
416 public Ruleset rewriteStarGM(@Nullable final URI globalGraph) {
417 final List<Rule> rewrittenRules = new ArrayList<>();
418 for (final Rule rule : this.rules) {
419 rewrittenRules.add(rule.rewriteStarGM(globalGraph));
420 }
421 return new Ruleset(rewrittenRules, this.metaVocabularyTerms);
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435
436 public Ruleset rewriteVariables(@Nullable final BindingSet bindings) {
437 if (bindings == null || bindings.size() == 0) {
438 return this;
439 }
440 final List<Rule> rewrittenRules = new ArrayList<>();
441 for (final Rule rule : this.rules) {
442 rewrittenRules.add(rule.rewriteVariables(bindings));
443 }
444 return new Ruleset(rewrittenRules, this.metaVocabularyTerms);
445 }
446
447
448
449
450
451
452
453
454 public Ruleset mergeSameWhereExpr() {
455 final List<Rule> rules = Rule.mergeSameWhereExpr(this.rules);
456 return rules.size() == this.rules.size() ? this : new Ruleset(rules,
457 this.metaVocabularyTerms);
458 }
459
460
461
462
463 @Override
464 public boolean equals(final Object object) {
465 if (object == this) {
466 return true;
467 }
468 if (!(object instanceof Ruleset)) {
469 return false;
470 }
471 final Ruleset other = (Ruleset) object;
472 return this.rules.equals(other.rules)
473 && this.metaVocabularyTerms.equals(other.metaVocabularyTerms);
474 }
475
476
477
478
479
480 @Override
481 public int hashCode() {
482 if (this.hash == 0) {
483 this.hash = Objects.hash(this.rules, this.metaVocabularyTerms);
484 }
485 return this.hash;
486 }
487
488
489
490
491
492 @Override
493 public String toString() {
494 final StringBuilder builder = new StringBuilder();
495 builder.append("META-VOCABULARY TERMS (").append(this.metaVocabularyTerms.size())
496 .append("):");
497 for (final URI metaVocabularyTerm : this.metaVocabularyTerms) {
498 builder.append("\n").append(
499 Statements.formatValue(metaVocabularyTerm, Namespaces.DEFAULT));
500 }
501 builder.append("\n\nRULES (").append(this.rules.size()).append("):");
502 for (final Rule rule : this.rules) {
503 builder.append("\n").append(rule);
504 }
505 return builder.toString();
506 }
507
508
509
510
511
512
513
514
515
516 public <T extends Collection<? super Statement>> T toRDF(final T output) {
517
518
519 final ValueFactory vf = Statements.VALUE_FACTORY;
520 for (final URI metaVocabularyTerm : this.metaVocabularyTerms) {
521 vf.createStatement(metaVocabularyTerm, RDF.TYPE, RR.META_VOCABULARY_TERM);
522 }
523
524
525 for (final Rule rule : this.rules) {
526 rule.toRDF(output);
527 }
528 return output;
529 }
530
531
532
533
534
535
536
537
538
539
540 public static Ruleset fromRDF(final Iterable<Statement> model) {
541
542
543 final List<URI> metaVocabularyTerms = new ArrayList<>();
544 for (final Statement stmt : model) {
545 if (stmt.getSubject() instanceof URI && RDF.TYPE.equals(stmt.getPredicate())
546 && RR.META_VOCABULARY_TERM.equals(stmt.getObject())) {
547 metaVocabularyTerms.add((URI) stmt.getSubject());
548 }
549 }
550
551
552 final List<Rule> rules = Rule.fromRDF(model);
553
554
555 return new Ruleset(rules, metaVocabularyTerms);
556 }
557
558
559
560
561
562
563
564
565
566
567 public static Ruleset fromRDF(final String location) {
568 final String url = IO.extractURL(location).toString();
569 final RDFSource rulesetSource = RDFSources.read(true, true, null, null, url);
570 return Ruleset.fromRDF(rulesetSource);
571 }
572
573
574
575
576
577
578
579
580
581
582 public static Ruleset merge(final Ruleset... rulesets) {
583 if (rulesets.length == 0) {
584 return new Ruleset(Collections.emptyList(), Collections.emptyList());
585 } else if (rulesets.length == 1) {
586 return rulesets[0];
587 } else {
588 final List<URI> metaVocabularyTerms = new ArrayList<>();
589 final List<Rule> rules = new ArrayList<>();
590 for (final Ruleset ruleset : rulesets) {
591 metaVocabularyTerms.addAll(ruleset.getMetaVocabularyTerms());
592 rules.addAll(ruleset.getRules());
593 }
594 return new Ruleset(rules, metaVocabularyTerms);
595 }
596 }
597
598 private static final class RuleSplit {
599
600 final Rule rule;
601
602 @Nullable
603 final TupleExpr tboxDeleteExpr;
604
605 @Nullable
606 final TupleExpr aboxDeleteExpr;
607
608 @Nullable
609 final TupleExpr tboxInsertExpr;
610
611 @Nullable
612 final TupleExpr aboxInsertExpr;
613
614 @Nullable
615 final TupleExpr tboxWhereExpr;
616
617 @Nullable
618 final TupleExpr aboxWhereExpr;
619
620 RuleSplit(final Rule rule, final Set<URI> terms) {
621 try {
622 final TupleExpr[] deleteExprs = Algebra.splitTupleExpr(rule.getDeleteExpr(),
623 terms, -1);
624 final TupleExpr[] insertExprs = Algebra.splitTupleExpr(rule.getInsertExpr(),
625 terms, -1);
626 final TupleExpr[] whereExprs = Algebra.splitTupleExpr(
627 Algebra.explodeFilters(rule.getWhereExpr()), terms, 1);
628
629 this.rule = rule;
630 this.tboxDeleteExpr = deleteExprs[0];
631 this.aboxDeleteExpr = deleteExprs[1];
632 this.tboxInsertExpr = insertExprs[0];
633 this.aboxInsertExpr = insertExprs[1];
634 this.tboxWhereExpr = whereExprs[0];
635 this.aboxWhereExpr = whereExprs[1];
636
637 LOGGER.trace("{}", this);
638
639 } catch (final Throwable ex) {
640 throw new IllegalArgumentException("Cannot split rule " + rule.getID(), ex);
641 }
642 }
643
644 @Override
645 public String toString() {
646 final StringBuilder builder = new StringBuilder();
647 builder.append("Splitting of rule ").append(this.rule.getID());
648 toStringHelper(builder, "\n DELETE original: ", this.rule.getDeleteExpr());
649 toStringHelper(builder, "\n DELETE tbox: ", this.tboxDeleteExpr);
650 toStringHelper(builder, "\n DELETE abox: ", this.aboxDeleteExpr);
651 toStringHelper(builder, "\n INSERT original: ", this.rule.getInsertExpr());
652 toStringHelper(builder, "\n INSERT tbox: ", this.tboxInsertExpr);
653 toStringHelper(builder, "\n INSERT abox: ", this.aboxInsertExpr);
654 toStringHelper(builder, "\n WHERE original: ", this.rule.getWhereExpr());
655 toStringHelper(builder, "\n WHERE tbox: ", this.tboxWhereExpr);
656 toStringHelper(builder, "\n WHERE abox: ", this.aboxWhereExpr);
657 return builder.toString();
658 }
659
660 private void toStringHelper(final StringBuilder builder, final String prefix,
661 @Nullable final TupleExpr expr) {
662 if (expr != null) {
663 builder.append(prefix).append(Algebra.format(expr));
664 }
665 }
666
667 }
668
669 }