Skip to content
This repository was archived by the owner on Oct 8, 2020. It is now read-only.

Commit 1df279b

Browse files
Resolve Test case errors for RDFS
1 parent 85f8104 commit 1df279b

File tree

3 files changed

+112
-66
lines changed

3 files changed

+112
-66
lines changed

sansa-inference-spark/src/main/resources/ont_functional.owl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ FunctionalDataProperty(bar:dataProp1)
9797
HasKey(bar:Cls1 () (bar:dataProp1))
9898

9999
## assertions -- 63
100+
AnnotationAssertion(bar:annProp1 foo:indivB "ZYXW")
100101
SameIndividual(foo:sameAsIndivA foo:indivA)
101102
SameIndividual(foo:indivC foo:sameAsIndivC)
102103
DifferentIndividuals(foo:indivA foo:indivB)

sansa-inference-spark/src/main/scala/net/sansa_stack/inference/spark/forwardchaining/axioms/ForwardRuleReasonerRDFS.scala

Lines changed: 110 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package net.sansa_stack.inference.spark.forwardchaining.axioms
22

33
import scala.collection.JavaConverters._
4-
import java.io.File
5-
import java.util.stream.Collectors
64

75
import org.apache.spark.rdd.RDD
86
import org.apache.spark.sql.SparkSession
@@ -11,7 +9,6 @@ import org.semanticweb.owlapi.model._
119
import org.semanticweb.owlapi.apibinding.OWLManager
1210
import net.sansa_stack.inference.utils.{CollectionUtils, Logging}
1311
import net.sansa_stack.owl.spark.rdd.FunctionalSyntaxOWLAxiomsRDDBuilder
14-
import net.sansa_stack.owl.spark.rdd.OWLAxiomsRDD
1512
import org.apache.spark.broadcast.Broadcast
1613

1714

@@ -34,9 +31,9 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
3431

3532

