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