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