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