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.jena.Constants.*; 016import static org.apache.juneau.jena.RdfSerializer.*; 017 018import java.io.IOException; 019import java.util.*; 020 021import org.apache.jena.rdf.model.*; 022import org.apache.juneau.*; 023import org.apache.juneau.collections.*; 024import org.apache.juneau.internal.*; 025import org.apache.juneau.jena.annotation.*; 026import org.apache.juneau.serializer.*; 027import org.apache.juneau.transform.*; 028import org.apache.juneau.xml.*; 029import org.apache.juneau.xml.annotation.*; 030 031/** 032 * Session object that lives for the duration of a single use of {@link RdfSerializer}. 033 * 034 * <p> 035 * This class is NOT thread safe. 036 * It is typically discarded after one-time use although it can be reused within the same thread. 037 */ 038@SuppressWarnings({ "rawtypes", "unchecked" }) 039public final class RdfSerializerSession extends WriterSerializerSession { 040 041 private final RdfSerializer ctx; 042 private final Property pRoot, pValue; 043 private final Model model; 044 private final RDFWriter writer; 045 private final Namespace[] namespaces; 046 047 /** 048 * Create a new session using properties specified in the context. 049 * 050 * @param ctx 051 * The context creating this session object. 052 * The context contains all the configuration settings for this object. 053 * @param args 054 * Runtime arguments. 055 * These specify session-level information such as locale and URI context. 056 * It also include session-level properties that override the properties defined on the bean and 057 * serializer contexts. 058 */ 059 protected RdfSerializerSession(RdfSerializer ctx, SerializerSessionArgs args) { 060 super(ctx, args); 061 this.ctx = ctx; 062 063 namespaces = getInstanceArrayProperty(RDF_namespaces, Namespace.class, ctx.namespaces); 064 model = ModelFactory.createDefaultModel(); 065 addModelPrefix(ctx.getJuneauNs()); 066 addModelPrefix(ctx.getJuneauBpNs()); 067 for (Namespace ns : this.namespaces) 068 addModelPrefix(ns); 069 pRoot = model.createProperty(ctx.getJuneauNs().getUri(), RDF_juneauNs_ROOT); 070 pValue = model.createProperty(ctx.getJuneauNs().getUri(), RDF_juneauNs_VALUE); 071 writer = model.getWriter(ctx.getLanguage()); 072 073 // Only apply properties with this prefix! 074 String propPrefix = RdfCommon.LANG_PROP_MAP.get(ctx.getLanguage()); 075 if (propPrefix == null) 076 throw new BasicRuntimeException("Unknown RDF language encountered: ''{0}''", ctx.getLanguage()); 077 078 // RDF/XML specific properties. 079 if (propPrefix.equals("rdfXml.")) { 080 writer.setProperty("tab", isUseWhitespace() ? 2 : 0); 081 writer.setProperty("attributeQuoteChar", Character.toString(getQuoteChar())); 082 } 083 084 for (Map.Entry<String,Object> e : ctx.jenaProperties.entrySet()) 085 if (e.getKey().startsWith(propPrefix, 5)) 086 writer.setProperty(e.getKey().substring(5 + propPrefix.length()), e.getValue()); 087 088 for (String k : getPropertyKeys()) 089 if (k.startsWith("RdfCommon.jena.") && k.startsWith(propPrefix, 15)) 090 writer.setProperty(k.substring(15 + propPrefix.length()), getProperty(k)); 091 } 092 093 /* 094 * Adds the specified namespace as a model prefix. 095 */ 096 private void addModelPrefix(Namespace ns) { 097 model.setNsPrefix(ns.getName(), ns.getUri()); 098 } 099 100 /* 101 * XML-encodes the specified string using the {@link XmlUtils#escapeText(Object)} method. 102 */ 103 private String encodeTextInvalidChars(Object o) { 104 if (o == null) 105 return null; 106 String s = toString(o); 107 return XmlUtils.escapeText(s); 108 } 109 110 /* 111 * XML-encoded the specified element name using the {@link XmlUtils#encodeElementName(Object)} method. 112 */ 113 private String encodeElementName(Object o) { 114 return XmlUtils.encodeElementName(toString(o)); 115 } 116 117 @Override /* Serializer */ 118 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 119 120 Resource r = null; 121 122 ClassMeta<?> cm = getClassMetaForObject(o); 123 if (isLooseCollections() && cm != null && cm.isCollectionOrArray()) { 124 Collection c = sort(cm.isCollection() ? (Collection)o : toList(cm.getInnerClass(), o)); 125 for (Object o2 : c) 126 serializeAnything(o2, false, object(), "root", null, null); 127 } else { 128 RDFNode n = serializeAnything(o, false, getExpectedRootType(o), "root", null, null); 129 if (n.isLiteral()) { 130 r = model.createResource(); 131 r.addProperty(pValue, n); 132 } else { 133 r = n.asResource(); 134 } 135 136 if (isAddRootProp()) 137 r.addProperty(pRoot, "true"); 138 } 139 140 writer.write(model, out.getWriter(), "http://unknown/"); 141 } 142 143 private RDFNode serializeAnything(Object o, boolean isURI, ClassMeta<?> eType, 144 String attrName, BeanPropertyMeta bpm, Resource parentResource) throws IOException, SerializeException { 145 Model m = model; 146 147 ClassMeta<?> aType = null; // The actual type 148 ClassMeta<?> wType = null; // The wrapped type 149 ClassMeta<?> sType = object(); // The serialized type 150 151 aType = push2(attrName, o, eType); 152 153 if (eType == null) 154 eType = object(); 155 156 // Handle recursion 157 if (aType == null) { 158 o = null; 159 aType = object(); 160 } 161 162 // Handle Optional<X> 163 if (isOptional(aType)) { 164 o = getOptionalValue(o); 165 eType = getOptionalType(eType); 166 aType = getClassMetaForObject(o, object()); 167 } 168 169 if (o != null) { 170 171 if (aType.isDelegate()) { 172 wType = aType; 173 aType = ((Delegate)o).getClassMeta(); 174 } 175 176 sType = aType; 177 178 // Swap if necessary 179 PojoSwap swap = aType.getSwap(this); 180 if (swap != null) { 181 o = swap(swap, o); 182 sType = swap.getSwapClassMeta(this); 183 184 // If the getSwapClass() method returns Object, we need to figure out 185 // the actual type now. 186 if (sType.isObject()) 187 sType = getClassMetaForObject(o); 188 } 189 } else { 190 sType = eType.getSerializedClassMeta(this); 191 } 192 193 String typeName = getBeanTypeName(this, eType, aType, bpm); 194 195 RDFNode n = null; 196 197 if (o == null || sType.isChar() && ((Character)o).charValue() == 0) { 198 if (bpm != null) { 199 if (isKeepNullProperties()) { 200 n = m.createResource(RDF_NIL); 201 } 202 } else { 203 n = m.createResource(RDF_NIL); 204 } 205 206 } else if (sType.isUri() || isURI) { 207 // Note that RDF URIs must be absolute to be valid! 208 String uri = getUri(o, null); 209 if (StringUtils.isAbsoluteUri(uri)) 210 n = m.createResource(uri); 211 else 212 n = m.createLiteral(encodeTextInvalidChars(uri)); 213 214 } else if (sType.isCharSequence() || sType.isChar()) { 215 n = m.createLiteral(encodeTextInvalidChars(o)); 216 217 } else if (sType.isNumber() || sType.isBoolean()) { 218 if (! isAddLiteralTypes()) 219 n = m.createLiteral(o.toString()); 220 else 221 n = m.createTypedLiteral(o); 222 223 } else if (sType.isMap() || (wType != null && wType.isMap())) { 224 if (o instanceof BeanMap) { 225 BeanMap bm = (BeanMap)o; 226 Object uri = null; 227 RdfBeanMeta rbm = getRdfBeanMeta(bm.getMeta()); 228 if (rbm.hasBeanUri()) 229 uri = rbm.getBeanUriProperty().get(bm, null); 230 String uri2 = getUri(uri, null); 231 n = m.createResource(uri2); 232 serializeBeanMap(bm, (Resource)n, typeName); 233 } else { 234 Map m2 = (Map)o; 235 n = m.createResource(); 236 serializeMap(m2, (Resource)n, sType); 237 } 238 239 } else if (sType.isBean()) { 240 BeanMap bm = toBeanMap(o); 241 Object uri = null; 242 RdfBeanMeta rbm = getRdfBeanMeta(bm.getMeta()); 243 if (rbm.hasBeanUri()) 244 uri = rbm.getBeanUriProperty().get(bm, null); 245 String uri2 = getUri(uri, null); 246 n = m.createResource(uri2); 247 serializeBeanMap(bm, (Resource)n, typeName); 248 249 } else if (sType.isCollectionOrArray() || (wType != null && wType.isCollection())) { 250 251 Collection c = sort(sType.isCollection() ? (Collection)o : toList(sType.getInnerClass(), o)); 252 RdfCollectionFormat f = getCollectionFormat(); 253 RdfClassMeta cRdf = getRdfClassMeta(sType); 254 RdfBeanPropertyMeta bpRdf = getRdfBeanPropertyMeta(bpm); 255 256 if (cRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT) 257 f = cRdf.getCollectionFormat(); 258 if (bpRdf.getCollectionFormat() != RdfCollectionFormat.DEFAULT) 259 f = bpRdf.getCollectionFormat(); 260 261 switch (f) { 262 case BAG: n = serializeToContainer(c, eType, m.createBag()); break; 263 case LIST: n = serializeToList(c, eType); break; 264 case MULTI_VALUED: serializeToMultiProperties(c, eType, bpm, attrName, parentResource); break; 265 default: n = serializeToContainer(c, eType, m.createSeq()); 266 } 267 268 } else if (sType.isReader() || sType.isInputStream()) { 269 n = m.createLiteral(encodeTextInvalidChars(IOUtils.read(o))); 270 271 } else { 272 n = m.createLiteral(encodeTextInvalidChars(toString(o))); 273 } 274 275 pop(); 276 277 return n; 278 } 279 280 private String getUri(Object uri, Object uri2) { 281 String s = null; 282 if (uri != null) 283 s = uri.toString(); 284 if ((s == null || s.isEmpty()) && uri2 != null) 285 s = uri2.toString(); 286 if (s == null) 287 return null; 288 return getUriResolver().resolve(s); 289 } 290 291 private void serializeMap(Map m, Resource r, ClassMeta<?> type) throws IOException, SerializeException { 292 293 m = sort(m); 294 295 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 296 297 ArrayList<Map.Entry<Object,Object>> l = new ArrayList<>(m.entrySet()); 298 Collections.reverse(l); 299 for (Map.Entry<Object,Object> me : l) { 300 Object value = me.getValue(); 301 302 Object key = generalize(me.getKey(), keyType); 303 304 Namespace ns = getJuneauBpNs(); 305 Property p = model.createProperty(ns.getUri(), encodeElementName(toString(key))); 306 RDFNode n = serializeAnything(value, false, valueType, toString(key), null, r); 307 if (n != null) 308 r.addProperty(p, n); 309 } 310 } 311 312 private void serializeBeanMap(BeanMap<?> m, Resource r, String typeName) throws IOException, SerializeException { 313 List<BeanPropertyValue> l = m.getValues(isKeepNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null); 314 Collections.reverse(l); 315 for (BeanPropertyValue bpv : l) { 316 317 BeanPropertyMeta bpMeta = bpv.getMeta(); 318 ClassMeta<?> cMeta = bpMeta.getClassMeta(); 319 RdfBeanPropertyMeta bpRdf = getRdfBeanPropertyMeta(bpMeta); 320 XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(bpMeta); 321 322 if (bpRdf.isBeanUri()) 323 continue; 324 325 String key = bpv.getName(); 326 Object value = bpv.getValue(); 327 Throwable t = bpv.getThrown(); 328 if (t != null) 329 onBeanGetterException(bpMeta, t); 330 331 if (canIgnoreValue(cMeta, key, value)) 332 continue; 333 334 Namespace ns = bpRdf.getNamespace(); 335 if (ns == null && isUseXmlNamespaces()) 336 ns = bpXml.getNamespace(); 337 if (ns == null) 338 ns = getJuneauBpNs(); 339 else if (isAutoDetectNamespaces()) 340 addModelPrefix(ns); 341 342 Property p = model.createProperty(ns.getUri(), encodeElementName(key)); 343 RDFNode n = serializeAnything(value, bpMeta.isUri(), cMeta, key, bpMeta, r); 344 if (n != null) 345 r.addProperty(p, n); 346 } 347 } 348 349 350 private Container serializeToContainer(Collection c, ClassMeta<?> type, Container list) throws IOException, SerializeException { 351 352 ClassMeta<?> elementType = type.getElementType(); 353 for (Object e : c) { 354 RDFNode n = serializeAnything(e, false, elementType, null, null, null); 355 list = list.add(n); 356 } 357 return list; 358 } 359 360 private RDFList serializeToList(Collection c, ClassMeta<?> type) throws IOException, SerializeException { 361 ClassMeta<?> elementType = type.getElementType(); 362 List<RDFNode> l = new ArrayList<>(c.size()); 363 for (Object e : c) { 364 l.add(serializeAnything(e, false, elementType, null, null, null)); 365 } 366 return model.createList(l.iterator()); 367 } 368 369 private void serializeToMultiProperties(Collection c, ClassMeta<?> sType, 370 BeanPropertyMeta bpm, String attrName, Resource parentResource) throws IOException, SerializeException { 371 372 ClassMeta<?> elementType = sType.getElementType(); 373 RdfBeanPropertyMeta bpRdf = getRdfBeanPropertyMeta(bpm); 374 XmlBeanPropertyMeta bpXml = getXmlBeanPropertyMeta(bpm); 375 376 for (Object e : c) { 377 Namespace ns = bpRdf.getNamespace(); 378 if (ns == null && isUseXmlNamespaces()) 379 ns = bpXml.getNamespace(); 380 if (ns == null) 381 ns = getJuneauBpNs(); 382 else if (isAutoDetectNamespaces()) 383 addModelPrefix(ns); 384 RDFNode n2 = serializeAnything(e, false, elementType, null, null, null); 385 Property p = model.createProperty(ns.getUri(), encodeElementName(attrName)); 386 parentResource.addProperty(p, n2); 387 } 388 } 389 390 391 //----------------------------------------------------------------------------------------------------------------- 392 // Common properties 393 //----------------------------------------------------------------------------------------------------------------- 394 395 /** 396 * Configuration property: RDF format for representing collections and arrays. 397 * 398 * @see RdfSerializer#RDF_collectionFormat 399 * @return 400 * RDF format for representing collections and arrays. 401 */ 402 protected final RdfCollectionFormat getCollectionFormat() { 403 return ctx.getCollectionFormat(); 404 } 405 406 /** 407 * Configuration property: Default XML namespace for bean properties. 408 * 409 * @see RdfSerializer#RDF_juneauBpNs 410 * @return 411 * The XML namespace to use for bean properties. 412 */ 413 protected final Namespace getJuneauBpNs() { 414 return ctx.getJuneauBpNs(); 415 } 416 417 /** 418 * Configuration property: XML namespace for Juneau properties. 419 * 420 * @see RdfSerializer#RDF_juneauNs 421 * @return 422 * The XML namespace to use for Juneau properties. 423 */ 424 protected final Namespace getJuneauNs() { 425 return ctx.getJuneauNs(); 426 } 427 428 /** 429 * Configuration property: RDF language. 430 * 431 * @see RdfSerializer#RDF_language 432 * @return 433 * The RDF language to use. 434 */ 435 protected final String getLanguage() { 436 return ctx.getLanguage(); 437 } 438 439 /** 440 * Configuration property: Collections should be serialized and parsed as loose collections. 441 * 442 * @see RdfSerializer#RDF_looseCollections 443 * @return 444 * <jk>true</jk> if collections of resources are handled as loose collections of resources in RDF instead of 445 * resources that are children of an RDF collection (e.g. Sequence, Bag). 446 */ 447 protected final boolean isLooseCollections() { 448 return ctx.isLooseCollections(); 449 } 450 451 //----------------------------------------------------------------------------------------------------------------- 452 // Jena properties 453 //----------------------------------------------------------------------------------------------------------------- 454 455 /** 456 * Configuration property: All Jena-related configuration properties. 457 * 458 * @return 459 * A map of all Jena-related configuration properties. 460 */ 461 protected final Map<String,Object> getJenaProperties() { 462 return ctx.getJenaProperties(); 463 } 464 465 //----------------------------------------------------------------------------------------------------------------- 466 // Properties 467 //----------------------------------------------------------------------------------------------------------------- 468 469 /** 470 * Configuration property: Add <js>"_type"</js> properties when needed. 471 * 472 * @see RdfSerializer#RDF_addBeanTypes 473 * @return 474 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 475 * through reflection. 476 */ 477 @Override 478 protected final boolean isAddBeanTypes() { 479 return ctx.isAddBeanTypes(); 480 } 481 482 /** 483 * Configuration property: Add XSI data types to non-<c>String</c> literals. 484 * 485 * @see RdfSerializer#RDF_addLiteralTypes 486 * @return 487 * <jk>true</jk> if XSI data types should be added to string literals. 488 */ 489 protected final boolean isAddLiteralTypes() { 490 return ctx.isAddLiteralTypes(); 491 } 492 493 /** 494 * Configuration property: Add RDF root identifier property to root node. 495 * 496 * @see RdfSerializer#RDF_addRootProperty 497 * @return 498 * <jk>true</jk> if RDF property <c>http://www.apache.org/juneau/root</c> is added with a value of <js>"true"</js> 499 * to identify the root node in the graph. 500 */ 501 protected final boolean isAddRootProp() { 502 return ctx.isAddRootProp(); 503 } 504 505 /** 506 * Configuration property: Auto-detect namespace usage. 507 * 508 * @see RdfSerializer#RDF_autoDetectNamespaces 509 * @return 510 * <jk>true</jk> if namespaces usage should be detected before serialization. 511 */ 512 protected final boolean isAutoDetectNamespaces() { 513 return ctx.isAutoDetectNamespaces(); 514 } 515 516 /** 517 * Configuration property: Default namespaces. 518 * 519 * @see RdfSerializer#RDF_namespaces 520 * @return 521 * The default list of namespaces associated with this serializer. 522 */ 523 protected final Namespace[] getNamespaces() { 524 return ctx.getNamespaces(); 525 } 526 527 /** 528 * Configuration property: Reuse XML namespaces when RDF namespaces not specified. 529 * 530 * @see RdfSerializer#RDF_useXmlNamespaces 531 * @return 532 * <jk>true</jk> if namespaces defined using {@link XmlNs @XmlNs} and {@link org.apache.juneau.xml.annotation.Xml @Xml} will be inherited by the RDF serializers. 533 * <br>Otherwise, namespaces will be defined using {@link RdfNs @RdfNs} and {@link Rdf @Rdf}. 534 */ 535 protected final boolean isUseXmlNamespaces() { 536 return ctx.isUseXmlNamespaces(); 537 } 538 539 //----------------------------------------------------------------------------------------------------------------- 540 // Extended metadata 541 //----------------------------------------------------------------------------------------------------------------- 542 543 /** 544 * Returns the language-specific metadata on the specified class. 545 * 546 * @param cm The class to return the metadata on. 547 * @return The metadata. 548 */ 549 protected RdfClassMeta getRdfClassMeta(ClassMeta<?> cm) { 550 return ctx.getRdfClassMeta(cm); 551 } 552 553 /** 554 * Returns the language-specific metadata on the specified bean. 555 * 556 * @param bm The bean to return the metadata on. 557 * @return The metadata. 558 */ 559 protected RdfBeanMeta getRdfBeanMeta(BeanMeta<?> bm) { 560 return ctx.getRdfBeanMeta(bm); 561 } 562 563 /** 564 * Returns the language-specific metadata on the specified bean property. 565 * 566 * @param bpm The bean property to return the metadata on. 567 * @return The metadata. 568 */ 569 protected RdfBeanPropertyMeta getRdfBeanPropertyMeta(BeanPropertyMeta bpm) { 570 return bpm == null ? RdfBeanPropertyMeta.DEFAULT : ctx.getRdfBeanPropertyMeta(bpm); 571 } 572 573 /** 574 * Returns the language-specific metadata on the specified bean property. 575 * 576 * @param bpm The bean property to return the metadata on. 577 * @return The metadata. 578 */ 579 protected XmlBeanPropertyMeta getXmlBeanPropertyMeta(BeanPropertyMeta bpm) { 580 return bpm == null ? XmlBeanPropertyMeta.DEFAULT : ctx.getXmlBeanPropertyMeta(bpm); 581 } 582 583 //----------------------------------------------------------------------------------------------------------------- 584 // Other methods 585 //----------------------------------------------------------------------------------------------------------------- 586 587 @Override /* Session */ 588 public OMap toMap() { 589 return super.toMap() 590 .a("RdfSerializerSession", new DefaultFilteringOMap() 591 ); 592 } 593}