Counter in Firestore database in Firebase - java

I'm creating an Android application wherein I have to keep count of YTD, MTD, and Daily record punched for an organization as well as individual users. I tried the approach where for every save of record, I have a counter collection where I save data like
ORG_ORGID_2020 (for YTD)
ORG_ORGID_202005 (for MTD)
ORG_ORGID_20200513 (for Daily data)
ORG_USER1_2020 (for YTD)
ORG_USER2_202005 (for MTD)
ORG_USER3_20200513 (for Daily data)
so that I don't have to read many documents while fetching reports. Now to minimize the reads I save properties in the above documents (org_ID, year (i.e. 2020), yearMonth (i.e.202005), and so on. I save above documents in the form of the counter object
public class Counter {
#DocumentId
private String id;
private long count;
private String dealerId;
private String userId;
private String year;
private String yearMonth;
private String yearMonthDate;
}
Not the issue arises when I have to update the counter. I tried using
private FieldValue count;
and was able to update the count properly using
Counter counter = new Counter();
counter.setCount(FieldValue.increment(1));
counter.setDealerId(intentDealer.getId());
counter.setYear(strFullYear);
batch.set(dealerYtdColRef, counter, SetOptions.merge());
but when I try to fetch the record, I get
java.lang.RuntimeException: No properties to serialize found on class com.google.firebase.firestore.FieldValue
if I change the field to
private long count;
I'm not getting as, how to update the counter. I have to set all fields too along with the counter. I tried using .update method too, but it gives an error when the document is not present and has to be created for the first time.
Hw can I properly manage the counters? I'm doing counter part from app-only instead of functions because I'm trying to get the app work in free firebase tier only.

The problem in your code is the following line of code:
counter.setCount(FieldValue.increment(1));
Your count property is defined in your Counter class to be of type long. When you are using the setCount() method to set its value, you should pass a long value as an argument but you actually don't. The following statement:
FieldValue.increment(1)
Return an object of type FieldValue and not a long, hence that error. To increment the value of your count property atomically by one, please use the following lines of code:
Map<String, Object> updateCount = new HashMap<>();
updateCount.put("count", FieldValue.increment(1));
yourDocRef.set(updateCount, SetOptions.merge());

Finally, as per suggestion from Alex, I use the map values, but at the same time, I moved my code to google. Please let me know if I have done the implementation wrong. It seems to work though
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
const db = admin.firestore();
// [START_EXCLUDE]
const settings = { timestampsInSnapshots: true };
db.settings(settings);
// [END_EXCLUDE]
// [START aggregate_function]
exports.aggregateEnquiries = functions.firestore
.document('enquiries/{id}')
.onWrite(async (change, context) => {
if (!change.before.exists) {
// New document Created : add one to count
var dealerId = change.after.data().dealerId;
var userId = change.after.data().assignedTo;
var date = change.after.data().createdDt.toDate();
var day = date.getDate();
var month = date.getMonth() + 1;
var year = date.getFullYear();
var yearMonth = String(year) + (month < 10 ? "0" + month : month);
var yearMonthDate = yearMonth + (day < 10 ? "0" + day : day);
try {
return await db.collection("dealers").doc(dealerId)
.get()
.then((doc) => {
if (doc !== null && doc.exists) {
const increment = admin.firestore.FieldValue.increment(1);
db.collection("enquiries_agg")
.doc("D_" + dealerId + "_" + year)
.set({ "count": increment }, { merge: true });
db.collection("enquiries_agg")
.doc("D_" + dealerId + "_" + monthYear)
.set({ "count": increment }, { merge: true });
db.collection("enquiries_agg")
.doc("U_" + userId + "_" + year)
.set({
"count": increment,
"dealerId": dealerId,
"userId": userId,
"reference": String(year)
}, { merge: true });
db.collection("enquiries_agg")
.doc("U_" + userId + "_" + yearMonth)
.set({
"count": increment,
"dealerId": dealerId,
"userId": userId,
"reference": String(yearMonth)
}, { merge: true });
db.collection("enquiries_agg")
.doc("U_" + userId + "_" + yearMonthDate)
.set({
"count": increment,
"dealerId": dealerId,
"userId": userId,
"reference": String(yearMonthDate)
}, { merge: true });
} else {
console.log("error in aggregare entries.");
}
return null;
});
}
catch (error) {
console.log("Error getting documents: ", error);
throw new Error("Profile doesn't exist : " + error);
}
} else if (change.before.exists && change.after.exists) {
// Updating existing document : Do nothing
} else if (!change.after.exists) {
// Deleting document : subtract one from count
}
return null;
});

