Dynamic scheduler in Liferay - cron

I need to execute a task periodically in Liferay, I find that you can create a class that implements MessageListener and in the receive method you can implement the action. Until here its fine, but after this to program the time you need to do it in liferay-portlet.xml, if the xml file is used I cannot edit the duration dynamically.
Is there any way I can create a task that can be programmed to dynamically get the values for scheduling duration from the DB?
I believe that with SchedulerEngineUtil I must to be able tho do this, but I don't know if it's the correct way or how do it?
Does anyone know how I can find any documentation and some example?
Thanks in advance

Exactly, to execute a task periodically you must implement MessageListener and do the following in your liferay-portlet.xml -
<scheduler-entry>
<scheduler-event-listener-class>com.#className</scheduler-event-listener-class>
<trigger>
<simple>
<simple-trigger-value>1</simple-trigger-value>
<time-unit>minute</time-unit>
</simple>
</trigger>
</scheduler-entry>
This will trigger the event listener class every 1 minute.So try fetching data from DB using Util method in here.
Let me know if that helped.

To add a dynamic scheduler you have to implement a MessageListener and register it using the SchedulerEngineHelperUtil:
String pId = "portlet id here"; // TODO put portlet ID here
Message msg = new Message();
msg.put(SchedulerEngine.PORTLET_ID, pId);
msg.put(SchedulerEngine.MESSAGE_LISTENER_CLASS_NAME, MyListener.class.getName());
Trigger listener = new IntervalTrigger(MyListener.class.getName(), MyListener.class.getName(), 50);
SchedulerEngineHelperUtil.schedule(
listener, StorageType.PERSISTED, "some description",
DestinationNames.SCHEDULER_DISPATCH, msg, 0);
You may also want to read my answer also covering a solution for creating a static scheduled-task.

The time is set in cronText, where 12 is minutes and 10 is hours. Currently this cronText makes it schedule every day at 10:12 am.
String portletId = "yourportlet_WAR_yourportlet";
String cronText = "0 12 10 1/1 * ? *";
Message message = new Message();
message.put(SchedulerEngine.MESSAGE_LISTENER_CLASS_NAME, YourListenerClass.class.getName());
message.put(SchedulerEngine.PORTLET_ID, portletId);
message.put("portletId", portletId);
SchedulerEngineHelperUtil.schedule(
new CronTrigger("jobName", "jobGroup", cronText),
StorageType.PERSISTED,
"Message Scheduler Description",
DestinationNames.SCHEDULER_DISPATCH,
message,
0);

Related

Spring Aggregation Group

