View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *  http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.any23.extractor.yaml;
18  
19  import com.google.common.collect.Sets;
20  import java.util.HashSet;
21  import java.util.List;
22  import java.util.Map;
23  import org.apache.any23.rdf.RDFUtils;
24  import org.apache.any23.vocab.YAML;
25  import org.eclipse.rdf4j.model.BNode;
26  import org.eclipse.rdf4j.model.IRI;
27  import org.eclipse.rdf4j.model.Literal;
28  import org.eclipse.rdf4j.model.Model;
29  import org.eclipse.rdf4j.model.ModelFactory;
30  import org.eclipse.rdf4j.model.Resource;
31  import org.eclipse.rdf4j.model.Value;
32  import org.eclipse.rdf4j.model.ValueFactory;
33  import org.eclipse.rdf4j.model.impl.LinkedHashModelFactory;
34  import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
35  import org.eclipse.rdf4j.model.util.Literals;
36  import org.eclipse.rdf4j.model.util.Values;
37  import org.eclipse.rdf4j.model.vocabulary.RDF;
38  import org.eclipse.rdf4j.model.vocabulary.RDFS;
39  
40  /**
41   * Converts Object into RDF graph encoded to {@link ModelHolder}. Where key is a graph root node and value is a graph
42   * itself inside a {@link Model}.
43   *
44   * This parser performs conversion for three main types:
45   * <ul>
46   * <li>List - Creates RDF:List with bnode as root
47   * <li>Map - Creates simple graph where {key: value} is converted to predicate:object pair
48   * <li>Simple type - Crates RDF Literal
49   * </ul>
50   *
51   * @author Jacek Grzebyta (grzebyta.dev [at] gmail.com)
52   */
53  public class ElementsProcessor {
54  
55      private final ModelFactory modelFactory = new LinkedHashModelFactory();
56      private final YAML vocab = YAML.getInstance();
57      protected ValueFactory vf = SimpleValueFactory.getInstance();
58  
59      private static final ElementsProcessor _ep = new ElementsProcessor();
60  
61      // hide constructor
62      private ElementsProcessor() {
63      }
64  
65      /**
66       * A model holder describes the two required parameters which makes a model useful in further processing: a root
67       * node and model itself.
68       */
69      public static class ModelHolder {
70          private final Value root;
71          private final Model model;
72  
73          public ModelHolder(Value root, Model model) {
74              this.root = root;
75              this.model = model;
76          }
77  
78          public Value getRoot() {
79              return root;
80          }
81  
82          public Model getModel() {
83              return model;
84          }
85      }
86  
87      private ModelHolder asModelHolder(Value v, Model m) {
88          return new ModelHolder(v, m);
89      }
90  
91      /**
92       * Converts a data structure to {@link ModelHolder}. where value is a root node of the data structure and model is a
93       * content of the RDF graph.
94       *
95       * If requested object is simple object (i.e. is neither List or Map) than method returns map entry of relevant
96       * instance of {@link Literal} as key and empty model as value.
97       *
98       * @param namespace
99       *            Namespace for predicates
100      * @param t
101      *            Object (or data structure) converting to RDF graph
102      * @param rootNode
103      *            root node of the graph. If not given then blank node is created.
104      * 
105      * @return instance of {@link ModelHolder},
106      */
107     @SuppressWarnings("unchecked")
108     public ModelHolder asModel(IRI namespace, final Object t, Value rootNode) {
109 
110         if (t instanceof List) {
111             return processList(namespace, (List<Object>) t);
112         } else if (t instanceof Map) {
113             return processMap(namespace, (Map<String, Object>) t, rootNode);
114         } else if (t instanceof String) {
115             return asModelHolder(RDFUtils.makeIRI(t.toString()), modelFactory.createEmptyModel());
116         } else if (t == null) {
117             return asModelHolder(vocab.nullValue, modelFactory.createEmptyModel());
118         } else {
119             return asModelHolder(Values.literal(t), modelFactory.createEmptyModel());
120         }
121     }
122 
123     /**
124      * This method processes a map with non bnode root.
125      * 
126      * If a map has instantiated root (not a blank node) it is simpler to create SPARQL query.
127      * 
128      * @param ns
129      *            the namespace to associated with statements
130      * @param object
131      *            a populated {@link java.util.Map}
132      * @param parentNode
133      *            a {@link org.eclipse.rdf4j.model.Value} subject node to use in the new statement
134      * 
135      * @return instance of {@link ModelHolder}.
136      */
137     protected ModelHolder processMap(IRI ns, Map<String, Object> object, Value parentNode) {
138         // check if map is empty
139         if (object.isEmpty()) {
140             return null;
141         }
142         HashSet<Object> vals = Sets.newHashSet(object.values());
143         boolean isEmpty = false;
144         if (vals.size() == 1 && vals.contains(null)) {
145             isEmpty = true;
146         }
147         assert ns != null : "Namespace value is null";
148 
149         Model model = modelFactory.createEmptyModel();
150         Value nodeURI = parentNode instanceof BNode ? RDFUtils.makeIRI("node", ns, true) : parentNode;
151 
152         if (!isEmpty) {
153             model.add(vf.createStatement((Resource) nodeURI, RDF.TYPE, vocab.mapping));
154         }
155         object.keySet().forEach((k) -> {
156             /*
157              * False prevents adding _<int> to the predicate. Thus the predicate pattern is: "some string" --->
158              * ns:someString
159              */
160             Resource predicate = RDFUtils.makeIRI(k, ns, false);
161             /*
162              * add map's key as statements: predicate rdf:type rdf:predicate . predicate rdfs:label predicate name
163              */
164             model.add(vf.createStatement(predicate, RDF.TYPE, RDF.PREDICATE));
165             model.add(vf.createStatement(predicate, RDFS.LABEL, RDFUtils.literal(k)));
166             Value subGraphRoot = RDFUtils.makeIRI();
167             ModelHolder valInst = asModel(ns, object.get(k), subGraphRoot);
168             // if asModel returns null than
169             if (valInst != null) {
170                 /*
171                  * Subgraph root node is added always. If subgraph is null that root node is Literal. Otherwise submodel
172                  * in added to the current model.
173                  */
174                 model.add(vf.createStatement((Resource) nodeURI, (IRI) predicate, valInst.root));
175                 if (valInst.model != null) {
176                     model.addAll(valInst.model);
177                 }
178             }
179 
180         });
181         return asModelHolder(nodeURI, model);
182     }
183 
184     protected ModelHolder processList(IRI ns, List<Object> object) {
185 
186         if (object.isEmpty() || object.stream().noneMatch((i) -> {
187             return i != null;
188         })) {
189             return null;
190         }
191         assert ns != null : "Namespace value is null";
192 
193         int objectSize = object.size();
194         Value listRoot = null;
195         Resource prevNode = null;
196         Model finalModel = modelFactory.createEmptyModel();
197         for (int i = 0; i < objectSize; i++) {
198             ModelHolder node = asModel(ns, object.get(i), RDFUtils.bnode());
199             BNode currentNode = RDFUtils.bnode();
200 
201             if (i == 0) {
202                 listRoot = currentNode;
203             }
204 
205             finalModel.add(currentNode, RDF.FIRST, node.root, (Resource[]) null);
206 
207             if (prevNode != null) {
208                 finalModel.add(prevNode, RDF.REST, currentNode, (Resource[]) null);
209             }
210 
211             if (i == objectSize - 1) {
212                 finalModel.add(currentNode, RDF.REST, RDF.NIL, (Resource[]) null);
213             }
214 
215             if (node.model != null) {
216                 finalModel.addAll(node.model);
217             }
218 
219             prevNode = currentNode;
220         }
221 
222         return asModelHolder(listRoot, finalModel);
223     }
224 
225     public static final ElementsProcessor getInstance() {
226         return _ep;
227     }
228 }