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 }