Spring validation dependent on other variables - spring

I'm trying to write some validation annotations for a Spring project.
I have several fields which are nullable, unless the value of a bool (in the same object) is true.
Is there some easy way to do something like:
#NotNull(if xyz=true)
? Thanks

I think it's not possible to do with annotations (too complex), but this can be done fairly easy by implementing org.springframework.validation.Validator interface:
public class MyClassValidator implements Validator {
#Override
public boolean supports(Class c) {
return MyClass.class.equals(c);
}
#Override
public void validate(Object object, Errors errors) {
MyClass myClass = (MyClass) object;
if (myClass.getA() == null) {
errors.rejectValue("avalue", "avalue.empty", "'A' value cannot be empty");
}
else if (myClass.getA() == true && (myClass.getB() == null || myClass.getB() < 0)) {
errors.rejectValue("bvalue", "bvalue.notvalid", "'B' value is not valid");
}
}
}

Related

spring cache key generator why not use spel key?

keyGenerator not work on spel key,may I custom keyGenerator for all key contains #Cacheable(key="#spelKey")
I found code: org.springframework.cache.interceptor.CacheAspectSupport
protected Object generateKey(Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = this.createEvaluationContext(result);
return CacheAspectSupport.this.evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
} else {
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
}
change code like:
protected Object generateKey(Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext));
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
I am not sure what you're trying to do with #spelKey but it's fairly documented. If you want to do something complex, I'd implement a KeyGenerator instead, see the documentation

Different Validator For Create and Update in Spring MVC

I looking for different validation style for forms when I create and update entities.
For Instance, when I create an "UserClass" object it requires an ID to define, but when I update, I do not need ID again, because it is defined by user at the creation step. I have lots of entity and I need to find most proper way.
For instance is this logical?
public interface RecordGroupValidator {
public void validateNew(RecordGroup recordGroup, Errors errors);
public void validateUpdate(RecordGroup recordGroup, Errors errors);
}
Validator :
public class RecordGroupValidatorImpl implements RecordGroupValidator {
#Autowired
RecordGroupService recordGroupService;
#Override
public void validateNew(RecordGroup recordGroup, Errors errors) {
if (!ValidationHandler.validText(recordGroup.getIds())) {
errors.rejectValue(ColumnIdentifier.COLUMN.Ids.name(), TextParameters.SERVLET_RESPONSE.InvalidParameter.getText());
}
if (!ValidationHandler.validText(recordGroup.getName())) {
errors.rejectValue(ColumnIdentifier.COLUMN.Name.name(), TextParameters.SERVLET_RESPONSE.InvalidParameter.getText());
}
if (recordGroup.getRecordGroupType() == null) {
errors.rejectValue(ColumnIdentifier.COLUMN.RecordGroupType.name(), TextParameters.SERVLET_RESPONSE.InvalidParameter.getText());
}
if (recordGroupService.idsExist(recordGroup.getIds())) {
errors.rejectValue(ColumnIdentifier.COLUMN.Ids.name(), TextParameters.SERVLET_RESPONSE.DuplicateEntry.getText());
}
if (recordGroupService.nameExist(recordGroup.getName())) {
errors.rejectValue(ColumnIdentifier.COLUMN.Name.name(), TextParameters.SERVLET_RESPONSE.DuplicateEntry.getText());
}
}
#Override
public void validateUpdate(RecordGroup recordGroup, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, ColumnIdentifier.COLUMN.Name.name(), TextParameters.SERVLET_RESPONSE.InvalidParameter.getText());
if (recordGroup.getRecordGroupType() == null) {
errors.rejectValue(ColumnIdentifier.COLUMN.Type.name(), TextParameters.SERVLET_RESPONSE.InvalidParameter.getText());
}
}
}
I think you should create two validation. One for create and one for update. This will create clever architecture. Because for now you have only one difference but in the future you can have more. In my opinion you should split them now.

How to detect valueChange event in validator?

