} else {
beganTrans = TransactionUtil.begin(modelService.transactionTimeout);
}
// enlist for XAResource debugging
if (beganTrans && TransactionUtil.debugResources) {
DebugXaResource dxa = new DebugXaResource(modelService.name);
try {
dxa.enlist();
} catch (Exception e) {
Debug.logError(e, module);
}
}
}
try {
int lockRetriesRemaining = LOCK_RETRIES;
boolean needsLockRetry = false;
do {
lockRetriesRemaining--;
// NOTE: general pattern here is to do everything up to the main service call, and retry it all if
//needed because those will be part of the same transaction and have been rolled back
// TODO: if there is an ECA called async or in a new transaction it won't get rolled back
//but will be called again, which means the service may complete multiple times! that would be for
//pre-invoke and earlier events only of course
// setup global transaction ECA listeners to execute later
if (eventMap != null) ServiceEcaUtil.evalRules(modelService.name, eventMap, "global-rollback", ctx, context, result, isError, isFailure);
if (eventMap != null) ServiceEcaUtil.evalRules(modelService.name, eventMap, "global-commit", ctx, context, result, isError, isFailure);
// pre-auth ECA
if (eventMap != null) ServiceEcaUtil.evalRules(modelService.name, eventMap, "auth", ctx, context, result, isError, isFailure);
// check for pre-auth failure/errors
isFailure = ServiceUtil.isFailure(result);
isError = ServiceUtil.isError(result);
//Debug.logInfo("After [" + modelService.name + "] pre-auth ECA, before auth; isFailure=" + isFailure + ", isError=" + isError, module);
context = checkAuth(localName, context, modelService);
GenericValue userLogin = (GenericValue) context.get("userLogin");
if (modelService.auth && userLogin == null) {
throw new ServiceAuthException("User authorization is required for this service: " + modelService.name + modelService.debugInfo());
}
// now that we have authed, if there is a userLogin, set the EE userIdentifier
if (userLogin != null && userLogin.getString("userLoginId") != null) {
GenericDelegator.pushUserIdentifier(userLogin.getString("userLoginId"));
}
// pre-validate ECA
if (eventMap != null) ServiceEcaUtil.evalRules(modelService.name, eventMap, "in-validate", ctx, context, result, isError, isFailure);
// check for pre-validate failure/errors
isFailure = ServiceUtil.isFailure(result);
isError = ServiceUtil.isError(result);
//Debug.logInfo("After [" + modelService.name + "] pre-in-validate ECA, before in-validate; isFailure=" + isFailure + ", isError=" + isError, module);
// validate the context
if (modelService.validate && !isError && !isFailure) {
try {
modelService.validate(context, ModelService.IN_PARAM, locale);
} catch (ServiceValidationException e) {
Debug.logError(e, "Incoming context (in runSync : " + modelService.name + ") does not match expected requirements", module);
throw e;
}
}
// pre-invoke ECA
if (eventMap != null) ServiceEcaUtil.evalRules(modelService.name, eventMap, "invoke", ctx, context, result, isError, isFailure);
// check for pre-invoke failure/errors
isFailure = ServiceUtil.isFailure(result);
isError = ServiceUtil.isError(result);
//Debug.logInfo("After [" + modelService.name + "] pre-invoke ECA, before invoke; isFailure=" + isFailure + ", isError=" + isError, module);
// ===== invoke the service =====
if (!isError && !isFailure) {
Map<String, Object> invokeResult = null;
if (serviceDebugMode) {
invokeResult = modelService.invoker.runSync(localName, engine, context);
modelService.invoker.sendCallbacks(engine, context, invokeResult, null, GenericEngine.SYNC_MODE);
} else {
invokeResult = engine.runSync(localName, modelService, context);
engine.sendCallbacks(modelService, context, invokeResult, GenericEngine.SYNC_MODE);
}
if (invokeResult != null) {
result.putAll(invokeResult);
} else {
Debug.logWarning("Service (in runSync : " + modelService.name + ") returns null result", module);
}
}
// re-check the errors/failures
isFailure = ServiceUtil.isFailure(result);
isError = ServiceUtil.isError(result);
//Debug.logInfo("After [" + modelService.name + "] invoke; isFailure=" + isFailure + ", isError=" + isError, module);
if (beganTrans) {
// crazy stuff here: see if there was a deadlock or other such error and if so retry... which we can ONLY do if we own the transaction!
String errMsg = ServiceUtil.getErrorMessage(result);
// look for the string DEADLOCK in an upper-cased error message; tested on: Derby, MySQL
// - Derby 10.2.2.0 deadlock string: "A lock could not be obtained due to a deadlock"
// - MySQL ? deadlock string: "Deadlock found when trying to get lock; try restarting transaction"
// - Postgres ? deadlock string: TODO
// - Other ? deadlock string: TODO
// TODO need testing in other databases because they all return different error messages for this!
// NOTE DEJ20070908 are there other things we need to check? I don't think so because these will
//be Entity Engine errors that will be caught and come back in an error message... IFF the
//service is written to not ignore it of course!
if (errMsg != null && errMsg.toUpperCase().indexOf("DEADLOCK") >= 0) {
// it's a deadlock! retry...
String retryMsg = "RETRYING SERVICE [" + modelService.name + "]: Deadlock error found in message [" + errMsg + "]; retry [" + (LOCK_RETRIES - lockRetriesRemaining) + "] of [" + LOCK_RETRIES + "]";
// make sure the old transaction is rolled back, and then start a new one
// if there is an exception in these things, let the big overall thing handle it
TransactionUtil.rollback(beganTrans, retryMsg, null);
beganTrans = TransactionUtil.begin(modelService.transactionTimeout);
// enlist for XAResource debugging
if (beganTrans && TransactionUtil.debugResources) {
DebugXaResource dxa = new DebugXaResource(modelService.name);
try {
dxa.enlist();
} catch (Exception e) {
Debug.logError(e, module);
}
}