I have a Spring Boot project in Kotlin which uses a custom locale interceptor to know from which .properties file it load messages.
This works pretty fine. Also I have a custom annotation which is simple and has a default message value, as follow:
#Target(AnnotationTarget.Field)
#Constraint(validatedBy = [MyCustomValidator::class])
annotation class MyAnnotation(
val message: String = "{javax.validation.constraints.MyAnnotation.message}",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class MyCustomValidator : ConstraintValidator<MyAnnotation, String> {
override fun isValid(value: String, context: ConstraintValidatorContext) {
return true //Just to make it easy
}
}
The locale properties files contains the key MyAnnotation.value=This field is required and shows as the exception message.
The problem is when I want to add more validations and so, custom messages according to each condition. I read that I should disable the default constraint validator and add the messages, but it is not working.
For example, if I want to create a key at locale file as MyAnnotation.conditionOne, it still prints the message from MyAnnotation.value.
//The only thing that changes in MyAnnotation is that message: String = ""
//Assuming that ConditionOne and ConditionTwo has a isValid static method
class MyCustomValidator : ConstraintValidator<MyAnnotation, String> {
override fun isValid(value: String, context: ConstraintValidatorContext): Boolean {
context.disableDefaultConstraintViolation()
return if (!ConditionOne.isValid(value)) {
context
.buildConstraintViolationWithTemplate("{javax.validation.constraints.MyAnnotation.conditionOne}")
.addConstraintViolation()
false
}
else if (!ConditonTwo.isValid(value)) {
context
.buildConstraintViolationWithTemplate("{javax.validation.constraints.MyAnnotation.message}")
.addConstraintViolation()
false
}
else
true
}
}
Is this the right way to set the message?
As I can see on the example above, you added two constraint violation message but in the different if cases. For a getting several checks and violation messages from one validator you should have not placed return after each if cases, instead of this you can create a local boolean variable and set its value from each of if statement cases, and after all make return that variable.
Pay attention to a little thing: it is important for your validator to set a temporary boolean variable correctly, because if once your if was set into false that means ultimate return value should be false. Cuz there is a principle anyMatch(false) or allMatch(true)
Related
I am trying to use Spring LDAP's object-directory mapping to write an object to an LDAP server. The object is annotated with #Entity and several fields are annotated with #Attribute.
As long as all of the annotated fields are populated, everything works. But if the value of a field, say myattribute, is null or an empty string, the create and update methods of LdapTemplate throw errors. The server rejects the operation, with the complaint "Attribute value '' for attribute 'myattribute' is syntactically incorrect"
The LDAP schema permits 'myattribute' to be missing (it is a "may" attribute for the relevant objectclass), but if it is present, it is not permitted to be blank (it has Directory String syntax). I cannot change the schema.
Is there some way to get Spring LDAP to omit 'myattribute' when the corresponding POJO field is null or empty, rather than attempting to create the attribute with a blank value?
I have found a solution, which may not be the most elegant for my application, but it works. Instead of declaring the Java field to be type String, declare it to be type List. Then, in the setter, if the value is blank or null, I set the list length to zero instead of setting a single empty value.
#Entry( objectClasses={"myObject"} )
public class MyDataContainer {
#Attribute("myattribute")
private List<String> _myattribute = new ArrayList<String>(1);
public String getMyAttribute() {
if ( _myattribute.length() > 0 ) {
return _myattribute.get(0);
}
return null;
}
public void setMyAttribute( String value ) {
_myattribute.clear();
value = ( value == null ) ? "" : value.trim();
if ( ! "".equals( value ) ) {
_myattribute.add( value );
}
}
}
I am using Apache Commons Configuration library with PropertiesConfiguration.
My application loads the config file right after its started, like this:
public PropertiesConfiguration loadConfigFile(File configFile) throws ConfigurationNotFoundException {
try {
if (configFile != null && configFile.exists()) {
config.load(configFile);
config.setListDelimiter(';');
config.setAutoSave(true);
config.setReloadingStrategy(new FileChangedReloadingStrategy());
setConfigLoaded(true);
}
else {
throw new ConfigurationNotFoundException("Configuration file not found.");
}
} catch (ConfigurationException e) {
logger.warn(e.getMessage());
setDefaultConfigValues(config);
config.setFile(configFile);
}
return config;
}
My question is, how can I validate the configFile, so I can be sure that no property in that file is missing and later in my code I won't get a NullPointerException when trying to access the properties, e.g.:
PropertiesConfiguration config = loadConfig(configFile);
String rootDir = config.getString("paths.download"); // I want to be sure that this property exists right at the app start
I didn't found anything in the documentation or google, just something about XML validation.
The goal is to provide feedback to the user at program start that the configuration file is corrupted.
There is no build-in mechanism for properties-file?
What is a configuration object supposed to do if you pass in a key to one of its get methods that does not map to an existing property?
the default behavior as implemented in AbstractConfiguration is to return null if the return value is an object type.
For primitive types as return values returning null (or any other special value) is not possible, so in this case a NoSuchElementException is thrown
// This will return null if no property with key "NonExistingProperty" exists
String strValue = config.getString("NonExistingProperty");
// This will throw a NoSuchElementException exception if no property with
// key "NonExistingProperty" exists
long longValue = config.getLong("NonExistingProperty");
For object types like String, BigDecimal, or BigInteger this default behavior can be changed:
If the setThrowExceptionOnMissing() method is called with an argument of true, these methods will behave like their primitive counter parts and also throw an exception if the passed in property key cannot be resolved.
Situation is little tricky for Collection & array types as they will return empty collection or array.
Null value in second column (incoming csv file):->
input CSV: 10512,,
10513,12345,
impex:
INSERT_UPDATE Product;code[unique=true];vendors(code)[translator=ca.batch.converter.StiboSetDefaultVendorIfNullTranslator];...
code:
Extending de.hybris.platform.impex.jalo.translators.AbstractValueTranslator;
private final String defaultVendorCode = "000000";
#Override
public Object importValue(String valueExpr, final Item item)
throws JaloInvalidParameterException {
if (valueExpr == null || StringUtils.isEmpty(valueExpr)) {
LOG.debug("Current attribute value is null so inserting "
+ defaultVendorCode);
valueExpr = defaultVendorCode;
}
return valueExpr;
}
getting the same below error here also for the 12345 but final impex conveterd row has the number (impex row -> 10153;12345)
due to Argument mismatch trying to set value '000000' for attribute de.hybris.platform.jalo.product.Product.vendors (got java.lang.String, expected de.h
ybris.platform.jalo.product.Product).,
(impex row -> 10153;;)
You should try this:[allownull=true] attribute modifier;
Your impex should look like this:
INSERT_UPDATE Product;code[unique=true];vendors(code)[allownull=true]
Only Import
true / false
Default: false
If set to true, this modifier explicitly allows null values for the column values. If there is no business code that blocks null values, this modifier even allows null values in mandatory attributes, such as the catalogVersion attribute of the Media type, for example.
Example:
[allownull=true]
Tip
In the Service Layer mode, import may fail if allownull is set. Since hybris Commerce Suite version 5.1.1, import will switch dynamically to the legacy mode if it encounters this parameter. After processing a given line, the import will switch back to the SL mode.
I think the error message is quite clear on this:
(got java.lang.String, expected de.h ybris.platform.jalo.product.Product).,
For the translator you'd have to lookup the actual default vendor object instead of returning the default vendor code.
I think the easiest solution would be if you used a Decorator instead that then returns the code values of your "vendors" attribute.
You can find detailed instructions here:
https://wiki.hybris.com/display/release5/ImpEx+API#ImpExAPI-WritingOwnCellDecorator
but basically something like this:
public class MyDecorator implements CSVCellDecorator
{
public String decorate( int position, Map<Integer, String> srcLine )
{
// here add your custom logic to check and if applies return your default vendor code, otherwise return the given input value
//String parsedValue=srcLine.get(position);
//return parsedValue+"modified"; // some decoration stuff
}
}
Hope that helps a bit :)
I am using Spring Message source to load my property file.
String placeDetails = messageSource.getMessage(code,
null, new Locale(locale.toLowerCase()));
sample entry in property file:
BNA=Nashville:Nashville:USA
property file name:
placeDetails_locale.properties
The messagesource.getMessage method returns the assigned value ex If code "BNA"
String "placeDetails" will hold "Nashville:Nashville:USA".If the code not found in the property file it throws "No such message found exception".But I need to handle this situation like If no message found in that scenario I have to set the default value to placeDetails.
Approach I tried:
1.I need to check first If the code is available in that property file then only i need to call get messagesource method.But I am unaware on how to check the value presence through Message source
2.I need to define the default value look up in the catch block.
I tried second but not working as expected.Help me on how to check the availability of code in property file through Message source(first appraoch)
Use below to return a default message in case you don't one. You don't have to handle exception cases.
getMessage(String code, Object[] args, String defaultMessage, Locale locale)
There are two ways you can specify the default values for localized messages.
The preferred one is to provide a placeDetails.properties as well, that works as a catch all in case a given message key isn't found in placeDetails_locale.properties.
The second one (a quick fix) is to use the following overloaded getMessage() method
String placeDetails = messageSource.getMessage(code, null,
"placeDetails", new Locale(locale.toLowerCase()));
Notice, how the third argument specifies the default value in case the key lookup fails.
Unfortunately MessageSource seems not to have a containsMessageKey(String) method or similar and in addition to that a null value as defaultMessage is getting ignored - in such case the messageKey will be returned.
I tried to solve that issue with the following approach:
#Service
#Slf4j
public class MessageService {
#Autowired
private MessageSource messageSource;
private static final String messageKeyNotFound = "messageKey-not-found";
public String getMessage(MessageKey messageKey, Locale locale) {
String i18nMessage = messageSource.getMessage(messageKey.name(), null, messageKeyNotFound, locale);
if (i18nMessage.equals(messageKeyNotFound)) {
log.error("missing translation for messageKey '"+messageKey.name()+"' and locale '" + locale.getLanguage()+"'");
return messageKey.name();
}
return i18nMessage;
}
}
While MessageKey is an Enum containing valid keys.
public enum MessageKey {
title,
name,
age;
}
With this solution the enum's and property's messageKeys need to have identical names
title=Title
name=Name
age=Age
You should write a unit test which iterates through all available keys and expected locales to catch any typos. But to be on the save side you will get error logs in case an unexpected locale comes in.
My principle is an instance of 'foo' that has a collection of 'bars'. Each 'bar' has an id that I want to match with the 'id' passed as the resource requested. Can I do something like the following in Spring SpEL? And if so, how?
Example (psuedo syntax; cause I don't know the correct way which is why I am here)
#PreAuthorize("principal.transactions.contains(instance where dto.transactionId == instance.id")")
public SomeResponse processTransaction(RequestDto dto) {
...
}
Essentially the equivalent of this
for(Transaction t : principal.transactions){
if(t.getId() == dto.getTransactionId())
return true;
}
return false;
I am not sure that it is possible to do in plain SpEL. But you can try a workaround:
#PreAuthorize("principal.hasTransactionId(#dto.transactionId)")
Then you need to add hasTransactionId(Integer transactionId) method to your principal. This method must return a boolean value.