Skip to content

Commit

Permalink
Merge pull request #56 from futures/tx-sessions
Browse files Browse the repository at this point in the history
Tx sessions
  • Loading branch information
barmintor committed May 13, 2013
2 parents 376bb9f + 528eed7 commit 469684b
Show file tree
Hide file tree
Showing 4 changed files with 360 additions and 0 deletions.
@@ -0,0 +1,98 @@
package org.fcrepo.api;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;

import org.fcrepo.AbstractResource;
import org.fcrepo.FedoraObject;
import org.fcrepo.Transaction;
import org.fcrepo.Transaction.State;
import org.fcrepo.jaxb.responses.access.ObjectProfile;
import org.fcrepo.services.ObjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Path("/rest/fcr:tx")
public class FedoraTransactions extends AbstractResource {

@Autowired
private ObjectService objectService;

/* TODO: since transactions have to be available on all nodes, they have to be either persisted or written to a */
/* distributed map or sth, not just this plain hashmap that follows */
private static Map<String, Transaction> transactions = new ConcurrentHashMap<String, Transaction>();

@POST
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public Transaction createTransaction() throws RepositoryException {
Session sess = getAuthenticatedSession();
Transaction tx = new Transaction(sess);
transactions.put(tx.getId(), tx);
return tx;
}

@GET
@Path("/{txid}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public Transaction getTransaction(@PathParam("txid") final String txid) throws RepositoryException {
Transaction tx = transactions.get(txid);
if (tx == null) {
throw new RepositoryException("Transaction with id " + txid + " is not available");
}
return tx;
}

@POST
@Path("/{txid}/fcr:commit")
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public Transaction commit(@PathParam("txid") final String txid) throws RepositoryException {
Transaction tx = transactions.remove(txid);
if (tx == null) {
throw new RepositoryException("Transaction with id " + txid + " is not available");
}
tx.getSession().save();
tx.setState(State.COMMITED);
return tx;
}

@POST
@Path("/{txid}/fcr:rollback")
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
public Transaction rollback(@PathParam("txid") final String txid) throws RepositoryException {
Transaction tx = transactions.remove(txid);
if (tx == null) {
throw new RepositoryException("Transaction with id " + txid + " is not available");
}
tx.setState(State.ROLLED_BACK);
return tx;
}

@POST
@Path("/{txid}/{path: .*}/fcr:newhack")
@Produces({MediaType.TEXT_PLAIN})
public Response createObjectInTransaction(@PathParam("txid") final String txid, @PathParam("path") final List<PathSegment> pathlist)throws RepositoryException {
Transaction tx = transactions.get(txid);
if (tx == null) {
throw new RepositoryException("Transaction with id " + txid + " is not available");
}
final String path = toPath(pathlist);
final FedoraObject obj = objectService.createObject(tx.getSession(), path);
tx.setState(State.DIRTY);
return Response.ok(path).build();
}

}
@@ -0,0 +1,82 @@
package org.fcrepo.api;

import static org.junit.Assert.*;

import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.fcrepo.Transaction;
import org.fcrepo.Transaction.State;
import org.fcrepo.test.util.TestHelpers;
import org.junit.Before;
import org.junit.Test;

public class FedoraTransactionsTest {

FedoraTransactions resource;

Session mockSession;

@Before
public void setup() throws Exception {
resource = new FedoraTransactions();
mockSession = TestHelpers.mockSession(resource);
}

@Test
public void testCreateTx() throws Exception {
Transaction tx = resource.createTransaction();

assertNotNull(tx);
assertNotNull(tx.getCreated());
assertNotNull(tx.getId());
assertTrue(tx.getState() == State.NEW);
}

@Test
public void testGetTx() throws Exception {
Transaction tx = resource.createTransaction();
tx = resource.getTransaction(tx.getId());

assertNotNull(tx);
assertNotNull(tx.getCreated());
assertNotNull(tx.getId());
assertTrue(tx.getState() == State.NEW);
}

@Test
public void testCommitTx() throws Exception {
Transaction tx = resource.createTransaction();
tx = resource.commit(tx.getId());

assertNotNull(tx);
assertNotNull(tx.getCreated());
assertNotNull(tx.getId());
assertTrue(tx.getState() == State.COMMITED);
try{
assertNull(resource.getTransaction(tx.getId()));
fail("Transaction is available after commit");
}catch(RepositoryException e){
// just checking that the exception occurs
}

}

@Test
public void testRollbackTx() throws Exception {
Transaction tx = resource.createTransaction();
tx = resource.rollback(tx.getId());

assertNotNull(tx);
assertNotNull(tx.getCreated());
assertNotNull(tx.getId());
assertTrue(tx.getState() == State.ROLLED_BACK);
try{
assertNull(resource.getTransaction(tx.getId()));
fail("Transaction is available after rollback");
}catch(RepositoryException e){
// just checking that the exception occurs
}
}

}
@@ -0,0 +1,111 @@
package org.fcrepo.integration.api;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import javax.validation.constraints.AssertTrue;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.codehaus.jackson.map.ObjectMapper;
import org.fcrepo.Transaction;
import org.fcrepo.Transaction.State;
import org.junit.Test;

