diff --git a/src/main/java/info/textgrid/services/aggregator/zip/ZIP.java b/src/main/java/info/textgrid/services/aggregator/zip/ZIP.java
index bb97193e8452268ed65b7f9a78615b44af46cde7..9007b62bf8b538a9089c7877957830306d83dc5f 100644
--- a/src/main/java/info/textgrid/services/aggregator/zip/ZIP.java
+++ b/src/main/java/info/textgrid/services/aggregator/zip/ZIP.java
@@ -1,20 +1,14 @@
 package info.textgrid.services.aggregator.zip;
 
-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.TGCrudService;
-import info.textgrid.services.aggregator.ArgUtils;
+import info.textgrid.namespaces.middleware.tgcrud.services.tgcrudservice.ProtocolNotImplementedFault;
 import info.textgrid.services.aggregator.ITextGridRep;
-import info.textgrid.services.aggregator.RESTUtils;
-import info.textgrid.utils.export.filenames.DefaultFilenamePolicy;
 
-import java.util.Date;
-import java.util.logging.Logger;
+import java.io.IOException;
 
-import javax.servlet.ServletContext;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
@@ -23,17 +17,14 @@
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Request;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
+
+import net.sf.saxon.s9api.SaxonApiException;
 
 import org.apache.cxf.jaxrs.model.wadl.Description;
 
 @Path("/zip")
 public class ZIP {
 	private final ITextGridRep repository;
-	final Logger logger = Logger.getLogger(ZIP.class.getCanonicalName());
-
-	@Context
-	private ServletContext servlet;
 
 	public ZIP(final ITextGridRep repository) {
 		this.repository = repository;
@@ -49,42 +40,14 @@ public Response get(
 			@QueryParam("sid") final String sid,
 			@Description("(optional) title for the exported data, currently only used for generating the filename. If none is given, the first title of the first object will be used.")
 			@QueryParam("title") String title,
-			@Context final Request request) throws MetadataParseFault, ObjectNotFoundFault, IoFault, AuthFault {
-
-		final TGCrudService crud = repository.getCRUDService();
-
-		if (uriList == null)
-			throw new IllegalArgumentException("Specify at least one URI to zip");
-
-		final ObjectType[] objects = ArgUtils.extractRootObjects(uriList, sid, crud);
-		final Date lastModified = RESTUtils.createLastModified(objects);
-		if (request != null) {
-			ResponseBuilder builder = request.evaluatePreconditions(lastModified);
-			if (builder != null)
-				return builder.build();
-		}
-
-		if (title == null) {
-			title = objects[0].getGeneric().getProvided().getTitle().get(0);
-		}
-
-		ResponseBuilder builder = RESTUtils
-				.attachmentResponse(DefaultFilenamePolicy.INSTANCE.translate(title) + ".zip")
-				.type("application/zip");
-		RESTUtils.configureCache(builder, lastModified, sid != null);
-		return builder
-				.entity(new ZipResult(repository, sid, objects))
-				.build();
+			@Context final Request request) throws MetadataParseFault, ObjectNotFoundFault, IoFault, AuthFault, ProtocolNotImplementedFault, IOException, SaxonApiException {
+		
+		ZipResult zipResult = new ZipResult(repository, request, uriList);
+		if (title != null)
+			zipResult.setTitle(title);
+		if (sid != null)
+			zipResult.sid(sid);
+		return zipResult.createResponse().build();
 	}
 
-//	private void appendLS(final Aggregation aggregation, final StringBuilder output) {
-//		output.append(aggregation.getFileName(true)).append(":\n");
-//		for(final IAggregationEntry child : aggregation.getChildren())
-//			if (child instanceof Aggregation) {
-//				appendLS((Aggregation) child, output);
-//			} else {
-//				output.append(' ').append(child.getFileName(true)).append('\n');
-//			}
-//	}
-
 }
diff --git a/src/main/java/info/textgrid/services/aggregator/zip/ZipResult.java b/src/main/java/info/textgrid/services/aggregator/zip/ZipResult.java
index 1bdbae5a4ac4d67e9922438c5ad55d44c2c53128..022c4634849dbb09f6a4095577bcc6c94be5b468 100644
--- a/src/main/java/info/textgrid/services/aggregator/zip/ZipResult.java
+++ b/src/main/java/info/textgrid/services/aggregator/zip/ZipResult.java
@@ -3,6 +3,11 @@
 import info.textgrid._import.ImportObject;
 import info.textgrid._import.RewriteMethod;
 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.services.aggregator.AbstractExporter;
 import info.textgrid.services.aggregator.ITextGridRep;
 import info.textgrid.services.aggregator.tree.AggregationTreeFactory;
 import info.textgrid.utils.export.aggregations.AggregationEntry;
@@ -27,6 +32,8 @@
 import java.util.zip.ZipOutputStream;
 
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.StreamingOutput;
 import javax.xml.bind.JAXB;
 import javax.xml.stream.XMLStreamException;
@@ -42,110 +49,136 @@
 import com.google.common.io.ByteStreams;
 import com.google.common.io.FileBackedOutputStream;
 
-public class ZipResult implements StreamingOutput {
+public class ZipResult extends AbstractExporter implements StreamingOutput {
 
-	private final ObjectType[] rootObjects;
-	private final ITextGridRep repository;
-	private final String sid;
 	private final IFilenamePolicy policy;
 	private final IFilenamePolicy metaPolicy;
 	private ImportMapping mapping;
-	private final static Logger logger = Logger.getLogger(ZipResult.class.getCanonicalName());
-	
-	private final Set<URI> written = Sets.newHashSet();
+	private final static Logger logger = Logger.getLogger(ZipResult.class
+			.getCanonicalName());
 
+	private final Set<URI> written = Sets.newHashSet();
 
 	private static final Function<ObjectType, String> GetURI = new Function<ObjectType, String>() {
 
 		@Override
 		public String apply(final ObjectType input) {
-				return input.getGeneric().getGenerated().getTextgridUri().getValue();
+			return input.getGeneric().getGenerated().getTextgridUri()
+					.getValue();
 		}
 
 	};
 
-
-	public ZipResult(final ITextGridRep repository, final String sid, final ObjectType... rootObjects) {
-		this.rootObjects = rootObjects;
-		this.repository = repository;
-		this.sid = sid;
+	public ZipResult(final ITextGridRep repository, final Request request,
+			final String uriList) {
+		super(repository, request, uriList);
 		this.policy = new DefaultFilenamePolicy();
 		this.metaPolicy = new DefaultMetaFilenamePolicy(policy);
+		setFileExtension("zip");
+		setMediaType("application/zip");
 	}
 
 	@Override
 	public void write(final OutputStream output) throws IOException,
 			WebApplicationException {
-		final long startTime = System.currentTimeMillis();
-		final String uriList = Joiner.on(", ").join(Iterators.transform(Iterators.forArray(rootObjects), GetURI));
-		logger.log(Level.INFO, "Starting ZIP export of {0}", uriList);
 		final ZipOutputStream zip = new ZipOutputStream(output);
-		zip.setComment(MessageFormat.format("# Exported from TextGrid -- www.textgrid.de\nRoot objects: {0}", uriList));
+		try {
+			ObjectType[] rootObjects = getRootObjects();
+			final String uriList = Joiner.on(", ")
+					.join(Iterators.transform(Iterators.forArray(rootObjects),
+							GetURI));
+			logger.log(Level.INFO, MessageFormat.format(
+					"Starting ZIP export of {0} after {1}", uriList, stopwatch));
+			zip.setComment(MessageFormat
+					.format("# Exported from TextGrid -- www.textgrid.de\nRoot objects: {0}",
+							uriList));
 
-		mapping = new ImportMapping();
+			mapping = new ImportMapping();
 
-		final List<IAggregationEntry> roots = Lists.newArrayListWithCapacity(rootObjects.length);
-		for (final ObjectType rootMetadata : rootObjects) {
-			final IAggregationEntry entry;
-			if (rootMetadata.getGeneric().getProvided().getFormat().contains("aggregation")) {
-				entry = AggregationTreeFactory.create(rootMetadata, repository, sid);
-			} else {
-				entry = new AggregationEntry(rootMetadata, null);
+			final List<IAggregationEntry> roots = Lists
+					.newArrayListWithCapacity(rootObjects.length);
+			for (final ObjectType rootMetadata : rootObjects) {
+				final IAggregationEntry entry;
+				if (rootMetadata.getGeneric().getProvided().getFormat()
+						.contains("aggregation")) {
+					entry = AggregationTreeFactory.create(rootMetadata,
+							repository, getSid().orNull());
+				} else {
+					entry = new AggregationEntry(rootMetadata, null);
+				}
+				logger.log(Level.INFO, MessageFormat.format(
+						"  Built aggregation tree for {1} after {0}",
+						stopwatch, GetURI.apply(rootMetadata)));
+				roots.add(entry);
+				addToMapping(mapping, entry);
 			}
-			logger.log(Level.INFO, MessageFormat.format("  Built aggregation tree for {1} after {0,number} ms",
-					System.currentTimeMillis() - startTime, GetURI.apply(rootMetadata)));
-			roots.add(entry);
-			addToMapping(mapping, entry);
-		}
-
-		for (final IAggregationEntry root : roots) {
-			if (root instanceof IAggregation)
-				writeAggregation(zip, (IAggregation) root);
-			else
-				writeFile(zip, root);
-			logger.log(Level.INFO, MessageFormat.format("  Zipped {1} after {0,number} ms",
-					System.currentTimeMillis() - startTime, root.getTextGridURI()));
-		}
 
+			for (final IAggregationEntry root : roots) {
+				if (root instanceof IAggregation)
+					writeAggregation(zip, (IAggregation) root);
+				else
+					writeFile(zip, root);
+				logger.log(Level.INFO, MessageFormat.format(
+						"  Zipped {1} after {0}", stopwatch,
+						root.getTextGridURI()));
+			}
 
-		// now serializing the mapping
-		zip.putNextEntry(new ZipEntry(".INDEX.imex"));
-		JAXB.marshal(mapping.toImportSpec(), zip);
-		zip.closeEntry();
-		zip.close();
-		logger.log(Level.INFO, MessageFormat.format(
-				"Finished exporting after {0,number} ms",
-				System.currentTimeMillis() - startTime));
+			// now serializing the mapping
+			zip.putNextEntry(new ZipEntry(".INDEX.imex"));
+			JAXB.marshal(mapping.toImportSpec(), zip);
+			zip.closeEntry();
+		} catch (MetadataParseFault e) {
+			throw new WebApplicationException(e);
+		} catch (ObjectNotFoundFault e) {
+			throw new WebApplicationException(e, Status.NOT_FOUND);
+		} catch (IoFault e) {
+			throw new WebApplicationException(e);
+		} catch (AuthFault e) {
+			throw new WebApplicationException(e, Status.FORBIDDEN);
+		} finally {
+			zip.close();
+			stopwatch.stop();
+		}
+		logger.log(Level.INFO,
+				MessageFormat.format("Finished exporting after {0}", stopwatch));
 	}
 
 	// FIXME refactor -> Rewrite library
 	private static ImmutableMap<String, String> REWRITE_CONFIGS = null;
+
 	private static Optional<String> getRewriteConfig(final String contentType) {
 		if (REWRITE_CONFIGS == null) {
-			REWRITE_CONFIGS = ImmutableMap.<String, String>builder()
-					.put("text/tg.aggregation+xml", "internal:textgrid#aggregation")
-					.put("text/tg.edition+tg.aggregation+xml", "internal:textgrid#aggregation")
-					.put("text/tg.collection+tg.aggregation+xml", "internal:textgrid#aggregation")
+			REWRITE_CONFIGS = ImmutableMap
+					.<String, String> builder()
+					.put("text/tg.aggregation+xml",
+							"internal:textgrid#aggregation")
+					.put("text/tg.edition+tg.aggregation+xml",
+							"internal:textgrid#aggregation")
+					.put("text/tg.collection+tg.aggregation+xml",
+							"internal:textgrid#aggregation")
 					.put("text/xsd+xml", "internal:schema#xsd")
 					.put("text/linkeditorlinkedfile", "internal:tei#tei")
 					.put("text/xml", "internal:tei#tei")
-					.put("application/xhtml+xml", "internal:html#html")
-					.build();
+					.put("application/xhtml+xml", "internal:html#html").build();
 		}
 		return Optional.fromNullable(REWRITE_CONFIGS.get(contentType));
 	}
 
 	/**
 	 * Recursively adds the entry to the mapping.
+	 * 
 	 * @param mapping
 	 * @param entry
 	 */
-	private void addToMapping(final ImportMapping mapping, final IAggregationEntry entry) {
-		final Optional<String> rewriteConfig = getRewriteConfig(((AggregationEntry) entry).getFormat());
+	private void addToMapping(final ImportMapping mapping,
+			final IAggregationEntry entry) {
+		final Optional<String> rewriteConfig = getRewriteConfig(((AggregationEntry) entry)
+				.getFormat());
 		final ImportObject importObject = new ImportObject();
 		importObject.setTextgridUri(entry.getTextGridURI().toString());
 		importObject.setLocalData(policy.getFilename(entry, false).toString());
-		importObject.setLocalMetadata(metaPolicy.getFilename(entry, false).toString());
+		importObject.setLocalMetadata(metaPolicy.getFilename(entry, false)
+				.toString());
 		if (rewriteConfig.isPresent()) {
 			importObject.setRewriteMethod(RewriteMethod.XML);
 			importObject.setRewriteConfig(rewriteConfig.get());
@@ -162,16 +195,19 @@ private void addToMapping(final ImportMapping mapping, final IAggregationEntry e
 		}
 	}
 
-	private void writeAggregation(final ZipOutputStream zip, final IAggregation root) throws IOException {
+	private void writeAggregation(final ZipOutputStream zip,
+			final IAggregation root) throws IOException {
 		final URI uri = root.getTextGridURI();
 		if (written.contains(uri)) {
 			logger.log(Level.WARNING, "Skipping duplicate aggregation {0}", uri);
 			return;
 		}
 		writeFile(zip, root);
-		final ZipEntry zipEntry = new ZipEntry(policy.getFilename(root, true).toString());
+		final ZipEntry zipEntry = new ZipEntry(policy.getFilename(root, true)
+				.toString());
 		zip.putNextEntry(zipEntry);
-		zipEntry.setTime(root.getMetadata().getGeneric().getGenerated().getLastModified().toGregorianCalendar().getTimeInMillis());
+		zipEntry.setTime(root.getMetadata().getGeneric().getGenerated()
+				.getLastModified().toGregorianCalendar().getTimeInMillis());
 		zip.closeEntry();
 
 		for (final IAggregationEntry child : root.getChildren()) {
@@ -183,7 +219,8 @@ private void writeAggregation(final ZipOutputStream zip, final IAggregation root
 		}
 	}
 
-	private void writeFile(final ZipOutputStream zip, final IAggregationEntry child) throws IOException {
+	private void writeFile(final ZipOutputStream zip,
+			final IAggregationEntry child) throws IOException {
 		URI uri = child.getTextGridURI();
 		if (written.contains(uri)) {
 			logger.log(Level.WARNING, "Skipping duplicate object {0}", uri);
@@ -191,34 +228,42 @@ private void writeFile(final ZipOutputStream zip, final IAggregationEntry child)
 		}
 		written.add(uri);
 		writeMetadata(zip, child);
-		final ZipEntry zipEntry = new ZipEntry(policy.getFilename(child, false).toString());
-		zipEntry.setTime(child.getMetadata().getGeneric().getGenerated().getLastModified().toGregorianCalendar().getTimeInMillis());
+		final ZipEntry zipEntry = new ZipEntry(policy.getFilename(child, false)
+				.toString());
+		zipEntry.setTime(child.getMetadata().getGeneric().getGenerated()
+				.getLastModified().toGregorianCalendar().getTimeInMillis());
 
-		final ImportObject importObject = mapping.getImportObjectForTextGridURI(child.getTextGridURI().toString());
+		final ImportObject importObject = mapping
+				.getImportObjectForTextGridURI(child.getTextGridURI()
+						.toString());
 		try {
-			final InputStream content = repository.getContent(child.getTextGridURI(), sid);
+			final InputStream content = repository.getContent(
+					child.getTextGridURI(), getSid().orNull());
 			if (importObject.getRewriteMethod().equals(RewriteMethod.XML)) {
-				final ConfigurableXMLRewriter rewriter = new ConfigurableXMLRewriter(mapping, true);
+				final ConfigurableXMLRewriter rewriter = new ConfigurableXMLRewriter(
+						mapping, true);
 				rewriter.configure(URI.create(importObject.getRewriteConfig()));
 				final Optional<URI> base = policy.getBase(child);
 				if (base.isPresent()) {
 					rewriter.setBase(base.get());
 				}
-				final FileBackedOutputStream buffer = new FileBackedOutputStream(1024 * 1024);
+				final FileBackedOutputStream buffer = new FileBackedOutputStream(
+						1024 * 1024);
 				try {
 					rewriter.rewrite(content, buffer);
 					zip.putNextEntry(zipEntry);
 					ByteStreams.copy(buffer.getSupplier(), zip);
 				} catch (final XMLStreamException e) {
-					final String errorMsg = MessageFormat.format("Failed to rewrite {0} (error: {1}). Exported with verbatim links instead.", child, e.getMessage());
+					final String errorMsg = MessageFormat
+							.format("Failed to rewrite {0} (error: {1}). Exported with verbatim links instead.",
+									child, e.getMessage());
 					logger.log(Level.WARNING, errorMsg, e);
 					zipEntry.setComment(errorMsg);
 					importObject.setRewriteMethod(RewriteMethod.NONE);
 					zip.putNextEntry(zipEntry);
 					ByteStreams.copy(buffer.getSupplier(), zip);
 				}
-			}
-			else {
+			} else {
 				zip.putNextEntry(zipEntry);
 				ByteStreams.copy(content, zip);
 			}
@@ -229,23 +274,30 @@ private void writeFile(final ZipOutputStream zip, final IAggregationEntry child)
 		}
 	}
 
-	private void writeMetadata(final ZipOutputStream zip, final IAggregationEntry child) throws IOException {
-		final ZipEntry zipEntry = new ZipEntry(metaPolicy.getFilename(child, false).toString());
-		zipEntry.setTime(child.getMetadata().getGeneric().getGenerated().getLastModified().toGregorianCalendar().getTimeInMillis());
+	private void writeMetadata(final ZipOutputStream zip,
+			final IAggregationEntry child) throws IOException {
+		final ZipEntry zipEntry = new ZipEntry(metaPolicy.getFilename(child,
+				false).toString());
+		zipEntry.setTime(child.getMetadata().getGeneric().getGenerated()
+				.getLastModified().toGregorianCalendar().getTimeInMillis());
 		zip.putNextEntry(zipEntry);
-		final ConfigurableXMLRewriter rewriter = new ConfigurableXMLRewriter(mapping, true);
+		final ConfigurableXMLRewriter rewriter = new ConfigurableXMLRewriter(
+				mapping, true);
 		rewriter.configure(URI.create("internal:textgrid#metadata"));
 		final Optional<URI> base = metaPolicy.getBase(child);
 		if (base.isPresent()) {
 			rewriter.setBase(base.get());
 		}
 
-		final FileBackedOutputStream buffer = new FileBackedOutputStream(1024*1024);
+		final FileBackedOutputStream buffer = new FileBackedOutputStream(
+				1024 * 1024);
 		JAXB.marshal(child.getMetadata(), buffer);
 		try {
 			rewriter.rewrite(buffer.getSupplier().getInput(), zip);
 		} catch (final XMLStreamException e) {
-			logger.log(Level.SEVERE, MessageFormat.format("Error rewriting the metadata of {0}. Should not happen.", child), e);
+			logger.log(Level.SEVERE, MessageFormat.format(
+					"Error rewriting the metadata of {0}. Should not happen.",
+					child), e);
 		}
 		zip.closeEntry();
 	}