diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..05a73016427276249b29d6af6cad8646c6133420
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/.settings
+/.classpath
diff --git a/.project b/.project
new file mode 100644
index 0000000000000000000000000000000000000000..1d0ba7966df3f4531eb5665b12d3659fbe49dd51
--- /dev/null
+++ b/.project
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>aggregator</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+		<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+	</natures>
+</projectDescription>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7cf4d6e24c77b04cf7b7d5c690ab055c5f60b75b
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,285 @@
+<?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>
+	<groupId>info.textgrid.services</groupId>
+	<artifactId>aggregator</artifactId>
+	<version>1.0-SNAPSHOT</version>
+	<packaging>war</packaging>
+	<name>Simple CXF JAX-RS webapp service using spring configuration</name>
+	<description>Simple CXF JAX-RS webapp service using spring configuration</description>
+	<properties>
+		<jackson.version>1.9.7</jackson.version>
+	</properties>
+
+	<developers>
+		<developer>
+			<id>vitt</id>
+			<name>Thorsten Vitt</name>
+			<email>thorsten.vitt@uni-wuerzburg.de</email>
+			<url>http://www.thorstenvitt.de/</url>
+			<organization>Universität Würzburg</organization>
+			<organizationUrl>http://www.germanistik.uni-wuerzburg.de/lehrstuehle/computerphilologie</organizationUrl>
+			<roles>
+			</roles>
+		</developer>
+	</developers>
+
+
+	<repositories>
+		<repository>
+			<id>bibforge.internal.http</id>
+			<name>Bibforge Managed Internal Repository</name>
+			<url>http://repository.bibforge.org/archiva/repository/internal</url>
+			<releases>
+				<enabled>true</enabled>
+			</releases>
+			<snapshots>
+				<enabled>false</enabled>
+			</snapshots>
+		</repository>
+		<repository>
+			<id>bibforge.snapshots.http</id>
+			<name>Bibforge Managed Snapshot Repository</name>
+			<url>http://repository.bibforge.org/archiva/repository/snapshots</url>
+			<releases>
+				<enabled>true</enabled>
+			</releases>
+			<snapshots>
+				<enabled>true</enabled>
+			</snapshots>
+		</repository>
+	</repositories>
+
+
+	<dependencies>
+		<dependency>
+			<groupId>org.codehaus.jackson</groupId>
+			<artifactId>jackson-core-asl</artifactId>
+			<version>${jackson.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.codehaus.jackson</groupId>
+			<artifactId>jackson-mapper-asl</artifactId>
+			<version>${jackson.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.codehaus.jackson</groupId>
+			<artifactId>jackson-jaxrs</artifactId>
+			<version>${jackson.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.10</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+			<version>12.0</version>
+		</dependency>
+		<dependency>
+			<groupId>info.textgrid.middleware</groupId>
+			<artifactId>tgcrud-client</artifactId>
+			<version>2.3.4-SNAPSHOT</version>
+		</dependency>
+		<dependency>
+			<groupId>info.textgrid.middleware</groupId>
+			<artifactId>tgsearch-client</artifactId>
+			<version>2.1.0-SNAPSHOT</version>
+		</dependency>
+		<dependency>
+			<groupId>info.textgrid.middleware</groupId>
+			<artifactId>confclient</artifactId>
+			<version>1.0-SNAPSHOT</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.cxf</groupId>
+			<artifactId>cxf-rt-frontend-jaxrs</artifactId>
+			<version>2.5.2</version>
+		</dependency>
+	</dependencies>
+	
+	
+	<build>
+		<pluginManagement>
+			<plugins>
+				<plugin>
+					<groupId>org.codehaus.mojo</groupId>
+					<artifactId>tomcat-maven-plugin</artifactId>
+					<version>1.1</version>
+					<executions>
+						<execution>
+							<id>default-cli</id>
+							<goals>
+								<goal>run</goal>
+							</goals>
+							<configuration>
+								<port>13000</port>
+								<path>/aggregator</path>
+								<useSeparateTomcatClassLoader>true</useSeparateTomcatClassLoader>
+							</configuration>
+						</execution>
+					</executions>
+				</plugin>
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-compiler-plugin</artifactId>
+					<configuration>
+						<source>1.5</source>
+						<target>1.5</target>
+					</configuration>
+				</plugin>
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-eclipse-plugin</artifactId>
+					<configuration>
+						<projectNameTemplate>[artifactId]-[version]</projectNameTemplate>
+						<wtpmanifest>true</wtpmanifest>
+						<wtpapplicationxml>true</wtpapplicationxml>
+						<wtpversion>2.0</wtpversion>
+					</configuration>
+				</plugin>
+				<!--This plugin's configuration is used to store Eclipse m2e settings 
+					only. It has no influence on the Maven build itself. -->
+				<plugin>
+					<groupId>org.eclipse.m2e</groupId>
+					<artifactId>lifecycle-mapping</artifactId>
+					<version>1.0.0</version>
+					<configuration>
+						<lifecycleMappingMetadata>
+							<pluginExecutions>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>
+											org.codehaus.mojo
+										</groupId>
+										<artifactId>
+											build-helper-maven-plugin
+										</artifactId>
+										<versionRange>
+											[1.5,)
+										</versionRange>
+										<goals>
+											<goal>
+												reserve-network-port
+											</goal>
+										</goals>
+									</pluginExecutionFilter>
+									<action>
+										<ignore></ignore>
+									</action>
+								</pluginExecution>
+							</pluginExecutions>
+						</lifecycleMappingMetadata>
+					</configuration>
+				</plugin>
+			</plugins>
+		</pluginManagement>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>2.3.1</version>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>build-helper-maven-plugin</artifactId>
+				<version>1.5</version>
+				<executions>
+					<execution>
+						<id>reserve-network-port</id>
+						<goals>
+							<goal>reserve-network-port</goal>
+						</goals>
+						<phase>process-test-resources</phase>
+						<configuration>
+							<portNames>
+								<portName>test.server.port</portName>
+							</portNames>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>tomcat-maven-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>start-tomcat</id>
+						<goals>
+							<goal>run-war</goal>
+						</goals>
+						<phase>pre-integration-test</phase>
+						<configuration>
+							<port>${test.server.port}</port>
+							<path>/aggregator</path>
+							<fork>true</fork>
+							<useSeparateTomcatClassLoader>true</useSeparateTomcatClassLoader>
+						</configuration>
+					</execution>
+					<execution>
+						<id>stop-tomcat</id>
+						<goals>
+							<goal>shutdown</goal>
+						</goals>
+						<phase>post-integration-test</phase>
+						<configuration>
+							<path>/aggregator</path>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-failsafe-plugin</artifactId>
+				<version>2.8.1</version>
+				<executions>
+					<execution>
+						<id>integration-test</id>
+						<goals>
+							<goal>integration-test</goal>
+						</goals>
+						<configuration>
+							<systemPropertyVariables>
+								<service.url>http://localhost:${test.server.port}/aggregator</service.url>
+							</systemPropertyVariables>
+						</configuration>
+					</execution>
+					<execution>
+						<id>verify</id>
+						<goals>
+							<goal>verify</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+	<profiles>
+	    <profile>
+		<id>wue</id>
+		<build>
+		    <plugins>
+			<plugin>
+			    <groupId>org.codehaus.mojo</groupId>
+			    <artifactId>tomcat-maven-plugin</artifactId>
+			    <configuration>
+				<url>http://wrzh075.rzhousing.uni-wuerzburg.de:8180/manager</url>
+				<server>wrzh075</server>
+			    </configuration>
+			</plugin>
+		    </plugins>
+		</build>
+	    </profile>
+	</profiles>
+	<organization>
+		<name>TextGrid</name>
+		<url>http://www.textgrid.de/</url>
+	</organization>
+</project>
diff --git a/src/main/java/info/textgrid/services/aggregator/HelloWorld.java b/src/main/java/info/textgrid/services/aggregator/HelloWorld.java
new file mode 100644
index 0000000000000000000000000000000000000000..392fc78298972fb9d644c30256bf6de5d28e2cc6
--- /dev/null
+++ b/src/main/java/info/textgrid/services/aggregator/HelloWorld.java
@@ -0,0 +1,29 @@
+package info.textgrid.services.aggregator;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+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.Response;
+
+@Path("/hello")
+public class HelloWorld {
+
+    @GET
+    @Path("/echo/{input}")
+    @Produces("text/plain")
+    public String ping(@PathParam("input") String input) {
+        return input;
+    }
+
+    @POST
+    @Produces("application/json")
+    @Consumes("application/json")
+    @Path("/jsonBean")
+    public Response modifyJson(JsonBean input) {
+	input.setVal2(input.getVal1());
+	return Response.ok().entity(input).build();
+    }
+}
+
diff --git a/src/main/java/info/textgrid/services/aggregator/JsonBean.java b/src/main/java/info/textgrid/services/aggregator/JsonBean.java
new file mode 100644
index 0000000000000000000000000000000000000000..72342b9be84ca8460f00992a0628effc6517b889
--- /dev/null
+++ b/src/main/java/info/textgrid/services/aggregator/JsonBean.java
@@ -0,0 +1,24 @@
+package info.textgrid.services.aggregator;
+
+
+public class JsonBean {
+    private String val1;
+    private String val2;
+
+    public String getVal1() {
+	return val1;
+    }
+
+    public void setVal1(String val1) {
+	this.val1 = val1;
+    }
+
+    public String getVal2() {
+	return val2;
+    }
+
+    public void setVal2(String val2) {
+	this.val2 = val2;
+    }
+
+}
diff --git a/src/main/java/info/textgrid/services/aggregator/TEICorpus.java b/src/main/java/info/textgrid/services/aggregator/TEICorpus.java
new file mode 100644
index 0000000000000000000000000000000000000000..0786051cb0207a5cfcf8ce23b8298c25a0cf4eea
--- /dev/null
+++ b/src/main/java/info/textgrid/services/aggregator/TEICorpus.java
@@ -0,0 +1,166 @@
+package info.textgrid.services.aggregator;
+
+import info.textgrid.middleware.confclient.ConfservClient;
+import info.textgrid.middleware.confclient.ConfservClientConstants;
+import info.textgrid.middleware.tgsearch.client.SearchClient;
+import info.textgrid.namespaces.metadata.core._2010.MetadataContainerType;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.AuthFault;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.IoFault;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.MetadataParseFault;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.ObjectNotFoundFault;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.TGCrudService;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.TGCrudService_Service;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.ws.BindingProvider;
+import javax.xml.ws.soap.MTOMFeature;
+
+import org.codehaus.jettison.json.JSONException;
+
+/*
+ * Generating a TEIcorpus document from a (single) aggregation
+ * 
+ * Header:
+ * 
+ * Header could be generated in the same way as in the metadata editor, i.e.
+ * paste together the ancestor metadata records (including matching work
+ * records) and then run an XSLT stylesheet.
+ * 
+ * Children's Header:
+ * 
+ * For descendant aggregations, we still need a header. Either we consider this
+ * redundant, or we preserve the metadata records we copied together for the
+ * respective ancestor levels, append the current level & run the
+ * transformation.
+ * 
+ * Body:
+ * 
+ * Naïve approach: Load and recursively process the aggregations, check each
+ * one's content type Probably better: SearchClient has a listAggregation()
+ * method that is also used by the navigator and that returns metadata records.
+ * We could use this, so it's only one large request per aggregation. Should be
+ * reasonably fast.
+ */
+@Path("/teicorpus")
+public class TEICorpus {
+
+	private static final String CONF_ENDPOINT = "https://www.textgridlab.org/1.0/confserv";
+	private ConfservClient confservClient;
+	private SearchClient searchClient;
+	private HashMap<String, String> config;
+	private TGCrudService crud;
+	final Logger logger = Logger.getLogger("info.textgrid.services.aggregator");
+
+	@GET
+	@Path(value="/{aggregation}")
+	@Produces("text/xml")
+	public Response get(@PathParam("aggregation") final URI uri, @QueryParam("attach") @DefaultValue("true") final boolean attach) throws URISyntaxException {
+		logger.fine("TEIcorpus called for root aggregation: " + uri);
+		final TGCrudService crud = getCRUDService();
+		logger.finest("Yo, clients are there.");
+		try {
+			final MetadataContainerType rootAggregationMetadata = crud.readMetadata(null, null, uri.toString());
+			logger.finer("CRUD request for root aggregation successful");
+			final TEICorpusSerializer serializer = new TEICorpusSerializer(rootAggregationMetadata.getObject(), this);
+			final String format = rootAggregationMetadata.getObject().getGeneric().getProvided().getFormat();
+			if (!format.contains("aggregation")) {
+				logger.log(Level.SEVERE, "The requested object {0} is a {1}, not an aggregation, and thus cannot be aggregated.", new Object[] {uri,format});
+				throw new WebApplicationException(javax.ws.rs.core.Response
+						.status(Status.BAD_REQUEST)
+						.entity(MessageFormat.format(
+								"The requested object {0} is a {1}, not an aggregation, and thus cannot be aggregated.", uri,
+								format)).build());
+			}
+			ResponseBuilder builder = Response.ok(serializer, "text/xml");
+			if (attach) {
+				String fileName = rootAggregationMetadata.getObject().getGeneric().getProvided().getTitle().get(0) + ".xml";
+				String asciiFileName = fileName.replaceAll("[^A-Za-z0-9.-]", "_");
+					String extendedNameSpec = "UTF-8''" + UriBuilder.fromPath(fileName).build().toASCIIString();
+					builder.header("Content-Disposition", "attachment; filename*=" + extendedNameSpec + " ");
+				builder.header("Content-Disposition", " filename=\"" + asciiFileName + "\" " );
+			}
+			return builder.build();
+		} catch (final ObjectNotFoundFault e) {
+			logger.log(Level.SEVERE, "CRUD: (Root aggregation) not found", e);
+			throw new WebApplicationException(e, javax.ws.rs.core.Response.status(Status.NOT_FOUND).entity(e.toString()).build());
+		} catch (final MetadataParseFault e) {
+			logger.log(Level.SEVERE, "CRUD: Could not parse metadata", e);
+			throw new WebApplicationException(e);
+		} catch (final IoFault e) {
+			logger.log(Level.SEVERE, "CRUD: IO Fault", e);
+			throw new WebApplicationException(e);
+		} catch (final AuthFault e) {
+			logger.log(Level.SEVERE, "CRUD: (Root aggregation) AuthFault", e);
+			throw new WebApplicationException(e, Status.FORBIDDEN);
+		}
+
+		// final Response response = search.listAggregation(uri.toString());
+		// for (final ResultType result : response.getResult()) {
+		// final String contentType =
+		// result.getObject().getGeneric().getProvided().getFormat();
+		// if (contentType.contains("aggregation")) {
+		// // TODO deal with aggregation
+		// } else if ("text/xml".equals(contentType)) {
+		// // TODO deal with
+		// }
+		// }
+		// return null;
+	}
+
+	TGCrudService getCRUDService() {
+		if (crud == null) {
+			final URL wsdl = TGCrudService_Service.class.getResource("/wsdl/TGCrudService.wsdl");
+			final TGCrudService_Service tgCrudService_Service = new TGCrudService_Service(wsdl);
+			final TGCrudService _crud = tgCrudService_Service.getTGCrudPort(new MTOMFeature());
+			final BindingProvider bp = (BindingProvider) _crud;
+			final Map<String, Object> requestContext = bp.getRequestContext();
+			requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, getConfValue(ConfservClientConstants.TG_CRUD));
+			crud = _crud;
+		}
+		return crud;
+	}
+
+	SearchClient getSearchClient() {
+		if (searchClient == null)
+			searchClient = new SearchClient(getConfValue(ConfservClientConstants.TG_SEARCH_PUBLIC));
+		return searchClient;
+	}
+
+	String getConfValue(final String key) throws WebApplicationException {
+		if (config == null) {
+			try {
+				confservClient = new ConfservClient(CONF_ENDPOINT);
+				config = confservClient.getAll();
+			} catch (final IOException e) {
+				throw new WebApplicationException(e);
+			} catch (final JSONException e) {
+				throw new WebApplicationException(e);
+			} catch (final XMLStreamException e) {
+				throw new WebApplicationException(e);
+			}
+		}
+		return config.get(key);
+	}
+
+}
diff --git a/src/main/java/info/textgrid/services/aggregator/TEICorpusSerializer.java b/src/main/java/info/textgrid/services/aggregator/TEICorpusSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..74bb8a44bbf7a9762efea28fae6efb0f24719bbb
--- /dev/null
+++ b/src/main/java/info/textgrid/services/aggregator/TEICorpusSerializer.java
@@ -0,0 +1,204 @@
+package info.textgrid.services.aggregator;
+
+import info.textgrid.namespaces.metadata.agent._2010.AgentType;
+import info.textgrid.namespaces.metadata.core._2010.MetadataContainerType;
+import info.textgrid.namespaces.metadata.core._2010.ObjectType;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.AuthFault;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.IoFault;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.MetadataParseFault;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.ObjectNotFoundFault;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.ProtocolNotImplementedFault;
+import info.textgrid.namespaces.middleware.tgsearch.ResultType;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.activation.DataHandler;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
+import javax.xml.stream.EventFilter;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.ws.Holder;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Sets;
+
+public class TEICorpusSerializer implements StreamingOutput {
+
+	private static final String TEI_CORPUS = "teiCorpus";
+	private static final String TEI_NS = "http://www.tei-c.org/ns/1.0";
+	private final Logger logger = Logger.getLogger("info.textgrid.services.aggregator.teicorpus.serializer");
+	
+	private XMLOutputFactory outputFactory;
+	private XMLEventFactory eventFactory;
+	private XMLEventWriter writer;
+	private final ObjectType rootObject;
+	private final TEICorpus teiCorpus;
+	private XMLInputFactory inputFactory;
+	private final Set<String> seen = Sets.newHashSet();
+	private static final EventFilter NO_DOCUMENT_NODE = new EventFilter() {
+
+		@Override
+		public boolean accept(final XMLEvent event) {
+			return !(event.isStartDocument() || event.isEndDocument());
+		}
+	};
+	
+	private static String toString(final ObjectType object) {
+		return Joiner.on(" / ").join(object.getGeneric().getProvided().getTitle()) + " (" + object.getGeneric().getGenerated().getTextgridUri().getValue() + ", " +
+			object.getGeneric().getProvided().getFormat() + ")";
+	}
+
+	public TEICorpusSerializer(final ObjectType object, final TEICorpus teiCorpus) {
+		this.rootObject = object;
+		this.teiCorpus = teiCorpus;
+		logger.info("Starting TEIcorpus serialization for " + toString(object));
+	}
+
+	@Override
+	public void write(final OutputStream output) throws IOException, WebApplicationException {
+		outputFactory = XMLOutputFactory.newInstance();
+		eventFactory = XMLEventFactory.newInstance();
+		outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
+		inputFactory = XMLInputFactory.newInstance();
+		try {
+			writer = outputFactory.createXMLEventWriter(output);
+			writer.setDefaultNamespace(TEI_NS);
+			writer.add(eventFactory.createStartDocument());
+
+			write(output, rootObject);
+
+			writer.close();
+		} catch (final XMLStreamException e) {
+			logger.log(Level.SEVERE, "XML Error during serialization", e);
+			throw new WebApplicationException(e);
+		}
+	}
+
+	private void write(final OutputStream output, final ObjectType object) throws XMLStreamException {
+		if (seen.contains(object.getGeneric().getGenerated().getTextgridUri().getValue()))
+			writeSkip(output, object);
+		else
+			seen.add(object.getGeneric().getGenerated().getTextgridUri().getValue());
+
+
+		if (object.getGeneric().getProvided().getFormat().contains("aggregation")) {
+			writeAggregation(output, object);
+		} else if (object.getGeneric().getProvided().getFormat().equals("text/xml")) {
+			writeXML(output, object);
+		} else
+			writeSkip(output, object);
+	}
+
+	private void writeAggregation(final OutputStream output, final ObjectType object) throws XMLStreamException {
+		logger.fine("[Processing aggregation " + toString(object));
+		final info.textgrid.namespaces.middleware.tgsearch.Response response = teiCorpus.getSearchClient().listAggregation(
+				object.getGeneric().getGenerated().getTextgridUri().getValue());
+		final List<ResultType> result = response.getResult();
+
+		writer.add(eventFactory.createStartElement("", TEI_NS, TEI_CORPUS));
+		writer.add(eventFactory.createAttribute("corresp", object.getGeneric().getGenerated().getTextgridUri().getValue()));
+		
+		writeHeader(object);
+
+		for (final ResultType resultType : result) {
+			final ObjectType aggregate = resultType.getObject();
+			if (aggregate != null)
+				write(output, aggregate);
+		}
+
+		writer.add(eventFactory.createEndElement("", TEI_NS, TEI_CORPUS));
+		logger.fine("Finished aggregation " + toString(object) + "]");
+	}
+
+	private void writeHeader(ObjectType object) throws XMLStreamException {
+		writer.add(eventFactory.createStartElement("",  TEI_NS, "teiHeader"));
+		writer.add(eventFactory.createComment("This is a (temporary) header generated from aggregation metadata."));
+		writer.add(eventFactory.createStartElement("",  TEI_NS, "fileDesc"));
+		writer.add(eventFactory.createStartElement("",  TEI_NS, "titleStmt"));
+		for (final String title : object.getGeneric().getProvided().getTitle()) {
+			writeSimpleTEIElement("title", title);
+		}
+		if (object.getEdition() != null) {
+			for (AgentType agent : object.getEdition().getAgent()) {
+				writer.add(eventFactory.createStartElement("",  TEI_NS, "respStmt"));
+				writeSimpleTEIElement("resp", agent.getRole().toString());
+				writer.add(eventFactory.createStartElement("",  TEI_NS, "author"));
+				if (agent.getId() != null)
+					writer.add(eventFactory.createAttribute("key", agent.getId()));
+				writer.add(eventFactory.createCharacters(agent.getValue()));
+				writer.add(eventFactory.createEndElement("",  TEI_NS, "author"));
+				writer.add(eventFactory.createEndElement("",  TEI_NS, "respStmt"));
+			}
+		}
+		writer.add(eventFactory.createEndElement("",  TEI_NS, "titleStmt"));
+		writer.add(eventFactory.createStartElement("",  TEI_NS, "publicationStmt"));
+		writer.add(eventFactory.createStartElement("",  TEI_NS, "idno"));
+		writer.add(eventFactory.createAttribute("type", "textgrid"));
+		writer.add(eventFactory.createCharacters(object.getGeneric().getGenerated().getTextgridUri().getValue()));
+		writer.add(eventFactory.createEndElement("",  TEI_NS, "idno"));
+		writer.add(eventFactory.createEndElement("",  TEI_NS, "publicationStmt"));
+		writer.add(eventFactory.createEndElement("",  TEI_NS, "fileDesc"));
+		writer.add(eventFactory.createEndElement("",  TEI_NS, "teiHeader"));
+	}
+
+	private void writeSimpleTEIElement(String element, final String content) throws XMLStreamException {
+		writer.add(eventFactory.createStartElement("",  TEI_NS, element));
+		writer.add(eventFactory.createCharacters(content));
+		writer.add(eventFactory.createEndElement("",  TEI_NS, element));
+	}
+
+	private void writeXML(final OutputStream output, final ObjectType object) {
+		logger.fine("Processing XML " + toString(object));
+		final Holder<MetadataContainerType> mdHolder = new Holder<MetadataContainerType>();
+		final Holder<DataHandler> dhHolder = new Holder<DataHandler>();
+		try {
+			teiCorpus.getCRUDService().read("", "", object.getGeneric().getGenerated().getTextgridUri().getValue(), mdHolder,
+					dhHolder);
+			final XMLEventReader reader = inputFactory.createFilteredReader(
+					inputFactory.createXMLEventReader(dhHolder.value.getInputStream()),
+					NO_DOCUMENT_NODE);
+
+			// skip to document node:
+			while (reader.hasNext() && !reader.peek().isStartElement())
+				reader.next();
+
+			writer.add(reader);
+		} catch (final ObjectNotFoundFault e) {
+			throw new WebApplicationException(e, Response.Status.NOT_FOUND);
+		} catch (final MetadataParseFault e) {
+			throw new WebApplicationException(e);
+		} catch (final IoFault e) {
+			throw new WebApplicationException(e);
+		} catch (final ProtocolNotImplementedFault e) {
+			throw new WebApplicationException(e);
+		} catch (final AuthFault e) {
+			throw new WebApplicationException(e, Response.Status.FORBIDDEN);
+		} catch (final XMLStreamException e) {
+			throw new WebApplicationException(e);
+		} catch (final IOException e) {
+			throw new WebApplicationException(e, Response.Status.SERVICE_UNAVAILABLE);
+		}
+	}
+
+	/**
+	 * Writes a comment for objects that are skipped.
+	 */
+	private void writeSkip(final OutputStream output, final ObjectType object) throws XMLStreamException {
+		logger.fine("Skipped " + toString(object));
+		writer.add(eventFactory.createComment(MessageFormat.format("Skipped {0}.", toString(object))));
+	}
+
+}
diff --git a/src/main/webapp/WEB-INF/beans.xml b/src/main/webapp/WEB-INF/beans.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5499619b6f729d70ddf7fb04bfe0111bc509f665
--- /dev/null
+++ b/src/main/webapp/WEB-INF/beans.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns:jaxrs="http://cxf.apache.org/jaxrs"
+  xmlns:context="http://www.springframework.org/schema/context"
+  xsi:schemaLocation="
+http://www.springframework.org/schema/beans 
+http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
+http://cxf.apache.org/jaxrs
+http://cxf.apache.org/schemas/jaxrs.xsd">
+
+  <import resource="classpath:META-INF/cxf/cxf.xml" />
+  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
+  <context:property-placeholder/>
+  <context:annotation-config/>
+  <bean class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer"/>
+  <bean class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer"/>
+
+
+   <jaxrs:server id="services" address="/">
+    <jaxrs:serviceBeans>
+      <!-- bean class="info.textgrid.services.aggregator.HelloWorld" /-->
+      <bean class="info.textgrid.services.aggregator.TEICorpus" scope="singleton" />
+    </jaxrs:serviceBeans>
+    <jaxrs:providers>
+        <!-- bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/-->
+        <bean class="org.apache.cxf.jaxrs.impl.WebApplicationExceptionMapper"/>        
+    </jaxrs:providers>
+    
+    </jaxrs:server>
+
+</beans>
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b6dd67f341e6e18b5e5ce5cfa951364d10e046dd
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<web-app 
+    xmlns="http://java.sun.com/xml/ns/j2ee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+    version="2.4">
+	<display-name>TextGrid Aggregator Service</display-name>
+	<description>A service that extracts TextGrid data by aggregations,
+	    recursively</description>
+
+	<context-param>
+		<param-name>contextConfigLocation</param-name>
+		<param-value>WEB-INF/beans.xml</param-value>
+	</context-param>
+
+	<listener>
+		<listener-class>
+			org.springframework.web.context.ContextLoaderListener
+		</listener-class>
+	</listener>
+
+	<servlet>
+		<servlet-name>CXFServlet</servlet-name>
+		<servlet-class>
+			org.apache.cxf.transport.servlet.CXFServlet
+		</servlet-class>
+		<load-on-startup>1</load-on-startup>
+	</servlet>
+
+	<servlet-mapping>
+		<servlet-name>CXFServlet</servlet-name>
+		<url-pattern>/*</url-pattern>
+	</servlet-mapping>
+</web-app>
diff --git a/src/test/java/info/textgrid/services/aggregator/HelloWorldIT.java b/src/test/java/info/textgrid/services/aggregator/HelloWorldIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..40ada9ede2dfdae8a7ff95f07bff4af07dffff3b
--- /dev/null
+++ b/src/test/java/info/textgrid/services/aggregator/HelloWorldIT.java
@@ -0,0 +1,49 @@
+package info.textgrid.services.aggregator;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.map.MappingJsonFactory;
+
+public class HelloWorldIT {
+	private static String endpointUrl;
+
+	// @BeforeClass
+	public static void beforeClass() {
+		endpointUrl = System.getProperty("service.url");
+	}
+
+	// @Test
+	public void testPing() throws Exception {
+		final WebClient client = WebClient.create(endpointUrl + "/hello/echo/SierraTangoNevada");
+		final Response r = client.accept("text/plain").get();
+		assertEquals(Response.Status.OK.getStatusCode(), r.getStatus());
+		final String value = IOUtils.toString((InputStream)r.getEntity());
+		assertEquals("SierraTangoNevada", value);
+	}
+
+	// @Test
+	public void testJsonRoundtrip() throws Exception {
+		final List<Object> providers = new ArrayList<Object>();
+		providers.add(new org.codehaus.jackson.jaxrs.JacksonJsonProvider());
+		final JsonBean inputBean = new JsonBean();
+		inputBean.setVal1("Maple");
+		final WebClient client = WebClient.create(endpointUrl + "/hello/jsonBean", providers);
+		final Response r = client.accept("application/json")
+				.type("application/json")
+				.post(inputBean);
+		assertEquals(Response.Status.OK.getStatusCode(), r.getStatus());
+		final MappingJsonFactory factory = new MappingJsonFactory();
+		final JsonParser parser = factory.createJsonParser((InputStream)r.getEntity());
+		final JsonBean output = parser.readValueAs(JsonBean.class);
+		assertEquals("Maple", output.getVal2());
+	}
+}
diff --git a/src/test/java/info/textgrid/services/aggregator/TEICorpusTest.java b/src/test/java/info/textgrid/services/aggregator/TEICorpusTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f635f062e89fb22b5a92ea901e5a74e561a0d60f
--- /dev/null
+++ b/src/test/java/info/textgrid/services/aggregator/TEICorpusTest.java
@@ -0,0 +1,41 @@
+package info.textgrid.services.aggregator;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import info.textgrid.middleware.confclient.ConfservClientConstants;
+import info.textgrid.middleware.tgsearch.client.SearchClient;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.TGCrudService;
+
+import org.junit.Test;
+
+public class TEICorpusTest {
+
+	private TEICorpus teicorpus;
+
+	TEICorpus getTeiCorpus() {
+		if (teicorpus == null)
+			teicorpus = new TEICorpus();
+		return teicorpus;
+	}
+
+	@Test
+	public void testGetConfValue() {
+		final String confValue = getTeiCorpus().getConfValue(ConfservClientConstants.TG_SEARCH_PUBLIC);
+		assertNotNull("Configuration value is null.", confValue);
+		assertEquals("https://textgridlab.org/1.0/tgsearch-public", confValue);
+	}
+
+	@Test
+	public void testGetCRUDService() {
+		final TGCrudService crudService = getTeiCorpus().getCRUDService();
+		assertNotNull("TG-crud service is null!?", crudService);
+	}
+
+	@Test
+	public void testGetSearchClient() {
+		final SearchClient searchClient = getTeiCorpus().getSearchClient();
+		assertNotNull("TG-search service is null!?", searchClient);
+	}
+
+
+}