Guava Cache: How to handle null values - java

I've build a cache that returns a value in list format when you enter the parameters. If that value is not in the cache, it goes to the database and retrieves it, putting it in the cache for future reference:
private ProfileDAO profileDAO;
private String[] temp;
private LoadingCache<String, List<Profile>> loadingCache = CacheBuilder.newBuilder()
.refreshAfterWrite(5, TimeUnit.MINUTES)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(
new CacheLoader<String, List<Profile>>() {
#Override
public List<Profile> load(String key) throws Exception {
logger.info("Running method to retrieve from database");
temp = key.split("\\|");
String instance = temp[0];
String name = temp[1];
List<Profile> profiles= profileDAO.getProfileByFields(id, name);
if (profiles.isEmpty()) {
List<Profile> nullValue = new ArrayList<Profile>();
logger.info("Unable to find a value.");
return nullValue;
}
logger.info("Found a value");
return profileDAO.getProfileByFields(id, name);
}
}
);
public List<Profile> getProfileByFields(String id, String name) throws Exception {
String key = id.toLowerCase() + "|" + name.toLowerCase()
return loadingCache.get(key);
}
This seems to work fine, but it does not take into account null values. If I look for an entry that does not exist, I get an exception for :
com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key A01|Peter
I'd like to simply return an empty List(Profile) if there is no match in the database, but my if statement has failed. Is there any way around this error for this particular use case?

Though this feels a bit hacky, I think it's a more complete solution (Suresh's answer only really applies to collections).
Define a singleton object that will represent null, and insert that value into the cache instead of null (converting to null at retrieval time):
class MyDAO
{
static final Object NULL = new Object();
LoadingCache<String,Object> cache = CacheBuilder.newBuilder()
.build( new CacheLoader<>()
{
public Object load( String key )
{
Object value = database.get( key );
if( value == null )
return NULL;
return value;
}
});
Object get( String key )
{
Object value = cache.get( key );
if( value == NULL ) // use '==' to compare object references
return null;
return value;
}
}
I believe this approach is preferable, in terms of efficiency, to any involving the use of exceptions.

Using Optional class Optional<Object> as the cache value is the easiest and cleanest way to do it.

Make changes in your code to check first profiles is null or not as(using profiles == null ...) :
private ProfileDAO profileDAO;
private String[] temp;
private LoadingCache<String, List<Profile>> loadingCache = CacheBuilder.newBuilder()
.refreshAfterWrite(5, TimeUnit.MINUTES)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(
new CacheLoader<String, List<Profile>>() {
#Override
public List<Profile> load(String key) throws Exception {
logger.info("Running method to retrieve from database");
temp = key.split("\\|");
String instance = temp[0];
String name = temp[1];
List<Profile> profiles= profileDAO.getProfileByFields(id, name);
if (profiles == null || profiles.isEmpty()) {
List<Profile> nullValue = new ArrayList<Profile>();
logger.info("Unable to find a value.");
return nullValue;
}
logger.info("Found a value");
return profileDAO.getProfileByFields(id, name);
}
}
);
public List<Profile> getProfileByFields(String id, String name) throws Exception {
String key = id.toLowerCase() + "|" + name.toLowerCase()
return loadingCache.get(key);
}
Please check this code is working for you null values or not..

Related

JPA many to many constraint violation exception

