I'm throwing exceptions in services (especially these validation ones) and try-catch them in controllers. I'm getting data to
in firmController:
try{
def data = request.JSON
firmService.createAndSave(data)
}
catch(ValidationException exception){
}
in firmService:
def createAndSave(data){
firm.year = data.year as BigDecimal
firm.price = data.price as Float
firm.employees = data.employees as Integer
firm.name = data.name
if(!firm.validate()){
throw new ValidationException(null, firm.errors)
}
firm.save(flush:true)
firm
}
but if I send JSON with invalid data: {year:"asd", price: "fgh", employees: "3", name: "zxc"} I got an NumberFormatException. I know, I can catch NumberFormatException (or some kind of my own exception) in controller but how can I get a fields/properties for which it were thrown (and still throw it as an exception)?
With the current approach that you are using to initialize your domain object you can't. The NFE is being thrown when grails tries to cast String value asd as BigDecimal (data.year as BigDecimal) and it has nothing to do with ValidationException.
JSONObject class implements Map and in grails all Domains have a constructor that accepts a Map and can initialize the object using map properties. So instead of binding each property manually you can directly instantiate the object using new Firm(data) in firmService. In this way you will get a binding exception when grails will try to bind a non decimal value to a BigDecimal type field.
Related
I have a problem with a p:selectOneMenu, no matter what I do I cannot get JSF to call the setter on the JPA entity. JSF validation fails with this message:
form:location: Validation Error: Value is not valid
I have this working on several other class of the same type (ie, join table classes) but cannot for the life of me get this one working.
If anyone can throw some troubleshooting/debugging tips for this sort of problem it would be greatly appreciated.
Using log statements I have verified the following:
The Conveter is returning correct, non null values.
I have no Bean Validation in my JPA entities.
The setter setLocation(Location location) is never called.
This is the simplest example I can do and it simply will not work:
<h:body>
<h:form id="form">
<p:messages id="messages" autoUpdate="true" />
<p:selectOneMenu id="location" value="#{locationStockList.selected.location}" converter="locationConverter">
<p:ajax event="change" update=":form:lblLocation"/>
<f:selectItems value="#{locationStockList.locationSelection}"/>
</p:selectOneMenu>
</h:form>
</h:body>
Converter:
#FacesConverter(forClass=Location.class, value="locationConverter")
public class LocationConverter implements Converter, Serializable {
private static final Logger logger = Logger.getLogger(LocationConverter.class.getName());
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value.isEmpty())
return null;
try {
Long id = Long.parseLong(value);
Location location = ((LocationManagedBean) context.getApplication().getELResolver().getValue(context.getELContext(), null, "location")).find(id);
logger.log(Level.SEVERE, "Converted {0} to {1}" , new Object[] {value, location});
return location;
} catch (NumberFormatException e) {
return new Location();
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null || value.toString().isEmpty() || !(value instanceof Location))
return "";
return String.valueOf(((Location) value).getId());
}
}
Console output:
// Getter method
INFO: Current value=ejb.locations.Location[id=null, name=null, latitude=0.0, longitude=0.0]
// Session Bean
INFO: Finding ejb.locations.Location with id=3
// Session Bean
INFO: ### Returning : ejb.locations.Location[id=3, name=mdmd, latitude=4.5, longitude=2.3]
// Converter
SEVERE: Converted 3 to ejb.locations.Location[id=3, name=mdmd, latitude=4.5, longitude=2.3]
// Getter method -> Where did my selected Location go ??
INFO: Current value=ejb.locations.Location[id=null, name=null, latitude=0.0, longitude=0.0]
Validation fails with the message "form:location: Validation Error: Value is not valid"
This error boils down to that the selected item does not match any of the available select item values specified by any nested <f:selectItem(s)> tag during processing of the form submit request.
As part of safeguard against tampered/hacked requests, JSF will reiterate over all available select item values and test if selectedItem.equals(availableItem) returns true for at least one available item value. If no one item value matches, then you'll get exactly this validation error.
This process is under the covers basically as below, whereby bean.getAvailableItems() fictionally represents the entire list of available select items as defined by <f:selectItem(s)>:
String submittedValue = request.getParameter(component.getClientId());
Converter converter = component.getConverter();
Object selectedItem = (converter != null) ? converter.getAsObject(context, component, submittedValue) : submittedValue;
boolean valid = false;
for (Object availableItem : bean.getAvailableItems()) {
if (selectedItem.equals(availableItem)) {
valid = true;
break;
}
}
if (!valid) {
throw new ValidatorException("Validation Error: Value is not valid");
}
So, based on the above logic, this problem can logically have at least the following causes:
The selected item is missing in the list of available items.
The equals() method of the class representing the selected item is missing or broken.
If a custom Converter is involved, then it has returned the wrong object in getAsObject(). Perhaps it's even null.
To solve it:
Ensure that exactly the same list is been preserved during the subsequent request, particularly in case of multiple cascading menus. Making the bean #ViewScoped instead of #RequestScoped should fix it in most cases. Also make sure that you don't perform the business logic in the getter method of <f:selectItem(s)>, but instead in #PostConstruct or an action event (listener) method. If you're relying on specific request parameters, then you'd need to explicitly store them in the #ViewScoped bean, or to re-pass them on subsequent requests by e.g. <f:param>. See also How to choose the right bean scope?
Ensure that the equals() method is implemented right. This is already done right on standard Java types such as java.lang.String, java.lang.Number, etc, but not necessarily on custom objects/beans/entites. See also Right way to implement equals contract. In case you're already using String, make sure that the request character encoding is configured right. If it contains special characters and JSF is configured to render the output as UTF-8 but interpret the input as e.g. ISO-8859-1, then it will fail. See also a.o. Unicode input retrieved via PrimeFaces input components become corrupted.
Debug/log the actions of your custom Converter and fix it accordingly. For guidelines, see also Conversion Error setting value for 'null Converter' In case you're using java.util.Date as available items with <f:convertDateTime>, make sure that you don't forget the full time part in the pattern. See also "Validation Error: Value is not valid" error from f:datetimeConverter.
See also:
Our selectOneMenu wiki page
How to populate options of h:selectOneMenu from database?
Make multiple dependent / cascading selectOneMenu dropdown lists in JSF
If anyone can throw some troubleshooting/debugging tips for this sort of problem it would be greatly appreciated.
Just ask a clear and concrete question here. Do not ask too broad questions ;)
In my case I forgot to implement a correct get/set methods. It happened because I have changed a lot of attributes along the development.
Without a proper get method, JSF canĀ“t recover your selected item, and happens what BalusC said at item 1 of his answer:
1 . The selected item is missing in the list of available items. This can happen if the list of available items is served by a request scoped bean which is not properly reinitialized on subsequent request, or is incorrectly doing the business job inside a getter method which causes it to return a different list in some way.
This can be a Converter Issue or else DTO issue.
Try to solve this, by adding hashCode() and equals() methods in your object DTO; In the above scenario you can generate these methods within the Location object class which indicate as the 'DTO' here.
Example:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Location other = (Location) obj;
if (id != other.id)
return false;
return true;
}
Please note that the above example is for an 'id' of type 'long'.
Coming from Struts/XWorks I was able to do something like this
Messages.properties
myMessage.invalid.fieldvalue=Invalid input for $'{'getText("{0}")'}'
email.label=Email Address
Application Code
getText("myMessage.invalid.fieldvalue",new String[] {"email.label"})
Output: Invalid input for Email Address
So basically the parameter I am passing into a message is actually the code for another message and I want to render them both together.
I can't seem to find a way to do this with Grails/Spring messaging. Is this possible and if so how?
EDIT:
To more clearly show one of the reason I am asking for this take this example.
Lets say I have 5 Domain classes with the property emailAddress
To validate for NULL I would have to do this
myClass1.emailAddress.nullable=Email Address cannot be NULL
myClass2.emailAddress.nullable=Email Address cannot be NULL
myClass3.emailAddress.nullable=Email Address cannot be NULL
myClass4.emailAddress.nullable=Email Address cannot be NULL
myClass5.emailAddress.nullable=Email Address cannot be NULL
What I want to be able to do is simply the messaging by overriding the default validation message as such
OLD: default.null.message=Property [{0}] of class [{1}] cannot be null
NEW: default.null.message=getMessage({0}) cannot be null
emailAddress=Email Address
So now anytime any class has a property called emailAddress and it validates as NULL I will get the message Email Address cannot be null. There is no need to have 5 messages that basically say the same exact thing. If I had another class with the property emailAddress then its already handled and I dont have to copy and paste a 6th line.
Anytime I have classes with shared property names, all I have to do is add just add a single line for each property that will be applied to all classes
sharedProp1= Shared property 1
sharedProp2= Shared property 2
When in a controller, call message for the param you want to internationalize and store that in a local variable.
Then call message for the full message and add your local variable, with the internationalized param value, as a param to that message.
def emailLabel = message(code: "email.label")
def fullMessage = message(code: 'myMessage.invalid.fieldvalue', args: [emailLabel])
Your messages.properties would contain something like
myMessage.invalid.fieldvalue=Invalid input for {0}
email.label=Email Address
I was able to get this done by extending the MessageSource and overriding the resolveArguments method.
class CustomMessageSource extends ReloadableResourceBundleMessageSource {
#Override
protected Object[] resolveArguments(Object[] args, Locale locale) {
if (args == null) {
return new Object[0];
}
List<Object> resolvedArgs = new ArrayList<Object>(args.length);
for (Object arg : args) {
if (arg instanceof MessageSourceResolvable) {
resolvedArgs.add(getMessage((MessageSourceResolvable) arg, locale));
}
else {
//resolvedArgs.add(arg) **REPLACED THIS LINE
resolvedArgs.add(getMessage(arg, null, arg, locale));
}
}
return resolvedArgs.toArray(new Object[resolvedArgs.size()]);
}
}
I replaced a single line within the loop that evaluates your message arguments. I basically take the argument and see if its a key to another message. If yes, then replace the argument with that message, if no then continue as normal to use the argument
Make sure to map the new messageSource in your resources.groovy file
beans = {
messageSource(groovyUtils.CustomMessageSource) {
basenames = "messages"
}
}
I have a class that looks like this
#Validateable
class StudentBean{
def String name;
def String age;
def String address;
def List<ErrorBean> errors = [];
static constraints = {
age nullable : false, validator : { val, obj, errors->
if(val<10)
errors.rejectValue("age", "student.age.notQualified.message", [val] as Object[], "Student not qualified.");
}
}
}
You can see that I have a errors property in StudentBean (List). i realized that variable name has a conflict with the errors where the bean error is stored. What i did was changed the closure to something like this
age nullable : false, validator : { val, obj, errorsValue->
if(val<10)
errorsValue.rejectValue("age", "student.age.notQualified.message", [val] as Object[], "Student not qualified.");
}
but I couldnt loop the errorsValue to get the error.
When I check what errors is this is what docs says:
The errors property on domain classes is an instance of the Spring
Errors interface. The Errors interface provides methods to navigate
the validation errors and also retrieve the original values.
https://grails.github.io/grails-doc/latest/guide/validation.html
so this is the property of the domain class, probably declared but hidden.
The question is can I change the errors property name to something else?
You can not change the validation errors property.
The reason for this comes down to the philosophy behind Grails. errors exists on all Grails domain classes by convention. Much of what makes Grails powerful is not the configuration but rather the "convention over configuration". This ensures consistency between Grails projects and makes the entire framework simpler to learn and use.
Here are the models:
public class myFormData {
private BigDecimal trackingNumber;
#Valid
private List<myCustomRecord> records = new ArrayList<myCustomRecord>();
}
public class myCustomRecord {
//the field also appears here (for child records)
private BigDecimal trackingNumber;
}
I have a controller method which receives this object at some point to do a save.
public #ResponseBody ValidationResponse save(Model model,
#Valid myFormData formData, BindingResult result,
HttpSession session){
//do stuff
}
The problem I'm having is if a string is passed into the trackingNumber field, the error message is not nice.
Failed to convert property value of type 'java.lang.String' to required type 'java.math.BigDecimal' for property 'records[0].trackingNumber'; nested exception is java.lang.NumberFormatException
Failed to convert property value of type 'java.lang.String' to required type 'java.math.BigDecimal' for property 'trackingNumber'; nested exception is java.lang.NumberFormatException
One other potential complication is I'm not using Spring form since I'm trying to do some ajax submissions. This is how I'm submitting the data:
function collectFormData(fields) {
var data = {};
//myFormData
data["trackingNumber"] = <<some value here>>;
for (var i = 0; i < fields.length; i++) {
data["records["+i+"].trackingNumber"] = <<some value>>;
//some more data to submit for child records
}
return data;
}
var mData = collectFormData(aSelectedRows);
if (aSelectedRows.length> 0) {
$.post('call_controller_save', mData, function(response) {
if (response.status === 'FAIL') {
displayError(response);
} else {
//SAVE SUCCESSFUL
$('#successMsgs').append("Data Saved");
}, 'json');
}
I've tried changing trackingNumber's data type to String and wrote a custom ConstraintValidator<CheckNumeric, String>. It does the trick... I can put in a custom message via ValidationMessages.properties, but I would like to keep the models true to the intended data type. Is there another way to control the error message and turn it into something nice and user friendly?
(I also understand the NumberFormatException is most likely happening in data binding, before the validation step? But I'm still not sure how to go about fixing the default message)
Edit
I use the following configuration
<bean class="org.springframework.context.support.ResourceBundleMessageSource"
id="messageSource">
<property name="basenames">
<list>
<value>messages</value>
<value>ValidationMessages</value>
</list>
</property>
</bean>
In each properties file, I have the identical content.
formData.trackingNumber=Invalid format
But the custom message is still not getting picked up. Question 2: Should it be formData or myFormData? I have confusion whether it is the class name or name of the form object used on the jsp page?
Add to validation messages - this kind of format is used by default by Spring if it cannot convert from supplied text to number:
typeMismatch.java.math.BigDecimal=Required format is number.
I dont think there is a way to control the validation error message in this case as it happens before the invocation of validate().
The thing we can do is,
Define validationError.properties
Have custom error message like
formData.trackingNumber=Invalid Format
Define a spring bean for org.springframework.context.support.ResourceBundleMessageSource class with p:basename as validationError
I have a Linq query which I am selecting into a string, of course a string can contain null!
So is there a way I can throw an exception within my Linq query, if I detect a null?
Can I decorate my class with an attribute that won't let it allow null?
I would like to wrap my Linq query in a try catch, and as soon as a null is detected then it would enter the catch, and I can handle it.
Edit
Here's my Linq query, it's quite simple currently. I am going to extend it, but this shows the basic shape:
var localText = from t in items select new Items { item = t.name }
Basically item is set to t.name, t.name is a string so it could be empty / null is this perfectly legal as its a string and strings can hold NULL.
So if it returns NULL then I need to throw an exception. Actually it would be handy to be able to throw an exception is NULL or empty.
I seemed to remember some kind of Attributes that can be set on top of properties that says "Don't accept null" etc.?
Edit
I think I found it: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.requiredattribute.aspx
This doesn't allow null or strings so I presume it throws an exception, I have used this with MVC but I am not sure if I can use it with a standard class.
As a string being null isn't particularly exceptional, you could do something like:
var items = myStrings.Where(s => !string.IsNullOrEmpty(s)).Select(s => new Item(s));
UPDATE
If you are reading this data from an XML file, then you should look into LINQ to XML, and also use XSD to validate the XML file rather than throwing exceptions on elements or attributes that don't contain strings.
You could try intentionally generating a NullReferenceException:
try
{
//Doesn't change the output, but throws if that string is null.
myStrings.Select(s=>s.ToString());
}
catch(NullReferenceException ex)
{
...
}
You could also create an extension method you could tack on to a String that would throw if null:
public static void ThrowIfNull(this string s, Exception ex)
{
if(s == null) throw ex;
}
...
myString.ThrowIfNull(new NullReferenceException());
Why do you want to throw an exception in this case? This sounds like throwing the baby out with the bath water for something that should not happen in the first place.
If you just want to detect that there are null/empty items:
int nullCount= items.Count( x=> string.IsNullOrEmpty(x.name));
If you want to filter them out:
var localText = from t in items where !string.IsNullOrEmpty(t.name) select new Items { item = t.name };