001/** 002 * Copyright 2015 DuraSpace, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.fcrepo.kernel.modeshape.rdf.converters; 017 018import com.google.common.base.Converter; 019import com.google.common.base.Splitter; 020import com.hp.hpl.jena.datatypes.BaseDatatype; 021import com.hp.hpl.jena.datatypes.RDFDatatype; 022import com.hp.hpl.jena.rdf.model.Literal; 023import com.hp.hpl.jena.rdf.model.RDFNode; 024import com.hp.hpl.jena.rdf.model.Resource; 025import org.fcrepo.kernel.api.models.FedoraResource; 026import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 027import org.slf4j.Logger; 028 029import javax.jcr.Node; 030import javax.jcr.PropertyType; 031import javax.jcr.RepositoryException; 032import javax.jcr.Session; 033import javax.jcr.Value; 034import javax.jcr.ValueFactory; 035 036import java.util.Iterator; 037 038import static com.hp.hpl.jena.rdf.model.ResourceFactory.createLangLiteral; 039import static com.hp.hpl.jena.rdf.model.ResourceFactory.createPlainLiteral; 040import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource; 041import static com.hp.hpl.jena.rdf.model.ResourceFactory.createTypedLiteral; 042import static javax.jcr.PropertyType.BOOLEAN; 043import static javax.jcr.PropertyType.DATE; 044import static javax.jcr.PropertyType.DECIMAL; 045import static javax.jcr.PropertyType.DOUBLE; 046import static javax.jcr.PropertyType.LONG; 047import static javax.jcr.PropertyType.PATH; 048import static javax.jcr.PropertyType.REFERENCE; 049import static javax.jcr.PropertyType.STRING; 050import static javax.jcr.PropertyType.UNDEFINED; 051import static javax.jcr.PropertyType.URI; 052import static javax.jcr.PropertyType.WEAKREFERENCE; 053import static org.fcrepo.kernel.modeshape.identifiers.NodeResourceConverter.nodeToResource; 054import static org.slf4j.LoggerFactory.getLogger; 055 056/** 057 * @author cabeer 058 * @since 10/8/14 059 */ 060public class ValueConverter extends Converter<Value, RDFNode> { 061 062 private static final Logger LOGGER = getLogger(ValueConverter.class); 063 064 private final Session session; 065 private final Converter<Node, Resource> graphSubjects; 066 067 /** 068 * Convert values between JCR values and RDF objects with the given session and subjects 069 * @param session the session 070 * @param graphSubjects the graph subjects 071 */ 072 public ValueConverter(final Session session, 073 final Converter<Resource, FedoraResource> graphSubjects) { 074 this.session = session; 075 this.graphSubjects = nodeToResource(graphSubjects); 076 } 077 078 @Override 079 protected RDFNode doForward(final Value value) { 080 try { 081 switch (value.getType()) { 082 case BOOLEAN: 083 return literal2node(value.getBoolean()); 084 case DATE: 085 return literal2node(value.getDate()); 086 case DECIMAL: 087 return literal2node(value.getDecimal()); 088 case DOUBLE: 089 return literal2node(value.getDouble()); 090 case LONG: 091 return literal2node(value.getLong()); 092 case URI: 093 return createResource(value.getString()); 094 case REFERENCE: 095 case WEAKREFERENCE: 096 case PATH: 097 return traverseLink(value); 098 default: 099 return stringliteral2node(value.getString()); 100 } 101 } catch (final RepositoryException e) { 102 throw new RepositoryRuntimeException(e); 103 } 104 } 105 106 @Override 107 protected Value doBackward(final RDFNode resource) { 108 109 try { 110 111 final ValueFactory valueFactory = session.getValueFactory(); 112 113 if (resource.isAnon()) { 114 // a non-URI resource (e.g. a blank node) 115 return valueFactory.createValue(resource.toString(), UNDEFINED); 116 } 117 118 final RdfLiteralJcrValueBuilder rdfLiteralJcrValueBuilder = new RdfLiteralJcrValueBuilder(); 119 120 if (resource.isURIResource()) { 121 rdfLiteralJcrValueBuilder.value(resource.asResource().getURI()).datatype("URI"); 122 } else { 123 124 final Literal literal = resource.asLiteral(); 125 final RDFDatatype dataType = literal.getDatatype(); 126 127 rdfLiteralJcrValueBuilder.value(literal.getString()).datatype(dataType).lang(literal.getLanguage()); 128 } 129 130 return valueFactory.createValue(rdfLiteralJcrValueBuilder.toString(), STRING); 131 } catch (final RepositoryException e) { 132 throw new RepositoryRuntimeException(e); 133 } 134 } 135 136 private static Literal literal2node(final Object literal) { 137 final Literal result = createTypedLiteral(literal); 138 LOGGER.trace("Converting {} into {}", literal, result); 139 return result; 140 } 141 142 143 private static RDFNode stringliteral2node(final String literal) { 144 final RdfLiteralJcrValueBuilder rdfLiteralJcrValueBuilder = new RdfLiteralJcrValueBuilder(literal); 145 146 if (rdfLiteralJcrValueBuilder.hasLang()) { 147 return createLangLiteral(rdfLiteralJcrValueBuilder.value(), rdfLiteralJcrValueBuilder.lang()); 148 } else if (rdfLiteralJcrValueBuilder.isResource()) { 149 return createResource(rdfLiteralJcrValueBuilder.value()); 150 } else if (rdfLiteralJcrValueBuilder.hasDatatypeUri()) { 151 return createTypedLiteral(rdfLiteralJcrValueBuilder.value(), rdfLiteralJcrValueBuilder.datatype()); 152 } else { 153 return createPlainLiteral(literal); 154 } 155 } 156 157 private RDFNode traverseLink(final Value v) throws RepositoryException { 158 return getGraphSubject(nodeForValue(session,v)); 159 } 160 161 /** 162 * Get the node that a property value refers to. 163 * @param session Session to use to load the node. 164 * @param v Value that refers to a node. 165 * @throws RepositoryException When there is an error accessing the node. 166 * @throws RepositoryRuntimeException When the value type is not PATH, REFERENCE or WEAKREFERENCE. 167 **/ 168 public static javax.jcr.Node nodeForValue(final Session session, final Value v) throws RepositoryException { 169 if (v.getType() == PATH) { 170 return session.getNode(v.getString()); 171 } else if (v.getType() == REFERENCE || v.getType() == WEAKREFERENCE) { 172 return session.getNodeByIdentifier(v.getString()); 173 } else { 174 throw new RepositoryRuntimeException("Cannot convert value of type " 175 + PropertyType.nameFromValue(v.getType()) + " to a node reference"); 176 } 177 } 178 179 private RDFNode getGraphSubject(final javax.jcr.Node n) { 180 return graphSubjects.convert(n); 181 } 182 183 protected static class RdfLiteralJcrValueBuilder { 184 private static final String FIELD_DELIMITER = "\30^^\30"; 185 public static final Splitter JCR_VALUE_SPLITTER = Splitter.on(FIELD_DELIMITER); 186 187 private String value; 188 private String datatypeUri; 189 private String lang; 190 191 RdfLiteralJcrValueBuilder() { 192 193 } 194 195 public RdfLiteralJcrValueBuilder(final String literal) { 196 this(); 197 198 final Iterator<String> tokenizer = JCR_VALUE_SPLITTER.split(literal).iterator(); 199 200 value = tokenizer.next(); 201 202 if (tokenizer.hasNext()) { 203 datatypeUri = tokenizer.next(); 204 } 205 206 if (tokenizer.hasNext()) { 207 lang = tokenizer.next(); 208 } 209 } 210 211 @Override 212 public String toString() { 213 final StringBuilder b = new StringBuilder(); 214 215 b.append(value); 216 217 if (hasDatatypeUri()) { 218 b.append(FIELD_DELIMITER); 219 b.append(datatypeUri); 220 } else if (hasLang()) { 221 // if it has a language, but not a datatype, add a placeholder. 222 b.append(FIELD_DELIMITER); 223 } 224 225 if (hasLang()) { 226 b.append(FIELD_DELIMITER); 227 b.append(lang); 228 } 229 230 return b.toString(); 231 232 } 233 234 public String value() { 235 return value; 236 } 237 238 public RDFDatatype datatype() { 239 if (hasDatatypeUri()) { 240 return new BaseDatatype(datatypeUri); 241 } 242 return null; 243 } 244 245 public String lang() { 246 return lang; 247 } 248 249 public RdfLiteralJcrValueBuilder value(final String value) { 250 this.value = value; 251 return this; 252 } 253 254 public RdfLiteralJcrValueBuilder datatype(final String datatypeUri) { 255 this.datatypeUri = datatypeUri; 256 return this; 257 } 258 259 public RdfLiteralJcrValueBuilder datatype(final RDFDatatype datatypeUri) { 260 if (datatypeUri != null && !datatypeUri.getURI().isEmpty()) { 261 this.datatypeUri = datatypeUri.getURI(); 262 } 263 return this; 264 } 265 266 public RdfLiteralJcrValueBuilder lang(final String lang) { 267 this.lang = lang; 268 return this; 269 } 270 271 public boolean hasLang() { 272 return lang != null && !lang.isEmpty(); 273 } 274 275 public boolean hasDatatypeUri() { 276 return datatypeUri != null && !datatypeUri.isEmpty(); 277 } 278 279 public boolean isResource() { 280 return hasDatatypeUri() && datatypeUri.equals("URI"); 281 } 282 } 283}