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}