Related

Check if some key value exists in Firebase Database and do some action Android

I want to check if a specific value of a key exists in the Realtime Database or not and perform some action based on it.
I have the following data:
"Orders" : {
"03403426747" : {
"17" : {
"State" : "(4) Canceled",
"address" : "yubt",
"date" : "Feb 28, 2022",
"discount" : "0",
"name" : "uk",
"phone" : "0311111111",
"time" : "15:33:58 PM",
"totalAmount" : "3778"
},
"18" : {
"State" : "(1) Approved",
"address" : "yubt",
"date" : "Feb 28, 2022",
"discount" : "120",
"name" : "uk",
"phone" : "03111111111",
"time" : "16:01:58 PM",
"totalAmount" : "7703"
}
}
}
I want to check If any order from these has has "State" value other than "(3) Completed" & "(4) Canceled".
if anyone's order has a value other than these, I want to remove that user from the list which contains users with pending orders.
and if at any time that user has new order or older order State changed I want to again add that user to the list.
I want to check If any order from these has has "State" value other than "(3) Completed" & "(4) Canceled".
There is no way you can query the Realtime Database using a negation. What you can do instead is to create a separate query for each "other" state and join the results on the client.
However, if you consider at some point in time to try using Cloud Firestore, then you should consider using not equal (!=) query or if it fits your needs the not-in query.
After a long time, my brain light burned and I came up with a solution.
Create a data class for OrderState
public class OrderState {
public static int ordersCount = 0;
public static boolean state = false;
public static void update(boolean state){
if (state){
ordersCount = ordersCount + 1;
OrderState.state = true;
}else
if (!state && ordersCount > 0){
ordersCount = ordersCount - 1;
if (ordersCount < 1) OrderState.state = false;
}
}
public static void reset(){
ordersCount = 0;
state = false;
}
}
On FirebaseRecyclerAdapter -> onBindViewHolder
//if order not canceled or completed. it will update OrderState,
//ordersCount + 1 and state to true
if (adminOrders.getState().equals(Prevalent.orderStateNew) ||
adminOrders.getState().equals(Prevalent.orderStateApproved) ||
adminOrders.getState().equals(Prevalent.orderStateShipped) ||
adminOrders.getState().equals(Prevalent.orderStateApproved)){
OrderState.update(true);
}
changeUserWithOrderState();
On changing state of order by admin
//if the order is not already cancelled or completed, reduce one order from OrderState as it will be readded automatically upon Recycler refresh.
if (!adminOrders.getState().equals(Prevalent.orderStateCanceled) &&
!adminOrders.getState().equals(Prevalent.orderStateCompleted)) OrderState.update(false);
Al last if the user does not has any order with states New, Approved, and Shipped
OrderState.orderCount = 0;
OrderState.state = false;
and upon updating the database it will set the state to false.
private void changeUserWithOrderState() {
DatabaseReference userWithOrder = FirebaseDatabase.getInstance().getReference()
.child(Prevalent.usersWithOrders)
.child(userPhoneKey);
HashMap<String, Object> map = new HashMap<>();
map.put(Prevalent.orderState, String.valueOf(OrderState.state));
userWithOrder.updateChildren(map).addOnCompleteListener(task -> {
//Changed state based upon OrderState.state value...
});
}

Return substring of analyzed, non-stored text field in elasticsearch java api