I am using JPA to store data and faced two problems during implementation. I have two entities (Station and Commodity) that have many-to-many relationship with intermediate table so that I had to created the third one. When app receives message it converts its data to entites and should save but sometimes app throwing a ConstraintViolationException because there is null value at foreign key field referencing to Commodity entity.
I've tried simple approach: selecting needed commodity from database and saving it if there is no one. Then I started to use bulk searching all commodities of message and then putting it where are needed. None of them did a trick.
In my opinion the problem could be caused by multi-threading read\insert.
The second problem is that service stop running when exception is thrown. App can lost some of transactions that's not a big deal but it simply stops after rollback.
How can I resolve these conflicts?
Here is code of data handling class and diagram of entities :
#Service
#AllArgsConstructor
#Slf4j
public class ZeromqCommoditiesServiceImpl implements ZeromqCommoditesService {
private final CategoryTransactionHandler categoryHandler;
private final CommodityTransactionHandler commodityHandler;
private final EconomyTransactionHandler economyHandler;
private final StationTransactionHandler stationHandler;
private final SystemTransactionHandler systemHandler;
#Override
#Transactional(
isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRES_NEW,
rollbackFor = Throwable.class)
#Modifying
public void saveData(ZeromqCommodityPayload payload) {
CommodityContent content = payload.getContent();
var station = stationHandler.createOrFindStation(content.getStationName());
var system = systemHandler.createOrFindSystem(content.getSystemName());
var commodityReferences = getMapOfCommodities(content);
station.setSystem(system);
updateEconomies(station, content);
updateProhibited(station, content, commodityReferences);
updateStationCommodities(station, content, commodityReferences);
try {
saveStation(station);
} catch (ConstraintViolationException | PersistentObjectException | DataAccessException e) {
log.error("Error saving commodity info \n" + content, e);
}
}
public void saveStation(StationEntity station) {
stationHandler.saveStation(station);
if (station.getId() != null) {
log.debug(String.format("Updated \"%s\" station info", station.getName()));
} else {
log.debug(String.format("Updated \"%s\" station info", station.getName()));
}
}
private void updateEconomies(StationEntity station, CommodityContent content) {
station.getEconomies().clear();
if (content.getEconomies() != null) {
var economies = content.getEconomies()
.stream()
.map(economy -> {
var stationEconomyEntity = economyHandler.createOrFindEconomy(economy.getName());
Double proportion = economy.getProportion();
stationEconomyEntity.setProportion(proportion != null ? proportion : 1.0);
return stationEconomyEntity;
})
.peek(economy -> economy.setStation(station))
.toList();
station.getEconomies().addAll(economies);
}
}
private void updateProhibited(
StationEntity station,
CommodityContent content,
Map<String, CommodityEntity> commodityEntityMap) {
station.getProhibited().clear();
if (content.getProhibited() != null) {
var prohibitedCommodityEntities = content.getProhibited()
.stream()
.map(prohibited -> {
String eddnName = prohibited.toLowerCase(Locale.ROOT);
CommodityEntity commodityReference = getCommodityEntity(commodityEntityMap, eddnName);
return new ProhibitedCommodityEntity(station, commodityReference);
}
)
.toList();
station.getProhibited().addAll(prohibitedCommodityEntities);
}
}
private void updateStationCommodities(
StationEntity station,
CommodityContent content,
Map<String, CommodityEntity> commodityEntityMap) {
station.getCommodities().clear();
if (content.getCommodities() != null) {
var commodities = content.getCommodities()
.stream()
.map(commodity -> {
CommodityEntity commodityReference = getCommodityEntity(
commodityEntityMap,
commodity.getEddnName());
return StationCommodityEntity.builder()
.commodity(commodityReference)
.buyPrice(commodity.getBuyPrice())
.sellPrice(commodity.getSellPrice())
.demand(commodity.getDemand())
.stock(commodity.getStock())
.station(station)
.build();
})
.toList();
station.getCommodities().addAll(commodities);
}
}
private CommodityEntity getCommodityEntity(Map<String, CommodityEntity> commodityEntityMap, String eddnName) {
return commodityEntityMap.get(eddnName);
}
private Map<String, CommodityEntity> getMapOfCommodities(#NotNull CommodityContent content) {
Set<String> commodities = content.getCommodities()
.stream()
.map(Commodity::getEddnName)
.collect(Collectors.toSet());
if (content.getProhibited() != null && content.getProhibited().size() > 0) {
commodities.addAll(content.getProhibited().
stream()
.map(item -> item.toLowerCase(Locale.ROOT))
.collect(Collectors.toSet()));
}
var commodityReferencesMap = commodityHandler.findAllByEddnName(commodities)
.stream()
.collect(Collectors.toMap(
CommodityEntity::getEddnName,
item -> item
));
commodities.forEach(commodity -> {
if (commodityReferencesMap.get(commodity.toLowerCase()) == null) {
CommodityCategoryEntity category = categoryHandler.createOrFindCategory("Unknown");
CommodityEntity newCommodity = new CommodityEntity(commodity, commodity, category);
CommodityEntity managedCommodity = commodityHandler.saveCommodity(newCommodity);
commodityReferencesMap.put(managedCommodity.getEddnName(), managedCommodity);
}
});
return commodityReferencesMap;
}
}
Thanks in advance

