I am fairly new to functional programming and reactive RxJava. I want to get id and name of a device from database and store it in a Map, I am doing it in RxJava style. I am calling a function that doesn't need to return anything
.doOnNext(t -> updateAssetNameMap())
then the function looks like;
private void updateDeviceNameMap() {
LOGGER.debug("Reading device name and id from database");
Observable<SQLConnection> jdbcConnection = createJdbcConnection();
Scheduler defaultScheduler = RxHelper.scheduler(vertx);
Observable<JsonArray> res = jdbcConnection //need to return JsonArray
.flatMap(connection -> just(connection)
.flatMap(j -> runQuery(connection, "SELECT name,id FROM device")
.observeOn(defaultScheduler)
.doOnNext(m -> LOGGER.info("size: " + m.size()))
.flatMap(job -> { LOGGER.info(">>" + job.getJsonArray(0));
//or if I can extract JsonArray items here,
//I can update my Map here too.
return just(job.getJsonArray(0));
}
)
.doOnError(e -> { LOGGER.error("failed to connect to db", e);
connection.close(); })
.doOnCompleted(connection::close)
.onErrorReturn(e -> null));
//System.out.println("" + res.map(d -> LOGGER.info(d.toString())));
//get the JsonArray and update the deviceNameMap
The connection to DB is made successfully and query is also done correctly.
I can convert any Object to Observable by Observable.from(ObjectName), but can't to the opposite. An appropriate mapping needs to be done after .flatMap(job -> just(job.getJsonArray(0)) but I have no clue how. After running the Verticle, I even cannot see anything logged from line .flatMap(job -> { LOGGER.info(">>" + job.getJsonArray(0));.
Am I missing something ?
You must subscribe to your Observable<JsonArray> otherwise nothing happens.
Related
I have many reactive operations .existById() to do,
then on the collected list I want to execute once some operations.
The problem is I don't know which reactive operators I should use, or how to rewrite this to archive the desirable effect.
Every try I coded effect execution of only one part of that code.
First try where no-reactive code weren't waiting on Cassandra repo:
//First part:
signaturesFromFile.forEach(signatureFromFile -> {
SignatureKey signatureKey = new SignatureKey(signatureFromFile.getProfileId(), signatureFromFile.getUserId(), signatureFromFile.getId());
signatureRepositoryCassandra.existsById(signatureKey)
.map(exists -> {
if (!exists) {
notSavedSignatures.add(signatureFromFile);
}
return null;
}).subscribe();
});
//Second part:
String notSavedSignaturesListJson = convertSignaturesToJson(notSavedSignatures);
String pathToNotSavedSigantures = NOT_TRANSFERRED_SIGNATURES_DIRECTORY + signatureFilename;
saveIfFileNotExist(getUserIdFromFileName(signatureFilename), pathToNotSavedSigantures, notSavedSignaturesListJson);
deleteTransferredByUserIdSignaturesFile(signatureFilename);
The second idea was to close it in one reactive stream, but the problem recurred and the roles reversed - operations in the second part weren't executed
Flux.fromIterable(signaturesFromFile).map(signatureFromFile -> {
SignatureKey signatureKey = new SignatureKey(signatureFromFile.getProfileId(), signatureFromFile.getUserId(), signatureFromFile.getId());
//First part:
signatureRepositoryCassandra.existsById(signatureKey)
.map(exists -> {
if (!exists) {
notSavedSignatures.add(signatureFromFile);
}
return null;
}).subscribe();
return null;
}).mergeWith(e -> {
//Second part:
String notSavedSignaturesListJson = convertSignaturesToJson(notSavedSignatures);
String pathToNotSavedSigantures = NOT_TRANSFERRED_SIGNATURES_DIRECTORY + signatureFilename;
saveIfFileNotExist(getUserIdFromFileName(signatureFilename), pathToNotSavedSigantures, notSavedSignaturesListJson);
deleteTransferredByUserIdSignaturesFile(signatureFilename);
}).subscribe();
I have a workaround for that problem ->
.findAll() and filter data instead of many executed .existById()
but I would like to resolve it with the correct operators ;)
I am new to reactive programming and using helidon reactive java libraries in our code.
I am unable to achieve the below use case.
I have a scenario as below.
First I invoke a REST API and get response.From the response that contains list of countries I have to invoke another
REST api that retrieves the response for a country id and update the country object.
By the time I invoke second API and set value to country object as below the response is already returned.
I get use .get() and wait() on Single as it blocks the thread.
Please suggest how to overcome the below for loop and update the list of objects reactive way.
Single<WebClientResponse> singleWebClientResp = webClient.get("REST_URL");
Single<String> apiResponse = singleWebClientResponse.flatMapSingle(webClientResponse -> {
return webClientResponse.content().as(String.class);
});
apiResponse.flatMapSingle(fusionAPIResponseString -> {
List<Country> countries =
objectMapper.readValue(fusionAPIResponseString,new TypeReference<List<Country>>() {});
for (Country country : countries) {
getCountryByRegion(country.getRegion()).forSingle(newCountry -> {
LOGGER.log(Level.FINE, "newCountry ---> " + newCountry);
country.setRegion(country.getRegion() + "modified" + newCountry);
});
}
});
private Single<String> getCountryByRegion(String regionName) {
LOGGER.log(Level.FINE, "Entering getCountryByRegion");
Single<WebClientResponse> singleWebClientResponse2 = webClient.get().path("v3.1/region/" + regionName)
.contentType(MediaType.APPLICATION_JSON).request();
Single<String> retVal = singleWebClientResponse2.flatMapSingle(webClientResponse -> {
return webClientResponse.content().as(String.class);
});
LOGGER.log(Level.FINE, "Exiting getCountryByRegion");
return retVal;
}
Regards
// NOTE: this should be a static constant
GenericType<List<Country>> countriesType = new GenericType<>() {};
// NOTE: create the webClient only once, not for every request
WebClient webClient = WebClient.builder()
.addMediaSupport(JacksonSupport.create())
.baseUri("service-url")
.build();
// the pipeline starts with the initial countries (i.e. Single<List<Country>>)
webClient.get()
.path("/countries")
// get the countries as List<Country>
.request(countriesType)
// add each country to the reactive pipeline (i.e. Multi<Country>)
// to allow individual reactive mapping
.flatMap(Multi::just)
// map each country by creating a new country with new region
// use flatMap to inline the webClient result in the reactive pipeline
.flatMap(country ->
webClient.get()
.path("/region/" + country.getRegion())
.request(String.class)
.map(newCountry -> new Country(newCountry, country.getRegion())))
// aggregate all items (i.e. Single<List<Country>>)
.collectList()
.onError(res::send)
.forSingle(res::send))
If it wasn't reactive I'd do something like.
/**
* Given a refresh token and the user name, create a "payload" to represent
* secret data and store it into Redis as a hash and set it to expire in 30 seconds.
*/
Map<String, String> provideAuthenticatedData(String refreshTokenMono, String username) {
var ops = redisTemplate.opsForHash();
var payload = Map.of(
"username", username,
"secret", UUID.randomUUID().toString()
);
var puts = payload.entrySet()
.stream()
.map(e->ops.putIfAbsent(refreshToken, e.key(), e.value())
.filter(success -> !success) // finds those that have failed
.toList();
if (!puts.isEmpty()) {
throw new IllegalStateException("some elements failed to save");
}
var expireCheck = redisTemplate.expireAt(refreshToken, Instant.now().plusSeconds(30));
if (!expireCheck) {
throw new IllegalStateException("unable to expire");
}
return payload;
}
Trying to do it with Reactive it looks to get a bit messier and I got stuck after a point
/**
* Given a refresh token mono and the user name, create a "payload" to represent
* secret data and store it into Redis as a hash and set it to expire in 30 seconds.
*/
Mono<Map<String, String>> provideAuthenticatedData(Mono<String> refreshTokenMono, String username) {
var ops = reactiveRedisTemplate.opsForHash();
var payload = Map.of(
"username", username,
"secret", UUID.randomUUID().toString()
);
return refreshTokenMono
.flatMapIterable(
refreshToken -> payload.entrySet()
.stream()
.map(
e -> ops.putIfAbsent(refreshToken, e.getKey(), e.getValue())
)
.toList() // can't find an operator that would take a stream
)
// at this point I have a Flux<Mono<Boolean>>
// somehow I have to find out if any of them are false then return a Mono.error()
// then once all of it is done, set the key to expire
// finally return the payload I originally created
}
Another approach I did was this but it does not do any error handling.
Mono<Map<String, String>> provideAuthenticatedDataMono(
Mono<String> refreshTokenMono, String username) {
var ops = reactiveRedisTemplate.opsForHash();
var payload = Map.of(
"username", username,
"secret", UUID.randomUUID().toString()
);
return refreshTokenMono
.doOnNext(
refreshToken ->
payload
.entrySet()
.stream()
.map(
e -> ops.putIfAbsent(
refreshToken,
e.getKey(),
e.getValue())
)
.forEach(Mono::subscribe)
)
.doOnNext(
refreshToken ->
redisTemplate
.expireAt(
refreshToken,
Instant.now().plusSeconds(30)
)
.subscribe()
)
.flatMap((x) -> just(payload));
}
}
The main idea of reactive is to work in stream so you should avoid subscribing everywhere you should just return the Flux, Mono Stream.
THe first example is not working as you dont subscribe the mono that redis gives you.
As you are using reactive why you mix it with java stream.
one solution would be like
public Mono< YOUR_OBJECT > save(Object YOUR_OBJECT) {
return template.opsForValue().set(YOUR_OBJECT.key, YOUR_OBJECT)
.filter(aBoolean -> aBoolean)
.map(aBoolean -> YOUR_OBJECT)
.switchIfEmpty(Mono.error(new RuntimeException("Could not save data to redis")));
}
And you should be continuing the stream till the end when it will be subscribed by a controller or you
I created a reactive resteasy service with Quarkus and Mutiny. In POST method I insert object in my PostgreSQL table and get an Uni< id > in return. I need to set this id as part of my Response class which contains f.ex. "id", "errorCode", "errorString" and other variables, but I struggle as id comes as Uni object and I don't know how to extract id from it.
Create method:
public static Uni<Long> create(PgPool client, RetailPlace retailPlace) {
return client.withTransaction(conn -> conn
.preparedQuery("INSERT INTO retail_place (title) VALUES ($1) RETURNING id")
.execute(Tuple.of(retailPlace.getRetailPlaceTitle()))
.onItem().transformToUni(id -> conn.
preparedQuery("INSERT INTO retail_place_address (retail_place_id,region_code,city,locality_id,apartment,house,region,street) " +
"VALUES ($1,$2,$3,$4,$5,$6,$7,$8) returning retail_place_id")
.execute(Tuple.tuple(Arrays.asList(id.iterator().next().getLong("id"), retailPlace.getRetailPlaceAddress().getRegionCode(),
retailPlace.getRetailPlaceAddress().getCity(), retailPlace.getRetailPlaceAddress().getLocalityId(),
retailPlace.getRetailPlaceAddress().getApartment(), retailPlace.getRetailPlaceAddress().getHouse(),
retailPlace.getRetailPlaceAddress().getRegion(), retailPlace.getRetailPlaceAddress().getStreet())))))
.onItem().transform(pgRowSet -> pgRowSet.iterator().next().getLong("retail_place_id"));
}
I get long value of id in return. Now I need to return a RetailPlaceResponse with id value in it:
public RetailPlaceResponse(String errorCode, long id, boolean isSuccessful) {
this.errorCode = errorCode;
this.id = id;
this.isSuccessful = isSuccessful;
}
If you need both successful and non-successful responses to map to the same class, you could write separate transforms. One for success and one for failure.
public static Uni<RetailPlaceResponse> create(PgPool client, RetailPlace retailPlace) {
return client.withTransaction(conn -> conn
.preparedQuery("INSERT INTO retail_place (title) VALUES ($1) RETURNING id")
.execute(Tuple.of(retailPlace.getRetailPlaceTitle()))
.onItem().transformToUni(id -> conn.
preparedQuery("INSERT INTO retail_place_address (retail_place_id,region_code,city,locality_id,apartment,house,region,street) " +
"VALUES ($1,$2,$3,$4,$5,$6,$7,$8) returning retail_place_id")
.execute(Tuple.tuple(Arrays.asList(id.iterator().next().getLong("id"), retailPlace.getRetailPlaceAddress().getRegionCode(),
retailPlace.getRetailPlaceAddress().getCity(), retailPlace.getRetailPlaceAddress().getLocalityId(),
retailPlace.getRetailPlaceAddress().getApartment(), retailPlace.getRetailPlaceAddress().getHouse(),
retailPlace.getRetailPlaceAddress().getRegion(), retailPlace.getRetailPlaceAddress().getStreet())))))
.onItem().transform(pgRowSet -> new RetailPlaceResponse(null, pgRowSet.iterator().next().getLong("retail_place_id"), true))
.onFailure().transform(error -> new RetailPlaceResponse(error.toString(), 0, false));
}
I'm still learning Ionic and programming in general. I followed a link on the internet and I was able to create the white and read the necessary data, but I am not able to insert data in the created table. Can anyone help me with this?
I follow this tutorial: ionic-sqlite
My code:
getRegiao() { // RegiƵes //
return new Promise<Regiao[]>((resolve, reject) => {
let sql = "SELECT NOM_REGIAO, ID " +
"FROM TB_REGIAO "
this.executeQuery(sql).then(data => {
let regioes = [];
if (data != undefined)
data.forEach(function (row) {
let regiao: Regiao = { nom_regiao: row[0], id: row[1] }
regioes.push(regiao);
});
resolve(regioes);
}).catch(error => {
console.log(error);
});
});
}
addUser() {
let sql = "INSERT INTO TB_USUARIO (EMAIL) VALUES ('BLITCRANK#HOTMAIL.COM')";
// let sql = "SELECT EMAIL FROM TB_USUARIO";
this.executeQuery(sql);
}
executeQuery(sql: string) {
let db: any;
return new Promise<any>((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', this.dbName, true);
xhr.responseType = 'arraybuffer';
xhr.onload = (e) => {
let uInt8Array = new Uint8Array(xhr.response);
db = new SQL.Database(uInt8Array);
let contents = db.exec(sql);
console.log(contents);
if (contents.length > 0)
resolve(contents[0].values);
else
resolve("query executada sem retorno")
};
xhr.send();
});
}
Please use this plugin , this is the plugin that I am using.
Take note in using execute sql like this db.executeSql('create table danceMoves(name VARCHAR(32))', {})
But rather use this db.executeSql('create table danceMoves(name VARCHAR(32))', [])
I dont know why instead of using object '{}' they replace it as array '[]' I think they forgot to update the documentaion