diff --git a/fcrepo-http-commons/src/main/java/org/fcrepo/responses/QueryExecutionProvider.java b/fcrepo-http-commons/src/main/java/org/fcrepo/responses/QueryExecutionProvider.java new file mode 100644 index 0000000000..34d3eae146 --- /dev/null +++ b/fcrepo-http-commons/src/main/java/org/fcrepo/responses/QueryExecutionProvider.java @@ -0,0 +1,90 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.responses; + +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.ResultSet; +import com.hp.hpl.jena.sparql.resultset.ResultsFormat; +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static com.google.common.collect.ImmutableList.of; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Helper for writing QueryExecutions results out in a variety + * of serialization formats. + * + */ +@Provider +@Component +public class QueryExecutionProvider implements MessageBodyWriter { + + private static final Logger logger = getLogger(QueryExecutionProvider.class); + + @Override + public void writeTo(final QueryExecution qexec, final Class type, + final Type genericType, final Annotation[] annotations, + final MediaType mediaType, + final MultivaluedMap httpHeaders, + final OutputStream entityStream) throws IOException, + WebApplicationException { + + logger.debug("Writing a response for: {} with MIMEtype: {}", qexec, + mediaType); + + // add standard headers + httpHeaders.put("Content-type", of((Object) mediaType.toString())); + + try { + final ResultSet results = qexec.execSelect(); + new ResultSetStreamingOutput(results, mediaType).write(entityStream); + } finally { + qexec.close(); + } + } + + @Override + public boolean isWriteable(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { + + // we can return a result for any MIME type that Jena can serialize + final Boolean appropriateResultType = ResultSetStreamingOutput.getResultsFormat(mediaType) != ResultsFormat.FMT_UNKNOWN; + return appropriateResultType && + (QueryExecution.class.isAssignableFrom(type) || QueryExecution.class + .isAssignableFrom(genericType.getClass())); + } + + @Override + public long getSize(final QueryExecution rdf, final Class type, + final Type genericType, final Annotation[] annotations, + final MediaType mediaType) { + // we don't know in advance how large the result might be + return -1; + } + +} diff --git a/fcrepo-http-commons/src/main/java/org/fcrepo/responses/ResultSetStreamingOutput.java b/fcrepo-http-commons/src/main/java/org/fcrepo/responses/ResultSetStreamingOutput.java new file mode 100644 index 0000000000..ace6c52370 --- /dev/null +++ b/fcrepo-http-commons/src/main/java/org/fcrepo/responses/ResultSetStreamingOutput.java @@ -0,0 +1,111 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.responses; + +import com.hp.hpl.jena.query.DatasetFactory; +import com.hp.hpl.jena.query.ResultSet; +import com.hp.hpl.jena.query.ResultSetFormatter; +import com.hp.hpl.jena.sparql.resultset.ResultsFormat; +import org.apache.jena.riot.WebContent; + +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Stream the results of a SPARQL Query + */ +class ResultSetStreamingOutput { + private final ResultSet results; + private final MediaType mediaType; + + /** + * Stream the results of a SPARQL Query with the given MediaType + * @param results + * @param mediaType + */ + public ResultSetStreamingOutput(final ResultSet results, final MediaType mediaType) { + this.mediaType = mediaType; + this.results = results; + } + + /** + * + * @param entityStream + * @throws IOException + */ + public void write(OutputStream entityStream) throws IOException { + + final ResultsFormat resultsFormat = getResultsFormat(mediaType); + + if (resultsFormat == ResultsFormat.FMT_UNKNOWN) { + new GraphStoreStreamingOutput(DatasetFactory.create(ResultSetFormatter.toModel(results)), mediaType).write(entityStream); + } else { + ResultSetFormatter.output(entityStream, results, resultsFormat); + } + + } + + /** + * Map the HTTP MediaType to a SPARQL ResultsFormat + * @param mediaType + * @return + */ + public static ResultsFormat getResultsFormat(final MediaType mediaType) { + switch (mediaType.toString()) { + case WebContent.contentTypeTextTSV: + return ResultsFormat.FMT_RS_TSV; + + case WebContent.contentTypeTextCSV: + return ResultsFormat.FMT_RS_CSV; + + case WebContent.contentTypeSSE: + return ResultsFormat.FMT_RS_SSE; + + case WebContent.contentTypeTextPlain: + return ResultsFormat.FMT_TEXT; + + case WebContent.contentTypeResultsJSON: + return ResultsFormat.FMT_RS_JSON; + + case WebContent.contentTypeResultsXML: + return ResultsFormat.FMT_RS_XML; + + case WebContent.contentTypeResultsBIO: + return ResultsFormat.FMT_RS_BIO; + + case WebContent.contentTypeTurtle: + case WebContent.contentTypeTurtleAlt1: + case WebContent.contentTypeTurtleAlt2: + return ResultsFormat.FMT_RDF_TTL; + + case WebContent.contentTypeN3: + case WebContent.contentTypeN3Alt1: + case WebContent.contentTypeN3Alt2: + return ResultsFormat.FMT_RDF_N3; + + case WebContent.contentTypeNTriples: + return ResultsFormat.FMT_RDF_NT; + + case WebContent.contentTypeRDFXML: + return ResultsFormat.FMT_RDF_XML; + + } + + return ResultsFormat.FMT_UNKNOWN; + } +} diff --git a/fcrepo-http-commons/src/test/java/org/fcrepo/responses/QueryExecutionProviderTest.java b/fcrepo-http-commons/src/test/java/org/fcrepo/responses/QueryExecutionProviderTest.java new file mode 100644 index 0000000000..dcfbca27df --- /dev/null +++ b/fcrepo-http-commons/src/test/java/org/fcrepo/responses/QueryExecutionProviderTest.java @@ -0,0 +1,109 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.responses; + +import com.hp.hpl.jena.graph.Triple; +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.QueryFactory; +import com.hp.hpl.jena.sparql.core.DatasetImpl; +import org.apache.commons.io.IOUtils; +import org.apache.jena.riot.WebContent; +import org.junit.Test; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MultivaluedMap; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Type; + +import static com.hp.hpl.jena.graph.NodeFactory.createLiteral; +import static com.hp.hpl.jena.graph.NodeFactory.createURI; +import static com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel; +import static javax.ws.rs.core.MediaType.TEXT_HTML_TYPE; +import static javax.ws.rs.core.MediaType.valueOf; +import static org.fcrepo.responses.RdfSerializationUtils.primaryTypePredicate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public class QueryExecutionProviderTest { + + final QueryExecutionProvider testObj = new QueryExecutionProvider(); + + Dataset testData = new DatasetImpl(createDefaultModel()); + + + { + testData.asDatasetGraph().getDefaultGraph().add( + new Triple(createURI("test:subject"), + createURI("test:predicate"), + createLiteral("test:object"))); + testData.asDatasetGraph().getDefaultGraph().add( + new Triple(createURI("test:subject"), primaryTypePredicate, + createLiteral("nt:file"))); + + } + + @SuppressWarnings("unchecked") + @Test + public void testWriteTo() throws WebApplicationException, + IllegalArgumentException, IOException { + + final Query sparqlQuery = + QueryFactory.create("SELECT ?x WHERE { ?x ?y ?z }"); + + QueryExecution testResult = QueryExecutionFactory.create(sparqlQuery, testData); + + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + + testObj.writeTo(testResult, QueryExecution.class, mock(Type.class), null, + valueOf(WebContent.contentTypeResultsXML), mock(MultivaluedMap.class), + outStream); + final byte[] results = outStream.toByteArray(); + assertTrue("Got no output from serialization!", results.length > 0); + assertTrue("Couldn't find test RDF-object mentioned!", new String( + results).contains("test:subject")); + } + + @Test + public void testGetSize() { + assertEquals("Returned wrong size from QueryExecutionProvider!", testObj + .getSize(null, null, null, null, null), -1); + + } + + @Test + public void testIsWritable() throws Exception { + assertTrue( + "Gave false response to QueryExecutionProvider.isWriteable() that contained a legitimate combination of parameters!", + testObj.isWriteable(QueryExecution.class, QueryExecution.class, null, + valueOf(WebContent.contentTypeResultsXML))); + assertFalse( + "RdfProvider.isWriteable() should return false if asked to serialize anything other than QueryExecution!", + testObj.isWriteable(QueryExecutionProvider.class, QueryExecutionProvider.class, + null, valueOf(WebContent.contentTypeResultsXML))); + assertFalse( + "RdfProvider.isWriteable() should return false to text/html!", + testObj.isWriteable(QueryExecution.class, QueryExecution.class, null, + TEXT_HTML_TYPE)); + + } +} diff --git a/fcrepo-http-commons/src/test/java/org/fcrepo/responses/RdfProviderTest.java b/fcrepo-http-commons/src/test/java/org/fcrepo/responses/RdfProviderTest.java index b284771eef..e5fe8382fe 100644 --- a/fcrepo-http-commons/src/test/java/org/fcrepo/responses/RdfProviderTest.java +++ b/fcrepo-http-commons/src/test/java/org/fcrepo/responses/RdfProviderTest.java @@ -75,7 +75,7 @@ public void testIsWriteable() { @Test public void testGetSize() { - assertEquals("Returned wrong size from HtmlProvider!", rdfProvider + assertEquals("Returned wrong size from RdfProvider!", rdfProvider .getSize(null, null, null, null, null), -1); } diff --git a/fcrepo-http-commons/src/test/java/org/fcrepo/responses/ResultSetStreamingOutputTest.java b/fcrepo-http-commons/src/test/java/org/fcrepo/responses/ResultSetStreamingOutputTest.java new file mode 100644 index 0000000000..6239c0e223 --- /dev/null +++ b/fcrepo-http-commons/src/test/java/org/fcrepo/responses/ResultSetStreamingOutputTest.java @@ -0,0 +1,122 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.responses; + +import com.hp.hpl.jena.graph.Triple; +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.QueryFactory; +import com.hp.hpl.jena.query.ResultSet; +import com.hp.hpl.jena.sparql.core.DatasetImpl; +import com.hp.hpl.jena.sparql.resultset.ResultsFormat; +import org.apache.jena.riot.WebContent; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import javax.ws.rs.core.MediaType; + +import java.io.ByteArrayOutputStream; + +import static com.hp.hpl.jena.graph.NodeFactory.createLiteral; +import static com.hp.hpl.jena.graph.NodeFactory.createURI; +import static com.hp.hpl.jena.rdf.model.ModelFactory.createDefaultModel; +import static org.fcrepo.responses.RdfSerializationUtils.primaryTypePredicate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.MockitoAnnotations.initMocks; + +public class ResultSetStreamingOutputTest { + + @Mock + ResultSet mockResultSet; + + ResultSetStreamingOutput testObj; + + Dataset testData = new DatasetImpl(createDefaultModel()); + + + { + testData.asDatasetGraph().getDefaultGraph().add( + new Triple(createURI("test:subject"), + createURI("test:predicate"), + createLiteral("test:object"))); + testData.asDatasetGraph().getDefaultGraph().add( + new Triple(createURI("test:subject"), primaryTypePredicate, + createLiteral("nt:file"))); + + } + + @Before + public void setUp() { + initMocks(this); + } + + @Test + public void testWrite() throws Exception { + + final Query sparqlQuery = + QueryFactory.create("SELECT ?x WHERE { ?x ?y ?z }"); + + QueryExecution testResult = QueryExecutionFactory.create(sparqlQuery, testData); + + try { + final ResultSet resultSet = testResult.execSelect(); + + testObj = new ResultSetStreamingOutput(resultSet, MediaType.valueOf(WebContent.contentTypeTextTSV)); + + try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { + testObj.write(out); + final String serialized = out.toString(); + assertTrue(serialized.contains("test:subject")); + } + } finally { + testResult.close(); + } + } + + @Test + public void testWriteWithRDFFormat() throws Exception { + + final Query sparqlQuery = + QueryFactory.create("SELECT ?x WHERE { ?x ?y ?z }"); + + QueryExecution testResult = QueryExecutionFactory.create(sparqlQuery, testData); + + try { + final ResultSet resultSet = testResult.execSelect(); + + testObj = new ResultSetStreamingOutput(resultSet, MediaType.valueOf(WebContent.contentTypeRDFXML)); + + try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { + testObj.write(out); + final String serialized = out.toString(); + assertTrue(serialized.contains("rs:ResultSet")); + } + } finally { + testResult.close(); + } + } + + @Test + public void testGetResultsFormat() throws Exception { + assertEquals(ResultsFormat.FMT_RS_TSV, ResultSetStreamingOutput.getResultsFormat(MediaType.valueOf(WebContent.contentTypeTextTSV))); + assertEquals(ResultsFormat.FMT_UNKNOWN, ResultSetStreamingOutput.getResultsFormat(MediaType.valueOf("some/type"))); + } +} diff --git a/fcrepo-kernel/src/test/java/org/fcrepo/utils/TestHelpers.java b/fcrepo-kernel/src/test/java/org/fcrepo/utils/TestHelpers.java index 5b4fcb4a31..48654d9328 100644 --- a/fcrepo-kernel/src/test/java/org/fcrepo/utils/TestHelpers.java +++ b/fcrepo-kernel/src/test/java/org/fcrepo/utils/TestHelpers.java @@ -24,6 +24,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.lang.reflect.Field; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -141,4 +142,40 @@ public static byte[] randomData(final int byteLength) { GARBAGE_GENERATOR.nextBytes(bytes); return bytes; } + + + /** + * Set a field via reflection + * + * @param parent the owner object of the field + * @param name the name of the field + * @param obj the value to set + * @throws NoSuchFieldException + */ + public static void setField(final Object parent, final String name, + final Object obj) throws NoSuchFieldException { + /* check the parent class too if the field could not be found */ + try { + final Field f = findField(parent.getClass(), name); + f.setAccessible(true); + f.set(parent, obj); + } catch (final Exception e) { + e.printStackTrace(); + } + } + + private static Field findField(final Class clazz, final String name) + throws NoSuchFieldException { + for (final Field f : clazz.getDeclaredFields()) { + if (f.getName().equals(name)) { + return f; + } + } + if (clazz.getSuperclass() == null) { + throw new NoSuchFieldException("Field " + name + + " could not be found"); + } else { + return findField(clazz.getSuperclass(), name); + } + } } diff --git a/fcrepo-transform/pom.xml b/fcrepo-transform/pom.xml new file mode 100644 index 0000000000..de9d401af8 --- /dev/null +++ b/fcrepo-transform/pom.xml @@ -0,0 +1,157 @@ + + + + fcrepo + org.fcrepo + 4.0.0-alpha-2-SNAPSHOT + + 4.0.0 + + fcrepo-transform + + + + + org.fcrepo + fcrepo-kernel + ${project.version} + + + ch.qos.logback + logback-classic + + + org.jboss.jbossts + jbossjta + + + org.springframework + spring-beans + + + + + + + org.fcrepo + fcrepo-http-commons + ${project.version} + + + + org.apache.marmotta + ldpath-core + 3.1.0-incubating-SNAPSHOT + + + + org.apache.marmotta + ldpath-backend-jena + 3.1.0-incubating-SNAPSHOT + + + org.apache.jena + jena-core + + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + + + + + org.springframework + spring-context + + + + javax.servlet + javax.servlet-api + test + + + + + org.fcrepo + fcrepo-http-commons + ${project.version} + test + test-jar + + + + com.sun.jersey.contribs + jersey-spring + test + + + + ch.qos.logback + logback-classic + test + + + + org.springframework + spring-test + + + + org.fcrepo + fcrepo-kernel + ${project.version} + tests + test + + + org.glassfish.grizzly + grizzly-http-server + test + + + org.glassfish.grizzly + grizzly-http-servlet + test + + + com.sun.jersey + jersey-grizzly2 + test + + + + + javax.mail + mail + test + + + com.sun.jersey + jersey-core + + + com.sun.jersey + jersey-json + + + + + javax + javaee-api + + + + + diff --git a/fcrepo-transform/src/main/java/org/fcrepo/transform/Transformation.java b/fcrepo-transform/src/main/java/org/fcrepo/transform/Transformation.java new file mode 100644 index 0000000000..361d8ea527 --- /dev/null +++ b/fcrepo-transform/src/main/java/org/fcrepo/transform/Transformation.java @@ -0,0 +1,39 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.transform; + +import com.google.common.base.Function; +import com.hp.hpl.jena.query.Dataset; + +import java.io.InputStream; + +public interface Transformation extends Function { + + /** + * Execute a transform on a dataset + * @param dataset + * @return + */ + Object apply(final Dataset dataset); + + /** + * Get the Query the transformation is using + * @return + */ + InputStream getQuery(); + +} diff --git a/fcrepo-transform/src/main/java/org/fcrepo/transform/TransformationFactory.java b/fcrepo-transform/src/main/java/org/fcrepo/transform/TransformationFactory.java new file mode 100644 index 0000000000..9664fd5d33 --- /dev/null +++ b/fcrepo-transform/src/main/java/org/fcrepo/transform/TransformationFactory.java @@ -0,0 +1,82 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.transform; + +import org.apache.jena.riot.WebContent; +import org.fcrepo.transform.transformations.LDPathTransform; +import org.fcrepo.transform.transformations.SparqlQueryTransform; + +import javax.ws.rs.core.MediaType; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Throwables.propagate; + +/** + * Get a Transformation from a MediaType + */ +public class TransformationFactory { + + private Map mimeToTransform; + + /** + * Get a new TransformationFactory with the default classes + */ + public TransformationFactory() { + mimeToTransform = new HashMap(); + mimeToTransform.put(WebContent.contentTypeSPARQLQuery, SparqlQueryTransform.class); + mimeToTransform.put(LDPathTransform.APPLICATION_RDF_LDPATH, LDPathTransform.class); + + } + + /** + * Get a new TransformationFactory using the provided mapping + * @param mimeToTransform + */ + public TransformationFactory(Map mimeToTransform) { + mimeToTransform = mimeToTransform; + } + + /** + * Get a Transformation from a MediaType and an InputStream with + * the transform program + * @param contentType + * @param inputStream + * @return + */ + public Transformation getTransform(final MediaType contentType, + final InputStream inputStream) { + + if (mimeToTransform.containsKey(contentType.toString())) { + Class transform = mimeToTransform.get(contentType.toString()); + + if (Transformation.class.isAssignableFrom(transform)) { + try { + return (Transformation)(transform.getConstructor(InputStream.class).newInstance(inputStream)); + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { + propagate(e); + } + } + + } + + return null; + + } +} diff --git a/fcrepo-transform/src/main/java/org/fcrepo/transform/http/FedoraTransform.java b/fcrepo-transform/src/main/java/org/fcrepo/transform/http/FedoraTransform.java new file mode 100644 index 0000000000..fd66c0a291 --- /dev/null +++ b/fcrepo-transform/src/main/java/org/fcrepo/transform/http/FedoraTransform.java @@ -0,0 +1,180 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.transform.http; + +import com.codahale.metrics.annotation.Timed; +import com.hp.hpl.jena.query.Dataset; +import org.apache.jena.riot.WebContent; +import org.apache.marmotta.ldpath.exception.LDPathParseException; +import org.fcrepo.AbstractResource; +import org.fcrepo.FedoraResource; +import org.fcrepo.transform.Transformation; +import org.fcrepo.transform.TransformationFactory; +import org.fcrepo.session.InjectedSession; +import org.fcrepo.transform.transformations.LDPathTransform; +import org.modeshape.jcr.api.JcrTools; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.PathSegment; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static org.apache.jena.riot.WebContent.contentTypeSPARQLQuery; +import static org.fcrepo.transform.transformations.LDPathTransform.APPLICATION_RDF_LDPATH; +import static org.fcrepo.transform.transformations.LDPathTransform.getNodeTypeTransform; +import static org.slf4j.LoggerFactory.getLogger; + +@Component +@Scope("prototype") +@Path("/{path: .*}/fcr:transform") +public class FedoraTransform extends AbstractResource { + + @InjectedSession + protected Session session; + + private final Logger logger = getLogger(FedoraTransform.class); + + @Autowired(required = false) + private TransformationFactory transformationFactory; + + /** + * Register the LDPath configuration tree in JCR + * @throws RepositoryException + * @throws java.io.IOException + */ + @PostConstruct + public void setUpRepositoryConfiguration() + throws RepositoryException, IOException { + + if (transformationFactory == null) { + transformationFactory = new TransformationFactory(); + } + + final Session session = sessions.getSession(); + final JcrTools jcrTools = new JcrTools(true); + + // register our CND + jcrTools.registerNodeTypes(session, "ldpath.cnd"); + + // create the configuration base path + jcrTools.findOrCreateNode(session, "/fedora:system/fedora:transform", "fedora:configuration", "fedora:node_type_configuration"); + final Node node = jcrTools.findOrCreateNode(session, LDPathTransform.CONFIGURATION_FOLDER + "default", NodeType.NT_FOLDER, NodeType.NT_FOLDER); + + // register an initial demo program + if (!node.hasNode(NodeType.NT_BASE)) { + final Node base_config = node.addNode(NodeType.NT_BASE, NodeType.NT_FILE); + jcrTools.uploadFile(session, base_config.getPath(), getClass().getResourceAsStream("/ldpath/default/nt_base_ldpath_program.txt")); + } + + session.save(); + session.logout(); + } + + /** + * Execute an LDpath program transform + * @param pathList + * @return Binary blob + * @throws RepositoryException + */ + @GET + @Path("{program}") + @Produces({MediaType.APPLICATION_JSON}) + @Timed + public Object evaluateLdpathProgram( + @PathParam("path") final List pathList, + @PathParam("program") final String program) + throws RepositoryException, LDPathParseException { + + try { + final String path = toPath(pathList); + final FedoraResource object = nodeService.getObject(session, path); + + final Transformation t = getNodeTypeTransform(object.getNode(), program); + + final Dataset propertiesDataset = object.getPropertiesDataset(); + + return t.apply(propertiesDataset); + + } finally { + session.logout(); + } + } + + /** + * Get the LDPath output as a JSON stream appropriate for e.g. Solr + * + * @param pathList + * @param requestBodyStream + * @return + * @throws RepositoryException + * @throws LDPathParseException + */ + @POST + @Consumes({APPLICATION_RDF_LDPATH, contentTypeSPARQLQuery}) + @Produces({MediaType.APPLICATION_JSON, + WebContent.contentTypeTextTSV, + WebContent.contentTypeTextCSV, + WebContent.contentTypeSSE, + WebContent.contentTypeTextPlain, + WebContent.contentTypeResultsJSON, + WebContent.contentTypeResultsXML, + WebContent.contentTypeResultsBIO, + WebContent.contentTypeTurtle, + WebContent.contentTypeN3, + WebContent.contentTypeNTriples, + WebContent.contentTypeRDFXML}) + @Timed + public Object evaluateTransform( + @PathParam("path") final List pathList, + @HeaderParam("Content-Type") MediaType contentType, + final InputStream requestBodyStream) + throws RepositoryException, LDPathParseException { + + try { + final String path = toPath(pathList); + final FedoraResource object = nodeService.getObject(session, path); + final Dataset propertiesDataset = object.getPropertiesDataset(); + + Transformation t = + transformationFactory.getTransform(contentType, requestBodyStream); + return t.apply(propertiesDataset); + } finally { + session.logout(); + } + + } + + +} diff --git a/fcrepo-transform/src/main/java/org/fcrepo/transform/transformations/LDPathTransform.java b/fcrepo-transform/src/main/java/org/fcrepo/transform/transformations/LDPathTransform.java new file mode 100644 index 0000000000..fe8f642629 --- /dev/null +++ b/fcrepo-transform/src/main/java/org/fcrepo/transform/transformations/LDPathTransform.java @@ -0,0 +1,178 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.transform.transformations; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.ResourceFactory; +import org.apache.marmotta.ldpath.LDPath; +import org.apache.marmotta.ldpath.backend.jena.GenericJenaBackend; +import org.apache.marmotta.ldpath.exception.LDPathParseException; +import org.fcrepo.transform.Transformation; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static org.fcrepo.rdf.SerializationUtils.getDatasetSubject; +import static org.fcrepo.rdf.SerializationUtils.unifyDatasetModel; + +/** + * Utilities for working with LDPath + */ +public class LDPathTransform implements Transformation { + + public static final String CONFIGURATION_FOLDER = "/fedora:system/fedora:transform/fedora:ldpath/"; + + + // TODO: this mime type was made up + public static final String APPLICATION_RDF_LDPATH = "application/rdf+ldpath"; + private final InputStream query; + + /** + * Construct a new Transform from the InputStream + * @param query + */ + public LDPathTransform(final InputStream query) { + this.query = query; + } + + /** + * Pull a node-type specific transform out of JCR + * @param node + * @param key + * @return + * @throws RepositoryException + */ + public static LDPathTransform getNodeTypeTransform(final Node node, + final String key) + throws RepositoryException { + + final Node programNode = node.getSession().getNode(LDPathTransform.CONFIGURATION_FOLDER + key); + + final NodeType primaryNodeType = node.getPrimaryNodeType(); + + final NodeType[] supertypes = primaryNodeType.getSupertypes(); + + final Iterable nodeTypes = + Iterables.concat(ImmutableList.of(primaryNodeType), + ImmutableList.copyOf(supertypes)); + + for (NodeType nodeType : nodeTypes) { + if (programNode.hasNode(nodeType.toString())) { + return new LDPathTransform(programNode.getNode(nodeType.toString()) + .getNode("jcr:content") + .getProperty("jcr:data") + .getBinary().getStream()); + } + } + + return null; + } + + @Override + public List>> apply(final Dataset dataset) { + try { + final LDPath ldpathForResource = getLdpathResource(dataset); + + final Resource context = + ResourceFactory.createResource(getDatasetSubject(dataset).getURI()); + + final Map> wildcardCollection = + ldpathForResource.programQuery(context, new InputStreamReader(query)); + + return ImmutableList.of(transformLdpathOutputToSomethingSerializable(wildcardCollection)); + } catch (LDPathParseException | RepositoryException e) { + throw new RuntimeException(e); + } + } + + + @Override + public InputStream getQuery() { + return query; + } + + @Override + public boolean equals(Object other) { + return other instanceof LDPathTransform && + query.equals(((LDPathTransform)other).getQuery()); + } + + /** + * Get the LDPath resource for an object + * @param dataset + * @return + * @throws RepositoryException + */ + private LDPath getLdpathResource(final Dataset dataset) throws RepositoryException { + + final Model model = unifyDatasetModel(dataset); + + final GenericJenaBackend genericJenaBackend = new GenericJenaBackend(model); + + LDPath ldpath = new LDPath(genericJenaBackend); + + return ldpath; + } + + /** + * In order for the JAX-RS serialization magic to work, we have to turn the map into + * a non-wildcard type. + * @param collectionMap + * @return + */ + private Map> + transformLdpathOutputToSomethingSerializable(Map> collectionMap) { + + return Maps.transformValues(collectionMap, + WILDCARD_COLLECTION_TO_STRING_COLLECTION); + } + + private static final Function,Collection> + WILDCARD_COLLECTION_TO_STRING_COLLECTION = + new Function, Collection>() { + + @Override + public Collection apply(Collection input) { + return Collections2.transform(input, + ANYTHING_TO_STRING_FUNCTION); + } + }; + + + private static final Function ANYTHING_TO_STRING_FUNCTION = + new Function() { + @Override + public Object apply(Object input) { + return input; + } + }; + +} diff --git a/fcrepo-transform/src/main/java/org/fcrepo/transform/transformations/SparqlQueryTransform.java b/fcrepo-transform/src/main/java/org/fcrepo/transform/transformations/SparqlQueryTransform.java new file mode 100644 index 0000000000..59201239ce --- /dev/null +++ b/fcrepo-transform/src/main/java/org/fcrepo/transform/transformations/SparqlQueryTransform.java @@ -0,0 +1,74 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.transform.transformations; + +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.QueryFactory; +import com.hp.hpl.jena.rdf.model.Model; +import org.apache.commons.io.IOUtils; +import org.fcrepo.transform.Transformation; + +import java.io.IOException; +import java.io.InputStream; + +import static org.fcrepo.rdf.SerializationUtils.unifyDatasetModel; + +/** + * SPARQL Query-based transforms + */ +public class SparqlQueryTransform implements Transformation { + + private final InputStream query; + + /** + * Construct a new SparqlQueryTransform from the data from + * the InputStream + * @param query + */ + public SparqlQueryTransform(final InputStream query) { + this.query = query; + } + + @Override + public QueryExecution apply(final Dataset dataset) { + + try { + final Model model = unifyDatasetModel(dataset); + final Query sparqlQuery = + QueryFactory.create(IOUtils.toString(query)); + + return QueryExecutionFactory.create(sparqlQuery, model); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @Override + public InputStream getQuery() { + return query; + } + + @Override + public boolean equals(Object other) { + return other instanceof SparqlQueryTransform && + query.equals(((SparqlQueryTransform)other).getQuery()); + } + +} diff --git a/fcrepo-transform/src/main/resources/ldpath.cnd b/fcrepo-transform/src/main/resources/ldpath.cnd new file mode 100644 index 0000000000..ded7712506 --- /dev/null +++ b/fcrepo-transform/src/main/resources/ldpath.cnd @@ -0,0 +1,11 @@ +/* + * Generic Fedora namespace + */ + + +/* + * Any Fedora resource. + */ +[fedora:configuration] > nt:folder + +[fedora:node_type_configuration] > nt:folder \ No newline at end of file diff --git a/fcrepo-transform/src/main/resources/ldpath/default/nt_base_ldpath_program.txt b/fcrepo-transform/src/main/resources/ldpath/default/nt_base_ldpath_program.txt new file mode 100644 index 0000000000..f8f7f73abb --- /dev/null +++ b/fcrepo-transform/src/main/resources/ldpath/default/nt_base_ldpath_program.txt @@ -0,0 +1,4 @@ +@prefix fedora-internal : +id = . :: xsd:string ; +title = dc:title :: xsd:string; +uuid = fedora-internal:uuid :: xsd:string ; \ No newline at end of file diff --git a/fcrepo-transform/src/test/java/org/fcrepo/integration/AbstractResourceIT.java b/fcrepo-transform/src/test/java/org/fcrepo/integration/AbstractResourceIT.java new file mode 100644 index 0000000000..1fda873fee --- /dev/null +++ b/fcrepo-transform/src/test/java/org/fcrepo/integration/AbstractResourceIT.java @@ -0,0 +1,71 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.fcrepo.integration; + +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.PoolingClientConnectionManager; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static java.lang.Integer.parseInt; +import static java.lang.System.getProperty; + +@RunWith(SpringJUnit4ClassRunner.class) +public abstract class AbstractResourceIT { + + protected Logger logger; + + @Before + public void setLogger() { + logger = LoggerFactory.getLogger(this.getClass()); + } + + protected static final int SERVER_PORT = parseInt(getProperty("test.port", + "8080")); + + protected static final String HOSTNAME = "localhost"; + + protected static final String serverAddress = "http://" + HOSTNAME + ":" + + SERVER_PORT; + + protected final PoolingClientConnectionManager connectionManager = + new PoolingClientConnectionManager(); + + protected static HttpClient client; + + public AbstractResourceIT() { + connectionManager.setMaxTotal(Integer.MAX_VALUE); + connectionManager.setDefaultMaxPerRoute(5); + connectionManager.closeIdleConnections(3, TimeUnit.SECONDS); + client = new DefaultHttpClient(connectionManager); + } + + protected int getStatus(HttpUriRequest method) + throws ClientProtocolException, IOException { + logger.debug("Executing: " + method.getMethod() + " to " + + method.getURI()); + return client.execute(method).getStatusLine().getStatusCode(); + } +} diff --git a/fcrepo-transform/src/test/java/org/fcrepo/integration/FedoraTransformIT.java b/fcrepo-transform/src/test/java/org/fcrepo/integration/FedoraTransformIT.java new file mode 100644 index 0000000000..e0fa545c62 --- /dev/null +++ b/fcrepo-transform/src/test/java/org/fcrepo/integration/FedoraTransformIT.java @@ -0,0 +1,108 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.fcrepo.integration; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.http.util.EntityUtils; +import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.map.ObjectMapper; +import org.fcrepo.services.ObjectService; +import org.fcrepo.transform.transformations.LDPathTransform; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +import javax.jcr.Repository; +import javax.jcr.Session; + +import java.io.ByteArrayInputStream; + +import static org.junit.Assert.assertEquals; + + +@ContextConfiguration({"/spring-test/master.xml", "/spring-test/test-container.xml"}) +public class FedoraTransformIT extends AbstractResourceIT { + + + @Autowired + Repository repo; + + @Autowired + ObjectService objectService; + + @Test + public void testLdpathWithConfiguredProgram() throws Exception { + + final Session session = repo.login(); + objectService.createObject(session, "/ldpathConfigTestObject"); + session.save(); + session.logout(); + + HttpGet postLdpathProgramRequest = new HttpGet(serverAddress + "/ldpathConfigTestObject/fcr:transform/default"); + HttpResponse response = client.execute(postLdpathProgramRequest); + assertEquals(200, response.getStatusLine().getStatusCode()); + String content = EntityUtils.toString(response.getEntity()); + logger.debug("Retrieved ldpath feed:\n" + content); + + JsonFactory jsonFactory = new JsonFactory(); + + ObjectMapper mapper = new ObjectMapper(); + final JsonParser jsonParser = jsonFactory.createJsonParser(content); + + JsonNode rootNode = mapper.readTree(jsonParser); + + assertEquals("info:fedora/ldpathConfigTestObject", rootNode.get(0).get("id").getElements().next().asText()); + + } + + @Test + public void testLdpathWithProgramBody() throws Exception { + + final Session session = repo.login(); + objectService.createObject(session, "/ldpathTestObject"); + session.save(); + session.logout(); + + HttpPost postLdpathProgramRequest = new HttpPost(serverAddress + "/ldpathTestObject/fcr:transform"); + BasicHttpEntity e = new BasicHttpEntity(); + + String s = "id = . :: xsd:string ;\n"; + + e.setContent(new ByteArrayInputStream(s.getBytes())); + + postLdpathProgramRequest.setEntity(e); + postLdpathProgramRequest.setHeader("Content-Type", LDPathTransform.APPLICATION_RDF_LDPATH); + HttpResponse response = client.execute(postLdpathProgramRequest); + assertEquals(200, response.getStatusLine().getStatusCode()); + String content = EntityUtils.toString(response.getEntity()); + logger.debug("Retrieved ldpath feed:\n" + content); + + JsonFactory jsonFactory = new JsonFactory(); + + ObjectMapper mapper = new ObjectMapper(); + final JsonParser jsonParser = jsonFactory.createJsonParser(content); + + JsonNode rootNode = mapper.readTree(jsonParser); + + assertEquals("info:fedora/ldpathTestObject", rootNode.get(0).get("id").getElements().next().asText()); + + } +} diff --git a/fcrepo-transform/src/test/java/org/fcrepo/integration/LDPathServiceIT.java b/fcrepo-transform/src/test/java/org/fcrepo/integration/LDPathServiceIT.java new file mode 100644 index 0000000000..c1c275e007 --- /dev/null +++ b/fcrepo-transform/src/test/java/org/fcrepo/integration/LDPathServiceIT.java @@ -0,0 +1,89 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.integration; + +import org.apache.marmotta.ldpath.exception.LDPathParseException; +import org.fcrepo.FedoraObject; +import org.fcrepo.transform.transformations.LDPathTransform; +import org.fcrepo.services.ObjectService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.inject.Inject; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/spring-test/master.xml"}) +public class LDPathServiceIT { + + @Inject + Repository repo; + + @Inject + ObjectService objectService; + private LDPathTransform testObj; + + @Before + public void setUp() { + + } + + @Test + public void shouldDoStuff() throws RepositoryException, LDPathParseException { + Session session = repo.login(); + + final FedoraObject object = objectService.createObject(session, "/testObject"); + object.getNode().setProperty("dc:title", "some-title"); + + String s = "@prefix dc : \n" + + "@prefix fedora-internal : \n" + + "id = . :: xsd:string ;\n" + + "title = dc:title :: xsd:string ;\n" + + "uuid = fedora-internal:uuid :: xsd:string ;"; + final InputStream stringReader = new ByteArrayInputStream(s.getBytes()); + + testObj = new LDPathTransform(stringReader); + + final List>> list = testObj.apply(object.getPropertiesDataset()); + + assert(list != null); + assertEquals(1, list.size()); + Map> stuff = list.get(0); + + assertTrue(stuff.containsKey("id")); + assertTrue(stuff.containsKey("title")); + + assertEquals(1, stuff.get("id").size()); + assertEquals("info:fedora/testObject", stuff.get("id").iterator().next()); + assertEquals("some-title", stuff.get("title").iterator().next()); + assertEquals(object.getNode().getIdentifier(), stuff.get("uuid").iterator().next()); + } +} diff --git a/fcrepo-transform/src/test/java/org/fcrepo/integration/SparqlQueryTransformIT.java b/fcrepo-transform/src/test/java/org/fcrepo/integration/SparqlQueryTransformIT.java new file mode 100644 index 0000000000..f44e0d5465 --- /dev/null +++ b/fcrepo-transform/src/test/java/org/fcrepo/integration/SparqlQueryTransformIT.java @@ -0,0 +1,82 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.integration; + +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.ResultSet; +import org.apache.marmotta.ldpath.exception.LDPathParseException; +import org.fcrepo.FedoraObject; +import org.fcrepo.services.ObjectService; +import org.fcrepo.transform.transformations.SparqlQueryTransform; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.inject.Inject; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.junit.Assert.assertTrue; + + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/spring-test/master.xml"}) +public class SparqlQueryTransformIT { + + @Inject + Repository repo; + + @Inject + ObjectService objectService; + private SparqlQueryTransform testObj; + + @Before + public void setUp() { + + } + + @Test + public void shouldDoStuff() throws RepositoryException, LDPathParseException { + Session session = repo.login(); + + final FedoraObject object = objectService.createObject(session, "/testObject"); + + String s = "SELECT ?x ?uuid\n" + + "WHERE { ?x ?uuid }"; + final InputStream stringReader = new ByteArrayInputStream(s.getBytes()); + + testObj = new SparqlQueryTransform(stringReader); + + final QueryExecution qexec = testObj.apply(object.getPropertiesDataset()); + + assert(qexec != null); + + try { + final ResultSet results = qexec.execSelect(); + + assert(results != null); + assertTrue(results.hasNext()); + } finally { + qexec.close(); + } + } +} diff --git a/fcrepo-transform/src/test/java/org/fcrepo/transform/TransformationFactoryTest.java b/fcrepo-transform/src/test/java/org/fcrepo/transform/TransformationFactoryTest.java new file mode 100644 index 0000000000..2748bb7e3c --- /dev/null +++ b/fcrepo-transform/src/test/java/org/fcrepo/transform/TransformationFactoryTest.java @@ -0,0 +1,72 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.fcrepo.transform; + +import org.apache.jena.riot.WebContent; +import org.fcrepo.transform.transformations.LDPathTransform; +import org.fcrepo.transform.transformations.SparqlQueryTransform; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import javax.ws.rs.core.MediaType; +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.MockitoAnnotations.initMocks; + +public class TransformationFactoryTest { + + @Mock + InputStream mockInputStream; + + TransformationFactory transformationFactory; + + @Before + public void setUp() { + initMocks(this); + transformationFactory = new TransformationFactory(); + } + + @Test + public void testLDPathCreation() { + + final Transformation transform = transformationFactory.getTransform(MediaType.valueOf(LDPathTransform.APPLICATION_RDF_LDPATH), mockInputStream); + + assertEquals(new LDPathTransform(mockInputStream), transform); + + } + + @Test + public void testSparqlCreation() { + + final Transformation transform = transformationFactory.getTransform(MediaType.valueOf(WebContent.contentTypeSPARQLQuery), mockInputStream); + + assertEquals(new SparqlQueryTransform(mockInputStream), transform); + + } + + + @Test + public void testOtherCreation() { + + final Transformation transform = transformationFactory.getTransform(MediaType.valueOf("some/mime-type"), mockInputStream); + + assertNull(transform); + + } +} diff --git a/fcrepo-transform/src/test/java/org/fcrepo/transform/http/FedoraTransformTest.java b/fcrepo-transform/src/test/java/org/fcrepo/transform/http/FedoraTransformTest.java new file mode 100644 index 0000000000..bd81eecb98 --- /dev/null +++ b/fcrepo-transform/src/test/java/org/fcrepo/transform/http/FedoraTransformTest.java @@ -0,0 +1,110 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.transform.http; + +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.query.DatasetFactory; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import org.apache.jena.riot.WebContent; +import org.fcrepo.FedoraResource; +import org.fcrepo.services.NodeService; +import org.fcrepo.test.util.TestHelpers; +import org.fcrepo.transform.Transformation; +import org.fcrepo.transform.TransformationFactory; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.fcrepo.test.util.PathSegmentImpl.createPathList; +import static org.fcrepo.test.util.TestHelpers.getUriInfoImpl; +import static org.fcrepo.test.util.TestHelpers.mockSession; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +public class FedoraTransformTest { + + @Mock + NodeService mockNodeService; + + @Mock + FedoraResource mockResource; + + @Mock + Node mockNode; + + + FedoraTransform testObj; + + private Session mockSession; + private UriInfo uriInfo; + + @Mock + private TransformationFactory mockTransformationFactory; + + @Mock + Transformation mockTransform; + + @Before + public void setUp() throws NoSuchFieldException, RepositoryException { + initMocks(this); + testObj = new FedoraTransform(); + TestHelpers.setField(testObj, "nodeService", mockNodeService); + TestHelpers.setField(testObj, "transformationFactory", mockTransformationFactory); + + this.uriInfo = getUriInfoImpl(); + TestHelpers.setField(testObj, "uriInfo", uriInfo); + mockSession = mockSession(testObj); + TestHelpers.setField(testObj, "session", mockSession); + + when(mockResource.getNode()).thenReturn(mockNode); + } + + @Test + public void testEvaluateTransform() throws Exception { + when(mockNodeService.getObject(mockSession, "/testObject")).thenReturn(mockResource); + final Model model = ModelFactory.createDefaultModel(); + model.add(model.createResource("http://example.org/book/book1"), + model.createProperty("http://purl.org/dc/elements/1.1/title"), + model.createLiteral("some-title")); + final Dataset dataset = DatasetFactory.create(model); + when(mockResource.getPropertiesDataset()).thenReturn(dataset); + + InputStream query = new ByteArrayInputStream(("SELECT ?title WHERE\n" + + "{\n" + + " ?title .\n" + + "} ").getBytes()); + + when(mockTransformationFactory.getTransform(MediaType.valueOf(WebContent.contentTypeSPARQLQuery), query)).thenReturn(mockTransform); + + testObj.evaluateTransform(createPathList("testObject"), MediaType.valueOf(WebContent.contentTypeSPARQLQuery), query); + + verify(mockTransform).apply(dataset); + } + + +} diff --git a/fcrepo-transform/src/test/java/org/fcrepo/transform/transformations/LDPathTransformTest.java b/fcrepo-transform/src/test/java/org/fcrepo/transform/transformations/LDPathTransformTest.java new file mode 100644 index 0000000000..c6f0bb6511 --- /dev/null +++ b/fcrepo-transform/src/test/java/org/fcrepo/transform/transformations/LDPathTransformTest.java @@ -0,0 +1,142 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.fcrepo.transform.transformations; + +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.query.DatasetFactory; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.sparql.util.Symbol; +import org.apache.marmotta.ldpath.exception.LDPathParseException; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +public class LDPathTransformTest { + + @Mock + Node mockNode; + + @Mock + Session mockSession; + + LDPathTransform testObj; + + @Before + public void setUp() throws RepositoryException { + initMocks(this); + + when(mockNode.getSession()).thenReturn(mockSession); + } + + @Test + public void testGetNodeTypeSpecificLdpathProgramForMissingProgram() throws RepositoryException { + final Node mockConfigNode = mock(Node.class); + when(mockSession.getNode(LDPathTransform.CONFIGURATION_FOLDER + "some-program")).thenReturn(mockConfigNode); + + final NodeType mockNodeType = mock(NodeType.class); + final NodeType mockNtBase = mock(NodeType.class); + when(mockNodeType.getSupertypes()).thenReturn(new NodeType[] { mockNtBase }); + when(mockNode.getPrimaryNodeType()).thenReturn(mockNodeType); + final LDPathTransform nodeTypeSpecificLdpathProgramStream = LDPathTransform.getNodeTypeTransform(mockNode, "some-program"); + + assertNull(nodeTypeSpecificLdpathProgramStream); + } + + @Test + public void testGetNodeTypeSpecificLdpathProgramForNodeTypeProgram() throws RepositoryException { + final Node mockConfigNode = mock(Node.class); + final Node mockTypeConfigNode = mock(Node.class, Mockito.RETURNS_DEEP_STUBS); + when(mockSession.getNode(LDPathTransform.CONFIGURATION_FOLDER + "some-program")).thenReturn(mockConfigNode); + + final NodeType mockNodeType = mock(NodeType.class); + final NodeType mockNtBase = mock(NodeType.class); + when(mockNodeType.getSupertypes()).thenReturn(new NodeType[] { mockNtBase }); + when(mockNode.getPrimaryNodeType()).thenReturn(mockNodeType); + when(mockNodeType.toString()).thenReturn("custom:type"); + when(mockConfigNode.hasNode("custom:type")).thenReturn(true); + when(mockConfigNode.getNode("custom:type")).thenReturn(mockTypeConfigNode); + final InputStream mockInputStream = mock(InputStream.class); + when(mockTypeConfigNode.getNode("jcr:content").getProperty("jcr:data").getBinary().getStream()).thenReturn(mockInputStream); + final LDPathTransform nodeTypeSpecificLdpathProgramStream = LDPathTransform.getNodeTypeTransform(mockNode, "some-program"); + + assertEquals(new LDPathTransform(mockInputStream), nodeTypeSpecificLdpathProgramStream); + } + + @Test + public void testGetNodeTypeSpecificLdpathProgramForSupertypeProgram() throws RepositoryException { + final Node mockConfigNode = mock(Node.class); + final Node mockTypeConfigNode = mock(Node.class, Mockito.RETURNS_DEEP_STUBS); + when(mockSession.getNode(LDPathTransform.CONFIGURATION_FOLDER + "some-program")).thenReturn(mockConfigNode); + + final NodeType mockNodeType = mock(NodeType.class); + final NodeType mockNtBase = mock(NodeType.class); + when(mockNodeType.getSupertypes()).thenReturn(new NodeType[] { mockNtBase }); + when(mockNodeType.toString()).thenReturn("custom:type"); + when(mockNtBase.toString()).thenReturn("nt:base"); + when(mockNode.getPrimaryNodeType()).thenReturn(mockNodeType); + + when(mockConfigNode.hasNode("custom:type")).thenReturn(false); + + when(mockConfigNode.hasNode("nt:base")).thenReturn(true); + when(mockConfigNode.getNode("nt:base")).thenReturn(mockTypeConfigNode); + + final InputStream mockInputStream = mock(InputStream.class); + when(mockTypeConfigNode.getNode("jcr:content").getProperty("jcr:data").getBinary().getStream()).thenReturn(mockInputStream); + + final LDPathTransform nodeTypeSpecificLdpathProgramStream = LDPathTransform.getNodeTypeTransform(mockNode, "some-program"); + + assertEquals(new LDPathTransform(mockInputStream), nodeTypeSpecificLdpathProgramStream); + } + + @Test + public void testProgramQuery() throws LDPathParseException, RepositoryException { + + final Model model = ModelFactory.createDefaultModel(); + model.add(model.createResource("abc"), model.createProperty("http://purl.org/dc/elements/1.1/title"), model.createLiteral("some-title")); + final Dataset testDataset = DatasetFactory.create(model); + testDataset.getContext().set(Symbol.create("uri"), "abc"); + final InputStream testReader = new ByteArrayInputStream("title = dc:title :: xsd:string ;".getBytes()); + + testObj = new LDPathTransform(testReader); + final List>> stringCollectionMap = testObj.apply(testDataset); + + assert(stringCollectionMap != null); + assertEquals(1, stringCollectionMap.size()); + assertEquals(1, stringCollectionMap.get(0).get("title").size()); + assertTrue(stringCollectionMap.get(0).get("title").contains("some-title")); + } +} diff --git a/fcrepo-transform/src/test/java/org/fcrepo/transform/transformations/SparqlQueryTransformTest.java b/fcrepo-transform/src/test/java/org/fcrepo/transform/transformations/SparqlQueryTransformTest.java new file mode 100644 index 0000000000..3150d2e874 --- /dev/null +++ b/fcrepo-transform/src/test/java/org/fcrepo/transform/transformations/SparqlQueryTransformTest.java @@ -0,0 +1,81 @@ +/** + * Copyright 2013 DuraSpace, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.fcrepo.transform.transformations; + +import com.hp.hpl.jena.query.DatasetFactory; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.ResultSet; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +public class SparqlQueryTransformTest { + + @Mock + Node mockNode; + + @Mock + Session mockSession; + + SparqlQueryTransform testObj; + + @Before + public void setUp() throws RepositoryException { + initMocks(this); + + when(mockNode.getSession()).thenReturn(mockSession); + } + + @Test + public void testApply() { + Model model = ModelFactory.createDefaultModel(); + model.add(model.createResource("http://example.org/book/book1"), + model.createProperty("http://purl.org/dc/elements/1.1/title"), + model.createLiteral("some-title")); + InputStream query = new ByteArrayInputStream(("SELECT ?title WHERE\n" + + "{\n" + + " ?title .\n" + + "} ").getBytes()); + testObj = new SparqlQueryTransform(query); + final QueryExecution apply = testObj.apply(DatasetFactory.create(model)); + + assert(apply != null); + + try { + + final ResultSet resultSet = apply.execSelect(); + assertTrue(resultSet.hasNext()); + assertEquals("some-title", resultSet.nextSolution().get("title").asLiteral().getValue()); + } finally { + apply.close(); + } + + } +} diff --git a/fcrepo-transform/src/test/resources/logback-test.xml b/fcrepo-transform/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..e0a232ebb1 --- /dev/null +++ b/fcrepo-transform/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + + + %p %d{HH:mm:ss.SSS} \(%c{0}\) %m%n + + + + + + + + + + + + + + + + diff --git a/fcrepo-transform/src/test/resources/spring-test/master.xml b/fcrepo-transform/src/test/resources/spring-test/master.xml new file mode 100644 index 0000000000..9f366c401e --- /dev/null +++ b/fcrepo-transform/src/test/resources/spring-test/master.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/fcrepo-transform/src/test/resources/spring-test/repo.xml b/fcrepo-transform/src/test/resources/spring-test/repo.xml new file mode 100644 index 0000000000..7d69361974 --- /dev/null +++ b/fcrepo-transform/src/test/resources/spring-test/repo.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/fcrepo-transform/src/test/resources/spring-test/rest.xml b/fcrepo-transform/src/test/resources/spring-test/rest.xml new file mode 100644 index 0000000000..0225af2dc3 --- /dev/null +++ b/fcrepo-transform/src/test/resources/spring-test/rest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/fcrepo-transform/src/test/resources/spring-test/test-container.xml b/fcrepo-transform/src/test/resources/spring-test/test-container.xml new file mode 100644 index 0000000000..a9c9fae6e3 --- /dev/null +++ b/fcrepo-transform/src/test/resources/spring-test/test-container.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fcrepo-transform/src/test/resources/web.xml b/fcrepo-transform/src/test/resources/web.xml new file mode 100644 index 0000000000..00f952dc0c --- /dev/null +++ b/fcrepo-transform/src/test/resources/web.xml @@ -0,0 +1,40 @@ + + + + + Fedora-on-ModeShape + + + contextConfigLocation + classpath:spring-test/master.xml + + + + org.springframework.web.context.ContextLoaderListener + + + + jersey-servlet + com.sun.jersey.spi.spring.container.servlet.SpringServlet + + com.sun.jersey.config.property.packages + org.fcrepo.services, org.fcrepo.session, + org.fcrepo.transform.http,org.fcrepo.exceptionhandlers + + + + com.sun.jersey.api.json.POJOMappingFeature + true + + 1 + + + + jersey-servlet + /* + + + diff --git a/fcrepo-webapp/pom.xml b/fcrepo-webapp/pom.xml index 67dd150fef..b3281f3ea8 100644 --- a/fcrepo-webapp/pom.xml +++ b/fcrepo-webapp/pom.xml @@ -56,6 +56,11 @@ fcrepo-http-api ${project.version} + + org.fcrepo + fcrepo-transform + ${project.version} + org.fcrepo fcrepo-auth-oauth diff --git a/pom.xml b/pom.xml index e55b781ce3..d0e0a8595c 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,7 @@ fcrepo-jcr fcrepo-serialization fcrepo-auth-oauth + fcrepo-transform @@ -1007,6 +1008,18 @@ false + + + apache-snapshot + Apache Snapshot Repository + http://repository.apache.org/snapshots/ + + false + + + true + +