Create new Object to replace values in an immutable Object

I have immutable objects as follows.
#Getter
#Builder
class MainDetail {
// 5 other String fields
private Data data;
}
#Getter
#Builder
class ImageUrl {
private final String dataOne; // looking to change these 2 values
private final String dataTwo; // if rest call returns null for these.
}
Information to fill these up is fetched from a rest call, working fine as follows.
List<MainDetail> featureBenefits = // value from a rest response
I wish to switch out the dataOne and dataTwo values in here if it is null for each MainDetail Object.
I can't just use a set method to do this cos it is immutable.
I end up with the following verbose way of doing it where I need to do multiple variations of the check to swap values.
I can't just check one at a time and switch cos Object becomes immutable. Can't add another if the second one is null too after that.
Is there a way to do this more elegantly, possibly via streams? Appreciate any help. Thanks.
List<MainDetail> mainDetails = new ArrayList<>();
for (MainDetail mainDetail : featureBenefits) {
if (mainDetail.getImageUrl().getDataOne() == null && mainDetail.getImageUrl().getdataTwo() == null) {
ImageUrl imageUrl = ImageUrl.builder()
.dataOne("default1")
.dataTwo("default12")
.build();
MainDetail detail = MainDetail.builder()
.imageUrl(imageUrl)
.build();
mainDetails.add(detail);
}
else if (mainDetail.getImageUrl().getdataOne() == null) {
ImageUrl imageUrl = ImageUrl.builder()
.dataOne("default1")
.build();
MainDetail detail = MainDetail.builder()
.imageUrl(imageUrl)
.build();
mainDetails.add(detail);
}
else if (mainDetail.getImageUrl().getDataTwo() == null) {
ImageUrl imageUrl = ImageUrl.builder()
.dataTwo("default2")
.build();
MainDetail detail = MainDetail.builder()
.imageUrl(imageUrl)
.build();
mainDetails.add(detail);
}
}
What about this one:
List<MainDetail> featureBenefits = Collections.emptyList();
List<MainDetail> mainDetails = new ArrayList<>();
for (MainDetail mainDetail : featureBenefits) {
ImageUrl imageUrl = mainDetail.getImageUrl();
mainDetails.add(MainDetail.builder()
.imageUrl(ImageUrl.builder()
.dataOne(Optional.ofNullable(imageUrl.getDataOne()).orElse("default1"))
.dataTwo(Optional.ofNullable(imageUrl.getDataTwo()).orElse("default2"))
.build())
.build());
}
If you are not limited to sticking with standard builders then you could add your own methods for providing default values:
class ImageUrl {
private final String dataOne; // looking to change these 2 values
private final String dataTwo; // if rest call returns null for these.
public ImageUrl withDefaultDataOne(String value) {
return dataOne == null ? new ImageUrl(value, dataTwo) : this;
}
public ImageUrl withDefaultDataTwo(String value) {
return dataTwo == null ? new ImageUrl(dataOne, value) : this;
}
}
Then your translation code becomes:
for (MainDetail mainDetail : featureBenefits) {
ImageUrl imageUrl = mainDetail.getImageUrl()
.withDefaultDataOne("default1")
.withDefaultDataTwo("default2");
mainDetails.add(MainDetail.builder().imageUrl(imageUrl).build());
}

Inserting into embedded list of orient db through java