3633
def apply(axioms: RDD[OWLAxiom]): RDD[OWLAxiom] = {
34+
3735
val manager = OWLManager.createOWLOntologyManager()
3836
val dataFactory = manager.getOWLDataFactory
39-
4037
val axiomsRDD = axioms.cache() // cache this RDD because it will be used quiet often
4138

4239
// ------------ extract the schema data -------------------
@@ -52,7 +49,8 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
5249

5350
// OWLClassAssertionAxiom
5451
val classAsserAxiom = axiomsRDD
55-
.filter(axiom => axiom.getAxiomType.equals(AxiomType.CLASS_ASSERTION)).asInstanceOf[RDD[OWLClassAssertionAxiom]].cache()
52+
.filter(axiom => axiom.getAxiomType.equals(AxiomType.CLASS_ASSERTION))
53+
.asInstanceOf[RDD[OWLClassAssertionAxiom]].cache()
5654

5755
// val cmap: Map[OWLClassExpression, Set[OWLIndividual]] = CollectionUtils
5856
// .toMultiMap(classAsserAxiom.asInstanceOf[RDD[OWLClassAssertionAxiom]]
@@ -78,61 +76,73 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
7876
// OWLSubDataPropertyofAxiom
7977
val subDataPropertyofAxiom = axiomsRDD
8078
.filter(axiom => axiom.getAxiomType.equals(AxiomType.SUB_DATA_PROPERTY))
81-
// .asInstanceOf[RDD[OWLSubDataPropertyOfAxiom]]
79+
8280
// println("\n\nOWLSubDataPropertyofAxioms\n-------\n")
8381
// subDataPropertyofAxiom.collect().foreach(println)
8482

8583
// OWLSubObjectPropertyofAxiom
86-
val subObjectPropertyofAxiom: RDD[OWLAxiom] = axiomsRDD
84+
val subObjectPropertyofAxiom = axiomsRDD
8785
.filter(axiom => axiom.getAxiomType.equals(AxiomType.SUB_OBJECT_PROPERTY))
8886

8987
// println("\n\nOWLSubObjectPropertyofAxioms\n-------\n")
9088
// subObjectPropertyofAxiom.collect().foreach(println)
9189

9290
// OWLObjectPropertyDomainAxiom
93-
val objectProDomain: RDD[OWLAxiom] = axiomsRDD
91+
val objectProDomain = axiomsRDD
9492
.filter(axiom => axiom.getAxiomType.equals(AxiomType.OBJECT_PROPERTY_DOMAIN))
9593

9694
// println("\n\nOWLObjectPropertyDomainAxiom\n-------\n")
9795
// objectProDomain.collect().foreach(println)
9896

9997
// OWLDataPropertyDomainAxiom
100-
val dataProDomain: RDD[OWLAxiom] = axiomsRDD
98+
val dataProDomain = axiomsRDD
10199
.filter(axiom => axiom.getAxiomType.equals(AxiomType.DATA_PROPERTY_DOMAIN))
102100

103101
// println("\n\nOWLDataPropertyDomainAxiom\n-------\n")
104102
// dataProDomain.collect().foreach(println)
105103

104+
// OWLAnnotationPropertyDomainAxiom
105+
val AnnProDomain = axiomsRDD
106+
.filter(axiom => axiom.getAxiomType.equals(AxiomType.ANNOTATION_PROPERTY_DOMAIN))
107+
106108
// OWLDataPropertyRangeAxiom
107-
val dataProRange: RDD[OWLAxiom] = axiomsRDD
109+
val dataProRange = axiomsRDD
108110
.filter(axiom => axiom.getAxiomType.equals(AxiomType.DATA_PROPERTY_RANGE))
109111

110112
// println("\n\nOWLDataPropertyRangeAxiom\n-------\n")
111113
// dataProRange.collect().foreach(println)
112114

113115
// OWLObjectPropertyRangeAxiom
114-
val objProRange: RDD[OWLAxiom] = axiomsRDD
116+
val objProRange = axiomsRDD
115117
.filter(axiom => axiom.getAxiomType.equals(AxiomType.OBJECT_PROPERTY_RANGE))
116118

117119
// println("\n\nOWLObjectPropertyRangeAxiom\n-------\n")
118120
// objProRange.collect().foreach(println)
119121

122+
// OWLAnnotationPropertyRangeAxiom
123+
val AnnProRange = axiomsRDD
124+
.filter(axiom => axiom.getAxiomType.equals(AxiomType.ANNOTATION_PROPERTY_RANGE))
125+
120126
// OWLDataPropertyAssertionAxiom
121-
val dataPropAsserAxiom: RDD[OWLAxiom] = axiomsRDD
127+
val dataPropAsserAxiom = axiomsRDD
122128
.filter(axiom => axiom.getAxiomType.equals(AxiomType.DATA_PROPERTY_ASSERTION))
123129

124130
// println("\n\nOWLDataPropertyAssertionAxiom\n-------\n")
125131
// dataPropAsserAxiom.collect().foreach(println)
126132

127133
// OWLObjectPropertyAssertionAxiom
128-
val objPropAsserAxiom: RDD[OWLAxiom] = axiomsRDD
134+
val objPropAsserAxiom = axiomsRDD
129135
.filter(axiom => axiom.getAxiomType.equals(AxiomType.OBJECT_PROPERTY_ASSERTION))
130136

131137
// println("\n\nOWLObjectPropertyAssertionAxiom\n-------\n")
132138
// objPropAsserAxiom.collect().foreach(println)
133139

140+
// OWLAnnotationPropertyAssertionAxiom
141+
val AnnAsserAxiom = axiomsRDD
142+
.filter(axiom => axiom.getAxiomType.equals(AxiomType.ANNOTATION_ASSERTION))
143+
134144
// OWLSubAnnotationPropertyAssertionAxiom
135-
val subAnnProp: RDD[OWLAxiom] = axiomsRDD
145+
val subAnnProp = axiomsRDD
136146
.filter(axiom => axiom.getAxiomType.equals(AxiomType.SUB_ANNOTATION_PROPERTY_OF))
137147

138148
// println("\n\nOWLSubAnnotationPropertyOAxiom\n-------\n")
@@ -148,7 +158,8 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
148158
*/
149159

150160
val tr = new TransitiveReasoner()
151-
val subClassOfAxiomsTrans = tr.computeTransitiveClosure(subClassofAxiom, AxiomType.SUBCLASS_OF).setName("rdfs11")
161+
val subClassOfAxiomsTrans = tr.computeTransitiveClosure(subClassofAxiom, AxiomType.SUBCLASS_OF)
162+
.setName("rdfs11")
152163

153164
// println("\n Transitive subClassOfAxiom closures: \n----------------\n")
154165
// subClassOfAxiomsTrans.collect().foreach(println)
@@ -160,18 +171,20 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
160171
*/
161172

162173
// val subDataPropertyOfAxiomsTrans = tr.computeSubDataPropertyTransitiveClosure(subDataPropertyofAxiom).setName("rdfs5")
163-
val subDataPropertyOfAxiomsTrans = tr.computeTransitiveClosure(subDataPropertyofAxiom, AxiomType.SUB_DATA_PROPERTY).setName("rdfs5")
174+
val subDataPropertyOfAxiomsTrans = tr.computeTransitiveClosure(subDataPropertyofAxiom, AxiomType.SUB_DATA_PROPERTY)
175+
.setName("rdfs5a")
164176

165177
// println("\n Transitive subDataPropertyOfAxiom closures: \n----------------\n")
166178
// subDataPropertyOfAxiomsTrans.collect().foreach(println)
167179

168-
val subObjectPropertyOfAxiomsTrans = tr.computeTransitiveClosure(subObjectPropertyofAxiom, AxiomType.SUB_OBJECT_PROPERTY).setName("rdfs5")
180+
val subObjectPropertyOfAxiomsTrans = tr.computeTransitiveClosure(subObjectPropertyofAxiom, AxiomType.SUB_OBJECT_PROPERTY)
181+
.setName("rdfs5b")
169182

170183
// println("\n Transitive subObjectPropertyOfAxiom closures: \n----------------\n")
171184
// subObjectPropertyOfAxiomsTrans.collect().foreach(println)
172185

173186
val subAnnotationPropertyOfAxiomsTrans = tr.computeTransitiveClosure(subAnnProp, AxiomType.SUB_ANNOTATION_PROPERTY_OF)
174-
187+
.setName("rdfs5c")
175188
// println("\n Transitive subAnnotationPropertyOfAxiom closures: \n----------------\n")
176189
// subAnnotationPropertyOfAxiomsTrans.collect().foreach(println)
177190

@@ -193,10 +206,15 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
193206
.toMultiMap(subObjectPropertyOfAxiomsTrans.asInstanceOf[RDD[OWLSubObjectPropertyOfAxiom]]
194207
.map(a => (a.getSubProperty, a.getSuperProperty)).collect())
195208

209+
val subAnnPropMap = CollectionUtils
210+
.toMultiMap(subAnnotationPropertyOfAxiomsTrans.asInstanceOf[RDD[OWLSubAnnotationPropertyOfAxiom]]
211+
.map(a => (a.getSubProperty, a.getSuperProperty)).collect())
212+
196213
// distribute the schema data structures by means of shared variables
197214
val subClassOfBC = sc.broadcast(subClassMap)
198215
val subDataPropertyBC = sc.broadcast(subDataPropMap)
199216
val subObjectPropertyBC = sc.broadcast(subObjectPropMap)
217+
val subAnnPropertyBC = sc.broadcast(subAnnPropMap)
200218

201219
// split ontology Axioms based on type, sameAs, and the rest of axioms
202220

@@ -222,8 +240,17 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
222240
.map(s => dataFactory.getOWLObjectPropertyAssertionAxiom(s, a.getSubject, a.getObject)))
223241
.setName("rdfs7b")
224242

