Anonymous avatar Anonymous committed 3d4d9f9

Rename package

Comments (0)

Files changed (33)

chempound-webapp/src/main/java/net/chempound/webapp/ChempoundApplication.java

 
 import com.google.inject.Injector;
 import net.chempound.config.BaseUri;
-import net.chempound.webapp.content.ContentRouter;
+import net.chempound.webapp.data.ContentRouter;
 import net.chempound.webapp.feeds.FeedRouter;
 import net.chempound.webapp.guice.GuiceRouter;
 import net.chempound.webapp.pingback.PingbackResource;

chempound-webapp/src/main/java/net/chempound/webapp/content/AggregationResource.java

-package net.chempound.webapp.content;
-
-import net.chempound.datastore.TripleStore;
-import net.chempound.util.MimeType;
-import net.chempound.webapp.splashpage.SplashPageGenerator;
-import org.json.JSONObject;
-import org.restlet.data.MediaType;
-import org.restlet.ext.json.JsonRepresentation;
-import org.restlet.representation.Representation;
-import org.restlet.representation.Variant;
-import org.restlet.resource.Get;
-import org.restlet.resource.ServerResource;
-
-import javax.inject.Inject;
-import java.net.URI;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * @author sea36
- */
-public class AggregationResource extends ServerResource {
-
-    private final TripleStore tripleStore;
-    private final SplashPageGenerator splashPageGenerator;
-    private final ResourceMapGenerator resourceMapGenerator;
-    private final JsonGenerator jsonGenerator;
-    private final PingbackProcessor pingbackProcessor;
-
-    private URI aggregationUri;
-    private Map<MimeType, URI> typeMap;
-
-    @Inject
-    public AggregationResource(final TripleStore tripleStore, final SplashPageGenerator splashPageGenerator, final ResourceMapGenerator resourceMapGenerator, final JsonGenerator jsonGenerator, final PingbackProcessor pingbackProcessor) {
-        this.tripleStore = tripleStore;
-        this.splashPageGenerator = splashPageGenerator;
-        this.resourceMapGenerator = resourceMapGenerator;
-        this.jsonGenerator = jsonGenerator;
-        this.pingbackProcessor = pingbackProcessor;
-    }
-
-    @Override
-    protected void doInit() {
-        aggregationUri = getReference().toUri();
-
-        typeMap = tripleStore.getResourceVariants(aggregationUri);
-        if (typeMap.containsKey(MimeType.TEXT_HTML) && !typeMap.containsKey(MimeType.APPLICATION_XHTML)) {
-            typeMap.put(MimeType.APPLICATION_XHTML, typeMap.get(MimeType.TEXT_HTML));
-        }
-
-        // Hack to ensure 'default' representation is text/html
-        if (getClientInfo().getAcceptedMediaTypes().isEmpty() ||
-                (getClientInfo().getAcceptedMediaTypes().size() == 1 &&
-                    MediaType.ALL.equals(getClientInfo().getAcceptedMediaTypes().get(0).getMetadata()))) {
-            for (final Iterator<Variant> it = getVariants().iterator(); it.hasNext();) {
-                final MediaType mediaType = it.next().getMediaType();
-                if (!MediaType.TEXT_HTML.equals(mediaType)) {
-                    it.remove();
-                }
-            }
-        }
-    }
-
-    @Get("html")
-    public Representation getHtml() throws Exception {
-        final Representation representation = splashPageGenerator.createSplashPage(aggregationUri);
-        addContentLocation(representation, MediaType.TEXT_HTML);
-        pingbackProcessor.addPingbackHeader(getResponse());
-        return representation;
-    }
-
-    @Get("xhtml")
-    public Representation getXHtml() throws Exception {
-        final Representation representation = splashPageGenerator.createSplashPage(aggregationUri);
-        addContentLocation(representation, MediaType.APPLICATION_XHTML);
-        pingbackProcessor.addPingbackHeader(getResponse());
-        return representation;
-    }
-
-    @Get("json")
-    public Representation getJson() throws Exception {
-        final JSONObject json = jsonGenerator.createJson(aggregationUri);
-        final JsonRepresentation representation = new JsonRepresentation(json);
-        representation.setIndenting(true);
-        representation.setIndentingSize(2);
-        addContentLocation(representation, MediaType.APPLICATION_JSON);
-        return representation;
-    }
-
-    @Get("rdf")
-    public Representation getRdfXml() {
-        final Representation representation = resourceMapGenerator.getRdfXml(aggregationUri);
-        addContentLocation(representation, MediaType.APPLICATION_RDF_XML);
-        return representation;
-    }
-
-    @Get("ttl")
-    public Representation getRdfTurtle() {
-        final Representation representation = resourceMapGenerator.getTurtle(aggregationUri);
-        addContentLocation(representation, MediaType.APPLICATION_RDF_TURTLE);
-        return representation;
-    }
-
-    @Get("n3")
-    public Representation getRdfN3() {
-        final Representation representation = resourceMapGenerator.getNotation3(aggregationUri);
-        addContentLocation(representation, MediaType.TEXT_RDF_N3);
-        return representation;
-    }
-
-    @Get("nt")
-    public Representation getRdfNTriples() {
-        final Representation representation = resourceMapGenerator.getNTriples(aggregationUri);
-        addContentLocation(representation, MediaType.TEXT_RDF_NTRIPLES);
-        return representation;
-    }
-
-    private void addContentLocation(final Representation representation, final MediaType mediaType) {
-        final MimeType mimeType = new MimeType(mediaType.getName());
-        if (typeMap.containsKey(mimeType)) {
-            final URI uri = typeMap.get(mimeType);
-            representation.setLocationRef(uri.toString());
-        }
-    }
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/CollectionResource.java

-package net.chempound.webapp.content;
-
-import net.chempound.datastore.TripleStore;
-import net.chempound.webapp.Restlet;
-import net.chempound.webapp.splashpage.SplashPageGenerator;
-import net.chempound.webapp.sword.AbderaRepresentation;
-import org.apache.abdera.Abdera;
-import org.apache.abdera.i18n.iri.IRI;
-import org.apache.abdera.model.Document;
-import org.apache.abdera.model.Entry;
-import org.apache.abdera.parser.Parser;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.fileupload.FileItemHeaders;
-import org.apache.commons.fileupload.FileItemIterator;
-import org.apache.commons.fileupload.FileItemStream;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.io.IOUtils;
-import org.restlet.data.MediaType;
-import org.restlet.data.Status;
-import org.restlet.engine.header.Header;
-import org.restlet.ext.fileupload.RestletFileUpload;
-import org.restlet.representation.EmptyRepresentation;
-import org.restlet.representation.Representation;
-import org.restlet.resource.Post;
-import org.restlet.util.Series;
-import org.swordapp.server.*;
-
-import javax.inject.Inject;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import static org.restlet.data.MediaType.APPLICATION_ATOM;
-
-/**
- * @author Sam Adams
- */
-public class CollectionResource extends AggregationResource {
-
-    public static final MediaType APPLICATION_ATOM_FEED = MediaType.register(
-        "application/atom+xml;type=feed", "Atom feed document");
-
-    public static final MediaType APPLICATION_ATOM_ENTRY = MediaType.register(
-        "application/atom+xml;type=entry", "Atom entry document");
-
-    public static final MediaType MULTIPART_RELATED = MediaType.register(
-        "multipart/related", "Multipart related data");
-
-    private static final boolean IGNORE_CASE = true;
-
-    private final SwordConfiguration configuration;
-    private final CollectionDepositManager depositManager;
-
-    @Inject
-    public CollectionResource(final TripleStore tripleStore, final SwordConfiguration configuration, final CollectionDepositManager depositManager, final SplashPageGenerator splashPageGenerator, final JsonGenerator jsonGenerator, final ResourceMapGenerator resourceMapGenerator, final PingbackProcessor pingbackProcessor) {
-        super(tripleStore, splashPageGenerator, resourceMapGenerator, jsonGenerator, pingbackProcessor);
-
-        this.configuration = configuration;
-        this.depositManager = depositManager;
-    }
-
-    @Post("*:atom")
-    public Representation handleDeposit(final Representation data) throws SwordError, SwordServerException, IOException, FileUploadException, SwordAuthException {
-
-//        if (data.getSize() == Representation.UNKNOWN_SIZE) {
-//            setStatus(Status.CLIENT_ERROR_LENGTH_REQUIRED);
-//            return null;
-//        }
-
-        final AuthCredentials auth = null;
-
-        final Deposit deposit = new Deposit();
-        deposit.setSlug(getSlug());
-        deposit.setInProgress(isInProgress());
-
-        getLogger().info("MIME type: " + data.getMediaType());
-
-        final MediaType mediaType = data.getMediaType();
-        if (mediaType != null && MULTIPART_RELATED.includes(mediaType)) {
-            getLogger().info("Performing multipart deposit");
-            handleMultipartDeposit(deposit);
-        }
-        else if (mediaType != null && APPLICATION_ATOM.includes(mediaType)) {
-            getLogger().info("Performing atom entry deposit");
-            handleAtomEntryDeposit(deposit);
-        }
-        else {
-            getLogger().info("Performing binary deposit");
-            handleBinaryDeposit(deposit);
-        }
-
-        getLogger().info("SWORD endpoint: " + getReference());
-
-        final DepositReceipt receipt = depositManager.createNew(getReference().toString(), deposit, auth, configuration);
-        final IRI editIRI = receipt.getEditIRI();
-        if (editIRI == null) {
-            throw new SwordServerException("No Edit-IRI found in Deposit Receipt; unable to send valid response");
-        }
-
-        setStatus(Status.SUCCESS_CREATED);
-        setLocationRef(editIRI.toString());
-        if (configuration.returnDepositReceipt()) {
-            return new AbderaRepresentation(receipt.getAbderaEntry(), APPLICATION_ATOM_ENTRY);
-        } else {
-            return new EmptyRepresentation();
-        }
-
-    }
-
-    private String getSlug() {
-        final Series<Header> headers = Restlet.getHeaders(getRequest());
-        return headers.getFirstValue("Slug", IGNORE_CASE);
-    }
-
-    protected boolean isInProgress() throws SwordError {
-        final Series<Header> headers = Restlet.getHeaders(getRequest());
-        final String iph = headers.getFirstValue("In-Progress", IGNORE_CASE);
-
-        boolean inProgress = false; // default value
-        if (iph != null) {
-            // first of all validate that the value is "true" or "false"
-            if (!"true".equals(iph.trim()) && !"false".equals(iph.trim())) {
-                throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "The In-Progress header MUST be 'true' or 'false'");
-            }
-            inProgress = "true".equals(iph.trim());
-        }
-        return inProgress;
-    }
-
-
-    private void handleMultipartDeposit(final Deposit deposit) throws IOException, FileUploadException, SwordError {
-
-        final RestletFileUpload fileUpload = new RestletFileUpload();
-        final FileItemIterator itr = fileUpload.getItemIterator(getRequestEntity());
-
-        while (itr.hasNext()) {
-
-            final FileItemStream in = itr.next();
-            getLogger().info("Part: " + in.getFieldName() + " [" + in.getContentType() + "]");
-            if ("atom".equals(in.getFieldName())) {
-                getLogger().info("Reading atom entry");
-                final InputStream is = in.openStream();
-                Entry entry;
-                try {
-                    entry = readEntry(is);
-                } finally {
-                    is.close();
-                }
-                deposit.setEntry(entry);
-            }
-            else if ("payload".equals(in.getFieldName())) {
-                getLogger().info("Reading payload");
-
-                final MediaType contentType = getContentType();
-                final FileItemHeaders headers = in.getHeaders();
-                final String packaging = headers.getHeader("Packaging");
-                final String filename = in.getName();
-
-                getLogger().info("Received file [" + filename + "] [" + packaging + "] [" + contentType + "]");
-
-                final InputStream is = in.openStream();
-                try {
-                    byte[] b = IOUtils.toByteArray(is);
-                    // TODO - should check for 'Content-Transfer-Encoding: base64' header,
-                    // but that is not set by SWORD client right now...
-                    final String transferEncoding = headers.getHeader("Content-Transfer-Encoding");
-                    getLogger().info("Transfer encoding: " + transferEncoding);
-                    if (!"binary".equalsIgnoreCase(transferEncoding)) {
-                        final Base64 base64 = new Base64();
-                        b = base64.decode(b);
-                    }
-                    addPayload(deposit, contentType, packaging, filename, new ByteArrayInputStream(b));
-                } finally {
-                    is.close();
-                }
-            }
-            else {
-                throw new SwordError("Unknown part: " + in.getFieldName());
-            }
-        }
-    }
-
-    private void handleAtomEntryDeposit(final Deposit deposit) throws IOException {
-        final InputStream entryPart = getRequestEntity().getStream();
-        final Entry entry = readEntry(entryPart);
-        deposit.setEntry(entry);
-    }
-
-    private Entry readEntry(final InputStream entryPart) throws IOException {
-        final byte[] buffer = IOUtils.toByteArray(entryPart);
-
-        final Abdera abdera = new Abdera();
-        final Parser parser = abdera.getParser();
-        final Document<Entry> entryDoc = parser.parse(new ByteArrayInputStream(buffer));
-        return entryDoc.getRoot();
-    }
-
-    private void handleBinaryDeposit(final Deposit deposit) throws SwordError, IOException {
-        readPayload(deposit);
-    }
-
-    private void readPayload(final Deposit deposit) throws SwordError, IOException {
-        final MediaType contentType = getContentType();
-        final String packaging = getPackaging();
-
-        final String filename = getFilename();
-        if (filename == null || "".equals(filename)) {
-            throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Filename could not be extracted from Content-Disposition header");
-        }
-
-        final InputStream file = getRequestEntity().getStream();
-
-        final byte[] md5 = getMd5();
-
-        addPayload(deposit, contentType, packaging, filename, file);
-    }
-
-    private void addPayload(final Deposit deposit, final MediaType contentType, final String packaging, final String filename, final InputStream inputStream) {
-        getLogger().info("Adding '" + filename + "' to deposit payload");
-        deposit.setFilename(filename);
-//        deposit.setMd5(md5);  TODO to hex
-        deposit.setPackaging(packaging);
-        deposit.setInputStream(inputStream);
-        deposit.setMimeType(contentType.toString());
-    }
-
-    private String getPackaging() {
-        final Series<Header> headers = Restlet.getHeaders(getRequest());
-        String packaging = headers.getFirstValue("Packaging", IGNORE_CASE);
-        if (packaging == null || "".equals(packaging)) {
-            packaging = UriRegistry.PACKAGE_BINARY;
-        }
-        return packaging;
-    }
-
-    private MediaType getContentType() {
-        MediaType contentType = getRequestEntity().getMediaType();
-        if (contentType == null) {
-            // TODO guess!
-            contentType = MediaType.APPLICATION_OCTET_STREAM;
-        }
-        return contentType;
-    }
-
-    private String getFilename() {
-        String filename = null;
-        if (getRequestEntity().getDisposition() != null) {
-            filename = getRequestEntity().getDisposition().getFilename();
-        } else {
-            final Series<Header> headers = Restlet.getHeaders(getRequest());
-            final String cd = headers.getFirstValue("Content-Disposition", IGNORE_CASE);
-            if (cd != null) {
-                final int i = cd.indexOf("filename=");
-                filename = cd.substring(i + 9);
-            }
-        }
-
-        return filename;
-    }
-
-    private byte[] getMd5() {
-        byte[] md5 = null;
-        if (getRequestEntity().getDigest() != null) {
-            if ("MD5".equals(getRequestEntity().getDigest().getAlgorithm())) {
-                md5 = getRequestEntity().getDigest().getValue();
-            }
-        }
-        return md5;
-    }
-
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/ContentResource.java

-package net.chempound.webapp.content;
-
-import com.hp.hpl.jena.rdf.model.ResourceFactory;
-import net.chempound.datastore.TripleStore;
-import org.restlet.data.Reference;
-import org.restlet.resource.ServerResource;
-
-import java.net.URI;
-
-/**
- * @author sea36
- */
-public abstract class ContentResource extends ServerResource {
-
-    protected final TripleStore tripleStore;
-
-    protected ContentResource(final TripleStore tripleStore) {
-        this.tripleStore = tripleStore;
-    }
-
-    protected URI getAggregation(final Reference ref) {
-        return tripleStore.getAggregationUriForResourceMap(ResourceFactory.createResource(ref.toString()));
-    }
-
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/ContentRouter.java

-package net.chempound.webapp.content;
-
-import com.google.inject.Injector;
-import com.hp.hpl.jena.rdf.model.Resource;
-import com.hp.hpl.jena.rdf.model.ResourceFactory;
-import net.chempound.datastore.TripleStore;
-import net.chempound.rdf.CPTerms;
-import net.chempound.rdf.ORE;
-import org.restlet.Context;
-import org.restlet.Request;
-import org.restlet.Response;
-import org.restlet.data.Reference;
-import org.restlet.resource.Finder;
-import org.restlet.resource.ServerResource;
-
-import javax.inject.Inject;
-
-/**
- * @author sea36
- */
-public class ContentRouter extends Finder {
-
-    private final Injector injector;
-    private final TripleStore tripleStore;
-
-    @Inject
-    public ContentRouter(final Injector injector, final TripleStore tripleStore, final Context context) {
-        super(context);
-        this.injector = injector;
-        this.tripleStore = tripleStore;
-    }
-
-    @Override
-    public ServerResource create(final Request request, final Response response) {
-
-        final Reference ref = request.getResourceRef();
-        final Resource resource = ResourceFactory.createResource(ref.toString());
-
-        if (isCollection(resource)) {
-            return injector.getInstance(CollectionResource.class);
-        }
-        if (isAggregation(resource)) {
-            return injector.getInstance(AggregationResource.class);
-        }
-        if (isAggregatedResource(resource)) {
-            return injector.getInstance(FileResource.class);
-        }
-        if (isResourceMap(resource)) {
-            return injector.getInstance(ResourceMapResource.class);
-        }
-        
-        if (!ref.getPath().endsWith("/")) {
-            final Reference redirectRef = new Reference(ref);
-            redirectRef.setPath(redirectRef.getPath() + '/');
-            final Resource redirect = ResourceFactory.createResource(redirectRef.toString());
-
-            if (isAggregation(redirect) || isAggregatedResource(redirect) || isResourceMap(redirect)) {
-                response.getAttributes().put("chempound.redirect", redirectRef);
-                return injector.getInstance(RedirectResource.class);
-            }
-        }
-
-        return null;
-    }
-
-    private boolean isCollection(final Resource resource) {
-        return tripleStore.containsResourceOfType(resource, CPTerms.Collection);
-    }
-
-    private boolean isAggregation(final Resource resource) {
-        return tripleStore.containsResourceOfType(resource, ORE.Aggregation);
-    }
-
-    private boolean isAggregatedResource(final Resource resource) {
-        return tripleStore.containsResourceOfType(resource, ORE.AggregatedResource);
-    }
-
-    private boolean isResourceMap(final Resource resource) {
-        return tripleStore.containsResourceOfType(resource, ORE.ResourceMap);
-    }
-
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/ETagFactory.java

-package net.chempound.webapp.content;
-
-import net.chempound.storage.LocalResource;
-
-import javax.inject.Singleton;
-
-/**
- * @author Sam Adams
- */
-@Singleton
-public class ETagFactory {
-
-    public String createETag(final LocalResource resource) {
-        final long length = resource.getLength();
-        final long modified = resource.getLastModified().getTime();
-        return Long.toHexString(modified) + '-' + Long.toHexString(length);
-    }
-
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/FileResource.java

-package net.chempound.webapp.content;
-
-import net.chempound.datastore.TripleStore;
-import net.chempound.storage.LocalResource;
-import net.chempound.storage.ResourceStore;
-import org.apache.commons.io.FilenameUtils;
-import org.restlet.data.MediaType;
-import org.restlet.data.Status;
-import org.restlet.data.Tag;
-import org.restlet.representation.Representation;
-import org.restlet.resource.ServerResource;
-
-import javax.inject.Inject;
-import java.io.IOException;
-import java.net.URI;
-
-/**
- * @author sea36
- */
-public class FileResource extends ServerResource {
-
-    private final TripleStore tripleStore;
-    private final ResourceStore resourceStore;
-    private final ETagFactory tagFactory;
-
-    private LocalResource resource;
-    private MediaType mediaType;
-
-    @Inject
-    public FileResource(final TripleStore tripleStore, final ResourceStore resourceStore, final ETagFactory tagFactory) {
-        this.tripleStore = tripleStore;
-        this.resourceStore = resourceStore;
-        this.tagFactory = tagFactory;
-
-        setNegotiated(false);
-    }
-
-    @Override
-    protected void doInit() {
-        try {
-            final String path = getReference().getRemainingPart(true, false);
-            final LocalResource resource = resourceStore.retrieveItem(path);
-
-            if (resource == null || !resource.isAvailable()) {
-                setExisting(false);
-            } else {
-                this.resource = resource;
-                this.mediaType = getMediaType();
-            }
-        } catch (IOException e) {
-            setStatus(Status.SERVER_ERROR_INTERNAL, e);
-        }
-    }
-
-    @Override
-    protected Representation get() {
-        final ResourceRepresentation representation = new ResourceRepresentation(resource, mediaType);
-        representation.setTag(new Tag(tagFactory.createETag(resource)));
-        return representation;
-    }
-
-    private MediaType getMediaType() {
-        final URI uri = getReference().toUri();
-        final String mediaType = tripleStore.getMediaType(uri);
-        if (mediaType == null) {
-            final String ext = FilenameUtils.getExtension(uri.toString());
-            return getApplication().getMetadataService().getMediaType(ext);
-        }
-        return MediaType.valueOf(mediaType);
-    }
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/JsonGenerator.java

-package net.chempound.webapp.content;
-
-import com.hp.hpl.jena.rdf.model.Model;
-import net.chempound.datastore.TripleStore;
-import net.chempound.rdf.chempound.ChempoundAggregation;
-import net.chempound.rdf.ore.AggregatedResource;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.net.URI;
-
-@Singleton
-public class JsonGenerator {
-
-    private static final String URI = "uri";
-    private static final String TITLE = "title";
-    private static final String RESOURCES = "resources";
-
-    private final TripleStore tripleStore;
-
-    @Inject
-    public JsonGenerator(final TripleStore tripleStore) {
-        this.tripleStore = tripleStore;
-    }
-
-    public JSONObject createJson(final URI aggregationUri) throws JSONException {
-        final Model model = tripleStore.getModel(aggregationUri);
-        final ChempoundAggregation aggregation = model.getResource(aggregationUri.toString()).as(ChempoundAggregation.class);
-
-        final JSONObject obj = new JSONObject();
-        obj.put(URI, aggregation.getURI());
-        obj.put(TITLE, aggregation.getTitle());
-        obj.put(RESOURCES, createResourceLinks(aggregation));
-        return obj;
-    }
-
-    private JSONArray createResourceLinks(final ChempoundAggregation aggregation) throws JSONException {
-        final JSONArray resources = new JSONArray();
-        for (final AggregatedResource resource : aggregation.getAggregatedResources()) {
-            resources.put(createResourceLink(resource));
-        }
-        return resources;
-    }
-
-    private JSONObject createResourceLink(final AggregatedResource resource) throws JSONException {
-        final JSONObject json = new JSONObject();
-        json.put(URI, resource.getURI());
-        return json;
-    }
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/PingbackProcessor.java

-package net.chempound.webapp.content;
-
-import net.chempound.config.BaseUri;
-import net.chempound.webapp.Restlet;
-import org.restlet.Response;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.net.URI;
-
-@Singleton
-public class PingbackProcessor {
-
-    private static final String HEADER_PINGBACK = "X-Pingback";
-
-    private final URI pingbackUrl;
-
-    @Inject
-    public PingbackProcessor(@BaseUri final URI baseUri) {
-        this.pingbackUrl = baseUri.resolve("pingback");
-    }
-
-    public void addPingbackHeader(final Response response) {
-        Restlet.setHeader(response, HEADER_PINGBACK, pingbackUrl.toString());
-    }
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/RedirectResource.java

-package net.chempound.webapp.content;
-
-import org.restlet.data.Reference;
-import org.restlet.resource.ServerResource;
-
-/**
- * @author Sam Adams
- */
-public class RedirectResource extends ServerResource {
-
-    @Override
-    protected void doInit() {
-        final Reference redirectRef = (Reference) getResponseAttributes().get("chempound.redirect");
-        redirectSeeOther(redirectRef);
-    }
-
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/ResourceMapGenerator.java

-package net.chempound.webapp.content;
-
-import com.hp.hpl.jena.rdf.model.Model;
-import net.chempound.datastore.TripleStore;
-import net.chempound.webapp.utils.ByteArrayRepresentation;
-import org.restlet.data.MediaType;
-import org.restlet.representation.Representation;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.io.ByteArrayOutputStream;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-
-@Singleton
-public class ResourceMapGenerator {
-
-    private final TripleStore tripleStore;
-    private final Map<MediaType, String> rdfLangMap;
-
-    @Inject
-    public ResourceMapGenerator(final TripleStore tripleStore) {
-        this.tripleStore = tripleStore;
-        this.rdfLangMap = registerLanguages();
-    }
-
-    private Map<MediaType, String> registerLanguages() {
-        final Map<MediaType, String> map = new HashMap<MediaType, String>();
-        map.put(MediaType.APPLICATION_RDF_XML, "RDF/XML");
-        map.put(MediaType.TEXT_RDF_N3, "N3");
-        map.put(MediaType.APPLICATION_RDF_TURTLE, "TTL");
-        map.put(MediaType.TEXT_RDF_NTRIPLES, "N-TRIPLE");
-        return map;
-    }
-
-    public Representation getRdfXml(final URI aggregationUri) {
-        return getRdf(aggregationUri, MediaType.APPLICATION_RDF_XML);
-    }
-
-    public Representation getTurtle(final URI aggregationUri) {
-        return getRdf(aggregationUri, MediaType.APPLICATION_RDF_TURTLE);
-    }
-
-    public Representation getNotation3(final URI aggregationUri) {
-        return getRdf(aggregationUri, MediaType.TEXT_RDF_N3);
-    }
-
-    public Representation getNTriples(final URI aggregationUri) {
-        return getRdf(aggregationUri, MediaType.TEXT_RDF_NTRIPLES);
-    }
-
-    private Representation getRdf(final URI aggregationUri, final MediaType mediaType) {
-        final Model model = tripleStore.getModel(aggregationUri);
-        final String lang = rdfLangMap.get(mediaType);
-        final byte[] bytes = writeRdf(model, lang);
-        return new ByteArrayRepresentation(bytes, mediaType);
-    }
-
-    private byte[] writeRdf(final Model model, final String lang) {
-        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        model.write(buffer, lang);
-        return buffer.toByteArray();
-    }
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/ResourceMapResource.java

-package net.chempound.webapp.content;
-
-import net.chempound.datastore.TripleStore;
-import net.chempound.webapp.splashpage.SplashPageGenerator;
-import org.json.JSONObject;
-import org.restlet.data.Reference;
-import org.restlet.ext.json.JsonRepresentation;
-import org.restlet.representation.Representation;
-import org.restlet.representation.Variant;
-import org.restlet.resource.Get;
-
-import javax.inject.Inject;
-import java.net.URI;
-import java.util.Iterator;
-
-/**
- * @author Sam Adams
- */
-public class ResourceMapResource extends ContentResource {
-
-    private final SplashPageGenerator splashPageGenerator;
-    private final PingbackProcessor pingbackProcessor;
-    private final ResourceMapGenerator resourceMapGenerator;
-    private final JsonGenerator jsonGenerator;
-
-    private URI aggregationUri;
-
-    @Inject
-    public ResourceMapResource(final TripleStore tripleStore, final SplashPageGenerator splashPageGenerator, final JsonGenerator jsonGenerator, final ResourceMapGenerator resourceMapGenerator, final PingbackProcessor pingbackProcessor) {
-        super(tripleStore);
-        this.splashPageGenerator = splashPageGenerator;
-        this.pingbackProcessor = pingbackProcessor;
-        this.jsonGenerator = jsonGenerator;
-        this.resourceMapGenerator = resourceMapGenerator;
-    }
-
-    @Override
-    protected void doInit() {
-        final URI aggr = (URI) getRequestAttributes().get("chempound.aggregation.uri");
-        if (aggr != null) {
-            this.aggregationUri = aggr;
-        } else {
-            final Reference ref = getReference();
-            this.aggregationUri = getAggregation(ref);
-        }
-        URI uri = (URI) getRequestAttributes().get("chempound.request.uri");
-        if (uri == null) {
-            uri = getReference().toUri();
-        }
-
-        boolean existing = false;
-        if (tripleStore.containsNamedModel(aggregationUri)) {
-            final String mimeType = tripleStore.getMediaType(uri);
-            if (mimeType != null) {
-                for (final Iterator<Variant> it = getVariants().iterator(); it.hasNext();) {
-                    final Variant variant = it.next();
-                    if (!mimeType.equals(variant.getMediaType().getName())) {
-                        it.remove();
-                    }
-                }
-                existing = true;
-            }
-        }
-        setExisting(existing);
-    }
-
-
-    @Get("html")
-    public Representation getHtml() throws Exception {
-        final Representation representation = splashPageGenerator.createSplashPage(aggregationUri);
-        pingbackProcessor.addPingbackHeader(getResponse());
-        return representation;
-    }
-
-    @Get("xhtml")
-    public Representation getXHtml() throws Exception {
-        final Representation representation = splashPageGenerator.createSplashPage(aggregationUri);
-        pingbackProcessor.addPingbackHeader(getResponse());
-        return representation;
-    }
-
-    @Get("json")
-    public Representation getJson() throws Exception {
-        final JSONObject json = jsonGenerator.createJson(aggregationUri);
-        final JsonRepresentation representation = new JsonRepresentation(json);
-        representation.setIndenting(true);
-        representation.setIndentingSize(2);
-        return representation;
-    }
-
-    @Get("rdf")
-    public Representation getRdfXml() {
-        return resourceMapGenerator.getRdfXml(aggregationUri);
-    }
-
-    @Get("ttl")
-    public Representation getRdfTurtle() {
-        return resourceMapGenerator.getTurtle(aggregationUri);
-    }
-
-    @Get("n3")
-    public Representation getRdfN3() {
-        return resourceMapGenerator.getNotation3(aggregationUri);
-    }
-
-    @Get("nt")
-    public Representation getRdfNTriples() {
-        return resourceMapGenerator.getNTriples(aggregationUri);
-    }
-}

chempound-webapp/src/main/java/net/chempound/webapp/content/ResourceRepresentation.java

-package net.chempound.webapp.content;
-
-import net.chempound.storage.LocalResource;
-import org.apache.commons.io.IOUtils;
-import org.restlet.data.Disposition;
-import org.restlet.data.MediaType;
-import org.restlet.representation.StreamRepresentation;
-
-import java.io.*;
-
-/**
- * @author Sam Adams
- */
-public class ResourceRepresentation extends StreamRepresentation {
-
-    private final LocalResource resource;
-
-    public ResourceRepresentation(final LocalResource resource, final MediaType mediaType) {
-        super(mediaType);
-        this.resource = resource;
-        setModificationDate(resource.getLastModified());
-
-        final Disposition disposition = new Disposition();
-        disposition.setFilename(getName(resource.getPath()));
-        this.setDisposition(disposition);
-    }
-
-
-    private String getName(final String path) {
-        final int i = path.lastIndexOf('/');
-        return i == -1 ? path : path.substring(i + 1);
-    }
-
-    @Override
-    public long getSize() {
-        return resource.getLength();
-    }
-
-    @Override
-    public InputStream getStream() throws IOException {
-        final byte[] bytes = getBytes();
-        return new ByteArrayInputStream(bytes);
-    }
-
-    private byte[] getBytes() throws IOException {
-        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        write(buffer);
-        return buffer.toByteArray();
-    }
-
-    @Override
-    public void write(final OutputStream outputStream) throws IOException {
-        final InputStream in = resource.openInputStream();
-        try {
-            IOUtils.copy(in, outputStream);
-        } finally {
-            IOUtils.closeQuietly(in);
-        }
-    }
-
-}

chempound-webapp/src/main/java/net/chempound/webapp/data/AggregationResource.java

+package net.chempound.webapp.data;
+
+import net.chempound.datastore.TripleStore;
+import net.chempound.util.MimeType;
+import net.chempound.webapp.splashpage.SplashPageGenerator;
+import org.json.JSONObject;
+import org.restlet.data.MediaType;
+import org.restlet.ext.json.JsonRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.representation.Variant;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+import javax.inject.Inject;
+import java.net.URI;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * @author sea36
+ */
+public class AggregationResource extends ServerResource {
+
+    private final TripleStore tripleStore;
+    private final SplashPageGenerator splashPageGenerator;
+    private final ResourceMapGenerator resourceMapGenerator;
+    private final JsonGenerator jsonGenerator;
+    private final PingbackProcessor pingbackProcessor;
+
+    private URI aggregationUri;
+    private Map<MimeType, URI> typeMap;
+
+    @Inject
+    public AggregationResource(final TripleStore tripleStore, final SplashPageGenerator splashPageGenerator, final ResourceMapGenerator resourceMapGenerator, final JsonGenerator jsonGenerator, final PingbackProcessor pingbackProcessor) {
+        this.tripleStore = tripleStore;
+        this.splashPageGenerator = splashPageGenerator;
+        this.resourceMapGenerator = resourceMapGenerator;
+        this.jsonGenerator = jsonGenerator;
+        this.pingbackProcessor = pingbackProcessor;
+    }
+
+    @Override
+    protected void doInit() {
+        aggregationUri = getReference().toUri();
+
+        typeMap = tripleStore.getResourceVariants(aggregationUri);
+        if (typeMap.containsKey(MimeType.TEXT_HTML) && !typeMap.containsKey(MimeType.APPLICATION_XHTML)) {
+            typeMap.put(MimeType.APPLICATION_XHTML, typeMap.get(MimeType.TEXT_HTML));
+        }
+
+        // Hack to ensure 'default' representation is text/html
+        if (getClientInfo().getAcceptedMediaTypes().isEmpty() ||
+                (getClientInfo().getAcceptedMediaTypes().size() == 1 &&
+                    MediaType.ALL.equals(getClientInfo().getAcceptedMediaTypes().get(0).getMetadata()))) {
+            for (final Iterator<Variant> it = getVariants().iterator(); it.hasNext();) {
+                final MediaType mediaType = it.next().getMediaType();
+                if (!MediaType.TEXT_HTML.equals(mediaType)) {
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    @Get("html")
+    public Representation getHtml() throws Exception {
+        final Representation representation = splashPageGenerator.createSplashPage(aggregationUri);
+        addContentLocation(representation, MediaType.TEXT_HTML);
+        pingbackProcessor.addPingbackHeader(getResponse());
+        return representation;
+    }
+
+    @Get("xhtml")
+    public Representation getXHtml() throws Exception {
+        final Representation representation = splashPageGenerator.createSplashPage(aggregationUri);
+        addContentLocation(representation, MediaType.APPLICATION_XHTML);
+        pingbackProcessor.addPingbackHeader(getResponse());
+        return representation;
+    }
+
+    @Get("json")
+    public Representation getJson() throws Exception {
+        final JSONObject json = jsonGenerator.createJson(aggregationUri);
+        final JsonRepresentation representation = new JsonRepresentation(json);
+        representation.setIndenting(true);
+        representation.setIndentingSize(2);
+        addContentLocation(representation, MediaType.APPLICATION_JSON);
+        return representation;
+    }
+
+    @Get("rdf")
+    public Representation getRdfXml() {
+        final Representation representation = resourceMapGenerator.getRdfXml(aggregationUri);
+        addContentLocation(representation, MediaType.APPLICATION_RDF_XML);
+        return representation;
+    }
+
+    @Get("ttl")
+    public Representation getRdfTurtle() {
+        final Representation representation = resourceMapGenerator.getTurtle(aggregationUri);
+        addContentLocation(representation, MediaType.APPLICATION_RDF_TURTLE);
+        return representation;
+    }
+
+    @Get("n3")
+    public Representation getRdfN3() {
+        final Representation representation = resourceMapGenerator.getNotation3(aggregationUri);
+        addContentLocation(representation, MediaType.TEXT_RDF_N3);
+        return representation;
+    }
+
+    @Get("nt")
+    public Representation getRdfNTriples() {
+        final Representation representation = resourceMapGenerator.getNTriples(aggregationUri);
+        addContentLocation(representation, MediaType.TEXT_RDF_NTRIPLES);
+        return representation;
+    }
+
+    private void addContentLocation(final Representation representation, final MediaType mediaType) {
+        final MimeType mimeType = new MimeType(mediaType.getName());
+        if (typeMap.containsKey(mimeType)) {
+            final URI uri = typeMap.get(mimeType);
+            representation.setLocationRef(uri.toString());
+        }
+    }
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/CollectionResource.java

+package net.chempound.webapp.data;
+
+import net.chempound.datastore.TripleStore;
+import net.chempound.webapp.Restlet;
+import net.chempound.webapp.splashpage.SplashPageGenerator;
+import net.chempound.webapp.sword.AbderaRepresentation;
+import org.apache.abdera.Abdera;
+import org.apache.abdera.i18n.iri.IRI;
+import org.apache.abdera.model.Document;
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.parser.Parser;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.fileupload.FileItemHeaders;
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileItemStream;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.io.IOUtils;
+import org.restlet.data.MediaType;
+import org.restlet.data.Status;
+import org.restlet.engine.header.Header;
+import org.restlet.ext.fileupload.RestletFileUpload;
+import org.restlet.representation.EmptyRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.resource.Post;
+import org.restlet.util.Series;
+import org.swordapp.server.*;
+
+import javax.inject.Inject;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.restlet.data.MediaType.APPLICATION_ATOM;
+
+/**
+ * @author Sam Adams
+ */
+public class CollectionResource extends AggregationResource {
+
+    public static final MediaType APPLICATION_ATOM_FEED = MediaType.register(
+        "application/atom+xml;type=feed", "Atom feed document");
+
+    public static final MediaType APPLICATION_ATOM_ENTRY = MediaType.register(
+        "application/atom+xml;type=entry", "Atom entry document");
+
+    public static final MediaType MULTIPART_RELATED = MediaType.register(
+        "multipart/related", "Multipart related data");
+
+    private static final boolean IGNORE_CASE = true;
+
+    private final SwordConfiguration configuration;
+    private final CollectionDepositManager depositManager;
+
+    @Inject
+    public CollectionResource(final TripleStore tripleStore, final SwordConfiguration configuration, final CollectionDepositManager depositManager, final SplashPageGenerator splashPageGenerator, final JsonGenerator jsonGenerator, final ResourceMapGenerator resourceMapGenerator, final PingbackProcessor pingbackProcessor) {
+        super(tripleStore, splashPageGenerator, resourceMapGenerator, jsonGenerator, pingbackProcessor);
+
+        this.configuration = configuration;
+        this.depositManager = depositManager;
+    }
+
+    @Post("*:atom")
+    public Representation handleDeposit(final Representation data) throws SwordError, SwordServerException, IOException, FileUploadException, SwordAuthException {
+
+//        if (data.getSize() == Representation.UNKNOWN_SIZE) {
+//            setStatus(Status.CLIENT_ERROR_LENGTH_REQUIRED);
+//            return null;
+//        }
+
+        final AuthCredentials auth = null;
+
+        final Deposit deposit = new Deposit();
+        deposit.setSlug(getSlug());
+        deposit.setInProgress(isInProgress());
+
+        getLogger().info("MIME type: " + data.getMediaType());
+
+        final MediaType mediaType = data.getMediaType();
+        if (mediaType != null && MULTIPART_RELATED.includes(mediaType)) {
+            getLogger().info("Performing multipart deposit");
+            handleMultipartDeposit(deposit);
+        }
+        else if (mediaType != null && APPLICATION_ATOM.includes(mediaType)) {
+            getLogger().info("Performing atom entry deposit");
+            handleAtomEntryDeposit(deposit);
+        }
+        else {
+            getLogger().info("Performing binary deposit");
+            handleBinaryDeposit(deposit);
+        }
+
+        getLogger().info("SWORD endpoint: " + getReference());
+
+        final DepositReceipt receipt = depositManager.createNew(getReference().toString(), deposit, auth, configuration);
+        final IRI editIRI = receipt.getEditIRI();
+        if (editIRI == null) {
+            throw new SwordServerException("No Edit-IRI found in Deposit Receipt; unable to send valid response");
+        }
+
+        setStatus(Status.SUCCESS_CREATED);
+        setLocationRef(editIRI.toString());
+        if (configuration.returnDepositReceipt()) {
+            return new AbderaRepresentation(receipt.getAbderaEntry(), APPLICATION_ATOM_ENTRY);
+        } else {
+            return new EmptyRepresentation();
+        }
+
+    }
+
+    private String getSlug() {
+        final Series<Header> headers = Restlet.getHeaders(getRequest());
+        return headers.getFirstValue("Slug", IGNORE_CASE);
+    }
+
+    protected boolean isInProgress() throws SwordError {
+        final Series<Header> headers = Restlet.getHeaders(getRequest());
+        final String iph = headers.getFirstValue("In-Progress", IGNORE_CASE);
+
+        boolean inProgress = false; // default value
+        if (iph != null) {
+            // first of all validate that the value is "true" or "false"
+            if (!"true".equals(iph.trim()) && !"false".equals(iph.trim())) {
+                throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "The In-Progress header MUST be 'true' or 'false'");
+            }
+            inProgress = "true".equals(iph.trim());
+        }
+        return inProgress;
+    }
+
+
+    private void handleMultipartDeposit(final Deposit deposit) throws IOException, FileUploadException, SwordError {
+
+        final RestletFileUpload fileUpload = new RestletFileUpload();
+        final FileItemIterator itr = fileUpload.getItemIterator(getRequestEntity());
+
+        while (itr.hasNext()) {
+
+            final FileItemStream in = itr.next();
+            getLogger().info("Part: " + in.getFieldName() + " [" + in.getContentType() + "]");
+            if ("atom".equals(in.getFieldName())) {
+                getLogger().info("Reading atom entry");
+                final InputStream is = in.openStream();
+                Entry entry;
+                try {
+                    entry = readEntry(is);
+                } finally {
+                    is.close();
+                }
+                deposit.setEntry(entry);
+            }
+            else if ("payload".equals(in.getFieldName())) {
+                getLogger().info("Reading payload");
+
+                final MediaType contentType = getContentType();
+                final FileItemHeaders headers = in.getHeaders();
+                final String packaging = headers.getHeader("Packaging");
+                final String filename = in.getName();
+
+                getLogger().info("Received file [" + filename + "] [" + packaging + "] [" + contentType + "]");
+
+                final InputStream is = in.openStream();
+                try {
+                    byte[] b = IOUtils.toByteArray(is);
+                    // TODO - should check for 'Content-Transfer-Encoding: base64' header,
+                    // but that is not set by SWORD client right now...
+                    final String transferEncoding = headers.getHeader("Content-Transfer-Encoding");
+                    getLogger().info("Transfer encoding: " + transferEncoding);
+                    if (!"binary".equalsIgnoreCase(transferEncoding)) {
+                        final Base64 base64 = new Base64();
+                        b = base64.decode(b);
+                    }
+                    addPayload(deposit, contentType, packaging, filename, new ByteArrayInputStream(b));
+                } finally {
+                    is.close();
+                }
+            }
+            else {
+                throw new SwordError("Unknown part: " + in.getFieldName());
+            }
+        }
+    }
+
+    private void handleAtomEntryDeposit(final Deposit deposit) throws IOException {
+        final InputStream entryPart = getRequestEntity().getStream();
+        final Entry entry = readEntry(entryPart);
+        deposit.setEntry(entry);
+    }
+
+    private Entry readEntry(final InputStream entryPart) throws IOException {
+        final byte[] buffer = IOUtils.toByteArray(entryPart);
+
+        final Abdera abdera = new Abdera();
+        final Parser parser = abdera.getParser();
+        final Document<Entry> entryDoc = parser.parse(new ByteArrayInputStream(buffer));
+        return entryDoc.getRoot();
+    }
+
+    private void handleBinaryDeposit(final Deposit deposit) throws SwordError, IOException {
+        readPayload(deposit);
+    }
+
+    private void readPayload(final Deposit deposit) throws SwordError, IOException {
+        final MediaType contentType = getContentType();
+        final String packaging = getPackaging();
+
+        final String filename = getFilename();
+        if (filename == null || "".equals(filename)) {
+            throw new SwordError(UriRegistry.ERROR_BAD_REQUEST, "Filename could not be extracted from Content-Disposition header");
+        }
+
+        final InputStream file = getRequestEntity().getStream();
+
+        final byte[] md5 = getMd5();
+
+        addPayload(deposit, contentType, packaging, filename, file);
+    }
+
+    private void addPayload(final Deposit deposit, final MediaType contentType, final String packaging, final String filename, final InputStream inputStream) {
+        getLogger().info("Adding '" + filename + "' to deposit payload");
+        deposit.setFilename(filename);
+//        deposit.setMd5(md5);  TODO to hex
+        deposit.setPackaging(packaging);
+        deposit.setInputStream(inputStream);
+        deposit.setMimeType(contentType.toString());
+    }
+
+    private String getPackaging() {
+        final Series<Header> headers = Restlet.getHeaders(getRequest());
+        String packaging = headers.getFirstValue("Packaging", IGNORE_CASE);
+        if (packaging == null || "".equals(packaging)) {
+            packaging = UriRegistry.PACKAGE_BINARY;
+        }
+        return packaging;
+    }
+
+    private MediaType getContentType() {
+        MediaType contentType = getRequestEntity().getMediaType();
+        if (contentType == null) {
+            // TODO guess!
+            contentType = MediaType.APPLICATION_OCTET_STREAM;
+        }
+        return contentType;
+    }
+
+    private String getFilename() {
+        String filename = null;
+        if (getRequestEntity().getDisposition() != null) {
+            filename = getRequestEntity().getDisposition().getFilename();
+        } else {
+            final Series<Header> headers = Restlet.getHeaders(getRequest());
+            final String cd = headers.getFirstValue("Content-Disposition", IGNORE_CASE);
+            if (cd != null) {
+                final int i = cd.indexOf("filename=");
+                filename = cd.substring(i + 9);
+            }
+        }
+
+        return filename;
+    }
+
+    private byte[] getMd5() {
+        byte[] md5 = null;
+        if (getRequestEntity().getDigest() != null) {
+            if ("MD5".equals(getRequestEntity().getDigest().getAlgorithm())) {
+                md5 = getRequestEntity().getDigest().getValue();
+            }
+        }
+        return md5;
+    }
+
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/ContentResource.java

+package net.chempound.webapp.data;
+
+import com.hp.hpl.jena.rdf.model.ResourceFactory;
+import net.chempound.datastore.TripleStore;
+import org.restlet.data.Reference;
+import org.restlet.resource.ServerResource;
+
+import java.net.URI;
+
+/**
+ * @author sea36
+ */
+public abstract class ContentResource extends ServerResource {
+
+    protected final TripleStore tripleStore;
+
+    protected ContentResource(final TripleStore tripleStore) {
+        this.tripleStore = tripleStore;
+    }
+
+    protected URI getAggregation(final Reference ref) {
+        return tripleStore.getAggregationUriForResourceMap(ResourceFactory.createResource(ref.toString()));
+    }
+
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/ContentRouter.java

+package net.chempound.webapp.data;
+
+import com.google.inject.Injector;
+import com.hp.hpl.jena.rdf.model.Resource;
+import com.hp.hpl.jena.rdf.model.ResourceFactory;
+import net.chempound.datastore.TripleStore;
+import net.chempound.rdf.CPTerms;
+import net.chempound.rdf.ORE;
+import org.restlet.Context;
+import org.restlet.Request;
+import org.restlet.Response;
+import org.restlet.data.Reference;
+import org.restlet.resource.Finder;
+import org.restlet.resource.ServerResource;
+
+import javax.inject.Inject;
+
+/**
+ * @author sea36
+ */
+public class ContentRouter extends Finder {
+
+    private final Injector injector;
+    private final TripleStore tripleStore;
+
+    @Inject
+    public ContentRouter(final Injector injector, final TripleStore tripleStore, final Context context) {
+        super(context);
+        this.injector = injector;
+        this.tripleStore = tripleStore;
+    }
+
+    @Override
+    public ServerResource create(final Request request, final Response response) {
+
+        final Reference ref = request.getResourceRef();
+        final Resource resource = ResourceFactory.createResource(ref.toString());
+
+        if (isCollection(resource)) {
+            return injector.getInstance(CollectionResource.class);
+        }
+        if (isAggregation(resource)) {
+            return injector.getInstance(AggregationResource.class);
+        }
+        if (isAggregatedResource(resource)) {
+            return injector.getInstance(FileResource.class);
+        }
+        if (isResourceMap(resource)) {
+            return injector.getInstance(ResourceMapResource.class);
+        }
+        
+        if (!ref.getPath().endsWith("/")) {
+            final Reference redirectRef = new Reference(ref);
+            redirectRef.setPath(redirectRef.getPath() + '/');
+            final Resource redirect = ResourceFactory.createResource(redirectRef.toString());
+
+            if (isAggregation(redirect) || isAggregatedResource(redirect) || isResourceMap(redirect)) {
+                response.getAttributes().put("chempound.redirect", redirectRef);
+                return injector.getInstance(RedirectResource.class);
+            }
+        }
+
+        return null;
+    }
+
+    private boolean isCollection(final Resource resource) {
+        return tripleStore.containsResourceOfType(resource, CPTerms.Collection);
+    }
+
+    private boolean isAggregation(final Resource resource) {
+        return tripleStore.containsResourceOfType(resource, ORE.Aggregation);
+    }
+
+    private boolean isAggregatedResource(final Resource resource) {
+        return tripleStore.containsResourceOfType(resource, ORE.AggregatedResource);
+    }
+
+    private boolean isResourceMap(final Resource resource) {
+        return tripleStore.containsResourceOfType(resource, ORE.ResourceMap);
+    }
+
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/ETagFactory.java

+package net.chempound.webapp.data;
+
+import net.chempound.storage.LocalResource;
+
+import javax.inject.Singleton;
+
+/**
+ * @author Sam Adams
+ */
+@Singleton
+public class ETagFactory {
+
+    public String createETag(final LocalResource resource) {
+        final long length = resource.getLength();
+        final long modified = resource.getLastModified().getTime();
+        return Long.toHexString(modified) + '-' + Long.toHexString(length);
+    }
+
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/FileResource.java

+package net.chempound.webapp.data;
+
+import net.chempound.datastore.TripleStore;
+import net.chempound.storage.LocalResource;
+import net.chempound.storage.ResourceStore;
+import org.apache.commons.io.FilenameUtils;
+import org.restlet.data.MediaType;
+import org.restlet.data.Status;
+import org.restlet.data.Tag;
+import org.restlet.representation.Representation;
+import org.restlet.resource.ServerResource;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * @author sea36
+ */
+public class FileResource extends ServerResource {
+
+    private final TripleStore tripleStore;
+    private final ResourceStore resourceStore;
+    private final ETagFactory tagFactory;
+
+    private LocalResource resource;
+    private MediaType mediaType;
+
+    @Inject
+    public FileResource(final TripleStore tripleStore, final ResourceStore resourceStore, final ETagFactory tagFactory) {
+        this.tripleStore = tripleStore;
+        this.resourceStore = resourceStore;
+        this.tagFactory = tagFactory;
+
+        setNegotiated(false);
+    }
+
+    @Override
+    protected void doInit() {
+        try {
+            final String path = getReference().getRemainingPart(true, false);
+            final LocalResource resource = resourceStore.retrieveItem(path);
+
+            if (resource == null || !resource.isAvailable()) {
+                setExisting(false);
+            } else {
+                this.resource = resource;
+                this.mediaType = getMediaType();
+            }
+        } catch (IOException e) {
+            setStatus(Status.SERVER_ERROR_INTERNAL, e);
+        }
+    }
+
+    @Override
+    protected Representation get() {
+        final ResourceRepresentation representation = new ResourceRepresentation(resource, mediaType);
+        representation.setTag(new Tag(tagFactory.createETag(resource)));
+        return representation;
+    }
+
+    private MediaType getMediaType() {
+        final URI uri = getReference().toUri();
+        final String mediaType = tripleStore.getMediaType(uri);
+        if (mediaType == null) {
+            final String ext = FilenameUtils.getExtension(uri.toString());
+            return getApplication().getMetadataService().getMediaType(ext);
+        }
+        return MediaType.valueOf(mediaType);
+    }
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/JsonGenerator.java

+package net.chempound.webapp.data;
+
+import com.hp.hpl.jena.rdf.model.Model;
+import net.chempound.datastore.TripleStore;
+import net.chempound.rdf.chempound.ChempoundAggregation;
+import net.chempound.rdf.ore.AggregatedResource;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.net.URI;
+
+@Singleton
+public class JsonGenerator {
+
+    private static final String URI = "uri";
+    private static final String TITLE = "title";
+    private static final String RESOURCES = "resources";
+
+    private final TripleStore tripleStore;
+
+    @Inject
+    public JsonGenerator(final TripleStore tripleStore) {
+        this.tripleStore = tripleStore;
+    }
+
+    public JSONObject createJson(final URI aggregationUri) throws JSONException {
+        final Model model = tripleStore.getModel(aggregationUri);
+        final ChempoundAggregation aggregation = model.getResource(aggregationUri.toString()).as(ChempoundAggregation.class);
+
+        final JSONObject obj = new JSONObject();
+        obj.put(URI, aggregation.getURI());
+        obj.put(TITLE, aggregation.getTitle());
+        obj.put(RESOURCES, createResourceLinks(aggregation));
+        return obj;
+    }
+
+    private JSONArray createResourceLinks(final ChempoundAggregation aggregation) throws JSONException {
+        final JSONArray resources = new JSONArray();
+        for (final AggregatedResource resource : aggregation.getAggregatedResources()) {
+            resources.put(createResourceLink(resource));
+        }
+        return resources;
+    }
+
+    private JSONObject createResourceLink(final AggregatedResource resource) throws JSONException {
+        final JSONObject json = new JSONObject();
+        json.put(URI, resource.getURI());
+        return json;
+    }
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/PingbackProcessor.java

+package net.chempound.webapp.data;
+
+import net.chempound.config.BaseUri;
+import net.chempound.webapp.Restlet;
+import org.restlet.Response;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.net.URI;
+
+@Singleton
+public class PingbackProcessor {
+
+    private static final String HEADER_PINGBACK = "X-Pingback";
+
+    private final URI pingbackUrl;
+
+    @Inject
+    public PingbackProcessor(@BaseUri final URI baseUri) {
+        this.pingbackUrl = baseUri.resolve("pingback");
+    }
+
+    public void addPingbackHeader(final Response response) {
+        Restlet.setHeader(response, HEADER_PINGBACK, pingbackUrl.toString());
+    }
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/RedirectResource.java

+package net.chempound.webapp.data;
+
+import org.restlet.data.Reference;
+import org.restlet.resource.ServerResource;
+
+/**
+ * @author Sam Adams
+ */
+public class RedirectResource extends ServerResource {
+
+    @Override
+    protected void doInit() {
+        final Reference redirectRef = (Reference) getResponseAttributes().get("chempound.redirect");
+        redirectSeeOther(redirectRef);
+    }
+
+}

chempound-webapp/src/main/java/net/chempound/webapp/data/ResourceMapGenerator.java

+package net.chempound.webapp.data;
+
+import com.hp.hpl.jena.rdf.model.Model;
+import net.chempound.datastore.TripleStore;
+import net.chempound.webapp.utils.ByteArrayRepresentation;
+import org.restlet.data.MediaType;
+import org.restlet.representation.Representation;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.ByteArrayOutputStream;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+@Singleton
+public class ResourceMapGenerator {
+
+    private final TripleStore tripleStore;
+    private final Map<MediaType, String> rdfLangMap;
+
+    @Inject
+    public ResourceMapGenerator(final TripleStore tripleStore) {
+        this.tripleStore = tripleStore;
+        this.rdfLangMap = registerLanguages();
+    }
+
+    private Map<MediaType, String> registerLanguages() {
+        final Map<MediaType, String> map = new HashMap<MediaType, String>();
+        map.put(MediaType.APPLICATION_RDF_XML, "RDF/XML");
+        map.put(MediaType.TEXT_RDF_N3, "N3");
+        map.put(MediaType.APPLICATION_RDF_TURTLE, "TTL");
+        map.put(MediaType.TEXT_RDF_NTRIPLES, "N-TRIPLE");
+        return map;
+    }
+
+    public Representation getRdfXml(final URI aggregationUri) {
+        return getRdf(aggregationUri, MediaType.APPLICATION_RDF_XML);
+    }
+
+    public Representation getTurtle(final URI aggregationUri) {
+        return getRdf(aggregationUri, MediaType.APPLICATION_RDF_TURTLE);
+    }
+
+    public Representation getNotation3(final URI aggregationUri) {
+        return getRdf(aggregationUri, MediaType.TEXT_RDF_N3);
+    }
+
+    public Representation getNTriples(final URI aggregationUri) {
+        return getRdf(aggregationUri, MediaType.TEXT_RDF_NTRIPLES);
+    }
+
+    private Representation getRdf(final URI aggregationUri, final MediaType mediaType) {
+        final Model model = tripleStore.getModel(aggregationUri);
+        final String lang = rdfLangMap.get(mediaType);
+        final byte[] bytes = writeRdf(model, lang);
+        return new ByteArrayRepresentation(bytes, mediaType);
+    }
+
+    private byte[] writeRdf(final Model model, final String lang) {
+        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        model.write(buffer, lang);
+        return buffer.toByteArray();