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