I did create an aggregate service as below
#EnableBinding(Processor.class)
class Configuration {
#Autowired
Processor processor;
#ServiceActivator(inputChannel = Processor.INPUT)
#Bean
public MessageHandler aggregator() {
AggregatingMessageHandler aggregatingMessageHandler =
new AggregatingMessageHandler(new DefaultAggregatingMessageGroupProcessor(),
new SimpleMessageStore(10));
//AggregatorFactoryBean aggregatorFactoryBean = new AggregatorFactoryBean();
//aggregatorFactoryBean.setMessageStore();
aggregatingMessageHandler.setOutputChannel(processor.output());
//aggregatorFactoryBean.setDiscardChannel(processor.output());
aggregatingMessageHandler.setSendPartialResultOnExpiry(true);
aggregatingMessageHandler.setSendTimeout(1000L);
aggregatingMessageHandler.setCorrelationStrategy(new ExpressionEvaluatingCorrelationStrategy("requestType"));
aggregatingMessageHandler.setReleaseStrategy(new MessageCountReleaseStrategy(3)); //ExpressionEvaluatingReleaseStrategy("size() == 5")
aggregatingMessageHandler.setExpireGroupsUponCompletion(true);
aggregatingMessageHandler.setGroupTimeoutExpression(new ValueExpression<>(3000L)); //size() ge 2 ? 5000 : -1
aggregatingMessageHandler.setExpireGroupsUponTimeout(true);
return aggregatingMessageHandler;
}
}
Now i want to release the group as soon as a new group is created, so i only have one group at a time.
To be more specific i do receive two types of requests 'PUT' and 'DEL' . i want to keep aggregating per the above rules but as soon as i receive a request type other than what i am aggregating i want to release the current group and start aggregating the new Type.
The reason i want to do this is because these requests are sent to another party that don't support having PUT and DEL requests at the same time and i can't delay any DEL request as sequence between PUT and DEL is important.
I understand that i need to create a custom release Pojo but will i be able to check the current groups ?
For Example
If i receive 6 messages like below
PUT PUT PUT DEL DEL PUT
they should be aggregated as below
3PUT
2 DEL
1 PUT
OK. Thank you for sharing more info.
Yes, you custom ReleaseStrategy can check that message type and return true to lead to the group completion function.
As long as you have only static correlationKey, so only one group is there in the store. When your message is stepping to the ReleaseStrategy, there won't be much magic just to check the current group for completion signal. Since there are no any other groups in the store, there is no need any complex release logic.
You should add expireGroupsUponCompletion = true to let the group to be removed after completion and the next message will form a new group for the same correlationKey.
UPDATE
Thank you for further info!
So, yes, your original PoC is good. And even static correlationKey is fine, since you are just going to collect incoming messages to batches.
Your custom ReleaseStrategy should analyze MessageGroup for a message with different key and return true in that case.
The custom MessageGroupProcessor should filter a message with different key from the output List and send that message to the aggregator back to let to form a new group for a sequence for its key.
i ended up implementing the below ReleaseStrategy as i found it simpler than removing message and queuing it again.
class MessageCountAndOnlyOneGroupReleaseStrategy implements org.springframework.integration.aggregator.ReleaseStrategy {
private final int threshold;
private final MessageGroupProcessor messageGroupProcessor;
public MessageCountAndOnlyOneGroupReleaseStrategy(int threshold,MessageGroupProcessor messageGroupProcessor) {
super();
this.threshold = threshold;
this.messageGroupProcessor = messageGroupProcessor;
}
private MessageGroup currentGroup;
#Override
public boolean canRelease(MessageGroup group) {
if(currentGroup == null)
currentGroup = group;
if(!group.getGroupId().equals(currentGroup.getGroupId())) {
messageGroupProcessor.processMessageGroup(currentGroup);
currentGroup = group;
return false;
}
return group.size() >= this.threshold;
}
}
Note that i did used new HeaderAttributeCorrelationStrategy("request_type") instead of just FOO for CollorationStrategy

workflow replicator activity: how to determine if task was approved or rejected

I have basic workflow with replicator activity inside it. Replicator contains my custom sequence activity with standard create task --> ontaskchanged --> complete task sequence.
Now: tasks are created and can be completed without problem. The thing is I cannot find a way to get a value of completed task. Was it approved or rejected ?
Please provide couple lines of code of replicator's ChildCompleted event to get anything out of Sequence activity instance (or any other way).
thanks
UPDATE: It seems in order to exchange values between instances of workflow you need to use DependencyProperty. So solution here is:
1) add DependencyProperty to parent workflow and add property which you will use to store value like this:
public static DependencyProperty childStatusProperty =
System.Workflow.ComponentModel.DependencyProperty.Register("childStatus",
typeof(string), typeof(parentWorkflowTypeName));
public string childStatus
{
get
{
return (string)base.GetValue(childStatusProperty);
}
set
{
base.SetValue(childStatusProperty, value);
}
}
2) in custom sequence activity access parent's instance and use defined DependencyProperty to set property to value like this:
private void completeTask1_MethodInvoking(object sender, EventArgs e)
{
var replicator = this.Parent;
var workflowParent = (parentWorkflowTypeName)replicator.Parent;
workflowParent.childStatus = "my custom status value";
}
3) read this value using normal property:
//from parent workflow
string status = childStatus;
The issue is that you have to record somewhere the list of all tasks created. I guess you are creating the tasks in parallel (not sequential).
I had the same issue, it took me a while to fix this.
Please check this link as a good starting point: http://rmanimaran.wordpress.com/2010/12/02/sharepoint-workflow-replicator-parallel-approval-problem-solution/

Docuemt postopen event not operating on profile document

