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
41   * graph root node and value is a graph 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
47   * 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 ElementsProcessorlementsProcessor.html#ElementsProcessor">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
67       * in further processing: a root node and model itself.
68       */
69      public 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      
88      private ModelHolder asModelHolder(Value v, Model m) {
89          return new ModelHolder(v,m);
90      }
91  
92      /**
93       * Converts a data structure to {@link ModelHolder}. where value
94       * is a root node of the data structure and model is a content of the RDF
95       * graph.
96       *
97       * If requested object is simple object (i.e. is neither List or Map) than
98       * method returns map entry of relevant instance of {@link Literal} as key
99       * and empty model as value.
100      *
101      * @param namespace Namespace for predicates
102      * @param t Object (or data structure) converting to RDF graph
103      * @param rootNode root node of the graph. If not given then blank node is
104      * created.
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(Literals.createLiteral(vf, t), modelFactory.createEmptyModel());
120         }
121     }
122 
123     /**
124      * This method creates 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      * @param object
130      * @param parentNode
131      * @return instance of {@link ModelHolder}.
132      */
133     protected ModelHolder processMap(IRI ns, Map<String, Object> object, Value parentNode) {
134         // check if map is empty
135         if (object.isEmpty()) {
136             return null;
137         }
138         HashSet<Object> vals = Sets.newHashSet(object.values());
139         boolean isEmpty = false;
140         if (vals.size() == 1 && vals.contains(null)) {
141             isEmpty = true;
142         }
143         assert ns != null : "Namespace value is null";
144 
145         Model model = modelFactory.createEmptyModel();
146         Value nodeURI = parentNode instanceof BNode ? RDFUtils.makeIRI("node", ns, true) : parentNode;
147 
148         if (!isEmpty) {
149             model.add(vf.createStatement((Resource) nodeURI, RDF.TYPE, vocab.mapping));
150         }
151         object.keySet().forEach((k) -> {
152             /* False prevents adding _<int> to the predicate.
153             Thus the predicate pattern is:
154             "some string" ---> ns:someString
155              */
156             Resource predicate = RDFUtils.makeIRI(k, ns, false);
157             /* add map's key as statements:
158             predicate rdf:type rdf:predicate .
159             predicate rdfs:label predicate name
160              */
161             model.add(vf.createStatement(predicate, RDF.TYPE, RDF.PREDICATE));
162             model.add(vf.createStatement(predicate, RDFS.LABEL, RDFUtils.literal(k)));
163             Value subGraphRoot = RDFUtils.makeIRI();
164             ModelHolder valInst = asModel(ns, object.get(k), subGraphRoot);
165             // if asModel returns null than 
166             if (valInst != null) {
167                 /*
168             Subgraph root node is added always. If subgraph is null that root node is Literal.
169             Otherwise submodel in added to the current model.
170                  */
171                 model.add(vf.createStatement((Resource) nodeURI, (IRI) predicate, valInst.root));
172                 if (valInst.model != null) {
173                     model.addAll(valInst.model);
174                 }
175             }
176 
177         });
178         return asModelHolder(nodeURI, model);
179     }
180 
181     protected ModelHolder processList(IRI ns, List<Object> object) {
182 
183         if (object.isEmpty() || object.stream().noneMatch((i) -> {
184             return i != null;
185         })) {
186             return null;
187         }
188         assert ns != null : "Namespace value is null";
189 
190         int objectSize = object.size();
191         Value listRoot = null;
192         Resource prevNode = null;
193         Model finalModel = modelFactory.createEmptyModel();
194         for (int i=0; i < objectSize; i++) {
195             ModelHolder node = asModel(ns, object.get(i), RDFUtils.bnode());
196             BNode currentNode = RDFUtils.bnode();
197 
198             if (i == 0) {
199                 listRoot = currentNode;
200             }
201 
202             finalModel.add(currentNode, RDF.FIRST, node.root, (Resource[]) null);
203 
204             if (prevNode != null) {
205                 finalModel.add(prevNode, RDF.REST, currentNode, (Resource[]) null);
206             }
207 
208             if (i == objectSize-1) {
209                 finalModel.add(currentNode, RDF.REST, RDF.NIL, (Resource[]) null);
210             }
211 
212             if(node.model != null) {
213                 finalModel.addAll(node.model);
214             }
215 
216             prevNode = currentNode;
217         }
218 
219         return asModelHolder(listRoot, finalModel);
220     }
221 
222     public static final ElementsProcessor getInstance() {
223         return _ep;
224     }
225 }