Greetings
I have being looking at the documentation and I think I doing fairly good, for now I can generate a timer code and run it in debugg mode. But I want to do something and Im really confuse how to do it.
I want to set a timer for every day at 9:00 in the morning, at that hour, a query will be executed. In my data table there is a date variable. The query will look for every date that match the current date (day, month, and year, no hour). For every element found I want to send a push notification to the user owner of that data.
Im very confuse about this cause, how do I start (besides of what I have alrready done).
Should I download different files and start adding them in to the timer code I alrready downloaded
Can I reference other clases from one file to another, like in normal JAVA? Or should I have only 1 large file?
I see the business logic heavily depends on annotations, can I use annotations anywhere, like inside methods also?
Can I do this, is it supported?
Any help will be highly appreciated this is the last I need and my app is ready :) Thanks.
Hi Erick,
Please see my answers below:
Should I download different files and start adding them in to the timer code I already downloaded
Yes, you need to have one project which includes all your server-side code. As you add event handlers and timers, you will need to add the generated classes to your project.
Can I reference other clases from one file to another, like in normal JAVA? Or should I have only 1 large file?
You can definitely reference classes from one to another. Keep it clean and object-oriented. We built the system so you can keep your code nicely organized.
I see the business logic heavily depends on annotations, can I use annotations anywhere, like inside methods also?
We use annotations only to declare timers and (optionally) declare event handlers as asynchronous. Annotations needs to be used only as intended… How would you use inside of a method? I do not understand what that would do.
Can I do this, is it supported?
What do you mean “this”?
Regards,
Mark
Hello Mark
With this, I mean: Can I do such long concatenated business logic? Which include a timer, triggering a query, triggering push notifications on result based…
This is gonna be really cool, when I finish it, Im gonna share my code… if I can…
BTW, thanks for the answer, specially cause you said “Keep it clean and object-oriented” that is what I wish to do. Now I can think the task more clearly.
Yes, your code can do anything you want, but keep in mind the following:
If you need to access an external host, you need to register it in Manage > App Settings > External Hosts
Your code does not have access to the local file system. If you need to store a file, use Backendless File Service API
With the free plan, your code (timer or event handler) must complete its execution within 5 seconds, otherwise you will get an exception. You can increase that limit by switching to the Backendless Plus plan.
Regards,
Mark
Greetings
Im back, I have a problem with the timer. Im trying to excecute this code but doesnt work:
@BackendlessTimer("{'startDate':1445540160000,'frequency':{'schedule':'daily','repeat':{'every':1}},'timername':'CronDiligence'}")
public class CronDiligenceTimer extends com.backendless.servercode.extension.TimerExtender {
@Override
public void execute(String appVersionId) throws Exception {
System.out.println("cron executed");
DateTime currentDate = new DateTime();
String day = String.valueOf(currentDate.getDayOfMonth());
String month = String.valueOf(currentDate.getMonthOfYear());
String year = String.valueOf(currentDate.getYear());
String dateToMatch = day + "/" + month + "/" + year;
String whereClause = "notification = " + dateToMatch;
BackendlessDataQuery dataQuery = new BackendlessDataQuery();
dataQuery.setPageSize(50);
dataQuery.setWhereClause(whereClause);
Backendless.Persistence.of(Diligence.class).find(dataQuery, new AsyncCallback<BackendlessCollection<Diligence>>() {
@Override
public void handleResponse(BackendlessCollection<Diligence> diligenceBackendlessCollection) {
System.out.println("query executed");
List<Diligence> diligenceList = new ArrayList<Diligence>();
Iterator<Diligence> iterator = diligenceBackendlessCollection.getCurrentPage().iterator();
System.out.println("size = " + diligenceBackendlessCollection.getCurrentPage().size());
while (iterator.hasNext()) {
Diligence singleDiligence = iterator.next();
diligenceList.add(singleDiligence);
System.out.println("name = " + singleDiligence.getName());
}
}
@Override
public void handleFault(BackendlessFault backendlessFault) {
System.out.println("error = " + backendlessFault.getMessage() + " " + backendlessFault.getCode());
}
});
}
}
I want to make a query everyday at certain time to match the “dateToMatch” (current date) with the objects that have an equal date variable “notification”.
The problem is nothing is happening, I add some logs so I can see what happens, but nothing happens.
Let me show you how Im setting the start date:
Im sorry not to show you what is going in the terminal but the scret key is there so I hope this screenshot helps:
Clarification. Im using JodaTime to simplify the process of getting the dates values, and the search is based on this https://backendless.com/documentation/data/rest/data_search_with_dates.htm however
Hi Erick,
Thanks for the video, it is helpful to see what’s going on. Couple of things:
The time you set in the interface is going to be used on the server. When you take the timestamp generated by console, try it here to see what that timestamp is in GMT0: http://www.epochconverter.com/ The time you see is when our servers will run your timer.
When you run Debug CodeRunner on your machine, you should be able to see it in the Debug section in Backendless console. In there, you will see the "Run Now" link. Try that to run the timer on demand, without waiting for the timestamp to trigger the execution.
Suggestion: put all your code inside of timer into a try/catch block and print out the exception - it will help with debugging
You can attach to the local process with the debugger and inspect the execution while the timer is running.
Hope this helps.
Mark
Hello Mark
I havent seeing the time yet cause everything else worked, but sadly Im getting an error and dont know what to do.
You have no permission to thread manipulation IllegalArgumentException
According to my logs, and when I do it with debugger, the query is failing, the complete error is this (so you can compare with my code):
query error = You have no permission to thread manipulation IllegalArgumentException
My first thought was, I have no permissions cause there is no user, after that I added a login. So I create a “God” all powerfull role with all permissions and then assing the role to the loged in user. But still getting the same error.
This is my code:
@BackendlessTimer("{'startDate':1445540700000,'frequency':{'schedule':'daily','repeat':{'every':1}},'timername':'CronDiligence'}")
public class CronDiligenceTimer extends com.backendless.servercode.extension.TimerExtender {
@Override
public void execute(String appVersionId) throws Exception {
try {
System.out.println("cron executed");
System.out.println("login job");
try {
System.out.println("login");
Backendless.UserService.login("very@user.doge", "wow");
} catch (BackendlessException e) {
System.out.println("error = " + e);
}
System.out.println("role job");
try {
System.out.println("role");
Backendless.UserService.assignRole("very@user.doge", "SuchaRole");
} catch (BackendlessException e) {
System.out.println("error = " + e);
}
DateTime currentDate = new DateTime();
String day = String.valueOf(currentDate.getDayOfMonth());
String month = String.valueOf(currentDate.getMonthOfYear());
String year = String.valueOf(currentDate.getYear());
String dateToMatch = day + "/" + month + "/" + year;
String whereClause = "notification = " + dateToMatch;
BackendlessDataQuery dataQuery = new BackendlessDataQuery();
dataQuery.setPageSize(50);
dataQuery.setWhereClause(whereClause);
Backendless.Persistence.of(Diligence.class).find(dataQuery, new AsyncCallback<BackendlessCollection<Diligence>>() {
@Override
public void handleResponse(BackendlessCollection<Diligence> diligenceBackendlessCollection) {
System.out.println("query executed");
List<Diligence> diligenceList = new ArrayList<Diligence>();
Iterator<Diligence> iterator = diligenceBackendlessCollection.getCurrentPage().iterator();
System.out.println("size = " + diligenceBackendlessCollection.getCurrentPage().size());
while (iterator.hasNext()) {
Diligence singleDiligence = iterator.next();
diligenceList.add(singleDiligence);
System.out.println("name = " + singleDiligence.getName());
}
}
@Override
public void handleFault(BackendlessFault backendlessFault) {
System.out.println("query error = " + backendlessFault.getMessage() + " " + backendlessFault.getCode());
}
});
} catch (BackendlessException e) {
System.out.println("general error = " + e);
}
}
}
I know the role is assigning cause I can see in the “Table Schema and Permissions” for the Diligence table that the user has both roles “SuchaRole” and the normal authentificated user. And I dont think is a role permissions conflict cause I made an experiment. One of the roles of the user “very@user.doge” has no permissions to find others users objects (Diligences) but the other role does have. So in the console I loged in with the “very@user.doge” and did a GET, got every object from every user as expected. But with an authentificated user, the GET only returns the objects belonging to the users. Both cases behave as expected according to permissions.
I also try to assing the role inside an event handler listening for the login, also didnt worked, I guess is cause is listening for apps login, not for server logins, it was the same error anyway.
What am I doing wrong?
You receive a pretty self-explanatory error:
You have no permission to thread manipulation IllegalArgumentException
The new thread is created when you use AsyncCallback. Just use sync API.
You cannot use Async API in custom code. Async API causes a new thread to be created and it is not allowed in that environment
Hey Guys! I did it! Thanks for all the help Mark This happens right in the nick of time cause the spare time I got for this project run out yesterday…
So im going to share my code, which now has to be tested in production. I have to do some clarifications before. Besides making a super god user with all the permissions for data, I also have to grant it permission for messaging. Also I have to include in my model the user device, this way I can target specific user devices related to the query result.
A couple of things before the code. The paging is now set on 100, cause my currents users are 0, so if for some reason this turns up, Im gonna fix that. And, after the query I create a HashSet. This is very important cause you dont want to send your users spam, what if the user has 10 reminders that day, are you gonna spam 10 notifications? That is lack of respect, so with the Hashset I remove the duplicate.
@BackendlessTimer("{'startDate':1445540700000,'frequency':{'schedule':'daily','repeat':{'every':1}},'timername':'CronDiligence'}")
public class CronDiligenceTimer extends com.backendless.servercode.extension.TimerExtender {
@Override
public void execute(String appVersionId) throws Exception {
try {
System.out.println("cron executed");
System.out.println("login job");
try {
System.out.println("login");
Backendless.UserService.login("god@heaven.univese", "creation");
} catch (BackendlessException e) {
System.out.println("error = " + e);
}
System.out.println("role job");
try {
System.out.println("role");
Backendless.UserService.assignRole("god@heaven.univese", "Ubicuos");
} catch (BackendlessException e) {
System.out.println("error = " + e);
}
DateTime currentDate = new DateTime();
String day = String.valueOf(currentDate.getDayOfMonth());
String month = String.valueOf(currentDate.getMonthOfYear());
String year = String.valueOf(currentDate.getYear());
String dateToMatch = "'" + month + "/" + day + "/" + year + "'";
String whereClause = "notification at or before " + dateToMatch;
BackendlessDataQuery dataQuery = new BackendlessDataQuery();
dataQuery.setPageSize(100);
dataQuery.setWhereClause(whereClause);
try {
System.out.println("query executed");
BackendlessCollection<Diligence> diligenceBackendlessCollection = Backendless.Persistence.of(Diligence.class).find(dataQuery);
List<Diligence> diligenceList = diligenceBackendlessCollection.getCurrentPage();
List<String> deviceList = new ArrayList<String>();
for (int i = 0; i < diligenceList.size(); i++) {
deviceList.add(diligenceList.get(i).getDevice());
}
HashSet<String> deviceHash = new HashSet<String>(deviceList);
Object[] deviceArray = deviceHash.toArray();
for (int i = 0; i < deviceArray.length; i++) {
try {
DeliveryOptions deliveryOptions = new DeliveryOptions();
deliveryOptions.setPushPolicy(PushPolicyEnum.ONLY);
System.out.println("device = " + String.valueOf(deviceArray));
deliveryOptions.addPushSinglecast(String.valueOf(deviceArray));
PublishOptions publishOptions = new PublishOptions();
publishOptions.putHeader( "android-ticker-text", "Recordatorio" );
publishOptions.putHeader("android-content-title", "Tienes cosas que hacer hoy");
publishOptions.putHeader("android-content-text", "Por favor revisa tu Calendarless");
try {
Backendless.Messaging.publish( "Recordatorio", publishOptions, deliveryOptions );
} catch (BackendlessException e) {
System.out.println("pushing error = " + e);
}
} catch (BackendlessException e) {
System.out.println("push error = " + e);
}
}
} catch (BackendlessException e) {
System.out.println("query error = " + e);
}
} catch (BackendlessException e) {
System.out.println("general error = " + e);
}
}
}
Thanks everybody, it was a blast, Im gonna finish the last details on my spare time and publish it in the appstore when I can
That’s a reasonable suggestion. Thanks, Erick.
Hi Erick,
Thanks for sharing the code. The argument for addPushSinglecast does not look right. You need to take an element out of array…
An optimization you could do is take out DeliveryOptions and PublishOptions out of the loop - there is no reason to run it every time for every device . Inside of the loop use only “addPushSinglecast” and then make only ONE call to publish. So something like this:
DeliveryOptions deliveryOptions = new DeliveryOptions();
deliveryOptions.setPushPolicy(PushPolicyEnum.ONLY);
PublishOptions publishOptions = new PublishOptions();
publishOptions.putHeader( "android-ticker-text", "Recordatorio" );
publishOptions.putHeader("android-content-title", "Tienes cosas que hacer hoy");
publishOptions.putHeader("android-content-text", "Por favor revisa tu Calendarless");
for (int i = 0; i < deviceArray.length; i++)
deliveryOptions.addPushSinglecast( deviceArray[ i ] );
try {
Backendless.Messaging.publish( "Recordatorio", publishOptions, deliveryOptions );
} catch (BackendlessException e) {
System.out.println("pushing error = " + e);
}
Hope this helps.
Mark
Your reply was definitevely more polite than the other user provide, thanks. Can I suggest you to change the error to “Async is not supported in custom bussines logic. Please use sync”, or anything more human readable, not everybody has 10 years JAVA experiences
Great! I was about to refactor when I saw th message, Im gonna share the last final refactor code below now
This is the final code im passing to production. I will provide an explanation below:
CronDiligenceTimer
@BackendlessTimer("{'startDate':1445540700000,'frequency':{'schedule':'daily','repeat':{'every':1}},'timername':'CronDiligence'}")
public class CronDiligenceTimer extends com.backendless.servercode.extension.TimerExtender {
@Override
public void execute(String appVersionId) throws Exception {
ManageUser manageUser = new ManageUser();
manageUser.login();
manageUser.role();
new DiligenceQuery().theQuery(dateToMatch());
}
private String dateToMatch() {
DateTime currentDate = new DateTime();
String day = String.valueOf(currentDate.getDayOfMonth());
String month = String.valueOf(currentDate.getMonthOfYear());
String year = String.valueOf(currentDate.getYear());
String dateToMatch = "'" + month + "/" + day + "/" + year + "'";
return dateToMatch;
}
}
ManageUser
public class ManageUser {
public void login() {
Backendless.UserService.login("god@heaven.universe", "originalsin123");
}
public void role() {
Backendless.UserService.assignRole("god@heaven.universe", "Ubicuos");
}
public void logout() {
Backendless.UserService.logout();
}
}
DiligenceQuery
public class DiligenceQuery {
public void theQuery(String dateToMatch){
String whereClause = "notification at or before " + dateToMatch;
BackendlessDataQuery dataQuery = new BackendlessDataQuery();
dataQuery.setPageSize(100);
dataQuery.setWhereClause(whereClause);
BackendlessCollection<Diligence> diligenceBackendlessCollection = Backendless.Persistence.of(Diligence.class).find(dataQuery);
queryResults(diligenceBackendlessCollection);
}
private void queryResults(BackendlessCollection<Diligence> diligenceBackendlessCollection) {
java.util.List<Diligence> diligenceList = diligenceBackendlessCollection.getCurrentPage();
java.util.List<String> deviceList = new ArrayList<String>();
for (int i = 0; i < diligenceList.size(); i++) {
deviceList.add(diligenceList.get(i).getDevice());
}
new PushNotificationOperation().pushNotification(deviceList);
}
}
PushNotificationsOperation
public class PushNotificationOperation {
public void pushNotification(List<String> deviceList) {
HashSet<String> deviceHash = new HashSet<String>(deviceList);
Object[] deviceArray = deviceHash.toArray();
DeliveryOptions deliveryOptions = new DeliveryOptions();
deliveryOptions.setPushPolicy(PushPolicyEnum.ONLY);
PublishOptions publishOptions = new PublishOptions();
publishOptions.putHeader("android-ticker-text", "Recordatorio");
publishOptions.putHeader("android-content-title", "Tienes cosas que hacer hoy");
publishOptions.putHeader("android-content-text", "Por favor revisa tu Calendarless");
for (int i = 0; i < deviceArray.length; i++) {
deliveryOptions.addPushSinglecast(String.valueOf(deviceArray));
Backendless.Messaging.publish("Recordatorio", publishOptions, deliveryOptions);
}
new ManageUser().logout();
}
}
So this is the workflow. Firs the cron job will login and asign the role to the user, using ManageUser. Aftert that the cron will execute the query which needs a formated current date, that value is obtained from a private String method in the cron job. Once the query is executed everything pass to the DiligenceQuery file, there the query will be executed in the theQuery method, at the end of that method, a private method inside the same file is invoked. This “queryResults” method will handle the query result data and will create a list to be pass to the notification. So the PushNotificatiosOperations file is executed passing as param the list with the devices ids we need to send the notifications. On it some conversations of that list are done and the push notification is executed. Finally for preventing mis use of the user and role the user is loged out.
Hope to help.