org.fcrepo.test.api.TestRESTAPI.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.test.api.TestRESTAPI.java

Source

/* The contents of this file are subject to the license and copyright terms
 * detailed in the license directory at the root of the source tree (also
 * available online at http://fedora-commons.org/license/).
 */

package org.fcrepo.test.api;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.apache.http.HttpStatus.SC_CREATED;
import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.apache.http.HttpStatus.SC_MOVED_TEMPORARILY;
import static org.apache.http.HttpStatus.SC_NOT_FOUND;
import static org.apache.http.HttpStatus.SC_OK;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import static org.custommonkey.xmlunit.XMLAssert.assertXpathExists;

import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import junit.framework.JUnit4TestAdapter;

import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.io.FileUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.AbstractContentBody;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.NamespaceContext;
import org.custommonkey.xmlunit.SimpleNamespaceContext;
import org.custommonkey.xmlunit.XMLUnit;
import org.fcrepo.client.FedoraClient;
import org.fcrepo.common.Constants;
import org.fcrepo.common.Models;
import org.fcrepo.common.PID;
import org.fcrepo.server.access.FedoraAPIAMTOM;
import org.fcrepo.server.management.FedoraAPIMMTOM;
import org.fcrepo.server.types.gen.Datastream;
import org.fcrepo.server.types.gen.FieldSearchQuery;
import org.fcrepo.server.types.gen.FieldSearchResult;
import org.fcrepo.server.types.gen.ObjectFields;
import org.fcrepo.server.types.mtom.gen.MIMETypedStream;
import org.fcrepo.server.utilities.TypeUtility;
import org.fcrepo.test.FedoraServerTestCase;
import org.jrdf.graph.Triple;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.trippi.RDFFormat;
import org.trippi.TripleIterator;
import org.trippi.TrippiException;
import org.trippi.io.TripleIteratorFactory;

/**
 * Tests of the REST API. Tests assume a running instance of Fedora with the
 * REST API enabled. //TODO: actually validate the ResponseBody instead of just
 * HTTP status codes
 *
 * @author Edwin Shin
 * @author Bill Branan
 * @version $Id$
 * @since 3.0
 */
public class TestRESTAPI extends FedoraServerTestCase {

    private static final String TEXT_XML = "text/xml";

    private static final Logger LOGGER = LoggerFactory.getLogger(TestRESTAPI.class);

    private static FedoraClient s_client;

    private FedoraAPIAMTOM apia;

    private FedoraAPIMMTOM apim;

    // used for determining test configuration
    private static String authAccessProperty = "fedora.authorize.access";

    protected Boolean authorizeAccess = null;

    private static String DEMO_OWNERID = "nondefaultOwner";

    private static String DEMO_REST;

    private static byte[] DEMO_REST_FOXML;

    private static String DEMO_MIN;

    private static String DEMO_MIN_PID;

    private final static PID DEMO_REST_PID = PID.getInstance("demo:REST");

    private static final String REST_RESOURCE_PATH = System
            .getProperty("fcrepo-integrationtest-core.classes") != null
                    ? System.getProperty("fcrepo-integrationtest-core.classes") + "rest"
                    : "src/test/resources/rest";

    private static String DS1RelsFilename = "Datastream 1 filename from rels.extension";

    private static String DS2LabelFilename = "Datastream 2 filename from label";

    private static String DS3LabelFilename = "Datastream 3 filename from label";

    private static String DodgyChars = "\\/*?<>:|";

    private static String DS4LabelFilenameOriginal = "Datastream 4 filename " + DodgyChars + "from label";

    // this one in foxml
    private static String DS4LabelFilename = "Datastream 4 filename from label"; // this should be the cleaned version

    private static String DS5ID = "DS5";

    private static String DS6ID = "DS6.xml";

