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