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 org.apache.jena.datatypes.RDFDatatype; 023import org.apache.jena.rdf.model.Literal; 024import org.apache.jena.rdf.model.RDFNode; 025import org.apache.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 org.apache.jena.datatypes.TypeMapper.getInstance; 041import static org.apache.jena.rdf.model.ResourceFactory.createLangLiteral; 042import static org.apache.jena.rdf.model.ResourceFactory.createPlainLiteral; 043import static org.apache.jena.rdf.model.ResourceFactory.createResource; 044import static org.apache.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.FedoraJcrConstants.FIELD_DELIMITER; 058import static org.fcrepo.kernel.modeshape.identifiers.NodeResourceConverter.nodeToResource; 059import static org.slf4j.LoggerFactory.getLogger; 060 061/** 062 * @author cabeer 063 * @since 10/8/14 064 */ 065public class ValueConverter extends Converter<Value, RDFNode> { 066 067 private static final Logger LOGGER = getLogger(ValueConverter.class); 068 069 private final Session session; 070 private final Converter<Node, Resource> graphSubjects; 071 072 /** 073 * Convert values between JCR values and RDF objects with the given session and subjects 074 * @param session the session 075 * @param graphSubjects the graph subjects 076 */ 077 public ValueConverter(final Session session, 078 final Converter<Resource, FedoraResource> graphSubjects) { 079 this.session = session; 080 this.graphSubjects = nodeToResource(graphSubjects); 081 } 082 083 @Override 084 protected RDFNode doForward(final Value value) { 085 try { 086 switch (value.getType()) { 087 case BOOLEAN: 088 return literal2node(value.getBoolean()); 089 case DATE: 090 return literal2node(value.getDate()); 091 case DECIMAL: 092 return literal2node(value.getDecimal()); 093 case DOUBLE: 094 return literal2node(value.getDouble()); 095 case LONG: 096 return literal2node(value.getLong()); 097 case URI: 098 return createResource(value.getString()); 099 case REFERENCE: 100 case WEAKREFERENCE: 101 case PATH: 102 return traverseLink(value); 103 default: 104 return stringliteral2node(value.getString()); 105 } 106 } catch (final RepositoryException e) { 107 throw new RepositoryRuntimeException(e); 108 } 109 } 110 111 @Override 112 protected Value doBackward(final RDFNode resource) { 113 114 try { 115 116 final ValueFactory valueFactory = session.getValueFactory(); 117 118 if (resource.isAnon()) { 119 // a non-URI resource (e.g. a blank node) 120 return valueFactory.createValue(resource.toString(), UNDEFINED); 121 } 122 123 final RdfLiteralJcrValueBuilder rdfLiteralJcrValueBuilder = new RdfLiteralJcrValueBuilder(); 124 125 if (resource.isURIResource()) { 126 rdfLiteralJcrValueBuilder.value(resource.asResource().getURI()).datatype("URI"); 127 } else { 128 129 final Literal literal = resource.asLiteral(); 130 final RDFDatatype dataType = literal.getDatatype(); 131 132 rdfLiteralJcrValueBuilder.value(literal.getString()).datatype(dataType).lang(literal.getLanguage()); 133 } 134 135 return valueFactory.createValue(rdfLiteralJcrValueBuilder.toString(), STRING); 136 } catch (final RepositoryException e) { 137 throw new RepositoryRuntimeException(e); 138 } 139 } 140 141 private static Literal literal2node(final Object literal) { 142 final Literal result = createTypedLiteral(literal); 143 LOGGER.trace("Converting {} into {}", literal, result); 144 return result; 145 } 146 147 148 private static RDFNode stringliteral2node(final String literal) { 149 final RdfLiteralJcrValueBuilder rdfLiteralJcrValueBuilder = new RdfLiteralJcrValueBuilder(literal); 150 151 if (rdfLiteralJcrValueBuilder.hasLang()) { 152 return createLangLiteral(rdfLiteralJcrValueBuilder.value(), rdfLiteralJcrValueBuilder.lang()); 153 } else if (rdfLiteralJcrValueBuilder.isResource()) { 154 return createResource(rdfLiteralJcrValueBuilder.value()); 155 } else if (rdfLiteralJcrValueBuilder.hasDatatypeUri()) { 156 return createTypedLiteral(rdfLiteralJcrValueBuilder.value(), rdfLiteralJcrValueBuilder.datatype()); 157 } else { 158 return createPlainLiteral(rdfLiteralJcrValueBuilder.value()); 159 } 160 } 161 162 private RDFNode traverseLink(final Value v) throws RepositoryException { 163 try { 164 return getGraphSubject(nodeForValue(session, v)); 165 } catch (final AccessDeniedException e) { 166 LOGGER.info("Link inaccessible by requesting user: {}, {}", v, session.getUserID()); 167 return INACCESSIBLE_RESOURCE; 168 } 169 } 170 171 /** 172 * Get the node that a property value refers to. 173 * @param session Session to use to load the node. 174 * @param v Value that refers to a node. 175 * @return the JCR node 176 * @throws RepositoryException When there is an error accessing the node. 177 * @throws RepositoryRuntimeException When the value type is not PATH, REFERENCE or WEAKREFERENCE. 178 **/ 179 public static javax.jcr.Node nodeForValue(final Session session, final Value v) throws RepositoryException { 180 if (v.getType() == PATH) { 181 return session.getNode(v.getString()); 182 } else if (v.getType() == REFERENCE || v.getType() == WEAKREFERENCE) { 183 return session.getNodeByIdentifier(v.getString()); 184 } else { 185 throw new RepositoryRuntimeException("Cannot convert value of type " 186 + PropertyType.nameFromValue(v.getType()) + " to a node reference"); 187 } 188 } 189 190 private RDFNode getGraphSubject(final javax.jcr.Node n) { 191 return graphSubjects.convert(n); 192 } 193 194 protected static class RdfLiteralJcrValueBuilder { 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}