/* * 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.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.fcrepo.http.commons.test.util.TestHelpers.parseTriples; import static org.fcrepo.kernel.api.FedoraTypes.FCR_METADATA; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.slf4j.LoggerFactory.getLogger; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Collection; import javax.ws.rs.core.Response.Status; import org.fcrepo.http.commons.test.util.CloseableDataset; 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.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; @Before public void setLogger() { logger = getLogger(this.getClass()); } protected static final int SERVER_PORT = parseInt(System.getProperty("fcrepo.dynamic.test.port", "8080")); protected static final String HOSTNAME = "localhost"; protected static final String PROTOCOL = "http"; protected static final String serverAddress = PROTOCOL + "://" + HOSTNAME + ":" + SERVER_PORT + "/"; protected static CloseableHttpClient client = createClient(); protected static CloseableHttpClient createClient() { return HttpClientBuilder.create().setMaxConnPerRoute(MAX_VALUE).setMaxConnTotal(MAX_VALUE).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 HttpPost postObjMethod(final String id, final String query) { if (query.equals("")) { return new HttpPost(serverAddress + id); } return new HttpPost(serverAddress + id + "?" + query); } 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); 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(); } 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 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 */ protected 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 CloseableHttpResponse createObject() { return createObject(""); } protected CloseableHttpResponse createObject(final String pid) { final HttpPost httpPost = postObjMethod("/"); if (pid.length() > 0) { httpPost.addHeader("Slug", pid); } 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 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); } protected 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; } /** * 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); } } protected static void addMixin(final String pid, final String mixinUrl) throws IOException { final HttpPatch updateObjectGraphMethod = new HttpPatch(serverAddress + pid); updateObjectGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); updateObjectGraphMethod.setEntity(new StringEntity( "INSERT DATA { <> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <" + mixinUrl + "> . } ")); try (final CloseableHttpResponse response = execute(updateObjectGraphMethod)) { assertEquals(NO_CONTENT.getStatusCode(), getStatus(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(); } /** * Gets a random (but valid) property name for use in testing. * * @return string containing random property name */ protected static String getRandomPropertyName() { 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())); } }