How to use a variable from Uni, Mutiny - java

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));
}

Related

Data validation and account creation with Either - how to write it better?

I have an AccountCreator class with a create method that takes a DTO with the data needed to create an account. At the beginning there is an attempt to create 2 value objects (UserName and Password), then validate the uniqueness of the user name, create the Account entity which takes these 2 value objects in the constructor and save it in the repo. Of course, errors such as incorrect password length, etc. may be returned. I used Eithers for this and now the question is whether this code is ok or maybe it can be written somehow better?
public Either<Error, AccountDto> create(AccountCreateDto accountCreateDto) {
var errorType = ErrorType.ACCOUNT_PERSISTENCE_ERROR;
var errorMessage = "Not unique user name: " + accountCreateDto.userName;
var error = new Error(errorType, errorMessage);
return UserName
.create(accountCreateDto.userName)
.flatMap(userName ->
userNameUniquenessChecker.isUnique(userName.text) ?
Password
.create(accountCreateDto.password)
.flatMap(password -> {
var createdAccount = new Account(
userName,
password,
AccountStatus.OPEN,
LocalDateTime.now(),
new ArrayList<>()
);
var addedAccount = accountRepository.add(createdAccount);
var accountDto = new AccountDto(
addedAccount.userName.text,
addedAccount.password.text,
addedAccount.status,
addedAccount.creationDate,
(long) addedAccount.tasks.size()
);
return Either.right(accountDto);
}) : Either.left(error));
}
The customary FP approach would be to use a Validation style applicative functor - there's one in Vavr called Validation. You can use Validation.combine to combine multiple Validation values into one, e.g.:
public Validation<Seq<<Error>, AccountDto> create(AccountCreateDto accountCreateDto) {
Validation<Seq<<Error>, Account> validAcc =
Validation.combine(
UserName.create(accountCreateDto.userName),
Password.create(accountCreateDto.password)
).ap((un, pw) -> new Account(
un,
pw,
AccountStatus.OPEN,
LocalDateTime.now()
);
Validation<Seq<<Error>, Account> validAcc2 =
validAcc.flatMap(acc -> validateUserIdIsUnique(acc));
Validation<<Seq<Error>, AccountDto> validAccDto =
validAcc2.map(accountRepository::add)
.map(addedAccount ->
new AccountDto(
addedAccount.userName.text,
addedAccount.password.text,
addedAccount.status,
addedAccount.creationDate,
(long) addedAccount.tasks.size()
)
);
}
return validAccDto;
}
private static final var errorType = ErrorType.ACCOUNT_PERSISTENCE_ERROR;
private static final var errorMessage = "Not unique user name: " + accountCreateDto.userName;
private static final var error = new Error(errorType, errorMessage);
Validation<Seq<Error>, Account> validateUserIdIsUnique(Account acc) {
return userNameUniquenessChecker.isUnique(acc.userName.text) ?
Validation.valid(userName) :
Validation.invalid(error);
}
You can elide the temporaries - validAcc, validAcc2 & validAccDto, however I left them in for clarity.
(caveat emptor - haven't tested that this code works, or even compiles)

How to use BeanMapHandler with field to column mapping with key as ID in a nested SELECT query?

I have a nested SQL query to fetch employee details using their ID.
Right now I am using BeanListHandler to fetch data as a List<Details> but want to store it as a Map<String, Details> where the ID I originally pass needs to be the key for easy retrieval instead of searching the List with streams every time.
I have tried to convert to Maps but I am not sure of how to map the ID as String nor how to get the original ID passed to the inner Query as a column in the final result.
MainTest.java:
String candidateId = "('1111', '2222', '3333', '4444')";
String detailsQuery =
"select PARTNER, BIRTHDT, XSEXM, XSEXF from \"schema\".\"platform.view/table2\" where partner IN \r\n"
+ "(select SID from \"schema\".\"platform.view/table1\" where TYPE='BB' and CLASS='yy' and ID IN \r\n"
+ "(select SID from \"schema\".\"platform.view/table1\" where TYPE='AA' and CLASS='zz' and ID IN"
+ candidateId + "\r\n" + "))";
Map<String, Details> detailsView = queryRunner.query(conn, detailsQuery, new DetailsViewHandler());
Details.java:
public class Details {
private String candidateId;
private String birthDate;
private String maleSex;
private String femaleSex;
// getter and setter
}
DetailsViewHandler.java:
public class DetailsViewHandler extends BeanMapHandler<String, Details> {
public DetailsViewHandler() {
super(Details.class, new BasicRowProcessor(new BeanProcessor(getColumnsToFieldsMap())));
}
public static Map<String, String> getColumnsToFieldsMap() {
Map<String, String> columnsToFieldsMap = new HashMap<>();
columnsToFieldsMap.put("PARTNER", "candidateId");
columnsToFieldsMap.put("BIRTHDT", "birthDate");
columnsToFieldsMap.put("XSEXM", "maleSex");
columnsToFieldsMap.put("XSEXF", "femaleSex");
return columnsToFieldsMap;
}
}
Is there a way to get the ID (candidateId) in the result and what am I missing in terms of creating the key-value pairing ?
From the doc https://commons.apache.org/proper/commons-dbutils/apidocs/org/apache/commons/dbutils/handlers/BeanMapHandler.html
of constructor which you are using
public BeanMapHandler(Class<V> type,
RowProcessor convert)
// Creates a new instance of BeanMapHandler. The value of the first column of each row will be a key in the Map.
Above should work.
You can also try overriding createKey like so
protected K createKey(ResultSet rs)
throws SQLException {
return rs.getString("PARTNER"); // or getInt whatever suits
}

RxJava return JsonArray from Observable<JsonArray>

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.

Setting Parameters through url javaplay

so as part of some work I've been doing I was given a file with WebServices that are being used in a Swift application. I have zero familiarity with WebServices and only know Java through syntax understanding. I need to call one of these gets with a parameter from the swift application. What I'm trying to figure out first and foremost is how I can call one of these webservices with a parameter from the URL it's associated with. For example down below I want to call the method
http://localhost:9000/ListVehicleByPlateNumber
and I want to specify the parameter through the URL say something like
http://localhost:9000/ListVehicleByPlateNumber?para="123"
But this doesn't assign any value to the parameter and I'm not getting results. If I hardcode so that the string used in the function is = "123" it gives me the results I'm looking for. I just need to know how I can pass this parameter through the url, syntax-wise.
Routes file
GET /ListVehicleByPlateNumber controllers.NewVehicle.listVehicleByPlateNumber(para: String ?="")
Controller
public Result listVehicleByPlateNumber(String para){
NewVehicleModel v = new NewVehicleModel();
List<NewVehicleModel> vehiclesC = v.searchByPlateVehicle(para);
ObjectNode wrapper = Json.newObject();
ObjectNode msg = Json.newObject();
if(vehiclesC != null) {
msg.set("VehicleList", toJson(vehiclesC));
wrapper.set("success", msg);
return ok(wrapper);
}else{
msg.put("error", "There are no vehicles with the plate number");
wrapper.set("error", msg);
return badRequest(wrapper);
}
}
Where it's called
public List<NewVehicleModel> searchByPlateVehicle(String plateNumber){
Transaction t = Ebean.beginTransaction();
List<NewVehicleModel> vehicles = new ArrayList<>();
try {
String sql = "SELECT V.idNewVehicle, V.VehicleType,V.PlateNumber,V.VehicleJurisdiction,V.State,V.Vin,V.Year, " +
"V.Make,V.modelos,V.RegistrationNumber,V.InsuranceCompany,V.PurchaseDate,V.ExpirationDate,V.idPersonaFK " +
"FROM NewVehicle V " +
"WHERE V.PlateNumber = :plateNumber";
RawSql rawSql = RawSqlBuilder.parse(sql)
.columnMapping("V.idNewVehicle", "idNewVehicle")
.columnMapping("V.State", "state")
.columnMapping("V.VehicleType", "vehicleType")
.columnMapping("V.PlateNumber", "plateNumber")
.columnMapping("V.VehicleJurisdiction", "vehicleJurisdiction")
.columnMapping("V.Vin", "vin")
.columnMapping("V.Year", "year")
.columnMapping("V.Make", "make")
.columnMapping("V.modelos", "modelos")
.columnMapping("V.RegistrationNumber", "registrationNumber")
.columnMapping("V.InsuranceCompany", "insuranceCompany")
.columnMapping("V.PurchaseDate", "purchaseDate")
.columnMapping("V.ExpirationDate", "expirationDate")
.columnMapping("V.idPersonaFK", "idPersonaFK")
.create();
Query<NewVehicleModel> query = Ebean.find(NewVehicleModel.class);
query.setRawSql(rawSql)
.setParameter("plateNumber", plateNumber);
vehicles = query.findList();
t.commit();
}
catch (Exception e){
System.out.println(e.getMessage());
}finally {
t.end();
}
return vehicles;
}
Found my own answer. I ended up casting from Integer to String here's how it looks in routes
GET /ListVehicleByPlateNumber/:para controllers.NewVehicle.listVehicleByPlateNumber(para: Integer )
Controller
public Result listVehicleByPlateNumber(int para){
String p = String.valueOf(para);
URI Format for value 123 example.
http://localhost:9000/ListVehicleByPlateNumber/123

Handling more properties than what is needed

I'm trying to load a set of objects via a SQL call. The SQL query returns more properties than needed. Other than correcting the SQL query. How would I get groovy to ignore all of the extraneous Parameters upon declaration of Product?
import groovy.sql.Sql
class Product {
Integer id;
Integer type;
String unique;
}
def var = [];
sql = Sql.newInstance("jdbc:jtds:sqlserver://localhost/[DB]", "user", "password","net.sourceforge.jtds.jdbc.Driver");
sql.eachRow("select * from Products", { var << new Product(it.toRowResult()) } );
I'm getting the exception:
groovy.lang.MissingPropertyException: No such property: [other fields from the SQL result]
The default Groovy behavior is to throw a groovy.lang.MissingPropertyException whenever you try to set a value to a property that doesn't exist in the bean. I am not quite sure if you can change that behavior. However, you could write a helper method that filters out the properties that do not exist.
def filterResult(bean, row) {
def filteredProps = [:]
row.each { key, value ->
if(bean.metaClass.properties.find { prop -> prop.name == key }) {
filteredProps.put(key, value)
}
}
filteredProps
}
def var = []
sql.eachRow("select * from Products", {
var << new Product(filterResult(Product, it.toRowResult()))
})

Categories

Resources