This is the sql script that I have used to create the necessary classes :
CREATE CLASS ProductSummary;
CREATE PROPERTY ProductSummary.name STRING (NOTNULL, MANDATORY TRUE);
CREATE PROPERTY ProductSummary.modelNumber LONG (NOTNULL, MANDATORY TRUE);
ALTER CLASS ProductSummary STRICTMODE TRUE;
CREATE CLASS PricingSummary;
CREATE PROPERTY PricingSummary.price LONG (NOTNULL, MANDATORY TRUE);
CREATE PROPERTY PricingSummary.discount LONG (NOTNULL, MANDATORY TRUE);
ALTER CLASS PricingSummary STRICTMODE TRUE;
CREATE CLASS TotalSummary EXTENDS V;
CREATE PROPERTY TotalSummary.projectLink LINK Project (NOTNULL, MANDATORY TRUE);
CREATE PROPERTY TotalSummary.productSummaries EMBEDDEDLIST ProductSummary;
CREATE PROPERTY TotalSummary.pricingSummaries EMBEDDEDLIST PricingSummary;
ALTER CLASS TotalSummary STRICTMODE TRUE;
CREATE INDEX TotalSummary_projectLink_idx ON TotalSummary (projectLink) UNIQUE;
I am trying to insert some values into my TotalSummary class, where I also need to insert some values into the EmbeddedList for pricingSummaries and productSummaries.
public TotalSummary create(final TotalSummary totalSummary) {
final Long projectId = 1;
final StatementBuilder builder = new StatementBuilder();
final StringBuilder query = new StringBuilder();
final List<Map<?, ?>> productSummaries = totalSummary.getProductSummaries().stream()
.map(ProductSummary::toMap)
.collect(Collectors.toList());
final List<Map<?, ?>> pricingSummaries = totalSummary.getPricingSummaries().stream()
.map(PricingSummary::toMap)
.collect(Collectors.toList());
builder.addAttribute("projectLink = (SELECT FROM project WHERE id = ?)", projectId);
if ( ! productSummaries.isEmpty()) {
builder.addAttribute("productSummaries = ?", productSummaries);
}
if ( ! pricingSummaries.isEmpty()) {
builder.addAttribute("pricingSummaries = ?", pricingSummaries);
}
try {
insert(TotalSummary.class.getSimpleName(), builder.attributes(), statement -> {
builder.init(statement);
return statement;
});
} catch (final UncategorizedSQLException e) {
throw new ConstraintViolationException(totalSummary, ExceptionUtils.getRootCauseMessage(e), e);
}
return assertNotNull(findById(projectId));
}
This is the utility method that I am using to build the insert query :
protected String insert(final String vertex, final String fieldValuePairs, final PreparedStatementInitializer initializer) {
final String sql = "INSERT INTO " + vertex + " SET " + fieldValuePairs + " RETURN #rid";
return executeStatement(sql, initializer);
}
The toMap methods to convert the List<ProductSummary> to List<Map<?,?>>
**ProductSummary**
public Map<String, Object> toMap() {
final Map<String, Object> ret = new HashMap<>();
ret.put("name", name);
ret.put("model number", Long.valueOf(modelNumber));
return ret;
}
The toMap methods to convert the List<PricingSummary> to List<Map<?,?>>
**PricingSummary**
public Map<String, Object> toMap() {
final Map<String, Object> ret = new HashMap<>();
ret.put("price", Long.valueOf(price));
ret.put("discount", Long.valueOf(discount));
return ret;
}
I am getting the following exception when I execute the code
Constraint violation for: TotalSummary#58f79eeb[recordId=<null>,customProperties=[]]. Reason: OValidationException: The field 'TotalSummary.productSummaries' has been declared as EMBEDDEDLIST but an incompatible type is used.
Things that I have already tried :
I have tried to covert to list to json format before adding it to
the builder like so new Gson().toJson(pricingSummaries);
Converted the pricingSummaries and productSummaries toArray().
You defined the summaries as EMBEDDEDLIST ProductSummary but you are passing lists of maps as values.
Try to create real ProductSummary objects instead eg.
OElement ret = db.newEmbeddedElement("ProductSummary");
ret.setProperty("price", Long.valueOf(price));
ret.setProperty("discount", Long.valueOf(discount));

