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.IoUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023import static org.apache.juneau.marshall.jena.Constants.*; 024 025import java.io.*; 026import java.util.*; 027import java.util.function.*; 028 029import org.apache.jena.rdf.model.*; 030import org.apache.jena.riot.*; 031import org.apache.juneau.commons.bean.*; 032import org.apache.juneau.commons.utils.*; 033import org.apache.juneau.marshall.*; 034import org.apache.juneau.marshall.serializer.*; 035import org.apache.juneau.marshall.xml.*; 036 037/** 038 * Session object that lives for the duration of a single use of {@link RdfSerializer}. 039 * 040 * <h5 class='section'>Notes:</h5><ul> 041 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 042 * </ul> 043 * 044 * <h5 class='section'>See Also:</h5><ul> 045 * <li class='link'>{doc jmr.RdfDetails} 046 047 * </ul> 048 */ 049@SuppressWarnings({ 050 "java:S110", // Deep inheritance inherent to the RDF serializer session hierarchy. 051 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 052 "java:S2176", // Inheritance depth exceeds 5; necessary to participate in the serializer session hierarchy 053 "java:S3776", // Cognitive complexity acceptable for RDF serializer session methods 054 "java:S6541", // Brain Method complexity acceptable for core RDF serializer dispatch logic 055 "rawtypes", // Raw types necessary for generic type handling 056 "unchecked" // Type erasure requires unchecked casts 057}) 058public class RdfSerializerSession extends WriterSerializerSession { 059 060 /** 061 * Builder class. 062 */ 063 public static class Builder extends WriterSerializerSession.Builder<Builder> { 064 065 private final RdfSerializer ctx; 066 067 /** 068 * Constructor 069 * 070 * @param ctx The context creating this session. 071 * <br>Cannot be <jk>null</jk>. 072 */ 073 protected Builder(RdfSerializer ctx) { 074 super(reqnn("ctx", ctx)); 075 this.ctx = ctx; 076 } 077 078 @Override 079 public RdfSerializerSession build() { 080 return new RdfSerializerSession(this); 081 } 082 083 } 084 085 /** 086 * Maps RDF writer names to property prefixes that apply to them. 087 */ 088 // @formatter:off 089 static final Map<String,String> LANG_PROP_MAP = mb(String.class, String.class) 090 .unmodifiable() 091 .add("RDF/XML", "rdfXml.") 092 .add("RDF/XML-ABBREV", "rdfXml.") 093 .add("N3", "n3.") 094 .add("N3-PP", "n3.") 095 .add("N3-PLAIN", "n3.") 096 .add("N3-TRIPLES", "n3.") 097 .add("TURTLE", "n3.") 098 .add("N-TRIPLE", "ntriple.") 099 .add("JSON-LD", "jsonLd.") 100 .add("N-QUADS", "nquads.") 101 .add("TRIG", "trig.") 102 .add("TRIX", "trix.") 103 .add("RDF/JSON", "rdfJson.") 104 .build(); 105 // @formatter:on 106 107 /** 108 * Creates a new builder for this object. 109 * 110 * @param ctx The context creating this session. 111 * <br>Cannot be <jk>null</jk>. 112 * @return A new builder. 113 */ 114 public static Builder create(RdfSerializer ctx) { 115 return new Builder(reqnn("ctx", ctx)); 116 } 117 118 private final Model model; 119 private final Property pRoot; 120 private final Property pValue; 121 private final Lang lang; 122 private final RdfSerializer ctx; 123 private final Namespace[] namespaces; 124 125 /** 126 * Constructor. 127 * 128 * @param builder The builder for this object. 129 * <br>Cannot be <jk>null</jk>. 130 */ 131 protected RdfSerializerSession(Builder builder) { 132 super(builder); 133 ctx = builder.ctx; 134 135 model = ModelFactory.createDefaultModel(); 136 namespaces = ctx.namespaces; 137 addModelPrefix(ctx.getJuneauNs()); 138 addModelPrefix(ctx.getJuneauBpNs()); 139 for (var ns : this.namespaces) 140 addModelPrefix(ns); 141 pRoot = model.createProperty(ctx.getJuneauNs().getUri(), "root"); 142 pValue = model.createProperty(ctx.getJuneauNs().getUri(), "value"); 143 144 // Map legacy language names to RIOT Lang (e.g. "N-TRIPLE" -> NTRIPLES) 145 var langName = ctx.getLanguage(); 146 lang = toLang(langName); 147 if (lang == null) 148 throw rex("Unknown RDF language encountered: '%s'", langName); 149 } 150 151 // Package-private (not private) for white-box testing. 152 static Lang toLang(String langName) { 153 var lang = RDFLanguages.nameToLang(langName); 154 if (lang != null) 155 return lang; 156 if (eq(langName, "RDF/PROTO")) 157 return Lang.RDFPROTO; 158 return null; 159 } 160 161 /* 162 * Adds the specified namespace as a model prefix. 163 */ 164 private void addModelPrefix(Namespace ns) { 165 model.setNsPrefix(ns.getName(), ns.getUri()); 166 } 167 168 /** 169 * XML-encoded the specified element name using the {@link XmlUtils#encodeElementName(Object)} method. 170 */ 171 private String encodeElementName(Object o) { 172 return XmlUtils.encodeElementName(toString(o)); 173 } 174 175 /** 176 * XML-encodes the specified string using the {@link XmlUtils#escapeText(Object)} method. 177 */ 178 // Package-private (not private) for white-box testing. 179 String encodeTextInvalidChars(Object o) { 180 if (o == null) 181 return null; 182 var s = toString(o); 183 return XmlUtils.escapeText(s); 184 } 185 186 private String getUri(Object uri) { 187 if (! nn(uri)) 188 return null; 189 return getUriResolver().resolve(uri.toString()); 190 } 191 192 @SuppressWarnings({ 193 "null" // Null analysis not applicable to RDF serialization 194 }) 195 private RDFNode writeAnything(Object o, boolean isURI, ClassMeta<?> eType, String attrName, BeanPropertyMeta bpm, Resource parentResource) throws SerializeException { 196 var m = model; 197 198 ClassMeta<?> wType = null; // The wrapped type 199 ClassMeta<?> sType; // The serialized type 200 201 var aType = push2(attrName, o, eType); // The actual type 202 203 if (eType == null) 204 eType = object(); 205 206 // Handle recursion 207 if (aType == null) { 208 o = null; 209 aType = object(); 210 } 211 212 // Handle Optional values 213 if (isOptional(aType)) { 214 o = getOptionalValue(o); 215 eType = getOptionalType(eType); 216 aType = getClassMetaForObject(o, object()); 217 } 218 219 if (nn(o)) { 220 221 if (aType.isDelegate()) { 222 wType = aType; 223 aType = (ClassMeta)((Delegate)o).getBeanInfo(); 224 } 225 226 sType = aType; 227 228 // Swap if necessary 229 var swap = aType.getSwap(this); 230 if (nn(swap)) { 231 o = swap(swap, o); 232 sType = swap.getSwapClassMeta(this); 233 234 // If the getSwapClass() method returns Object, we need to figure out 235 // the actual type now. 236 if (sType.isObject()) 237 sType = getClassMetaForObject(o); 238 } 239 } else { 240 sType = eType.getSerializedClassMeta(this); 241 } 242 243 var typeName = getBeanTypeName(this, eType, aType, bpm); 244 245 RDFNode n = null; 246 247 if (o == null || sType.isChar() && ((Character)o).charValue() == 0) { 248 if (nn(bpm)) { 249 if (isKeepNullProperties()) // HTT: bpm!=null only reaches this branch via writeBeanMap's checkNull-filtered call site, which only lets a null/NUL value through when isKeepNullProperties() is already true. So whenever this line executes, isKeepNullProperties() is structurally guaranteed true; the false side (leaving n unset) is dead in this call path. 250 n = m.createResource(RDF_NIL); 251 } else { 252 n = m.createResource(RDF_NIL); 253 } 254 255 } else if (sType.isUri() || isURI) { 256 // RDF URI gate must come before isBean/isMap/isCharSequence: values annotated with Uri (where sType could be String or a bean) need to route through the Resource emission path. RDF URIs must be absolute to be valid. 257 var uri = getUri(o); 258 if (isAbsoluteUri(uri)) 259 n = m.createResource(uri); 260 else 261 n = m.createLiteral(encodeTextInvalidChars(uri)); 262 263 } else if (sType.isBean()) { 264 var bm = toBeanMap(o); 265 Object uri = null; 266 RdfBeanMeta rbm = getRdfBeanMeta(bm.getMeta()); 267 if (rbm.hasBeanUri()) 268 uri = rbm.getBeanUriProperty().get(bm, null); 269 String uri2 = getUri(uri); 270 n = m.createResource(uri2); 271 writeBeanMap(bm, (Resource)n, typeName); 272 273 } else if (sType.isMap() || (nn(wType) && wType.isMap())) { // HTT: 1 residual branch -- the "wType.isMap()" right-hand sub-check is evaluated only when sType.isMap()==false AND nn(wType)==true, but the only concrete Delegate wrapper reaching this far, DelegateMap (internal.DelegateMap#getBeanInfo()), always returns a MAP-category ClassMeta, so sType.isMap() is already true before wType is even consulted; the sub-check's own "true" outcome is therefore never separately exercised (short-circuited away every time). Same root cause as the isCollectionOrArray() check below. 274 if (o instanceof BeanMap o2) { // HTT: suspected structurally dead. The only way to reach this else-if with wType!=null is via a Delegate: for BeanMap specifically (which IS a Delegate, and IS a java.util.Map), aType gets reassigned to the WRAPPED bean's own BEAN-category ClassMeta, so sType ends up isBean()==true and dispatches through the EARLIER sType.isBean() branch instead -- this else-if is never reached for a real BeanMap. For the OTHER disjunct (sType.isMap() directly true, e.g. an ordinary Map/JsonMap), `o` is never itself a BeanMap. Flagged, not deleted -- see also RdfStreamSerializerSession, which has the identical pattern (and an empirical confirmation via its o10_stream_serialize_rawBeanMap_direct test). 275 Object uri = null; // HTT: unreachable -- see guard above. 276 var rbm = getRdfBeanMeta(o2.getMeta()); // HTT: unreachable -- see guard above. 277 if (rbm.hasBeanUri()) // HTT: unreachable -- see guard above. 278 uri = rbm.getBeanUriProperty().get(o2, null); // HTT: unreachable -- see guard above. 279 var uri2 = getUri(uri); // HTT: unreachable -- see guard above. 280 n = m.createResource(uri2); // HTT: unreachable -- see guard above. 281 writeBeanMap(o2, (Resource)n, typeName); // HTT: unreachable -- see guard above. 282 } else { // HTT: the residual "goto" jump instruction that would exit the if-block above is unreachable -- see guard above; every actual invocation takes this else branch directly. 283 var m2 = (Map)o; 284 n = m.createResource(); 285 writeMap(m2, (Resource)n, sType); 286 } 287 288 } else if (sType.isCollectionOrArray() || (nn(wType) && wType.isCollection())) { // HTT: the "nn(wType) && wType.isCollection()" fallback is suspected structurally dead for the same reason as the isMap() fallback above -- the only concrete Delegate collection wrapper in the codebase, DelegateList (internal.DelegateList#getBeanInfo()), always returns a COLLECTION-category ClassMeta from getBeanInfo(), so sType stays isCollectionOrArray()==true and the left disjunct is always what's satisfied; nn(wType)==true is therefore never actually evaluated at this specific check (short-circuited away). Flagged, not deleted. 289 290 var c = sort(sType.isCollection() ? (Collection)o : toList(sType.inner(), o)); 291 var f = getCollectionFormat(); 292 var cRdf = getRdfClassMeta(sType); 293 var bpRdf = getRdfBeanPropertyMeta(bpm); 294 295 if (cRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT) 296 f = cRdf.getCollectionFormat(); 297 if (bpRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT) 298 f = bpRdf.getCollectionFormat(); 299 300 if (f == RdfCollectionFormat.MULTI_VALUED) { 301 writeToMultiProperties(c, eType, bpm, attrName, parentResource); 302 } else { 303 n = switch (f) { 304 case BAG -> writeToContainer(c, eType, m.createBag()); 305 case LIST -> writeToList(c, eType); 306 default -> writeToContainer(c, eType, m.createSeq()); 307 }; 308 } 309 310 } else if (sType.isCharSequence() || sType.isChar()) { 311 n = m.createLiteral(encodeTextInvalidChars(o)); 312 313 } else if (sType.isNumber() || sType.isBoolean()) { 314 if (! isAddLiteralTypes()) 315 n = m.createLiteral(o.toString()); 316 else 317 n = m.createTypedLiteral(o); 318 319 } else if (sType.isReader()) { 320 n = m.createLiteral(encodeTextInvalidChars(read((Reader)o, SerializerSession::handleThrown))); 321 } else if (sType.isInputStream()) { 322 n = m.createLiteral(encodeTextInvalidChars(read((InputStream)o, SerializerSession::handleThrown))); 323 324 } else { 325 n = m.createLiteral(encodeTextInvalidChars(toString(o))); 326 } 327 328 pop(); 329 330 return n; 331 } 332 333 private void writeBeanMap(BeanMap<?> m, Resource r, String typeName) throws SerializeException { 334 var l = new ArrayList<BeanPropertyValue>(); 335 336 if (nn(typeName)) { 337 var pm = m.getMeta().getTypeProperty(); 338 l.add(new BeanPropertyValue(pm, pm.getName(), typeName, null)); 339 } 340 341 var checkNull = (Predicate<Object>)(x -> isKeepNullProperties() || nn(x)); 342 m.forEachValue(checkNull, (pMeta, key, value, thrown) -> l.add(new BeanPropertyValue(pMeta, key, value, thrown))); 343 344 Collections.reverse(l); 345 l.forEach(x -> { 346 var bpMeta = x.getMeta(); 347 var cMeta = (ClassMeta<?>) bpMeta.getBeanInfo(); 348 var bpRdf = getRdfBeanPropertyMeta(bpMeta); 349 var bpXml = getXmlBeanPropertyMeta(bpMeta); 350 351 if (bpRdf.isBeanUri()) 352 return; 353 354 var key = x.getName(); 355 var value = x.getValue(); 356 var t = x.getThrown(); 357 if (nn(t)) 358 onBeanGetterException(bpMeta, t); // HTT: onBeanGetterException always throws when reached (isIgnoreInvocationExceptionsOnGetters() is false whenever thrown is actually populated), so JaCoCo cannot register full instruction coverage past a call that never returns; same pattern as CborSerializerSession/HjsonSerializerSession. 359 360 if (canIgnoreValue(bpMeta, key, value)) 361 return; 362 363 var ns = bpRdf.getNamespace(); 364 if (ns == null && isUseXmlNamespaces()) 365 ns = bpXml.getNamespace(); 366 if (ns == null) 367 ns = getJuneauBpNs(); 368 else if (isAutoDetectNamespaces()) 369 addModelPrefix(ns); 370 371 var p = model.createProperty(ns.getUri(), encodeElementName(key)); 372 var n = writeAnything(value, bpMeta.isUri(), cMeta, key, bpMeta, r); 373 if (nn(n)) 374 r.addProperty(p, n); 375 }); 376 } 377 378 private void writeMap(Map m, Resource r, ClassMeta<?> type) throws SerializeException { 379 380 m = sort(m); 381 382 var keyType = type.getKeyType(); 383 var valueType = type.getValueType(); 384 385 List<Map.Entry<Object,Object>> l = CollectionUtils.toList(m.entrySet()); 386 Collections.reverse(l); 387 l.forEach(x -> { 388 Object value = x.getValue(); 389 Object key = generalize(x.getKey(), keyType); 390 Namespace ns = getJuneauBpNs(); 391 Property p = model.createProperty(ns.getUri(), encodeElementName(toString(key))); 392 RDFNode n = writeAnything(value, false, valueType, toString(key), null, r); 393 if (nn(n)) // HTT: writeMap always passes bpm=null to writeAnything, and writeAnything's null-value branch unconditionally assigns n=RDF_NIL when bpm is null (regardless of keepNullProperties) -- so n is never null here; the false side is only reachable when bpm != null (see writeBeanMap's equivalent check). Flagged as suspected structurally-dead code in this call path, not fixed. 394 r.addProperty(p, n); 395 }); 396 } 397 398 private Container writeToContainer(Collection c, ClassMeta<?> type, Container list) throws SerializeException { 399 var elementType = type.getElementType(); 400 c.forEach(x -> list.add(writeAnything(x, false, elementType, null, null, null))); 401 return list; 402 } 403 404 private RDFList writeToList(Collection c, ClassMeta<?> type) throws SerializeException { 405 var elementType = type.getElementType(); 406 List<RDFNode> l = listOfSize(c.size()); 407 c.forEach(x -> l.add(writeAnything(x, false, elementType, null, null, null))); 408 return model.createList(l.iterator()); 409 } 410 411 private void writeToMultiProperties(Collection c, ClassMeta<?> sType, BeanPropertyMeta bpm, String attrName, Resource parentResource) throws SerializeException { 412 413 var elementType = sType.getElementType(); 414 RdfBeanPropertyMeta bpRdf = getRdfBeanPropertyMeta(bpm); 415 XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(bpm); 416 417 c.forEach(x -> { 418 var ns = bpRdf.getNamespace(); 419 if (ns == null && isUseXmlNamespaces()) 420 ns = bpXml.getNamespace(); 421 if (ns == null) 422 ns = getJuneauBpNs(); 423 else if (isAutoDetectNamespaces()) 424 addModelPrefix(ns); 425 RDFNode n2 = writeAnything(x, false, elementType, null, null, null); 426 Property p = model.createProperty(ns.getUri(), encodeElementName(attrName)); 427 parentResource.addProperty(p, n2); 428 }); 429 } 430 431 @SuppressWarnings({ 432 "deprecation", // RDFWriter.output(Writer) - RDF uses UTF-8 433 "resource" // Resource management handled by Serializer 434 }) 435 @Override /* Overridden from Serializer */ 436 protected void doWrite(SerializerPipe out, Object o) throws SerializeException { 437 438 var cm = getClassMetaForObject(o); 439 if (isLooseCollections() && nn(cm) && cm.isCollectionOrArray()) { 440 Collection c = cm.isCollection() ? (Collection)o : toList(cm.inner(), o); 441 forEachEntry(c, x -> writeAnything(x, false, object(), "root", null, null)); 442 } else { 443 RDFNode n = writeAnything(o, false, getExpectedRootType(o), "root", null, null); 444 Resource r; 445 if (n.isLiteral()) { 446 r = model.createResource(); 447 r.addProperty(pValue, n); 448 } else { 449 r = n.asResource(); 450 } 451 452 if (isAddRootProp()) 453 r.addProperty(pRoot, "true"); 454 } 455 456 // Use RDFWriter - build() returns RDFWriter which has output(Writer) 457 org.apache.jena.riot.RDFWriter.create() 458 .source(model) 459 .lang(lang) 460 .build() 461 .output(out.getWriter()); 462 } 463 464 /** 465 * RDF format for representing collections and arrays. 466 * 467 * @see RdfSerializer.Builder#collectionFormat(RdfCollectionFormat) 468 * @return 469 * RDF format for representing collections and arrays. 470 */ 471 protected final RdfCollectionFormat getCollectionFormat() { return ctx.getCollectionFormat(); } 472 473 /** 474 * All Jena-related configuration properties. 475 * 476 * @return 477 * A map of all Jena-related configuration properties. 478 */ 479 protected final Map<String,Object> getJenaSettings() { return ctx.getJenaSettings(); } 480 481 /** 482 * Default XML namespace for bean properties. 483 * 484 * @see RdfSerializer.Builder#juneauBpNs(Namespace) 485 * @return 486 * The XML namespace to use for bean properties. 487 */ 488 protected final Namespace getJuneauBpNs() { return ctx.getJuneauBpNs(); } 489 490 /** 491 * XML namespace for Juneau properties. 492 * 493 * @see RdfSerializer.Builder#juneauNs(Namespace) 494 * @return 495 * The XML namespace to use for Juneau properties. 496 */ 497 protected final Namespace getJuneauNs() { return ctx.getJuneauNs(); } 498 499 /** 500 * RDF language. 501 * 502 * @see RdfSerializer.Builder#language(String) 503 * @return 504 * The RDF language to use. 505 */ 506 protected final String getLanguage() { return ctx.getLanguage(); } 507 508 /** 509 * Default namespaces. 510 * 511 * @see RdfSerializer.Builder#namespaces(Namespace...) 512 * @return 513 * The default list of namespaces associated with this serializer. 514 */ 515 protected final Namespace[] getNamespaces() { return ctx.getNamespaces(); } 516 517 /** 518 * Returns the language-specific metadata on the specified bean. 519 * 520 * @param bm The bean to return the metadata on. 521 * @return The metadata. 522 */ 523 protected RdfBeanMeta getRdfBeanMeta(BeanMeta<?> bm) { 524 return ctx.getRdfBeanMeta(bm); 525 } 526 527 /** 528 * Returns the language-specific metadata on the specified bean property. 529 * 530 * @param bpm The bean property to return the metadata on. 531 * @return The metadata. 532 */ 533 protected RdfBeanPropertyMeta getRdfBeanPropertyMeta(BeanPropertyMeta bpm) { 534 return bpm == null ? RdfBeanPropertyMeta.DEFAULT : ctx.getRdfBeanPropertyMeta(bpm); 535 } 536 537 /** 538 * Returns the language-specific metadata on the specified class. 539 * 540 * @param cm The class to return the metadata on. 541 * @return The metadata. 542 */ 543 protected RdfClassMeta getRdfClassMeta(ClassMeta<?> cm) { 544 return ctx.getRdfClassMeta(cm); 545 } 546 547 /** 548 * Returns the language-specific metadata on the specified bean property. 549 * 550 * @param bpm The bean property to return the metadata on. 551 * @return The metadata. 552 */ 553 protected XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) { 554 return bpm == null ? XmlBeanPropertyMeta.DEFAULT : ctx.getXmlBeanPropertyMeta(bpm); 555 } 556 557 /** 558 * Add XSI data types to non-<c>String</c> literals. 559 * 560 * @see RdfSerializer.Builder#addLiteralTypes() 561 * @return 562 * <jk>true</jk> if XSI data types should be added to string literals. 563 */ 564 protected final boolean isAddLiteralTypes() { return ctx.isAddLiteralTypes(); } 565 566 /** 567 * Add RDF root identifier property to root node. 568 * 569 * @see RdfSerializer.Builder#addRootProperty() 570 * @return 571 * <jk>true</jk> if RDF property <c>http://www.apache.org/juneau/root</c> is added with a value of <js>"true"</js> 572 * to identify the root node in the graph. 573 */ 574 protected final boolean isAddRootProp() { return ctx.isAddRootProp(); } 575 576 /** 577 * Auto-detect namespace usage. 578 * 579 * @see RdfSerializer.Builder#disableAutoDetectNamespaces() 580 * @return 581 * <jk>true</jk> if namespaces usage should be detected before serialization. 582 */ 583 protected final boolean isAutoDetectNamespaces() { return ctx.isAutoDetectNamespaces(); } 584 585 /** 586 * Collections should be serialized and parsed as loose collections. 587 * 588 * @see RdfSerializer.Builder#looseCollections() 589 * @return 590 * <jk>true</jk> if collections of resources are handled as loose collections of resources in RDF instead of 591 * resources that are children of an RDF collection (e.g. Sequence, Bag). 592 */ 593 protected final boolean isLooseCollections() { return ctx.isLooseCollections(); } 594 595 /** 596 * Reuse XML namespaces when RDF namespaces not specified. 597 * 598 * @see RdfSerializer.Builder#disableUseXmlNamespaces() 599 * @return 600 * <jk>true</jk> if namespaces defined using {@link XmlNs @XmlNs} and {@link Xml @Xml} will be inherited by the RDF serializers. 601 * <br>Otherwise, namespaces will be defined using {@link RdfNs @RdfNs} and {@link Rdf @Rdf}. 602 */ 603 protected final boolean isUseXmlNamespaces() { return ctx.isUseXmlNamespaces(); } 604}