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 }