225-
SPOAxioms = SPOAxioms.union(RDFS7a.asInstanceOf[RDD[OWLAxiom]])
226-
.union(RDFS7b.asInstanceOf[RDD[OWLAxiom]]).setName("SPO Axioms + rule 7a + rule 7b ")
243+
val RDFS7c = AnnAsserAxiom.asInstanceOf[RDD[OWLAnnotationAssertionAxiom]]
244+
.filter(a => subAnnPropertyBC.value.contains(a.getProperty))
245+
.flatMap(a => subAnnPropertyBC.value(a.getProperty)
246+
.map(s => dataFactory.getOWLAnnotationAssertionAxiom(s, a.getSubject, a.getValue)))
247+
.setName("rdfs7b")
248+
249+
SPOAxioms = sc.union(SPOAxioms,
250+
RDFS7a.asInstanceOf[RDD[OWLAxiom]],
251+
RDFS7b.asInstanceOf[RDD[OWLAxiom]],
252+
RDFS7c.asInstanceOf[RDD[OWLAxiom]])
253+
.setName("SPO Axioms + rule 7a + rule 7b + rule 7c ")
227254

228255
/* 3. Domain and Range inheritance according to rdfs2 and rdfs3 is computed
229256
@@ -233,58 +260,74 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
233260
*/
234261

