Using ElasticSearch's script_upsert to create a document - java

According to the official documentation Update API - Upserts one can use scripted_upsert in order to handle update (for existing document) or insert (for new document) form within the script. The thing is they never show how the script should look to do that. The Java - Update API Doesn't have any information on the ScriptUpsert uses.
This is the code I'm using:
//My function to build and use the upsert
public void scriptedUpsert(String key, String parent, String scriptSource, Map<String, ? extends Object> parameters) {
Script script = new Script(scriptSource, ScriptType.INLINE, null, parameters);
UpdateRequest request = new UpdateRequest(index, type, key);
request.scriptedUpsert(true);
request.script(script);
if (parent != null) {
request.parent(parent);
}
this.bulkProcessor.add(request);
}
//A test call to validate the function
String scriptSource = "if (!ctx._source.hasProperty(\"numbers\")) {ctx._source.numbers=[]}";
Map<String, List<Integer>> parameters = new HashMap<>();
List<Integer> numbers = new LinkedList<>();
numbers.add(100);
parameters.put("numbers", numbers);
bulk.scriptedUpsert("testUser", null, scriptSource, parameters);
And I'm getting the following exception when "testUser" documents doesn't exists:
DocumentMissingException[[user][testUser]: document missing
How can I make the scriptUpsert work from the Java code?

This is how a scripted_upsert command should look like (and its script):
POST /sessions/session/1/_update
{
"scripted_upsert": true,
"script": {
"inline": "if (ctx.op == \"create\") ctx._source.numbers = newNumbers; else ctx._source.numbers += updatedNumbers",
"params": {
"newNumbers": [1,2,3],
"updatedNumbers": [55]
}
},
"upsert": {}
}
If you call the above command and the index doesn't exist, it will create it, together with the newNumbers values in the new documents. If you call again the exact same command the numbers values will become 1,2,3,55.
And in your case you are missing "upsert": {} part.

As Andrei suggested I was missing the upsert part, changing the function to:
public void scriptedUpsert(String key, String parent, String scriptSource, Map<String, ? extends Object> parameters) {
Script script = new Script(scriptSource, ScriptType.INLINE, null, parameters);
UpdateRequest request = new UpdateRequest(index, type, key);
request.scriptedUpsert(true);
request.script(script);
request.upsert("{}"); // <--- The change
if (parent != null) {
request.parent(parent);
}
this.bulkProcessor.add(request);
}
Fix it.

Related

jooq transaction jumping the gun - cant use 'returningResult()'?

Using JOOQ 3.11.11 / Java 11
Creating a transaction to write to a few tables. Two of these are interacting in that I use the auto_inc row #s from one as fk column values for another.
DSLContext writeContext = DSL.using(sourceDestConnection.getDestination(), SQLDialect.POSTGRES_10);
writeContext.transaction(writeTransaction -> {
try {
...
Map returnMap = writeFn(dataToWrite, writeTransaction);
secondWriteFn(moreDataToWrite, returnMap, writeTransaction);
throw new RuntimeException();
}
}
// this fn should write a series of records and save the auto_increment field in a map
public void writeFn(...) {
Map<Long, Long> idMap = new HashMap<>();
DSLContext context = DSL.using(configuration);
for (Record record : importBits) {
Record result = context.insertInto(IMPORT_TABLE).set(record)
.returningResult(ID_FIELD).fetchOne();
idMap.put((Long) record.get(ID_FIELD_LOOKUP), (Long) result.get(ID_FIELD));
}
return idMap;
}
// this fn should use the saved auto_inc fields from the previous fn as FK columns
public void secondWriteFn(...) throws IOException {
DSLContext context = DSL.using(configuration);
for (Map mergeMap : importTypes) {
context.insertInto(MERGE_TYPE_TABLE)
.set(buildMergeMap(mergeMap, idMap));
}
}
// this just builds a map to insert
public ImmutableMap<Object, Object> buildMergeMap(Map mergeMap, Map idMap) {
return ImmutableMap.builder()
.put(... columns ...)
.put(foreignKeyColumn, idMap.get(fkLookup_from_first_write_fn))
.build();
}
The p-code is from memory (different PC) but the end result (expected) would be that both tables would be empty after the RuntimeException. What Im seeing is that the first table has data but the 2nd doesn't.
Follow up questions:
do I need to use .execute() after each insert?
is there a problem with using the putative return value from one insert in a subsequent insert if the whole process is transacted?
It looks like you're not actually throwing your exception:
writeContext.transaction(writeTransaction -> {
// Try here!
try {
...
Map returnMap = writeFn(dataToWrite, writeTransaction);
secondWriteFn(moreDataToWrite, returnMap, writeTransaction);
throw new RuntimeException();
}
// You omitted the catch, but if you catch the above RuntimeException, then
// jOOQ does not know about it, and happily commit your transaction
}

How to automate function according to Array list size

I'm sorry this question header is not 100% correct. Because of that, I'll explain my scenario here.
I created a function to merge 4 data sets into one return format. Because that's the format front-end side needed. So this is working fine now.
public ReturnFormat makeThribleLineChart(List<NameCountModel> totalCount, List<NameCountModel>,p1Count, List<NameCountModel> p2Count, List<NameCountModel> average) {
ReturnFormat returnFormat = new ReturnFormat(null,null);
try {
String[] totalData = new String[totalCount.size()];
String[] p1Data = new String[p1Count.size()];
String[] p2Data = new String[p2Count.size()];
String[] averageData = new String[p2Count.size()];
String[] lableList = new String[totalCount.size()];
for (int x = 0; x < totalCount.size(); x++) {
totalData[x] = totalCount.get(x).getCount();
p1Data[x] = p1Count.get(x).getCount();
p2Data[x] = p2Count.get(x).getCount();
averageData[x] = average.get(x).getCount();
lableList[x] = totalCount.get(x).getName();
}
FormatHelper<String[]> totalFormatHelper= new FormatHelper<String[]>();
totalFormatHelper.setData(totalData);
totalFormatHelper.setType("line");
totalFormatHelper.setLabel("Uudet");
totalFormatHelper.setyAxisID("y-axis-1");
FormatHelper<String[]> p1FormatHelper= new FormatHelper<String[]>();
p1FormatHelper.setData(p1Data);
p1FormatHelper.setType("line");
p1FormatHelper.setLabel("P1 päivystykseen heti");
FormatHelper<String[]> p2FormatHelper= new FormatHelper<String[]>();
p2FormatHelper.setData(p2Data);
p2FormatHelper.setType("line");
p2FormatHelper.setLabel("P2 päivystykseen muttei yöllä");
FormatHelper<String[]> averageFormatHelper= new FormatHelper<String[]>();
averageFormatHelper.setData(averageData);
averageFormatHelper.setType("line");
averageFormatHelper.setLabel("Jonotusaika keskiarvo");
averageFormatHelper.setyAxisID("y-axis-2");
List<FormatHelper<String[]>> formatHelpObj = new ArrayList<FormatHelper<String[]>>();
formatHelpObj.add(totalFormatHelper);
formatHelpObj.add(p1FormatHelper);
formatHelpObj.add(p2FormatHelper);
formatHelpObj.add(averageFormatHelper);
returnFormat.setData(formatHelpObj);
returnFormat.setLabels(lableList);
returnFormat.setMessage(Messages.Success);
returnFormat.setStatus(ReturnFormat.Status.SUCCESS);
} catch (Exception e) {
returnFormat.setData(null);
returnFormat.setMessage(Messages.InternalServerError);
returnFormat.setStatus(ReturnFormat.Status.ERROR);
}
return returnFormat;
}
so, as you can see here, all the formatting is hardcoded. So my question is how to automate this code for list count. Let's assume next time I have to create chart formatting for five datasets. So I have to create another function to it. That's the thing I want to reduce. So I hope you can understand my question.
Thank you.
You're trying to solve the more general problem of composing a result object (in this case ReturnFormat) based on dynamic information. In addition, there's some metadata being setup along with each dataset - the type, label, etc. In the example that you've posted, you've hardcoded the relationship between a dataset and this metadata, but you'd need some way to establish this relationship for data dynamically if you have a variable number of parameters here.
Therefore, you have a couple of options:
Make makeThribleLineChart a varargs method to accept a variable number of parameters representing your data. Now you have the problem of associating metadata with your parameters - best option is probably to wrap the data and metadata together in some new object that is provided as each param of makeThribleLineChart.
So you'll end up with a signature that looks a bit like ReturnFormat makeThribleLineChart(DataMetadataWrapper... allDatasets), where DataMetadataWrapper contains everything required to build one FormatHelper instance.
Use a builder pattern, similar to the collection builders in guava, for example something like so:
class ThribbleLineChartBuilder {
List<FormatHelper<String[]>> formatHelpObj = new ArrayList<>();
ThribbleLineChartBuilder addDataSet(String describeType, String label, String yAxisId, List<NameCountModel> data) {
String[] dataArray = ... ; // build your array of data
FormatHelper<String[]> formatHelper = new FormatHelper<String[]>();
formatHelper.setData(dataArray);
formatHelper.setType(describeType);
... // set any other parameters that the FormatHelper requires here
formatHelpObj.add(formatHelper);
return this;
}
ReturnFormat build() {
ReturnFormat returnFormat = new ReturnFormat(null, null);
returnFormat.setData(this.formatHelpObj);
... // setup any other fields you need in ReturnFormat
return returnFormat;
}
}
// usage:
new ThribbleLineChartBuilder()
.addDataSet("line", "Uudet", "y-axis-1", totalCount)
.addDataSet("line", "P1 päivystykseen heti", null, p1Count)
... // setup your other data sources
.build()

Can't edit other Parse User objects , save In Background doesn't write in Parse Dashboard

I have been bonking my head everywhere on this problem , I would really need some help please !! I am pretty new to Android.
My problem is the following , I have completed the User Class with some columns , for example "Former Friends" which are a list of Strings .
I do a first query , then I find the Parseuser objects matching the query (which are not the logged in user) and then I try to fill those columns.
I also update the info for the logged in user
It properly works for the logged in user ,however I can't see the filled info for the other Parse object user
I tried modifying the write access for the first user (objects.get(0)) ,but it doesn't work
ParseQuery<ParseUser> query = ParseUser.getQuery();
query.whereNotEqualTo("username", getCurrentUser().getUsername());
query.whereNotContainedIn("username",getCurrentUser().getList("Former_friends"));
query.findInBackground(new FindCallback<ParseUser>() {
#Override
public void done(List<ParseUser> objects, ParseException e) {
if (e == null) {
if (objects.size() > 0) {
// Here I just add the first object to a list and I update the current user data ,that works fine
List<String> aList = ParseUser.getCurrentUser().getList("Former_friends");
aList.add(objects.get(0).getString("username"));
ParseUser.getCurrentUser().put("Former_friends", aList);
ParseUser.getCurrentUser().saveInBackground();
ParseUser userfound =objects.get(0);
// The two following Lines doesn't work. I don't see "Any String" in the ParseDashboard "Name" columns..
userfound.put("Name","Any String");
userfound.saveInBackground();
There are no bugs , but no update for the non-logged-in user
Big thx,
Serge
For security reasons, in Parse Server, one user cannot change another user object directly from client side. If it were possible, a bad user could erase all other users data, for example.
So this operation requires you to write a cloud code function. You should have a cloud function similar to this one here:
Parse.cloud.define('formerFriends', async request => {
const query = new Parse.Query(Parse.User);
query.notEqualTo('username', request.user.getUsername());
query.notContainedIn('username', request.user.get('Former_friends'));
const objects = await query.find({ useMasterKey: true });
if (object.length > 0) {
const aList = request.user.get('Former_friends');
aList.add(objects[0].getUsername());
request.user.set('Former_friends', aList);
await request.user.save();
const userfound = objects[0];
userfound.set('Name', 'Any String');
await userfound.save(null, { useMasterKey: true });
}
});
And then call the cloud function from Android like this:
HashMap<String, Object> params = new HashMap<String, Object>();
ParseCloud.callFunctionInBackground("formerFriends", params, new FunctionCallback<Float>() {
void done(Object result, ParseException e) {
if (e == null) {
// success
}
}
});

Get Data From Redis in batch RedisTemplate

I am using RedisTemplate in my spring boot application and I am able to read using singleKey.
String valueJson = (String) redisTemplate.opsForValue().get(setKey(someId));
I have now a List of "someId" like "List someIds" and I want to get the data of all Ids. Of course I can iterate on the list and hit redis with indivisual keys, but I dont want that, instead I want provide the whole list to get the response in one go.
Please help.
You need to use pipelining: https://redis.io/topics/pipelining
List<Object> results = redisTemplate.executePipelined(
new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
for(String id:someIds)
stringRedisConn.get(id);
return null;
}
});
Or in Java 8:
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
someIds.forEach(id -> {
stringRedisConn.get(id);
});
return null;
});
The results List will contain all that you want.

read, replace placeholders and write a word file with docx4j

I have a Word File look like this:
You don't need to understand the content, just take a look to my placeholders <env> and <applikationsabkürzung>. There are 10 pages with these placeholders and now I should replace them with other content. The black and yellow box are company pictures which I won't share.
Now I started to read the whole docx4j doc and generate after some time the following code:
public void manipulateWord(String path, String env, String appl) {
try {
WordprocessingMLPackage wpml = WordprocessingMLPackage.load(new File(path));
MainDocumentPart mdp = wpml.getMainDocumentPart();
List<Object> content = mdp.getContent();
// Include all Strings to replace
HashMap<String, String> mappings = new HashMap<String, String>();
mappings.put("<env>", env);
mappings.put("<applikationsabkürzung>", appl);
for (Object object : content) {
Text textElement = (Text) object;
String textToReplace = textElement.getValue();
if (mappings.keySet().contains(textToReplace)) {
textElement.setValue(mappings.get(textToReplace));
}
}
wpml.save(new File("C:\\Users\\kristina\\Desktop\\outputfile.docx"));
} catch (Docx4JException e) {
LOG.error(e);
}
Some explanaition:
String path is the path of the file in the picture above
String env is the value which should replace <env>
String appl is the value which should replace <applikationsabkürzung>
But when I run the method, nothing happen, my console just print just some infos. If they're important, i'll edit the post, but i don't think so.
So where is my fault? Would it work like that? I'm shortly before to despair...
MainDocumentPart.getContent() will return all OpenXml components within the main document flow (things like headers and footers have their own elements). Your code is assuming that the result of List<Object> content will be a collection of Text elements, which is not necessarily the case. For example, a typical (simple) document structure would be like this:
P // Paragraph element
-> R // Run element
-> Text // Text element
… so getContent() is, in all likelihood, going to spit out a load of P objects for a start.
There are a few ways to traverse docx4 files -- see the main docx4j site for more -- but one approach is shown in the method below. You can pass in MaindocumentPart as the first Object, and Text.class as the object type to search for. This should then assist in identifying all Text elements which contain one of your mapping values:
public List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {
List<Object> result = new ArrayList<Object>();
if (obj instanceof JAXBElement)
obj = ((JAXBElement<?>) obj).getValue();
if (obj.getClass().equals(toSearch))
result.add(obj);
else if (obj instanceof ContentAccessor) {
List<?> children = ((ContentAccessor) obj).getContent();
for (Object child : children) {
result.addAll(getAllElementFromObject(child, toSearch));
}
}
return result;
}

Categories