Commit a4aa41dc authored by acoburn's avatar acoburn
Browse files

Create a simple image processing service

parent c128d101
......@@ -10,6 +10,7 @@ Services
* `acrepo-idiomatic`: Id Mapping Service: This maps a public ID to a (internal and typically much longer) fedora URI
* `acrepo-idiomatic-pgsql`: Id Mapping Service Database: This exposes a Postgres datastore for use with the Id Mapping service
* `acrepo-image-service`: An image manipulation service
* `acrepo-jsonld-osgi`: This service creates expanded or compact JSON-LD representations of input documents
* `acrepo-jsonld-service`: This service exposes an HTTP endpoint for creating compact JSON-LD documents from a fedora repository using a pluggable context document
* `acrepo-mint-service`: This mints random (public) URIs for use with fedora resources
......@@ -32,6 +33,7 @@ command from its shell:
feature:repo-add mvn:edu.amherst.acdc/acrepo-karaf/LATEST/xml/features
feature:install acrepo-idiomatic
feature:install acrepo-idiomatic-pgsql
feature:install acrepo-image-service
feature:install acrepo-jsonld-osgi
feature:install acrepo-jsonld-service
feature:install acrepo-mint-service
......
Repository Image manipulation service
=====================================
This comprises an image manipulation service used in combination with ImageMagick's `convert` utility.
This service assumes that ImageMagick is installed somewhere.
The endpoint location is configurable. Any path appended to that endpoint root will be used
as a path to a Fedora Resource. The `options` parameter can be used to add output parameters
to ImageMagick. The `Accept` header is used to change the format of the image. By default,
the output is JPEG.
Building
--------
To build this project use
mvn install
Deploying in OSGi
-----------------
This 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-image-service
Or by copying any of the compiled bundles into `$KARAF_HOME/deploy`.
Configuration
-------------
The application can be configured by creating the following configuration
file `$KARAF_HOME/etc/edu.amherst.acdc.image.service.cfg`. The following values
are available for configuration:
The base url of the fedora repository and any authentication parameters
fcrepo.baseUrl=localhost:8080/fcrepo/rest
fcrepo.authUsername=
fcrepo.authPassword=
The acceptable output formats for images
valid.formats=jpeg,jp2,tiff
The path to the `convert` utility
convert.path=convert
The port on which the service is made availalbe
rest.port=9081
The hostname on which the service is available
rest.host=localhost
The prefix for the service
rest.prefix=/image
By editing this file, any currently running routes will be immediately redeployed
with the new values.
For more help see the [Apache Camel](http://camel.apache.org) documentation
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>repository-services</artifactId>
<groupId>edu.amherst.acdc</groupId>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>acrepo-image-service</artifactId>
<packaging>bundle</packaging>
<name>Image manipulation service</name>
<properties>
<osgi.export.packages>edu.amherst.acdc.image.service;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-blueprint</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jetty9</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-exec</artifactId>
</dependency>
<dependency>
<groupId>org.fcrepo.camel</groupId>
<artifactId>fcrepo-camel</artifactId>
</dependency>
<!-- Testing & Camel Plugin -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-blueprint</artifactId>
</dependency>
</dependencies>
<build>
<defaultGoal>install</defaultGoal>
<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>
<!-- 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.image.service.cfg</file>
<type>cfg</type>
<classifier>configuration</classifier>
</artifact>
</artifacts>
</configuration>
</execution>
</executions>
</plugin>
<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>
# HTTP Port
rest.host=localhost
rest.port=9081
rest.prefix=/image
# Path to convert utility
convert.path=convert
# Valid output image formats
valid.formats=jpeg,jp2,tiff
# Repository Base URL
fcrepo.baseUrl=localhost:8080/fcrepo/rest
fcrepo.authUsername=
fcrepo.authPassword=
/*
* 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.image.service;
import static java.util.Arrays.stream;
import static org.apache.camel.Exchange.CONTENT_TYPE;
import static org.apache.camel.Exchange.HTTP_METHOD;
import static org.apache.camel.Exchange.HTTP_PATH;
import static org.apache.camel.Exchange.HTTP_RESPONSE_CODE;
import static org.apache.camel.Exchange.HTTP_URI;
import static org.apache.camel.LoggingLevel.INFO;
import static org.apache.camel.builder.PredicateBuilder.and;
import static org.apache.camel.component.exec.ExecBinding.EXEC_COMMAND_ARGS;
import static org.fcrepo.camel.FcrepoHeaders.FCREPO_IDENTIFIER;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.InputStream;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.builder.RouteBuilder;
import org.slf4j.Logger;
/**
* A content router for handling JMS events.
*
* @author Aaron Coburn
*/
public class EventRouter extends RouteBuilder {
private final String IMAGE_OUTPUT = "CamelImageOutput";
private final String IMAGE_INPUT = "CamelImageInput";
private static final Logger LOGGER = getLogger(EventRouter.class);
/**
* Configure the message route workflow.
*/
public void configure() throws Exception {
from("jetty:http://{{rest.host}}:{{rest.port}}{{rest.prefix}}?" +
"matchOnUriPrefix=true&sendServerVersion=false&httpMethodRestrict=GET,OPTIONS")
.routeId("ImageRouter")
.setHeader(FCREPO_IDENTIFIER).header(HTTP_PATH)
.setHeader(IMAGE_OUTPUT).header("Accept")
.removeHeaders("Accept")
.choice()
.when(header(HTTP_METHOD).isEqualTo("GET"))
.setHeader(FCREPO_IDENTIFIER).header(HTTP_PATH)
.to("direct:get")
.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:get")
.routeId("ImageGet")
.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(and(header(CONTENT_TYPE).startsWith("image/"),
header("Link").contains("<http://www.w3.org/ns/ldp#NonRDFSource>;rel=\"type\"")))
.log(INFO, LOGGER, "Image Processing ${headers[CamelHttpPath]}")
.to("direct:convert")
.when(header(HTTP_RESPONSE_CODE).isEqualTo(200))
.to("direct:notBinary")
.otherwise()
.to("direct:error");
from("direct:notBinary")
.routeId("ImageNotBinary")
.removeHeaders("*")
.setBody(constant("Error: this resource is not a fedora:Binary"))
.setHeader(CONTENT_TYPE).constant("text/plain")
.setHeader(HTTP_RESPONSE_CODE).constant(400);
from("direct:error")
.routeId("ImageError")
.setBody(constant("Error: this resource is not accessible"));
from("direct:convert")
.setHeader(HTTP_METHOD).constant("GET")
.setHeader(HTTP_PATH).header(FCREPO_IDENTIFIER)
.to("http4://{{fcrepo.baseUrl}}?authUsername={{fcrepo.authUsername}}" +
"&authPassword={{fcrepo.authPassword}}&throwExceptionOnFailure=true")
.setHeader(IMAGE_INPUT).header(CONTENT_TYPE)
.process(exchange -> {
final String accept = exchange.getIn().getHeader(IMAGE_OUTPUT, "", String.class);
final String fmt = accept.matches("^image/\\w+$") ? accept.replace("image/", "") : "jpeg";
final boolean valid;
try {
valid = stream(getContext().resolvePropertyPlaceholders("{{valid.formats}}").split(","))
.anyMatch(fmt::equals);
} catch (final Exception ex) {
throw new RuntimeCamelException("Couldn't resolve property placeholder", ex);
}
if (valid) {
exchange.getIn().setHeader(IMAGE_OUTPUT, "image/" + fmt);
exchange.getIn().setHeader(EXEC_COMMAND_ARGS,
" - " + exchange.getIn().getHeader("options", "", String.class) + " " + fmt + ":-");
} else {
throw new RuntimeCamelException("Invalid format: " + fmt);
}
})
.removeHeaders("CamelHttp*")
.log(INFO, LOGGER, "Converting from ${headers[CamelImageInput]} to ${headers[CamelImageOutput]}")
.to("exec:{{convert.path}}")
.process(exchange -> {
exchange.getOut().setBody(exchange.getIn().getBody(InputStream.class));
});
}
}
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
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
http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
<!-- OSGI blueprint property placeholder -->
<cm:property-placeholder persistent-id="edu.amherst.acdc.image.service" update-strategy="reload">
<cm:default-properties>
<cm:property name="rest.port" value="9081"/>
<cm:property name="rest.prefix" value="/image"/>
<cm:property name="rest.host" value="localhost"/>
<cm:property name="fcrepo.baseUrl" value="localhost:8080/fcrepo/rest"/>
<cm:property name="fcrepo.authUsername" value=""/>
<cm:property name="fcrepo.authPassword" value=""/>
<cm:property name="convert.path" value="convert"/>
<cm:property name="valid.formats" value="jpeg,jp2,tiff"/>
</cm:default-properties>
</cm:property-placeholder>
<camelContext id="AcrepoImageService" xmlns="http://camel.apache.org/schema/blueprint">
<package>edu.amherst.acdc.image.service</package>
</camelContext>
</blueprint>
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix fedora: <http://fedora.info/definitions/v4/repository#> .
[ owl:unionOf (
[ a owl:Restriction ; owl:onProperty ebucore:mimeType ; owl:hasValue "image/tiff" ]
[ a owl:Restriction ; owl:onProperty ebucore:mimeType ; owl:hasValue "image/jpeg" ]
[ a owl:Restriction ; owl:onProperty ebucore:mimeType ; owl:hasValue "image/jp2" ] ) ] .
<?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.image.service" 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>
......@@ -73,7 +73,8 @@ public class AcrepoServicesIT extends AbstractOSGiIT {
features(maven().groupId("edu.amherst.acdc").artifactId("acrepo-karaf")
.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-jsonld-service", "acrepo-jsonld-osgi", "acrepo-template-mustache",
"acrepo-image-service"),
editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiRegistryPort", rmiRegistryPort),
editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiServerPort", rmiServerPort),
......@@ -92,5 +93,6 @@ public class AcrepoServicesIT extends AbstractOSGiIT {
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-jsonld-service")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-jsonld-osgi")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-template-mustache")));
assertTrue(featuresService.isInstalled(featuresService.getFeature("acrepo-image-service")));
}
}
......@@ -39,6 +39,20 @@
<configfile finalname="/etc/org.ops4j.datasource-idiomatic.cfg">mvn:edu.amherst.acdc/acrepo-idiomatic-pgsql/${project.version}/cfg/configuration</configfile>
</feature>
<feature name="acrepo-image-service" version="${project.version}" resolver="(obr)" start-level="50">
<details>Installs the image service</details>
<feature version="${camel.version}">camel</feature>
<feature version="${camel.version}">camel-blueprint</feature>
<feature version="${camel.version}">camel-jetty9</feature>
<feature version="${camel.version}">camel-http4</feature>
<feature version="${camel.version}">camel-exec</feature>
<bundle>mvn:edu.amherst.acdc/acrepo-image-service/${project.version}</bundle>
<configfile finalname="/etc/edu.amherst.acdc.image.service.cfg">mvn:edu.amherst.acdc/acrepo-image-service/${project.version}/cfg/configuration</configfile>
</feature>
<feature name="acrepo-mint-service" version="${project.version}" resolver="(obr)" start-level="50">
<details>Installs the id minter</details>
......
......@@ -83,6 +83,7 @@
<modules>
<module>acrepo-idiomatic-pgsql</module>
<module>acrepo-idiomatic</module>
<module>acrepo-image-service</module>
<module>acrepo-karaf</module>
<module>acrepo-mint-service</module>
<module>acrepo-xml-metadata</module>
......@@ -118,6 +119,11 @@
<artifactId>camel-jetty9</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-exec</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-saxon</artifactId>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment