org.fcrepo.apix.jena.impl.LdpContainerRegistry.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.apix.jena.impl.LdpContainerRegistry.java

Source

/*
 * Licensed to DuraSpace under one or more contributor license agreements.
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.
 *
 * DuraSpace licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.fcrepo.apix.jena.impl;

import static org.fcrepo.apix.model.Ontologies.LDP_CONTAINS;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collection;

import org.fcrepo.apix.jena.Util;
import org.fcrepo.apix.model.WebResource;
import org.fcrepo.apix.model.components.Initializer;
import org.fcrepo.apix.model.components.Initializer.Initialization;
import org.fcrepo.apix.model.components.Registry;

import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Uses a single LDP container as a registry.
 * <p>
 * This uses Jena to parse the LDP membership list, and a delegate (presumably a http-based delegate) to GET
 * individual resources. {@link #put(WebResource)} will issue a PUT or GET to a given container as appropriate to
 * create or update a resource.
 * </p>
 *
 * @author apb@jhu.edu
 */
public class LdpContainerRegistry implements Registry {

    private Registry delegate;

    private URI containerId;

    private CloseableHttpClient client;

    private boolean binary = false;

    private boolean create = true;

    private URI containerContent;

    private Initialization init = Initialization.NONE;

    private Initializer initializer;

    private static final Logger LOG = LoggerFactory.getLogger(LdpContainerRegistry.class);

    /**
     * Underlying registry delegate for GETs.
     *
     * @param registry the registry.
     */
    public void setRegistryDelegate(final Registry registry) {
        this.delegate = registry;
    }

    /**
     * HttpClient for performing LDP requests.
     *
     * @param client the client.
     */
    public void setHttpClient(final CloseableHttpClient client) {

        this.client = client;
    }

    /**
     * Set the initializer.
     *
     * @param initializer the initializer
     */
    public void setInitializer(final Initializer initializer) {
        this.initializer = initializer;
    }

    /** Cancel the container creation, if it's running. */
    public void shutdown() {
        init.cancel();
    }

    /**
     * Create the container if it doesn't exist.
     */
    public void init() {

        init = initializer.initialize(() -> {

            if (!create) {
                return;
            }

            boolean found = false;

            while (!found) {
                LOG.info("Looking for container {}", containerId);
                found = exists(containerId);

                if (!found) {
                    LOG.info("Container {} does not exist, adding", containerId);
                    final URI added = put(WebResource.of(initialContent(), "text/turtle", containerId, null),
                            false);
                    LOG.info("Added container {} as <{}>", containerId, added);
                    found = true;
                }
            }
        });
    }

    /**
     * Set the URI of the LDP container containing objects relevant to this registry.
     *
     * @param containerId the LDP container URI
     */
    public void setContainer(final URI containerId) {
        this.containerId = containerId;
    }

    /**
     * Indicate whether to treat resources in this registry as binaries
     *
     * @param binary true if the resources are to be treated as binaries
     */
    public void setBinary(final boolean binary) {
        this.binary = binary;
    }

    /**
     * Indicate whether to attempt to create the container if not present
     *
     * @param create true if the container is to be created if it is missing
     */
    public void setCreateContainer(final boolean create) {
        this.create = create;
    }

    /**
     * Initial content content to initialize container with .
     * <p>
     * This is only relevant if {@link #setCreateContainer(boolean)} is true, at time of container creation. No
     * attempts to update the content of an existing container will be made.
     * </p>
     *
     * @param loc URI to a resource containing rdf. May be a file URI (for local content), classpath (for classpath
     *        resource), or http.
     */
    public void setContainerContent(final URI loc) {
        this.containerContent = loc;
    }

    @Override
    public WebResource get(final URI id) {
        init.await();
        return delegate.get(id);
    }

    @Override
    public URI put(final WebResource resource) {
        return put(resource, binary);
    }

