From 05525aff097948e3c469829e4d50784682e0ba75 Mon Sep 17 00:00:00 2001 From: Andrew Hind Date: Thu, 20 Apr 2006 13:57:06 +0000 Subject: [PATCH] Scheduled Actions git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2673 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../scheduled-action-services-context.xml | 442 ++++++++++++ .../scheduled/AbstractScheduledAction.java | 645 ++++++++++++++++++ .../AbstractTemplateActionDefinition.java | 113 +++ .../CompensatingActionException.java | 74 ++ .../CompositeTemplateActionDefinition.java | 84 +++ ...ledQueryBasedTemplateActionDefinition.java | 309 +++++++++ .../FreeMarkerModelLuceneFunctionTest.java | 150 ++++ ...arkerWithLuceneExtensionsModelFactory.java | 277 ++++++++ .../scheduled/InvalidCronExpression.java | 47 ++ .../scheduled/ScheduledActionDefinition.java | 110 +++ .../scheduled/ScheduledActionException.java | 54 ++ .../SimpleTemplateActionDefinition.java | 224 ++++++ .../scheduled/TemplateActionDefinition.java | 37 + .../scheduled/TemplateActionModelFactory.java | 50 ++ .../repo/template/FreeMarkerProcessor.java | 70 ++ .../repo/template/TemplateServiceImpl.java | 32 +- .../service/cmr/repository/TemplateNode.java | 15 + .../cmr/repository/TemplateProcessor.java | 9 + .../cmr/repository/TemplateService.java | 28 + 19 files changed, 2769 insertions(+), 1 deletion(-) create mode 100644 config/alfresco/extension/scheduled-action-services-context.xml create mode 100644 source/java/org/alfresco/repo/action/scheduled/AbstractScheduledAction.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/AbstractTemplateActionDefinition.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/CompensatingActionException.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/CompositeTemplateActionDefinition.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinition.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/FreeMarkerModelLuceneFunctionTest.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/FreeMarkerWithLuceneExtensionsModelFactory.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/InvalidCronExpression.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/ScheduledActionDefinition.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/ScheduledActionException.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/SimpleTemplateActionDefinition.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/TemplateActionDefinition.java create mode 100644 source/java/org/alfresco/repo/action/scheduled/TemplateActionModelFactory.java diff --git a/config/alfresco/extension/scheduled-action-services-context.xml b/config/alfresco/extension/scheduled-action-services-context.xml new file mode 100644 index 0000000000..674aa84b5b --- /dev/null +++ b/config/alfresco/extension/scheduled-action-services-context.xml @@ -0,0 +1,442 @@ + + + + + + + + + + + + + + + + add-features + + + + + + aspect-name + + {http://www.alfresco.org/model/content/1.0}generalclassifiable + + + + + {http://www.alfresco.org/model/content/1.0}categories + + ${selectSingleNode('workspace://SpacesStore', 'lucene', 'PATH:"/cm:generalclassifiable/cm:Languages/cm:English"' )} + + + + + + + + + + + + + + + + + + + + + remove-features + + + + + + aspect-name + + {http://www.alfresco.org/model/content/1.0}generalclassifiable + + + + + + + + + + + + + + + + + + + + + copy + + + + + + destination-folder + + ${selectSingleNode('workspace://SpacesStore', 'lucene', 'PATH:"/app:company_home"' )} + + + + assoc-type + + ${node.primaryParentAssoc.typeQName} + + + + assoc-name + + ${node.primaryParentAssoc.QName} + + + + deep-copy + + false + + + + + + + + + + + + + + + + + + + + + set-property-value + + + + + + property + + {http://www.alfresco.org/model/content/1.0}created + + + + value + + ${today?string("yyyy-MM-dd'T'HH:mm:ss.sss'Z'")} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UNTIL_FIRST_FAILURE + + + IGNORE + + + + + + + + + lucene + + + + workspace://SpacesStore + + + + + PATH:"//\*" -ASPECT:"{http://www.alfresco.org/model/content/1.0}generalclassifiable" + + + 0 50 * * * ? + + + jobA + + + jobGroup + + + triggerA + + + triggerGroup + + + + + + + + + + + + + + + + + + System + + + + + + + UNTIL_FIRST_FAILURE + + + IGNORE + + + + + + + + + lucene + + + + workspace://SpacesStore + + + + ASPECT:"{http://www.alfresco.org/model/content/1.0}generalclassifiable" + + + 0 55 * * * ? + + + jobB + + + jobGroup + + + triggerB + + + triggerGroup + + + + + + + + + + + + + + + + + + System + + + + + + + UNTIL_FIRST_FAILURE + + + IGNORE + + + + + + + + + lucene + + + + workspace://SpacesStore + + + + +PATH:"/app:company_home/*//*" +TEXT:"tutorial" + + + 0 40 * * * ? + + + jobC + + + jobGroup + + + triggerC + + + triggerGroup + + + + + + + + + + + + + + + + + + System + + + + + + + UNTIL_FIRST_FAILURE + + + IGNORE + + + + + + + + + lucene + + + + workspace://SpacesStore + + + + @cm\:created:${luceneDateRange(yesterday, "-P10Y")} + + + 0 0/1 * * * ? + + + jobD + + + jobGroup + + + triggerD + + + triggerGroup + + + + + + + + + + + + + + + + + + System + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/scheduled/AbstractScheduledAction.java b/source/java/org/alfresco/repo/action/scheduled/AbstractScheduledAction.java new file mode 100644 index 0000000000..231ac14223 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/AbstractScheduledAction.java @@ -0,0 +1,645 @@ +/* + * 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); + } + } +} diff --git a/source/java/org/alfresco/repo/action/scheduled/AbstractTemplateActionDefinition.java b/source/java/org/alfresco/repo/action/scheduled/AbstractTemplateActionDefinition.java new file mode 100644 index 0000000000..d7ea57f972 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/AbstractTemplateActionDefinition.java @@ -0,0 +1,113 @@ +/* + * 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 org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.TemplateService; + +/** + * Common attributes for template action definitions. + * + * @author Andy Hind + */ +public abstract class AbstractTemplateActionDefinition implements TemplateActionDefinition +{ + /* + * The action service + */ + public ActionService actionService; + + /* + * The template service + */ + public TemplateService templateService; + + /* + * The compensating action + */ + protected TemplateActionDefinition compensatingTemplateActionDefinition; + + /** + * Simple construction + */ + public AbstractTemplateActionDefinition() + { + super(); + } + + /** + * Get the action service. + * + * @return + */ + public ActionService getActionService() + { + return actionService; + } + + /** + * Set the action service - IOC. + * + * @param actionService + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * Get the template service. + * + * @return + */ + public TemplateService getTemplateService() + { + return templateService; + } + + /** + * Set the template service - IOC. + * + * @param templateService + */ + public void setTemplateService(TemplateService templateService) + { + this.templateService = templateService; + } + + /** + * Set the template to define the compensating action. + * + * @param compensatingTemplateActionDefinition + */ + public void setCompensatingTemplateCompositeActionDefinition( + TemplateActionDefinition compensatingTemplateActionDefinition) + { + this.compensatingTemplateActionDefinition = compensatingTemplateActionDefinition; + } + + /** + * Get the template that defines the conpensating action. + * + * @return + */ + public TemplateActionDefinition getCompensatingTemplateCompositeActionDefinition() + { + return compensatingTemplateActionDefinition; + } + +} diff --git a/source/java/org/alfresco/repo/action/scheduled/CompensatingActionException.java b/source/java/org/alfresco/repo/action/scheduled/CompensatingActionException.java new file mode 100644 index 0000000000..80d6250496 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/CompensatingActionException.java @@ -0,0 +1,74 @@ +/* + * 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.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.action.scheduled.AbstractScheduledAction.Pair; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Error that triggers the execution of compensating actions. + * + * The required compensating actions are contained by the exception thrown. + * + * @author Andy Hind + */ +public class CompensatingActionException extends AlfrescoRuntimeException +{ + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 2144573075007116603L; + + List> compensatingActions; + + public CompensatingActionException(String msgId) + { + super(msgId); + } + + public CompensatingActionException(String msgId, Throwable cause, List> compensatingActions) + { + super(msgId, cause); + this.compensatingActions = compensatingActions; + } + + public List> getCompensatingActions() + { + return compensatingActions; + } + + public CompensatingActionException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + public CompensatingActionException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public CompensatingActionException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + +} diff --git a/source/java/org/alfresco/repo/action/scheduled/CompositeTemplateActionDefinition.java b/source/java/org/alfresco/repo/action/scheduled/CompositeTemplateActionDefinition.java new file mode 100644 index 0000000000..9741d921d2 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/CompositeTemplateActionDefinition.java @@ -0,0 +1,84 @@ +/* + * 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.List; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.CompositeAction; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * The template to define a composite action. + * + * @author Andy Hind + */ +public class CompositeTemplateActionDefinition extends AbstractTemplateActionDefinition +{ + + /* + * The list of action templates that define this composite + */ + private List templateActionDefinitions; + + public CompositeTemplateActionDefinition() + { + super(); + } + + /** + * Set the action templates - IOC. + * + * @param templateActionDefinitions + */ + public void setTemplateActionDefinitions(List templateActionDefinitions) + { + this.templateActionDefinitions = templateActionDefinitions; + + } + + /** + * Get the list of template actions. + * + * @return + */ + public List templateActionDefinitions() + { + return templateActionDefinitions; + } + + /** + * Build the composite action in the context of the given node. + * + */ + public Action getAction(NodeRef nodeRef) + { + CompositeAction compositeAction = getActionService().createCompositeAction(); + for(TemplateActionDefinition tad : templateActionDefinitions) + { + compositeAction.addAction(tad.getAction(nodeRef)); + } + + if (getCompensatingTemplateCompositeActionDefinition() != null) + { + compositeAction.setCompensatingAction(getCompensatingTemplateCompositeActionDefinition().getAction(nodeRef)); + } + + return compositeAction; + } + +} diff --git a/source/java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinition.java b/source/java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinition.java new file mode 100644 index 0000000000..20804ae276 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/CronScheduledQueryBasedTemplateActionDefinition.java @@ -0,0 +1,309 @@ +/* + * 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.text.ParseException; +import java.util.LinkedList; +import java.util.List; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.quartz.CronTrigger; +import org.quartz.Scheduler; +import org.quartz.Trigger; + +/** + * A scheduled action for which the trigger is defined in the standard cron format and the nodes to which the + * action should be run is defined from the nodes selected by query. + * + * @author Andy Hind + */ +public class CronScheduledQueryBasedTemplateActionDefinition extends AbstractScheduledAction +{ + /* + * The search service. + */ + private SearchService searchService; + + /* + * The template service. + */ + private TemplateService templateService; + + /* + * The query language to use + */ + private String queryLanguage; + + /* + * The stores against which the query should run + */ + private List stores; + + /* + * The template for the query + */ + private String queryTemplate; + + /* + * The cron expression + */ + private String cronExpression; + + /* + * The name of the job + */ + private String jobName; + + /* + * The job group + */ + private String jobGroup; + + /* + * The name of the trigger + */ + private String triggerName; + + /* + * The name of the trigger group + */ + private String triggerGroup; + + /* + * The scheduler + */ + private Scheduler scheduler; + + /* + * The templateModelFactory + * + * This defines in which template language the query template is defined + */ + private TemplateActionModelFactory templateActionModelFactory; + + public CronScheduledQueryBasedTemplateActionDefinition() + { + super(); + } + + // + // IOC + // + + public SearchService getSearchService() + { + return searchService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public TemplateService getTemplateService() + { + return templateService; + } + + public void setTemplateService(TemplateService templateService) + { + this.templateService = templateService; + } + + public Scheduler getScheduler() + { + return scheduler; + } + + public void setScheduler(Scheduler scheduler) + { + this.scheduler = scheduler; + } + + public TemplateActionModelFactory getTemplateActionModelFactory() + { + return templateActionModelFactory; + } + + public void setTemplateActionModelFactory(TemplateActionModelFactory templateActionModelFactory) + { + this.templateActionModelFactory = templateActionModelFactory; + } + + // + // End of IOC + // + + @Override + public Trigger getTrigger() + { + try + { + return new CronTrigger(getTriggerName(), getTriggerGroup(), getCronExpression()); + } + catch (ParseException e) + { + throw new InvalidCronExpression("Invalid chron expression: n" + getCronExpression()); + } + } + + @Override + public List getNodes() + { + LinkedList nodeRefs = new LinkedList(); + + // Build the actual query string + String queryTemplate = getQueryTemplate(); + String query = templateService.processTemplateString(getTemplateActionModelFactory().getTemplateEngine(), + queryTemplate, getTemplateActionModelFactory().getModel()); + + // Execute the query + SearchParameters sp = new SearchParameters(); + sp.setLanguage(getQueryLanguage()); + sp.setQuery(query); + for (String storeRef : getStores()) + { + sp.addStore(new StoreRef(storeRef)); + } + + // Transform the reults into a node list + ResultSet results = null; + try + { + results = searchService.query(sp); + for (ResultSetRow row : results) + { + nodeRefs.add(row.getNodeRef()); + } + } + finally + { + if (results != null) + { + results.close(); + } + } + return nodeRefs; + } + + @Override + public Action getAction(NodeRef nodeRef) + { + // Use the template to build its action + return getTemplateActionDefinition().getAction(nodeRef); + } + + // + // IOC/Getters/Setters for instance variables + // + + public void setQueryLanguage(String queryLanguage) + { + this.queryLanguage = queryLanguage; + } + + public String getQueryLanguage() + { + return queryLanguage; + } + + public void setStores(List stores) + { + this.stores = stores; + } + + public List getStores() + { + return stores; + } + + public void setQueryTemplate(String queryTemplate) + { + this.queryTemplate = queryTemplate; + } + + public String getQueryTemplate() + { + return queryTemplate; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + public String getCronExpression() + { + return cronExpression; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobName() + { + return jobName; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setTriggerName(String triggerName) + { + this.triggerName = triggerName; + } + + public String getTriggerName() + { + return triggerName; + } + + public void setTriggerGroup(String triggerGroup) + { + this.triggerGroup = triggerGroup; + } + + public String getTriggerGroup() + { + return this.triggerGroup; + } + + /** + * Register with the scheduler. + */ + public void afterPropertiesSet() throws Exception + { + register(getScheduler()); + } + +} diff --git a/source/java/org/alfresco/repo/action/scheduled/FreeMarkerModelLuceneFunctionTest.java b/source/java/org/alfresco/repo/action/scheduled/FreeMarkerModelLuceneFunctionTest.java new file mode 100644 index 0000000000..d596744276 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/FreeMarkerModelLuceneFunctionTest.java @@ -0,0 +1,150 @@ +/* + * 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.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.datatype.Duration; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.ISO8601DateFormat; +import org.springframework.context.ApplicationContext; + +/** + * Test that the correct date ranges are generated for lucene + * + * @author Andy Hind + */ +public class FreeMarkerModelLuceneFunctionTest extends TestCase +{ + //private static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sssZ"); + private static SimpleDateFormat SDF2 = new SimpleDateFormat("yyyy-MM-dd"); + + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + private AuthenticationComponent authenticationComponent; + private ServiceRegistry serviceRegistry; + private UserTransaction tx; + + private Date today; + + public FreeMarkerModelLuceneFunctionTest() + { + super(); + } + + public FreeMarkerModelLuceneFunctionTest(String arg0) + { + super(arg0); + } + + public void setUp() throws Exception + { + + authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl"); + serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); + + this.authenticationComponent.setSystemUserAsCurrentUser(); + + TransactionService transactionService = (TransactionService) ctx.getBean(ServiceRegistry.TRANSACTION_SERVICE + .getLocalName()); + tx = transactionService.getUserTransaction(); + tx.begin(); + + GregorianCalendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + today = cal.getTime(); + + + } + + @Override + protected void tearDown() throws Exception + { + authenticationComponent.clearCurrentSecurityContext(); + tx.rollback(); + super.tearDown(); + } + + public void testDate() + { + String template = "${date?date?string(\"yyyy-MM-dd\")}"; + FreeMarkerWithLuceneExtensionsModelFactory mf = new FreeMarkerWithLuceneExtensionsModelFactory(); + mf.setServiceRegistry(serviceRegistry); + String result = serviceRegistry.getTemplateService().processTemplateString("freemarker", template, mf.getModel()); + assertEquals(result, SDF2.format(new Date())); + } + + public void testLuceneDateRangeFunction() + { + String template = "${luceneDateRange(\"2000-01-01T00:00:00.000Z\", \"P1D\")}"; + FreeMarkerWithLuceneExtensionsModelFactory mf = new FreeMarkerWithLuceneExtensionsModelFactory(); + mf.setServiceRegistry(serviceRegistry); + String result = serviceRegistry.getTemplateService().processTemplateString("freemarker", template, mf.getModel()); + assertEquals(result, "[2000-01-01T00:00:00.000Z TO 2000-01-02T00:00:00.000Z]"); + } + + public void testLuceneDateRangeFunctionToAdte() + { + String template = "${luceneDateRange(\"2000-01-01T00:00:00.000Z\", \"2000-01-05T00:00:00.000Z\")}"; + FreeMarkerWithLuceneExtensionsModelFactory mf = new FreeMarkerWithLuceneExtensionsModelFactory(); + mf.setServiceRegistry(serviceRegistry); + String result = serviceRegistry.getTemplateService().processTemplateString("freemarker", template, mf.getModel()); + assertEquals(result, "[2000-01-01T00:00:00.000Z TO 2000-01-05T00:00:00.000Z]"); + } + + public void testLuceneDateRangeFunctionTodayPlus4() + { + String template = "${luceneDateRange(today, \"P4D\")}"; + FreeMarkerWithLuceneExtensionsModelFactory mf = new FreeMarkerWithLuceneExtensionsModelFactory(); + mf.setServiceRegistry(serviceRegistry); + String result = serviceRegistry.getTemplateService().processTemplateString("freemarker", template, mf.getModel()); + assertNotNull(result); + assertEquals(result, "["+ISO8601DateFormat.format(today) + " TO " + ISO8601DateFormat.format(Duration.add(today, new Duration("P4D"))) + "]"); + } + + public void testLuceneDateRangeFunctionTodayMinus4() + { + String template = "${luceneDateRange(today, \"-P4D\")}"; + FreeMarkerWithLuceneExtensionsModelFactory mf = new FreeMarkerWithLuceneExtensionsModelFactory(); + mf.setServiceRegistry(serviceRegistry); + String result = serviceRegistry.getTemplateService().processTemplateString("freemarker", template, mf.getModel()); + assertEquals(result, "["+ ISO8601DateFormat.format(Duration.add(today, new Duration("-P4D"))) + " TO " + ISO8601DateFormat.format(today) + "]"); + } + + + public void testLuceneDateRangeFunctionTodayToday() + { + String template = "${luceneDateRange(today, today)}"; + FreeMarkerWithLuceneExtensionsModelFactory mf = new FreeMarkerWithLuceneExtensionsModelFactory(); + mf.setServiceRegistry(serviceRegistry); + String result = serviceRegistry.getTemplateService().processTemplateString("freemarker", template, mf.getModel()); + assertEquals(result, "["+ISO8601DateFormat.format(today) + " TO " + ISO8601DateFormat.format(today) + "]"); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/action/scheduled/FreeMarkerWithLuceneExtensionsModelFactory.java b/source/java/org/alfresco/repo/action/scheduled/FreeMarkerWithLuceneExtensionsModelFactory.java new file mode 100644 index 0000000000..a38dd4d49a --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/FreeMarkerWithLuceneExtensionsModelFactory.java @@ -0,0 +1,277 @@ +/* + * 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.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateNode; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.repository.datatype.Duration; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.util.ISO8601DateFormat; + +import freemarker.template.TemplateDateModel; +import freemarker.template.TemplateMethodModelEx; +import freemarker.template.TemplateModelException; +import freemarker.template.TemplateScalarModel; + +/** + * A factory implementation to build suitable models for the freemarker templating language. + * + * @author Andy Hind + */ +public class FreeMarkerWithLuceneExtensionsModelFactory implements TemplateActionModelFactory +{ + /* + * Service registry + */ + private ServiceRegistry serviceRegistry; + + public FreeMarkerWithLuceneExtensionsModelFactory() + { + super(); + } + + // IOC + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * Get the non-contextual model. + * + * This defines: + *
    + *
  1. dates: date, today, yesterday, tomorrow + *
  2. functions: luceneDateRange, selectSingleNode + *
+ */ + public Map getModel() + { + GregorianCalendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + HashMap model = new HashMap(); + + model.put("date", new Date()); + + Date today = cal.getTime(); + model.put("today", today); + + model.put("yesterday", Duration.add(today, new Duration("-P1D"))); + + model.put("tomorrow", Duration.add(today, new Duration("P1D"))); + + model.put("luceneDateRange", new LuceneDateRangeFunction()); + + model.put("selectSingleNode", new QueryForSingleNodeFunction()); + + return model; + } + + /** + * Defines a non-contextual nod model + the contextual node + */ + public Map getModel(NodeRef nodeRef) + { + Map model = getModel(); + + TemplateNode companyRootNode = new TemplateNode(nodeRef, serviceRegistry, null); + model.put("node", companyRootNode); + + return model; + } + + /** + * Function to find a single node by query + * + * @author Andy Hind + */ + private class QueryForSingleNodeFunction implements TemplateMethodModelEx + { + public Object exec(List args) throws TemplateModelException + { + if (args.size() == 3) + { + Object arg0 = args.get(0); + Object arg1 = args.get(1); + Object arg2 = args.get(2); + StoreRef storeRef; + String language; + String query; + + if (arg0 instanceof TemplateScalarModel) + { + storeRef = new StoreRef(((TemplateScalarModel) arg0).getAsString()); + } + else + { + throw new TemplateModelException("Invalid store string"); + } + + if (arg1 instanceof TemplateScalarModel) + { + language = ((TemplateScalarModel) arg1).getAsString(); + } + else + { + throw new TemplateModelException("Invalid language string"); + } + + if (arg2 instanceof TemplateScalarModel) + { + query = ((TemplateScalarModel) arg2).getAsString(); + } + else + { + throw new TemplateModelException("Invalid query string"); + } + + SearchParameters sp = new SearchParameters(); + sp.addStore(storeRef); + sp.setLanguage(language); + sp.setQuery(query); + + ResultSet results = serviceRegistry.getSearchService().query(sp); + + if (results.length() == 0) + { + throw new TemplateModelException("No nodes selected"); + } + + else if (results.length() == 1) + { + return results.getNodeRef(0).toString(); + } + else + { + throw new TemplateModelException("More than one node selected"); + } + } + else + { + throw new TemplateModelException("Incorrect arguments"); + } + } + } + + /** + * Function to generate the date range portion of a lucene query + * + * @author Andy Hind + */ + private static class LuceneDateRangeFunction implements TemplateMethodModelEx + { + + public Object exec(List args) throws TemplateModelException + { + if (args.size() == 2) + { + + Object arg0 = args.get(0); + Object arg1 = args.get(1); + + Date startDate = null; + Date endDate = null; + + if (arg0 instanceof TemplateDateModel) + { + startDate = (Date) ((TemplateDateModel) arg0).getAsDate(); + } + else if (arg0 instanceof TemplateScalarModel) + { + String startDateString = ((TemplateScalarModel) arg0).getAsString(); + startDate = ISO8601DateFormat.parse(startDateString); + } + else + { + throw new TemplateModelException("Invalid date entry"); + } + + if (arg1 instanceof TemplateDateModel) + { + endDate = (Date) ((TemplateDateModel) arg0).getAsDate(); + } + else if (arg1 instanceof TemplateScalarModel) + { + + String valueString = ((TemplateScalarModel) arg1).getAsString(); + try + { + Duration duration = new Duration(valueString); + endDate = Duration.add(startDate, duration); + } + catch (Exception e) + { + endDate = ISO8601DateFormat.parse(valueString); + } + } + else + { + throw new TemplateModelException("Invalid date entry"); + } + + if (startDate.compareTo(endDate) > 0) + { + Date temp = startDate; + startDate = endDate; + endDate = temp; + } + + StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append(DefaultTypeConverter.INSTANCE.convert(String.class, startDate)); + builder.append(" TO "); + builder.append(DefaultTypeConverter.INSTANCE.convert(String.class, endDate)); + builder.append("]"); + + return builder.toString(); + + } + else + { + throw new TemplateModelException("Invalid date entry"); + } + + } + + } + + /** + * The name of the template engine for which this model applies. + * In this case, "freemarker". + */ + public String getTemplateEngine() + { + return "freemarker"; + } + +} diff --git a/source/java/org/alfresco/repo/action/scheduled/InvalidCronExpression.java b/source/java/org/alfresco/repo/action/scheduled/InvalidCronExpression.java new file mode 100644 index 0000000000..5092e9168e --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/InvalidCronExpression.java @@ -0,0 +1,47 @@ +/* + * 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; + +public class InvalidCronExpression extends ScheduledActionException +{ + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = -6618964886875008727L; + + public InvalidCronExpression(String msgId) + { + super(msgId); + } + + public InvalidCronExpression(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + public InvalidCronExpression(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public InvalidCronExpression(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + +} diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledActionDefinition.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledActionDefinition.java new file mode 100644 index 0000000000..6f6734a3e7 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledActionDefinition.java @@ -0,0 +1,110 @@ +/* + * 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 org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.InitializingBean; + +/** + * The information needed to schedule a job. + * + * The implementation is responsible for creating job details, triggers and registering with a scheduler. + * + * This is not used anywhere at the moment. When we have a service then scheduled actions will be registered with the service. + * + * @author Andy Hind + */ +public interface ScheduledActionDefinition extends InitializingBean +{ + /** + * Set the template action definition that is used to build the Action to execute. + * + * @param templateActionDefinition + */ + public void setTemplateActionDefinition(TemplateActionDefinition templateActionDefinition); + + /** + * Get the template action definition that is used to build the Action to execute. + * + * @return + */ + public TemplateActionDefinition getTemplateActionDefinition(); + + /** + * Register with a scheduler. This should be called in the implementation afterPropertiesSet() method of InitializingBean + * + * @param scheduler + * @throws SchedulerException + */ + public void register(Scheduler scheduler) throws SchedulerException; + + /** + * Set the name of the job - used for job admin + * + * @param jobName + */ + public void setJobName(String jobName); + + /** + * Get the name of the job - used for job admin + * + * @return + */ + public String getJobName(); + + /** + * Set the job group - used for job admin + * + * @param jobGroup + */ + public void setJobGroup(String jobGroup); + + /** + * Get the job group - used for job admin + * @return + */ + public String getJobGroup(); + + /** + * Set the trigger name - used for job admin + * + * @param triggerName + */ + public void setTriggerName(String triggerName); + + /** + * Get the trigger name - used for job admin + * @return + */ + public String getTriggerName(); + + /** + * Set the trigger group - used for job admin + * + * @param triggerGroup + */ + public void setTriggerGroup(String triggerGroup); + + /** + * Get the trigger group - used for job admin + * + * @return + */ + public String getTriggerGroup(); + +} diff --git a/source/java/org/alfresco/repo/action/scheduled/ScheduledActionException.java b/source/java/org/alfresco/repo/action/scheduled/ScheduledActionException.java new file mode 100644 index 0000000000..33e6bab4b1 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/ScheduledActionException.java @@ -0,0 +1,54 @@ +/* + * 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 org.alfresco.error.AlfrescoRuntimeException; + +/** + * Base exception for sceduled actions. + * + * @author Andy Hind + */ +public class ScheduledActionException extends AlfrescoRuntimeException +{ + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = -543079391770744598L; + + public ScheduledActionException(String msgId) + { + super(msgId); + } + + public ScheduledActionException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + public ScheduledActionException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + public ScheduledActionException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + +} diff --git a/source/java/org/alfresco/repo/action/scheduled/SimpleTemplateActionDefinition.java b/source/java/org/alfresco/repo/action/scheduled/SimpleTemplateActionDefinition.java new file mode 100644 index 0000000000..b1ed289165 --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/SimpleTemplateActionDefinition.java @@ -0,0 +1,224 @@ +/* + * 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.io.Serializable; +import java.util.Map; + +import org.alfresco.repo.action.executer.ActionExecuter; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * This class defines the template used to build a single action. + * + * @author Andy Hind + */ +public class SimpleTemplateActionDefinition extends AbstractTemplateActionDefinition implements ApplicationContextAware +{ + /* + * The name of the action + */ + private String actionName; + + /* + * The parameters used by the action + */ + private Map parameterTemplates; + + /* + * The model factory to build models appropriate to the template language used to define + * templated parameters. + */ + private TemplateActionModelFactory templateActionModelFactory; + + /* + * The dictionary service. + */ + private DictionaryService dictionaryService; + + /* + * The application context + * (Some actions are not publicly exposed via the action service. + * They can always be obtained via the appropriate action excecutor.) + */ + private ApplicationContext applicationContext; + + /** + * Simple constructor. + * + */ + public SimpleTemplateActionDefinition() + { + super(); + } + + /** + * Get the template model factory. + * + * @return + */ + public TemplateActionModelFactory getTemplateActionModelFactory() + { + return templateActionModelFactory; + } + + /** + * Set the template model factory IOC. + * + * @param templateActionModelFactory + */ + public void setTemplateActionModelFactory(TemplateActionModelFactory templateActionModelFactory) + { + this.templateActionModelFactory = templateActionModelFactory; + } + + /** + * Get the dictionary service. + * + * @return + */ + public DictionaryService getDictionaryService() + { + return dictionaryService; + } + + /** + * Set the dictionary service - IOC. + * + * @param dictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Set the name of the action. + * + * @param actionName + */ + public void setActionName(String actionName) + { + this.actionName = actionName; + } + + /** + * Get the name of the action. + * + * @return + */ + public String getActionName() + { + return actionName; + } + + /** + * Set the map of parameters used by the template. + * These are processed via the template service to produce the actual poarameters. + * + * @param parameterTemplates + */ + public void setParameterTemplates(Map parameterTemplates) + { + this.parameterTemplates = parameterTemplates; + } + + + /** + * Get the templates that define the parameters for the action. + * + * @return + */ + public Map getParameterTemplates() + { + return parameterTemplates; + } + + /** + * Generate the action from the template using the context node. + */ + public Action getAction(NodeRef nodeRef) + { + // Get the action definition. We can not go to the service are some are not exposed. + // So we find them from the application context. + ActionExecuter actionExecutor = (ActionExecuter)applicationContext.getBean(getActionName()); + ActionDefinition actionDefinition = actionExecutor.getActionDefinition(); + + + // Build the base action + Action action = actionService.createAction(getActionName()); + + // Go through the template definitions and set the values. + for (String paramName : parameterTemplates.keySet()) + { + // Transform the template + String template = parameterTemplates.get(paramName); + String stringValue = templateService.processTemplateString(getTemplateActionModelFactory() + .getTemplateEngine(), template, getTemplateActionModelFactory().getModel(nodeRef)); + + // Find the data type from the action defintion + DataTypeDefinition dataTypeDef; + if (actionDefinition.getParameterDefintion(paramName) != null) + { + dataTypeDef = dictionaryService + .getDataType(actionDefinition.getParameterDefintion(paramName).getType()); + } + // Fall back to the DD using the property name of it is not defined + // This is sometimes used for setting a property to a value. + // There can be no definition for such an ad hoc property. + else + { + dataTypeDef = dictionaryService.getProperty(QName.createQName(paramName)).getDataType(); + } + + // Convert the template result into the correct type and set the parameter + Object value = DefaultTypeConverter.INSTANCE.convert(dataTypeDef, stringValue); + if (value instanceof Serializable) + { + action.setParameterValue(paramName, (Serializable) value); + } + + } + + // If there is a compensating action then set it. + if (getCompensatingTemplateCompositeActionDefinition() != null) + { + action.setCompensatingAction(getCompensatingTemplateCompositeActionDefinition().getAction(nodeRef)); + } + + return action; + } + + /** + * ApplciationContextAware - get the application context. + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + this.applicationContext = applicationContext; + + } +} diff --git a/source/java/org/alfresco/repo/action/scheduled/TemplateActionDefinition.java b/source/java/org/alfresco/repo/action/scheduled/TemplateActionDefinition.java new file mode 100644 index 0000000000..49ebf2da5d --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/TemplateActionDefinition.java @@ -0,0 +1,37 @@ +/* + * 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 org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * A template action definition is used to generate an action from a template style + * definition. + * + * @author Andy Hind + */ +public interface TemplateActionDefinition +{ + /** + * Generate an action definition for the action defined by this template. + * + * @param nodeRef + * @return + */ + public Action getAction(NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/repo/action/scheduled/TemplateActionModelFactory.java b/source/java/org/alfresco/repo/action/scheduled/TemplateActionModelFactory.java new file mode 100644 index 0000000000..1a05ad535f --- /dev/null +++ b/source/java/org/alfresco/repo/action/scheduled/TemplateActionModelFactory.java @@ -0,0 +1,50 @@ +/* + * 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 org.alfresco.service.cmr.repository.NodeRef; + +/** + * A factory that builds models to use with a particular template engine for use with scheduled actions built + * from action templates. + * + * @author Andy Hind + */ +public interface TemplateActionModelFactory +{ + /** + * Get the name of the template engine for which this factory applies + * + * @return + */ + public String getTemplateEngine(); + + /** + * Build a model with no default node context. + * + * @return + */ + public Object getModel(); + + /** + * Build a model with a default node context. + * + * @param nodeRef + * @return + */ + public Object getModel(NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java b/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java index 06d81afef6..b3ea0f06c6 100644 --- a/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java +++ b/source/java/org/alfresco/repo/template/FreeMarkerProcessor.java @@ -23,9 +23,11 @@ import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TemplateException; import org.alfresco.service.cmr.repository.TemplateProcessor; +import org.alfresco.util.ISO9075; import org.apache.log4j.Logger; import freemarker.cache.MruCacheStorage; +import freemarker.cache.StringTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateExceptionHandler; @@ -98,6 +100,28 @@ public class FreeMarkerProcessor implements TemplateProcessor return this.config; } + private Configuration getStringConfig(String path, String template) + { + + Configuration config = new Configuration(); + + // setup template cache + config.setCacheStorage(new MruCacheStorage(20, 0)); + + // use our custom loader to find templates on the ClassPath + StringTemplateLoader stringTemplateLoader = new StringTemplateLoader(); + stringTemplateLoader.putTemplate(path, template); + config.setTemplateLoader(stringTemplateLoader); + + // use our custom object wrapper that can deal with QNameMap objects directly + config.setObjectWrapper(new QNameAwareObjectWrapper()); + + // rethrow any exception so we can deal with them + config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + return config; + } + /** * @see org.alfresco.service.cmr.repository.TemplateProcessor#process(java.lang.String, java.lang.Object, java.io.Writer) */ @@ -144,4 +168,50 @@ public class FreeMarkerProcessor implements TemplateProcessor throw new TemplateException(MSG_ERROR_TEMPLATE_IO, new Object[] {template}, ioerr); } } + + private static final String PATH = "string://fixed"; + + public void processString(String template, Object model, Writer out) + { + if (template == null || template.length() == 0) + { + throw new IllegalArgumentException("Template is mandatory."); + } + if (model == null) + { + throw new IllegalArgumentException("Model is mandatory."); + } + if (out == null) + { + throw new IllegalArgumentException("Output Writer is mandatory."); + } + + try + { + if (logger.isDebugEnabled()) + logger.debug("Executing template: " + template + " on model: " + model); + + Template t = getStringConfig(PATH, template).getTemplate(PATH); + if (t != null) + { + try + { + // perform the template processing against supplied data model + t.process(model, out); + } + catch (Throwable err) + { + throw new TemplateException(MSG_ERROR_TEMPLATE_FAIL, new Object[] {err.getMessage()}, err); + } + } + else + { + throw new TemplateException(MSG_ERROR_NO_TEMPLATE, new Object[] {template}); + } + } + catch (IOException ioerr) + { + throw new TemplateException(MSG_ERROR_TEMPLATE_IO, new Object[] {template}, ioerr); + } + } } diff --git a/source/java/org/alfresco/repo/template/TemplateServiceImpl.java b/source/java/org/alfresco/repo/template/TemplateServiceImpl.java index defb56482d..1dcd768708 100644 --- a/source/java/org/alfresco/repo/template/TemplateServiceImpl.java +++ b/source/java/org/alfresco/repo/template/TemplateServiceImpl.java @@ -48,7 +48,7 @@ public class TemplateServiceImpl implements TemplateService, ApplicationContextA private Map templateEngines; /** Threadlocal instance for template processor cache */ - private static ThreadLocal> processors = new ThreadLocal(); + private static ThreadLocal> processors = new ThreadLocal>(); /** * Set the application context @@ -127,6 +127,36 @@ public class TemplateServiceImpl implements TemplateService, ApplicationContextA return out.toString(); } + public void processTemplateString(String engine, String template, Object model, Writer out) + throws TemplateException + { + try + { + // execute template processor + TemplateProcessor processor = getTemplateProcessorImpl(engine); + processor.processString(template, model, out); + } + catch (TemplateException terr) + { + throw terr; + } + catch (Throwable err) + { + throw new TemplateException(err.getMessage(), err); + } + } + + + public String processTemplateString(String engine, String template, Object model) + throws TemplateException + { + Writer out = new StringWriter(1024); + processTemplateString(engine, template, model, out); + return out.toString(); + } + + + /** * Return the TemplateProcessor implementation for the named template engine * diff --git a/source/java/org/alfresco/service/cmr/repository/TemplateNode.java b/source/java/org/alfresco/service/cmr/repository/TemplateNode.java index f764f6b761..b8a0d3c58f 100644 --- a/source/java/org/alfresco/service/cmr/repository/TemplateNode.java +++ b/source/java/org/alfresco/service/cmr/repository/TemplateNode.java @@ -91,6 +91,8 @@ public final class TemplateNode implements Serializable private Long size = null; private TemplateImageResolver imageResolver = null; private TemplateNode parent = null; + + private ChildAssociationRef primaryParentAssoc; /** @@ -491,6 +493,19 @@ public final class TemplateNode implements Serializable return parent; } + /** + * + * @return the primary parent association so we can get at the association QName and the association type QName. + */ + public ChildAssociationRef getPrimaryParentAssoc() + { + if (primaryParentAssoc == null) + { + primaryParentAssoc = this.services.getNodeService().getPrimaryParent(nodeRef); + } + return primaryParentAssoc; + } + /** * @return the content String for this node from the default content property * (@see ContentModel.PROP_CONTENT) diff --git a/source/java/org/alfresco/service/cmr/repository/TemplateProcessor.java b/source/java/org/alfresco/service/cmr/repository/TemplateProcessor.java index 2365ea6ce3..adce9935df 100644 --- a/source/java/org/alfresco/service/cmr/repository/TemplateProcessor.java +++ b/source/java/org/alfresco/service/cmr/repository/TemplateProcessor.java @@ -35,4 +35,13 @@ public interface TemplateProcessor * @param out Writer object to send output too */ public void process(String template, Object model, Writer out); + + /** + * Process a string template against the supplied data model and write to the out. + * + * @param template Template string + * @param model Object model to process template against + * @param out Writer object to send output too + */ + public void processString(String template, Object model, Writer out); } diff --git a/source/java/org/alfresco/service/cmr/repository/TemplateService.java b/source/java/org/alfresco/service/cmr/repository/TemplateService.java index 35ae9ad106..e1ee46e7db 100644 --- a/source/java/org/alfresco/service/cmr/repository/TemplateService.java +++ b/source/java/org/alfresco/service/cmr/repository/TemplateService.java @@ -55,6 +55,34 @@ public interface TemplateService public void processTemplate(String engine, String template, Object model, Writer out) throws TemplateException; + /** + * Process a given template, provided as a string, against the supplied data model and return the result as a String + * + * @param engine Name of the template engine to use + * @param template Template string + * @param model Object model to process template against + * + * @return output of the template process as a String + * + * @throws TemplateException + */ + public String processTemplateString(String engine, String template, Object model) + throws TemplateException; + + /** + * Process a given template, provided as a string, against the supplied data model and report the + * result back in the provided writer. + * + * @param engine Name of the template engine to use + * @param template Template string + * @param model Object model to process template against + * @param out Writer object to send output too + * + * @throws TemplateException + */ + public void processTemplateString(String engine, String template, Object model, Writer out) + throws TemplateException; + /** * Return a TemplateProcessor instance for the specified engine name. * Note that the processor instance is NOT thread safe!