is it possible to return "if condition satisfies return a list else return an error message" using a java method

I know that in Java a method can return only one return type... But if there is any possiblity to this, kindly let me know. From the below method I am trying to return a list if condition satisfies else i am trying to return an error message.
Here is my code:
#RequestMapping(value = "/getcompanies", method = RequestMethod.POST)
public List<CompanyMaster> getCompanies(#RequestBody UserDetails user) {
String OrgLoginId = user.getOrgLoginId();
String password = user.getuPassword();
String checkLoginId = null;
String uPassword = null;
String encPassword = null;
String loginId = null;
String checkAuthorized = null;
// String loginId=userService.getLoginId(OrgLoginId);
List<Object[]> CheckIdPassword = userService.checkLoginId(OrgLoginId);
List<Object[]> results = CheckIdPassword;
for (Object[] obj : results) {
checkLoginId = obj[0].toString();
if (null == obj[1]) {
uPassword = "";
} else {
uPassword = obj[1].toString();
}
loginId = obj[2].toString();
}
checkAuthorized = loginId.substring(0, 3);
if (null != password) {
MD5 md5 = new MD5();
encPassword = md5.getPassword(password);
}
if (checkLoginId == null) {
return "Incorrect loginId..Please enter valid loginId";
} else if (encPassword.equals(uPassword)) {
if (checkAuthorized.equals("STE")) {
List<CompanyMaster> companyList = userService.getCompanyList(OrgLoginId);
return companyList;
} else {
return "You are not Authorized";
}
} else {
return "Incorrect Password";
}
Yes its possible, create a custom Exception say 'MyAppException' and throw that exception with the error message you want.
Write your logic in a try{}catch block and throw the exception in catch so that the response has the error message
public List<CompanyMaster> getCompanies(#RequestBody UserDetails user) throws MyAppppException
{
try
{
//your logic which throws error
return companyList;
}
catch( final MyAppException we )
{
throw new MyAppException("User not found", HttpStatus.NOT_FOUND);
}
}
Refer this link
https://www.codejava.net/java-core/exception/how-to-create-custom-exceptions-in-java
You can achieve this by creating a new presenter Class which contains List and status of type String and change the return type of getCompanies method to presenter class like
public CompaniesPresenter getCompanies()
And your CompaniesPresenter class should look like
public class CompaniesPresenter {
private List<CompanyMaster> companyMaster;
private string status;
//default constructor
public CompaniesPresenter(){
}
//parameterized constructor to return only string in exception case
public CompaniesPresenter(Stirng status){
this.status = status;
}
//parametirized constructor to return success case
public CompaniesPresenter(List<CompanyMaster> companyMaster, Stirng status){
this.companyMaster = companyMaster;
this.status = status;
}
//getters and setters
}
This is how your updated method lokks like
#RequestMapping(value = "/getcompanies", method = RequestMethod.POST)
public CompaniesPresenter getCompanies(#RequestBody UserDetails user) {
String OrgLoginId = user.getOrgLoginId();
String password = user.getuPassword();
String checkLoginId = null;
String uPassword = null;
String encPassword = null;
String loginId = null;
String checkAuthorized = null;
// String loginId=userService.getLoginId(OrgLoginId);
List<Object[]> CheckIdPassword = userService.checkLoginId(OrgLoginId);
List<Object[]> results = CheckIdPassword;
for (Object[] obj : results) {
checkLoginId = obj[0].toString();
if (null == obj[1]) {
uPassword = "";
} else {
uPassword = obj[1].toString();
}
loginId = obj[2].toString();
}
checkAuthorized = loginId.substring(0, 3);
if (null != password) {
MD5 md5 = new MD5();
encPassword = md5.getPassword(password);
}
if (checkLoginId == null) {
return new CompaniesPresenter("Incorrect loginId..Please enter valid loginId");
} else if (encPassword.equals(uPassword)) {
if (checkAuthorized.equals("STE")) {
List<CompanyMaster> companyList = userService.getCompanyList(OrgLoginId);
return new CompaniesPresenter(companyList,"success");
} else {
return new CompaniesPresenter("You are not Authorized");
}
} else {
return new CompaniesPresenter("Incorrect Password");
}
This is not tested please make sure for any compilation errors
vavr's Either class would be a good choice.
The usage of custom exception is most reasonable solution. However, creating custom exception for just one case is not ideal always.
Another solution is to return empty List from your method, check if the List is empty in your servlet (or wherever you are invoking this method from), and show error message there.
It seems like you want to return multiple error messages for different cases. In this case, custom exception is recommended solution. If you don't like custom exceptions, you can return List<Object> and populate error message as the first element in the list. In the place where this List is obtained, check if the first element is instanceOf String or CompanyMaster. Based on what it is, you can perform your operations. This is a weird but possible solution (only if you don't like custom exceptions).
You need to understand the problem first. You are mixing two things here, first authorization, does the user has correct privileges to get company details, second giving the company details itself. Let's understand the first problem when a user tries to access "/getcompanies" endpoint will you let him in if does not have access, in REST world your security model should take care of it. I would use spring security to achieve this. My recommendation would be to explore on "interceptor" and solve the problem of invalid user. This will make your other problem easy as your "/getcompanies" endpoint can focus only on getting the details and return it (SRP).

Unique Validation In Broadleaf

I am trying to add a unique validation on a field while adding a product on Broadleaf. Currently we have added a 'SKU' field while adding product from admin screen. I have used the following annotation to validate:
#AdminPresentationMergeOverride(name = "userSku", mergeEntries = #AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.VALIDATIONCONFIGURATIONS, validationConfigurations = {
#ValidationConfiguration(validationImplementation = "blUniqueValueValidator", configurationItems = {
#ConfigurationItem(itemName = "otherField", itemValue = "userSku") }) })
It works perfect when we try to add a new product.
But the problem is, If I try to update any product to change any field, it gives the same validation error
Looks like that doesn't work quite right, can you open an issue in https://github.com/BroadleafCommerce/Issues?
You can also write your own uniqueness validates that does not run into the same ID problem like so:
#Component
public class MyUniqueValueValidator implements PropertyValidator {
protected static final Log LOG = LogFactory.getLog(UniqueValueValidator.class);
#Override
public PropertyValidationResult validate(Entity entity,
Serializable instance,
Map<String, FieldMetadata> entityFieldMetadata,
Map<String, String> validationConfiguration,
BasicFieldMetadata propertyMetadata,
String propertyName,
String value) {
String instanceClassName = instance.getClass().getName();
DynamicEntityDao dynamicEntityDao = getDynamicEntityDao(instanceClassName);
List<Long> responseIds = dynamicEntityDao.readOtherEntitiesWithPropertyValue(instance, propertyName, value);
String message = validationConfiguration.get(ConfigurationItem.ERROR_MESSAGE);
if (message == null) {
message = entity.getType()[0] + " with this value for attribute " +
propertyName + " already exists. This attribute's value must be unique.";
}
boolean onlyInCurrentEntity = CollectionUtils.isEmpty(responseIds)
|| (responseIds.size() == 1 && responseIds.get(0).equals(getDynamicEntityDao(instanceClassName).getIdentifier(instance)));
return new PropertyValidationResult(onlyInCurrentEntity, message);
}
protected DynamicEntityDao getDynamicEntityDao(String className) {
return PersistenceManagerFactory.getPersistenceManager(className).getDynamicEntityDao();
}
}
And then use the validator by passing in the bean ID to the validationImplementation:
#AdminPresentationMergeOverride(name = "userSku", mergeEntries = #AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.VALIDATIONCONFIGURATIONS, validationConfigurations = {
#ValidationConfiguration(validationImplementation = "myUniqueValidator", configurationItems = {
#ConfigurationItem(itemName = "otherField", itemValue = "userSku") }) })

Categories