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)
Related
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 am following this tutorial by Raywenderlich on paging-library-for-android-with-kotlin on how to use android paging library. This is one of the easiest tutorials on the net and I have followed it thoroughly. However, I would like to make some changes so that I can intelligently switch between online data and offline data.
That is, I have old some posts in my database. Initially I have internet connection. So I load latest data from internet, then insert it into my database. Finally, I show this latest data in my recyclerView / PagedListAdapter.
If for some reason, there is no internet connection after sometime, I should show the old posts from database.
How can I do this?
My attempts:
This is my code on github repository.
Here, I tried to create a factory pattern. It checks if initially I have internet, the factory returns pagedList from online dataSource. ELse, the factory returns pagedList from offline dataSource.
But this doesnot intelligently switch between the 2 states.
I tried some random codes such as creating a boundary callback. But I am not sure how to make the necessary changes.
I am not adding codes here (at least for now) to keep it short and precise.
Can anyone help me?
Edit:
To be specific, I am loading paged data primarily from network. If there is a network error, I don't want to show the user an error. Instead I load paged data from cache / database and continuously show it to my user as long as possible. If the network is back,switch back to network paged data. (that's what instagram / facebook does I think). What is the appropriate way to implement this? See my code / attemp in the answer.
Okay, so after trying out some codes for 2 days, this is what I came up with. However, I really don't know if this is a good pratice or not. So I am open to any acceptable answers.
Explanation:
Since I have multiple data sources(network and database), I created ProfilePostDataSource: PageKeyedDataSource<Pair<Long, Long>, ProfilePost> here the key is a pair, the 1st one for network pagination, the 2nd one is for database pagination.
I used kotlin's Coroutine to write some asynchronous codes in a simple if-else like manner. So we can write it in a psudo-code like this:
Database db;
Retrofit retrofit;
inside loadInitial/loadBefore / loadAfter:
currNetworkKey = params.key.first;
currDBKey = params.key.second;
ArrayList<Model> pagedList;
coroutine{
ArrayList<Model> onlineList = retrofit.getNetworkData(currNetworkKey); // <-- we primarily load data from network
if(onlineList != null) {
pagedList = onlineList;
db.insertAll(onlineList); // <-- update our cache
}else{
ArrayList<Model> offlineList = db.getOfflineData(currDBKey); // <-- incase the network fails, we load cache from database
if(offlineList !=null){
pagedList = offlineList;
}
}
if(pagedList != null or empty) {
nextNetworkKey = // update it accordingly
nextDBKey = // update it accordingly
Pair<int, int> nextKey = new Pair(nextNetworkKey, nextDBKey);
pagingLibraryCallBack.onResult(pagedList, nextKey); // <-- submit the data to paging library via callback. this updates your adapter, recyclerview etc...
}
}
So in apps like facebook, instagram etc, we see them primarily loading data from network. But if the network is down, they show you a cashed data. We can intelligently make this switch like this code.
Here is a relevant code snippet, the PageKeyedDataSource written in kotlin:
ProfilePostDataSource.kt
/** #brief: <Key, Value> = <Integer, ProfilePost>. The key = pageKey used in api. Value = single item data type in the recyclerView
*
* We have a situation. We need a 2nd id to fetch profilePosts from database.
* Change of plan: <Key, Value> = < Pair<Int, Int>, ProfilePost>. here the
*
* key.first = pageKey used in api. <-- Warning: Dont switch these 2!
* Key.second = db last items id
* used as out db page key
*
* Value = single item data type in the recyclerView
*
* */
class ProfilePostDataSource: PageKeyedDataSource<Pair<Long, Long>, ProfilePost> {
companion object{
val TAG: String = ProfilePostDataSource::class.java.simpleName;
val INVALID_KEY: Long = -1;
}
private val context: Context;
private val userId: Int;
private val liveLoaderState: MutableLiveData<NetworkState>;
private val profilePostLocalData: ProfilePostLocalDataProvider;
public constructor(context: Context, userId: Int, profilePostLocalData: ProfilePostLocalDataProvider, liveLoaderState: MutableLiveData<NetworkState>) {
this.context = context;
this.userId = userId;
this.profilePostLocalData = profilePostLocalData;
this.liveLoaderState = liveLoaderState;
}
override fun loadInitial(params: LoadInitialParams<Pair<Long, Long>>, pagingLibraryCallBack: LoadInitialCallback<Pair<Long, Long>, ProfilePost>) {
val initialNetworkKey: Long = 1L; // suffix = networkKey cz later we'll add dbKey
var nextNetworkKey = initialNetworkKey + 1;
val prevNetworkKey = null; // cz we wont be using it in this case
val initialDbKey: Long = Long.MAX_VALUE; // dont think I need it
var nextDBKey: Long = 0L;
GlobalScope.launch(Dispatchers.IO) {
val pagedProfilePosts: ArrayList<ProfilePost> = ArrayList(); // cz kotlin emptyList() sometimes gives a weird error. So use arraylist and be happy
val authorization : String = AuthManager.getInstance(context).authenticationToken;
try{
setLoading();
val res: Response<ProfileServerResponse> = getAPIService().getFeedProfile(
sessionToken = authorization, id = userId, withProfile = false, withPosts = true, page = initialNetworkKey.toInt()
);
if(res.isSuccessful && res.body()!=null) {
pagedProfilePosts.addAll(res.body()!!.posts);
}
}catch (x: Exception) {
Log.e(TAG, "Exception -> "+x.message);
}
if(pagedProfilePosts.isNotEmpty()) {
// this means network call is successfull
Log.e(TAG, "key -> "+initialNetworkKey+" size -> "+pagedProfilePosts.size+" "+pagedProfilePosts.toString());
nextDBKey = pagedProfilePosts.last().id;
val nextKey: Pair<Long, Long> = Pair(nextNetworkKey, nextDBKey);
pagingLibraryCallBack.onResult(pagedProfilePosts, prevNetworkKey, nextKey);
// <-- this is paging library's callback to a pipeline that updates data which inturn updates the recyclerView. There is a line: adapter.submitPost(list) in FeedProfileFragment. this callback is related to that line...
profilePostLocalData.insertProfilePosts(pagedProfilePosts, userId); // insert the latest data in db
}else{
// fetch data from cache
val cachedList: List<ProfilePost> = profilePostLocalData.getProfilePosts(userId);
pagedProfilePosts.addAll(cachedList);
if(pagedProfilePosts.size>0) {
nextDBKey = cachedList.last().id;
}else{
nextDBKey = INVALID_KEY;
}
nextNetworkKey = INVALID_KEY; // <-- probably there is a network error / sth like that. So no need to execute further network call. thus pass invalid key
val nextKey: Pair<Long, Long> = Pair(nextNetworkKey, nextDBKey);
pagingLibraryCallBack.onResult(pagedProfilePosts, prevNetworkKey, nextKey);
}
setLoaded();
}
}
override fun loadBefore(params: LoadParams<Pair<Long, Long>>, pagingLibraryCallBack: LoadCallback<Pair<Long, Long>, ProfilePost>) {} // we dont need it in feedProflie
override fun loadAfter(params: LoadParams<Pair<Long, Long>>, pagingLibraryCallBack: LoadCallback<Pair<Long, Long>, ProfilePost>) {
val currentNetworkKey: Long = params.key.first;
var nextNetworkKey = currentNetworkKey; // assuming invalid key
if(nextNetworkKey!= INVALID_KEY) {
nextNetworkKey = currentNetworkKey + 1;
}
val currentDBKey: Long = params.key.second;
var nextDBKey: Long = 0;
if(currentDBKey!= INVALID_KEY || currentNetworkKey!= INVALID_KEY) {
GlobalScope.launch(Dispatchers.IO) {
val pagedProfilePosts: ArrayList<ProfilePost> = ArrayList(); // cz kotlin emptyList() sometimes gives a weird error. So use arraylist and be happy
val authorization : String = AuthManager.getInstance(context).authenticationToken;
try{
setLoading();
if(currentNetworkKey!= INVALID_KEY) {
val res: Response<ProfileServerResponse> = getAPIService().getFeedProfile(
sessionToken = authorization, id = userId, withProfile = false, withPosts = true, page = currentNetworkKey.toInt()
);
if(res.isSuccessful && res.body()!=null) {
pagedProfilePosts.addAll(res.body()!!.posts);
}
}
}catch (x: Exception) {
Log.e(TAG, "Exception -> "+x.message);
}
if(pagedProfilePosts.isNotEmpty()) {
// this means network call is successfull
Log.e(TAG, "key -> "+currentNetworkKey+" size -> "+pagedProfilePosts.size+" "+pagedProfilePosts.toString());
nextDBKey = pagedProfilePosts.last().id;
val nextKey: Pair<Long, Long> = Pair(nextNetworkKey, nextDBKey);
pagingLibraryCallBack.onResult(pagedProfilePosts, nextKey);
setLoaded();
// <-- this is paging library's callback to a pipeline that updates data which inturn updates the recyclerView. There is a line: adapter.submitPost(list) in FeedProfileFragment. this callback is related to that line...
profilePostLocalData.insertProfilePosts(pagedProfilePosts, userId); // insert the latest data in db
}else{
// fetch data from cache
// val cachedList: List<ProfilePost> = profilePostLocalData.getProfilePosts(userId);
val cachedList: List<ProfilePost> = profilePostLocalData.getPagedProfilePosts(userId, nextDBKey, 20);
pagedProfilePosts.addAll(cachedList);
if(pagedProfilePosts.size>0) {
nextDBKey = cachedList.last().id;
}else{
nextDBKey = INVALID_KEY;
}
nextNetworkKey = INVALID_KEY; // <-- probably there is a network error / sth like that. So no need to execute further network call. thus pass invalid key
val nextKey: Pair<Long, Long> = Pair(nextNetworkKey, nextDBKey);
pagingLibraryCallBack.onResult(pagedProfilePosts, nextKey);
setLoaded();
}
}
}
}
private suspend fun setLoading() {
withContext(Dispatchers.Main) {
liveLoaderState.value = NetworkState.LOADING;
}
}
private suspend fun setLoaded() {
withContext(Dispatchers.Main) {
liveLoaderState.value = NetworkState.LOADED;
}
}
}
Thank you for reading this far. If you have a better solution, feel free to let me know. I'm open to any working solutions.
I've created a role and and a user in Keycloak and added one attribute in both of them; for example:
my_role_attr = 'x'
my_user_attr = 'y'
Then I'm trying to access that info. via the JAVA KeycloakSecurityContext (and the associated AccessToken / IDToken). I can see the name of the roles and the user information but I cannot find those attributes.
I understand that I've to creat a Protocol Mapper "User Attribute" to map the "my_user_attr" into a Token Claim. Then it's fine I can get the value in the AccessToken / IDToken.
But I cannot find a way to get the role attribute. I do not see any "Protocol Mapper" for role attribute. Am I missing something.
I have been trying to do the same thing; and it is not easy, however you can use the script mapper with some success. The source code at https://github.com/keycloak/keycloak/blob/master/server-spi/src/main/java/org/keycloak/models/RoleModel.java has some useful information. This SO item (thank you Andre B) also has useful information How to create a Script Mapper in Keycloak?.
My (not perfect or complete) solution is:
/**
* Available variables:
* user - the current user
* realm - the current realm
* token - the current token
* userSession - the current userSession
* keycloakSession - the current userSession
*/
var roles = [];
var client = keycloakSession.getContext().getClient();
user.getClientRoleMappings(client).forEach(function(roleModel) {
var attr = [];
var names = {};
var rn = roleModel.getName();
var map = roleModel.getAttributes();
map.forEach(function(key, value){
var k = {};
var a = false;
value.forEach(function(l){
a = (l === 'true');
});
k[key] = a;
attr.push(k);
});
names[rn] = attr;
roles.push(names);
});
exports = roles;
Note that you probably need to set 'Multivalued' to "ON" and the 'Claim JSON Type' to "Select One..".
No, you are not missing something. It is just not implemented.
However, there is a feature request. You may want to watch and vote up KEYCLOAK-17418.
A more generic answer should be
var roles = [];
var client = keycloakSession.getContext().getClient();
user.getRoleMappings().forEach(function(roleModel) {
var attr = {};
var names = {};
var rn = roleModel.getName();
var map = roleModel.getAttributes();
map.forEach(function(key, value){
attr[key] = value;
});
roles[rn] = attr;
});
exports = roles;
to export roles' attributes like
"Role1": {
"key1": [
"Values1"
],
"key2": [
"values2"
]
}
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
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()))
})