Best way to create a service that inserts a complex entity - java

Hope you are all healthy!
I have a situation in which I have a backend application that exposes a rest api to create data related to expense reporting. The main entity that this API allows to be created is an "Expense Report" which has other entities related to it, such as the country on which the expenses took place and the user that created it.
Thing is, my controller receives a DTO, converts it into a JPA entity and then sends it to the service class. In my service class, I have to check if a related entity field like the username for User or the country code for Country, and then go into the corresponding entity repository and get the corresponding entity.
public ExpenseReport save(ExpenseReport expenseReport) {
if (expenseReport.getId() != null) {
expenseReportRepository.findById(expenseReport.getId())
.ifPresent(currentObject -> {
expenseReport.setId(currentObject.getId());
expenseReport.setVersion(currentObject.getVersion());
});
}
if (expenseReport.getUser() != null && expenseReport.getUser().getUsername() != null) {
String username = expenseReport.getUser().getUsername();
userRepository.findByUsername(username)
.ifPresentOrElse(user -> {
expenseReport.setUser(user);
},
() -> {
throw new InvalidDataException(User.class, "username", username);
});
}
if (expenseReport.getCountry() != null && expenseReport.getCountry().getCode() != null) {
String countryCode = expenseReport.getCountry().getCode();
countryRepository.findByCode(countryCode)
.ifPresentOrElse(country -> {
expenseReport.setCountry(country);
},
() -> {
throw new InvalidDataException(Country.class, "countryCode", countryCode);
});
}
for (ExpenseItem expenseItem : expenseReport.getExpenses()) {
if (expenseItem.getCurrency() != null && expenseItem.getCurrency().getCode() != null) {
String currencyCode = expenseItem.getCurrency().getCode();
currencyRepository.findByCode(currencyCode)
.ifPresentOrElse(currency -> {
expenseItem.setCurrency(currency);
},
() -> {
throw new InvalidDataException(Currency.class, "currencyCode", currencyCode);
});
}
if (expenseItem.getExpenseCity() != null && expenseItem.getExpenseCity().getCode() != null) {
String expenseCityCode = expenseItem.getExpenseCity().getCode();
cityRepository.findByCode(expenseCityCode)
.ifPresentOrElse(city -> {
expenseItem.setExpenseCity(city);
},
() -> {
throw new InvalidDataException(City.class, "expenseCityCode", expenseCityCode);
});
}
if (expenseItem.getCategory() != null && expenseItem.getCategory().getCode() != null) {
String categoryCode = expenseItem.getCategory().getCode();
categoryRepository.findByCode(categoryCode)
.ifPresentOrElse(expenseCategory -> {
expenseItem.setCategory(expenseCategory);
},
() -> {
throw new InvalidDataException(ExpenseCategory.class, "expenseCategoryCode", categoryCode);
});
}
}
return expenseReportRepository.save(expenseReport);
}
My question is, is this the best way to do this? I feel that if the object gets too complex, I'll have to create this super huge save method.
Does JPA offer a better solution to this? I was thinking also to change the parameterized types (like country, city, state) to use the code itself as it's primary key, rather than an auto-generated id.
Regards.

Related

How to optimalize if else statemants with many specifications

I am trying to create dynamic search based on fields send in request body.
I prepared many Specifications and in "summary specification" (which is called in method) I want to call them if field is different than null.
It works but the problem is I will never know which parameter will start creating condition so I had to add boolean parameter which resulted in the creation of many if else statements.
Code:
public Specification<ShapeEntity> conditionalSearch(ShapeParams shapeParams) {
Specification spec = null;
boolean isFirstParam = true;
if (shapeParams.getType() != null) {
if (isFirstParam) {
spec = Specification.where(isTypeEqual(shapeParams.getType()));
isFirstParam = false;
} else {
spec = spec.and(isTypeEqual(shapeParams.getType()));
}
}
if (shapeParams.getWidthTo() != null) {
if (isFirstParam) {
spec = Specification.where(isWidthLessThan(shapeParams.getWidthTo()));
isFirstParam = false;
} else {
spec = spec.and(isWidthLessThan(shapeParams.getWidthTo()));
}
}
if (shapeParams.getWidthFrom() != null) {
if (isFirstParam) {
spec = Specification.where(isWidthGreaterThan(shapeParams.getWidthTo()));
isFirstParam = false;
} else {
spec = spec.and(isWidthGreaterThan(shapeParams.getWidthTo()));
}
}
return spec;
}
Is there any way to optimalize it? Specification has to always start with ".where" as first, and next I can add other conditions and I would like to have even 10+ params
You can write some methods that receive some values to validate and return boolean.
boolean checkType(CustomObject type){
return type == null;
}
You can check the use of Optional, it maybe helps with some if blocks.
Optional.ofNullable(type).ifPresent(t -> /*some operations*/);
You can check if you can merge some conditions.
if (shapeParams.getType() != null && isFirstParam) {
//do something....
} else {
//do other....
}