    @Override
    public URI put(final WebResource resource, final boolean asBinary) {
        init.await();
        HttpEntityEnclosingRequestBase request = null;

        if (resource.uri() == null || !resource.uri().isAbsolute()) {
            request = new HttpPost(containerId);
            final String name = slugText(resource);
            if (name != null) {
                request.addHeader("Slug", name);
            }

            if (asBinary) {
                request.addHeader("Content-Disposition",
                        String.format("attachment; filename=%s", name != null ? name : "file.bin"));
            }

        } else {
            request = new HttpPut(resource.uri());
            if (!asBinary) {
                request.addHeader("Prefer", "handling=lenient; received=\"minimal\"");
            }
        }

        if (resource.representation() != null) {
            request.setEntity(new InputStreamEntity(resource.representation()));
        }
        request.setHeader(HttpHeaders.CONTENT_TYPE, resource.contentType());

        try {
            return client.execute(request, (response -> {
                final int status = response.getStatusLine().getStatusCode();

                if (status == HttpStatus.SC_CREATED) {
                    return URI.create(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
                } else if (status == HttpStatus.SC_NO_CONTENT || status == HttpStatus.SC_OK) {
                    return resource.uri();
                } else {
                    throw new RuntimeException(String.format("Resource creation failed: %s; %s",
                            response.getStatusLine().toString(), EntityUtils.toString(response.getEntity())));
                }
            }));
        } catch (final Exception e) {
            throw new RuntimeException(String.format("Error executing %s request to %s",
                    request.getClass().getSimpleName(), request.getURI().toString()), e);
        }

    }

    @Override
    public boolean canWrite() {
        return true;
    }

    @Override
    public Collection<URI> list() {
        init.await();
        try {
            final Model model = Util.parse(delegate.get(containerId));

            return model.listObjectsOfProperty(model.getProperty(LDP_CONTAINS)).mapWith(RDFNode::asResource)
                    .mapWith(Resource::getURI).mapWith(URI::create).toSet();
        } catch (final Exception e) {
            throw new RuntimeException("Error reading from " + containerId, e);
        }

    }

    @Override
    public void delete(final URI uri) {
        init.await();
        try (CloseableHttpResponse response = client.execute(new HttpDelete(uri))) {
            final StatusLine status = response.getStatusLine();
            if (status.getStatusCode() != HttpStatus.SC_NO_CONTENT && status.getStatusCode() != HttpStatus.SC_OK) {
                throw new RuntimeException(String.format("DELETE failed on %s: %s", uri, status));
            }
        } catch (final Exception e) {
            throw new RuntimeException(uri.toString(), e);
        }
    }

    private boolean exists(final URI uri) {
        try (CloseableHttpResponse response = client.execute(new HttpHead(uri))) {
            return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean contains(final URI id) {
        init.await();
        return list().contains(id);
    }

    private InputStream initialContent() {
        if (containerContent == null || containerContent.getScheme() == null) {
            return new ByteArrayInputStream(new byte[0]);
        } else if (containerContent.getScheme().equals("classpath")) {
            return this.getClass().getResourceAsStream(containerContent.getPath());
        }

        try {
            return containerContent.toURL().openStream();
        } catch (final IOException e) {
            throw new RuntimeException("Error retrieving container content from " + containerContent, e);
        }
    }

    @Override
    public boolean hasInDomain(final URI uri) {
        return uri.toString().startsWith(containerId.toString());
    }

    private String slugText(final WebResource resource) {
        final String raw;
        if (resource.name() != null) {
            raw = resource.name();
        } else if (resource.uri() != null && !resource.uri().isAbsolute()) {
            raw = resource.uri().toString();
        } else if (resource.uri() != null) {
            raw = resource.uri().getPath();
        } else {
            return null;
        }

        return raw.replaceFirst("^/", "").replaceFirst("/$", "").replaceAll("[:/?#\\[\\]@#%]", "-");
    }

}