Commit dcd8b57e authored by bseeger's avatar bseeger
Browse files

Adds fits-rest OSGi bundle

parent a0d6fcff
Repository FITS Service
===================================
This service will return the FITS information associated with Fedora NonRdfResource, in
XML format. The service can be used with any camel route in an OSGi container.
Building
--------
To build this project use
mvn install
Deploying in OSGi
-----------------
Each of these projects can be deployed in an OSGi container. For example using
[Apache Karaf](http://karaf.apache.org) version 4.x or better, you can run the following
command from its shell:
feature:repo-add mvn:edu.amherst.acdc/acrepo-karaf/LATEST/xml/features
feature:install acrepo-fits-service
Or by copying any of the compiled bundles into `$KARAF_HOME/deploy`.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.
-->
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>repository-services</artifactId>
<groupId>edu.amherst.acdc</groupId>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>acrepo-fits-rest</artifactId>
<packaging>bundle</packaging>
<name>FITS service bundle</name>
<description>Repository FITS service, which will retrieve FITS information about a NonRdfResource located in a Fedora Repository.</description>
<properties>
<osgi.export.packages>edu.amherst.acdc.fits.rest;version=${project.version}</osgi.export.packages>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jetty9</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-blueprint</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>${httpcore.version}</version>
</dependency>
<!-- logging and testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-blueprint</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
</dependencies>
<build>
<defaultGoal>install</defaultGoal>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.camel</groupId>
<artifactId>camel-maven-plugin</artifactId>
</plugin>
<!-- add configuration file to artifact set for OSGi deployment -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>attach-artifact</goal>
</goals>
<configuration>
<artifacts>
<artifact>
<file>src/main/cfg/edu.amherst.acdc.fits.rest.cfg</file>
<type>cfg</type>
<classifier>configuration</classifier>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
<!-- to generate the MANIFEST-FILE of the bundle -->
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
# In the event of failure, the maximum number of times a redelivery will be attempted.
error.maxRedeliveries=10
# Fedora Repository location
fcrepo.baseUrl=localhost:8080/fcrepo/rest
fcrepo.authHost=
fcrepo.authUsername=
fcrepo.authPassword=
rest.prefix=/fits
rest.port=9080
rest.host=localhost
# The endpoint for the FITS server
fits.endpoint=localhost:9191/fits
/*
* Copyright 2016 Amherst College
*
* 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 edu.amherst.acdc.fits.rest;
import static org.apache.camel.Exchange.CONTENT_TYPE;
import static org.apache.camel.Exchange.HTTP_METHOD;
import static org.apache.camel.Exchange.HTTP_RESPONSE_CODE;
import static org.apache.camel.Exchange.HTTP_URI;
import static org.apache.camel.Exchange.HTTP_PATH;
import static org.apache.camel.LoggingLevel.INFO;
import static org.apache.http.entity.mime.MultipartEntityBuilder.create;
import java.io.InputStream;
import org.apache.camel.builder.RouteBuilder;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A content router for handling JMS events.
*
* @author Bethany Seeger
*/
public class FitsRouter extends RouteBuilder {
private static final String FEDORA_PATH = "CamelFedoraPath";
private static final Logger LOGGER = LoggerFactory.getLogger(FitsRouter.class);
/**
* Configure the message route workflow.
*/
public void configure() throws Exception {
/**
* A generic error handler (specific to this RouteBuilder)
*/
onException(Exception.class)
.maximumRedeliveries("{{error.maxRedeliveries}}")
.log("Fits Routing Error: ${routeId}");
from("jetty:http://{{rest.host}}:{{rest.port}}{{rest.prefix}}?" +
"matchOnUriPrefix=true&httpMethodRestrict=GET,OPTIONS&sendServerVersion=false")
.routeId("AcrepoFitsRest")
.routeDescription(
"FITS service to gather technical information about a binary located in a Fedora repository.")
.log(INFO, LOGGER, "Received request for Fits data for: ${headers[CamelHttpPath]}")
.setHeader(FEDORA_PATH).header(HTTP_PATH)
.choice()
.when(header(HTTP_METHOD).isEqualTo("GET"))
.to("direct:fitsService")
.when(header(HTTP_METHOD).isEqualTo("OPTIONS"))
.setHeader(CONTENT_TYPE).constant("text/turtle")
.setHeader("Allow").constant("GET,OPTIONS")
.to("language:simple:resource:classpath:options.ttl");
from("direct:fitsService")
.routeId("AcrepoFitsFedoraLookup")
.log(INFO, LOGGER, "FitsFedoraLookup - fetching ${headers[CamelHttpPath]}")
.setHeader(HTTP_METHOD).constant("HEAD")
.setHeader(HTTP_URI).simple("http://{{fcrepo.baseUrl}}")
.to("http4://{{fcrepo.baseUrl}}?authUsername={{fcrepo.authUsername}}" +
"&authPassword={{fcrepo.authPassword}}&throwExceptionOnFailure=false")
.choice()
.when(header("Link").contains("<http://www.w3.org/ns/ldp#NonRDFSource>;rel=\"type\""))
.to("direct:fits")
.when(header(HTTP_RESPONSE_CODE).isEqualTo(200))
.removeHeaders("*")
.log(INFO, LOGGER, "Object is not a binary resource, sending 4xx")
.setBody(constant("Error: this resource is not a fedora:Binary"))
.setHeader(CONTENT_TYPE).constant("text/plain")
.setHeader(HTTP_RESPONSE_CODE).constant(400);
from("direct:fits")
.routeId("AcrepoFitsEndpoint")
.log(INFO, LOGGER, "Object is invoking Fits Service")
.removeHeaders("CamelHttp*")
.setHeader(HTTP_METHOD).constant("GET")
.setHeader(HTTP_PATH).header(FEDORA_PATH)
.to("http4://{{fcrepo.baseUrl}}?authUsername={{fcrepo.authUsername}}" +
"&authPassword={{fcrepo.authPassword}}&throwExceptionOnFailure=false")
.removeHeaders("CamelHttp*")
.process(exchange -> {
exchange.getOut().setBody(
create().addPart("datafile",
new InputStreamBody(exchange.getIn().getBody(InputStream.class), "UTF-8")).build());
})
.setHeader(CONTENT_TYPE).constant("multipart/form-data")
.setHeader(HTTP_METHOD).constant("POST")
.to("http4://{{fits.endpoint}}");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd" >
<!-- OSGi blueprint property placeholder -->
<cm:property-placeholder id="properties" persistent-id="edu.amherst.acdc.fits.rest" update-strategy="reload">
<cm:default-properties>
<cm:property name="error.maxRedeliveries" value="10"/>
<cm:property name="fcrepo.baseUrl" value="localhost:8080/fcrepo/rest"/>
<cm:property name="fcrepo.authHost" value=""/>
<cm:property name="fcrepo.authUsername" value=""/>
<cm:property name="fcrepo.authPassword" value=""/>
<cm:property name="rest.prefix" value="/fits"/>
<cm:property name="rest.host" value="localhost"/>
<cm:property name="rest.port" value="9080"/>
<cm:property name="fits.endpoint" value="localhost:9191/fits/examine"/>
</cm:default-properties>
</cm:property-placeholder>
<camelContext streamCache="true" id="FitsService" xmlns="http://camel.apache.org/schema/blueprint">
<package>edu.amherst.acdc.fits.rest</package>
</camelContext>
</blueprint>
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
[ owl:equivalentClass fedora:Binary ] .
?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%p %d{HH:mm:ss.SSS} \(%c{0}\) %m%n</pattern>
</encoder>
</appender>
<logger name="edu.amherst.acdc.fits.rest" additivity="false" level="INFO">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.apache.camel" additivity="false" level="INFO">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.fcrepo.camel" additivity="false" level="INFO">
<appender-ref ref="STDOUT"/>
</logger>
<root additivity="false" level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
......@@ -68,7 +68,6 @@
<scope>test</scope>
</dependency>
<!-- Testing & Camel Plugin -->
<dependency>
<groupId>org.glassfish.hk2.external</groupId>
......@@ -182,6 +181,7 @@
<portName>karaf.template.port</portName>
<portName>karaf.metadata.port</portName>
<portName>karaf.image.port</portName>
<portName>karaf.fits.port</portName>
<portName>karaf.idiomatic.port</portName>
<portName>karaf.rmiRegistry.port</portName>
<portName>karaf.rmiServer.port</portName>
......@@ -217,6 +217,7 @@
<karaf.idiomatic.port>${karaf.idiomatic.port}</karaf.idiomatic.port>
<karaf.metadata.port>${karaf.metadata.port}</karaf.metadata.port>
<karaf.image.port>${karaf.image.port}</karaf.image.port>
<karaf.fits.port>${karaf.fits.port}</karaf.fits.port>
<karaf.ssh.port>${karaf.ssh.port}</karaf.ssh.port>
<karaf.rmiRegistry.port>${karaf.rmiRegistry.port}</karaf.rmiRegistry.port>
<karaf.rmiServer.port>${karaf.rmiServer.port}</karaf.rmiServer.port>
......
......@@ -25,6 +25,7 @@ import static org.osgi.framework.FrameworkUtil.createFilter;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
......@@ -33,9 +34,11 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.karaf.features.FeaturesService;
import org.ops4j.pax.exam.Option;
import org.osgi.framework.BundleContext;
......@@ -60,9 +63,17 @@ public abstract class AbstractOSGiIT {
public abstract Option[] config();
protected String post(final String url) {
return post(url, null, null);
}
protected String post(final String url, final InputStream stream, final String contentType) {
final CloseableHttpClient httpclient = createDefault();
try {
final HttpPost req = new HttpPost(url);
if (stream != null) {
req.setHeader("Content-Type", contentType);
req.setEntity(new InputStreamEntity(stream));
}
final HttpResponse response = httpclient.execute(req);
assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());
return EntityUtils.toString(response.getEntity(), "UTF-8");
......
/*
* Copyright 2016 Amherst College
*
* 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 edu.amherst.acdc.itests;
import static java.util.stream.IntStream.range;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.ops4j.pax.exam.CoreOptions.maven;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.File;
import org.apache.camel.CamelContext;
import org.junit.Test;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.ConfigurationManager;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerClass;
import org.slf4j.Logger;
/**
* @author Bethany Seeger
* @since June 13, 2016
*/
@RunWith(PaxExam.class)
@ExamReactorStrategy(PerClass.class)
public class AcrepoFitsIT extends AbstractOSGiIT {
private static Logger LOGGER = getLogger(AcrepoFitsIT.class);
@Configuration
public Option[] config() {
final ConfigurationManager cm = new ConfigurationManager();
final String fcrepoPort = cm.getProperty("fcrepo.dynamic.test.port");
final String fitsServicePort = cm.getProperty("karaf.fits.port");
final String rmiRegistryPort = cm.getProperty("karaf.rmiRegistry.port");
final String rmiServerPort = cm.getProperty("karaf.rmiServer.port");
final String fcrepoBaseUrl = "localhost:" + fcrepoPort + "/fcrepo/rest";
final String sshPort = cm.getProperty("karaf.ssh.port");
return new Option[] {
karafDistributionConfiguration()
.frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf")
.versionAsInProject().type("zip"))
.unpackDirectory(new File("target", "exam"))
.useDeployFolder(false),
logLevel(LogLevel.INFO),
keepRuntimeFolder(),
configureConsole().ignoreLocalConsole(),
features(maven().groupId("org.apache.karaf.features").artifactId("standard")
.versionAsInProject().classifier("features").type("xml"), "scr"),
features(maven().groupId("edu.amherst.acdc").artifactId("acrepo-karaf")
.type("xml").classifier("features").versionAsInProject(),
"acrepo-fits-rest"),
systemProperty("karaf.fits.port").value(fitsServicePort),
systemProperty("fcrepo.port").value(fcrepoPort),
editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiRegistryPort", rmiRegistryPort),
editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiServerPort", rmiServerPort),
editConfigurationFilePut("etc/org.apache.karaf.shell.cfg", "sshPort", sshPort),
editConfigurationFilePut("etc/edu.amherst.acdc.fits.rest.cfg", "fcrepo.baseUrl", fcrepoBaseUrl),
editConfigurationFilePut("etc/edu.amherst.acdc.fits.rest.cfg", "rest.port", fitsServicePort)
};
}
@Test
public void testInstallation() throws Exception {
assertTrue(featuresService.isInstalled(featuresService.getFeature("camel-core")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("camel-blueprint")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("camel-jetty9")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("camel-http4")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-fits-rest")));
}
@Test
@Ignore("Not actually going to run FITS service here")
public void testFitsRestService() throws Exception {
// make sure that the camel context has started up.
final CamelContext ctx = getOsgiService(CamelContext.class, "(camel.context.name=AcrepoFitsRest)",
10000);
assertNotNull(ctx);
final String baseUrl = "http://localhost:" + System.getProperty("fcrepo.port") + "/fcrepo/rest";
final String baseSvcUrl = "http://localhost:" + System.getProperty("karaf.fits.port") + "/fits";
assertTrue(options(baseSvcUrl).contains("owl:equivalentClass fedora:Binary"));
range(1, 2).mapToObj(x -> post(baseUrl, getClass().getResourceAsStream("/fitsfile" + x + ".jpg"), "image/jpeg"))
.forEach(url -> {
final String id = url.replace(baseUrl, "");
final String html = get(baseSvcUrl + id);
// make sure we got something from the fits service
assertTrue(html.contains("http://hul.harvard.edu/ois/xml/ns/fits/fits_output"));
assertTrue(html.contains("fits_output"));
});
}
}
......@@ -59,6 +59,7 @@ public class AcrepoServicesIT extends AbstractOSGiIT {
final String templatePort = cm.getProperty("karaf.template.port");
final String jsonldPort = cm.getProperty("karaf.jsonld.port");
final String imagePort = cm.getProperty("karaf.image.port");
final String fitsPort = cm.getProperty("karaf.fits.port");
return new Option[] {
karafDistributionConfiguration()
......@@ -79,14 +80,14 @@ public class AcrepoServicesIT extends AbstractOSGiIT {
.type("xml").classifier("features").versionAsInProject(), "acrepo-idiomatic",
"acrepo-idiomatic-pgsql", "acrepo-mint-service", "acrepo-xml-metadata",
"acrepo-jsonld-service", "acrepo-jsonld-osgi", "acrepo-template-mustache",
"acrepo-image-service"),
"acrepo-image-service", "acrepo-fits-rest"),
editConfigurationFilePut("etc/edu.amherst.acdc.jsonld.service.cfg", "rest.port", jsonldPort),
editConfigurationFilePut("etc/edu.amherst.acdc.template.mustache.cfg", "rest.port", templatePort),
editConfigurationFilePut("etc/edu.amherst.acdc.xml.metadata.cfg", "rest.port", metadataPort),
editConfigurationFilePut("etc/edu.amherst.acdc.idiomatic.cfg", "rest.port", idiomaticPort),
editConfigurationFilePut("etc/edu.amherst.acdc.image.service.cfg", "rest.port", imagePort),
editConfigurationFilePut("etc/edu.amherst.acdc.fits.rest.cfg", "rest.port", fitsPort),
editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiRegistryPort", rmiRegistryPort),
editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiServerPort", rmiServerPort),