Mojarra 2.1
I need to write a validator for the h:inputText which performs some logic only if the value for that input is changed. I.e.
public class MyValidator implements Validator{
public void validate(FacesContext context,
UIComponent component,
Object value) throws ValidatorException;
if(valueChanged(UIComponent component)){ //The method checks if the value's changed
//do some piece of logic
}
return;
}
}
I dug into the queuing events of the UIInput and found this:
validateValue(context, newValue);
// If our value is valid, store the new value, erase the
// "submitted" value, and emit a ValueChangeEvent if appropriate
if (isValid()) {
Object previous = getValue();
setValue(newValue);
setSubmittedValue(null);
if (compareValues(previous, newValue)) {
queueEvent(new ValueChangeEvent(this, previous, newValue));
}
}
This piece of code is from the method, executed by the Validation phase callback. The first thought that popped into my head was queriyng all events fired during handling the request. The method queueEvent(FacesEvent) is implemented as follows:
public void queueEvent(FacesEvent event) {
if (event == null) {
throw new NullPointerException();
}
UIComponent parent = getParent();
if (parent == null) {
throw new IllegalStateException();
} else {
parent.queueEvent(event);
}
}
Therefore every such invokation will end up in UIViewRoot.queueEvent(FacesEvent) which is implemented as:
public void queueEvent(FacesEvent event) {
if (event == null) {
throw new NullPointerException();
}
// We are a UIViewRoot, so no need to check for the ISE
if (events == null) {
int len = PhaseId.VALUES.size();
List<List<FacesEvent>> events = new ArrayList<List<FacesEvent>>(len);
for (int i = 0; i < len; i++) {
events.add(new ArrayList<FacesEvent>(5));
}
this.events = events;
}
events.get(event.getPhaseId().getOrdinal()).add(event);
}
Which means, all events is actually stored as a List<List<FacesEvent>> for each phase. But the List<List<FacesEvent>> events is a private field, so it's impossible to get direct acces to it.
Another thing is that the actual validation is being perfromed before the quingEvent, so implemting valueChangeListener doesn't seem useful as well.
Question: Is it possible to implements such validator in JSF in a natural way?
Just do the value comparison yourself. In the validator, the old value is just readily available via UIComponent argument.
#Override
public void validate(FacesContext context, UIComponent component, Object submittedValue) {
if (component instanceof EditableValueHolder) {
Object newValue = submittedValue;
Object oldValue = ((EditableValueHolder) component).getValue();
if (newValue == null ? oldValue == null : newValue.equals(oldValue)) {
return; // Not changed, so skip validation.
}
}
// Do actual validation here.
}
If you happen to use JSF utility library OmniFaces, it has a ValueChangeValidator which does exactly this.

Looking for a solution to extend Spring MVC with another Component/Annotation

