Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transaction Support #67

Merged
merged 38 commits into from Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a9d558a
Add startTransaction for creating transactions
mikejritter Mar 11, 2022
e0bf3ce
Add transaction header constants
mikejritter Mar 11, 2022
bc9e596
Add method to retrieve transaction uri
mikejritter Mar 11, 2022
a3d2c0e
Add method to add Atomic-Id header for transactions
mikejritter Mar 11, 2022
fb686ac
Use transaction endpoint constant
mikejritter Jun 7, 2022
6a615d8
Override addTransaction for all builders
mikejritter Jun 7, 2022
7b5d998
Initial TransactionBuilder idea
mikejritter Jun 8, 2022
2ba7721
Checkstyle updates
mikejritter Jun 9, 2022
bf4ef02
Create RequestBuilders per transaction endpoint
mikejritter Jun 9, 2022
4ab4583
Move start and TRANSACTION_ENDPOINT to TransactionBuilder
mikejritter Jun 9, 2022
26e68a3
Add uri validation methods
mikejritter Jun 13, 2022
e708c1e
Add simple tests for tx uri validation
mikejritter Jun 13, 2022
cb41eca
Split start valid into separate tests
mikejritter Jul 1, 2022
d5225d0
Use regex from fcrepo for validation help on tx ids
mikejritter Jul 12, 2022
3ab69aa
Create TransactionURI for more strict api definitions
mikejritter Jul 13, 2022
d757231
Update ATOMIC_ID constant
mikejritter Jul 28, 2022
798b055
Try to retrieve the Atomic-ID from a response when getting the transa…
mikejritter Jul 28, 2022
4f1c652
Add TransactionalFcrepoClient for automatically adding Atomic-IDs to …
mikejritter Jul 28, 2022
d1fe986
Add builder option for a TransactionFcrepoClient
mikejritter Jul 28, 2022
441ef5c
Create a container to test the transactional client
mikejritter Jul 28, 2022
bb007a2
Use DateTimeFormatter when checking all Atomic-Expires headers
mikejritter Aug 2, 2022
373e8a7
Add a method for creating transaction clients from a fcrepo client
mikejritter Aug 23, 2022
916e1ff
Extend BodyRequestBuilder to allow for removal of addTransaction
mikejritter Aug 24, 2022
6002a51
Make addTransaction protected to hide it in unused builders
mikejritter Sep 7, 2022
837afe5
Add function to start a tx and create a client
mikejritter Sep 12, 2022
87d40ec
Test for startTransactionalClient
mikejritter Sep 12, 2022
9f49e6e
Drop Optional from getTransactionUri
mikejritter Sep 27, 2022
913a529
Add transaction helpers to client
mikejritter Oct 5, 2022
e8daf3f
Move startTransactionClient to FcrepoClient
mikejritter Oct 28, 2022
e06e813
Update tests to be only through TransactionalFcrepoClient
mikejritter Oct 28, 2022
c68bbd6
Create RequestBuilders for tx endpoints in TransactionalFcrepoClient
mikejritter Oct 28, 2022
b97e05d
Update import for TRANSACTION_ENDPOINT
mikejritter Oct 28, 2022
42ad827
Remove unused classes
mikejritter Oct 28, 2022
6e0230b
Shorten transaction methods
mikejritter Nov 1, 2022
44b64b4
Keep functionality for base or transaction endpoints
mikejritter Nov 1, 2022
a786b92
Add test creating a client using the full transaction endpoint
mikejritter Nov 1, 2022
975192d
Drop need for TransactionURI
mikejritter Nov 1, 2022
1eb0043
Use Get/Post/etc builders for the transaction api
mikejritter Nov 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/main/java/org/fcrepo/client/DeleteBuilder.java
Expand Up @@ -35,4 +35,10 @@ protected HttpRequestBase createRequest() {
public DeleteBuilder addHeader(final String name, final String value) {
return (DeleteBuilder) super.addHeader(name, value);
}

@Override
public DeleteBuilder addTransaction(final URI transaction) {
return (DeleteBuilder) super.addTransaction(transaction);
}

}
63 changes: 61 additions & 2 deletions src/main/java/org/fcrepo/client/FcrepoClient.java
Expand Up @@ -16,6 +16,7 @@
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
Expand Down Expand Up @@ -48,9 +49,12 @@
public class FcrepoClient implements Closeable {

private CloseableHttpClient httpclient;
private FcrepoHttpClientBuilder httpClientBuilder;

private Boolean throwExceptionOnFailure = true;

public static final String TRANSACTION_ENDPOINT = "fcr:tx";

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

/**
Expand All @@ -72,7 +76,19 @@ public static FcrepoClientBuilder client() {
*/
protected FcrepoClient(final String username, final String password, final String host,
final Boolean throwExceptionOnFailure) {
this(new FcrepoHttpClientBuilder(username, password, host).build(), throwExceptionOnFailure);
this(new FcrepoHttpClientBuilder(username, password, host), throwExceptionOnFailure);
}

/**
* Create a FcrepoClient which uses the given {@link FcrepoHttpClientBuilder} to manage its http client.
* FcrepoClient will close the httpClient when {@link #close()} is called.
* @param httpClientBuilder http client builder to use to connect to the repository
* @param throwExceptionOnFailure whether to throw an exception on any non-2xx or 3xx HTTP responses
*/
protected FcrepoClient(final FcrepoHttpClientBuilder httpClientBuilder, final Boolean throwExceptionOnFailure) {
this.throwExceptionOnFailure = throwExceptionOnFailure;
this.httpclient = httpClientBuilder.build();
this.httpClientBuilder = httpClientBuilder;
}

/**
Expand Down Expand Up @@ -152,6 +168,49 @@ public HistoricMementoBuilder createMemento(final URI url, final String mementoD
return new HistoricMementoBuilder(url, this, mementoDatetime);
}

/**
* Start a transaction and create a new {@link TransactionalFcrepoClient}
*
* @param uri the base rest endpoint or the transaction endpoint
* @return the TransactionalFcrepoClient
* @throws IOException if there's an error with the http request
* @throws IllegalArgumentException if the uri is not the Fedora transaction endpoint
* @throws FcrepoOperationFailedException if there's an error in the fcrepo operation
*/
public TransactionalFcrepoClient startTransactionClient(final URI uri)
throws IOException, FcrepoOperationFailedException {
final var target = getTxEndpoint(uri);
try (final var response = post(target).perform()) {
return transactionalClient(response);
}
}

private URI getTxEndpoint(final URI uri) {
final var isRoot = Pattern.compile("rest/?$").asPredicate();
final var isTx = Pattern.compile("rest/" + TRANSACTION_ENDPOINT + "/?$").asPredicate();
final var base = uri.toString();
if (isRoot.test(base)) {
LOGGER.debug("Start transaction request matches root, appending {}", TRANSACTION_ENDPOINT);
// preface with ./ so fcr:tx isn't interpreted as a scheme
return uri.resolve("./" + TRANSACTION_ENDPOINT);
} else if (isTx.test(base)) {
return uri;
} else {
throw new IllegalArgumentException("Uri is not the base rest endpoint or the transaction endpoint");
}
}

/**
* Create a new {@link TransactionalFcrepoClient} which adds the transaction {@link URI} to each request
*
* @param response the FcrepoResponse with an Atomic-ID Header
* @return a TransactionFcrepoClient
* @throws IllegalArgumentException if the FcrepoResponse does not contain a transaction location
*/
public TransactionalFcrepoClient transactionalClient(final FcrepoResponse response) {
return new TransactionalFcrepoClient(response.getTransactionUri(), httpClientBuilder, throwExceptionOnFailure);
}

/**
* Make a DELETE request to delete a resource
*
Expand Down Expand Up @@ -354,7 +413,7 @@ public FcrepoClientBuilder throwExceptionOnFailure() {
*/
public FcrepoClient build() {
final FcrepoHttpClientBuilder httpClient = new FcrepoHttpClientBuilder(authUser, authPassword, authHost);
return new FcrepoClient(httpClient.build(), throwExceptionOnFailure);
return new FcrepoClient(httpClient, throwExceptionOnFailure);
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/org/fcrepo/client/FcrepoResponse.java
Expand Up @@ -7,6 +7,8 @@

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static org.fcrepo.client.FcrepoClient.TRANSACTION_ENDPOINT;
import static org.fcrepo.client.FedoraHeaderConstants.ATOMIC_ID;
import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION;
import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_TYPE;
import static org.fcrepo.client.FedoraHeaderConstants.LINK;
Expand All @@ -20,6 +22,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HeaderElement;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicHeader;
Expand Down Expand Up @@ -56,6 +59,8 @@ public class FcrepoResponse implements Closeable {

private InputStream body;

private URI transactionUri;

private String contentType;

private boolean closed = false;
Expand Down Expand Up @@ -313,4 +318,26 @@ public Map<String, String> getContentDisposition() {
}
return contentDisposition;
}

/**
* Get the transaction location. If the location is not for a transaction, check for the Atomic-ID,
* otherwise return null.
*
* @return the transaction location or null
*/
public URI getTransactionUri() {
if (transactionUri == null) {
final var location = getHeaderValue(LOCATION);
final var atomicId = getHeaderValue(ATOMIC_ID);

if (location != null && location.contains(TRANSACTION_ENDPOINT)) {
transactionUri = URI.create(location);
} else if (atomicId != null) {
transactionUri = URI.create(atomicId);
}
}

return transactionUri;
}

}
4 changes: 4 additions & 0 deletions src/main/java/org/fcrepo/client/FedoraHeaderConstants.java
Expand Up @@ -67,6 +67,10 @@ public class FedoraHeaderConstants {

public static final String ACCEPT_DATETIME = "Accept-Datetime";

public static final String ATOMIC_ID = "Atomic-ID";

public static final String ATOMIC_EXPIRES = "Atomic-Expires";

private FedoraHeaderConstants() {
}
}
5 changes: 5 additions & 0 deletions src/main/java/org/fcrepo/client/GetBuilder.java
Expand Up @@ -182,4 +182,9 @@ public GetBuilder addHeader(final String name, final String value) {
public GetBuilder addLinkHeader(final FcrepoLink linkHeader) {
return (GetBuilder) super.addLinkHeader(linkHeader);
}

@Override
public GetBuilder addTransaction(final URI transaction) {
return (GetBuilder) super.addTransaction(transaction);
}
}
5 changes: 5 additions & 0 deletions src/main/java/org/fcrepo/client/HeadBuilder.java
Expand Up @@ -67,4 +67,9 @@ public HeadBuilder addHeader(final String name, final String value) {
public HeadBuilder addLinkHeader(final FcrepoLink linkHeader) {
return (HeadBuilder) super.addLinkHeader(linkHeader);
}

@Override
public HeadBuilder addTransaction(final URI transaction) {
return (HeadBuilder) super.addTransaction(transaction);
}
}
107 changes: 106 additions & 1 deletion src/main/java/org/fcrepo/client/HistoricMementoBuilder.java
Expand Up @@ -5,19 +5,27 @@
*/
package org.fcrepo.client;

import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION;
import static org.fcrepo.client.FedoraHeaderConstants.SLUG;
import static org.fcrepo.client.HeaderHelpers.UTC_RFC_1123_FORMATTER;
import static org.fcrepo.client.FedoraHeaderConstants.MEMENTO_DATETIME;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.time.Instant;

import org.apache.http.client.methods.HttpRequestBase;
import org.springframework.http.ContentDisposition;

/**
* Builds a POST request for creating a memento (LDPRm) with the state given in the request body
* and the datetime given in the Memento-Datetime request header.
*
* @author bbpennel
*/
public class HistoricMementoBuilder extends PostBuilder {
public class HistoricMementoBuilder extends BodyRequestBuilder {

/**
* Instantiate builder
Expand Down Expand Up @@ -45,4 +53,101 @@ public HistoricMementoBuilder(final URI uri, final FcrepoClient client, final St
UTC_RFC_1123_FORMATTER.parse(mementoDatetime);
request.setHeader(MEMENTO_DATETIME, mementoDatetime);
}

@Override
protected HttpRequestBase createRequest() {
return HttpMethods.POST.createRequest(targetUri);
}

@Override
public HistoricMementoBuilder body(final InputStream stream, final String contentType) {
return (HistoricMementoBuilder) super.body(stream, contentType);
}

@Override
public HistoricMementoBuilder body(final File file, final String contentType) throws IOException {
return (HistoricMementoBuilder) super.body(file, contentType);
}

@Override
public HistoricMementoBuilder body(final InputStream stream) {
return (HistoricMementoBuilder) super.body(stream);
}

@Override
public HistoricMementoBuilder externalContent(final URI contentURI,
final String contentType,
final String handling) {
return (HistoricMementoBuilder) super.externalContent(contentURI, contentType, handling);
}

@Override
public HistoricMementoBuilder digest(final String digest, final String alg) {
return (HistoricMementoBuilder) super.digest(digest, alg);
}

@Override
public HistoricMementoBuilder digestMd5(final String digest) {
return (HistoricMementoBuilder) super.digestMd5(digest);
}

@Override
public HistoricMementoBuilder digestSha1(final String digest) {
return (HistoricMementoBuilder) super.digestSha1(digest);
}

@Override
public HistoricMementoBuilder digestSha256(final String digest) {
return (HistoricMementoBuilder) super.digestSha256(digest);
}

@Override
public HistoricMementoBuilder addInteractionModel(final String interactionModelUri) {
return (HistoricMementoBuilder) super.addInteractionModel(interactionModelUri);
}

@Override
public HistoricMementoBuilder linkAcl(final String aclUri) {
return (HistoricMementoBuilder) super.linkAcl(aclUri);
}

@Override
public HistoricMementoBuilder addHeader(final String name, final String value) {
return (HistoricMementoBuilder) super.addHeader(name, value);
}

@Override
public HistoricMementoBuilder addLinkHeader(final FcrepoLink linkHeader) {
return (HistoricMementoBuilder) super.addLinkHeader(linkHeader);
}

/**
* Provide a content disposition header which will be used as the filename
*
* @param filename the name of the file being provided in the body of the request
* @return this builder
* @throws FcrepoOperationFailedException if unable to encode filename
*/
public HistoricMementoBuilder filename(final String filename) throws FcrepoOperationFailedException {
final ContentDisposition.Builder builder = ContentDisposition.builder("attachment");
if (filename != null) {
builder.filename(filename);
}
request.addHeader(CONTENT_DISPOSITION, builder.build().toString());
return this;
}

/**
* Provide a suggested name for the new child resource, which the repository may ignore.
*
* @param slug value to supply as the slug header
* @return this builder
*/
public HistoricMementoBuilder slug(final String slug) {
if (slug != null) {
request.addHeader(SLUG, slug);
}
return this;
}

}
5 changes: 5 additions & 0 deletions src/main/java/org/fcrepo/client/OptionsBuilder.java
Expand Up @@ -40,4 +40,9 @@ public OptionsBuilder addHeader(final String name, final String value) {
public OptionsBuilder addLinkHeader(final FcrepoLink linkHeader) {
return (OptionsBuilder) super.addLinkHeader(linkHeader);
}

@Override
public OptionsBuilder addTransaction(final URI transaction) {
return (OptionsBuilder) super.addTransaction(transaction);
}
}
Expand Up @@ -40,4 +40,5 @@ public OriginalMementoBuilder addHeader(final String name, final String value) {
public OriginalMementoBuilder addLinkHeader(final FcrepoLink linkHeader) {
return (OriginalMementoBuilder) super.addLinkHeader(linkHeader);
}

}
5 changes: 5 additions & 0 deletions src/main/java/org/fcrepo/client/PatchBuilder.java
Expand Up @@ -101,4 +101,9 @@ public PatchBuilder addHeader(final String name, final String value) {
public PatchBuilder addLinkHeader(final FcrepoLink linkHeader) {
return (PatchBuilder) super.addLinkHeader(linkHeader);
}

@Override
public PatchBuilder addTransaction(final URI transaction) {
return (PatchBuilder) super.addTransaction(transaction);
}
}
5 changes: 5 additions & 0 deletions src/main/java/org/fcrepo/client/PostBuilder.java
Expand Up @@ -101,6 +101,11 @@ public PostBuilder addHeader(final String name, final String value) {
return (PostBuilder) super.addHeader(name, value);
}

@Override
public PostBuilder addTransaction(final URI transaction) {
return (PostBuilder) super.addTransaction(transaction);
}

@Override
public PostBuilder addLinkHeader(final FcrepoLink linkHeader) {
return (PostBuilder) super.addLinkHeader(linkHeader);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/fcrepo/client/PutBuilder.java
Expand Up @@ -121,6 +121,11 @@ public PutBuilder addLinkHeader(final FcrepoLink linkHeader) {
return (PutBuilder) super.addLinkHeader(linkHeader);
}

@Override
public PutBuilder addTransaction(final URI transaction) {
return (PutBuilder) super.addTransaction(transaction);
}

/**
* Provide a content disposition header which will be used as the filename
*
Expand Down