I work on a project that has a string field (the name is urlOrContent) and it can be small (less than 50 character) or very long (more than 50 character), and I just want to return the first 50 characters every time based on a specific query. My database is elasticsearch and my problem is raised in this link and the questioner’s response seems to be correct (urlOrContent field is analyzed and non stored text field). It uses following script:
{
"script_fields": {
"substring": {
"script": {
"lang": "painless",
"inline": "params._source.text.substring(0, 100)"
}
}
}
}
But my main problem is that I can not find the equivalent of elasticsearch java api code. In fact, what should be added to the code below, which only returns the first 50 characters of the urlOrContent field? Note that this field may not even have 50 characters in some cases, and then the entire string should be returned.
String queryString =
EnumLinkFields.CREATE_TIME.getFieldName() + ":(>=" + dateFrom + " AND <=" + dateTo + ")";
QueryBuilder query = QueryBuilders.queryStringQuery(queryString);
SearchResponse response = TRANSPORT_CLIENT.prepareSearch(MY_INDEX)
.setTypes(MY_TYPE)
.setSearchType(SEARCH_TYPE)
.setQuery(query)
.setFetchSource(null, new String[]{EnumLinkFields.USER_ID.getFieldName()})
.setFrom(offset)
.setSize(count)
.addSort(orderByField, sortOrder)
.execute().actionGet();
I found the best answer.
String queryString =
EnumLinkFields.CREATE_TIME.getFieldName() + ":(>=" + dateFrom + " AND <=" + dateTo + ")";
QueryBuilder query = QueryBuilders.queryStringQuery(queryString);
String codeUrlOrContent = "if (" + EnumElasticScriptField.URL_OR_CONTENT.getFieldName() + ".length() > 50) {" +
"return " + EnumElasticScriptField.URL_OR_CONTENT.getFieldName() + ".substring(0, 50);" +
"} else { " +
"return " + EnumElasticScriptField.URL_OR_CONTENT.getFieldName() + "; }";
Script scriptUrlOrContent = new Script(ScriptType.INLINE, "painless",
codeUrlOrContent, Collections.emptyMap());
Script scriptIsUrl = new Script(ScriptType.INLINE, "painless",
EnumElasticScriptField.IS_URL.getFieldName(), Collections.emptyMap());
SearchResponse response = TRANSPORT_CLIENT.prepareSearch(MY_INDEX)
.setTypes(MY_TYPE)
.setSearchType(SEARCH_TYPE)
.setQuery(query)
.addScriptField(EnumLinkFields.URL_OR_CONTENT.getFieldName(),
scriptUrlOrContent)
.addScriptField(EnumLinkFields.IS_URL.getFieldName(), scriptIsUrl)
.setFrom(offset)
.setSize(count)
.addSort(orderByField, sortOrder)
.execute().actionGet();
Note that the call to the setFetchSource function must be removed and all returned fields must be returned through the script.
You can put your script_fields query in the query object, i.e. in setQuery(query).
Your query object should be looking like this right now.
"query" : {
"term" : { "user" : "kimchy" }
}
After you add the script_fields in the object, it should become:
"query" : {
"term" : { "user" : "kimchy" }
},
"script_fields": {
"urlOrContent": {
"script": {
"lang": "painless",
"inline": "if(params._source.urlOrContent.length() > 50){
params._source.urlOrContent.substring(0, 50)
}
else {
params._source.urlOrContent
}"
}
}
}
The resulting hits will have a fields array with the substring you required.
You have to enable scripting by changing the elasticsearch.yml file like so and restart the elasticsearch:
script.engine.painless.inline.aggs: on
script.engine.painless.inline.update: on
script.inline: on
script.indexed: on

Database insertion synchronization

