package er.rest.util;
import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import com.webobjects.appserver.WOSession;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableDictionary;
import er.extensions.appserver.ERXSession;
import er.extensions.eof.ERXEC;
import er.extensions.foundation.ERXProperties;
import er.rest.routes.ERXRouteRequestHandler;
/**
* EXPERIMENTAL.
*
* @property ERXRest.transactionsEnabled (default 'false')
* @property ERXRestTransaction.transactionManager (default '50')
*
* @author mschrag
*/
public class ERXRestTransactionRequestAdaptor {
private static final String CLIENT_ID_HEADER_KEY = "Client-Id";
private static final String SEQUENCE_ID_HEADER_KEY = "Seq-Id";
private static final String TRANSACTION_HEADER_KEY = "Transaction";
private static final String OPEN_TRANSACTION_HEADER_VALUE = "open";
private static final String COMMIT_TRANSACTION_HEADER_VALUE = "commit";
private static final String EXECUTING_TRANSACTION_KEY = "er.rest.ERXRestTransaction.transaction";
private static final String TRANSACTION_MANAGER_KEY = "er.rest.ERXRestTransaction.transactionManager";
private static ERXRestTransactionRequestAdaptor _defaultAdaptor;
private boolean _transactionsEnabled;
private int _maxEventsPerTransaction;
public static synchronized ERXRestTransactionRequestAdaptor defaultAdaptor() {
if (_defaultAdaptor == null) {
_defaultAdaptor = new ERXRestTransactionRequestAdaptor();
}
return _defaultAdaptor;
}
public ERXRestTransactionRequestAdaptor() {
_transactionsEnabled = ERXProperties.booleanForKeyWithDefault("ERXRest.transactionsEnabled", false);
_maxEventsPerTransaction = ERXProperties.intForKeyWithDefault("ERXRest.maxEventsPerTransaction", 50);
}
protected EOEditingContext newEditingContext() {
return ERXEC.newEditingContext();
}
public boolean transactionsEnabled() {
return _transactionsEnabled;
}
public boolean hasSequence(WOContext context, WORequest request) {
return request.headerForKey(ERXRestTransactionRequestAdaptor.SEQUENCE_ID_HEADER_KEY) != null;
}
public boolean hasTransaction(WOContext context, WORequest request) {
return request.headerForKey(ERXRestTransactionRequestAdaptor.TRANSACTION_HEADER_KEY) != null;
}
public boolean isExecutingTransaction(WOContext context, WORequest request) {
return executingTransaction(context, request) != null;
}
public ERXRestTransaction executingTransaction(WOContext context, WORequest request) {
ERXRestTransaction transaction = null;
NSDictionary<String, Object> userInfo = request.userInfo();
if (userInfo != null) {
transaction = (ERXRestTransaction)userInfo.objectForKey(ERXRestTransactionRequestAdaptor.EXECUTING_TRANSACTION_KEY);
}
return transaction;
}
protected void setExecutingTransaction(ERXRestTransaction transaction, WOContext context, WORequest request) {
NSDictionary<String, Object> immutableUserInfo = request.userInfo();
NSMutableDictionary<String, Object> userInfo = (immutableUserInfo == null) ? new NSMutableDictionary<>() : immutableUserInfo.mutableClone();
userInfo.setObjectForKey(transaction, ERXRestTransactionRequestAdaptor.EXECUTING_TRANSACTION_KEY);
request.setUserInfo(userInfo);
}
public ERXRestTransaction transaction(WOContext context, WORequest request) {
ERXRestTransaction transaction = null;
if (transaction == null) {
ERXRestTransactionManager transactionManager = transactionManager(context, request);
transaction = transaction(context, request, transactionManager);
}
return transaction;
}
public boolean willHandleRequest(WOContext context, WORequest request) {
boolean shouldDispatchRequest = true;
Integer sequenceIDInteger = sequenceID(request);
if (sequenceIDInteger != null) {
int sequenceID = sequenceIDInteger.intValue();
ERXRestTransactionManager transactionManager = transactionManager(context, request);
transactionManager.addSequenceID(sequenceID);
ERXRestTransaction.State state = state(request);
if (state != null) {
ERXRestTransaction transaction = transaction(context, request, transactionManager);
transaction.addEvent(sequenceID, state, request);
shouldDispatchRequest = false;
if (transaction.size() > _maxEventsPerTransaction) {
transactionManager.removeTransaction(transaction);
throw new IllegalArgumentException("You exceeded the maximum number of events for a single transaction.");
}
}
}
return shouldDispatchRequest;
}
public boolean didHandleRequest(WOContext context, WORequest request) {
boolean shouldHandleRequest = true;
ERXRestTransactionManager transactionManager = transactionManager(context, request);
ERXRestTransaction transaction = transaction(context, request, transactionManager);
if (transactionManager.isTransactionReady(transaction)) {
shouldHandleRequest = false;
// MS: This is sketchy -- basically we're about to execute a pile of requests on the same thread and we can't
// check out the session again on the same thread, so we're going to forcefully check it back in
if (context._session() != null) {
// MS: Should we sleep it? Should we just not do this at all?
// wosession._sleepInContext(null);
WOApplication.application().sessionStore().checkInSessionForContext(context);
context._setSession(null);
ERXSession.setSession(null);
}
try {
EOEditingContext editingContext = transaction.editingContext();
try {
for (Object record : transaction.records()) {
WORequest recordRequest = (WORequest)record;
setExecutingTransaction(transaction, context, recordRequest);
recordRequest.removeHeadersForKey(ERXRestTransactionRequestAdaptor.CLIENT_ID_HEADER_KEY);
recordRequest.removeHeadersForKey(ERXRestTransactionRequestAdaptor.SEQUENCE_ID_HEADER_KEY);
recordRequest.removeHeadersForKey(ERXRestTransactionRequestAdaptor.TRANSACTION_HEADER_KEY);
ERXRouteRequestHandler requestHandler = (ERXRouteRequestHandler)WOApplication.application().handlerForRequest(recordRequest);
WOResponse response = requestHandler.handleRequest(recordRequest);
if (response.status() < 200 || response.status() > 299) {
throw new RuntimeException("Transaction failed: " + response.contentString());
}
}
editingContext.saveChanges();
}
finally {
transactionManager.removeTransaction(transaction);
editingContext.dispose();
}
}
finally {
context.session();
}
}
return shouldHandleRequest;
}
protected String clientID(WORequest request) {
return request.headerForKey(ERXRestTransactionRequestAdaptor.CLIENT_ID_HEADER_KEY);
}
protected Integer sequenceID(WORequest request) {
Integer sequenceID = null;
String sequenceIDStr = request.headerForKey(ERXRestTransactionRequestAdaptor.SEQUENCE_ID_HEADER_KEY);
if (sequenceIDStr != null) {
sequenceID = Integer.parseInt(sequenceIDStr);
}
return sequenceID;
}
protected ERXRestTransaction.State state(WORequest request) {
String stateStr = request.headerForKey(ERXRestTransactionRequestAdaptor.TRANSACTION_HEADER_KEY);
ERXRestTransaction.State state;
if (stateStr == null) {
state = null;
}
else if (ERXRestTransactionRequestAdaptor.COMMIT_TRANSACTION_HEADER_VALUE.equals(stateStr)) {
state = ERXRestTransaction.State.Commit;
}
else if (ERXRestTransactionRequestAdaptor.OPEN_TRANSACTION_HEADER_VALUE.equals(stateStr)) {
state = ERXRestTransaction.State.Open;
}
else {
throw new IllegalArgumentException("Unknown transaction state: " + stateStr);
}
return state;
}
protected ERXRestTransactionManager transactionManager(WOContext context, WORequest request) {
WOSession session = context.session();
ERXRestTransactionManager transactionManager = (ERXRestTransactionManager) session.objectForKey(ERXRestTransactionRequestAdaptor.TRANSACTION_MANAGER_KEY);
if (transactionManager == null) {
transactionManager = new ERXRestTransactionManager();
session.setObjectForKey(transactionManager, ERXRestTransactionRequestAdaptor.TRANSACTION_MANAGER_KEY);
}
return transactionManager;
}
protected ERXRestTransaction transaction(WOContext context, WORequest request, ERXRestTransactionManager transactionManager) {
WOSession session = context.session();
String clientID = clientID(request);
if (clientID == null) {
clientID = session.sessionID();
}
return transactionManager.transactionForID(clientID);
}
}