235262
// val dataProDomain = extractAxiom(SPOAxioms, AxiomType.DATA_PROPERTY_DOMAIN)
236-
val dataDomainMap: Map[OWLDataPropertyExpression, OWLClassExpression] = dataProDomain.asInstanceOf[RDD[OWLDataPropertyDomainAxiom]]
263+
val dataDomainMap = dataProDomain.asInstanceOf[RDD[OWLDataPropertyDomainAxiom]]
237264
.map(a => (a.getProperty, a.getDomain)).collect().toMap
238-
val dataDomainMapBC: Broadcast[Map[OWLDataPropertyExpression, OWLClassExpression]] = sc.broadcast(dataDomainMap)
265+
266+
val dataDomainMapBC = sc.broadcast(dataDomainMap)
239267

240268
val RDFS2a = dataPropAsserAxiom.asInstanceOf[RDD[OWLDataPropertyAssertionAxiom]]
241269
.filter(a => dataDomainMapBC.value.contains(a.getProperty))
242270
.map(a => dataFactory.getOWLClassAssertionAxiom(dataDomainMapBC.value(a.getProperty), a.getSubject))
243271
.setName("rdfs2a")
244272

245-
val objDomainMap: Map[OWLObjectPropertyExpression, OWLClassExpression] = objectProDomain.asInstanceOf[RDD[OWLObjectPropertyDomainAxiom]]
273+
val objDomainMap = objectProDomain.asInstanceOf[RDD[OWLObjectPropertyDomainAxiom]]
246274
.map(a => (a.getProperty, a.getDomain)).collect().toMap
247275

248-
val objDomainMapBC: Broadcast[Map[OWLObjectPropertyExpression, OWLClassExpression]] = sc.broadcast(objDomainMap)
276+
val objDomainMapBC = sc.broadcast(objDomainMap)
249277

250278
val RDFS2b = objPropAsserAxiom.asInstanceOf[RDD[OWLObjectPropertyAssertionAxiom]]
251279
.filter(a => objDomainMapBC.value.contains(a.getProperty))
252280
.map(a => dataFactory.getOWLClassAssertionAxiom(objDomainMapBC.value(a.getProperty), a.getSubject))
253281
.setName("rdfs2b")
254282

283+
val AnnDomainMap = AnnProDomain.asInstanceOf[RDD[OWLAnnotationPropertyDomainAxiom]]
284+
.map(a => (a.getProperty, a.getDomain)).collect().toMap
285+
286+
val AnnDomainMapBC = sc.broadcast(AnnDomainMap)
287+
288+
val RDFS2c = AnnAsserAxiom.asInstanceOf[RDD[OWLAnnotationAssertionAxiom]]
289+
.filter(a => AnnDomainMapBC.value.contains(a.getProperty))
290+
.map(a => dataFactory
291+
.getOWLClassAssertionAxiom(dataFactory.getOWLClass(AnnDomainMapBC.value(a.getProperty)),
292+
dataFactory.getOWLNamedIndividual(a.getSubject.toString)))
293+
.setName("rdfs2c")
294+
295+
// println("\n Annotation domain: \n----------------\n")
296+
// RDFS2c.collect().foreach(println)
297+
298+
255299
/* rule 5: --> rule 5a, rule 5b
256300
257301
rdfs3: a rdfs:range x . y a z --> z rdf:type x .
258302
*/
259303