I have a java code that generates a request number based on the data received from database, and then updates the database for newly generated
synchronized (this.getClass()) {
counter++;
System.out.println(counter);
System.out.println("start " + System.identityHashCode(this));
certRequest
.setRequestNbr(generateRequestNumber(certInsuranceRequestAddRq
.getAccountInfo().getAccountNumberId()));
System.out.println("outside funcvtion"+certRequest.getRequestNbr());
reqId = Utils.getUniqueId();
certRequest.setRequestId(reqId);
System.out.println(reqId);
ItemIdInfo itemIdInfo = new ItemIdInfo();
itemIdInfo.setInsurerId(certRequest.getRequestId());
certRequest.setItemIdInfo(itemIdInfo);
dao.insert(certRequest);
addAccountRel();
counter++;
System.out.println(counter);
System.out.println("end");
}
the output for System.out.println() statements is `
1
start 27907101
com.csc.exceed.certificate.domain.CertRequest#a042cb
inside function request number66
outside funcvtion66
AF88172D-C8B0-4DCD-9AC6-12296EF8728D
2
end
3
start 21695531
com.csc.exceed.certificate.domain.CertRequest#f98690
inside function request number66
outside funcvtion66
F3200106-6033-4AEC-8DC3-B23FCD3CA380
4
end
In my case I get a call from two threads for this code.
If you observe both the threads run independently. However the data for request number is same in both the cases.
is it possible that before the database updation for first thread completes the second thread starts execution.
`
the code for generateRequestNumber() is as follows:
public String generateRequestNumber(String accNumber) throws Exception {
String requestNumber = null;
if (accNumber != null) {
String SQL_QUERY = "select CERTREQUEST.requestNbr from CertRequest as CERTREQUEST, "
+ "CertActObjRel as certActObjRel where certActObjRel.certificateObjkeyId=CERTREQUEST.requestId "
+ " and certActObjRel.certObjTypeCd=:certObjTypeCd "
+ " and certActObjRel.certAccountId=:accNumber ";
String[] parameterNames = { "certObjTypeCd", "accNumber" };
Object[] parameterVaues = new Object[] {
Constants.REQUEST_RELATION_CODE, accNumber };
List<?> resultSet = dao.executeNamedQuery(SQL_QUERY,
parameterNames, parameterVaues);
// List<?> resultSet = dao.retrieveTableData(SQL_QUERY);
if (resultSet != null && resultSet.size() > 0) {
requestNumber = (String) resultSet.get(0);
}
int maxRequestNumber = -1;
if (requestNumber != null && requestNumber.length() > 0) {
maxRequestNumber = maxValue(resultSet.toArray());
requestNumber = Integer.toString(maxRequestNumber + 1);
} else {
requestNumber = Integer.toString(1);
}
System.out.println("inside function request number"+requestNumber);
return requestNumber;
}
return null;
}
Databases allow multiple simultaneous connections, so unless you write your code properly you can mess up the data.
Since you only seem to require a unique growing integer, you can easily generate one safely inside the database with for example a sequence (if supported by the database). Databases not supporting sequences usually provide some other way (such as auto increment columns in MySQL).

Create Custom InputFormat of ColumnFamilyInputFormat for cassandra

