Commit d2a010df authored by acoburn's avatar acoburn
Browse files

extract apix code

parent e42452fb
API Extension Framework for Fedora
==================================
This implements the API-X Framework for Fedora.
Building
--------
To build this project use
mvn install
Deploying in OSGi
-----------------
This project can be deployed in an OSGi container. For example using
[Apache Karaf](http://karaf.apache.org) version 4.x and above, you can run the following
command from its shell:
feature:repo-add mvn:edu.amherst.acdc/repository-services/LATEST/xml/features
feature:install acrepo-apix
Or by copying the compiled bundle into `$KARAF_HOME/deploy`.
Configuration
-------------
The application can be configured by creating the following configuration
file `$KARAF_HOME/etc/edu.amherst.acdc.apix.cfg`. The following values
are available for configuration:
The REST endpoint prefix.
rest.prefix=/apix
The port on which the API-X service is available.
rest.port=13431
The service prefix
apix.prefix=svc:
The fedora base URL
fcrepo.baseUrl=localhost:8080/fcrepo/rest
The fedora username
fcrepo.authUsername=
The fedora password
fcrepo.authPassword=
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">
<!--
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>
<groupId>edu.amherst.acdc</groupId>
<artifactId>repository-services</artifactId>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>acrepo-apix</artifactId>
<packaging>bundle</packaging>
<name>API-X Implementation</name>
<description>Camel-based service for implementing API-X</description>
<properties>
<osgi.export.packages>edu.amherst.acdc.apix;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.fcrepo.camel</groupId>
<artifactId>fcrepo-camel</artifactId>
</dependency>
<!-- logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- testing -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-blueprint</artifactId>
<scope>test</scope>
</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.apix.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>
rest.host=localhost
rest.port=13431
rest.proxy=/fcrepo/rest
rest.binding=/apix/bind
rest.registry=/apix/registry
# Fedora Repository location
fcrepo.baseUrl=localhost:8080/fcrepo/rest
fcrepo.authUsername=
fcrepo.authPassword=
apix.prefix=svc:
/*
* Copyright 2015 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.apix;
/**
* @author acoburn
*/
public final class ApixHeaders {
public static final String SERVICE_LIST = "CamelApixServiceList";
public static final String SERVICE_NAME = "CamelApixService";
public static final String SERVICE_REGISTRATION = "CamelApixRegistration";
public static final String SERVICE_ENDPOINT = "CamelApixEndpoint";
private ApixHeaders() {
// prevent instantiation
}
}
/*
* Copyright 2015 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.apix;
import static edu.amherst.acdc.apix.ApixHeaders.SERVICE_ENDPOINT;
import static edu.amherst.acdc.apix.ApixHeaders.SERVICE_NAME;
import static edu.amherst.acdc.apix.ApixHeaders.SERVICE_REGISTRATION;
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.fcrepo.camel.FcrepoHeaders.FCREPO_IDENTIFIER;
import org.apache.camel.builder.RouteBuilder;
/**
* A content router for API-X.
*
* @author Aaron Coburn
*/
public class ApixRouter extends RouteBuilder {
/**
* Configure the message route workflow.
*/
public void configure() throws Exception {
/**
* REST routing
*/
rest("{{rest.registry}}")
.get("/").to("direct:list")
.get("/{service}").to("direct:describe")
.put("/{service}").to("direct:register")
.delete("/{service}").to("direct:unregister");
rest("{{rest.binding}}")
.get("/").to("direct:bindings")
.get("/{service}").to("direct:list-bindings")
.post("/{service}").to("direct:bind")
.delete("/{service}/{identifier}").to("direct:unbind");
from("direct:bindings")
.routeId("ApixBindings")
.setBody().constant("FIXME: Here is some information about the binding endpoint...");
from("jetty:http://{{rest.host}}:{{rest.port}}{{rest.proxy}}?matchOnUriPrefix=true")
.routeId("ApixRest")
.routeDescription("The main API-X client REST interface")
.process(new ServiceProcessor())
.choice()
.when(header(SERVICE_NAME).isEqualTo("list"))
.setHeader("Link").simple("<${headers.CamelApixRegistration}>; rel=\"describedby\"")
.to("direct:list")
.when(header(SERVICE_NAME).isNotNull())
.to("direct:route-service")
.otherwise()
.log("Headers: ${headers}")
.to("http4://{{fcrepo.baseUrl}}?authUsername={{fcrepo.authUsername}}" +
"&authPassword={{fcrepo.authPassword}}&bridgeEndpoint=true" +
"&throwExceptionOnFailure=false&disableStreamCache=true")
.setHeader("Link")
.simple("<${headers.CamelHttpUri}/{{apix.prefix}}list>; rel=\"service\"");
from("direct:route-service")
.routeId("ApixServiceRouter")
.routeDescription("Route API-X service requests")
.to("direct:check-registered")
.choice()
.when(header(SERVICE_ENDPOINT).isNotNull())
.to("direct:proxy-to-endpoint")
.when(header(SERVICE_REGISTRATION).isNotNull())
.to("direct:unavailable")
.otherwise()
.to("direct:not-found");
from("direct:unavailable")
.routeId("ApixServiceUnavailable")
.routeDescription("Respond appropriately if the service is unavailable")
.setHeader("Link").simple("<${headers.CamelApixRegistration}>; rel=\"describedby\"")
.setHeader(HTTP_RESPONSE_CODE).constant(503)
.transform().constant("Service Unavailable");
from("direct:not-found")
.routeId("ApixServiceNotFound")
.routeDescription("Respond appropriately if the service has not been registered")
.setHeader(HTTP_RESPONSE_CODE).constant(404)
.transform().constant("Not Found");
from("direct:proxy-to-endpoint")
.routeId("ApixServiceProxy")
.routeDescription("Proxy requests to the remote service")
.setHeader("Link").simple("<${headers.CamelApixRegistration}>; rel=\"describedby\"")
.setHeader(HTTP_URI).header(SERVICE_ENDPOINT)
.setHeader(HTTP_PATH).header(FCREPO_IDENTIFIER)
.to("http4://localhost");
}
}
/*
* Copyright 2015 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.apix;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
import static edu.amherst.acdc.apix.ApixHeaders.SERVICE_LIST;
import static org.fcrepo.camel.FcrepoHeaders.FCREPO_IDENTIFIER;
import java.util.List;
import java.util.Optional;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.RuntimeCamelException;
/**
* A Processor to reformat service names as Link headers.
*
* @author Aaron Coburn
*/
public class LinkProcessor implements Processor {
/**
* Process an exchange
*
* @param exchange The exchange
*/
public void process(final Exchange exchange) throws Exception {
final String svcPrefix;
final String registryPath;
final String proxyPath;
final String bindingPath;
try {
svcPrefix = exchange.getContext().resolvePropertyPlaceholders("{{apix.prefix}}");
registryPath = exchange.getContext().resolvePropertyPlaceholders("{{rest.registry}}");
proxyPath = exchange.getContext().resolvePropertyPlaceholders("{{rest.proxy}}");
bindingPath = exchange.getContext().resolvePropertyPlaceholders("{{rest.binding}}");
} catch (final Exception ex) {
throw new RuntimeCamelException("Could not resolve property placeholders", ex);
}
final Message in = exchange.getIn();
final String base = in.getHeader("CamelServletContextPath", String.class);
final String identifier = in.getHeader(FCREPO_IDENTIFIER, String.class);
final List<String> services = getServicesAsList(in.getHeader(SERVICE_LIST));
final String prefix = getRegistryPrefix(registryPath, base)
.orElseGet(() -> getBindingPrefix(bindingPath, base)
.orElseGet(() -> getProxyPrefix(proxyPath, base, identifier, svcPrefix)
.orElse("/")));
in.setHeader("Link", services.stream().map(x -> "<" + prefix + x + ">; rel=\"service\"").collect(toList()));
}
private static Optional<String> getRegistryPrefix(final String registryPath, final String base) {
if (base.startsWith(registryPath)) {
return Optional.of(registryPath + "/");
} else {
return Optional.empty();
}
}
private static Optional<String> getBindingPrefix(final String bindingPath, final String base) {
if (base.startsWith(bindingPath)) {
return Optional.of("");
} else {
return Optional.empty();
}
}
private static Optional<String> getProxyPrefix(final String proxyPath, final String base, final String identifier,
final String svcPrefix) {
final String partial = base + identifier;
if (base.startsWith(proxyPath)) {
return Optional.of(partial.endsWith("/") ? partial + svcPrefix : partial + "/" + svcPrefix);
} else {
return Optional.empty();
}
}
@SuppressWarnings("unchecked")
private static List<String> getServicesAsList(final Object value) {
if (List.class.isInstance(value)) {
return List.class.cast(value);
} else if (String.class.isInstance(value)) {
return asList(String.class.cast(value));
} else {
return null;
}
}
}
/*
* Copyright 2015 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.apix;
import static edu.amherst.acdc.apix.ApixHeaders.SERVICE_NAME;
import static org.apache.camel.Exchange.HTTP_PATH;
import static org.fcrepo.camel.FcrepoHeaders.FCREPO_IDENTIFIER;
import java.util.Optional;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.RuntimeCamelException;
/**
* A Processor to extract the API-X Service name.
*
* @author Aaron Coburn
*/
public class ServiceProcessor implements Processor {
/**
* Process an exchange
*
* @param exchange The exchange
*/
public void process(final Exchange exchange) throws Exception {
final Message in = exchange.getIn();
final String svcPrefix;
try {
svcPrefix = exchange.getContext().resolvePropertyPlaceholders("{{apix.prefix}}");
} catch (final Exception ex) {
throw new RuntimeCamelException("Could not resolve property placeholders", ex);
}
final String path = in.getHeader(HTTP_PATH, String.class);
final Optional<String> svcName = getServiceName(path, svcPrefix);
svcName.ifPresent(x -> in.setHeader(SERVICE_NAME, x));
in.setHeader(FCREPO_IDENTIFIER, path.replaceAll("/" + svcPrefix + ".*$", ""));
}
private Optional<String> getServiceName(final String path, final String prefix) {
final int idx1 = path.lastIndexOf("/" + prefix);
final int idx2 = path.lastIndexOf("/");
if (idx1 >= 0 && idx1 == idx2) {
final String svcName = path.substring(path.lastIndexOf("/") + prefix.length() + 1);
return Optional.of(svcName);
}
return Optional.empty();
}
}
<?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.apix" update-strategy="reload">
<cm:default-properties>
<cm:property name="rest.proxy" value="/fcrepo/rest"/>
<cm:property name="rest.binding" value="/apix/bind"/>
<cm:property name="rest.registry" value="/apix/registry"/>
<cm:property name="rest.port" value="13431"/>
<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="apix.prefix" value="svc:"/>
</cm:default-properties>
</cm:property-placeholder>
<reference id="registryService" interface="edu.amherst.acdc.registry.api.RegistryService" filter="(osgi.jndi.service.name=registry)" />
<reference id="bindingService" interface="edu.amherst.acdc.binding.api.BindingService" filter="(osgi.jndi.service.name=binding)" />
<bean id="linkProcessor" class="edu.amherst.acdc.apix.LinkProcessor"/>
<camelContext xmlns="http://camel.apache.org/schema/blueprint">
<package>edu.amherst.acdc.apix</package>
<restConfiguration component="jetty" host="{{rest.host}}" port="{{rest.port}}"/>
<route id="ApixInstanceList">
<from uri="direct:list-bindings"/>
<setHeader headerName="CamelApixServiceList">
<method ref="bindingService" method="list(${header.service})"/>
</setHeader>
<process ref="linkProcessor"/>
<setBody>
<simple>These are the instances for the ${header.service} service: ${headers.CamelApixServiceList}</simple>
</setBody>
</route>
<route id="ApixBind">
<from uri="direct:bind"/>
<convertBodyTo type="java.lang.String"/>
<choice>
<when>
<method ref="bindingService" method="bind(${header.service}, ${body})"/>
<setBody>
<constant>OK</constant>
</setBody>
</when>
<otherwise>
<setBody>
<constant>Not OK</constant>
</setBody>
</otherwise>
</choice>
</route>
<route id="ApixUnbind">
<from uri="direct:unbind"/>
<choice>
<when>