FieldMask update method implementation

I'm implementing an API that uses an update_mask of type Field Mask to specify the fields that should be updated on a resource.
The API is similar to:
rpc UpdateBook(UpdateBookRequest) returns (Book)
message UpdateBookRequest {
Book book=1;
google.protobuf.FieldMask update_mask = 2;
}
Currently the code looks something like:
override suspend fun updateBook(request: UpdateBookRequest): Book {
if (!FieldMaskUtil.isValid(Book::class.java, request.updateMask)) {
throwStatusRuntimeException {
code = Code.INVALID_ARGUMENT_VALUE
message = "Invalid field mask"
}
}
request.book.let { book ->
request.updateMask.pathsList.let { updateMask ->
var displayName: BookDisplayNameValue? = null
var description: BookDescriptionValue? = null
if (updateMask.contains(BookFields.DISPLAY_NAME_FIELD)) {
displayName = BookDisplayNameValue(book.displayName)
}
if (updateMask.contains(BookFields.DESCRIPTION_FIELD)) {
description = BookDescriptionValue(book.description.ifBlank { null })
}
bookService.update(
bookId = BookIdValue(book.name),
displayName = displayName,
description = description,
)
}
}
//...
}
Which, although it works, is pretty horrendous for maintenance as more fields are added.
Are there any best practices as to how to implement this behaviour on the server (Kotlin + Spring framework + GRPC) that can reduce the amount of boilerplate.

Firebase Dynamic Link Not getting Query parameter like Utm Source when using custom Domain

Here I am not getting GoogleAnalyticsParameters like soource ,medium when i am using custom domain
FirebaseDynamicLinks.getInstance().getDynamicLink(intent).addOnSuccessListener {
if (it != null) {
deepLink = it.link
}
}.addOnCompleteListener {
callCampaignApi(deepLink)
if (!appUtils.readStringFromPref(Constant.KEY_TOKEN).isNullOrBlank() && deepLink != null) {
try {
//means user is allready logged in
//source is used for screenname
var source = deepLink!!.getQueryParameter("utm_source")
// campaign is used for additional data like agentId or feedId
var campaign = deepLink!!.getQueryParameter("utm_campaign")
// utm _medium to track external sources
var medium = deepLink!!.getQueryParameter("utm_medium")
handleDynamicLinks(this, source ?: "", campaign ?: "", medium ?: "", deepLink!!)
this.finish()
} catch (ex: Exception) {
Log.e("DEEPLINK EXCEPTIONS", ex.message)
mDelayHandler!!.postDelayed(mRunnable, SPLASH_DELAY)
}
} else {
//take him to login or Registration with deeplink
if (deepLink != null) {
var source = deepLink!!.getQueryParameter("utm_source")
var campaign = deepLink!!.getQueryParameter("utm_campaign")
if (source?.equals(Constant.SCREEN_SEND_INVITATION)!! && campaign != null) {
//it means it has cp code /// send that value of cpcode to registration screen
launchLoginActivity(campaign)
} else {
//regular flow
mDelayHandler!!.postDelayed(mRunnable, SPLASH_DELAY)
}
} else {
//regular flow
mDelayHandler!!.postDelayed(mRunnable, SPLASH_DELAY)
}
}
}
Expected Result is utm source,campaign gets appended in link automatically

Patching an entity and getting duplicate items

I'm having problems with editing an entity. I have ObjectSpecifications that have different conditions. The problem I have has to do with the DimensionalConditions.
Whenever I edit an ObjectSpecification which has let's say "length: 1", and I try to add "width: 2" to it, the conditions end up being "length: 1, length: 1, width: 2".
Long story short, the conditions that were present before, get inserted again for some reason.
public ObjectSpecification modifySpecification(Long id, ObjectSpecificationRequest request) {
ObjectSpecification objectSpecification = this.getObjectSpecificationById(id);
return this.setSpecData(request, objectSpecification);
}
public ObjectSpecification createSpecification(ObjectSpecificationRequest request){
return this.setSpecData(request, new ObjectSpecification());
}
public void deleteSpecification(Long id) {
objectSpecificationRepository.delete(getObjectSpecificationById(id));
}
public ObjectSpecification setSpecData(ObjectSpecificationRequest request, ObjectSpecification objectSpecification) {
if (request.getName() != null) {
objectSpecification.setName(request.getName());
}
if (request.getDimensionalConditions() != null) {
List<DimensionalCondition> dimensionalConditions = new ArrayList<>();
for (DimensionalCondition condition:request.getDimensionalConditions()) {
DimensionalCondition dimensionalCondition = new DimensionalCondition();
dimensionalCondition.setConditionType(condition.getConditionType());
dimensionalCondition.setValue(condition.getValue());
dimensionalCondition.setObjectSpecification(objectSpecification);
dimensionalConditions.add(dimensionalCondition);
}
objectSpecification.setDimensionalConditions(dimensionalConditions);
}
if (request.getMechanisms() != null) {
objectSpecification.setMechanismConditions(request.getMechanisms());
}
if (request.getServices() != null) {
objectSpecification.setServiceConditions(request.getServices());
}
if (request.getWorkDetails() != null) {
List<WorkDetail> workDetails = new ArrayList<>();
for (WorkDetail workDetail:request.getWorkDetails()) {
workDetails.add(workDetailService.getWorkDetailById(workDetail.getId()));
}
objectSpecification.setWorkDetailConditions(workDetails);
}
return objectSpecificationRepository.save(objectSpecification);
}
1) This is a big update list there, make sure you merge your ObjectSpecification so that it is aligned with the PersistenceContext before you do any amendments.
2) You do not take under consideration the existing DimensionalCondition that are linked to ObjectSpecification. You always create a new one:
List<DimensionalCondition> dimensionalConditions = new ArrayList<>();
3) Make sure you have your cascading configured to {PERSIST, MERGE} at least on #OneToMany List<DimensionalCondition>.

