org.fcrepo.integration.http.api.AbstractResourceIT.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.integration.http.api.AbstractResourceIT.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.integration.http.api;

import static java.lang.Integer.MAX_VALUE;
import static java.lang.Integer.parseInt;
import static java.util.Arrays.stream;
import static java.util.UUID.randomUUID;
import static java.util.stream.Collectors.toList;
import static javax.ws.rs.core.HttpHeaders.ACCEPT;
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
import static javax.ws.rs.core.HttpHeaders.CONTENT_LOCATION;
import static javax.ws.rs.core.HttpHeaders.LINK;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
import static javax.ws.rs.core.Response.Status.CREATED;
import static javax.ws.rs.core.Response.Status.GONE;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.Status.OK;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel;
import static org.fcrepo.http.commons.test.util.TestHelpers.parseTriples;
import static org.fcrepo.kernel.api.FedoraTypes.FCR_METADATA;
import static org.fcrepo.kernel.api.RdfLexicon.CONSTRAINED_BY;
import static org.fcrepo.kernel.api.RdfLexicon.CREATED_BY;
import static org.fcrepo.kernel.api.RdfLexicon.CREATED_DATE;
import static org.fcrepo.kernel.api.RdfLexicon.EXTERNAL_CONTENT;
import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_BY;
import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_DATE;
import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.slf4j.LoggerFactory.getLogger;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.Response.Status;
import javax.xml.bind.DatatypeConverter;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.jena.rdf.model.Model;
import org.fcrepo.http.commons.test.util.CloseableDataset;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * <p>Abstract AbstractResourceIT class.</p>
 *
 * @author awoods
 * @author ajs6f
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/spring-test/test-container.xml")
public abstract class AbstractResourceIT {

    protected static Logger logger;

    protected static final String NON_RDF_SOURCE_LINK_HEADER = "<" + NON_RDF_SOURCE.getURI() + ">;rel=\"type\"";

    @Before
    public void setLogger() {
        logger = getLogger(this.getClass());
    }

    private static final int SERVER_PORT = parseInt(System.getProperty("fcrepo.dynamic.test.port", "8080"));

    private static final String HOSTNAME = "localhost";

    private static final String PROTOCOL = "http";

    protected static final String serverAddress = PROTOCOL + "://" + HOSTNAME + ":" + SERVER_PORT + "/";

    protected static final CloseableHttpClient client = createClient();

    protected static CloseableHttpClient createClient() {
        return createClient(false);
    }

    protected static CloseableHttpClient createClient(final boolean disableRedirects) {
        final HttpClientBuilder client = HttpClientBuilder.create().setMaxConnPerRoute(MAX_VALUE)
                .setMaxConnTotal(MAX_VALUE);
        if (disableRedirects) {
            client.disableRedirectHandling();
        }
        return client.build();
    }

    protected static HttpPost postObjMethod() {
        return postObjMethod("/");
    }

    protected static HttpPost postObjMethod(final String id) {
        return new HttpPost(serverAddress + id);
    }

    protected static HttpPut putObjMethod(final String id) {
        return new HttpPut(serverAddress + id);
    }

    protected static HttpGet getObjMethod(final String id) {
        return new HttpGet(serverAddress + id);
    }

    protected static HttpHead headObjMethod(final String id) {
        return new HttpHead(serverAddress + id);
    }

    protected static HttpDelete deleteObjMethod(final String id) {
        return new HttpDelete(serverAddress + id);
    }

    protected static HttpPatch patchObjMethod(final String id) {
        return new HttpPatch(serverAddress + id);
    }

    protected static HttpPut putDSMethod(final String pid, final String ds, final String content)
            throws UnsupportedEncodingException {
        final HttpPut put = new HttpPut(serverAddress + pid + "/" + ds);
        put.setEntity(new StringEntity(content == null ? "" : content));
        put.setHeader(CONTENT_TYPE, TEXT_PLAIN);
        put.setHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
        return put;
    }

