/*
* Copyright (c) 2010-2011 Lockheed Martin Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eurekastreams.commons.server.async;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.eurekastreams.commons.actions.TaskHandlerAction;
import org.eurekastreams.commons.actions.async.AsyncAction;
import org.eurekastreams.commons.actions.context.ActionContext;
import org.eurekastreams.commons.actions.context.TaskHandlerActionContext;
import org.eurekastreams.commons.actions.context.TaskHandlerActionContextImpl;
import org.eurekastreams.commons.actions.context.async.AsyncActionContext;
import org.eurekastreams.commons.exceptions.ExecutionException;
import org.eurekastreams.commons.exceptions.GeneralException;
import org.eurekastreams.commons.exceptions.ValidationException;
import org.eurekastreams.commons.logging.LogFactory;
import org.eurekastreams.commons.server.UserActionRequest;
import org.eurekastreams.commons.task.TaskHandler;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* This class provides the business logic that controls the execution of an {@link AsyncAction}.
*
* Transaction management, Validation, and Execution strategy sequences are also controlled in here.
*
* Note: Authorization strategies are explicitly excluded from execution. This helps draw a line between the Service and
* Async sides of actions.
*
* In the case of a {@link TaskHandlerAction}, the List of UserActionRequest objects are collected and submitted through
* the action. This is necessary, because UserActionRequest submissions to the queue through JMS is currently not
* covered under the configured transaction strategy. When distributed transactions are available within EurekaStreams
* this behavior will change.
*
* The order of execution for the strategies contained within the {@link AsyncAction} is controlled by this class and
* executed in the following order:
*
* ValidationStrategy ExecutionStrategy
*
* Optionally:
*
* In the case of a {@link TaskHandlerAction}, UserActionRequests will be gathered from the TaskHandlerContext passed to
* the TaskHandlerExecutionStrategy and submitted to the configured TaskHandler of the action.
*
* Exception handling also occurs within this controller. - {@link GeneralException} will be thrown when an unwrapped
* exception is encountered. - {@link ValidationException} will be logged and passed to the client when encountered. -
* {@link ExecutionException} will be logged and passed to the client when encountered.
*
*/
public class AsyncActionController implements AsynchronousActionController
{
/**
* Local instance of the logger.
*/
private final Log logger = LogFactory.make();
/**
* Instance of the configured platform transaction manager.
*/
private final PlatformTransactionManager transMgr;
/**
* Constructor for the AsyncActionController.
*
* @param inTransMgr
* - instance of the {@link PlatformTransactionManager} for this controller.
*/
public AsyncActionController(final PlatformTransactionManager inTransMgr)
{
transMgr = inTransMgr;
}
/**
* Execute the supplied {@link AsyncAction} with the given {@link AsyncActionContext}.
*
* @param inAsyncActionContext
* - instance of the {@link AsyncActionContext} with which to execution the {@link AsyncAction}.
* @param inAsyncAction
* - instance of the {@link AsyncAction} to execute.
* @return - results from the execution of the AsyncAction.
*
* - GeneralException - when an unexpected error occurs. - ValidationException - when a
* {@link ValidationException} occurs. - ExecutionException - when an {@link ExecutionException} occurs.
*/
public Serializable execute(final AsyncActionContext inAsyncActionContext, final AsyncAction inAsyncAction)
{
Serializable results = null;
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
transDef.setName(inAsyncAction.toString());
transDef.setReadOnly(inAsyncAction.isReadOnly());
TransactionStatus transStatus = transMgr.getTransaction(transDef);
try
{
inAsyncAction.getValidationStrategy().validate(inAsyncActionContext);
results = inAsyncAction.getExecutionStrategy().execute(inAsyncActionContext);
transMgr.commit(transStatus);
}
catch (ValidationException vex)
{
onException(transStatus);
logger.warn("Validation failed for the current action.", vex);
for (Entry<String, String> currentError : vex.getErrors().entrySet())
{
logger.warn("Validation key: " + currentError.getKey() + ", value: " + currentError.getValue());
}
throw vex;
}
catch (ExecutionException eex)
{
onException(transStatus);
logger.error("Error occurred during execution.", eex);
throw eex;
}
catch (Exception ex)
{
onException(transStatus);
logger.error("Error occurred performing transaction.", ex);
throw new GeneralException(ex);
}
return results;
}
/**
* This method executes a {@link TaskHandlerAction} with the supplied {@link AsyncActionContext}.
*
* @param inAsyncActionContext
* - instance of the {@link AsyncActionContext} associated with this request.
* @param inTaskHandlerAction
* - instance of the {@link TaskHandlerAction}.
* @return - results of the execution.
*
* - GeneralException - when an unexpected error occurs. - ValidationException - when a
* {@link ValidationException} occurs. - ExecutionException - when an {@link ExecutionException} occurs.
*/
@SuppressWarnings("unchecked")
public Serializable execute(final AsyncActionContext inAsyncActionContext,
final TaskHandlerAction inTaskHandlerAction)
{
Serializable results = null;
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
transDef.setName(inTaskHandlerAction.toString());
transDef.setReadOnly(inTaskHandlerAction.isReadOnly());
TransactionStatus transStatus = transMgr.getTransaction(transDef);
// Assemble special context for TaskHandler actions.
TaskHandlerActionContext<ActionContext> taskHandlerContext = new TaskHandlerActionContextImpl<ActionContext>(
inAsyncActionContext, new ArrayList<UserActionRequest>());
try
{
inTaskHandlerAction.getValidationStrategy().validate(inAsyncActionContext);
results = inTaskHandlerAction.getExecutionStrategy().execute(taskHandlerContext);
transMgr.commit(transStatus);
}
catch (ValidationException vex)
{
onException(transStatus);
logger.warn("Validation failed for the current action.", vex);
for (Entry<String, String> currentError : vex.getErrors().entrySet())
{
logger.warn("Validation key: " + currentError.getKey() + ", value: " + currentError.getValue());
}
throw vex;
}
catch (ExecutionException eex)
{
onException(transStatus);
logger.error("Error occurred during execution.", eex);
throw eex;
}
catch (Exception ex)
{
onException(transStatus);
logger.error("Error occurred performing transaction.", ex);
throw new GeneralException(ex);
}
// Submit the TaskRequests gathered from the execution strategy into the TaskHandlerContext to the TaskHandler.
try
{
TaskHandler currentTaskHandler = inTaskHandlerAction.getTaskHandler();
for (UserActionRequest currentRequest : taskHandlerContext.getUserActionRequests())
{
currentTaskHandler.handleTask(currentRequest);
}
}
catch (Exception ex)
{
logger.error("Error occurred posting UserActionRequests to the queue.", ex);
throw (new GeneralException("Error occurred posting UserActionRequests to the queue.", ex));
}
return results;
}
/**
* Helper class that encapsulates transaction rollback when an exception is encountered.
*
* @param inTransStatus
* - instance of the {@link TransactionStatus} to go along with the currect block.
*/
private void onException(final TransactionStatus inTransStatus)
{
if (!inTransStatus.isCompleted())
{
transMgr.rollback(inTransStatus);
}
// TODO: Perform undo operations here.
}
}