How to sort the data according to the highest accuracy of results in elasticsearch using spring data

Hey I need to set in default searching elasticsearch sorting by highest accuracy of results how implement it by using spring data elasticsearch, spring data boot ?
Here is example code where I would like to add this:
boolean issetPriceFrom = Optional.ofNullable(searchParams.getPriceFrom()).isPresent();
boolean issetPriceTo = Optional.ofNullable(searchParams.getPriceTo()).isPresent();
final List<FilterBuilder> filters = Lists.newArrayList();
final NativeSearchQueryBuilder searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery());
Optional.ofNullable(searchParams.getBuyer()).ifPresent(v -> filters.add(boolFilter().must(termFilter("buyer", v))));
Optional.ofNullable(searchParams.getCat()).ifPresent(v -> filters.add(boolFilter().must(termFilter("cat", v))));
Optional.ofNullable(searchParams.getComment_type()).ifPresent(v -> filters.add(boolFilter().must(termFilter("comment_type", v))));
Optional.ofNullable(searchParams.getItem()).ifPresent(v -> filters.add(boolFilter().must(termFilter("item", v))));
Optional.ofNullable(searchParams.getSeller()).ifPresent(v -> filters.add(boolFilter().must(termFilter("seller", v))));
Optional.ofNullable(searchParams.getTree_cat()).ifPresent(v -> filters.add(boolFilter().must(termFilter("tree_cat", v))));
final BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//access for many sellers
if (searchParams.getSellers() != null) {
// Optional.ofNullable(searchParams.getSellers().split(",")).ifPresent(v -> {
// filters.add(boolFilter().must(termsFilter("seller", v)));
// });
for (String user : searchParams.getSellers().split(",")) {
boolQueryBuilder.must(queryStringQuery(user).field("seller"));
}
}
//access for many categories
if (searchParams.getCats() != null) {
// Optional.ofNullable(searchParams.getCats().split(",")).ifPresent(v -> {
// filters.add(boolFilter().must(termsFilter("cat", v)));
// });
for (String category : searchParams.getCats().split(",")) {
boolQueryBuilder.must(queryStringQuery(category).field("cat"));
}
}
if (Optional.ofNullable(searchParams.getTitle()).isPresent()) {
boolQueryBuilder.must(queryStringQuery(searchParams.getTitle()).analyzeWildcard(true).field("title"));
}
if (Optional.ofNullable(searchParams.getComment_text()).isPresent()) {
boolQueryBuilder.must(queryStringQuery(searchParams.getComment_text()).analyzeWildcard(true).field("comment_text"));
}
if (issetPriceFrom || issetPriceTo) {
filters.add(rangeFilter("price").from(searchParams.getPriceFrom()).to(searchParams.getPriceTo()));
}
if (Optional.ofNullable(searchParams.getTsFrom()).isPresent()
|| Optional.ofNullable(searchParams.getTsTo()).isPresent()) {
filters.add(rangeFilter("ts").from(searchParams.getTsFrom()).to(searchParams.getTsTo()));
}
if (hasComments != null && hasComments.equals(true)) {
filters.add(FilterBuilders.boolFilter().must(FilterBuilders.existsFilter("comment_text")));
}
if (hasComments != null && hasComments.equals(false)) {
filters.add(FilterBuilders.boolFilter().must(FilterBuilders.missingFilter("comment_text")));
}
searchQuery.withQuery(boolQueryBuilder);
FilterBuilder[] filterArr = new FilterBuilder[filters.size()];
filterArr = filters.toArray(filterArr);
searchQuery.withFilter(andFilter(filterArr));
if (pageable != null) {
searchQuery.withPageable(pageable);
}
return searchQuery;
I would be grateful if I got even a piece of code that could implement.
Thanks!
You just need to use withSort method.Use it like this:
SortBuilder sortBuilder = SortBuilders.fieldSort(fieldName).order(SortOrder.ASC)
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
.withSort(sortBuilder)
Hope this helps..

Categories

Resources