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