260-
val dataRangeMap: Map[OWLDataPropertyExpression, OWLDataRange] = dataProRange.asInstanceOf[RDD[OWLDataPropertyRangeAxiom]]
304+
val dataRangeMap = dataProRange.asInstanceOf[RDD[OWLDataPropertyRangeAxiom]]
261305
.map(a => (a.getProperty, a.getRange)).collect().toMap
262-
val dataRangeMapBC: Broadcast[Map[OWLDataPropertyExpression, OWLDataRange]] = sc.broadcast(dataRangeMap)
306+
val dataRangeMapBC = sc.broadcast(dataRangeMap)
263307

264308
val RDFS3a = dataPropAsserAxiom.asInstanceOf[RDD[OWLDataPropertyAssertionAxiom]]
265309
.filter(a => dataRangeMapBC.value.contains(a.getProperty) && !a.getObject.isLiteral) // Add checking for non-literals
266310
.map(a => dataFactory.getOWLClassAssertionAxiom
267-
(dataRangeMapBC.value(a.getProperty).asInstanceOf[OWLClassExpression], a.getObject.asInstanceOf[OWLIndividual]))
311+
(dataRangeMapBC.value(a.getProperty).asInstanceOf[OWLClassExpression],
312+
a.getObject.asInstanceOf[OWLIndividual]))
268313
.setName("rdfs3a")
269314

270-
val objRangeMap: Map[OWLObjectPropertyExpression, OWLClassExpression] = objProRange.asInstanceOf[RDD[OWLObjectPropertyRangeAxiom]]
315+
val objRangeMap = objProRange.asInstanceOf[RDD[OWLObjectPropertyRangeAxiom]]
271316
.map(a => (a.getProperty, a.getRange)).collect().toMap
272317

273-
val objRangeMapBC: Broadcast[Map[OWLObjectPropertyExpression, OWLClassExpression]] = sc.broadcast(objRangeMap)
318+
val objRangeMapBC = sc.broadcast(objRangeMap)
274319

275320
val RDFS3b = objPropAsserAxiom.asInstanceOf[RDD[OWLObjectPropertyAssertionAxiom]]
276321
.filter(a => objRangeMapBC.value.contains(a.getProperty)) // Add checking for non-literals
277322
.map(a => dataFactory.getOWLClassAssertionAxiom(objRangeMapBC.value(a.getProperty), a.getObject))
278323
.setName("rdfs3b")
279324

280325
// rdfs2 and rdf3 generate classAssertionAxiom which we will add to typeAxioms
281-
val axiome23ab = RDFS2a.union(RDFS2b).union(RDFS3a)
282-
.union(RDFS3b).distinct(parallelism)
283-
.asInstanceOf[RDD[OWLAxiom]]
284-
.setName("rdfs2a + rdfs2b + rdfs3a + rdfs3b")
285-
286-
typeAxioms = typeAxioms.union(axiome23ab).distinct.setName("classAssertion + rdfs2ab + rdfs3ab")
326+
val axiome23abc = sc.union(RDFS2a, RDFS2b, RDFS2c, RDFS3a, RDFS3b)
327+
.distinct(parallelism).asInstanceOf[RDD[OWLAxiom]]
328+
.setName("rdfs2a + rdfs2b + rdfs2c+ rdfs3a + rdfs3b")
287329

330+
typeAxioms = typeAxioms.union(axiome23abc).distinct.setName("classAssertion + rdfs2abc + rdfs3ab")
288331