I am working on a project, using cassandra 1.2, hadoop 1.2
I have created my normal cassandra mapper and reducer, but I want to create my own Input format class, which will read the records from cassandra, and I'll get the desired column's value, by splitting that value using splitting and indexing ,
so, I planned to create custom Format class. but I'm confused and not able to know, how would I make it? What classes are to be extend and implement, and how I will able to fetch the row key, column name, columns value etc.
I have my Mapperclass as follow:
public class MyMapper extends
Mapper<ByteBuffer, SortedMap<ByteBuffer, IColumn>, Text, Text> {
private Text word = new Text();
MyJDBC db = new MyJDBC();
public void map(ByteBuffer key, SortedMap<ByteBuffer, IColumn> columns,
Context context) throws IOException, InterruptedException {
long std_id = Long.parseLong(ByteBufferUtil.string(key));
long newSavePoint = 0;
if (columns.values().isEmpty()) {
System.out.println("EMPTY ITERATOR");
sb.append("column_N/A" + ":" + "N/A" + " , ");
} else {
for (IColumn cell : columns.values()) {
name = ByteBufferUtil.string(cell.name());
String value = null;
if (name.contains("int")) {
value = String.valueOf(ByteBufferUtil.toInt(cell.value()));
} else {
value = ByteBufferUtil.string(cell.value());
}
String[] data = value.toString().split(",");
// if (data[0].equalsIgnoreCase("login")) {
Long[] dif = getDateDiffe(d1, d2);
// logics i want to perform inside my custominput class , rather here, i just want a simple mapper class
if (condition1 && condition2) {
myhits++;
sb.append(":\t " + data[0] + " " + data[2] + " "+ data[1] /* + " " + data[3] */+ "\n");
newSavePoint = d2;
}
}
sb.append("~" + like + "~" + newSavePoint + "~");
word.set(sb.toString().replace("\t", ""));
}
db.setInterval(Long.parseLong(ByteBufferUtil.string(key)), newSavePoint);
db.setHits(Long.parseLong(ByteBufferUtil.string(key)), like + "");
context.write(new Text(ByteBufferUtil.string(key)), word);
}
I want to decrease my Mapper Class logics, and want to perform same calculations on my custom input class.
Please help, i wish for the positive r4esponse from stackies...
You can do the intended task by moving the Mapper logic to your custom input class (as you have indicated already)
I found this nice post which explains a similar problem statement as you have. I think it might solve your problem.

How to change the value of a variable once per day in Java/javascript

I'm trying to make an application/script for my school which displays the day's schedule. The catch with this is that my school runs on an 8 day cycle, so that complicates things. I have a a variable called cycleDay , but how would I go about updating this just once per day, and not more than that? If there's another way you can think of doing this, please let me know.
Thanks!
You can use, say, the getTime() function of the Date object, which returns the current time in milliseconds after January 1, 1970 (source: http://www.w3schools.com/jsref/jsref_gettime.asp), and save that value (e.g. in var lastUpdateTime). Then periodically check if the difference between the current time and that saved time is more than a day. If so, update cycleDay, and also update lastUpdateTime to the time when you updated.
For instance, initialize with:
var lastUpdateTime = new Date().getTime();
Somewhere else in your code:
var currentTime = new Date().getTime();
if(currentTime - lastUpdateTime >= 24*60*60*1000) // number of milliseconds in a day
{
// update cycleDay
lastUpdateTime = currentTime;
// ...
}
private Date lastUpdate = now()-1;
private myDailyChahgedValue;
Integer getMyDailyChangedValue() {
if (lastUpdate <> now()) {
myDailyChahgedValue ++;
}
return value;
}
Please note it is an a draft of code what shows main idea
Use Date and document.cookies to update your variable. Also i used two utility methods to manipulate document.cookies
var today = parseInt(new Date().getTime()/(1000*3600*24))
var cookies = getCookies();
if(cookies["last_updated"] && cookies["last_updated"]<today)
{
/* update variable */
setCookie("last_updated", today, 1);
}
/* utility methods starts, adding utility methods to simplify setting and getting cookies */
function setCookie(name, value, daysToLive) {
var cookie = name + "=" + encodeURIComponent(value);
if (typeof daysToLive === "number")
cookie += "; max-age=" + (daysToLive*60*60*24);
document.cookie = cookie;
}
function getCookies() {
var cookies = {}; // The object we will return
var all = document.cookie; // Get all cookies in one big string
if (all === "") // If the property is the empty string
return cookies; // return an empty object
var list = all.split("; "); // Split into individual name=value pairs
for(var i = 0; i < list.length; i++) { // For each cookie
var cookie = list[i];
var p = cookie.indexOf("="); // Find the first = sign
var name = cookie.substring(0,p); // Get cookie name
var value = cookie.substring(p+1); // Get cookie value
value = decodeURIComponent(value); // Decode the value
cookies[name] = value; // Store name and value in object
}
return cookies;
}
/* utility methods ends */

Categories

Resources