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