001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.http.commons.session;
007
008import static org.fcrepo.http.commons.session.TransactionConstants.ATOMIC_ID_HEADER;
009import static org.fcrepo.http.commons.session.TransactionConstants.TX_PREFIX;
010import static org.slf4j.LoggerFactory.getLogger;
011
012import java.net.URI;
013import java.util.regex.Matcher;
014import java.util.regex.Pattern;
015
016import javax.servlet.http.HttpServletRequest;
017
018import org.apache.commons.lang3.StringUtils;
019import org.fcrepo.kernel.api.Transaction;
020import org.fcrepo.kernel.api.TransactionManager;
021import org.fcrepo.kernel.api.exception.TransactionRuntimeException;
022import org.glassfish.hk2.api.Factory;
023import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
024import org.slf4j.Logger;
025
026/**
027 * Provide a fedora tranasction within the current request context
028 *
029 * @author awoods
030 */
031public class TransactionProvider implements Factory<Transaction> {
032
033    private static final Logger LOGGER = getLogger(TransactionProvider.class);
034
035    private final TransactionManager txManager;
036
037    private final HttpServletRequest request;
038
039    private final Pattern txIdPattern;
040
041    private final URI baseUri;
042
043    private final String jmsBaseUrl;
044
045    /**
046     * Create a new transaction provider for a request
047     * @param txManager the transaction manager
048     * @param request the request
049     * @param baseUri base uri for the application
050     * @param jmsBaseUrl base url to use for jms, optional
051     */
052    public TransactionProvider(final TransactionManager txManager,
053                               final HttpServletRequest request,
054                               final URI baseUri,
055                               final String jmsBaseUrl) {
056        this.txManager = txManager;
057        this.request = request;
058        this.txIdPattern = Pattern.compile("(^|" + baseUri + TX_PREFIX + ")([0-9a-f\\-]+)$");
059        this.baseUri = baseUri;
060        this.jmsBaseUrl = jmsBaseUrl;
061    }
062
063    @Override
064    public Transaction provide() {
065        final Transaction transaction = getTransactionForRequest();
066        if (!transaction.isShortLived()) {
067            transaction.refresh();
068        }
069        LOGGER.trace("Providing new transaction {}", transaction.getId());
070        return transaction;
071    }
072
073    @Override
074    public void dispose(final Transaction transaction) {
075        if (transaction.isShortLived()) {
076            LOGGER.trace("Disposing transaction {}", transaction.getId());
077            transaction.expire();
078        }
079    }
080
081    /**
082     * Returns the transaction for the Request. If the request has ATOMIC_ID_HEADER header,
083     * the transaction corresponding to that ID is returned, otherwise, a new transaction is
084     * created.
085     *
086     * @return the transaction for the request
087     */
088    public Transaction getTransactionForRequest() {
089        String txId = null;
090        // Transaction id either comes from header or is the path
091        String txUri = request.getHeader(ATOMIC_ID_HEADER);
092        if (!StringUtils.isEmpty(txUri)) {
093            final Matcher txMatcher = txIdPattern.matcher(txUri);
094            if (txMatcher.matches()) {
095                txId = txMatcher.group(2);
096            } else {
097                throw new TransactionRuntimeException("Invalid transaction id");
098            }
099        } else {
100            txUri = request.getPathInfo();
101            if (txUri != null) {
102                final Matcher txMatcher = txIdPattern.matcher(txUri);
103                if (txMatcher.matches()) {
104                    txId = txMatcher.group(2);
105                }
106            }
107        }
108
109        if (!StringUtils.isEmpty(txId)) {
110            return txManager.get(txId);
111        } else {
112            final var transaction = txManager.create();
113            transaction.setUserAgent(request.getHeader("user-agent"));
114            transaction.setBaseUri(resolveBaseUri());
115            return transaction;
116        }
117    }
118
119    private String resolveBaseUri() {
120        final String baseURL = getBaseUrlProperty();
121        if (baseURL.length() > 0) {
122            return baseURL;
123        }
124        return baseUri.toString();
125    }
126
127    /**
128     * Produce a baseURL for JMS events using the system property fcrepo.jms.baseUrl of the form http[s]://host[:port],
129     * if it exists.
130     *
131     * @return String the base Url
132     */
133    private String getBaseUrlProperty() {
134        if (StringUtils.isNotBlank(jmsBaseUrl) && jmsBaseUrl.startsWith("http")) {
135            final URI propBaseUri = URI.create(jmsBaseUrl);
136            if (propBaseUri.getPort() < 0) {
137                return JerseyUriBuilder.fromUri(baseUri).port(-1).uri(propBaseUri).toString();
138            }
139            return JerseyUriBuilder.fromUri(baseUri).uri(propBaseUri).toString();
140        }
141        return "";
142    }
143
144}