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.api;
019
020import static java.util.Date.from;
021import static javax.ws.rs.core.Response.created;
022import static javax.ws.rs.core.Response.noContent;
023import static javax.ws.rs.core.Response.status;
024import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
025import static org.slf4j.LoggerFactory.getLogger;
026
027import java.net.URI;
028import java.net.URISyntaxException;
029
030import javax.inject.Inject;
031import javax.ws.rs.POST;
032import javax.ws.rs.Path;
033import javax.ws.rs.PathParam;
034import javax.ws.rs.core.Response;
035
036import org.fcrepo.kernel.api.services.BatchService;
037import org.slf4j.Logger;
038import org.springframework.context.annotation.Scope;
039
040/**
041 * Transactions over REST
042 *
043 * @author awoods
044 * @author gregjan
045 */
046@Scope("prototype")
047@Path("/{path: .*}/fcr:tx")
048public class FedoraTransactions extends FedoraBaseResource {
049
050    private static final Logger LOGGER = getLogger(FedoraTransactions.class);
051
052    @Inject
053    protected BatchService batchService;
054
055    /**
056     * Create a new transaction resource and add it to the registry
057     *
058     * @param externalPath the external path
059     * @return 201 with the transaction id and expiration date
060     * @throws URISyntaxException if URI syntax exception occurred
061     */
062    @POST
063    public Response createTransaction(@PathParam("path") final String externalPath) throws URISyntaxException {
064
065        if (batchService.exists(session.getId(), getUserPrincipal())) {
066            LOGGER.debug("renewing transaction {}", session.getId());
067            batchService.refresh(session.getId(), getUserPrincipal());
068            final Response.ResponseBuilder res = noContent();
069            session.getFedoraSession().getExpires().ifPresent(expires -> {
070                res.expires(from(expires));
071            });
072            return res.build();
073        }
074
075        if (externalPath != null && !externalPath.isEmpty()) {
076            return status(BAD_REQUEST).build();
077        }
078
079        batchService.begin(session.getFedoraSession(), getUserPrincipal());
080        session.makeBatchSession();
081        LOGGER.info("Created transaction '{}'", session.getId());
082
083        final Response.ResponseBuilder res = created(
084                new URI(translator().toDomain("/tx:" + session.getId()).toString()));
085        session.getFedoraSession().getExpires().ifPresent(expires -> {
086            res.expires(from(expires));
087        });
088        return res.build();
089    }
090
091    /**
092     * Commit a transaction resource
093     *
094     * @param externalPath the external path
095     * @return 204
096     */
097    @POST
098    @Path("fcr:commit")
099    public Response commit(@PathParam("path") final String externalPath) {
100        LOGGER.info("Commit transaction '{}'", externalPath);
101        return finalizeTransaction(externalPath, getUserPrincipal(), true);
102    }
103
104    /**
105     * Rollback a transaction
106     *
107     * @param externalPath the external path
108     * @return 204
109     */
110    @POST
111    @Path("fcr:rollback")
112    public Response rollback(@PathParam("path") final String externalPath) {
113
114        LOGGER.info("Rollback transaction '{}'", externalPath);
115        return finalizeTransaction(externalPath, getUserPrincipal(), false);
116    }
117
118    private Response finalizeTransaction(@PathParam("path")
119        final String externalPath, final String username, final boolean commit) {
120
121        final String path = toPath(translator(), externalPath);
122        if (!path.equals("/")) {
123            return status(BAD_REQUEST).build();
124        }
125
126        if (!session.isBatchSession()) {
127            LOGGER.debug("cannot finalize an empty tx id {} at path {}", session.getId(), path);
128            return status(BAD_REQUEST).build();
129        }
130
131        if (commit) {
132            LOGGER.debug("commiting transaction {} at path {}", session.getId(), path);
133            batchService.commit(session.getId(), username);
134
135        } else {
136            LOGGER.debug("rolling back transaction {} at path {}", session.getId(), path);
137            batchService.abort(session.getId(), username);
138        }
139        return noContent().build();
140    }
141}