    // note: since we are explicitly formatting the date as "Z" (literal), need to ensure the formatter operates in GMT/UTC
    static SimpleDateFormat df;
    static {
        df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        df.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    private static final ValidatorHelper validator = new ValidatorHelper();

    private static final Date now = new Date();

    private static final String datetime = df.format(now);

    // tests for this must not be dependent on which object, so we will
    // use a constant that predates the system objects
    private static final String earlierDateTime = "2001-01-01T00:00:00.000Z";

    private boolean chunked = false;

    protected static FedoraClient initClient() throws Exception {
        s_client = getFedoraClient();
        return s_client;
    }

    protected static void stopClient() {
        s_client.shutdown();
        s_client = null;
    }

    @BeforeClass
    public static void bootStrap() throws Exception {
        Map<String, String> nsMap = new HashMap<String, String>();
        nsMap.put("management", "http://www.fedora.info/definitions/1/0/management/");
        nsMap.put("access", "http://www.fedora.info/definitions/1/0/access/");
        NamespaceContext ctx = new SimpleNamespaceContext(nsMap);
        XMLUnit.setXpathNamespaceContext(ctx);

        initClient();

        ingestImageManipulationDemoObjects(s_client);
        ingestDocumentTransformDemoObjects(s_client);

        if (DEMO_MIN == null)
            DEMO_MIN = FileUtils.readFileToString(new File(REST_RESOURCE_PATH + "/demo_min.xml"), "UTF-8");
        if (DEMO_MIN_PID == null)
            DEMO_MIN_PID = FileUtils.readFileToString(new File(REST_RESOURCE_PATH + "/demo_min_pid.xml"), "UTF-8");
        if (DEMO_REST == null) {
            StringTemplate tpl = new StringTemplate(
                    FileUtils.readFileToString(new File(REST_RESOURCE_PATH + "/demo_rest.xml"), "UTF-8"));
            tpl.setAttribute("MODEL_DOWNLOAD_FILENAME", MODEL.DOWNLOAD_FILENAME.localName);
            tpl.setAttribute("DS1_RELS_FILENAME", DS1RelsFilename);
            tpl.setAttribute("MODEL_URI", MODEL.uri);
            tpl.setAttribute("DATETIME", datetime);
            tpl.setAttribute("FEDORA_BASE_URL", getBaseURL());
            tpl.setAttribute("DS2_LABEL_FILENAME", DS2LabelFilename);
            tpl.setAttribute("DS3_LABEL_FILENAME", DS3LabelFilename);
            tpl.setAttribute("DS4_LABEL_FILENAME_ORIGINAL", DS4LabelFilenameOriginal);

            DEMO_REST = tpl.toString();
            DEMO_REST_FOXML = DEMO_REST.getBytes("UTF-8");
        }
    }

    @AfterClass
    public static void cleanUp() throws Exception {
        purgeDemoObjects(s_client);
        stopClient();
        XMLUnit.setXpathNamespaceContext(SimpleNamespaceContext.EMPTY_CONTEXT);
    }

    @Before
    public void setUp() throws Exception {
        apia = s_client.getAPIAMTOM();
        apim = s_client.getAPIMMTOM();
        apim.ingest(TypeUtility.convertBytesToDataHandler(DEMO_REST_FOXML), FOXML1_1.uri,
                "TestRESTAPI.setUp: ingesting new foxml object " + DEMO_REST_PID);
    }

    @After
    public void tearDown() throws Exception {
        apim.purgeObject(DEMO_REST_PID.toString(), "TestRESTAPI.tearDown: purging " + DEMO_REST_PID, false);
    }

    protected static byte[] readBytes(HttpResponse response) {
        byte[] body = new byte[0];
        if (response.getEntity() != null) {
            try {
                body = EntityUtils.toByteArray(response.getEntity());
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
        return body;
    }

    protected static String readString(HttpResponse response) {
        return readString(response, Charset.forName("UTF-8"));
    }

    protected static String readString(HttpResponse response, Charset charset) {
        String body = null;
        if (response.getEntity() != null) {
            try {
                body = EntityUtils.toString(response.getEntity(), charset);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
        return (body != null) ? body : "";
    }

    /**
     * Do Access requests require auth based on current test configuration?
     *
     * @return true if auth is required for API-requests
     */
    private boolean getAuthAccess() {
        if (authorizeAccess == null) {
            String property = System.getProperty(authAccessProperty);
            if (property.equals("true")) {
                authorizeAccess = true;
            } else if (property.equals("false")) {
                authorizeAccess = false;
            } else {
                assertTrue("Failed to determine whether to perform authorization on Access requests from: "
                        + authAccessProperty, false);
                throw new RuntimeException(
                        "Failed to determine whether to perform authorization on Access requests from: "
                                + authAccessProperty);
            }

        }
        return authorizeAccess;

    }

    protected void verifyDELETEStatusOnly(URI url, int expected, boolean authenticate) throws Exception {
        HttpDelete get = new HttpDelete(url);
        HttpResponse response = getOrDelete(get, authenticate, false);
        int status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(expected, status);
    }

    protected void verifyGETStatusOnly(URI url, int expected, boolean authenticate, boolean validate)
            throws Exception {
        HttpGet get = new HttpGet(url);
        HttpResponse response = getOrDelete(get, authenticate, validate);
        int status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(expected, status);
    }

    protected void verifyGETStatusOnly(URI url, int expected, boolean validate) throws Exception {
        verifyGETStatusOnly(url, expected, getAuthAccess(), validate);
    }

    protected void verifyGETStatusOnly(URI url, int expected) throws Exception {
        verifyGETStatusOnly(url, expected, false);
    }

    protected String verifyGETStatusString(URI url, int expected, boolean authenticate, boolean validate)
            throws Exception {
        HttpGet get = new HttpGet(url);
        HttpResponse response = getOrDelete(get, authenticate, validate);
        int status = response.getStatusLine().getStatusCode();
        String result = readString(response);
        assertEquals(expected, status);
        return result;
    }

    protected byte[] verifyGETStatusBytes(URI url, int expected, boolean authenticate, boolean validate)
            throws Exception {
        HttpGet get = new HttpGet(url);
        HttpResponse response = getOrDelete(get, authenticate, validate);
        int status = response.getStatusLine().getStatusCode();
        byte[] result = readBytes(response);
        assertEquals(expected, status);
        return result;
    }

    protected static StringEntity getStringEntity(String content, String contentType) {
        if (content == null) {
            return null;
        }

        StringEntity entity = new StringEntity(content, Charset.forName("UTF-8"));
        if (contentType != null) {
            entity.setContentType(contentType);
        }
        return entity;
    }

    protected void verifyPOSTStatusOnly(URI url, int expected, StringEntity content, boolean authenticate)
            throws Exception {
        HttpPost post = new HttpPost(url);
        HttpResponse response = putOrPost(post, content, authenticate);
        int status = response.getStatusLine().getStatusCode();
        if (status == SC_MOVED_TEMPORARILY) {
            String original = url.toString();
            url = URI.create(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
            if (!original.equals(url.toString())) {
                EntityUtils.consumeQuietly(response.getEntity());
                post = new HttpPost(url);
                response = putOrPost(post, content, true);
                status = response.getStatusLine().getStatusCode();
            }
        }
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(expected, status);
    }

    protected void verifyPOSTStatusOnly(URI url, int expected, StringEntity content, boolean authenticate,
            boolean validate) throws Exception {
        HttpPost post = new HttpPost(url);
        HttpResponse response = putOrPost(post, content, authenticate);
        int status = response.getStatusLine().getStatusCode();
        if (status == SC_MOVED_TEMPORARILY) {
            String original = url.toString();
            url = URI.create(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
            if (!original.equals(url.toString())) {
                EntityUtils.consumeQuietly(response.getEntity());
                post = new HttpPost(url);
                response = putOrPost(post, content, true);
                status = response.getStatusLine().getStatusCode();
            }
        }
        if (validate) {
            validateResponse(url, response);
        } else {
            EntityUtils.consumeQuietly(response.getEntity());
        }
        assertEquals(expected, status);
    }

    protected void verifyPUTStatusOnly(URI url, int expected, StringEntity content, boolean authenticate)
            throws Exception {
        HttpPut put = new HttpPut(url);
        HttpResponse response = putOrPost(put, content, authenticate);
        int status = response.getStatusLine().getStatusCode();
        if (status == SC_MOVED_TEMPORARILY) {
            String original = url.toString();
            url = URI.create(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
            if (!original.equals(url.toString())) {
                EntityUtils.consumeQuietly(response.getEntity());
                put = new HttpPut(url);
                response = putOrPost(put, content, true);
                status = response.getStatusLine().getStatusCode();
            }
        }
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(expected, status);
    }

    protected void verifyNoAuthFailOnAPIAAuth(URI url) throws Exception {
        if (this.getAuthAccess()) {
            HttpGet get = new HttpGet(url);
            HttpResponse response = getOrDelete(get, false, false);
            int status = response.getStatusLine().getStatusCode();
            EntityUtils.consumeQuietly(response.getEntity());
            assertEquals(SC_UNAUTHORIZED, status);
        }
    }

    @Test
    public void testGetWADL() throws Exception {
        URI url = getURI("/objects/application.wadl");

        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK);
    }

    //public void testDescribeRepository() throws Exception {}

    // API-A

    @Test
    public void testGetObjectProfile() throws Exception {
        URI url = getURI(String.format("/objects/%s", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        assertEquals(SC_OK, status);

        url = getURI(String.format("/objects/%s?format=xml", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), true);
        status = response.getStatusLine().getStatusCode();
        String responseXML = readString(response);
        assertEquals(SC_OK, status);
        assertTrue(responseXML.contains("<objLabel>"));
        assertTrue(responseXML.contains("<objOwnerId>"));
        assertTrue(responseXML.contains("<objCreateDate>"));
        assertTrue(responseXML.contains("<objLastModDate>"));
        assertTrue(responseXML.contains("<objDissIndexViewURL>"));
        assertTrue(responseXML.contains("<objItemIndexViewURL>"));
        assertTrue(responseXML.contains("<objState>"));

        url = getURI(String.format("/objects/%s?asOfDateTime=%s", DEMO_REST_PID.toString(), datetime));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK);

        // sanity check
        url = getURI(String.format("/objects/%s", "demo:BOGUS_PID"));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_NOT_FOUND);
    }

    @Test
    public void testListMethods() throws Exception {
        URI url = getURI(String.format("/objects/%s/methods", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK);

        url = getURI(String.format("/objects/%s/methods?format=xml", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK);

        url = getURI(String.format("/objects/%s/methods?asOfDateTime=%s", DEMO_REST_PID.toString(), datetime));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK);
    }

    @Test
    public void testListMethodsForSDep() throws Exception {
        URI url = getURI(String.format("/objects/%s/methods/fedora-system:3", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);

        url = getURI(String.format("/objects/%s/methods/fedora-system:3?format=xml", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), true);
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());
        String responseXML = readString(response);
        assertTrue(responseXML.contains("sDef=\"fedora-system:3\""));

        url = getURI(String.format("/objects/%s/methods/fedora-system:3?asOfDateTime=%s", DEMO_REST_PID.toString(),
                datetime));
        verifyNoAuthFailOnAPIAAuth(url);
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
    }

    //
    // GETs on built-in Service Definition methods
    //

    @Test
    public void testGETMethodBuiltInBadMethod() throws Exception {
        URI url = getURI(
                String.format("/objects/%s/methods/fedora-system:3/noSuchMethod", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertFalse(SC_OK == status);
    }

    @Test
    public void testGETMethodBuiltInBadUserArg() throws Exception {
        URI url = getURI(String.format("/objects/%s/methods/fedora-system:3/viewMethodIndex?noSuchArg=foo",
                DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response = getOrDelete(get, getAuthAccess(), false);
        int status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertFalse(SC_OK == status);
    }

    @Test
    public void testGETMethodBuiltInNoArg() throws Exception {
        URI url = getURI(
                String.format("/objects/%s/methods/fedora-system:3/viewMethodIndex", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
    }

    //
    // GETs on custom Service Definition methods
    //

    @Test
    public void testGETMethodCustomBadMethod() throws Exception {
        URI url = getURI("/objects/demo:14/methods/demo:12/noSuchMethod");
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertFalse(SC_OK == status);
    }

    @Test
    public void testGETMethodCustomBadUserArg() throws Exception {
        URI url = getURI("/objects/demo:14/methods/demo:12/getDocumentStyle1?noSuchArg=foo");
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response = getOrDelete(get, getAuthAccess(), false);
        int status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertFalse(SC_OK == status);
    }

    @Test
    public void testGETMethodCustomNoArg() throws Exception {
        URI url = getURI("/objects/demo:14/methods/demo:12/getDocumentStyle1");
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
    }

    @Test
    public void testGETMethodCustomGoodUserArg() throws Exception {
        URI url = getURI("/objects/demo:29/methods/demo:27/resizeImage?width=50");
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), true);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
        // imageManip service does not return content length
        //assertEquals(1486, response.getResponseBody().length);
        //assertEquals("1486", response.getResponseHeader("Content-Length").getValue());
        assertEquals("image/jpeg", response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
    }

    @Test
    public void testGETMethodCustomGoodUserArgGoodDate() throws Exception {
        URI url = getURI("/objects/demo:29/methods/demo:27/resizeImage?width=50&asOfDateTime=" + datetime);
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), true);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
        // imageManip service does not return content length
        //assertEquals(1486, response.getResponseBody().length);
        //assertEquals("1486", response.getResponseHeader("Content-Length").getValue());
        assertEquals("image/jpeg", response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
    }

    @Test
    public void testGETMethodCustomUserArgBadDate() throws Exception {
        URI url = getURI("/objects/demo:14/methods/demo:12/getDocumentStyle1?width=50&asOfDateTime=badDate");
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertFalse(SC_OK == status);
    }

    @Test
    public void testGETMethodCustomUserArgEarlyDate() throws Exception {
        URI url = getURI(
                "/objects/demo:14/methods/demo:12/getDocumentStyle1?width=50&asOfDateTime=1999-11-21T16:38:32.200Z");
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_NOT_FOUND, status);
    }

    @Test
    public void testGETMethodCustomGoodDate() throws Exception {
        URI url = getURI("/objects/demo:14/methods/demo:12/getDocumentStyle1?asOfDateTime=" + datetime);
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), true);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
    }

    @Test
    public void testGETMethodCustomBadDate() throws Exception {
        URI url = getURI("/objects/demo:14/methods/demo:12/getDocumentStyle1?asOfDateTime=badDate");
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertFalse(SC_OK == status);
    }

    @Test
    public void testGETMethodCustomEarlyDate() throws Exception {
        URI url = getURI(
                "/objects/demo:14/methods/demo:12/getDocumentStyle1?asOfDateTime=1999-11-21T16:38:32.200Z");
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        int status = 0;
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_NOT_FOUND, status);
    }

    @Test
    public void testListDatastreams() throws Exception {
        URI url = getURI(String.format("/objects/%s/datastreams", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK, true, true);

        url = getURI(String.format("/objects/%s/datastreams?format=xml", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK, true, true);

        url = getURI(String.format("/objects/%s/datastreams?asOfDateTime=%s", DEMO_REST_PID.toString(), datetime));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK, true, true);

        url = getURI(String.format("/objects/%s/datastreams?format=xml&profiles=true", DEMO_REST_PID.toString()));
        // this will always require authN with the profiles option on,
        // as it is an apim function
        verifyGETStatusOnly(url, SC_UNAUTHORIZED, false, false);
        String responseString = verifyGETStatusString(url, SC_OK, true, true);
        assertTrue(responseString.indexOf("datastreamProfile") > -1);
    }

    // if an object is marked as deleted, only administrators have access (default XACML policy)
    // see FCREPO-753: this also tests that preemptive auth can still take place when api-a auth is not required
    @Test
    public void testDeletedObject() throws Exception {
        // only test if api-a auth is disabled (configA)
        if (!this.getAuthAccess()) {

            // mark object as deleted
            String modResponse = apim.modifyObject(DEMO_REST_PID.toString(), "D", null, null,
                    "Mark object as deleted");

            LOGGER.info(modResponse);
            // verify no unauth access
            URI url = getURI(String.format("/objects/%s/datastreams", DEMO_REST_PID.toString()));
            LOGGER.info("Testing object marked deleted at {}", url);
            verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
            verifyGETStatusString(url, SC_OK, true, true);
        }

    }

    @Test
    public void testGetDatastreamProfile() throws Exception {
        // Datastream profile in HTML format
        URI url = getURI(String.format("/objects/%s/datastreams/RELS-EXT", DEMO_REST_PID.toString()));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        String responseXML = verifyGETStatusString(url, SC_OK, true, true);
        assertTrue(responseXML.contains("<html>"));

        // Datastream profile in XML format
        url = getURI(String.format("/objects/%s/datastreams/RELS-EXT?format=xml", DEMO_REST_PID.toString()));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        responseXML = verifyGETStatusString(url, SC_OK, true, true);
        assertTrue(responseXML.contains("<dsLabel>"));

        // Datastream profile as of the current date-time (XML format)
        url = getURI(String.format("/objects/%s/datastreams/RELS-EXT?asOfDateTime=%s&format=xml",
                DEMO_REST_PID.toString(), datetime));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        // FIXME: validation disabled
        // response is currently not schema-valid, see fcrepo-866, fcrepo-612
        // -> get(true) once fixed to enable validation
        responseXML = verifyGETStatusString(url, SC_OK, true, false);
        assertTrue(responseXML.contains("<dsLabel>"));

        // sanity check
        url = getURI(String.format("/objects/%s/datastreams/BOGUS_DSID", DEMO_REST_PID.toString()));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        verifyGETStatusString(url, SC_NOT_FOUND, true, true);
    }

    @Test
    public void testGetDatastreamHistory() throws Exception {

        // Ingest minimal object
        URI url = getURI("/objects/new");
        StringEntity entity = getStringEntity(DEMO_MIN_PID, TEXT_XML);
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPOSTStatusOnly(url, SC_CREATED, entity, true);

        // Get history in XML format
        url = getURI(String.format("/objects/demo:1234/datastreams/DS1/history?format=xml"));
        verifyNoAuthFailOnAPIAAuth(url);

        String responseXML = verifyGETStatusString(url, SC_OK, true, true);

        String control = FileUtils.readFileToString(new File(REST_RESOURCE_PATH + "/datastreamHistory.xml"),
                "UTF-8");
        StringTemplate tpl = new StringTemplate(control);
        tpl.setAttribute("FEDORA_BASE_URL", getProtocol() + "://" + getHost() + ":" + getPort());

        // Diff must be identical
        XMLUnit.setIgnoreWhitespace(true);
        Diff xmldiff = new Diff(tpl.toString(), responseXML);
        assertTrue(xmldiff.toString(), xmldiff.identical());

        // Sanity check
        url = getURI("/objects/demo:1234/datastreams/BOGUS_DSID");
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        // APIM function requires authentication
        verifyGETStatusOnly(url, SC_NOT_FOUND, true, false);

        // Clean up
        url = getURI("/objects/demo:1234");
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);
    }

    @Test
    public void testGetDatastreamDissemination() throws Exception {
        URI url = getURI(String.format("/objects/%s/datastreams/RELS-EXT/content", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        HttpGet get = new HttpGet(url);
        HttpResponse response = getOrDelete(get, getAuthAccess(), false);
        int status = response.getStatusLine().getStatusCode();
        Header length = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
        String responseXML = readString(response);
        assertEquals(SC_OK, status);
        assertNotNull(length);
        assertNotNull(length.getValue());
        long lengthlong = Long.parseLong(length.getValue());
        assertTrue(lengthlong > 0);
        assertTrue(responseXML.contains("rdf:Description"));

        url = getURI(String.format("/objects/%s/datastreams/RELS-EXT/content?asOfDateTime=%s",
                DEMO_REST_PID.toString(), datetime));
        verifyNoAuthFailOnAPIAAuth(url);
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        length = response.getFirstHeader(HttpHeaders.CONTENT_LENGTH);
        responseXML = readString(response);
        assertEquals(SC_OK, status);
        assertNotNull(length);
        assertNotNull(length.getValue());
        lengthlong = Long.parseLong(length.getValue());
        assertTrue(lengthlong > 0);

        assertTrue(responseXML.contains("rdf:Description"));

        // sanity check
        url = getURI(String.format("/objects/%s/datastreams/BOGUS_DSID/content", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusString(url, SC_NOT_FOUND, getAuthAccess(), false);
    }

    @Test
    public void testFindObjects() throws Exception {
        URI url = getURI(
                String.format("/objects?pid=true&terms=%s&query=&resultFormat=xml", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        // FIXME: findObjects should have a schema?  remove "false" to enable validation
        verifyGETStatusOnly(url, SC_OK, false);
    }

    /**
     * test case for FCREPO-867. Since all SQL statements have been switched to
     * PreparedStatement it's safe to use a singlequote in a search query.
     * @throws Exception
     */
    @Test
    public void testFindObjectWithSingleQuote() throws Exception {
        URI url = getURI(String.format(
                "/objects?pid=true&description=true&terms='Coliseum'&query&maxResults=20&resultFormat=xml",
                DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        // FIXME: findObjects should have a schema?  remove "false" to enable validation
        String xmlResult = verifyGETStatusString(url, SC_OK, getAuthAccess(), false);
        //        System.out.println("ResponseBody: " + xmlResult);
        assertTrue(xmlResult.indexOf("<pid>demo:REST</pid>") > 0);
    }

    /**
     * Disabled until FCREPO-798 is public public void testFindObjectsQuery()
     * throws Exception { String templateUrl = "/search?$value$";
     * TemplatedResourceIterator tri = new
     * TemplatedResourceIterator(templateUrl,
     * "src/test/resources/APIM2/restsearchvalues"); while (tri.hasNext()) { url
     * = tri.next(); HttpResponse resp = get(getAuthAccess());
     * //assertEquals(SC_OK, resp.getStatusCode()); url =
     * String.format("/objects/new"); HttpResponse response = post("", true);
     * assertEquals(SC_CREATED, response.getStatusCode()); } }
     */

    @Test
    public void testResumeFindObjects() throws Exception {
        // there are only the system objects and 8 demo objects, so maxResults must be constrained
        URI url = getURI("/objects?pid=true&query=&resultFormat=xml&maxResults=4");
        verifyNoAuthFailOnAPIAAuth(url);
        // FIXME: resumeFindObjects should have a schema?  remove "false" to enable validation
        String responseXML = verifyGETStatusString(url, SC_OK, getAuthAccess(), false);
        String sessionToken = responseXML.substring(responseXML.indexOf("<token>") + 7,
                responseXML.indexOf("</token>"));

        url = getURI(String.format("/objects?pid=true&query=&resultFormat=xml&sessionToken=%s", sessionToken));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK);
    }

    @Test
    public void testFindObjectsBadSyntax() throws Exception {
        URI url = getURI("/objects?pid=true&query=label%3D%3F&maxResults=20");
        // Try > 100 times; will hang if the connection isn't properly released
        for (int i = 0; i < 101; i++) {
            verifyGETStatusOnly(url, SC_INTERNAL_SERVER_ERROR, false);
        }

    }

    @Test
    public void testGetObjectHistory() throws Exception {
        URI url = getURI(String.format("/objects/%s/versions", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK);

        url = getURI(String.format("/objects/%s/versions?format=xml", DEMO_REST_PID.toString()));
        verifyNoAuthFailOnAPIAAuth(url);
        verifyGETStatusOnly(url, SC_OK);
    }

    private String extractPid(String source) {
        Matcher m = Pattern.compile("^.*/([^/]+$)").matcher(source);
        String pid = null;
        if (m.find() && m.groupCount() == 1) {
            pid = m.group(1);
        }
        pid = pid.replaceAll("\n", "").replaceAll("\r", "").replaceAll("%3A", ":");
        return pid;
    }

    // API-M

    @Test
    public void testIngest() throws Exception {
        // Create new empty object
        URI url = getURI(String.format("/objects/new"));
        StringEntity entity = getStringEntity("", TEXT_XML);
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        HttpPost post = new HttpPost(url);
        HttpResponse response = putOrPost(post, entity, true);

        String responseBody = readString(response);
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());

        String emptyObjectPid = extractPid(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
        assertNotNull(emptyObjectPid);
        // PID should be returned as a header and as the response body
        assertTrue(responseBody.equals(emptyObjectPid));

        // Delete empty object
        url = getURI(String.format("/objects/%s", emptyObjectPid));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);

        // Ensure that GETs of the deleted object immediately give 404s (See FCREPO-594)
        verifyGETStatusOnly(url, SC_NOT_FOUND);

        // Create new empty object with a PID namespace specified
        url = getURI(String.format("/objects/new?namespace=test"));
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        post = new HttpPost(url);
        response = putOrPost(post, entity, true);

        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());

        emptyObjectPid = extractPid(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
        assertTrue(emptyObjectPid.startsWith("test"));

        // Delete empty "test" object
        url = getURI(String.format("/objects/%s", emptyObjectPid));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);

        // Delete the demo:REST object (ingested as part of setup)
        url = getURI(String.format("/objects/%s", DEMO_REST_PID.toString()));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);

        // Create a new empty demo:REST object with parameterized ownerId
        url = getURI(String.format("/objects/%s?ownerId=%s", DEMO_REST_PID.toString(), DEMO_OWNERID));
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, null, false);
        post = new HttpPost(url);
        response = putOrPost(post, entity, true);

        responseBody = readString(response);
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());
        Header locationHeader = response.getFirstHeader(HttpHeaders.LOCATION);
        assertNotNull(locationHeader);
        assertTrue(locationHeader.getValue().contains(URLEncoder.encode(DEMO_REST_PID.toString(), "UTF-8")));
        assertTrue(responseBody.equals(DEMO_REST_PID.toString()));
        // verify ownerId
        responseBody = verifyGETStatusString(url, SC_OK, true, true);
        assertTrue(responseBody.indexOf(DEMO_OWNERID) > 0);

        // Delete the demo:REST object (ingested as part of setup)
        url = getURI(String.format("/objects/%s", DEMO_REST_PID.toString()));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);

        // Ingest the demo:REST object
        url = getURI(String.format("/objects/%s", DEMO_REST_PID.toString()));
        entity = getStringEntity(DEMO_REST, TEXT_XML);
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        post = new HttpPost(url);
        response = putOrPost(post, entity, true);
        responseBody = readString(response);
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());
        locationHeader = response.getFirstHeader(HttpHeaders.LOCATION);
        assertNotNull(locationHeader);
        assertTrue(locationHeader.getValue().contains(URLEncoder.encode(DEMO_REST_PID.toString(), "UTF-8")));
        assertTrue(responseBody.equals(DEMO_REST_PID.toString()));

        // Ingest minimal object with no PID
        url = getURI(String.format("/objects/new"));
        entity = getStringEntity(DEMO_MIN, TEXT_XML);
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        post = new HttpPost(url);
        response = putOrPost(post, entity, true);
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());

        // Delete minimal object
        String minimalObjectPid = extractPid(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
        url = getURI(String.format("/objects/%s", minimalObjectPid));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);
    }

    // Tests FCREPO-509

    @Test
    public void testIngestWithParameterPid() throws Exception {

        // Ingest minimal object with PID, use "new" as path parameter -> must succeed
        URI url = getURI(String.format("/objects/new"));
        StringEntity entity = getStringEntity(DEMO_MIN_PID, TEXT_XML);
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPOSTStatusOnly(url, SC_CREATED, entity, true);

        // clean up
        url = getURI(String.format("/objects/%s", "demo:1234"));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);

        // Ingest minimal object with PID, use a different PID than the one
        // specified in the foxml -> must fail
        url = getURI(String.format("/objects/%s", "demo:234"));
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPOSTStatusOnly(url, SC_INTERNAL_SERVER_ERROR, entity, true);

        // Ingest minimal object with PID equals to the PID specified in the foxml
        url = getURI(String.format("/objects/%s", "demo:1234"));
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPOSTStatusOnly(url, SC_CREATED, entity, true);

        // clean up
        url = getURI(String.format("/objects/%s", "demo:1234"));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);

        // Ingest minimal object with no PID, specify a PID parameter in the request
        url = getURI(String.format("/objects/%s", "demo:234"));
        entity = getStringEntity(DEMO_MIN, TEXT_XML);
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPOSTStatusOnly(url, SC_CREATED, entity, true);

        // clean up
        url = getURI(String.format("/objects/%s", "demo:234"));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);

    }

    @Test
    public void testModifyObject() throws Exception {
        URI url = getURI(String.format("/objects/%s?label=%s", DEMO_REST_PID.toString(), "foo"));
        StringEntity entity = getStringEntity("", TEXT_XML);
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPUTStatusOnly(url, SC_OK, entity, true);
        assertEquals("foo", apia.getObjectProfile(DEMO_REST_PID.toString(), null).getObjLabel());
    }

    @Test
    public void testGetObjectXML() throws Exception {
        URI url = getURI(String.format("/objects/%s/objectXML", DEMO_REST_PID.toString()));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, true);
        verifyGETStatusString(url, SC_OK, true, true);
    }

    @Test
    public void testValidate() throws Exception {
        String[] resultFields = { "pid" };
        java.math.BigInteger maxResults = new java.math.BigInteger("" + 1000);
        FieldSearchQuery query = new FieldSearchQuery();
        org.fcrepo.server.types.gen.ObjectFactory factory = new org.fcrepo.server.types.gen.ObjectFactory();
        query.setTerms(factory.createFieldSearchQueryTerms("*"));
        FieldSearchResult result = apia.findObjects(TypeUtility.convertStringtoAOS(resultFields), maxResults,
                query);

        List<ObjectFields> fields = result.getResultList().getObjectFields();
        String pid = "";
        URI url = null;
        for (ObjectFields objectFields : fields) {
            if (objectFields != null) {
                pid = objectFields.getPid() != null ? objectFields.getPid().getValue() : "";
                url = getURI(String.format("/objects/%s/validate", pid.toString()));
                verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
                String responseXML = verifyGETStatusString(url, SC_OK, true, true);
                assertXpathExists("/management:validation[@valid='true']", responseXML);
            }
        }
        // test with asOfDateTime set (just on the last object validated above)

        // fcrepo-947 - explicitly test demo:REST (pid) - was re-using last pid from above, which may change if test objects change
        // and demo:REST was the one that was failing
        // demo:REST has its datastream date/time values set to now (new Date()).

        // after ingest time - should pass
        url = getURI(String.format("/objects/%s/validate?asOfDateTime=%s", pid.toString(), df.format(new Date())));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        String responseXML = verifyGETStatusString(url, SC_OK, true, true);
        assertXpathExists("/management:validation[@valid='true']", responseXML);

        // date/time tests

        // before ingest time - should fail
        // (DC datastream version won't exist for example)
        url = getURI(String.format("/objects/%s/validate?asOfDateTime=%s", pid.toString(), earlierDateTime));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        responseXML = verifyGETStatusString(url, SC_OK, true, true);
        assertXpathExists("/management:validation[@valid='false']", responseXML);

        // original - testing at exact ingets time - fails under postgres
        // see fcrepo-947
        /*
        url =
        String.format("/objects/%s/validate?asOfDateTime=%s", pid
                      .toString(), datetime);
        getTrue = get(true);
        assertEquals(pid.toString(), SC_UNAUTHORIZED, get(false)
                 .getStatusCode());
        assertEquals(pid.toString(), SC_OK, getTrue.getStatusCode());
        responseXML = getTrue.getResponseBodyString();
        assertXpathExists("/management:validation[@valid='true']", responseXML);
        */

    }

    @Test
    public void testExportObject() throws Exception {
        URI url = getURI(String.format("/objects/%s/export", DEMO_REST_PID.toString()));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        verifyGETStatusString(url, SC_OK, true, true);

        url = getURI(String.format("/objects/%s/export?context=public", DEMO_REST_PID.toString()));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        verifyGETStatusString(url, SC_OK, true, true);
    }

    @Test
    public void testPurgeObject() throws Exception {
        URI url = getURI("/objects/demo:TEST_PURGE");
        StringEntity entity = getStringEntity("", TEXT_XML);
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPOSTStatusOnly(url, SC_CREATED, entity, true);
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);
    }

    @Test
    public void testAddDatastream() throws Exception {
        // inline (X) datastream
        String xmlData = "<foo>bar</foo>";
        String dsPath = "/objects/" + DEMO_REST_PID + "/datastreams/FOO";
        URI url = getURI(dsPath + "?controlGroup=X&dsLabel=foo");
        StringEntity entity = getStringEntity(xmlData, TEXT_XML);
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        HttpPost post = new HttpPost(url);
        HttpResponse response = putOrPost(post, entity, true);
        String expected = readString(response);
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());
        Header locationHeader = response.getFirstHeader(HttpHeaders.LOCATION);
        assertNotNull(locationHeader);
        assertEquals(getURI(dsPath), URI.create(locationHeader.getValue()));
        assertEquals(TEXT_XML, response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
        url = getURI(dsPath + "?format=xml");
        String actual = verifyGETStatusString(url, SC_OK, true, true);
        assertEquals(expected, actual);

        // managed (M) datastream
        String mimeType = "text/plain";
        Datastream ds = apim.getDatastream(DEMO_REST_PID.toString(), "BAR", null);
        assertNull(ds);
        dsPath = "/objects/" + DEMO_REST_PID + "/datastreams/BAR";
        url = getURI(dsPath + "?controlGroup=M&dsLabel=bar&mimeType=" + mimeType);
        File temp = File.createTempFile("test", null);
        DataOutputStream os = new DataOutputStream(new FileOutputStream(temp));
        os.write(42);
        os.close();
        response = post(url, temp, false);
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
        response = post(url, temp, true);
        expected = readString(response);
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());
        locationHeader = response.getFirstHeader(HttpHeaders.LOCATION);
        assertNotNull(locationHeader);
        assertEquals(getURI(dsPath), URI.create(locationHeader.getValue()));
        assertEquals(TEXT_XML, response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
        url = getURI(dsPath + "?format=xml");
        actual = verifyGETStatusString(url, SC_OK, true, true);
        assertEquals(expected, actual);
        ds = apim.getDatastream(DEMO_REST_PID.toString(), "BAR", null);
        assertNotNull(ds);
        assertEquals(ds.getMIMEType(), mimeType);
    }

    @Test
    public void testModifyDatastreamByReference() throws Exception {
        // Create BAR datastream
        URI url = getURI(String.format(
                "/objects/%s/datastreams/BAR?controlGroup=M&dsLabel=testModifyDatastreamByReference(bar)",
                DEMO_REST_PID.toString()));
        File temp = File.createTempFile("test", null);
        DataOutputStream os = new DataOutputStream(new FileOutputStream(temp));
        os.write(42);
        os.close();
        HttpResponse response = post(url, temp, false);
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
        response = post(url, temp, true);
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());

        // Update the content of the BAR datastream (using PUT)
        url = getURI(String.format("/objects/%s/datastreams/BAR", DEMO_REST_PID.toString()));
        assertEquals(SC_UNAUTHORIZED, put(url, temp, false).getStatusLine().getStatusCode());
        response = put(url, temp, true);
        String expected = readString(response);
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());
        assertEquals(TEXT_XML, response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
        url = getURI(url.toString() + "?format=xml");
        String actual = verifyGETStatusString(url, SC_OK, true, true);
        assertEquals(expected, actual);

        // Ensure 404 on attempt to update BOGUS_DS via PUT
        url = getURI("/objects/" + DEMO_REST_PID + "/datastreams/BOGUS_DS");
        response = put(url, temp, true);
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_NOT_FOUND, response.getStatusLine().getStatusCode());

        // Update the content of the BAR datastream (using POST)
        url = getURI(String.format("/objects/%s/datastreams/BAR", DEMO_REST_PID.toString()));
        response = post(url, temp, true);
        expected = readString(response);
        assertEquals(SC_CREATED, response.getStatusLine().getStatusCode());
        Header locationHeader = response.getFirstHeader(HttpHeaders.LOCATION);
        assertNotNull(locationHeader);
        assertEquals(url.toString(), locationHeader.getValue());
        assertEquals(TEXT_XML, response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
        url = getURI(url.toString() + "?format=xml");
        actual = verifyGETStatusString(url, SC_OK, true, true);
        assertEquals(expected, actual);

        // Update the label of the BAR datastream
        String newLabel = "tikibar";
        url = getURI(String.format("/objects/%s/datastreams/BAR?dsLabel=%s", DEMO_REST_PID.toString(), newLabel));
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, null, false);
        verifyPUTStatusOnly(url, SC_OK, null, true);
        assertEquals(newLabel, apim.getDatastream(DEMO_REST_PID.toString(), "BAR", null).getLabel());

        // Update the location of the EXTDS datastream (E type datastream)
        String newLocation = "http://" + getHost() + ":" + getPort() + "/" + getFedoraAppServerContext()
                + "/get/demo:REST/DC";
        url = getURI(String.format("/objects/%s/datastreams/EXTDS?dsLocation=%s", DEMO_REST_PID.toString(),
                newLocation));
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, null, false);
        verifyPUTStatusOnly(url, SC_OK, null, true);

        assertEquals(newLocation, apim.getDatastream(DEMO_REST_PID.toString(), "EXTDS", null).getLocation());
        String dcDS = new String(TypeUtility.convertDataHandlerToBytes(
                apia.getDatastreamDissemination(DEMO_REST_PID.toString(), "DC", null).getStream()));
        String extDS = new String(TypeUtility.convertDataHandlerToBytes(
                apia.getDatastreamDissemination(DEMO_REST_PID.toString(), "EXTDS", null).getStream()));
        assertEquals(dcDS, extDS);

        // Update DS1 by reference (X type datastream)
        // Error expected because attempting to access internal DS with API-A auth on
        if (getAuthAccess()) {
            // only ConfigB has API-A auth on
            url = getURI(String.format("/objects/%s/datastreams/DS1?dsLocation=%s", DEMO_REST_PID.toString(),
                    newLocation));
            verifyPUTStatusOnly(url, SC_UNAUTHORIZED, null, false);
            verifyPUTStatusOnly(url, SC_INTERNAL_SERVER_ERROR, null, true);
        }

        // Update DS1 by reference (X type datastream) - Success expected
        newLocation = getBaseURL() + "/ri/index.xsl";
        url = getURI(
                String.format("/objects/%s/datastreams/DS1?dsLocation=%s", DEMO_REST_PID.toString(), newLocation));
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, null, false);
        verifyPUTStatusOnly(url, SC_OK, null, true);
    }

    @Test
    public void testModifyDatastreamByValue() throws Exception {
        String xmlData = "<baz>quux</baz>";
        StringEntity entity = getStringEntity(xmlData, TEXT_XML);
        URI url = getURI(String.format("/objects/%s/datastreams/DS1?dsLabel=testModifyDatastreamByValue",
                DEMO_REST_PID.toString()));

        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPUTStatusOnly(url, SC_OK, entity, true);

        MIMETypedStream ds1 = apia.getDatastreamDissemination(DEMO_REST_PID.toString(), "DS1", null);
        assertXMLEqual(xmlData, new String(TypeUtility.convertDataHandlerToBytes(ds1.getStream()), "UTF-8"));
    }

    @Test
    public void testModifyDatastreamNoContent() throws Exception {
        String label = "testModifyDatastreamNoContent";
        URI url = getURI(String.format("/objects/%s/datastreams/DS1?dsLabel=%s", DEMO_REST_PID.toString(), label));

        StringEntity entity = getStringEntity("", TEXT_XML);
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPUTStatusOnly(url, SC_OK, entity, true);

        Datastream ds1 = apim.getDatastream(DEMO_REST_PID.toString(), "DS1", null);
        assertEquals(label, ds1.getLabel());
    }

    @Test
    public void testSetDatastreamState() throws Exception {
        String state = "D";
        URI url = getURI(String.format("/objects/%s/datastreams/DS1?dsState=%s", DEMO_REST_PID.toString(), state));
        StringEntity entity = getStringEntity("", TEXT_XML);
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPUTStatusOnly(url, SC_OK, entity, true);

        Datastream ds1 = apim.getDatastream(DEMO_REST_PID.toString(), "DS1", null);
        assertEquals(state, ds1.getState());
    }

    @Test
    public void testSetDatastreamVersionable() throws Exception {
        boolean versionable = false;
        URI url = getURI(
                String.format("/objects/%s/datastreams/DS1?versionable=%s", DEMO_REST_PID.toString(), versionable));
        StringEntity entity = getStringEntity("", TEXT_XML);
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPUTStatusOnly(url, SC_OK, entity, true);

        Datastream ds1 = apim.getDatastream(DEMO_REST_PID.toString(), "DS1", null);
        assertEquals(versionable, ds1.isVersionable());
    }

    @Test
    public void testPurgeDatastream() throws Exception {
        URI url = getURI(String.format("/objects/%s/datastreams/RELS-EXT", DEMO_REST_PID.toString()));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);
    }

    @Test
    public void testGetNextPID() throws Exception {
        URI url = getURI("/objects/nextPID");
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, null, false);
        // FIXME: not schema-validated, it should be
        // fcrepo-808 - when schema is available online, post("", true, false) -> post("", true)
        verifyPOSTStatusOnly(url, SC_OK, null, true, false);
    }

    @Test
    public void testLifecycle() throws Exception {
        HttpResponse response = null;

        // Get next PID
        URI url = getURI("/objects/nextPID?format=xml");
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, null, false);
        HttpPost post = new HttpPost(url);
        // FIXME: fcrepo-808, validation disabled currently
        response = putOrPost(post, null, true);
        String responseXML = readString(response);
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());

        String pid = responseXML.substring(responseXML.indexOf("<pid>") + 5, responseXML.indexOf("</pid>"));

        // Ingest object
        String label = "Lifecycle-Test-Label";
        url = getURI(String.format("/objects/%s?label=%s", pid, label));
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, null, false);
        verifyPOSTStatusOnly(url, SC_CREATED, null, true);

        // Add datastream
        String datastreamData = "<test>Test Datastream</test>";
        StringEntity entity = getStringEntity(datastreamData, TEXT_XML);
        url = getURI(String.format("/objects/%s/datastreams/TESTDS?controlGroup=X&dsLabel=Test", pid.toString()));
        verifyPOSTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPOSTStatusOnly(url, SC_CREATED, entity, true);

        // Get object XML
        url = getURI(String.format("/objects/%s/objectXML", pid));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        responseXML = verifyGETStatusString(url, SC_OK, true, true);
        assertTrue(responseXML.indexOf(label) > 0);
        assertTrue(responseXML.indexOf(datastreamData) > 0);

        // Modify object
        label = "Updated-Label";
        url = getURI(String.format("/objects/%s?label=%s", pid.toString(), label));
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, null, false);
        verifyPUTStatusOnly(url, SC_OK, null, true);

        // Modify datastream
        datastreamData = "<test>Update Test</test>";
        entity = getStringEntity(datastreamData, TEXT_XML);
        url = getURI(String.format("/objects/%s/datastreams/TESTDS", pid.toString()));
        verifyPUTStatusOnly(url, SC_UNAUTHORIZED, entity, false);
        verifyPUTStatusOnly(url, SC_OK, entity, true);

        // Export
        url = getURI(String.format("/objects/%s/export", pid.toString()));
        verifyGETStatusString(url, SC_UNAUTHORIZED, false, false);
        responseXML = verifyGETStatusString(url, SC_OK, true, true);
        assertTrue(responseXML.indexOf(label) > 0);
        assertTrue(responseXML.indexOf(datastreamData) > 0);

        // Purge datastream
        url = getURI(String.format("/objects/%s/datastreams/TESTDS", pid));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);

        // Purge object
        url = getURI(String.format("/objects/%s", pid));
        verifyDELETEStatusOnly(url, SC_UNAUTHORIZED, false);
        verifyDELETEStatusOnly(url, SC_OK, true);
    }

    @Test
    public void testChunked() throws Exception {
        chunked = true;
        testIngest();
        testModifyDatastreamByValue();
        testModifyDatastreamNoContent();
        testLifecycle();
    }

    @Test
    public void testResponseOverride() throws Exception {
        // Make request which returns error response
        URI url = getURI(String.format("/objects/%s", "demo:BOGUS_PID"));
        verifyPUTStatusOnly(url, SC_NOT_FOUND, null, true);

        // With flash=true parameter response should be 200
        url = getURI(String.format("/objects/%s?flash=true", "demo:BOGUS_PID"));
        verifyPUTStatusOnly(url, SC_OK, null, true);
    }

    // test correct content-disposition header on getDatastreamDissemination
    // Note that these tests are dependent on the following configuration in fedora.fcfg
    // Datastream filename sources: rels, label, id
    // Datastream extension preferences:
    // rels: never (filename always sourced from relationship)
    // label: always (filename always determined from mime-type to extension mapping)
    // id: ifmissing (filename sourced from mapping if none present in datastream id)

    @Test
    public void testDatastreamDisseminationContentDispositionFromRels() throws Exception {

        // filename from RELS-INT, no lookup of extension; no download
        URI url = getURI("/objects/demo:REST/datastreams/DS1/content");
        HttpGet get = new HttpGet(url);
        HttpResponse response = getOrDelete(get, getAuthAccess(), false);
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());
        CheckCDHeader(response, "inline", TestRESTAPI.DS1RelsFilename);
        // again with download
        url = getURI(url.toString() + "?download=true");
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), false);
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());
        CheckCDHeader(response, "attachment", TestRESTAPI.DS1RelsFilename);
    }

    @Test
    public void testDatastreamDisseminationContentDispositionFromLabel() throws Exception {

        HttpGet get;
        HttpResponse response;
        int status = 0;
        // filename from label, known MIMETYPE
        URI url = getURI("/objects/demo:REST/datastreams/DS2/content?download=true");
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
        CheckCDHeader(response, "attachment", TestRESTAPI.DS2LabelFilename + ".jpg"); // jpg should be from MIMETYPE mapping

        // filename from label, unknown MIMETYPE
        url = getURI("/objects/demo:REST/datastreams/DS3/content?download=true");
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
        CheckCDHeader(response, "attachment", TestRESTAPI.DS3LabelFilename + ".bin"); // default extension from config

        // filename from label with illegal characters, known MIMETYPE
        url = getURI("/objects/demo:REST/datastreams/DS4/content?download=true");
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), false);
        status = response.getStatusLine().getStatusCode();
        EntityUtils.consumeQuietly(response.getEntity());
        assertEquals(SC_OK, status);
        CheckCDHeader(response, "attachment", TestRESTAPI.DS4LabelFilename + ".xml"); // xml from mimetype mapping
    }

    @Test
    public void testDatastreamDisseminationContentDispositionFromId() throws Exception {

        // filename from id (no label present)
        URI url = getURI("/objects/demo:REST/datastreams/DS5/content?download=true");
        HttpGet get = new HttpGet(url);
        HttpResponse response = getOrDelete(get, getAuthAccess(), false);
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());
        // xml from mimetype mapping
        CheckCDHeader(response, "attachment", TestRESTAPI.DS5ID + ".xml");
        get.releaseConnection();

        // filename from id, id contains extension (no label present)
        url = getURI("/objects/demo:REST/datastreams/DS6.xml/content?download=true");
        get = new HttpGet(url);
        response = getOrDelete(get, getAuthAccess(), false);
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());
        CheckCDHeader(response, "attachment", TestRESTAPI.DS6ID); // no extension, id contains it
        get.releaseConnection();
    }

    @Test
    public void testUpload() throws Exception {
        String uploadUrl = "/upload";
        String url = getBaseURL() + uploadUrl;

        MultipartEntity entity = _doUploadPost();
        HttpPost post = new HttpPost(url);

        HttpResponse response = putOrPost(post, entity, true);
        if (response.getStatusLine().getStatusCode() == SC_MOVED_TEMPORARILY) {
            url = response.getFirstHeader(HttpHeaders.LOCATION).getValue();
            post = new HttpPost(url);
            entity = _doUploadPost();
            response = putOrPost(post, entity, true);
        }

        assertEquals(202, response.getStatusLine().getStatusCode());
        assertTrue(readString(response).startsWith("uploaded://"));

        // Test content not supplied
        entity = _doUploadPost(new HashMap<String, AbstractContentBody>(0));
        response = putOrPost(post, entity, true);
        assertEquals(400, response.getStatusLine().getStatusCode());
        post.releaseConnection();
    }

    /////////////////////////////////////////////////
    // Relationships methods
    /////////////////////////////////////////////////

    @Test
    public void testGetRelationships() throws Exception {
        String s = "info:fedora/" + DEMO_REST_PID;
        String p = Constants.MODEL.HAS_MODEL.uri;
        String o = Models.FEDORA_OBJECT_CURRENT.uri;

        // get all CModel relationships
        URI url = getURI("/objects/" + DEMO_REST_PID + "/relationships" + "?subject="
                + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8"));
        byte[] response = verifyGETStatusBytes(url, SC_OK, true, false);

        // check Fedora object CModel found
        checkRelationship(response, s, p, o, true);

    }

    @Test
    public void testAddRelationship() throws Exception {
        String s = "info:fedora/" + DEMO_REST_PID;
        String p = "http://www.example.org/test#relationship";
        String o = "addRelationship";
        // check relationship not present
        URI url = getURI("/objects/" + DEMO_REST_PID + "/relationships" + "?subject="
                + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8"));
        byte[] bytes = verifyGETStatusBytes(url, SC_OK, true, false);

        checkRelationship(bytes, s, p, o, false);

        // add relationship
        HttpPost post = new HttpPost();
        url = getURI("/objects/" + DEMO_REST_PID + "/relationships/new" + "?subject="
                + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8") + "&object="
                + URLEncoder.encode(o, "UTF-8") + "&isLiteral=true");
        post.setURI(url);
        HttpResponse response = putOrPost(post, null, true);
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());
        post.releaseConnection();

        // check relationship present
        url = getURI("/objects/" + DEMO_REST_PID + "/relationships"); // +
        //"?subject=" + URLEncoder.encode(s, "UTF-8") +
        //"&predicate=" + URLEncoder.encode(p, "UTF-8");
        bytes = verifyGETStatusBytes(url, SC_OK, true, false);

        checkRelationship(bytes, s, p, o, true);

        // check the same operation with URL-encoded PID
        o = "addRelationshipUrlEncoded";
        // check relationship not present
        url = getURI("/objects/" + URLEncoder.encode(DEMO_REST_PID.toString(), "UTF-8") + "/relationships"
                + "?subject=" + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8"));
        bytes = verifyGETStatusBytes(url, SC_OK, true, false);

        checkRelationship(bytes, s, p, o, false);

        // add relationship
        url = getURI("/objects/" + URLEncoder.encode(DEMO_REST_PID.toString(), "UTF-8") + "/relationships/new"
                + "?subject=" + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8")
                + "&object=" + URLEncoder.encode(o, "UTF-8") + "&isLiteral=true");
        post = new HttpPost(url);
        response = putOrPost(post, null, true);
        assertEquals(SC_OK, response.getStatusLine().getStatusCode());
        post.releaseConnection();

        // check relationship present
        url = getURI("/objects/" + URLEncoder.encode(DEMO_REST_PID.toString(), "UTF-8") + "/relationships");// +
        //"?subject=" + URLEncoder.encode(s, "UTF-8") +
        //"&predicate=" + URLEncoder.encode(p, "UTF-8");
        bytes = verifyGETStatusBytes(url, SC_OK, true, false);

        checkRelationship(bytes, s, p, o, true);

    }

    @Test
    public void testPurgeRelationship() throws Exception {
        String s = "info:fedora/" + DEMO_REST_PID;
        String p = "http://www.example.org/test#relationship";
        String o = "foo";

        // add relationship
        HttpPost post = new HttpPost();
        URI url = getURI("/objects/" + DEMO_REST_PID + "/relationships/new" + "?subject="
                + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8") + "&object="
                + URLEncoder.encode(o, "UTF-8") + "&isLiteral=true");
        post.setURI(url);
        HttpResponse response = putOrPost(post, null, true);

        int status = response.getStatusLine().getStatusCode();
        post.releaseConnection();
        assertEquals(SC_OK, status);
        HttpGet get = null;
        HttpDelete delete = null;
        try {
            // check present
            url = getURI("/objects/" + DEMO_REST_PID + "/relationships" + "?subject="
                    + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8"));
            get = new HttpGet(url);
            response = getOrDelete(get, true, false);
            status = response.getStatusLine().getStatusCode();
            byte[] responseBytes = readBytes(response);
            get.releaseConnection();
            assertEquals(SC_OK, status);
            checkRelationship(responseBytes, s, p, o, true);

            // purge it
            url = getURI("/objects/" + DEMO_REST_PID + "/relationships" + "?subject="
                    + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8") + "&object="
                    + URLEncoder.encode(o, "UTF-8") + "&isLiteral=true");
            delete = new HttpDelete(url);
            response = getOrDelete(delete, true, false);
            status = response.getStatusLine().getStatusCode();
            String responseString = readString(response);
            assertEquals(SC_OK, status);
            assertEquals("Purge relationship", "true", responseString);

            // check not present
            url = getURI("/objects/" + DEMO_REST_PID + "/relationships" + "?subject="
                    + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8"));
            get.setURI(url);
            response = getOrDelete(get, true, false);
            status = response.getStatusLine().getStatusCode();
            responseBytes = readBytes(response);
            assertEquals(SC_OK, response.getStatusLine().getStatusCode());
            checkRelationship(responseBytes, s, p, o, false);

            // purge again
            url = getURI("/objects/" + DEMO_REST_PID + "/relationships" + "?subject="
                    + URLEncoder.encode(s, "UTF-8") + "&predicate=" + URLEncoder.encode(p, "UTF-8") + "&object="
                    + URLEncoder.encode(o, "UTF-8") + "&isLiteral=true");
            delete.setURI(url);
            response = getOrDelete(delete, true, true);
            status = response.getStatusLine().getStatusCode();
            responseString = readString(response);
            assertEquals(SC_OK, status);
            assertEquals("Purge relationship", "false", responseString);
        } finally {
            if (get != null)
                get.releaseConnection();
            if (delete != null)
                delete.releaseConnection();
        }
    }

    @Test
    public void testDisseminationContentLengthWhenKnown() throws Exception {
        URI url = getURI("/objects/demo:14/methods/demo:12/getDocument");
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        verifyNoAuthFailOnAPIAAuth(url);
        // default to validated response
        response = getOrDelete(get, getAuthAccess(), true);
        int status = response.getStatusLine().getStatusCode();
        String cLen = (response.containsHeader(HttpHeaders.CONTENT_LENGTH))
                ? response.getFirstHeader(HttpHeaders.CONTENT_LENGTH).getValue()
                : null;
        EntityUtils.consumeQuietly(response.getEntity());
        get.releaseConnection();
        assertEquals(SC_OK, status);
        assertEquals("19498", cLen);
    }

    private MultipartEntity _doUploadPost() throws Exception {
        File temp = File.createTempFile("test.txt", null);
        FileUtils.writeStringToFile(temp, "This is the upload test file");

        FileBody part = new FileBody(temp);
        Map<String, AbstractContentBody> parts = new HashMap<String, AbstractContentBody>(1);
        parts.put("file", part);
        return _doUploadPost(parts);
    }

    private MultipartEntity _doUploadPost(Map<String, AbstractContentBody> parts) throws Exception {
        MultipartEntity entity = new MultipartEntity();

        for (String name : parts.keySet()) {
            entity.addPart(name, parts.get(name));
        }
        return entity;
    }

    // check content disposition header of response

    private void CheckCDHeader(HttpResponse response, String expectedType, String expectedFilename) {
        String contentDisposition = "";
        Header[] headers = response.getAllHeaders();
        for (Header header : headers) {
            if (header.getName().equalsIgnoreCase("content-disposition")) {
                contentDisposition = header.getValue();
            }
        }
        assertEquals(expectedType + "; " + "filename=\"" + expectedFilename + "\"", contentDisposition);
    }

    // helper methods

    private HttpClient getClient(boolean followRedirects, boolean auth) {
        DefaultHttpClient result = s_client.getHttpClient(followRedirects, auth);
        if (auth) {
            String host = getHost();
            LOGGER.debug("credentials set for scope of {}:[ANY PORT]", host);
            result.getCredentialsProvider().setCredentials(
                    new AuthScope(host, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
                    new UsernamePasswordCredentials(getUsername(), getPassword()));
        } else {
            result.getCredentialsProvider().clear();
        }
        return result;
    }

    private String _getPort(String url) {
        if (url != null && url.startsWith("https")) {
            return getServerConfiguration().getParameter("fedoraRedirectPort");
        }
        return getServerConfiguration().getParameter("fedoraServerPort");
    }

    protected static URI getURI(String url) {
        if (url == null || url.length() == 0) {
            throw new IllegalArgumentException("url must be a non-empty value");
        } else if (!(url.startsWith("http://") || url.startsWith("https://"))) {
            url = getBaseURL() + url;
        }

        return URI.create(url);
    }

    protected void validateResponse(URI url, HttpResponse res) throws Exception {
        // if response was ok... (and not doing flash override of error response)
        int sc = res.getStatusLine().getStatusCode();
        if (sc >= 200 && sc <= 299 && !url.toString().contains("flash=true")) {
            // if response is xml
            Header contentType = res.getFirstHeader(HttpHeaders.CONTENT_TYPE);

            if (contentType != null && (contentType.getValue().contains(TEXT_XML)
                    || contentType.getValue().contains("application/xml"))) {
                String xmlResponse = EntityUtils.toString(res.getEntity());
                // put the response right back, in case we need it elsewhere
                res.setEntity(new StringEntity(xmlResponse, Charset.forName("UTF-8")));
                // if a schema location is specified
                if (xmlResponse.contains(":schemaLocation=\"")) {

                    //Validate online if we can
                    String offline = System.getProperty("offline");
                    String online = System.getProperty("online");
                    if (!"false".equals(online) && (offline == null || !"true".equals(offline))) {
                        validator.onlineValidate(url.toString(), xmlResponse);
                    }

                    // Also validate offline unless explicitly disabled
                    if (!"false".equals(offline)) {
                        /*
                         * Offline schema validation does not work due to bug in
                         * xerces:
                         * https://issues.apache.org/jira/browse/XERCESJ-1130
                         */
                        //validator.offlineValidate(url, xmlResponse, getSchemaFiles(xmlResponse, null));
                    }
                } else {
                    // for now, requiring a schema
                    fail("No schema location specified in response - " + url);
                }
            }
        }
    }

    @Deprecated
    protected HttpResponse put(URI url, File requestContent, boolean authenticate) throws Exception {
        HttpPut method = new HttpPut(url);
        MultipartEntity entity = new MultipartEntity();
        entity.addPart("param_name", new StringBody("value"));
        entity.addPart(((File) requestContent).getName(), new FileBody((File) requestContent));
        HttpResponse response = putOrPost(method, entity, authenticate);
        int status = response.getStatusLine().getStatusCode();
        if (status == SC_MOVED_TEMPORARILY) {
            String original = url.toString();
            url = URI.create(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
            if (!original.equals(url.toString())) {
                EntityUtils.consumeQuietly(response.getEntity());
                method = new HttpPut(url);
                entity = new MultipartEntity();
                entity.addPart("param_name", new StringBody("value"));
                entity.addPart(((File) requestContent).getName(), new FileBody((File) requestContent));
                response = putOrPost(method, entity, true);
            }
        }

        return response;
    }

    @Deprecated
    protected HttpResponse post(URI url, File requestContent, boolean authenticate) throws Exception {
        HttpPost method = new HttpPost(url);
        MultipartEntity entity = new MultipartEntity();
        entity.addPart("param_name", new StringBody("value"));
        entity.addPart(((File) requestContent).getName(), new FileBody((File) requestContent));
        HttpResponse response = putOrPost(method, entity, authenticate);
        int status = response.getStatusLine().getStatusCode();
        if (status == SC_MOVED_TEMPORARILY) {
            String original = url.toString();
            url = URI.create(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
            if (!original.equals(url.toString())) {
                EntityUtils.consumeQuietly(response.getEntity());
                method = new HttpPost(url);
                entity = new MultipartEntity();
                entity.addPart("param_name", new StringBody("value"));
                entity.addPart(((File) requestContent).getName(), new FileBody((File) requestContent));
                response = putOrPost(method, entity, true);
            }
        }

        return response;
    }

    private HttpResponse getOrDelete(HttpRequestBase method, boolean authenticate, boolean validate)
            throws Exception {
        HttpClient client = getClient(true, authenticate);
        HttpResponse response = getOrDelete(client, method, authenticate, validate);
        return response;
    }

    private HttpResponse getOrDelete(HttpClient client, HttpRequestBase method, boolean authenticate,
            boolean validate) throws Exception {

        LOGGER.debug(method.getURI().toString());

        if (!(method instanceof HttpGet || method instanceof HttpDelete)) {
            throw new IllegalArgumentException("method must be one of GET or DELETE.");
        }
        HttpResponse response = client.execute(method);

        if (response.getStatusLine().getStatusCode() == SC_MOVED_TEMPORARILY) {
            String redir = response.getFirstHeader(HttpHeaders.LOCATION).getValue();
            if (!method.getURI().toString().equals(redir)) {
                method.setURI(getURI(redir));
                response = getOrDelete(client, method, authenticate, validate);
            }
        }

        if (validate) {
            validateResponse(method.getURI(), response);
        }

        return response;
    }

    private HttpResponse putOrPost(HttpEntityEnclosingRequestBase method, HttpEntity requestContent,
            boolean authenticate) throws Exception {
        HttpClient client = getClient(false, authenticate);
        if (method == null) {
            throw new IllegalArgumentException("method must be a non-empty value");
        }

        if (requestContent != null) {
            method.setEntity(requestContent);
        }

        HttpResponse response = client.execute(method);

        return response;
    }

    private void checkRelationship(byte[] rdf, String s, String p, String o, boolean exists)
            throws TrippiException, UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        sb.append("RDF: " + new String(rdf, "UTF-8"));
        TripleIterator it = TripleIteratorFactory.defaultInstance().fromStream(new ByteArrayInputStream(rdf), null,
                RDFFormat.RDF_XML);

        boolean found = false;
        while (it.hasNext()) {
            Triple t = it.next();

            sb.append(t.getSubject().stringValue() + ", ");
            sb.append(t.getPredicate().stringValue() + ", ");
            sb.append(t.getObject().stringValue() + "\n");

            sb.append("matching: " + s + " " + t.getSubject().stringValue() + " "
                    + (s.equals(t.getSubject().stringValue())) + "\n");
            sb.append("matching: " + p + " " + t.getPredicate().stringValue() + " "
                    + (p.equals(t.getPredicate().stringValue())) + "\n");
            sb.append("matching: " + o + " " + t.getObject().stringValue() + " "
                    + (o.equals(t.getObject().stringValue())) + "\n");

            if (s.equals(t.getSubject().stringValue()) && p.equals(t.getPredicate().stringValue())
                    && o.equals(t.getObject().stringValue())) {
                sb.append("Matched\n");
                found = true;
            }
        }

        assertTrue("Testing if relationship present: " + exists + " [ " + s + ", " + p + ", " + o + " ] \n "
                + sb.toString(), exists == found);
    }

    // Supports legacy test runners

    public static junit.framework.Test suite() {
        return new JUnit4TestAdapter(TestRESTAPI.class);
    }

    public static void main(String[] args) {
        JUnitCore.runClasses(TestRESTAPI.class);
    }

}