Suppose I have a Website that is used in normal mode (browser) and in some other mode, like a MobileView mode (inside a mobile app). For each Controller I create, there might be correspondent controller for MobileView, processing the same url.
The easiest solution is to create ifs in all the Controllers that have MobileView logic. Another solution would be to use a correspondent url for MobileView (similar to the normal url) and two separate Controllers (possible where one extends from another; or use some other way to recycle common code)
But, a more elegant solution would be to have some extra annotations, like #SupportsMobileView (to mark a controller, and tell the app that this will have a correspondent MobileView Controller) and #MobileViewController (to mark a second controller, and tell the app that this controller needs to run immediately after the initial controller marked with #SupportsMobileView). The link between a normal controller and a MobileView controller would be through the url they process (defined with #RequestMapping).
Is it possible to extend Spring MVC (A)? Where to inject new annotation scanners (B) and annotation handlers / component handlers (C)? How should the MobileView controller be executed (D) (right now I am thinking that it could be executed through AOP, where the new handler of my new controller type programatically creates a Join-Point on the corresponding normal controller)
Note that I did not mention how this MobileView mode is triggered and detected. Let's just say that there a Session boolean variable (flag) for that.
Critics on any points (A), (B), (C) or (D) are welcomed, as well as technical hints and alternative solution to any point or the whole solution.
HandlerInterceptor can be used to intercept the RequestMapping handling. This is a simple example how to configure and implement one.
You can check for your session variable and will have a bunch of methods that will allow you to do custom processing or just exchange the view from the normal controller handling with your mobile view.
Ok, warnings:
this is only a proof of concept of what I understood must be done so:
+#MobileViewEnable and #MobileView annotated (and related) methods need to stay in the same controller
+there's no check for the httpAction used
+the two methods must have the same signature
+mobileView annotation value and requestMapping annotation value must be equals and uniques
+the logic inside callYourLogic(..) defines which method is going to be called, at the moment there's a very simple logic that check if exist the parameter ("mobile") in the request, just to test
+this code is not intended to be used as is (at all)
+don't know if it works at all outside my pc (joke :D, ehm..)
SO:
Annotations:
#Retention(RetentionPolicy.RUNTIME)
public #interface MobileView {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
public #interface MobileViewEnable {
}
ExampleController:
#Controller
public class MainController extends BaseController {
private final static Logger logger = LoggerFactory.getLogger(MainController.class);
private final static String PROVA_ROUTE = "prova";
#MobileViewEnable
#RequestMapping(PROVA_ROUTE)
public String prova() {
logger.debug("inside prova!!!");
return "provaview";
}
#MobileView(PROVA_ROUTE)
public String prova2() {
logger.debug("inside prova2!!!");
return "prova2view";
}
}
Aspect definition:
<bean id="viewAspect" class="xxx.yyy.ViewAspect" />
<aop:config>
<aop:pointcut expression="#annotation(xxx.yyy.MobileViewEnable)" id="viewAspectPointcut" />
<aop:aspect ref="viewAspect" order="1">
<aop:around method="around" pointcut-ref="viewAspectPointcut" arg-names="viewAspectPointcut"/>
</aop:aspect>
</aop:config>
Aspect implementation:
public class ViewAspect implements BeforeAdvice, ApplicationContextAware {
private final static Logger logger = LoggerFactory.getLogger(ViewAspect.class);
private ApplicationContext applicationContext;
public Object around(ProceedingJoinPoint joinPoint) {
Method mobileViewAnnotatedMethod = null;
HttpServletRequest request = getCurrentHttpRequest();
String controllerName = getSimpleClassNameWithFirstLetterLowercase(joinPoint);
Object[] interceptedMethodArgs = getInterceptedMethodArgs(joinPoint);
String methodName = getCurrentMethodName(joinPoint);
Method[] methods = getAllControllerMethods(joinPoint);
Method interceptedMethod = getInterceptedMethod(methods, methodName);
String interceptedMethodRoute = getRouteFromInterceptedMethod(interceptedMethod);
if (callYourLogic(request)) {
mobileViewAnnotatedMethod = getMobileViewAnnotatedMethodWithRouteName(methods, interceptedMethodRoute);
if (mobileViewAnnotatedMethod != null)
return invokeMethod(mobileViewAnnotatedMethod, interceptedMethodArgs, controllerName);
}
return continueInterceptedMethodExecution(joinPoint, interceptedMethodArgs);
}
private Object continueInterceptedMethodExecution(ProceedingJoinPoint joinPoint, Object[] interceptedMethodArgs) {
try {
return joinPoint.proceed(interceptedMethodArgs);
} catch (Throwable e) {
logger.error("unable to proceed with intercepted method call: " + e);
}
return null;
}
private Object[] getInterceptedMethodArgs(JoinPoint joinPoint) {
return joinPoint.getArgs();
}
private boolean callYourLogic(HttpServletRequest request) {
// INSERT HERE YOUR CUSTOM LOGIC (e.g.: is the server accessed from a mobile device?)
// THIS IS A STUPID LOGIC USED ONLY FOR EXAMPLE
return request.getParameter("mobile")!= null;
}
private HttpServletRequest getCurrentHttpRequest() {
return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
}
private String invokeMethod(Method method, Object[] methodArgs, String className) {
if (method != null) {
try {
Object classInstance = getInstanceOfClass(method, className);
return (String) method.invoke(classInstance, methodArgs);
} catch (Exception e) {
logger.error("unable to invoke method" + method + " - " + e);
}
}
return null;
}
private Object getInstanceOfClass(Method method, String className) {
return applicationContext.getBean(className);
}
private Method getMobileViewAnnotatedMethodWithRouteName(Method[] methods, String routeName) {
for (Method m : methods) {
MobileView mobileViewAnnotation = m.getAnnotation(MobileView.class);
if (mobileViewAnnotation != null && mobileViewAnnotation.value().equals(routeName))
return m;
}
return null;
}
private String getRouteFromInterceptedMethod(Method method) {
RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
if (requestMappingAnnotation != null)
return requestMappingAnnotation.value()[0];
return null;
}
private String getCurrentMethodName(JoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}
private Method[] getAllControllerMethods(JoinPoint joinPoint) {
return joinPoint.getThis().getClass().getSuperclass().getMethods();
}
private String getSimpleClassNameWithFirstLetterLowercase(JoinPoint joinPoint) {
String simpleClassName = joinPoint.getThis().getClass().getSuperclass().getSimpleName();
return setFirstLetterLowercase(simpleClassName);
}
private String setFirstLetterLowercase(String simpleClassName) {
String firstLetterOfTheString = simpleClassName.substring(0, 1).toLowerCase();
String restOfTheString = simpleClassName.substring(1);
return firstLetterOfTheString + restOfTheString;
}
private Method getInterceptedMethod(Method[] methods, String lookingForMethodName) {
for (Method m : methods)
if (m.getName().equals(lookingForMethodName))
return m;
return null;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

EclipseLink converts Enum to BigDecimal

I try to convert an Enum into a BigDecimal using the Converter of EclipseLink. The conversion works, but the resulting database column has a type of String. Is it possible to set a parameter, that EclipseLink builds a decimal column type within the database?
I use a class, which implements org.eclipse.persistence.mappings.converters.Converter.
The application server logs
The default table generator could not locate or convert a java type (null) into a database type for database field (xyz). The generator uses java.lang.String as default java type for the field.
This message is generated for every field, which uses a converter. How can I define a specific database type for these fields?
public enum IndirectCosts {
EXTENDED {
public BigDecimal getPercent() {
return new BigDecimal("25.0");
}
},
NORMAL {
public BigDecimal getPercent() {
return new BigDecimal("12.0");
}
},
NONE {
public BigDecimal getPercent() {
return new BigDecimal("0.0");
}
};
public abstract BigDecimal getPercent();
public static IndirectCosts getType(BigDecimal percent) {
for (IndirectCosts v : IndirectCosts.values()) {
if (v.getPercent().compareTo(percent) == 0) {
return v;
}
}
throw new IllegalArgumentException();
}
}
The database has to store the numeric values. I use such a converter:
public class IndirectCostsConverter implements Converter {
#Override
public Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return objectValue;
} else if (objectValue instanceof IndirectCosts) {
return ((IndirectCosts) objectValue).getPercent();
}
throw new TypeMismatchException(objectValue, IndirectCosts.class);
}
#Override
public Object convertDataValueToObjectValue(Object dataValue, Session session) {
if (dataValue == null) {
return dataValue;
} else if (dataValue instanceof String) {
return IndirectCosts.getType(new BigDecimal((String) dataValue));
}
throw new TypeMismatchException(dataValue, BigDecimal.class);
}
#Override
public boolean isMutable() {
return false;
}
#Override
public void initialize(DatabaseMapping databaseMapping, Session session) {
}
}
Within convertDataValueToObjectValue(Object I have to use String because the SQL generator defines the database column as varchar(255). I would like to have decimal(15,2) or something.
Thanks a lot
Andre
The EclipseLink Converter interface defines a initialize(DatabaseMapping mapping, Session session); method that you can use to set the type to use for the field. Someone else posted an example showing how to get the field from the mapping here: Using UUID with EclipseLink and PostgreSQL
The DatabaseField's columnDefinition, if set, will be the only thing used to define the type for DDL generation, so set it carefully. The other settings (not null, nullable etc) will only be used if the columnDefinition is left unset.

Resources