    protected static HttpPut putObjMethod(final String pid, final String contentType, final String content)
            throws UnsupportedEncodingException {
        final HttpPut put = new HttpPut(serverAddress + pid);
        put.setEntity(new StringEntity(content));
        put.setHeader(CONTENT_TYPE, contentType);
        return put;
    }

    protected static HttpGet getDSMethod(final String pid, final String ds) {
        return new HttpGet(serverAddress + pid + "/" + ds);
    }

    protected static HttpGet getDSDescMethod(final String pid, final String ds) {
        return new HttpGet(serverAddress + pid + "/" + ds + "/" + FCR_METADATA);
    }

    /**
     * Execute an HTTP request and return the open response.
     *
     * @param req Request to execute
     * @return the open response
     * @throws IOException in case of an IOException
     */
    protected static CloseableHttpResponse execute(final HttpUriRequest req) throws IOException {
        logger.debug("Executing: " + req.getMethod() + " to " + req.getURI());
        return client.execute(req);
    }

    /**
     * Execute an HTTP request and close the response.
     *
     * @param req the request to execute
     */
    protected static void executeAndClose(final HttpUriRequest req) {
        logger.debug("Executing: " + req.getMethod() + " to " + req.getURI());
        try {
            execute(req).close();
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Execute an HTTP request with preemptive basic authentication.
     *
     * @param request the request to execute
     * @param username usename to use
     * @param password password to use
     * @return the open responses
     * @throws IOException in case of IOException
     */
    @SuppressWarnings("resource")
    protected CloseableHttpResponse executeWithBasicAuth(final HttpUriRequest request, final String username,
            final String password) throws IOException {
        final HttpHost target = new HttpHost(HOSTNAME, SERVER_PORT, PROTOCOL);
        final CredentialsProvider credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(new AuthScope(target.getHostName(), target.getPort()),
                new UsernamePasswordCredentials(username, password));
        final CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider)
                .build();
        final AuthCache authCache = new BasicAuthCache();
        final BasicScheme basicAuth = new BasicScheme();
        authCache.put(target, basicAuth);

        final HttpClientContext localContext = HttpClientContext.create();
        localContext.setAuthCache(authCache);
        return httpclient.execute(request, localContext);
    }

    /**
     * Retrieve the HTTP status code from an open response.
     *
     * @param response the open response
     * @return the HTTP status code of the response
     */
    protected static int getStatus(final HttpResponse response) {
        return response.getStatusLine().getStatusCode();
    }

    /**
     * Executes an HTTP request and returns the status code of the response, closing the response.
     *
     * @param req the request to execute
     * @return the HTTP status code of the response
     */
    protected static int getStatus(final HttpUriRequest req) {
        try (final CloseableHttpResponse response = execute(req)) {
            final int result = getStatus(response);
            if (!(result > 199) || !(result < 400)) {
                logger.warn("Got status {}", result);
                if (response.getEntity() != null) {
                    logger.trace(EntityUtils.toString(response.getEntity()));
                }
            }
            EntityUtils.consume(response.getEntity());
            return result;
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Executes an HTTP request and returns the first Location header in the response, then closes the response.
     *
     * @param req the request to execute
     * @return the value of the first Location header in the response
     * @throws IOException in case of IOException
     */
    protected static String getLocation(final HttpUriRequest req) throws IOException {
        try (final CloseableHttpResponse response = execute(req)) {
            EntityUtils.consume(response.getEntity());
            return getLocation(response);
        }
    }

    /**
     * Retrieve the value of the first Location header from an open HTTP response.
     *
     * @param response the open response
     * @return the value of the first Location header in the response
     */
    protected static String getLocation(final HttpResponse response) {
        return response.getFirstHeader("Location").getValue();
    }

    /**
    * Retrieve the value of the first Content-Location header from an open HTTP response.
    *
    * @param response the open response
    * @return the value of the first Content-Location header in the response
    */
    protected static String getContentLocation(final HttpResponse response) {
        return response.getFirstHeader(CONTENT_LOCATION).getValue();
    }

    protected String getContentType(final HttpUriRequest method) throws IOException {
        return getContentType(method, OK);
    }

    protected String getContentType(final HttpUriRequest method, final Status httpStatus) throws IOException {
        try (final CloseableHttpResponse response = execute(method)) {
            final int result = getStatus(response);
            assertEquals(httpStatus.getStatusCode(), result);
            EntityUtils.consume(response.getEntity());
            return response.getFirstHeader(CONTENT_TYPE).getValue();
        }
    }

    protected static Collection<String> getLinkHeaders(final HttpResponse response) {
        return getHeader(response, LINK);
    }

    protected Collection<String> getLinkHeaders(final HttpUriRequest method) throws IOException {
        try (final CloseableHttpResponse response = execute(method)) {
            return getLinkHeaders(response);
        }
    }

    protected static List<String> headerValues(final HttpResponse response, final String headerName) {
        return stream(response.getHeaders(headerName)).map(Header::getValue).map(s -> s.split(","))
                .flatMap(Arrays::stream).map(String::trim).collect(toList());
    }

    protected static Collection<String> getHeader(final HttpResponse response, final String header) {
        return stream(response.getHeaders(header)).map(Header::getValue).collect(toList());
    }

    /**
     * Executes an HTTP request and parses the RDF found in the response, returning it in a
     * {@link CloseableDataset}, then closes the response.
     *
     * @param client the client to use
     * @param req the request to execute
     * @return the graph retrieved
     * @throws IOException in case of IOException
     */
    private CloseableDataset getDataset(final CloseableHttpClient client, final HttpUriRequest req)
            throws IOException {
        if (!req.containsHeader(ACCEPT)) {
            req.addHeader(ACCEPT, "application/n-triples");
        }
        logger.debug("Retrieving RDF using mimeType: {}", req.getFirstHeader(ACCEPT));

        try (final CloseableHttpResponse response = client.execute(req)) {
            assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode());
            final CloseableDataset result = parseTriples(response.getEntity());
            logger.trace("Retrieved RDF: {}", result);
            return result;
        }

    }

    /**
     * Parses the RDF found in and HTTP response, returning it in a {@link CloseableDataset}.
     *
     * @param response the response to parse
     * @return the graph retrieved
     * @throws IOException in case of IOException
     */
    protected CloseableDataset getDataset(final HttpResponse response) throws IOException {
        assertEquals(OK.getStatusCode(), getStatus(response));
        final CloseableDataset result = parseTriples(response.getEntity());
        logger.trace("Retrieved RDF: {}", result);
        return result;
    }

    /**
     * Executes an HTTP request and parses the RDF found in the response, returning it in a
     * {@link CloseableDataset}, then closes the response.
     *
     * @param req the request to execute
     * @return the constructed graph
     * @throws IOException in case of IOException
     */
    protected CloseableDataset getDataset(final HttpUriRequest req) throws IOException {
        return getDataset(client, req);
    }

    protected Model getModel(final String pid) throws Exception {
        final Model model = createDefaultModel();
        try (final CloseableHttpResponse response = execute(new HttpGet(serverAddress + pid))) {
            model.read(response.getEntity().getContent(), serverAddress + pid, "TURTLE");
        }
        return model;
    }

    protected CloseableHttpResponse createObject() {
        return createObject("");
    }

    protected CloseableHttpResponse createObject(final String pid) {
        return createObjectWithLinkHeader(pid, null);
    }

    private CloseableHttpResponse createObjectWithLinkHeader(final String pid, final String linkHeader) {
        final HttpPost httpPost = postObjMethod("/");
        if (isNotEmpty(pid)) {
            httpPost.addHeader("Slug", pid);
        }
        if (isNotEmpty(linkHeader)) {
            httpPost.addHeader(LINK, linkHeader);
        }
        try {
            final CloseableHttpResponse response = execute(httpPost);
            assertEquals(CREATED.getStatusCode(), getStatus(response));
            return response;
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    protected void createObjectAndClose(final String pid) {
        try {
            createObject(pid).close();
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    protected void createObjectAndClose(final String pid, final String linkHeader) {
        try {
            createObjectWithLinkHeader(pid, linkHeader).close();
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    protected void createDatastream(final String pid, final String dsid, final String content) throws IOException {
        logger.trace("Attempting to create datastream for object: {} at datastream ID: {}", pid, dsid);
        assertEquals(CREATED.getStatusCode(), getStatus(putDSMethod(pid, dsid, content)));
    }

    protected CloseableHttpResponse setProperty(final String pid, final String propertyUri, final String value)
            throws IOException {
        return setProperty(pid, null, propertyUri, value);
    }

    private CloseableHttpResponse setProperty(final String id, final String txId, final String propertyUri,
            final String value) throws IOException {
        final HttpPatch postProp = new HttpPatch(serverAddress + (txId != null ? txId + "/" : "") + id);
        postProp.setHeader(CONTENT_TYPE, "application/sparql-update");
        final String updateString = "INSERT { <" + serverAddress + id + "> <" + propertyUri + "> \"" + value
                + "\" } WHERE { }";
        postProp.setEntity(new StringEntity(updateString));
        final CloseableHttpResponse dcResp = execute(postProp);
        assertEquals(dcResp.getStatusLine().toString(), NO_CONTENT.getStatusCode(), getStatus(dcResp));
        postProp.releaseConnection();
        return dcResp;
    }

    protected CloseableHttpResponse setDescriptionProperty(final String id, final String txId,
            final String propertyUri, final String value) throws IOException {
        final HttpPatch postProp = new HttpPatch(
                serverAddress + (txId != null ? txId + "/" : "") + id + "/fcr:metadata");
        postProp.setHeader(CONTENT_TYPE, "application/sparql-update");
        final String updateString = "INSERT { <" + serverAddress + id + "> <" + propertyUri + "> \"" + value
                + "\" } WHERE { }";
        postProp.setEntity(new StringEntity(updateString));
        final CloseableHttpResponse dcResp = execute(postProp);
        assertEquals(dcResp.getStatusLine().toString(), NO_CONTENT.getStatusCode(), getStatus(dcResp));
        postProp.releaseConnection();
        return dcResp;
    }

    /**
     * Creates a transaction, asserts that it's successful and returns the transaction location.
     *
     * @return string containing transaction location
     * @throws IOException exception thrown during the function
     */
    protected String createTransaction() throws IOException {
        final HttpPost createTx = new HttpPost(serverAddress + "fcr:tx");
        try (final CloseableHttpResponse response = execute(createTx)) {
            assertEquals(CREATED.getStatusCode(), getStatus(response));
            return getLocation(response);
        }
    }

    /**
     * Gets a random (but valid) id for use in testing. This id is guaranteed to be unique within runs of this
     * application.
     *
     * @return string containing new id
     */
    protected static String getRandomUniqueId() {
        return randomUUID().toString();
    }

    protected static void assertDeleted(final String id) {
        final String location = serverAddress + id;
        assertThat("Expected object to be deleted", getStatus(new HttpHead(location)), is(GONE.getStatusCode()));
        assertThat("Expected object to be deleted", getStatus(new HttpGet(location)), is(GONE.getStatusCode()));
    }

    protected static void assertNotDeleted(final String id) {
        final String location = serverAddress + id;
        assertThat("Expected object not to be deleted", getStatus(new HttpHead(location)), is(OK.getStatusCode()));
        assertThat("Expected object not to be deleted", getStatus(new HttpGet(location)), is(OK.getStatusCode()));
    }

    protected static String getTTLThatUpdatesServerManagedTriples(final String createdBy, final Calendar created,
            final String modifiedBy, final Calendar modified) {
        final StringBuilder ttl = new StringBuilder();
        if (createdBy != null) {
            addClause(ttl, CREATED_BY.getURI(), "\"" + createdBy + "\"");
        }
        if (created != null) {
            addClause(ttl, CREATED_DATE.getURI(), "\"" + DatatypeConverter.printDateTime(created)
                    + "\"^^<http://www.w3.org/2001/XMLSchema#dateTime>");
        }
        if (modifiedBy != null) {
            addClause(ttl, LAST_MODIFIED_BY.getURI(), "\"" + modifiedBy + "\"");
        }
        if (modified != null) {
            addClause(ttl, LAST_MODIFIED_DATE.getURI(), "\"" + DatatypeConverter.printDateTime(modified)
                    + "\"^^<http://www.w3.org/2001/XMLSchema#dateTime>");
        }
        ttl.append(" .\n");
        return ttl.toString();

    }

    private static void addClause(final StringBuilder ttl, final String predicateUri, final String literal) {
        if (ttl.length() == 0) {
            ttl.append("<>");
        } else {
            ttl.append(" ;\n");
        }
        ttl.append(" <" + predicateUri + "> ");
        ttl.append(literal);
    }

    /**
     * Test a response for the absence of a specific LINK header
     *
     * @param response the HTTP response
     * @param uri the URI not to exist in the LINK header
     * @param rel the rel argument to check for
     */
    protected static void assertNoLinkHeader(final HttpResponse response, final String uri, final String rel) {
        assertEquals(0, countLinkHeader(response, uri, rel));
    }

    /**
     * Test a response for a specific LINK header
     *
     * @param response the HTTP response
     * @param uri the URI expected in the LINK header
     * @param rel the rel argument to check for
     */
    protected static void checkForLinkHeader(final HttpResponse response, final String uri, final String rel) {
        assertEquals(1, countLinkHeader(response, uri, rel));
    }

    /**
     * Utility for counting LINK headers
     *
     * @param response the HTTP response
     * @param uri the URI expected in the LINK header
     * @param rel the rel argument to check for
     * @return the count of LINK headers.
     */
    private static int countLinkHeader(final HttpResponse response, final String uri, final String rel) {
        final Link linkA = Link.valueOf("<" + uri + ">; rel=" + rel);
        return (int) Arrays.stream(response.getHeaders(LINK)).filter(x -> {
            final Link linkB = Link.valueOf(x.getValue());
            return linkB.equals(linkA);
        }).count();
    }

    protected static String getOriginalResourceUri(final CloseableHttpResponse response) {
        return getLinkHeaders(response).stream().map(x -> Link.valueOf(x))
                .filter(x -> x.getRel().equals("original")).findFirst().get().getUri().toString();
    }

    protected String getExternalContentLinkHeader(final String url, final String handling, final String mimeType) {
        // leave lots of room to leave things out of the link to test variations.
        String link = "";
        if (url != null && !url.isEmpty()) {
            link += "<" + url + ">";
        }

        link += "; rel=\"" + EXTERNAL_CONTENT + "\"";

        if (handling != null && !handling.isEmpty()) {
            link += "; handling=\"" + handling + "\"";
        }

        if (mimeType != null && !mimeType.isEmpty()) {
            link += "; type=\"" + mimeType + "\"";
        }
        return link;
    }

    protected static void assertConstrainedByPresent(final CloseableHttpResponse response) {
        final Collection<String> linkHeaders = getLinkHeaders(response);
        assertTrue("Constrained by link header not present",
                linkHeaders.stream().map(Link::valueOf).anyMatch(l -> l.getRel().equals(CONSTRAINED_BY.getURI())));
    }

}