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.RdfCommon.*; 017import static org.apache.juneau.jena.RdfSerializer.*; 018 019import java.util.*; 020 021import org.apache.juneau.*; 022import org.apache.juneau.internal.*; 023import org.apache.juneau.serializer.*; 024import org.apache.juneau.transform.*; 025import org.apache.juneau.xml.*; 026 027import com.hp.hpl.jena.rdf.model.*; 028 029/** 030 * Session object that lives for the duration of a single use of {@link RdfSerializer}. 031 * 032 * <p> 033 * This class is NOT thread safe. 034 * It is typically discarded after one-time use although it can be reused within the same thread. 035 */ 036@SuppressWarnings({ "rawtypes", "unchecked" }) 037public final class RdfSerializerSession extends WriterSerializerSession { 038 039 private final String rdfLanguage; 040 private final Namespace juneauNs, juneauBpNs; 041 private final boolean 042 addLiteralTypes, 043 addRootProperty, 044 useXmlNamespaces, 045 looseCollections, 046 autoDetectNamespaces, 047 addBeanTypeProperties; 048 private final Property pRoot, pValue; 049 private final Model model; 050 private final RDFWriter writer; 051 private final RdfCollectionFormat collectionFormat; 052 private final Namespace[] namespaces; 053 054 /** 055 * Create a new session using properties specified in the context. 056 * 057 * @param ctx 058 * The context creating this session object. 059 * The context contains all the configuration settings for this object. 060 * @param args 061 * Runtime arguments. 062 * These specify session-level information such as locale and URI context. 063 * It also include session-level properties that override the properties defined on the bean and 064 * serializer contexts. 065 */ 066 protected RdfSerializerSession(RdfSerializer ctx, SerializerSessionArgs args) { 067 super(ctx, args); 068 069 rdfLanguage = getProperty(RDF_language, String.class, ctx.rdfLanguage); 070 juneauNs = getInstanceProperty(RDF_juneauNs, Namespace.class, ctx.juneauNs); 071 juneauBpNs = getInstanceProperty(RDF_juneauBpNs, Namespace.class, ctx.juneauBpNs); 072 addLiteralTypes = getProperty(RDF_addLiteralTypes, boolean.class, ctx.addLiteralTypes); 073 addRootProperty = getProperty(RDF_addRootProperty, boolean.class, ctx.addRootProperty); 074 collectionFormat = getProperty(RDF_collectionFormat, RdfCollectionFormat.class, ctx.collectionFormat); 075 looseCollections = getProperty(RDF_looseCollections, boolean.class, ctx.looseCollections); 076 useXmlNamespaces = getProperty(RDF_useXmlNamespaces, boolean.class, ctx.useXmlNamespaces); 077 autoDetectNamespaces = getProperty(RDF_autoDetectNamespaces, boolean.class, ctx.autoDetectNamespaces); 078 namespaces = getInstanceArrayProperty(RDF_namespaces, Namespace.class, ctx.namespaces); 079 addBeanTypeProperties = getProperty(RDF_addBeanTypeProperties, boolean.class, ctx.addBeanTypeProperties); 080 model = ModelFactory.createDefaultModel(); 081 addModelPrefix(juneauNs); 082 addModelPrefix(juneauBpNs); 083 for (Namespace ns : this.namespaces) 084 addModelPrefix(ns); 085 pRoot = model.createProperty(juneauNs.getUri(), RDF_juneauNs_ROOT); 086 pValue = model.createProperty(juneauNs.getUri(), RDF_juneauNs_VALUE); 087 writer = model.getWriter(rdfLanguage); 088 089 // Only apply properties with this prefix! 090 String propPrefix = RdfCommon.LANG_PROP_MAP.get(rdfLanguage); 091 if (propPrefix == null) 092 throw new FormattedRuntimeException("Unknown RDF language encountered: ''{0}''", rdfLanguage); 093 094 // RDF/XML specific properties. 095 if (propPrefix.equals("rdfXml.")) { 096 writer.setProperty("tab", isUseWhitespace() ? 2 : 0); 097 writer.setProperty("attributeQuoteChar", Character.toString(getQuoteChar())); 098 } 099 100 for (Map.Entry<String,Object> e : ctx.jenaSettings.entrySet()) 101 if (e.getKey().startsWith(propPrefix, 5)) 102 writer.setProperty(e.getKey().substring(5 + propPrefix.length()), e.getValue()); 103 104 for (String k : getPropertyKeys()) 105 if (k.startsWith("RdfCommon.jena.") && k.startsWith(propPrefix, 15)) 106 writer.setProperty(k.substring(15 + propPrefix.length()), getProperty(k)); 107 } 108 109 @Override /* Session */ 110 public ObjectMap asMap() { 111 return super.asMap() 112 .append("RdfSerializerSession", new ObjectMap() 113 .append("addBeanTypeProperties", addBeanTypeProperties) 114 .append("addLiteralTypes", addLiteralTypes) 115 .append("addRootProperty", addRootProperty) 116 .append("autoDetectNamespaces", autoDetectNamespaces) 117 .append("collectionFormat", collectionFormat) 118 .append("juneauNs", juneauNs) 119 .append("juneauBpNs", juneauBpNs) 120 .append("looseCollections", looseCollections) 121 .append("namespaces", namespaces) 122 .append("rdfLanguage", rdfLanguage) 123 .append("useXmlNamespaces", useXmlNamespaces) 124 ); 125 } 126 127 /* 128 * Adds the specified namespace as a model prefix. 129 */ 130 private void addModelPrefix(Namespace ns) { 131 model.setNsPrefix(ns.getName(), ns.getUri()); 132 } 133 134 /** 135 * Returns the {@link Serializer#SERIALIZER_addBeanTypeProperties} setting value for this session. 136 * 137 * @return The {@link Serializer#SERIALIZER_addBeanTypeProperties} setting value for this session. 138 */ 139 @Override /* SerializerSession */ 140 public final boolean isAddBeanTypeProperties() { 141 return addBeanTypeProperties; 142 } 143 144 /* 145 * XML-encodes the specified string using the {@link XmlUtils#escapeText(Object)} method. 146 */ 147 private String encodeTextInvalidChars(Object o) { 148 if (o == null) 149 return null; 150 String s = toString(o); 151 return XmlUtils.escapeText(s); 152 } 153 154 /* 155 * XML-encoded the specified element name using the {@link XmlUtils#encodeElementName(Object)} method. 156 */ 157 private String encodeElementName(Object o) { 158 return XmlUtils.encodeElementName(toString(o)); 159 } 160 161 @Override /* Serializer */ 162 protected void doSerialize(SerializerPipe out, Object o) throws Exception { 163 164 Resource r = null; 165 166 ClassMeta<?> cm = getClassMetaForObject(o); 167 if (looseCollections && cm != null && cm.isCollectionOrArray()) { 168 Collection c = sort(cm.isCollection() ? (Collection)o : toList(cm.getInnerClass(), o)); 169 for (Object o2 : c) 170 serializeAnything(o2, false, object(), "root", null, null); 171 } else { 172 RDFNode n = serializeAnything(o, false, getExpectedRootType(o), "root", null, null); 173 if (n.isLiteral()) { 174 r = model.createResource(); 175 r.addProperty(pValue, n); 176 } else { 177 r = n.asResource(); 178 } 179 180 if (addRootProperty) 181 r.addProperty(pRoot, "true"); 182 } 183 184 writer.write(model, out.getWriter(), "http://unknown/"); 185 } 186 187 private RDFNode serializeAnything(Object o, boolean isURI, ClassMeta<?> eType, 188 String attrName, BeanPropertyMeta bpm, Resource parentResource) throws Exception { 189 Model m = model; 190 191 ClassMeta<?> aType = null; // The actual type 192 ClassMeta<?> wType = null; // The wrapped type 193 ClassMeta<?> sType = object(); // The serialized type 194 195 aType = push(attrName, o, eType); 196 197 if (eType == null) 198 eType = object(); 199 200 // Handle recursion 201 if (aType == null) { 202 o = null; 203 aType = object(); 204 } 205 206 if (o != null) { 207 208 if (aType.isDelegate()) { 209 wType = aType; 210 aType = ((Delegate)o).getClassMeta(); 211 } 212 213 sType = aType; 214 215 // Swap if necessary 216 PojoSwap swap = aType.getPojoSwap(this); 217 if (swap != null) { 218 o = swap.swap(this, o); 219 sType = swap.getSwapClassMeta(this); 220 221 // If the getSwapClass() method returns Object, we need to figure out 222 // the actual type now. 223 if (sType.isObject()) 224 sType = getClassMetaForObject(o); 225 } 226 } else { 227 sType = eType.getSerializedClassMeta(this); 228 } 229 230 String typeName = getBeanTypeName(eType, aType, bpm); 231 232 RDFNode n = null; 233 234 if (o == null || sType.isChar() && ((Character)o).charValue() == 0) { 235 if (bpm != null) { 236 if (! isTrimNulls()) { 237 n = m.createResource(RDF_NIL); 238 } 239 } else { 240 n = m.createResource(RDF_NIL); 241 } 242 243 } else if (sType.isUri() || isURI) { 244 // Note that RDF URIs must be absolute to be valid! 245 String uri = getUri(o, null); 246 if (StringUtils.isAbsoluteUri(uri)) 247 n = m.createResource(uri); 248 else 249 n = m.createLiteral(encodeTextInvalidChars(uri)); 250 251 } else if (sType.isCharSequence() || sType.isChar()) { 252 n = m.createLiteral(encodeTextInvalidChars(o)); 253 254 } else if (sType.isNumber() || sType.isBoolean()) { 255 if (! addLiteralTypes) 256 n = m.createLiteral(o.toString()); 257 else 258 n = m.createTypedLiteral(o); 259 260 } else if (sType.isMap() || (wType != null && wType.isMap())) { 261 if (o instanceof BeanMap) { 262 BeanMap bm = (BeanMap)o; 263 Object uri = null; 264 RdfBeanMeta rbm = (RdfBeanMeta)bm.getMeta().getExtendedMeta(RdfBeanMeta.class); 265 if (rbm.hasBeanUri()) 266 uri = rbm.getBeanUriProperty().get(bm, null); 267 String uri2 = getUri(uri, null); 268 n = m.createResource(uri2); 269 serializeBeanMap(bm, (Resource)n, typeName); 270 } else { 271 Map m2 = (Map)o; 272 n = m.createResource(); 273 serializeMap(m2, (Resource)n, sType); 274 } 275 276 } else if (sType.isBean()) { 277 BeanMap bm = toBeanMap(o); 278 Object uri = null; 279 RdfBeanMeta rbm = (RdfBeanMeta)bm.getMeta().getExtendedMeta(RdfBeanMeta.class); 280 if (rbm.hasBeanUri()) 281 uri = rbm.getBeanUriProperty().get(bm, null); 282 String uri2 = getUri(uri, null); 283 n = m.createResource(uri2); 284 serializeBeanMap(bm, (Resource)n, typeName); 285 286 } else if (sType.isCollectionOrArray() || (wType != null && wType.isCollection())) { 287 Collection c = sort(sType.isCollection() ? (Collection)o : toList(sType.getInnerClass(), o)); 288 RdfCollectionFormat f = collectionFormat; 289 RdfClassMeta rcm = sType.getExtendedMeta(RdfClassMeta.class); 290 if (rcm.getCollectionFormat() != RdfCollectionFormat.DEFAULT) 291 f = rcm.getCollectionFormat(); 292 if (bpm != null && bpm.getExtendedMeta(RdfBeanPropertyMeta.class).getCollectionFormat() != RdfCollectionFormat.DEFAULT) 293 f = bpm.getExtendedMeta(RdfBeanPropertyMeta.class).getCollectionFormat(); 294 switch (f) { 295 case BAG: n = serializeToContainer(c, eType, m.createBag()); break; 296 case LIST: n = serializeToList(c, eType); break; 297 case MULTI_VALUED: serializeToMultiProperties(c, eType, bpm, attrName, parentResource); break; 298 default: n = serializeToContainer(c, eType, m.createSeq()); 299 } 300 301 } else if (sType.isReader() || sType.isInputStream()) { 302 n = m.createLiteral(encodeTextInvalidChars(IOUtils.read(o))); 303 304 } else { 305 n = m.createLiteral(encodeTextInvalidChars(toString(o))); 306 } 307 308 pop(); 309 310 return n; 311 } 312 313 private String getUri(Object uri, Object uri2) { 314 String s = null; 315 if (uri != null) 316 s = uri.toString(); 317 if ((s == null || s.isEmpty()) && uri2 != null) 318 s = uri2.toString(); 319 if (s == null) 320 return null; 321 return getUriResolver().resolve(s); 322 } 323 324 private void serializeMap(Map m, Resource r, ClassMeta<?> type) throws Exception { 325 326 m = sort(m); 327 328 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 329 330 ArrayList<Map.Entry<Object,Object>> l = new ArrayList<>(m.entrySet()); 331 Collections.reverse(l); 332 for (Map.Entry<Object,Object> me : l) { 333 Object value = me.getValue(); 334 335 Object key = generalize(me.getKey(), keyType); 336 337 Namespace ns = juneauBpNs; 338 Property p = model.createProperty(ns.getUri(), encodeElementName(toString(key))); 339 RDFNode n = serializeAnything(value, false, valueType, toString(key), null, r); 340 if (n != null) 341 r.addProperty(p, n); 342 } 343 } 344 345 private void serializeBeanMap(BeanMap<?> m, Resource r, String typeName) throws Exception { 346 List<BeanPropertyValue> l = m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null); 347 Collections.reverse(l); 348 for (BeanPropertyValue bpv : l) { 349 BeanPropertyMeta pMeta = bpv.getMeta(); 350 ClassMeta<?> cMeta = pMeta.getClassMeta(); 351 352 if (pMeta.getExtendedMeta(RdfBeanPropertyMeta.class).isBeanUri()) 353 continue; 354 355 String key = bpv.getName(); 356 Object value = bpv.getValue(); 357 Throwable t = bpv.getThrown(); 358 if (t != null) 359 onBeanGetterException(pMeta, t); 360 361 if (canIgnoreValue(cMeta, key, value)) 362 continue; 363 364 BeanPropertyMeta bpm = bpv.getMeta(); 365 Namespace ns = bpm.getExtendedMeta(RdfBeanPropertyMeta.class).getNamespace(); 366 if (ns == null && useXmlNamespaces) 367 ns = bpm.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace(); 368 if (ns == null) 369 ns = juneauBpNs; 370 else if (autoDetectNamespaces) 371 addModelPrefix(ns); 372 373 Property p = model.createProperty(ns.getUri(), encodeElementName(key)); 374 RDFNode n = serializeAnything(value, pMeta.isUri(), cMeta, key, pMeta, r); 375 if (n != null) 376 r.addProperty(p, n); 377 } 378 } 379 380 381 private Container serializeToContainer(Collection c, ClassMeta<?> type, Container list) throws Exception { 382 383 ClassMeta<?> elementType = type.getElementType(); 384 for (Object e : c) { 385 RDFNode n = serializeAnything(e, false, elementType, null, null, null); 386 list = list.add(n); 387 } 388 return list; 389 } 390 391 private RDFList serializeToList(Collection c, ClassMeta<?> type) throws Exception { 392 ClassMeta<?> elementType = type.getElementType(); 393 List<RDFNode> l = new ArrayList<>(c.size()); 394 for (Object e : c) { 395 l.add(serializeAnything(e, false, elementType, null, null, null)); 396 } 397 return model.createList(l.iterator()); 398 } 399 400 private void serializeToMultiProperties(Collection c, ClassMeta<?> sType, 401 BeanPropertyMeta bpm, String attrName, Resource parentResource) throws Exception { 402 ClassMeta<?> elementType = sType.getElementType(); 403 for (Object e : c) { 404 Namespace ns = null; 405 if (bpm != null) { 406 ns = bpm.getExtendedMeta(RdfBeanPropertyMeta.class).getNamespace(); 407 if (ns == null && useXmlNamespaces) 408 ns = bpm.getExtendedMeta(XmlBeanPropertyMeta.class).getNamespace(); 409 } 410 if (ns == null) 411 ns = juneauBpNs; 412 else if (autoDetectNamespaces) 413 addModelPrefix(ns); 414 RDFNode n2 = serializeAnything(e, false, elementType, null, null, null); 415 Property p = model.createProperty(ns.getUri(), encodeElementName(attrName)); 416 parentResource.addProperty(p, n2); 417 } 418 } 419}