The short version: How do I get JBPM5 Rule Nodes to use a DRL file which reads and updates process variables?
The long version:
I have a process definition, being run under JBPM5. The start of this process looks something like this:
[Start] ---> [Rule Node] ---> [Gateway (Diverge)] ... etc
The gateway uses constraints on a variable named 'isValid'.
My Rule Node is pointing to the RuleFlowGroup 'validate', which contains only one rule:
rule "Example validation rule"
ruleflow-group "validate"
when
processInstance : WorkflowProcessInstance()
then
processInstance.setVariable("isValid", new Boolean(false));
end
So, by my logic, if this is getting correctly processed then the gateway should always follow the "false" path.
In my Java code, I have something like the following:
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource("myProcess.bpmn"), ResourceType.BPMN2);
kbuilder.add(ResourceFactory.newClassPathResource("myRules.drl"), ResourceType.DRL);
KnowledgeBase kbase = kbuilder.newKnowledgeBase();
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
new Thread(new Runnable()
{
public void run()
{
ksession.fireUntilHalt();
}
}).start();
// start a new process instance
Map<String, Object> params = new HashMap<String, Object>();
params.put("isValid", true);
ksession.startProcess("test.processesdefinition.myProcess", params);
I can confirm the following:
The drl file is getting loaded into working memory, because when I put syntax errors in the file then I get errors.
If I include a value for "isValid" in the Java params map, the process only ever follows the path specified by Java, apparently ignoring the drools rule.
If I take the "isValid" parameter out of the params map, I get a runtime error.
From this I assume that the final "setVariable" line in the rule is either not executing, or is updating the wrong thing.
I think my issue is related to this statement in the official documentation:
Rule constraints do not have direct access to variables defined inside the process. It is
however possible to refer to the current process instance inside a rule constraint, by adding
the process instance to the Working Memory and matching for the process instance in your
rule constraint. We have added special logic to make sure that a variable processInstance of
type WorkflowProcessInstance will only match to the current process instance and not to other
process instances in the Working Memory. Note that you are however responsible yourself to
insert the process instance into the session and, possibly, to update it, for example, using Java
code or an on-entry or on-exit or explicit action in your process.
However I cannot figure out how to do what is described here. How do I add the process instance into working memory in a way that would make it accessible to this first Rule Node? Rule Nodes do not seem to support on-entry behaviors, and I can't add it to the Java code because the process could very easily complete execution of the rules node before the working memory has been updated to include the process.
As you mentioned, there are several options to inserting the process instance into the working memory:
- inserting it after calling startProcess()
- using an action script to insert it (using "insert(kcontext.getProcessInstance()")
If calling startProcess() might already have gone over the rule task (which is probably the case in your example), and you don't have another node in front of your rule task where you could just use an on-entry/exit script to do this (so that's is hidden), I would recommend using an explicit script task before your rule task to do this.
Kris
Related
I've stumbled upon a problem when using some more complex argo workflows with initialization and clean-up logic. We are running some initialization in one of the initial steps of the workflow (e.g. creation of some resources) and we'd like to perform a clean-up regardless of the status of the workflow. onExit template seems to be an ideal solution (I think that clean-up is even mentioned in argo documentation as predestined for tasks of the onExit template).
However, I haven't found a way yet to pass some values to it. For example - let's say that in the initialization phase we created some resource with id some-random-unique-id and we'd like to let the onExit container know what resources it needs to clean up.
We tried the outputs of some steps, but it seems that steps are unknown in the onExit template.
Is there a built-in argo mechanism to pass this kind of data? We'd like to avoid some external services (like key-value storage service that would hold the context).
You can mark output parameters as global using the globalName field. A global output parameter, assuming it has been set, can be accessed from anywhere in the Workflow, including in an exit handler.
The example file for writing and consuming global output parameters should contain all the information you need to use global output parameters in an exit handler.
https://github.com/argoproj/argo-workflows/blob/master/examples/global-outputs.yaml
Please share if you found a cleaner solution.
I see this workflow example for exit handler with parameters, which may resolve your issue: Exit Handler with Parameters
One method I found was to add steps to the template I want to call the exit template on, and call the exit template as the last step so I can access steps variables. I changed the original template to an inline template so the structure does not change too much, just involves adding the last step call to the exit template. Unfortunately, this does not utilize the actual onExit method.
Inline steps template example
1.https://sourcegraph.com/github.com/argoproj/argo-workflows/-/blob/examples/exit-handler-with-param.yaml?L21
2.https://raw.githubusercontent.com/argoproj/argo-workflows/master/examples/steps-inline-workflow.yaml
I have an Eclipse 3.x-based application which uses commands and handlers.
I'm in the process of upping the code coverage and want to test as much as I can. The simple cases (POJOs) I've learned how to test. However, there are cases where I can't find a good starting point, especially when creating a fixture.
For example: Eclipse Command Handlers. I have a handler class MyHandler extending org.eclipse.core.commands.AbstractHandler. It has a single method public Object execute(ExecutionEvent event) throws ExecutionException. Usually, event is passed in from a user action in the GUI, e.g., clicking a menu item.
How can I unit test this? Would I need to mock the ExecutionEvent with the help of a mocking framework?
Unless inevitable, I prefer to mock only types I do own. See here for a discussion of Should you only mock types you own?
Since ExecutionEvents can be created without too much hassle I wouldn't mock them. The snippet below creates an event that you can pass to your handlers' execute method.
IEvaluationContext context = new EvaluationContext( null, new Object() );
Map<String, String> parameters = new HashMap<>();
ExecutionEvent event = new ExecutionEvent( null, parameters, null, context );
The first argument of the ExecutionEvent constructor references the command - I have never had any use for it. If your code requires an actual command, you can use the ICommandService to obtain a reference to your command:
ICommandService commandService = ...
Command command = commandService.getCommand( "id.of.my.command" );
The second argument is a map of command parameters. The third argument is the trigger. If case of the Eclipse workbench this is the SWT Event if available. Leave it null if your production code does not evaluate it.
Before calling execute, you would probably want to prepare the variables of the context:
context.addVariable( ISources.ACTIVE_PART_NAME, myPart );
context.addVariable( ISources.ACTIVE_CURRENT_SELECTION_NAME, new StructuredSelection() );
Note that null is not allowed as a variable value. Either omit the call or - if already added, use removeVariable().
If you don't need a command (see above) - and of course your production code doesn't require a workbench - you can even run the tests as plain JUnit tests (as opposed to PDE JUnit test).
I am running into this exception in a really odd place. Immediately after the first transaction on a fresh graph instance.
This is the static factory pseudocode:
if (factory == null){
factory = new OrientGraphFactory("plocal:" + args[0] + File.separatorChar + "dir").setupPool(1, 10);
factory.setAutoStartTx(false);
}
return new GRAPHWRAPPER(factory.getTx(), arg2, arg3, arg4, arg5);
inside the GRAPHWRAPPER class constructor I have the following:
begin TX,
add two vertices,
commit TX <- Throws the exception
It only occurs on the second invocation of the factory. The invocations to the factory always happen inside the same thread (UI)
I am using 2.0.12/OS X 10.9/Java 7. What am I missing?
UPDATE:
I finally found an exception that makes more sense:
java.util.ConcurrentModificationException at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:394)
at java.util.LinkedHashMap$ValueIterator.next(LinkedHashMap.java:409) at com.orientechnologies.orient.core.tx.OTransactionRealAbstract.getNewRecordEntriesByClass(OTransactionRealAbstract.java:190)
at com.orientechnologies.orient.core.iterator.ORecordIteratorClass.config(ORecordIteratorClass.java:133)
at com.orientechnologies.orient.core.iterator.ORecordIteratorClass.<init>(ORecordIteratorClass.java:88)
at com.orientechnologies.orient.core.iterator.ORecordIteratorClass.<init>(ORecordIteratorClass.java:63)
at com.orientechnologies.orient.core.iterator.ORecordIteratorClass.<init>(ORecordIteratorClass.java:53)
at com.tinkerpop.blueprints.impls.orient.OrientElementScanIterable.iterator(OrientElementScanIterable.java:47)
at com.tinkerpop.blueprints.util.DefaultGraphQuery$DefaultGraphQueryIterable$1.<init>(DefaultGraphQuery.java:88)
at com.tinkerpop.blueprints.util.DefaultGraphQuery$DefaultGraphQueryIterable.iterator(DefaultGraphQuery.java:86)
at com.tinkerpop.blueprints.impls.orient.OrientGraphQuery$OrientGraphQueryIterable.iterator(OrientGraphQuery.java:79)
This is inside the GRAPHWRAPPER constructor.
My code just uses the UI thread to instantiate the storage instances. Then all access is done by worker threads.
I have been printing out to console the activity and everyhting seems to match thread->graph pool instance. Each thread modifies its own set of elements. Like a subgraph per thread.
The only point in common I see is the two vertices added during share a key across threads. The values differ. I will change the logic to make the key unique per thread so orientdb does not include elements from another thread in the queries.
I will report status back later. It has been so random that creating a repeatable case is not possible.
UPDATE 2:
Making the key unique as described above solved several exceptions. In the process, I found two places where the ui thread uses the graph. These are probably the other culprits that trip the wire. I will change the logic and if I don't update again it is because it was userland error
I need to add an Action to a Schedule object that is being created through the API. There are documented interfaces to set almost all the options except the Action. How are Actions attached to these Objects?
When I attempt to programmatically add a new event, read from a separate configuration file, to a Schedule object I get errors stating that the Schedule has already been initialized and that I must construct a new object and add its configuration manually. I can do most of that using the available Schedule API. I can set up everything about the Schedule except the Action code.
The Schedule is used in a Process Model. Looking at the model in the Java editor, I see the code I'm trying to replicate via the API in a function that looks like this:
#Override
#AnyLogicInternalCodegenAPI
public void executeActionOf( EventTimeout _e ) {
if ( _e == _fuelDeliverySchedule_Action_xjal ) {
Schedule<Integer> self = this.fuelDeliverySchedule;
Integer value = fuelDeliverySchedule.getValue();
logger.info("{} received {} pounds of fuel", this.getName(), this.fuelDeliverySchedule.getValue());
this.fuelAvailablePounds += fuelDeliverySchedule.getValue();
;
_fuelDeliverySchedule_Action_xjal.restartTo( fuelDeliverySchedule.getTimeOfNextValue() );
return;
}
super.executeActionOf( _e );
}
Maybe I can use something like this to create my own action function, but I'm not sure how to make the Scheduled event use it.
Thanks,
Thom
[Edited (expanded/rewrote) 03.11.2014 after more user detail on the context.]
You clarified the context with
When I attempt to programatically add "a thing that happens", read
from a separate configuration file, to a Schedule object I get errors
stating that the Schedule has already been initialized and that I must
construct a new object and add its configuration manually. I can do
most of that using the available Schedule API. I can set up everything
about the Schedule except the Action code.
(You might want to edit that into the question... In general, it's always good to explain the context for why you're trying to do the thing.)
I think I understand now. I presume that your config file contains scheduling details and, when you say you were trying to "add a thing that happens" (which errored), you meant that you were trying to change the scheduling 'pattern' in the Schedule. So your problem is that, since you couldn't adjust a pre-existing schedule, you had to instantiate (create) your own programmatically, but the Schedule API doesn't allow you to set the action code (as seen on the GUI schedule element).
This is a fairly involved solution so bear with me. I give a brief 'tl;dr'
summary before diving into the detail.
Summary
You can't programmatically code an AnyLogic action (for any element) because that would amount to
dynamically creating a Java class. Solving your problem requires recognising
that the schedule GUI element creates both a Schedule instance and a
timeout event (EventTimeout) instance to trigger the action. You can therefore create these two elements explicitly yourself (the former dynamically). The trick is to reset the timeout event when you replace the Schedule instance (to trigger at the next 'flip' point of the new Schedule).
[Actually, from your wording, I suspect that the action is always the same but, for generality, I show how you could handle it if your config file details might want to change the nature of the action as well as those of the scheduling pattern.]
Detail
The issue is that the GUI element (confusingly) isn't just a Schedule instance
in terms of the code it generates. There is one (with the same name as that of
the GUI element), which just contains the schedule 'pattern' and, as in the API,
has methods to determine when the next on/off period (for an on/off schedule) occurs. (So
it is kind of fancy calendar functionality.) But AnyLogic also generates a
timeout event to actually perform the action; if you look further in the code
generated, you'll see stuff similar to the below (assuming your GUI schedule is called
fuelSchedule, with Java comments added by
me):
// Definition of the timeout event
#AnyLogicInternalCodegenAPI
public EventTimeout _fuelSchedule_Action_xjal = new EventTimeout(this);
// First occurrence time of the event as the next schedule on/off change
// time
#Override
#AnyLogicInternalCodegenAPI
public double getFirstOccurrenceTime( EventTimeout _e ) {
if ( _e == _fuelSchedule_Action_xjal ) return fuelSchedule.getTimeOfValue() == time() ? time() : fuelSchedule.getTimeOfNextValue();
return super.getFirstOccurrenceTime( _e );
}
// After your user action code, the event is rescheduled for the next
// schedule on/off change time
_fuelSchedule_Action_xjal.restartTo( fuelSchedule.getTimeOfNextValue() );
i.e., this creates an event which triggers each time the schedule 'flips', and performs the action specified in the GUI schedule element.
So there is no action to change on the Schedule instance; it's actually related to the EventTimeout instance. However, you can't programmatically change it there (or create a new one dynamically) for the same reason that you can't change the action of any AnyLogic element:
this would effectively be programmatically
creating a Java class definition, which isn't possible without very specialised
Java code. (You can create Java source code in a string and
dynamically run a Java compiler on it to generate a class. However, this is very
'advanced' Java, has lots of potential pitfalls, and I would definitely not
recommend going that route. You would also have to be creating source for a user subclass
of EventTimeout, since you don't know the correct source code for AnyLogic's proprietary EventTimeout class, and this might change per release in any case.)
But you shouldn't need to: there should be a strict set of possible actions that your config file can contain. (They can't be arbitrary Java code snippets, since they have to 'fit in' with the simulation.) So you can do what you want by programmatically creating the Schedule but with a GUI-created timeout event that you adjust accordingly(assuming an off/on schedule here and that there is
only one schedule active at once; obviously tweak this skeleton to your needs
and I haven't completely tested this in AnyLogic):
1. Have an AnyLogic variable activeAction which specifies the current active
action. (I take this as an int here for simplicity, but it's better to use a
Java enum which is the same as an AnyLogic 7 Option List, and can just be
created in raw Java in AnyLogic 6.)
2. Create a variable in the GUI, say called fuelSchedule, of type Schedule but with initial value null. Create a separate timeout event, say called fuelScheduleTrigger, in User Control mode, with action as:
// Perform the appropriate action (dependent on activeAction)
doAppropriateScheduleAction();
// Set the event to retrigger at the next schedule on/off switch time
fuelScheduleTrigger.restartTo(fuelSchedule.getTimeOfNextValue());
(Being in User Control mode, this event isn't yet triggered to initially fire, which is what we want.)
3. Code a set of functions for each of the different action alternatives; let's say
there are only 2 (fuelAction1 and fuelAction2) here as an example. Code
doAppropriateScheduleAction as:
if (activeAction == 1) {
fuelAction1();
}
else if (activeAction == 2) {
fuelAction2();
}
4. In your code which reads the config file and gets updated schedule info.
(presumably run from a cyclic timeout event or similar), have this replace
fuelSchedule with a new instance with the revised schedule pattern (as you've
been doing), set activeAction appropriately, and then reset the timeout event to
the new fuelSchedule.getTimeOfValue() time:
[set up fuelSchedule and activeAction]
// Reset schedule action to match revised schedule
fuelScheduleTrigger.restartTo(fuelSchedule.getTimeOfNextValue());
I think this works OK in the edge case when the new Schedule had its next 'flip' at the time
you set it up. (If you restart an event to the current time, I think it schedules an event OK at the current time which will occur next if there are no other events also scheduled for the current time; actually, it will definitely occur next if you are using a LIFO simultaneous-time-scheduling regime---see my blog post.)
Alternative & AnyLogic Enhancement
An alternative is to create a 'full' schedule in the GUI with action as earlier. Your config file reading code can replace the underlying Schedule instance and then reset the internal AnyLogic-generated timeout event. However, this is less preferable because you are relying on an internally-named AnyLogic event (which might also change in future AnyLogic releases, breaking your code).
AnyLogic could help this situation by adding a method to the Schedule API that gets the related timeout event; e.g., getActionTriggeringEventTimeout(). Then you would be able to 'properly' restart it and the Schedule API would make much clearer that the Schedule was always associated with an EventTimeout that did the triggering for the action.
Of course, AnyLogic could also go further by changing Schedule to allow scheduling details to be changed dynamically (and internally handling the required updates to the timeout event if it continued to be designed like that), but that's a lot more work and there may be deeper technical reasons why they wanted the schedule pattern to be fixed once the Schedule is initialised.
Any AnyLogic support staff reading?
I am using Jboss-seam 2.2.2.Final and I have some quartz jobs. During the application execution, I have to verify if a specific job is running.
I already have access to the jobs, but each one has a name that is created by quartz. Here is the code for the seam injection:
#In("org.jboss.seam.async.dispatcher")
private QuartzDispatcher quartzDispatcher;
To get the running jobs, I have this code:
Scheduler scheduler = quartzDispatcher.getScheduler();
List<JobExecutionContext> currentJobs;
currentJobs = scheduler.getCurrentlyExecutingJobs();
for (JobExecutionContext jobCtx: currentJobs){
System.out.println(jobCtx.getJobDetail().getName());
}
Anyone know's how to put a name on a quartz job, using jboss-seam? I have doing some research and found this ticket on jira: https://issues.jboss.org/browse/JBSEAM-4399
Seam generates an UUID-based unique name for each job, there's no direct way of overriding this. Your alternatives are:
Use the patch found, this requires building a custom Seam library with the customized code added.
Override the OOTB quartz dispatcher with your own custom dispatcher, override the scheduleWithQuartzService method and use your own scheme for naming the job (look at the source code for org.jboss.seam.async.QuartzDispatcher you'll see that it calls a nextUniqueName() method that generates a new unique name for the job, you need to get the job name from elsewhere, eventually in the same way it is done in the patch you found, via an annotation).
Instead of trying to see if the job is running based on its name, you can create a quartz event listener implementing org.quartz.JobListener and register when a job begins and ends execution in a global hashtable or something like that. Then you lookup the job in the hashtable to get its status rather than searching it by name. This however works only if your scheduler is not clustered (quartz will notify only the listener in the local node, other nodes will not know the job was triggered).