289332
// 4. SubClass inheritance according to rdfs9
290333
/* rule 6
@@ -294,7 +337,8 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
294337

295338
val RDFs9 = typeAxioms.asInstanceOf[RDD[OWLClassAssertionAxiom]]
296339
.filter(a => subClassOfBC.value.contains(a.getClassExpression))
297-
.flatMap(a => subClassOfBC.value(a.getClassExpression).map(s => dataFactory.getOWLClassAssertionAxiom(s, a.getIndividual)))
340+
.flatMap(a => subClassOfBC.value(a.getClassExpression)
341+
.map(s => dataFactory.getOWLClassAssertionAxiom(s, a.getIndividual)))
298342
.setName("rdfs9")
299343

300344
typeAxioms = typeAxioms.union(RDFs9.asInstanceOf[RDD[OWLAxiom]])
@@ -317,32 +361,33 @@ class ForwardRuleReasonerRDFS(sc: SparkContext, parallelism: Int = 2) extends Lo
317361
}
318362
}
319363

320-
// object ForwardRuleReasonerRDFS{
321-
//
322-
// def main(args: Array[String]): Unit = {
323-
//
324-
// val input = getClass.getResource("/ont_functional.owl").getPath
325-
//
326-
// println("=====================================")
327-
// println("| OWLAxioms Forward Rule Reasoner |")
328-
// println("=====================================")
329-
//
330-
// val sparkSession = SparkSession.builder
331-
// .master("local[*]")
332-
// .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
333-
// // .config("spark.kryo.registrator", "net.sansa_stack.inference.spark.forwardchaining.axioms.Registrator")
334-
// .appName("OWL Axioms Forward Rule Reasoner")
335-
// .getOrCreate()
336-
//
337-
// val sc: SparkContext = sparkSession.sparkContext
338-
//
339-
// // Call the functional syntax OWLAxiom builder
340-
//
341-
// var owlAxiomsRDD: OWLAxiomsRDD = FunctionalSyntaxOWLAxiomsRDDBuilder.build(sparkSession, input)
342-
// owlAxiomsRDD.collect().foreach(println)
343-
//
344-
// val ruleReasoner = new ForwardRuleReasonerRDFS(sc, 2).apply(owlAxiomsRDD, input)
345-
//
346-
// sparkSession.stop
347-
// }
348-
// }
364+
object ForwardRuleReasonerRDFS{
365+
366+
def main(args: Array[String]): Unit = {
367+
368+
val input = getClass.getResource("/ont_functional.owl").getPath
369+
370+
println("=====================================")
371+
println("| OWLAxioms Forward Rule Reasoner |")
372+
println("=====================================")
373+
374+
val sparkSession = SparkSession.builder
375+
.master("local[*]")
376+
.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
377+
// .config("spark.kryo.registrator", "net.sansa_stack.inference.spark.forwardchaining.axioms.Registrator")
378+
.appName("OWL Axioms Forward Rule Reasoner")
379+
.getOrCreate()
380+
381+
val sc: SparkContext = sparkSession.sparkContext
382+
383+
// Call the functional syntax OWLAxiom builder
384+
385+
var owlAxiomsRDD = FunctionalSyntaxOWLAxiomsRDDBuilder.build(sparkSession, input)
386+
// owlAxiomsRDD.collect().foreach(println)
387+
388+
val ruleReasoner = new ForwardRuleReasonerRDFS(sc, 2)
389+
val res: RDD[OWLAxiom] = ruleReasoner(owlAxiomsRDD)
390+
391+
sparkSession.stop
392+
}
393+
}

sansa-inference-spark/src/test/scala/net/sansa_stack/inference/spark/forwardchaining/axioms/ForwardRuleReasonerRDFSTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class ForwardRuleReasonerRDFSTest extends FunSuite with SharedSparkContext with
2727
val owlAxiomsRDD: OWLAxiomsRDD = FunctionalSyntaxOWLAxiomsRDDBuilder.build(spark, input)
2828
val reasoner: RDD[OWLAxiom] = new ForwardRuleReasonerRDFS(sc, 4)(owlAxiomsRDD)
2929

30-
assert(reasoner.count() == 16)
30+
assert(reasoner.count() == 17)
3131

3232
}
3333

0 commit comments

Comments
 (0)