001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.jena;
014
015import static org.apache.juneau.internal.StringUtils.*;
016import static org.apache.juneau.jena.Constants.*;
017import static org.apache.juneau.jena.RdfCommon.*;
018import static org.apache.juneau.jena.RdfParser.*;
019
020import java.util.*;
021
022import org.apache.juneau.*;
023import org.apache.juneau.parser.*;
024import org.apache.juneau.transform.*;
025import org.apache.juneau.xml.*;
026
027import com.hp.hpl.jena.rdf.model.*;
028import com.hp.hpl.jena.util.iterator.*;
029
030/**
031 * Session object that lives for the duration of a single use of {@link RdfParser}.
032 * 
033 * <p>
034 * This class is NOT thread safe.  
035 * It is typically discarded after one-time use although it can be reused against multiple inputs.
036 */
037@SuppressWarnings({"unchecked", "rawtypes"})
038public class RdfParserSession extends ReaderParserSession {
039
040   private final String rdfLanguage;
041   private final Namespace juneauNs, juneauBpNs;
042   private final Property pRoot, pValue, pType, pRdfType;
043   private final Model model;
044   private final boolean trimWhitespace, looseCollections;
045   private final RDFReader rdfReader;
046   private final Set<Resource> urisVisited = new HashSet<>();
047   private final RdfCollectionFormat collectionFormat;
048
049   /**
050    * Create a new session using properties specified in the context.
051    * 
052    * @param ctx
053    *    The context creating this session object.
054    *    The context contains all the configuration settings for this object.
055    * @param args
056    *    Runtime session arguments.
057    */
058   protected RdfParserSession(RdfParser ctx, ParserSessionArgs args) {
059      super(ctx, args);
060      rdfLanguage = getProperty(RDF_language, String.class, ctx.rdfLanguage);
061      juneauNs = getInstanceProperty(RDF_juneauNs, Namespace.class, ctx.juneauNs);
062      juneauBpNs = getInstanceProperty(RDF_juneauBpNs, Namespace.class, ctx.juneauBpNs);
063      trimWhitespace = getProperty(RDF_trimWhitespace, boolean.class, ctx.trimWhitespace);
064      collectionFormat = getProperty(RDF_collectionFormat, RdfCollectionFormat.class, ctx.collectionFormat);
065      looseCollections = getProperty(RDF_looseCollections, boolean.class, ctx.looseCollections);
066      model = ModelFactory.createDefaultModel();
067      addModelPrefix(juneauNs);
068      addModelPrefix(juneauBpNs);
069      pRoot = model.createProperty(juneauNs.getUri(), RDF_juneauNs_ROOT);
070      pValue = model.createProperty(juneauNs.getUri(), RDF_juneauNs_VALUE);
071      pType = model.createProperty(juneauBpNs.getUri(), RDF_juneauNs_TYPE);
072      pRdfType = model.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
073      rdfReader = model.getReader(rdfLanguage);
074
075      // Note: NTripleReader throws an exception if you try to set any properties on it.
076      if (! rdfLanguage.equals(LANG_NTRIPLE)) {
077         for (Map.Entry<String,Object> e : ctx.jenaSettings.entrySet())
078            rdfReader.setProperty(e.getKey(), e.getValue());
079      }
080   }
081
082   @Override /* Session */
083   public ObjectMap asMap() {
084      return super.asMap()
085         .append("RdfParser", new ObjectMap()
086            .append("collectionFormat", collectionFormat)
087            .append("looseCollections", looseCollections)
088            .append("juneauNs", juneauNs)
089            .append("juneauBpNs", juneauBpNs)
090            .append("rdfLanguage", rdfLanguage)
091            .append("trimWhitespace", trimWhitespace)
092         );
093   }
094
095   @Override /* ReaderParserSession */
096   protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception {
097
098      RDFReader r = rdfReader;
099      r.read(model, pipe.getBufferedReader(), null);
100
101      List<Resource> roots = getRoots(model);
102
103      // Special case where we're parsing a loose collection of resources.
104      if (looseCollections && type.isCollectionOrArray()) {
105         Collection c = null;
106         if (type.isArray() || type.isArgs())
107            c = new ArrayList();
108         else
109            c = (
110               type.canCreateNewInstance(getOuter()) 
111               ? (Collection<?>)type.newInstance(getOuter()) 
112               : new ObjectList(this)
113            );
114
115         int argIndex = 0;
116         for (Resource resource : roots)
117            c.add(parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), resource, 
118               getOuter(), null));
119
120         if (type.isArray() || type.isArgs())
121            return (T)toArray(type, c);
122         return (T)c;
123      }
124
125      if (roots.isEmpty())
126         return null;
127      if (roots.size() > 1)
128         throw new ParseException(loc(), "Too many root nodes found in model:  {0}", roots.size());
129      Resource resource = roots.get(0);
130
131      return parseAnything(type, resource, getOuter(), null);
132   }
133
134   private final void addModelPrefix(Namespace ns) {
135      model.setNsPrefix(ns.getName(), ns.getUri());
136   }
137
138   /*
139    * Decodes the specified string.
140    * If {@link RdfParser#RDF_trimWhitespace} is <jk>true</jk>, the resulting string is trimmed before decoding.
141    * If {@link #isTrimStrings()} is <jk>true</jk>, the resulting string is trimmed after decoding.
142    */
143   private String decodeString(Object o) {
144      if (o == null)
145         return null;
146      String s = o.toString();
147      if (s.isEmpty())
148         return s;
149      if (trimWhitespace)
150         s = s.trim();
151      s = XmlUtils.decode(s, null);
152      if (isTrimStrings())
153         s = s.trim();
154      return s;
155   }
156   
157   /*
158    * Finds the roots in the model using either the "root" property to identify it,
159    *    or by resorting to scanning the model for all nodes with no incoming predicates.
160    */
161   private List<Resource> getRoots(Model m) {
162      List<Resource> l = new LinkedList<>();
163
164      // First try to find the root using the "http://www.apache.org/juneau/root" property.
165      Property root = m.createProperty(juneauNs.getUri(), RDF_juneauNs_ROOT);
166      for (ResIterator i  = m.listResourcesWithProperty(root); i.hasNext();)
167         l.add(i.next());
168
169      if (! l.isEmpty())
170         return l;
171
172      // Otherwise, we need to find all resources that aren't objects.
173      // We want to explicitly ignore statements where the subject
174      // and object are the same node.
175      Set<RDFNode> objects = new HashSet<>();
176      for (StmtIterator i = m.listStatements(); i.hasNext();) {
177         Statement st = i.next();
178         RDFNode subject = st.getSubject();
179         RDFNode object = st.getObject();
180         if (object.isResource() && ! object.equals(subject))
181            objects.add(object);
182      }
183      for (ResIterator i = m.listSubjects(); i.hasNext();) {
184         Resource r = i.next();
185         if (! objects.contains(r))
186            l.add(r);
187      }
188      return l;
189   }
190
191   private <T> BeanMap<T> parseIntoBeanMap(Resource r2, BeanMap<T> m) throws Exception {
192      BeanMeta<T> bm = m.getMeta();
193      RdfBeanMeta rbm = bm.getExtendedMeta(RdfBeanMeta.class);
194      if (rbm.hasBeanUri() && r2.getURI() != null)
195         rbm.getBeanUriProperty().set(m, null, r2.getURI());
196      for (StmtIterator i = r2.listProperties(); i.hasNext();) {
197         Statement st = i.next();
198         Property p = st.getPredicate();
199         String key = decodeString(p.getLocalName());
200         BeanPropertyMeta pMeta = m.getPropertyMeta(key);
201         setCurrentProperty(pMeta);
202         if (pMeta != null) {
203            RDFNode o = st.getObject();
204            ClassMeta<?> cm = pMeta.getClassMeta();
205            if (cm.isCollectionOrArray() && isMultiValuedCollections(pMeta)) {
206               ClassMeta<?> et = cm.getElementType();
207               Object value = parseAnything(et, o, m.getBean(false), pMeta);
208               setName(et, value, key);
209               pMeta.add(m, key, value);
210            } else {
211               Object value = parseAnything(cm, o, m.getBean(false), pMeta);
212               setName(cm, value, key);
213               pMeta.set(m, key, value);
214            }
215         } else if (! (p.equals(pRoot) || p.equals(pType))) {
216            onUnknownProperty(null, key, m, -1, -1);
217         }
218         setCurrentProperty(null);
219      }
220      return m;
221   }
222
223   private boolean isMultiValuedCollections(BeanPropertyMeta pMeta) {
224      if (pMeta != null && pMeta.getExtendedMeta(RdfBeanPropertyMeta.class).getCollectionFormat() != RdfCollectionFormat.DEFAULT)
225         return pMeta.getExtendedMeta(RdfBeanPropertyMeta.class).getCollectionFormat() == RdfCollectionFormat.MULTI_VALUED;
226      return collectionFormat == RdfCollectionFormat.MULTI_VALUED;
227   }
228
229   private <T> T parseAnything(ClassMeta<?> eType, RDFNode n, Object outer, BeanPropertyMeta pMeta) throws Exception {
230
231      if (eType == null)
232         eType = object();
233      PojoSwap<T,Object> swap = (PojoSwap<T,Object>)eType.getPojoSwap(this);
234      BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this);
235      ClassMeta<?> sType = null;
236      if (builder != null)
237         sType = builder.getBuilderClassMeta(this);
238      else if (swap != null)
239         sType = swap.getSwapClassMeta(this);
240      else
241         sType = eType;
242      setCurrentClass(sType);
243
244      if (! sType.canCreateNewInstance(outer)) {
245         if (n.isResource()) {
246            Statement st = n.asResource().getProperty(pType);
247            if (st != null) {
248               String c = st.getLiteral().getString();
249               ClassMeta tcm = getClassMeta(c, pMeta, eType);
250               if (tcm != null)
251                  sType = eType = tcm;
252            }
253         }
254      }
255
256      Object o = null;
257      if (n.isResource() && n.asResource().getURI() != null && n.asResource().getURI().equals(RDF_NIL)) {
258         // Do nothing.  Leave o == null.
259      } else if (sType.isObject()) {
260         if (n.isLiteral()) {
261            o = n.asLiteral().getValue();
262            if (o instanceof String) {
263               o = decodeString(o);
264            }
265         }
266         else if (n.isResource()) {
267            Resource r = n.asResource();
268            if (! urisVisited.add(r))
269               o = r.getURI();
270            else if (r.getProperty(pValue) != null) {
271               o = parseAnything(object(), n.asResource().getProperty(pValue).getObject(), outer, null);
272            } else if (isSeq(r)) {
273               o = new ObjectList(this);
274               parseIntoCollection(r.as(Seq.class), (Collection)o, sType, pMeta);
275            } else if (isBag(r)) {
276               o = new ObjectList(this);
277               parseIntoCollection(r.as(Bag.class), (Collection)o, sType, pMeta);
278            } else if (r.canAs(RDFList.class)) {
279               o = new ObjectList(this);
280               parseIntoCollection(r.as(RDFList.class), (Collection)o, sType, pMeta);
281            } else {
282               // If it has a URI and no child properties, we interpret this as an
283               // external resource, and convert it to just a URL.
284               String uri = r.getURI();
285               if (uri != null && ! r.listProperties().hasNext()) {
286                  o = r.getURI();
287               } else {
288                  ObjectMap m2 = new ObjectMap(this);
289                  parseIntoMap(r, m2, null, null, pMeta);
290                  o = cast(m2, pMeta, eType);
291               }
292            }
293         } else {
294            throw new ParseException(loc(), "Unrecognized node type ''{0}'' for object", n);
295         }
296      } else if (sType.isBoolean()) {
297         o = convertToType(getValue(n, outer), boolean.class);
298      } else if (sType.isCharSequence()) {
299         o = decodeString(getValue(n, outer));
300      } else if (sType.isChar()) {
301         o = decodeString(getValue(n, outer)).charAt(0);
302      } else if (sType.isNumber()) {
303         o = parseNumber(getValue(n, outer).toString(), (Class<? extends Number>)sType.getInnerClass());
304      } else if (sType.isMap()) {
305         Resource r = n.asResource();
306         if (! urisVisited.add(r))
307            return null;
308         Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : new ObjectMap(this));
309         o = parseIntoMap(r, m, eType.getKeyType(), eType.getValueType(), pMeta);
310      } else if (sType.isCollectionOrArray() || sType.isArgs()) {
311         if (sType.isArray() || sType.isArgs())
312            o = new ArrayList();
313         else
314            o = (sType.canCreateNewInstance(outer) ? (Collection<?>)sType.newInstance(outer) : new ObjectList(this));
315         Resource r = n.asResource();
316         if (! urisVisited.add(r))
317            return null;
318         if (isSeq(r)) {
319            parseIntoCollection(r.as(Seq.class), (Collection)o, sType, pMeta);
320         } else if (isBag(r)) {
321            parseIntoCollection(r.as(Bag.class), (Collection)o, sType, pMeta);
322         } else if (r.canAs(RDFList.class)) {
323            parseIntoCollection(r.as(RDFList.class), (Collection)o, sType, pMeta);
324         } else {
325            throw new ParseException("Unrecognized node type ''{0}'' for collection", n);
326         }
327         if (sType.isArray() || sType.isArgs())
328            o = toArray(sType, (Collection)o);
329      } else if (builder != null) {
330         Resource r = n.asResource();
331         if (! urisVisited.add(r))
332            return null;
333         BeanMap<?> bm = toBeanMap(builder.create(this, eType));
334         o = builder.build(this, parseIntoBeanMap(r, bm).getBean(), eType);
335      } else if (sType.canCreateNewBean(outer)) {
336         Resource r = n.asResource();
337         if (! urisVisited.add(r))
338            return null;
339         BeanMap<?> bm = newBeanMap(outer, sType.getInnerClass());
340         o = parseIntoBeanMap(r, bm).getBean();
341      } else if (sType.isUri() && n.isResource()) {
342         o = sType.newInstanceFromString(outer, decodeString(n.asResource().getURI()));
343      } else if (sType.canCreateNewInstanceFromString(outer)) {
344         o = sType.newInstanceFromString(outer, decodeString(getValue(n, outer)));
345      } else if (sType.canCreateNewInstanceFromNumber(outer)) {
346         o = sType.newInstanceFromNumber(this, outer, parseNumber(getValue(n, outer).toString(), sType.getNewInstanceFromNumberClass()));
347      } else if (n.isResource()) {
348         Resource r = n.asResource();
349         Map m = new ObjectMap(this);
350         parseIntoMap(r, m, sType.getKeyType(), sType.getValueType(), pMeta);
351         if (m.containsKey(getBeanTypePropertyName(eType)))
352            o = cast((ObjectMap)m, pMeta, eType);
353         else
354            throw new ParseException(loc(), "Class ''{0}'' could not be instantiated.  Reason: ''{1}''", sType.getInnerClass().getName(), sType.getNotABeanReason());
355      } else {
356         throw new ParseException("Class ''{0}'' could not be instantiated.  Reason: ''{1}''", sType.getInnerClass().getName(), sType.getNotABeanReason());
357      }
358
359      if (swap != null && o != null)
360         o = swap.unswap(this, o, eType);
361
362      if (outer != null)
363         setParent(eType, o, outer);
364
365      return (T)o;
366   }
367
368   private ObjectMap loc() {
369      return getLastLocation();
370   }
371
372   private boolean isSeq(RDFNode n) {
373      if (n.isResource()) {
374         Statement st = n.asResource().getProperty(pRdfType);
375         if (st != null)
376            return RDF_SEQ.equals(st.getResource().getURI());
377      }
378      return false;
379   }
380
381   private boolean isBag(RDFNode n) {
382      if (n.isResource()) {
383         Statement st = n.asResource().getProperty(pRdfType);
384         if (st != null)
385            return RDF_BAG.equals(st.getResource().getURI());
386      }
387      return false;
388   }
389
390   private Object getValue(RDFNode n, Object outer) throws Exception {
391      if (n.isLiteral())
392         return n.asLiteral().getValue();
393      if (n.isResource()) {
394         Statement st = n.asResource().getProperty(pValue);
395         if (st != null) {
396            n = st.getObject();
397            if (n.isLiteral())
398               return n.asLiteral().getValue();
399            return parseAnything(object(), st.getObject(), outer, null);
400         }
401      }
402      throw new ParseException(loc(), "Unknown value type for node ''{0}''", n);
403   }
404
405   private <K,V> Map<K,V> parseIntoMap(Resource r, Map<K,V> m, ClassMeta<K> keyType, 
406         ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws Exception {
407      // Add URI as "uri" to generic maps.
408      if (r.getURI() != null) {
409         K uri = convertAttrToType(m, "uri", keyType);
410         V value = convertAttrToType(m, r.getURI(), valueType);
411         m.put(uri, value);
412      }
413      for (StmtIterator i = r.listProperties(); i.hasNext();) {
414         Statement st = i.next();
415         Property p = st.getPredicate();
416         String key = p.getLocalName();
417         if (! (key.equals("root") && p.getURI().equals(juneauNs.getUri()))) {
418            key = decodeString(key);
419            RDFNode o = st.getObject();
420            K key2 = convertAttrToType(m, key, keyType);
421            V value = parseAnything(valueType, o, m, pMeta);
422            setName(valueType, value, key);
423            m.put(key2, value);
424         }
425
426      }
427      return m;
428   }
429
430   private <E> Collection<E> parseIntoCollection(Container c, Collection<E> l, 
431         ClassMeta<?> type, BeanPropertyMeta pMeta) throws Exception {
432      int argIndex = 0;
433      for (NodeIterator ni = c.iterator(); ni.hasNext();) {
434         E e = (E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), ni.next(), l, pMeta);
435         l.add(e);
436      }
437      return l;
438   }
439
440   private <E> Collection<E> parseIntoCollection(RDFList list, Collection<E> l, 
441         ClassMeta<?> type, BeanPropertyMeta pMeta) throws Exception {
442      int argIndex = 0;
443      for (ExtendedIterator<RDFNode> ni = list.iterator(); ni.hasNext();) {
444         E e = (E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), ni.next(), l, pMeta);
445         l.add(e);
446      }
447      return l;
448   }
449}