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.impl.utils; 017 018import static org.slf4j.LoggerFactory.getLogger; 019 020import javax.jcr.RepositoryException; 021import javax.jcr.Session; 022 023import com.google.common.base.Joiner; 024import org.fcrepo.kernel.models.FedoraResource; 025import org.fcrepo.kernel.exception.MalformedRdfException; 026import org.fcrepo.kernel.exception.RepositoryRuntimeException; 027import org.fcrepo.kernel.identifiers.IdentifierConverter; 028import org.fcrepo.kernel.impl.rdf.JcrRdfTools; 029import org.slf4j.Logger; 030 031import com.hp.hpl.jena.rdf.listeners.StatementListener; 032import com.hp.hpl.jena.rdf.model.Resource; 033import com.hp.hpl.jena.rdf.model.Statement; 034import com.hp.hpl.jena.rdf.model.RDFNode; 035import com.hp.hpl.jena.rdf.model.Property; 036import com.hp.hpl.jena.vocabulary.RDF; 037 038import java.util.ArrayList; 039import java.util.List; 040 041/** 042 * Listen to Jena statement events, and when the statement is changed in the 043 * graph store, make the change within JCR as well. 044 * 045 * @author awoods 046 */ 047public class JcrPropertyStatementListener extends StatementListener { 048 049 private static final Logger LOGGER = getLogger(JcrPropertyStatementListener.class); 050 051 private final JcrRdfTools jcrRdfTools; 052 053 private final IdentifierConverter<Resource, FedoraResource> idTranslator; 054 055 private final List<String> exceptions; 056 057 /** 058 * Construct a statement listener within the given session 059 * 060 * @param idTranslator 061 * @param session 062 */ 063 public JcrPropertyStatementListener(final IdentifierConverter<Resource, FedoraResource> idTranslator, 064 final Session session) { 065 this(idTranslator, new JcrRdfTools(idTranslator, session)); 066 } 067 068 /** 069 * Construct a statement listener within the given session 070 * 071 * @param idTranslator 072 */ 073 public JcrPropertyStatementListener(final IdentifierConverter<Resource, FedoraResource> idTranslator, 074 final JcrRdfTools jcrRdfTools) { 075 super(); 076 this.idTranslator = idTranslator; 077 this.jcrRdfTools = jcrRdfTools; 078 this.exceptions = new ArrayList<>(); 079 } 080 081 /** 082 * When a statement is added to the graph, serialize it to a JCR property 083 * 084 * @param input 085 */ 086 @Override 087 public void addedStatement(final Statement input) { 088 LOGGER.debug(">> added statement {}", input); 089 090 try { 091 final Resource subject = input.getSubject(); 092 093 // if it's not about a node, ignore it. 094 if (!idTranslator.inDomain(subject) && !subject.isAnon()) { 095 return; 096 } 097 098 final Statement s = jcrRdfTools.skolemize(idTranslator, input); 099 100 final FedoraResource resource = idTranslator.convert(s.getSubject()); 101 102 // special logic for handling rdf:type updates. 103 // if the object is an already-existing mixin, update 104 // the node's mixins. If it isn't, just treat it normally. 105 final Property property = s.getPredicate(); 106 final RDFNode objectNode = s.getObject(); 107 if (property.equals(RDF.type) && objectNode.isResource()) { 108 final Resource mixinResource = objectNode.asResource(); 109 jcrRdfTools.addMixin(resource, mixinResource, input.getModel().getNsPrefixMap()); 110 return; 111 } 112 113 jcrRdfTools.addProperty(resource, property, objectNode, input.getModel().getNsPrefixMap()); 114 } catch (final RepositoryException | RepositoryRuntimeException e) { 115 exceptions.add(e.getMessage()); 116 } 117 118 } 119 120 /** 121 * When a statement is removed, remove it from the JCR properties 122 * 123 * @param s 124 */ 125 @Override 126 public void removedStatement(final Statement s) { 127 LOGGER.trace(">> removed statement {}", s); 128 129 try { 130 final Resource subject = s.getSubject(); 131 132 // if it's not about a node, we don't care. 133 if (!idTranslator.inDomain(subject)) { 134 return; 135 } 136 137 final FedoraResource resource = idTranslator.convert(subject); 138 139 // special logic for handling rdf:type updates. 140 // if the object is an already-existing mixin, update 141 // the node's mixins. If it isn't, just treat it normally. 142 final Property property = s.getPredicate(); 143 final RDFNode objectNode = s.getObject(); 144 145 if (property.equals(RDF.type) && objectNode.isResource()) { 146 final Resource mixinResource = objectNode.asResource(); 147 try { 148 jcrRdfTools.removeMixin(resource, mixinResource, s.getModel().getNsPrefixMap()); 149 } catch (final RepositoryException e) { 150 // TODO 151 } 152 return; 153 } 154 155 jcrRdfTools.removeProperty(resource, property, objectNode, s.getModel().getNsPrefixMap()); 156 157 } catch (final RepositoryException | RepositoryRuntimeException e) { 158 exceptions.add(e.getMessage()); 159 } 160 161 } 162 163 /** 164 * Assert that no exceptions were thrown while this listener was processing change 165 */ 166 public void assertNoExceptions() throws MalformedRdfException { 167 if (!exceptions.isEmpty()) { 168 throw new MalformedRdfException(Joiner.on("\n").join(exceptions)); 169 } 170 } 171}