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.base.Function; 019import com.google.common.collect.ImmutableList; 020import com.hp.hpl.jena.rdf.model.RDFNode; 021import com.hp.hpl.jena.rdf.model.Resource; 022 023import org.apache.marmotta.ldpath.LDPath; 024import org.apache.marmotta.ldpath.backend.jena.GenericJenaBackend; 025import org.apache.marmotta.ldpath.exception.LDPathParseException; 026import org.fcrepo.kernel.exception.RepositoryRuntimeException; 027import org.fcrepo.kernel.utils.iterators.RdfStream; 028import org.fcrepo.transform.Transformation; 029import org.slf4j.Logger; 030 031import javax.jcr.Node; 032import javax.jcr.RepositoryException; 033import javax.jcr.nodetype.NodeType; 034import javax.ws.rs.WebApplicationException; 035 036import java.io.InputStream; 037import java.io.InputStreamReader; 038import java.util.Collection; 039import java.util.Comparator; 040import java.util.List; 041import java.util.Map; 042import java.util.Objects; 043import java.util.Set; 044 045import static com.google.common.collect.Collections2.transform; 046import static com.google.common.collect.ImmutableSortedSet.orderedBy; 047import static com.google.common.collect.Maps.transformValues; 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 064 // TODO: this mime type was made up 065 public static final String APPLICATION_RDF_LDPATH = "application/rdf+ldpath"; 066 private final InputStream query; 067 068 private static final Logger LOGGER = getLogger(LDPathTransform.class); 069 070 private static final Comparator<NodeType> nodeTypeComp = new Comparator<NodeType>() { 071 072 @Override 073 public int compare(final NodeType o1, final NodeType o2) { 074 return o1.getName().compareTo(o2.getName()); 075 076 } 077 }; 078 079 /** 080 * Construct a new Transform from the InputStream 081 * @param query the query 082 */ 083 public LDPathTransform(final InputStream query) { 084 this.query = query; 085 } 086 087 /** 088 * Pull a node-type specific transform out of JCR 089 * @param node the node 090 * @param key the key 091 * @return node-type specific transform 092 * @throws RepositoryException if repository exception occurred 093 */ 094 public static LDPathTransform getNodeTypeTransform(final Node node, 095 final String key) throws RepositoryException { 096 097 final Node programNode = node.getSession().getNode(CONFIGURATION_FOLDER + key); 098 099 LOGGER.debug("Found program node: {}", programNode.getPath()); 100 101 final NodeType primaryNodeType = node.getPrimaryNodeType(); 102 103 final Set<NodeType> supertypes = 104 orderedBy(nodeTypeComp).add(primaryNodeType.getSupertypes()) 105 .build(); 106 final Set<NodeType> mixinTypes = 107 orderedBy(nodeTypeComp).add(node.getMixinNodeTypes()).build(); 108 109 // start with mixins, primary type, and supertypes of primary type 110 final ImmutableList.Builder<NodeType> nodeTypesB = 111 new ImmutableList.Builder<NodeType>().addAll(mixinTypes).add( 112 primaryNodeType).addAll(supertypes); 113 114 // add supertypes of mixins 115 for (final NodeType mixin : mixinTypes) { 116 nodeTypesB.addAll(orderedBy(nodeTypeComp).add( 117 mixin.getDeclaredSupertypes()).build()); 118 } 119 120 final List<NodeType> nodeTypes = nodeTypesB.build(); 121 122 LOGGER.debug("Discovered node types: {}", nodeTypes); 123 124 for (final NodeType nodeType : nodeTypes) { 125 if (programNode.hasNode(nodeType.toString())) { 126 return new LDPathTransform(programNode.getNode(nodeType.toString()) 127 .getNode(JCR_CONTENT) 128 .getProperty(JCR_DATA) 129 .getBinary().getStream()); 130 } 131 } 132 133 throw new WebApplicationException(new Exception( 134 "Couldn't find transformation for " + node.getPath() 135 + " and transformation key " + key), SC_BAD_REQUEST); 136 } 137 138 @Override 139 public List<Map<String, Collection<Object>>> apply(final RdfStream stream) { 140 try { 141 final LDPath<RDFNode> ldpathForResource = 142 getLdpathResource(stream); 143 144 final Resource context = createResource(stream.topic().getURI()); 145 146 final Map<String, Collection<?>> wildcardCollection = 147 ldpathForResource.programQuery(context, new InputStreamReader( 148 query)); 149 150 return ImmutableList.of(transformLdpathOutputToSomethingSerializable(wildcardCollection)); 151 } catch (final LDPathParseException e) { 152 throw new RepositoryRuntimeException(e); 153 } 154 } 155 156 @Override 157 public InputStream getQuery() { 158 return query; 159 } 160 161 @Override 162 public boolean equals(final Object other) { 163 return other instanceof LDPathTransform && 164 query.equals(((LDPathTransform)other).getQuery()); 165 } 166 167 @Override 168 public int hashCode() { 169 return Objects.hashCode(getQuery()); 170 } 171 172 /** 173 * Get the LDPath resource for an object 174 * @param rdfStream 175 * @return the LDPath resource for the given object 176 */ 177 private static LDPath<RDFNode> getLdpathResource(final RdfStream rdfStream) { 178 179 return new LDPath<>(new GenericJenaBackend(rdfStream.asModel())); 180 181 } 182 183 /** 184 * In order for the JAX-RS serialization magic to work, we have to turn the map into 185 * a non-wildcard type. 186 * @param collectionMap 187 * @return map of the LDPath 188 */ 189 private static Map<String, Collection<Object>> transformLdpathOutputToSomethingSerializable( 190 final Map<String, Collection<?>> collectionMap) { 191 192 return transformValues(collectionMap, 193 WILDCARD_COLLECTION_TO_OBJECT_COLLECTION); 194 } 195 196 private static final Function<Collection<?>, Collection<Object>> WILDCARD_COLLECTION_TO_OBJECT_COLLECTION = 197 new Function<Collection<?>, Collection<Object>>() { 198 199 @Override 200 public Collection<Object> apply(final Collection<?> input) { 201 return transform(input, ANYTHING_TO_OBJECT_FUNCTION); 202 } 203 }; 204 205 private static final Function<Object, Object> ANYTHING_TO_OBJECT_FUNCTION = 206 new Function<Object, Object>() { 207 208 @Override 209 public Object apply(final Object input) { 210 return input; 211 } 212 }; 213 214 @Override 215 public LDPathTransform newTransform(final InputStream query) { 216 return new LDPathTransform(query); 217 } 218}