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