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.vocabulary.RDF;
37  import org.eclipse.rdf4j.model.vocabulary.RDFS;
38  
39  /**
40   * Converts Object into RDF graph encoded to {@link ModelHolder}. Where key is a graph root node and value is a graph
41   * itself inside a {@link Model}.
42   *
43   * This parser performs conversion for three main types:
44   * <ul>
45   * <li>List - Creates RDF:List with bnode as root
46   * <li>Map - Creates simple graph where {key: value} is converted to predicate:object pair
47   * <li>Simple type - Crates RDF Literal
48   * </ul>
49   *
50   * @author Jacek Grzebyta (grzebyta.dev [at] gmail.com)
51   */
52  public class ElementsProcessor {
53  
54      private final ModelFactory modelFactory = new LinkedHashModelFactory();
55      private final YAML vocab = YAML.getInstance();
56      protected ValueFactory vf = SimpleValueFactory.getInstance();
57  
58      private static final ElementsProcessorlementsProcessor.html#ElementsProcessor">ElementsProcessor _ep = new ElementsProcessor();
59  
60      // hide constructor
61      private ElementsProcessor() {
62      }
63  
64      /**
65       * A model holder describes the two required parameters which makes a model useful in further processing: a root
66       * node and model itself.
67       */
68      public static class ModelHolder {
69          private final Value root;
70          private final Model model;
71  
72          public ModelHolder(Value root, Model model) {
73              this.root = root;
74              this.model = model;
75          }
76  
77          public Value getRoot() {
78              return root;
79          }
80  
81          public Model getModel() {
82              return model;
83          }
84      }
85  
86      private ModelHolder asModelHolder(Value v, Model m) {
87          return new ModelHolder(v, m);
88      }
89  
90      /**
91       * Converts a data structure to {@link ModelHolder}. where value is a root node of the data structure and model is a
92       * content of the RDF graph.
93       *
94       * If requested object is simple object (i.e. is neither List or Map) than method returns map entry of relevant
95       * instance of {@link Literal} as key and empty model as value.
96       *
97       * @param namespace
98       *            Namespace for predicates
99       * @param t
100      *            Object (or data structure) converting to RDF graph
101      * @param rootNode
102      *            root node of the graph. If not given then blank node is created.
103      * 
104      * @return instance of {@link ModelHolder},
105      */
106     @SuppressWarnings("unchecked")
107     public ModelHolder asModel(IRI namespace, final Object t, Value rootNode) {
108 
109         if (t instanceof List) {
110             return processList(namespace, (List<Object>) t);
111         } else if (t instanceof Map) {
112             return processMap(namespace, (Map<String, Object>) t, rootNode);
113         } else if (t instanceof String) {
114             return asModelHolder(RDFUtils.makeIRI(t.toString()), modelFactory.createEmptyModel());
115         } else if (t == null) {
116             return asModelHolder(vocab.nullValue, modelFactory.createEmptyModel());
117         } else {
118             return asModelHolder(Literals.createLiteral(vf, t), modelFactory.createEmptyModel());
119         }
120     }
121 
122     /**
123      * This method processes a map with non bnode root.
124      * 
125      * If a map has instantiated root (not a blank node) it is simpler to create SPARQL query.
126      * 
127      * @param ns
128      *            the namespace to associated with statements
129      * @param object
130      *            a populated {@link java.util.Map}
131      * @param parentNode
132      *            a {@link org.eclipse.rdf4j.model.Value} subject node to use in the new statement
133      * 
134      * @return instance of {@link ModelHolder}.
135      */
136     protected ModelHolder processMap(IRI ns, Map<String, Object> object, Value parentNode) {
137         // check if map is empty
138         if (object.isEmpty()) {
139             return null;
140         }
141         HashSet<Object> vals = Sets.newHashSet(object.values());
142         boolean isEmpty = false;
143         if (vals.size() == 1 && vals.contains(null)) {
144             isEmpty = true;
145         }
146         assert ns != null : "Namespace value is null";
147 
148         Model model = modelFactory.createEmptyModel();
149         Value nodeURI = parentNode instanceof BNode ? RDFUtils.makeIRI("node", ns, true) : parentNode;
150 
151         if (!isEmpty) {
152             model.add(vf.createStatement((Resource) nodeURI, RDF.TYPE, vocab.mapping));
153         }
154         object.keySet().forEach((k) -> {
155             /*
156              * False prevents adding _<int> to the predicate. Thus the predicate pattern is: "some string" --->
157              * ns:someString
158              */
159             Resource predicate = RDFUtils.makeIRI(k, ns, false);
160             /*
161              * add map's key as statements: predicate rdf:type rdf:predicate . predicate rdfs:label predicate name
162              */
163             model.add(vf.createStatement(predicate, RDF.TYPE, RDF.PREDICATE));
164             model.add(vf.createStatement(predicate, RDFS.LABEL, RDFUtils.literal(k)));
165             Value subGraphRoot = RDFUtils.makeIRI();
166             ModelHolder valInst = asModel(ns, object.get(k), subGraphRoot);
167             // if asModel returns null than
168             if (valInst != null) {
169                 /*
170                  * Subgraph root node is added always. If subgraph is null that root node is Literal. Otherwise submodel
171                  * in added to the current model.
172                  */
173                 model.add(vf.createStatement((Resource) nodeURI, (IRI) predicate, valInst.root));
174                 if (valInst.model != null) {
175                     model.addAll(valInst.model);
176                 }
177             }
178 
179         });
180         return asModelHolder(nodeURI, model);
181     }
182 
183     protected ModelHolder processList(IRI ns, List<Object> object) {
184 
185         if (object.isEmpty() || object.stream().noneMatch((i) -> {
186             return i != null;
187         })) {
188             return null;
189         }
190         assert ns != null : "Namespace value is null";
191 
192         int objectSize = object.size();
193         Value listRoot = null;
194         Resource prevNode = null;
195         Model finalModel = modelFactory.createEmptyModel();
196         for (int i = 0; i < objectSize; i++) {
197             ModelHolder node = asModel(ns, object.get(i), RDFUtils.bnode());
198             BNode currentNode = RDFUtils.bnode();
199 
200             if (i == 0) {
201                 listRoot = currentNode;
202             }
203 
204             finalModel.add(currentNode, RDF.FIRST, node.root, (Resource[]) null);
205 
206             if (prevNode != null) {
207                 finalModel.add(prevNode, RDF.REST, currentNode, (Resource[]) null);
208             }
209 
210             if (i == objectSize - 1) {
211                 finalModel.add(currentNode, RDF.REST, RDF.NIL, (Resource[]) null);
212             }
213 
214             if (node.model != null) {
215                 finalModel.addAll(node.model);
216             }
217 
218             prevNode = currentNode;
219         }
220 
221         return asModelHolder(listRoot, finalModel);
222     }
223 
224     public static final ElementsProcessor getInstance() {
225         return _ep;
226     }
227 }