I need to save serial number of the document in a profile document and here is a code of action Execute Script:
if (document1.isNewNote()){
var pdoc:NotesDocument=database.getProfileDocument("LastNumber","")
var lnm=pdoc.getItemValue("lastNumber")[0];
var inputText6:com.ibm.xsp.component.xp.XspInputText = getComponent("inputText6");
inputText6.setValue(lnm);
pdoc.replaceItemValue("lastNumber",lnm);
pdoc.save();
}
This code is not opening profile document at all. Any thing wrong in the code?
"LastNumber" is the name of the form used to create Profile Document ?
this profile document already exist ?
there are no reader fields in this profile document ?
you have an error on this line : var pdoc:NotesDocument=database.getProfileDocument("LastNumber","") ?
or you have debug it and see that pdoc is null ?
instead of pdoc.getItemValue("lastNumber")[0] you can use pdoc.getItemValueInteger("lastNumber") to get a typed result
I supposed that this field contains a number and you want to increment it
instead of using inputText field you can set value directly with document1.setValue("NumberField", lnm);
I second the caution Per is suggesting. Profile documents can be a beast. You should abstract access to the "next number" into a SSJS function call. Btw. in your code snippet you don't actually increment the last number. Also: if your input text control is bound, go after the data source, not the UI.
A crude way (I would use a managed application bean for better isolation) for a better function could be this:
if(document1.isNewNote() {
document1.setValue("DocumentNumber",applicationTools.getNextNumber());
}
Then in a SSJS library you would have:
var applicationTools = {
"getNextNumber" : function() {
synchronized(applicationScope){
var pdoc:NotesDocument=database.getProfileDocument("LastNumber","");
if (!applicationScope.lastNumber) {
applicationScope.lastNumber = pdoc.getItemValueInteger("lastNumber");
}
applicationScope.lastNumber++;
pdoc.replaceItemValue("lastNumber",applicationScope.lastNumber);
pdoc.save(); //Make sure pdoc is writeable by ALL!!!!
pdoc.recycle();
return applicationScope.lastNumber;
}
},
"someOtherUtility" : function(nameToLookup, departments) {
// more stuff here
}
}
Which, in some way has been asked before, but not for a profile field. Someone still could simply go after the applicationScope.lastNumber variable, which is one of the reasons why I rather use a bean. The other: you could do the saving asynchronously, so it would be faster.
Note: in any case the number generation only works when you have a non-replicating database. But abstracting the function opens the possibility to replace fetching the number from the profile with a call to a central number generator ... or any other mechanism ... without changing your form again.

Post an activity entry to all and to me

I try to post an activity entry with the ActivityService class. I want that all my followers and myself can see it.
this.
ActivityStreamService service = new ActivityStreamService();
service.postEntry("#me", "#all", "", jsonObject, header);
I saw my entry but not my follower
With this.
ActivityStreamService service = new ActivityStreamService();
service.postEntry("#public", "#all", "", jsonObject, header);
My follower saw the entry, but I do not see this.
Have anybody an idea which one is the correct combination?
There are a couple of ways...
1 - You can use the Distribution method
http://www-10.lotus.com/ldd/appdevwiki.nsf/xpDocViewer.xsp?lookupName=IBM+Connections+4.5+API+Documentation#action=openDocument&res_title=Distributing_events_ic45&content=pdcontent
openSocial : {
"deliverTo":[
{"objectType":"person",
"id":"tag:example.org,2011:jane"}
]
}
*You will need a special j2ee role in order to distribute this content (trustedApplication Role in WidgetContainer Application)
2 - You can use the ublog
http://www-10.lotus.com/ldd/appdevwiki.nsf/xpDocViewer.xsp?lookupName=IBM+Connections+4.5+API+Documentation#action=openDocument&res_title=Posting_microblog_entries_ic45&content=pdcontent
POST to my board: /ublog/#me/#all
{ "content": "A new test post" }
3 -
Otherwise, you need to do multiple posts
This means than the event would have to be sent separately to each user that is to receive the event. In order to ensure that this can be done more efficiently, an extension to the the Open Social spec allows for a few means of distribution in the data model
I hope this helps.
As well as the openSocial JSON object, you could use to JSON object
For example this JSON snippet:
"to":[
{"objectType":"person", "id":"#me"}.
{"objectType":"person", "id":"#public"}
{"objectType":"community", "id":"xxx-xx-xxx0x0x0x0x0x"}
]
...can be produced by updating your jsonObject like so:
// #me
JsonJavaObject meJson = new JsonJavaObject();
meJson.put("objectType","person");
meJson.put("id","#me");
// #public
JsonJavaObject publicJson = new JsonJavaObject();
publicJson.put("objectType","person");
publicJson.put("id","#public");
// Community
JsonJavaObject communityJson = new JsonJavaObject();
communityJson.put("objectType","community");
communityJson.put("id","xxx-xx-xxx0x0x0x0x0x");
// Shove them all in a list
List<JsonJavaObject> toJson = new ArrayList<JsonJavaObject>();
toJson.add(meJson);
toJson.add(publicJson);
toJson.add(communityJson);
// add to: [...] to the root JsonJavaObject
jsonObject.put("to", toJson ) ;
Also: Here's a video on adding a user to the trustedExternalApplication role.

What is the best way to implement a timer that will be stopped and restarted in a Java Swing Application?

Ok, I'm working on my final dilemna for my project. The project is an IPv4 endpoint updater for TunnelBroker's IPv6 tunnel. I have everything working, except for the timer. It works, however if the user disables the "automatic update" and reenables it, the application crashes. I need the timer to be on an thread outside of the EDT (in such a way that it can be destroyed and recreated when the user unchecks/checks the automatic update feature or changes the amount of time between updates).
What I'm pasting here is the code for the checkbox that handles automatic updates, and the timer class. Hopefully this will be enough to get an answer on how to do this (I'm thinking either it needs to be a worker, or use multi-threading--even though only one timer will be active).
private void jCheckBox1ItemStateChanged(java.awt.event.ItemEvent evt) {
// TODO add your handling code here:
// if selected, then run timer for auto update
// set time textbox to setEditable(true) and get the time from it.
// else cancel timer. Try doing this on different
// class to prevent errors from happening on reselect.
int updateAutoTime = 0;
if (jCheckBox1.isSelected())
{
updateAutoTime = Integer.parseInt(jTextField4.getText())*60*1000;
if (updateAutoTime < 3600000)
{
updateAutoTime = 3600000;
jTextField4.setText(new Integer(updateAutoTime/60/1000).toString());
}
updateTimer.scheduleAtFixedRate(new TimerTask() {
public void run()
{
// Task here ...
if (jRadioButton1.isSelected())
{
newIPAddress = GetIP.getIPAddress();
}
else
{
newIPAddress = jTextField3.getText();
}
strUsername = jTextField1.getText();
jPasswordField1.selectAll();
strPassword = jPasswordField1.getSelectedText().toString();
strTunnelID = jTextField2.getText();
strIPAddress = newIPAddress;
if (!newIPAddress.equals(oldIPAddress))
{
//fire the tunnelbroker updater class
updateIP.setIPAddress(strUsername, strPassword, strTunnelID, strIPAddress);
oldIPAddress = newIPAddress;
jLabel8.setText(newIPAddress);
serverStatus = updateIP.getStatus().toString();
jLabel6.setText(serverStatus);
}
else
{
serverStatus = "No IP Update was needed.";
jLabel6.setText(serverStatus);
}
}
}, 0, updateAutoTime);
}
else
{
updateTimer.cancel();
System.out.println("Timer cancelled");
System.out.println("Purged {updateTimer.purge()} tasks.");
}
}
As I mentioned, this works once. But if the user deselects the checkbox, it won't work again. And the user can't change the value in jTextField4 after they select the checkbox.
So, what I'm looking for is this:
How to make this so that user can select and deselect the checkbox as they want (even if it's multiple times in a row).
How to make this so the user can change the value in jTextField4, and have it automatically cancel the current timer, and start a new one with the new value (I haven't done anything with the jTextField4 at all, so I'll have to create an event to cover it later).
Thanks, and have a great day:)
Patrick.
Perhaps this task would be better suited to a javax.swing.Timer. See Timer.restart() for details.
Note that Timer is relatively inaccurate over long time periods. One way to account for that is to have it repeat frequently but perform it's assigned task only one a certain time has been reached or passed.
Would I be able to wrap everything in the "task" portion of the call to Swing Timer, or do I have to create another class that handles the task?
You might want to wrap the grunt work in a SwingWorker to ensure the EDT is not blocked.
..I'm assuming that I would have to create the timer as a class-level declaration .. correct?
Yes, that is what I was thinking.

Resources