001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.jena; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021import static org.apache.juneau.commons.utils.StringUtils.*; 022import static org.apache.juneau.marshall.jena.Constants.*; 023 024import java.io.*; 025import java.util.*; 026import java.util.concurrent.atomic.*; 027 028import org.apache.jena.rdf.model.*; 029import org.apache.jena.riot.*; 030import org.apache.jena.util.iterator.*; 031import org.apache.juneau.commons.bean.*; 032import org.apache.juneau.commons.reflect.*; 033import org.apache.juneau.marshall.*; 034import org.apache.juneau.marshall.collections.*; 035import org.apache.juneau.marshall.parser.*; 036import org.apache.juneau.marshall.swap.spi.*; 037import org.apache.juneau.marshall.xml.*; 038 039/** 040 * Session object that lives for the duration of a single use of {@link RdfStreamParser}. 041 * 042 * <h5 class='section'>Notes:</h5><ul> 043 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 044 * </ul> 045 * 046 * <h5 class='section'>See Also:</h5><ul> 047 * <li class='link'>{doc jmr.RdfDetails} 048 * </ul> 049 */ 050@SuppressWarnings({ 051 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 052 "java:S3776", // Cognitive complexity acceptable for RDF stream parser state machine methods 053 "java:S6541", // Brain Method complexity acceptable for core RDF stream parser dispatch logic 054 "rawtypes", // Raw types necessary for generic type handling 055 "unchecked" // Type erasure requires unchecked casts 056}) 057public class RdfStreamParserSession extends InputStreamParserSession { 058 059 /** 060 * Builder class. 061 */ 062 public static class Builder extends InputStreamParserSession.Builder<Builder> { 063 064 private final RdfStreamParser ctx; 065 066 /** 067 * Constructor 068 * 069 * @param ctx The context creating this session. 070 * <br>Cannot be <jk>null</jk>. 071 */ 072 protected Builder(RdfStreamParser ctx) { 073 super(reqnn("ctx", ctx)); 074 this.ctx = ctx; 075 } 076 077 @Override 078 public RdfStreamParserSession build() { 079 return new RdfStreamParserSession(this); 080 } 081 082 } 083 084 /** 085 * Creates a new builder for this object. 086 * 087 * @param ctx The context creating this session. 088 * <br>Cannot be <jk>null</jk>. 089 * @return A new builder. 090 */ 091 public static Builder create(RdfStreamParser ctx) { 092 return new Builder(reqnn("ctx", ctx)); 093 } 094 095 private final Model model; 096 private final org.apache.jena.rdf.model.Property pRoot; 097 private final org.apache.jena.rdf.model.Property pValue; 098 private final org.apache.jena.rdf.model.Property pType; 099 private final org.apache.jena.rdf.model.Property pRdfType; 100 private final Lang lang; 101 private final RdfStreamParser ctx; 102 private final Set<Resource> urisVisited = st(); 103 104 /** 105 * Constructor. 106 * 107 * @param builder The builder for this object. 108 * <br>Cannot be <jk>null</jk>. 109 */ 110 protected RdfStreamParserSession(Builder builder) { 111 super(builder); 112 ctx = builder.ctx; 113 model = ModelFactory.createDefaultModel(); 114 addModelPrefix(ctx.getJuneauNs()); 115 addModelPrefix(ctx.getJuneauBpNs()); 116 pRdfType = model.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); 117 pRoot = model.createProperty(ctx.getJuneauNs().getUri(), "root"); 118 pType = model.createProperty(ctx.getJuneauBpNs().getUri(), "_type"); 119 pValue = model.createProperty(ctx.getJuneauNs().getUri(), "value"); 120 121 // Map legacy language names to RIOT Lang 122 var langName = ctx.getLanguage(); 123 lang = toLang(langName); 124 if (lang == null) 125 throw isex("Unknown RDF language: %s", langName); 126 } 127 128 @Override /* InputStreamParserSession */ 129 public boolean hasNativeBytes() { 130 // The RDF graph layer has no native byte-array literal type — byte[] is surfaced either as an 131 // xsd:base64Binary typed literal (at BinaryFormat.NOT_SET) or as a plain string literal 132 // carrying the configured BinaryFormat's text wire form (at any other BinaryFormat; BinarySwap fires 133 // to reverse the wire form back to byte[]). 134 return false; 135 } 136 137 private static Lang toLang(String langName) { 138 var lang = RDFLanguages.nameToLang(langName); 139 if (lang != null) 140 return lang; 141 if (eq(langName, "RDF/PROTO")) // HTT - not registered in Jena's RDFLanguages 142 return Lang.RDFPROTO; 143 return null; 144 } 145 146 private void addModelPrefix(Namespace ns) { 147 model.setNsPrefix(ns.getName(), ns.getUri()); 148 } 149 150 /** 151 * Decodes the specified string. 152 * If {@link RdfStreamParser#isTrimWhitespace} is <jk>true</jk>, the resulting string is trimmed before decoding. 153 * If {@link #isTrimStrings()} is <jk>true</jk>, the resulting string is trimmed after decoding. 154 */ 155 String decodeString(Object o) { 156 if (o == null) 157 return null; 158 var s = o.toString(); 159 if (s.isEmpty()) 160 return s; 161 if (isTrimWhitespace()) // HTT: RdfStreamParser.Builder only exposes language(); trimWhitespace is always false for stream parsers. 162 s = s.trim(); // HTT: unreachable — see guard above. 163 s = XmlUtils.decode(s, null); 164 if (isTrimStrings()) 165 s = s.trim(); 166 return s; 167 } 168 169 /* 170 * Finds the roots in the model using either the "root" property to identify it, 171 * or by resorting to scanning the model for all nodes with no incoming predicates. 172 */ 173 List<Resource> getRoots(Model m) { 174 var l = new LinkedList<Resource>(); 175 176 // First try to find the root using the "http://www.apache.org/juneau/root" property. 177 var root = m.createProperty(getJuneauNs().getUri(), "root"); 178 for (ResIterator i = m.listResourcesWithProperty(root); i.hasNext();) 179 l.add(i.next()); 180 181 if (! l.isEmpty()) 182 return l; 183 184 // Otherwise, we need to find all resources that aren't objects. 185 // We want to explicitly ignore statements where the subject 186 // and object are the same node. 187 var objects = new HashSet<RDFNode>(); 188 for (StmtIterator i = m.listStatements(); i.hasNext();) { 189 var st = i.next(); 190 var subject = st.getSubject(); 191 var object = st.getObject(); 192 if (object.isResource() && ! object.equals(subject)) 193 objects.add(object); 194 } 195 for (ResIterator i = m.listSubjects(); i.hasNext();) { 196 var r = i.next(); 197 if (! objects.contains(r)) 198 l.add(r); 199 } 200 return l; 201 } 202 203 Object getValue(RDFNode n, Object outer) throws ParseException { 204 if (n.isLiteral()) 205 return n.asLiteral().getValue(); 206 if (n.isResource()) { // HTT: RDFNode has only two concrete kinds (Literal, Resource); this is always true once isLiteral() is false. 207 var st = n.asResource().getProperty(pValue); 208 if (nn(st)) { 209 n = st.getObject(); 210 if (n.isLiteral()) 211 return n.asLiteral().getValue(); 212 return readAnything(object(), st.getObject(), outer, null); 213 } 214 } 215 throw new ParseException(this, "Unknown value type for node '%s'", n); 216 } 217 218 boolean isBag(RDFNode n) { 219 if (n.isResource()) { 220 var st = n.asResource().getProperty(pRdfType); 221 if (nn(st)) 222 return RDF_BAG.equals(st.getResource().getURI()); 223 } 224 return false; 225 } 226 227 boolean isMultiValuedCollections(BeanPropertyMeta pMeta) { 228 var bpRdf = (pMeta == null ? RdfBeanPropertyMeta.DEFAULT : getRdfBeanPropertyMeta(pMeta)); 229 230 if (bpRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT) 231 return bpRdf.getCollectionFormat() == RdfCollectionFormat.MULTI_VALUED; 232 233 return getCollectionFormat() == RdfCollectionFormat.MULTI_VALUED; // HTT: RdfStreamParser.Builder only exposes language(); the global collection format is always DEFAULT (never MULTI_VALUED) for stream parsers. 234 } 235 236 boolean isSeq(RDFNode n) { 237 if (n.isResource()) { 238 var st = n.asResource().getProperty(pRdfType); 239 if (nn(st)) 240 return RDF_SEQ.equals(st.getResource().getURI()); 241 } 242 return false; 243 } 244 245 <T> T readAnything(ClassMeta<?> eType, RDFNode n, Object outer, BeanPropertyMeta pMeta) throws ParseException, ExecutableException { 246 247 if (eType == null) 248 eType = object(); 249 var swap = (ObjectSwap<T,Object>)eType.getSwap(this); 250 var builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this); 251 ClassMeta<?> sType; 252 if (nn(builder)) 253 sType = builder.getBuilderClassMeta(this); 254 else if (nn(swap)) 255 sType = swap.getSwapClassMeta(this); 256 else 257 sType = eType; 258 259 if (sType.isOptional()) 260 return (T)o(readAnything(eType.getElementType(), n, outer, pMeta)); 261 262 setCurrentClass(sType); 263 264 if (! sType.canCreateNewInstance(outer) && n.isResource()) { 265 var st = n.asResource().getProperty(pType); 266 if (nn(st)) { 267 var c = st.getLiteral().getString(); 268 var tcm = getClassMeta(c, pMeta, eType); 269 if (nn(tcm)) 270 sType = eType = tcm; 271 } 272 } 273 274 Object o = null; 275 if (n.isResource() && nn(n.asResource().getURI()) && n.asResource().getURI().equals(RDF_NIL)) { 276 // Do nothing. Leave o == null. 277 } else if (sType.isObject()) { 278 if (n.isLiteral()) { 279 o = n.asLiteral().getValue(); 280 if (o instanceof String o2) { 281 o = decodeString(o2); 282 } 283 } else if (n.isResource()) { // HTT: RDFNode has only two concrete kinds (Literal, Resource); this is always true once isLiteral() (above) is false — the else below is defensive dead code. 284 var r = n.asResource(); 285 if (! urisVisited.add(r)) 286 o = r.getURI(); 287 else if (nn(r.getProperty(pValue))) { 288 o = readAnything(object(), n.asResource().getProperty(pValue).getObject(), outer, null); 289 } else if (isSeq(r)) { 290 o = new JsonList(this); 291 readIntoCollection(r.as(Seq.class), (Collection)o, sType, pMeta); 292 } else if (isBag(r)) { 293 o = new JsonList(this); 294 readIntoCollection(r.as(Bag.class), (Collection)o, sType, pMeta); 295 } else if (r.canAs(RDFList.class)) { 296 o = new JsonList(this); 297 readIntoCollection(r.as(RDFList.class), (Collection)o, sType, pMeta); 298 } else { 299 // If it has a URI and no child properties, we interpret this as an 300 // external resource, and convert it to just a URL. 301 var uri = r.getURI(); 302 if (nn(uri) && ! r.listProperties().hasNext()) { 303 o = r.getURI(); 304 } else { 305 var m2 = new JsonMap(this); 306 readIntoMap(r, m2, null, null, pMeta); 307 o = cast(m2, pMeta, eType); 308 } 309 } 310 } else { 311 throw new ParseException(this, "Unrecognized node type '%s' for object", n); // HTT: unreachable — see guard above; n is always Literal or Resource. 312 } 313 } else if (nn(builder)) { 314 var r = n.asResource(); 315 if (! urisVisited.add(r)) 316 return null; 317 var bm = toBeanMap(builder.create(this, eType)); 318 o = builder.build(this, checkRequired(readIntoBeanMap(r, bm)).getBean(), eType); 319 } else if (sType.canCreateNewBean(outer)) { 320 var r = n.asResource(); 321 if (! urisVisited.add(r)) 322 return null; 323 var bm = newBeanMap(outer, sType.inner()); 324 o = checkRequired(readIntoBeanMap(r, bm)).getBean(); 325 } else if (sType.isMap()) { 326 var r = n.asResource(); 327 if (! urisVisited.add(r)) 328 return null; 329 var m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : newGenericMap(sType)); 330 o = readIntoMap(r, m, eType.getKeyType(), eType.getValueType(), pMeta); 331 } else if (sType.isByteArray()) { 332 // Correctness gate: byte[] satisfies isArray()/isCollectionOrArray() too, but RDF emits 333 // byte[] as a typed base64 literal (not as a Seq/Bag/RDFList of byte resources). This 334 // branch MUST stay above isCollectionOrArray so the literal path fires first. 335 var v = getValue(n, outer); 336 if (v instanceof byte[] b) { 337 o = b; 338 } else if (v != null) { 339 o = Base64.getDecoder().decode(v.toString()); 340 } 341 } else if (sType.isCollectionOrArray() || sType.isArgs()) { // HTT: isArgs() requires method-argument list ClassMeta; not reachable through public parse() API — always false here. 342 if (sType.isArray() || sType.isArgs()) // HTT: isArgs() requires method-argument list ClassMeta; not reachable through public parse() API — always false here. 343 o = list(); 344 else 345 o = (sType.canCreateNewInstance(outer) ? (Collection<?>)sType.newInstance(outer) : new JsonList(this)); 346 var r = n.asResource(); 347 if (! urisVisited.add(r)) 348 return null; 349 if (isSeq(r)) { 350 readIntoCollection(r.as(Seq.class), (Collection)o, sType, pMeta); 351 } else if (isBag(r)) { 352 readIntoCollection(r.as(Bag.class), (Collection)o, sType, pMeta); 353 } else if (r.canAs(RDFList.class)) { 354 readIntoCollection(r.as(RDFList.class), (Collection)o, sType, pMeta); 355 } else { 356 throw new ParseException(this, "Unrecognized node type '%s' for collection", n); 357 } 358 if (sType.isArray() || sType.isArgs()) // HTT: isArgs() requires method-argument list ClassMeta; not reachable through public parse() API — always false here. 359 o = toArray(sType, (Collection)o); 360 } else if (sType.isCharSequence()) { 361 o = decodeString(getValue(n, outer)); 362 } else if (sType.isChar()) { 363 o = parseCharacter(decodeString(getValue(n, outer))); 364 } else if (sType.isNumber()) { 365 o = parseNumber(getValue(n, outer).toString(), (Class<? extends Number>)sType.inner()); 366 } else if (sType.isBoolean()) { 367 o = convertToType(getValue(n, outer), boolean.class); 368 } else if (sType.isUri() && n.isResource()) { 369 // RDF URIs must be reconstructed from the resource's URI string (not from a literal), 370 // so this branch is kept here to fire before the temporal/scalar coercion fallbacks. 371 o = sType.newInstanceFromString(outer, decodeString(n.asResource().getURI())); 372 } else if (sType.isDate()) { 373 o = readDate(getValue(n, outer).toString(), sType); 374 } else if (sType.isCalendar()) { 375 o = readCalendar(getValue(n, outer).toString(), sType); 376 } else if (sType.isTemporal()) { 377 o = readTemporal(getValue(n, outer).toString(), sType); 378 } else if (sType.isDuration()) { 379 o = readDuration(getValue(n, outer).toString()); 380 } else if (sType.isPeriod()) { 381 o = readPeriod(getValue(n, outer).toString()); 382 } else if (sType.canCreateNewInstanceFromString(outer)) { 383 o = sType.newInstanceFromString(outer, decodeString(getValue(n, outer))); 384 } else if (n.isResource()) { 385 var r = n.asResource(); 386 var m = newGenericMap(sType); 387 readIntoMap(r, m, sType.getKeyType(), sType.getValueType(), pMeta); 388 if (m.containsKey(getBeanTypePropertyName(eType))) 389 o = cast((MarshalledMap)m, pMeta, eType); 390 else if (nn(sType.getProxyInvocationHandler())) 391 o = checkRequired(newBeanMap(outer, sType.inner()).load(m)).getBean(); 392 else 393 throw new ParseException(this, "Class '%s' could not be instantiated. Reason: '%s'", cn(sType), sType.getNotABeanReason()); 394 } else { 395 throw new ParseException(this, "Class '%s' could not be instantiated. Reason: '%s'", cn(sType), sType.getNotABeanReason()); 396 } 397 398 if (nn(swap) && nn(o)) 399 o = unswap(swap, o, eType); 400 401 if (nn(outer)) 402 setParent(eType, o, outer); 403 404 return (T)o; 405 } 406 407 <T> BeanMap<T> readIntoBeanMap(Resource r2, BeanMap<T> m) throws ParseException, ExecutableException { 408 var bm = m.getMeta(); 409 var rbm = getRdfBeanMeta(bm); 410 if (rbm.hasBeanUri() && nn(r2.getURI())) 411 rbm.getBeanUriProperty().set(m, null, r2.getURI()); 412 for (StmtIterator i = r2.listProperties(); i.hasNext();) { 413 var st = i.next(); 414 var p = st.getPredicate(); 415 var key = decodeString(p.getLocalName()); 416 var pMeta = m.getPropertyMeta(key); 417 setCurrentProperty(pMeta); 418 if (nn(pMeta)) { 419 var o = st.getObject(); 420 var cm = (ClassMeta<?>) pMeta.getBeanInfo(); 421 if (cm.isCollectionOrArray() && isMultiValuedCollections(pMeta)) { 422 var et = cm.getElementType(); 423 var value = readAnything(et, o, m.getBean(false), pMeta); 424 setName(et, value, key); 425 try { 426 pMeta.add(m, key, value); 427 } catch (BeanRuntimeException e) { 428 onBeanSetterException(pMeta, e); 429 throw e; 430 } 431 } else { 432 var value = readAnything(cm, o, m.getBean(false), pMeta); 433 setName(cm, value, key); 434 try { 435 pMeta.set(m, key, value); 436 } catch (BeanRuntimeException e) { 437 onBeanSetterException(pMeta, e); 438 throw e; 439 } 440 } 441 } else if (! (p.equals(pRoot) || p.equals(pType))) { 442 var o = st.getObject(); 443 var value = readAnything(object(), o, m.getBean(false), null); 444 onUnknownProperty(key, m, value); 445 } 446 setCurrentProperty(null); 447 } 448 return m; 449 } 450 451 <E> Collection<E> readIntoCollection(Container c, Collection<E> l, ClassMeta<?> type, BeanPropertyMeta pMeta) throws ParseException, ExecutableException { 452 int argIndex = 0; 453 for (NodeIterator ni = c.iterator(); ni.hasNext();) { 454 E e = (E)readAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), ni.next(), l, pMeta); // HTT: isArgs() requires method-argument list ClassMeta; not reachable through public parse() API. 455 l.add(e); 456 } 457 return l; 458 } 459 460 <E> Collection<E> readIntoCollection(RDFList list, Collection<E> l, ClassMeta<?> type, BeanPropertyMeta pMeta) throws ParseException, ExecutableException { 461 int argIndex = 0; 462 for (ExtendedIterator<RDFNode> ni = list.iterator(); ni.hasNext();) { 463 E e = (E)readAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), ni.next(), l, pMeta); // HTT: isArgs() requires method-argument list ClassMeta; not reachable through public parse() API. 464 l.add(e); 465 } 466 return l; 467 } 468 469 <K,V> Map<K,V> readIntoMap(Resource r, Map<K,V> m, ClassMeta<K> keyType, ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws ParseException, ExecutableException { 470 // Add URI as "uri" to generic maps. 471 if (nn(r.getURI())) { 472 var uri = convertAttrToType(m, "uri", keyType); 473 var value = convertAttrToType(m, r.getURI(), valueType); 474 m.put(uri, value); 475 } 476 for (StmtIterator i = r.listProperties(); i.hasNext();) { 477 var st = i.next(); 478 var p = st.getPredicate(); 479 var key = p.getLocalName(); 480 if (! (eq(key, "root") && p.equals(pRoot))) { 481 key = decodeString(key); 482 var o = st.getObject(); 483 var key2 = convertAttrToType(m, key, keyType); 484 V value = readAnything(valueType, o, m, pMeta); 485 setName(valueType, value, key); 486 m.put(key2, value); 487 } 488 489 } 490 return m; 491 } 492 493 @SuppressWarnings({ 494 "resource" // Resource management handled by InputStreamParserSession 495 }) 496 @Override /* Overridden from InputStreamParserSession */ 497 protected <T> T doRead(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException { 498 499 RDFDataMgr.read(model, pipe.getInputStream(), lang); 500 501 var roots = getRoots(model); 502 503 // Special case where we're parsing a loose collection of resources. 504 if (isLooseCollections() && type.isCollectionOrArray()) { // HTT: RdfStreamParser.Builder only exposes language(); looseCollections is always false for stream parsers. 505 Collection c; // HTT: unreachable — see guard above. 506 if (type.isArray() || type.isArgs()) // HTT: unreachable — see guard above. 507 c = list(); // HTT: unreachable — see guard above. 508 else 509 c = (type.canCreateNewInstance(getOuter()) ? (Collection<?>)type.newInstance(getOuter()) : new JsonList(this)); // HTT: unreachable — see guard above. 510 511 var argIndex = new AtomicInteger(0); // HTT: unreachable — see guard above. 512 var c2 = c; // HTT: unreachable — see guard above. 513 roots.forEach(x -> c2.add(readAnything(type.isArgs() ? type.getArg(argIndex.getAndIncrement()) : type.getElementType(), x, getOuter(), null))); // HTT: unreachable — see guard above. 514 515 if (type.isArray() || type.isArgs()) // HTT: unreachable — see guard above. 516 return (T)toArray(type, c); // HTT: unreachable — see guard above. 517 return (T)c; // HTT: unreachable — see guard above. 518 } 519 520 if (roots.isEmpty()) 521 return type.isOptional() ? (T)oe() : null; 522 523 if (roots.size() > 1) 524 throw new ParseException(this, "Too many root nodes found in model: %s", roots.size()); 525 var resource = roots.get(0); 526 527 return readAnything(type, resource, getOuter(), null); 528 } 529 530 /** 531 * RDF format for representing collections and arrays. 532 * 533 * @see RdfStreamParser#getCollectionFormat() 534 * @return 535 * RDF format for representing collections and arrays. 536 */ 537 protected final RdfCollectionFormat getCollectionFormat() { return ctx.getCollectionFormat(); } 538 539 /** 540 * Default XML namespace for bean properties. 541 * 542 * @see RdfStreamParser#getJuneauBpNs() 543 * @return 544 * Default XML namespace for bean properties. 545 */ 546 protected final Namespace getJuneauBpNs() { return ctx.getJuneauBpNs(); } 547 548 /** 549 * XML namespace for Juneau properties. 550 * 551 * @see RdfStreamParser#getJuneauNs() 552 * @return 553 * XML namespace for Juneau properties. 554 */ 555 protected final Namespace getJuneauNs() { return ctx.getJuneauNs(); } 556 557 /** 558 * RDF language. 559 * 560 * @see RdfStreamParser#getLanguage() 561 * @return 562 * The RDF language to use. 563 */ 564 protected final String getLanguage() { return ctx.getLanguage(); } 565 566 /** 567 * Returns the language-specific metadata on the specified bean. 568 * 569 * @param bm The bean to return the metadata on. 570 * @return The metadata. 571 */ 572 protected RdfBeanMeta getRdfBeanMeta(BeanMeta<?> bm) { 573 return ctx.getRdfBeanMeta(bm); 574 } 575 576 /** 577 * Returns the language-specific metadata on the specified bean property. 578 * 579 * @param bpm The bean property to return the metadata on. 580 * @return The metadata. 581 */ 582 protected RdfBeanPropertyMeta getRdfBeanPropertyMeta(BeanPropertyMeta bpm) { 583 return ctx.getRdfBeanPropertyMeta(bpm); 584 } 585 586 /** 587 * Returns the language-specific metadata on the specified class. 588 * 589 * @param cm The class to return the metadata on. 590 * @return The metadata. 591 */ 592 protected RdfClassMeta getRdfClassMeta(ClassMeta<?> cm) { 593 return ctx.getRdfClassMeta(cm); 594 } 595 596 /** 597 * Returns the language-specific metadata on the specified bean property. 598 * 599 * @param bpm The bean property to return the metadata on. 600 * @return The metadata. 601 */ 602 protected XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) { 603 return ctx.getXmlBeanPropertyMeta(bpm); 604 } 605 606 /** 607 * Collections should be serialized and parsed as loose collections. 608 * 609 * @see RdfStreamParser#isLooseCollections() 610 * @return 611 * <jk>true</jk> if collections of resources are handled as loose collections of resources in RDF instead of 612 * resources that are children of an RDF collection (e.g. Sequence, Bag). 613 */ 614 protected final boolean isLooseCollections() { return ctx.isLooseCollections(); } 615 616 /** 617 * Trim whitespace from text elements. 618 * 619 * @see RdfStreamParser#isTrimWhitespace() 620 * @return 621 * <jk>true</jk> if whitespace in text elements will be automatically trimmed. 622 */ 623 protected final boolean isTrimWhitespace() { return ctx.isTrimWhitespace(); } 624}