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    static final String JMS_BASEURL_PROP = "fcrepo.jms.baseUrl";
048
049    private final TransactionManager txManager;
050
051    private final HttpServletRequest request;
052
053    private final Pattern txIdPattern;
054
055    private final URI baseUri;
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     */
063    public TransactionProvider(final TransactionManager txManager, final HttpServletRequest request,
064            final URI baseUri) {
065        this.txManager = txManager;
066        this.request = request;
067        this.txIdPattern = Pattern.compile("(^|" + baseUri + TX_PREFIX + ")([0-9a-f\\-]+)$");
068        this.baseUri = baseUri;
069    }
070
071    @Override
072    public Transaction provide() {
073        final Transaction transaction = getTransactionForRequest();
074        if (!transaction.isShortLived()) {
075            transaction.refresh();
076        }
077        LOGGER.trace("Providing new transaction {}", transaction.getId());
078        return transaction;
079    }
080
081    @Override
082    public void dispose(final Transaction transaction) {
083        if (transaction.isShortLived()) {
084            LOGGER.trace("Disposing transaction {}", transaction.getId());
085            transaction.expire();
086        }
087    }
088
089    /**
090     * Returns the transaction for the Request. If the request has ATOMIC_ID_HEADER header,
091     * the transaction corresponding to that ID is returned, otherwise, a new transaction is
092     * created.
093     *
094     * @return the transaction for the request
095     */
096    public Transaction getTransactionForRequest() {
097        String txId = null;
098        // Transaction id either comes from header or is the path
099        String txUri = request.getHeader(ATOMIC_ID_HEADER);
100        if (!StringUtils.isEmpty(txUri)) {
101            final Matcher txMatcher = txIdPattern.matcher(txUri);
102            if (txMatcher.matches()) {
103                txId = txMatcher.group(2);
104            } else {
105                throw new TransactionRuntimeException("Invalid transaction id");
106            }
107        } else {
108            txUri = request.getPathInfo();
109            if (txUri != null) {
110                final Matcher txMatcher = txIdPattern.matcher(txUri);
111                if (txMatcher.matches()) {
112                    txId = txMatcher.group(2);
113                }
114            }
115        }
116
117        if (!StringUtils.isEmpty(txId)) {
118            return txManager.get(txId);
119        } else {
120            final var transaction = txManager.create();
121            transaction.setUserAgent(request.getHeader("user-agent"));
122            transaction.setBaseUri(resolveBaseUri());
123            return transaction;
124        }
125    }
126
127    private String resolveBaseUri() {
128        final String baseURL = getBaseUrlProperty();
129        if (baseURL.length() > 0) {
130            return baseURL;
131        }
132        return baseUri.toString();
133    }
134
135    /**
136     * Produce a baseURL for JMS events using the system property fcrepo.jms.baseUrl of the form http[s]://host[:port],
137     * if it exists.
138     *
139     * @return String the base Url
140     */
141    private String getBaseUrlProperty() {
142        final String propBaseURL = System.getProperty(JMS_BASEURL_PROP, "");
143        if (propBaseURL.length() > 0 && propBaseURL.startsWith("http")) {
144            final URI propBaseUri = URI.create(propBaseURL);
145            if (propBaseUri.getPort() < 0) {
146                return JerseyUriBuilder.fromUri(baseUri).port(-1).uri(propBaseUri).toString();
147            }
148            return JerseyUriBuilder.fromUri(baseUri).uri(propBaseUri).toString();
149        }
150        return "";
151    }
152
153}