public class FedoraTransactionsIT extends AbstractResourceIT {
@Test
public void testCreateAndGetTransaction() throws Exception {
/* create a tx */
HttpPost createTx = new HttpPost(serverAddress + "fcr:tx");
HttpResponse resp = execute(createTx);
assertTrue(resp.getStatusLine().getStatusCode() == 200);
ObjectMapper mapper = new ObjectMapper();
Transaction tx = mapper.readValue(resp.getEntity().getContent(), Transaction.class);
assertNotNull(tx);
assertNotNull(tx.getId());
assertNotNull(tx.getState());
assertNotNull(tx.getCreated());
assertTrue(tx.getState() == State.NEW);

/* fetch the create dtx from the endpoint */
HttpGet getTx = new HttpGet(serverAddress + "fcr:tx/" + tx.getId());
resp = execute(getTx);
Transaction fetched = mapper.readValue(resp.getEntity().getContent(), Transaction.class);
assertEquals(tx.getId(), fetched.getId());
assertEquals(tx.getState(), fetched.getState());
assertEquals(tx.getCreated(), fetched.getCreated());
}

@Test
public void testCreateAndCommitTransaction() throws Exception {
/* create a tx */
HttpPost createTx = new HttpPost(serverAddress + "fcr:tx");
HttpResponse resp = execute(createTx);
assertTrue(resp.getStatusLine().getStatusCode() == 200);
ObjectMapper mapper = new ObjectMapper();
Transaction tx = mapper.readValue(resp.getEntity().getContent(), Transaction.class);
assertNotNull(tx);
assertNotNull(tx.getId());
assertNotNull(tx.getState());
assertNotNull(tx.getCreated());
assertTrue(tx.getState() == State.NEW);

/* create a new object inside the tx */
HttpPost postNew = new HttpPost(serverAddress + "fcr:tx/" + tx.getId() + "/objects/testobj1/fcr:newhack");
resp = execute(postNew);
assertTrue(resp.getStatusLine().getStatusCode() == 200);

/* check if the object is already there, before the commit */
HttpGet getObj = new HttpGet(serverAddress + "/objects/testobj1");
resp = execute(getObj);
assertTrue(resp.getStatusLine().toString(),resp.getStatusLine().getStatusCode() == 404);

/* commit the tx */
HttpPost commitTx = new HttpPost(serverAddress + "fcr:tx/" + tx.getId() + "/fcr:commit");
resp = execute(commitTx);
Transaction committed = mapper.readValue(resp.getEntity().getContent(), Transaction.class);
assertEquals(committed.getState(), State.COMMITED);

/* check if the object is there, after the commit */
resp = execute(getObj);
assertTrue(resp.getStatusLine().toString(),resp.getStatusLine().getStatusCode() == 200);
}

@Test
public void testCreateAndRollbackTransaction() throws Exception {
/* create a tx */
HttpPost createTx = new HttpPost(serverAddress + "fcr:tx");
HttpResponse resp = execute(createTx);
assertTrue(resp.getStatusLine().getStatusCode() == 200);
ObjectMapper mapper = new ObjectMapper();
Transaction tx = mapper.readValue(resp.getEntity().getContent(), Transaction.class);
assertNotNull(tx);
assertNotNull(tx.getId());
assertNotNull(tx.getState());
assertNotNull(tx.getCreated());
assertTrue(tx.getState() == State.NEW);

/* create a new object inside the tx */
HttpPost postNew = new HttpPost(serverAddress + "fcr:tx/" + tx.getId() + "/objects/testobj2/fcr:newhack");
resp = execute(postNew);
assertTrue(resp.getStatusLine().getStatusCode() == 200);

/* check if the object is already there, before the commit */
HttpGet getObj = new HttpGet(serverAddress + "/objects/testobj2");
resp = execute(getObj);
assertTrue(resp.getStatusLine().toString(),resp.getStatusLine().getStatusCode() == 404);

/* and rollback */
HttpPost rollbackTx = new HttpPost(serverAddress + "fcr:tx/" + tx.getId() + "/fcr:rollback");
resp = execute(rollbackTx);
Transaction rolledBack = mapper.readValue(resp.getEntity().getContent(), Transaction.class);
assertEquals(rolledBack.getId(),tx.getId());
assertTrue(rolledBack.getState() == State.ROLLED_BACK);

/* check if the object is there, after the rollback */
resp = execute(getObj);
assertTrue(resp.getStatusLine().toString(),resp.getStatusLine().getStatusCode() == 404);
}
}
69 changes: 69 additions & 0 deletions fcrepo-kernel/src/main/java/org/fcrepo/Transaction.java
@@ -0,0 +1,69 @@
package org.fcrepo;

import java.beans.Transient;
import java.util.Date;
import java.util.UUID;

import javax.jcr.Session;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import org.codehaus.jackson.annotate.JsonIgnore;

@XmlRootElement(name = "transaction")
@XmlAccessorType(XmlAccessType.FIELD)
public class Transaction {

public enum State {
DIRTY, NEW, COMMITED, ROLLED_BACK;
}

@XmlTransient
private final Session session;

@XmlAttribute(name = "id")
private final String id;

@XmlAttribute(name = "created")
private final Date created;

private State state = State.NEW;

private Transaction(){
this.session = null;
this.created = null;
this.id = null;
}

public Transaction(Session session) {
super();
this.session = session;
this.created = new Date();
this.id = UUID.randomUUID().toString();
}

public Session getSession() {
return session;
}

public Date getCreated() {
return created;
}

public String getId() {
return id;
}

public void setState(State state) {
this.state = state;
}

public State getState() {
return state;
}

}

0 comments on commit 469684b

Please sign in to comment.