/* * Copyright (C) 2005 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a * copy of the License at * * http://www.alfresco.org/legal/license.txt * * 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.alfresco.repo.action.scheduled; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; /** * Abstract action support. * * Each action applies to a set of nodes. * * These actions may be executed in one overall transaction or one individual transaction. If actions are in individual transactions an error may halt subsequent execution or * processing can try and invoke the action for each node. * * @author Andy Hind */ public abstract class AbstractScheduledAction implements ScheduledActionDefinition { /** * Logging */ private static Log s_logger = LogFactory.getLog(AbstractScheduledAction.class); /** * Enum to define the transaction mode. * * @author Andy Hind */ public enum TransactionMode { ISOLATED_TRANSACTIONS, UNTIL_FIRST_FAILURE, ONE_TRANSACTION; public static TransactionMode getTransactionMode(String transactionModeString) { TransactionMode transactionMode; if (transactionModeString.equalsIgnoreCase("ISOLATED_TRANSACTIONS")) { transactionMode = TransactionMode.ISOLATED_TRANSACTIONS; } else if (transactionModeString.equalsIgnoreCase("UNTIL_FIRST_FAILURE")) { transactionMode = TransactionMode.UNTIL_FIRST_FAILURE; } else if (transactionModeString.equalsIgnoreCase("ONE_TRANSACTION")) { transactionMode = TransactionMode.ONE_TRANSACTION; } else { // The default .... transactionMode = TransactionMode.ISOLATED_TRANSACTIONS; } return transactionMode; } } /** * Enum to define if compensating actions are run. * * @author Andy Hind */ public enum CompensatingActionMode { RUN_COMPENSATING_ACTIONS_ON_FAILURE, IGNORE; public static CompensatingActionMode getCompensatingActionMode(String compensatingActionModeString) { CompensatingActionMode compensatingActionMode; if (compensatingActionModeString.equalsIgnoreCase("RUN_COMPENSATING_ACTIONS_ON_FAILURE")) { compensatingActionMode = CompensatingActionMode.RUN_COMPENSATING_ACTIONS_ON_FAILURE; } else if (compensatingActionModeString.equalsIgnoreCase("IGNORE")) { compensatingActionMode = CompensatingActionMode.IGNORE; } else { // The default .... compensatingActionMode = CompensatingActionMode.IGNORE; } return compensatingActionMode; } } /* * Key used to pass the action in the quartz job definition */ private static final String ACTION_JOB_DATA_MAP_KEY = "Action"; /* * The Action service. */ private ActionService actionService; /* * The user in whose name the action will run. */ private String runAsUser; /* * The template definition of the action. */ private TemplateActionDefinition templateActionDefinition; /* * The transaction mode in which all the nodes found by this sceduled action will be treated. */ private TransactionMode transactionMode = TransactionMode.ISOLATED_TRANSACTIONS; /* * Control if compensating actions will be used. The default is not to apply compensating actions. */ private CompensatingActionMode compensatingActionMode = CompensatingActionMode.IGNORE; /* * The transaction service */ private TransactionService transactionService; /** * Simple constructor */ public AbstractScheduledAction() { super(); } /** * Get the user in whose name to run the action. * * @return */ public String getRunAsUser() { return runAsUser; } /** * Set the user in whose name to run the action. * * @param runAsUser */ public void setRunAsUser(String runAsUser) { this.runAsUser = runAsUser; } /** * Get the template definition. */ public TemplateActionDefinition getTemplateActionDefinition() { return templateActionDefinition; } /** * Set the action service - IOC. * * @param actionService */ public void setActionService(ActionService actionService) { this.actionService = actionService; } /** * Get the actions service. * * @return */ public ActionService getActionService() { return actionService; } /** * Set the behaviour for compensating actiions. * * @param compensatingActionModeString */ public void setCompensatingActionMode(String compensatingActionModeString) { this.compensatingActionMode = CompensatingActionMode.getCompensatingActionMode(compensatingActionModeString); } /** * Set transactional behaviour. * * @param transactionModeString */ public void setTransactionMode(String transactionModeString) { this.transactionMode = TransactionMode.getTransactionMode(transactionModeString); } /** * Get the transaction service. * * @return */ public TransactionService getTransactionService() { return transactionService; } /** * Set the transactions service - IOC. * * @param transactionService */ public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } /** * Set the template action that is used to generate the real action for each node. */ public void setTemplateActionDefinition(TemplateActionDefinition templateActionDefinition) { this.templateActionDefinition = templateActionDefinition; } /** * Get the behaviour for compensating actions. * * @return */ public CompensatingActionMode getCompensatingActionModeEnum() { return compensatingActionMode; } /** * Get the transaction mode. * * @return */ public TransactionMode getTransactionModeEnum() { return transactionMode; } public void register(Scheduler scheduler) throws SchedulerException { JobDetail jobDetail = getJobDetail(); Trigger trigger = getTrigger(); if (s_logger.isDebugEnabled()) { s_logger.debug(("Registering job: " + jobDetail)); s_logger.debug(("With trigger: " + trigger)); } scheduler.scheduleJob(jobDetail, trigger); } /** * Get the trigger definition for this job. Used to register with the injected scheduler. * * @return */ public abstract Trigger getTrigger(); /** * Get the list of nodes against which this action should run. * * @return */ public abstract List getNodes(); /** * Generate the actual action for the given node from the action template. * * @param nodeRef * @return */ public abstract Action getAction(NodeRef nodeRef); /** * Get the job detail. * * @return */ private JobDetail getJobDetail() { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put(ACTION_JOB_DATA_MAP_KEY, this); JobDetail jobDetail = new JobDetail(); jobDetail.setName(getJobName()); jobDetail.setGroup(getJobGroup()); jobDetail.setJobDataMap(jobDataMap); jobDetail.setJobClass(JobDefinition.class); return jobDetail; } /** * Job definition to run scheduled action * * @author Andy Hind */ public static class JobDefinition implements Job { public void execute(JobExecutionContext ctx) throws JobExecutionException { final AbstractScheduledAction abstractScheduledAction = (AbstractScheduledAction) ctx.getJobDetail() .getJobDataMap().get(ACTION_JOB_DATA_MAP_KEY); // Run as the required user AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { public Object doWork() { // Get the list of nodes List nodes = abstractScheduledAction.getNodes(); if (s_logger.isDebugEnabled()) { s_logger.debug("Found " + nodes.size()); } // Individual transactions if (abstractScheduledAction.getTransactionModeEnum() == TransactionMode.ONE_TRANSACTION) { if (s_logger.isDebugEnabled()) { s_logger.debug("Executing in one transaction"); } runTransactionalActions(nodes); return null; } // Single global transaction else { if (s_logger.isDebugEnabled()) { s_logger.debug("Executing in individual transaction"); } for (NodeRef nodeRef : nodes) { try { runTransactionalAction(nodeRef); } catch (Throwable t) { if (abstractScheduledAction.getTransactionModeEnum() == TransactionMode.ISOLATED_TRANSACTIONS) { s_logger .error( "Error in scheduled action executed in isolated transactions (other actions will continue", t); } else { throwRuntimeException(t); } } } return null; } } /** * Apply the action to all nodes in one overall transaction * * @param nodes */ public void runTransactionalActions(final List nodes) { boolean runCompensatingActions = abstractScheduledAction.getCompensatingActionModeEnum() == CompensatingActionMode.RUN_COMPENSATING_ACTIONS_ON_FAILURE; try { TransactionUtil.executeInUserTransaction(abstractScheduledAction.getTransactionService(), new TransactionUtil.TransactionWork() { public Object doWork() throws Exception { // Build the full list of compensating actions // If anything goes wrong we need to do all these instead List> compensatingActions = new ArrayList>( nodes.size()); for (NodeRef nodeRef : nodes) { Action action = abstractScheduledAction.getAction(nodeRef); Action compensatingAction = action.getCompensatingAction(); if (compensatingAction != null) { compensatingActions.add(new Pair(compensatingAction, nodeRef)); } } // Run all the actions try { for (NodeRef nodeRef : nodes) { Action action = abstractScheduledAction.getAction(nodeRef); abstractScheduledAction.getActionService().executeAction(action, nodeRef); } return null; } catch (Throwable t) { // Throw exception to trigger compensating actions throw new CompensatingActionException("Requires compensating action", t, compensatingActions); } } }); } catch (Throwable t) { // Do compensation if required doCompensation(runCompensatingActions, true, t); } } /** * Run compensating actions. * * These are always in their own transaction. We try to run all compensating actions. * * @param runCompensatingActions * @param rethrow * @param t */ private void doCompensation(boolean runCompensatingActions, boolean rethrow, Throwable t) { // If the error triggers compensation, and they should be processed. if (runCompensatingActions && (t instanceof CompensatingActionException)) { CompensatingActionException cae = (CompensatingActionException) t.getCause(); for (Pair pair : cae.getCompensatingActions()) if ((pair != null) && (pair.getFirst() != null) && (pair.getSecond() != null)) { try { // try the compensating action in its own tx runTransactionalCompensatingAction(pair); } catch (Throwable cat) { s_logger.error("Error executing compensating action", t); } } } if (rethrow) { throwRuntimeException(t); } } /** * Run a single transaction in its own tx * * @param nodeRef */ public void runTransactionalAction(final NodeRef nodeRef) { boolean runCompensatingActions = abstractScheduledAction.getCompensatingActionModeEnum() == CompensatingActionMode.RUN_COMPENSATING_ACTIONS_ON_FAILURE; boolean rethrow = abstractScheduledAction.getTransactionModeEnum() != TransactionMode.ISOLATED_TRANSACTIONS; try { TransactionUtil.executeInUserTransaction(abstractScheduledAction.getTransactionService(), new TransactionUtil.TransactionWork() { public Object doWork() throws Exception { // try action - failure triggers compensation Action action = abstractScheduledAction.getAction(nodeRef); Action compensatingAction = action.getCompensatingAction(); try { abstractScheduledAction.getActionService().executeAction(action, nodeRef); return null; } catch (Throwable t) { if (compensatingAction != null) { throw new CompensatingActionException( "Requires compensating action", t, Collections .> singletonList(new Pair( action.getCompensatingAction(), nodeRef))); } else { return throwRuntimeException(t); } } } }); } catch (Throwable t) { // Run compensating action if required doCompensation(runCompensatingActions, rethrow, t); } } /** * Manage running a compensating action and chaining all its compensating actions until done * * @param pair */ public void runTransactionalCompensatingAction(final Pair pair) { boolean runCompensatingActions = abstractScheduledAction.getCompensatingActionModeEnum() == CompensatingActionMode.RUN_COMPENSATING_ACTIONS_ON_FAILURE; try { TransactionUtil.executeInUserTransaction(abstractScheduledAction.getTransactionService(), new TransactionUtil.TransactionWork() { public Object doWork() throws Exception { try { abstractScheduledAction.getActionService().executeAction(pair.getFirst(), pair.getSecond()); return null; } catch (Throwable t) { List> compensatingActions = new ArrayList>( 1); if (pair.getFirst().getCompensatingAction() != null) { compensatingActions.add(new Pair(pair.getFirst() .getCompensatingAction(), pair.getSecond())); } throw new CompensatingActionException("Requires compensating action", t, compensatingActions); } } }); } catch (Throwable t) { // Run compensation doCompensation(runCompensatingActions, true, t); } } }, abstractScheduledAction.getRunAsUser()); } } /** * Simple class to hold to related objects * * @author Andy Hind */ public static class Pair { FIRST first; SECOND second; Pair(FIRST first, SECOND second) { this.first = first; this.second = second; } FIRST getFirst() { return first; } SECOND getSecond() { return second; } } /** * Support method to translate exceptions to runtime exceptions. * * @param t * @return */ private static Object throwRuntimeException(Throwable t) { if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new RuntimeException("Error during execution of transaction.", t); } } }