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.transform.transformations; 017 018import com.google.common.collect.ImmutableList; 019import com.hp.hpl.jena.rdf.model.RDFNode; 020import com.hp.hpl.jena.rdf.model.Resource; 021 022import org.apache.marmotta.ldpath.LDPath; 023import org.apache.marmotta.ldpath.backend.jena.GenericJenaBackend; 024import org.apache.marmotta.ldpath.exception.LDPathParseException; 025 026import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 027import org.fcrepo.kernel.api.utils.iterators.RdfStream; 028import org.fcrepo.transform.Transformation; 029 030import org.slf4j.Logger; 031 032import javax.jcr.Node; 033import javax.jcr.RepositoryException; 034import javax.jcr.nodetype.NodeType; 035import javax.ws.rs.WebApplicationException; 036 037import java.io.InputStream; 038import java.io.InputStreamReader; 039import java.util.Collection; 040import java.util.Comparator; 041import java.util.List; 042import java.util.Map; 043import java.util.Objects; 044import java.util.Set; 045 046import static com.google.common.collect.ImmutableList.builder; 047import static com.google.common.collect.ImmutableSortedSet.orderedBy; 048import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource; 049import static org.apache.http.HttpStatus.SC_BAD_REQUEST; 050import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT; 051import static org.modeshape.jcr.api.JcrConstants.JCR_DATA; 052import static org.slf4j.LoggerFactory.getLogger; 053 054/** 055 * Utilities for working with LDPath 056 * 057 * @author cbeer 058 */ 059public class LDPathTransform implements Transformation<List<Map<String, Collection<Object>>>> { 060 061 public static final String CONFIGURATION_FOLDER = "/fedora:system/fedora:transform/fedora:ldpath/"; 062 063 private static final Comparator<NodeType> BY_NAME = 064 (final NodeType o1, final NodeType o2) -> o1.getName().compareTo(o2.getName()); 065 066 // TODO: this mime type was made up 067 public static final String APPLICATION_RDF_LDPATH = "application/rdf+ldpath"; 068 private final InputStream query; 069 070 private static final Logger LOGGER = getLogger(LDPathTransform.class); 071 072 /** 073 * Construct a new Transform from the InputStream 074 * @param query the query 075 */ 076 public LDPathTransform(final InputStream query) { 077 this.query = query; 078 } 079 080 /** 081 * Pull a node-type specific transform out of JCR 082 * @param node the node 083 * @param key the key 084 * @return node-type specific transform 085 * @throws RepositoryException if repository exception occurred 086 */ 087 public static LDPathTransform getNodeTypeTransform(final Node node, 088 final String key) throws RepositoryException { 089 090 final Node programNode = node.getSession().getNode(CONFIGURATION_FOLDER + key); 091 092 LOGGER.debug("Found program node: {}", programNode.getPath()); 093 094 final NodeType primaryNodeType = node.getPrimaryNodeType(); 095 096 final Set<NodeType> supertypes = orderedBy(BY_NAME).add(primaryNodeType.getSupertypes()).build(); 097 final Set<NodeType> mixinTypes = orderedBy(BY_NAME).add(node.getMixinNodeTypes()).build(); 098 099 // start with mixins, primary type, and supertypes of primary type 100 final ImmutableList.Builder<NodeType> nodeTypesB = builder(); 101 nodeTypesB.addAll(mixinTypes).add(primaryNodeType).addAll(supertypes); 102 103 // add supertypes of mixins 104 mixinTypes.stream().map(mixin -> orderedBy(BY_NAME).add(mixin.getDeclaredSupertypes()).build()) 105 .forEach(nodeTypesB::addAll); 106 107 final List<NodeType> nodeTypes = nodeTypesB.build(); 108 109 LOGGER.debug("Discovered node types: {}", nodeTypes); 110 for (final NodeType nodeType : nodeTypes) { 111 if (programNode.hasNode(nodeType.toString())) { 112 return new LDPathTransform(programNode.getNode(nodeType.toString()) 113 .getNode(JCR_CONTENT) 114 .getProperty(JCR_DATA) 115 .getBinary().getStream()); 116 } 117 } 118 119 throw new WebApplicationException(new Exception( 120 "Couldn't find transformation for " + node.getPath() 121 + " and transformation key " + key), SC_BAD_REQUEST); 122 } 123 124 @Override 125 public List<Map<String, Collection<Object>>> apply(final RdfStream stream) { 126 final LDPath<RDFNode> ldpathForResource = 127 getLdpathResource(stream); 128 129 final Resource context = createResource(stream.topic().getURI()); 130 131 try { 132 return ImmutableList.of(unsafeCast( 133 ldpathForResource.programQuery(context, new InputStreamReader(query)))); 134 } catch (final LDPathParseException e) { 135 throw new RepositoryRuntimeException(e); 136 } 137 } 138 139 @SuppressWarnings("unchecked") 140 private static <F, T> T unsafeCast(final F from) { 141 return (T) from; 142 } 143 144 @Override 145 public boolean equals(final Object other) { 146 return other instanceof LDPathTransform && ((LDPathTransform) other).query.equals(query); 147 } 148 149 @Override 150 public int hashCode() { 151 return Objects.hashCode(query); 152 } 153 154 /** 155 * Get the LDPath resource for an object 156 * @param rdfStream 157 * @return the LDPath resource for the given object 158 */ 159 private static LDPath<RDFNode> getLdpathResource(final RdfStream rdfStream) { 160 161 return new LDPath<>(new GenericJenaBackend(rdfStream.asModel())); 162 163 } 164}