I am setting up a form using JSF (I'm pretty new at this) and I am getting a Validation Error: Value is not valid message on one of the fields. This field is actually a separate object (as I will show below) that has a custom converter.
Here is what I have (with non-relevant code removed):
I have a Citation class:
#ManagedBean(name="citation")
public class Citation {
private int id;
private Status status;
// getters and setters
}
I also have a Status class that you see referenced in the Citation class:
#ManagedBean(name="status")
public class Status {
private int id;
private String name;
// getters and setters
public List<Status> getAllStatuses() {
Session session = HibernateUtil.getCurrentSession();
session.beginTransaction();
session.clear();
Query query = session.createQuery("from Status");
List<Status> statuses = query.list();
try {
session.getTransaction().commit();
} catch (HibernateException e) {
// TODO: handle exception
session.getTransaction().rollback();
}
return statuses;
}
#Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!(obj instanceof Status)) return false;
if (this.id == ((Status)obj).getId()) {
return true;
} else {
return false;
}
}
#Override
public int hashCode() {
return this.name.hashCode();
}
}
Then for my form, I have:
<h:selectOneMenu id="citation_status" value="#{citation.status}">
<f:selectItems value="#{status.allStatuses} var="s" itemValue="#{s.id}" itemLabel="#{s.name}" />
</h:selectOneMenu>
<h:message for="citation_status" />
Lastly, for my converter, I have:
#FacesConverter(forClass=Status.class)
public class StatusConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// uses Hibernate to get the Status object (using a breakpoint in Eclipse, I have verified that this works)
// I can post this code if needed, but just trying to keep it short :)
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return String.valueOf(((Status) value).getId());
}
}
Now when I get to my form and submit, I get the Validation Error next to the Status. I'm pretty new at this and thanks to #BalusC, I'm this far along.
Any help is greatly appreciated.
Validation Error: Value is not valid
In case of <h:selectOneMenu>, you will get this when error whenever the selected item does not match any of the items available in the list. I.e. selectedItem.equals(selectItem) has never returned true for any of the items.
Since it's apparently a custom object (the Status class), did you implement its Object#equals() (and #hashCode()) properly? You can if necessary let the IDE (Eclipse/Netbeans) autogenerate them.
See also:
Overriding equals and hashCode in Java
How to implement equals() in beans/entities
Update: after having a closer look at your code, it turns out that you're actually submitting #{s.id} instead of #{s} (the whole Status object). Fix the itemValue accordingly and it should work (if equals() is still doing its job properly).
Related
I have been studying spring boot for a few weeks.
I am building a simple api using hibernate + jpa with a mysql database.
I have a resource call TvShowReminderResponseDTO :
public class TvShowReminderResponseDTO {
// Attributes
private Integer idTvShowReminder;
private User user;
private UserTvShow userTvShow;
private TvShowDetailsResponseDTO tvShowDetailsResponseDTO;
private Boolean completed;
private Integer currentSeason;
private Integer currentEpisode;
private Integer personalRating;
// rest of the code omittedfor brevity
}
In my controller i have a basic update PATCH endpoint, that receives the id of the tv show reminder (entity) that is stored in my database and also i receive a TvShowReminderPatchDTO with the information i want to update:
PatchDTO and Controller:
public class TvShowReminderPatchDTO {
// Attributes
private Optional<Boolean> completed;
private Optional<Integer> currentSeason;
private Optional<Integer> currentEpisode;
private Optional<Integer> personalRating;
// rest of the code omittedfor brevity
}
#PatchMapping("/{idTvShowReminder}")
public void updateTvShowReminder(#RequestBody #Valid TvShowReminderPatchDTO tvShowReminderToUpdate,
#PathVariable Integer idTvShowReminder){
tvShowReminderService.updateTvShowReminder(tvShowReminderToUpdate,idTvShowReminder);
}
Also I have my service method that is in charge of searching the TvShowReminder entity by its id, and then update the information we get from the client.
public void updateTvShowReminder(TvShowReminderPatchDTO tvShowReminderToUpdate, Integer idTvShowReminder) {
Optional<TvShowReminder> tvShowReminder = getTvShowReminder(idTvShowReminder);
TvShowReminder currentTvShowReminder = tvShowReminder.get();
if(tvShowReminderToUpdate.getCompleted() != null) {
if (tvShowReminderToUpdate.getCompleted().isPresent()) {
currentTvShowReminder.setCompleted(tvShowReminderToUpdate.getCompleted().get());
} else {
currentTvShowReminder.setCompleted(null);
}
}
if(tvShowReminderToUpdate.getCurrentSeason() != null) {
if (tvShowReminderToUpdate.getCurrentSeason().isPresent()) {
currentTvShowReminder.setCurrentSeason(tvShowReminderToUpdate.getCurrentSeason().get());
} else {
currentTvShowReminder.setCurrentSeason(null);
}
}
if(tvShowReminderToUpdate.getCurrentEpisode() != null) {
if (tvShowReminderToUpdate.getCurrentEpisode().isPresent()) {
currentTvShowReminder.setCurrentEpisode(tvShowReminderToUpdate.getCurrentEpisode().get());
} else {
currentTvShowReminder.setCurrentEpisode(null);
}
}
if(tvShowReminderToUpdate.getPersonalRating() != null) {
if (tvShowReminderToUpdate.getPersonalRating().isPresent()) {
currentTvShowReminder.setPersonalRating(tvShowReminderToUpdate.getPersonalRating().get());
} else {
currentTvShowReminder.setPersonalRating(null);
}
}
tvShowReminderRepository.save(currentTvShowReminder);
}
I have a question about the #valid annotation in the controller: i thought that it will check if the object that we send from postman for example is of type TvShowReminderPatchDTO , but i can send an entire different object and the controller will start its excecution, and the TvShowReminderPatchDTO will have all its attributes in NULL.
Whats the best way to check if the request body its in fact a TvShowReminderPatchDTO ?
I want to validate if the object we get from the Request is an instance of the TvShowReminderPatchDTO, and if not, throw an Exception.
The method that is doing the PATCH is working but its very ugly, I use optional as attributes in the TvShowReminderPatchDTO , so i can distinguish if the client wants to set a NULL (send an attribute with a null value ) or if the attribute was ommited (it does not appear on the request body) so we dont need to do anything, meaning we dont update it.
Can you guys recommend a better way to do this or improve the existing code?
Add some required fields using #NotNull annotation in your dto to help Spring understand which attributes should be present in your type
Don't use Optional. There is already JsonNullable for this purpose
public class TvShowReminderPatchDTO
{
#NotNull
private JsonNullable<Boolean> completed = JsonNullable.undefined();
}
And in controller method:
if (dto.getCompleted().isPresent()) {
object.setCompleted(dto.getCompleted().get());
}
That's it, no null-checks required, just set the value
This is a general issue/problem that I have come across. I wondered if anyone knows of any well suited design patterns or techniques.
private ExternalObject personObject;
private String name;
private int age;
private String address;
private String postCode;
public MyBuilderClass(ExternalObject obj)
this.personObject=obj;
build();
}
public build() {
setName(personObject.getName());
setAge(personObject.getAge());
setAddress(personObject.getAddress());
setPostCode(personObject.getPostCode());
.
.
. many more setters
}
The class above takes external objects from a queue and constructs MyBuilderClass objects.
A MyBuilderClass object is successfully built if all of the fields have been set to non-null non-empty values.
There will be many MyBuilderClass objects that cannot be built because data will be missing from the ExternalObject.
My problem, what is the best way to detect if an object has been correctly built?
I could check for null or empty values in the set methods and throw an exception. The problem with this approach is throwing exceptions is expensive and it will clogg the log files up because there will be many instances where an object cannot be built;
What other approaches could I use?
Correct me if I'm wrong: you are trying to find a good way to check if an object is valid, and if it is not, tell the client code about this without using an exception.
You can try a factory method:
private MyBuilderClass(ExternalObject obj)
this.personObject=obj;
build();
}
public static MyBuilderClass initWithExternalObject(ExternalObject obj) {
// check obj's properties...
if (obj.getSomeProperty() == null && ...) {
// invalid external object, so return null
return null;
} else {
// valid
MyBuilderClass builder = new MyBuilderClass(obj);
return builder.build();
}
}
Now you know whether an object is valid without using an exception. You just need to check whether the value returned by initWithExternalObject is null.
I wouldn't throw exceptions in cases that aren't exceptional. And as the only way for a constructor not to produce an object is to throw, you should not delay validation to the constructor.
I'd still recommend the constructor to throw if its results were to be invalid, but there should be a validation before that, so you don't even call the constructor with an invalid ExternalObject.
It's up to you if you want to implement that as a static method boolean MyBuilderClass.validate(ExternalObject) or by using the builder pattern with this validation.
Another approach for such a validation is to use java Annotations:
Make a simple annotaion class, let's say Validate:
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#interface Validate {
boolean required() default true;
}
then annotate the fields you want to be present as #Validate(required=true):
class MyBuilderClass {
private ExternalObject externalObject;
#Validate(required=true)
private String name;
#Validate(required=false) /*since it's a primitive field*/
private int age;
#Validate(required=true)
private String address;
#Validate(required=true)
private String postCode;
MyBuilderClass(ExternalObject externalObject) {
this.externalObject = externalObject;
build();
}
public void build() {
setName(personObject.getName());
setAge(personObject.getAge());
setAddress(personObject.getAddress());
setPostCode(personObject.getPostCode());
}
//.
//.
//. many more setters
}
And then add this method in the MyBuilderClass class, in order to check if your Object is built correctly:
public boolean isCorrectlyBuilt() throws IllegalAccessException {
boolean retVal = true;
for (Field f : getClass().getDeclaredFields()) {
f.setAccessible(true);
boolean isToBeChecked = f.isAnnotationPresent(Validate.class);
if (isToBeChecked) {
Validate validate = f.getAnnotation(Validate.class);
if (validate.required()/*==true*/) {
if (f.get(this) == null) {
retVal = false;
break;
/* return false; */
}
}
}
}
return retVal;
}
Here is an example of use :
public static void main(String[] args) throws Exception {
ExternalObject personObject = new ExternalObject();
personObject.setAge(20);
personObject.setName("Musta");
personObject.setAddress("Home");
personObject.setPostCode("123445678");
MyBuilderClass myBuilderClass = new MyBuilderClass(personObject);
System.out.println(myBuilderClass.isCorrectlyBuilt());
}
Output : true because the object is correctly built.
This will allow you to choose the fields that you want to be in the structure by reflection, without bringing those inherited from a base class.
As this previous answer suggests, here are 2 options either of which should be added after you have tried to set the variables.
use reflection to check whether any of the variables are null. (As mentioned in comments this will check all fields in this object but be careful with fields in any superclasses).
public boolean checkNull() throws IllegalAccessException {
for (Field f : getClass().getDeclaredFields())
if (f.get(this) != null)
return false;
return true;
}
perform a null check on each variable.
boolean isValidObject = !Stream.of(name, age, ...).anyMatch(Objects::isNull);
Previous answer
From what I've come across you could overwrite the equals method of your object and compare it with a valid example object. Its dirty and might only work in some cases.
Your approach is the best I could think of. Write a seperate method or class that has for example a static validate method. You could reuse it anywhere.
Using Spring cache abstraction, I want to cache the results of itemExists method calls. When an item is inserted by insertItem method, I want to put the value true in the cache.
class MyServiceImpl implements MyService {
private static final String CACHE_EXISTS_NAME = "existsCache";
#Override
#Cacheable(CACHE_EXISTS_NAME)
public boolean itemExists(Long id) {
// access the repository the check whether the item exists
}
#Override
#CachePut(cacheNames = CACHE_EXISTS_NAME, key = "#item.id")
public Item insertItem(Item item) {
...
}
}
How can I achieve what I need?
Its not supported. A work around could be to define #Cacheable on say your dao's finder method and then call it from itemExists method:
In the DAO:
#Cacheable(CACHE_EXISTS_NAME)
public Item findById(Long id) {
//..
}
In your service:
#Override
public boolean itemExists(Long id) {
if(null == dao.findById(id)) {
return false;
} else {
return true;
}
}
Note that since the method calls are proxied to support this, defining and calling finder inside the same object (service class in this case) will result in annotation being ignored.
I find your use case a bit disturbing. If you want to see if a particular item is in the cache, why don't you ask the cache rather than having those convoluted indirections? You could inject the Cache instance and asks for its content.
I guess that changing your code to the following might work but I really do not recommend that:
#Override
#CachePut(cacheNames = CACHE_EXISTS_NAME, key = "#item.id")
public boolean insertItem(Item item) {
...
return true;
}
I defined the following drop down box in my page:
...
<td>
<h:selectOneMenu id="bootenvironment1" value="#{detailModel.selectedBootenvironment1}"
disabled="#{detailModel.mode == detailModel.viewMode}">
<f:selectItems value="#{detailModel.availableBootenvironments}"/>
</h:selectOneMenu>
</td>
In my model I have:
...
private Map<String, Bootenvironment> availableBootenvironments;
public DefinitionDetailModel()
{
super();
}
public String getSelectedBootenvironment1()
{
if (((Definition) getAfterObject()).getBootenvironment1() != null)
{
return ((Definition) getAfterObject()).getBootenvironment1().getEnvironmentName();
}
return "--Please select one--";
}
public void setSelectedBootenvironment1( String selectedBootenvironment )
{
((Definition) getAfterObject()).setBootenvironment1(availableBootenvironments.get(selectedBootenvironment));
}
...
And in the controller I set the availableBootenvironments map:
private void fetchBootenvironments()
{
...
#SuppressWarnings( "unchecked" )
List<Bootenvironment> bootenvironments = (List<Bootenvironment>) ...
Map<String, Bootenvironment> availableBootenvironments = new HashMap<String, Bootenvironment>();
availableBootenvironments.put("--Please select one--", null);
for(Bootenvironment bootenvironment : bootenvironments)
{
availableBootenvironments.put(bootenvironment.getEnvironmentName(), bootenvironment);
}
((DefinitionDetailModel) detailModel).setAvailableBootenvironments(availableBootenvironments);
}
The problem is that when I click a button in the page (which is bound to an action), I get the error:
detailForm:bootenvironment1: Validation error: value is not valid.
I don't understand where the error is; the value for selectItems is a map with the object's name-field(so a string) as key and the object itself as value. Then the value for the default selected (value="#{detailModel.selectedBootenvironment1}") is a string too as you can see in the getter/setter method of the model.
Another problem (maybe related to the previous one) is that when the page first loads, the default selected value should be --Please select one--- as the getBootenvironment1() returns null, but this does not happen: another one from the list is selected.
Can you please help me understanding what/where am I doing wrong?
EDIT
I implemented the Converter as you said:
#FacesConverter( forClass = Bootenvironment.class )
public class BootenvironmentConverter implements Converter
{
#Override
public String getAsString( FacesContext context, UIComponent component, Object modelValue ) throws ConverterException
{
return String.valueOf(((Bootenvironment) modelValue).getDbId());
}
#Override
public Object getAsObject( FacesContext context, UIComponent component, String submittedValue ) throws ConverterException
{
List<Bootenvironment> bootenvironments = ... (get from DB where dbid=submittedValue)
return bootenvironments.get(0);
}
}
But now I have the following error:
java.lang.ClassCastException: java.lang.String cannot be cast to
ch.ethz.id.wai.bootrobot.bo.Bootenvironment
You will get this error when the selected item value doesn't pass the equals() test on any of the available item values.
And indeed, you've there a list of Bootenvironment item values, but you've bound the property to a String which indicates that you're relying on the Bootenvironment#toString() value being passed as submitted value and that you aren't using a normal JSF Converter at all. A String can never return true on an equals() test with an Bootenvironment object.
You'd need to provide a Converter which converts between Bootenvironment and its unique String representation. Usually, the technical ID (such as the autogenerated PK from the database) is been used as the unique String representation.
#FacesConverter(forClass=Bootenvironment.class)
public class BootenvironmentConverter implements Converter {
#Override
public void getAsString(FacesContext context, UIComponent component, Object modelValue) throws ConverterException {
// Write code to convert Bootenvironment to its unique String representation. E.g.
return String.valueOf(((Bootenvironment) modelValue).getId());
}
#Override
public void getAsObject(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException {
// Write code to convert unique String representation of Bootenvironment to concrete Bootenvironment. E.g.
return someBootenvironmentService.find(Long.valueOf(submittedValue));
}
}
Finally, after implementing the converter accordingly, you should be able to fix the selectedBootenvironment1 property to be a normal property without any mess in getter/setter:
private Bootenvironment selectedBootenvironment1;
public Bootenvironment getSelectedBootenvironment1() {
return selectedBootenvironment1;
}
public void setSelectedBootenvironment1(Bootenvironment selectedBootenvironment1) {
this.selectedBootenvironment1 = selectedBootenvironment1;
}
According this article, I've implemented #ManagedProperty(value="#{settings}") to my backing bean:
BEAN Bde.java:
#Entity
#Table(name="bdeDATA")
#ViewScoped
#ManagedBean(name="BDE")
public class Bde implements Serializable
{
/**/
private static final long serialVersionUID = -705775502999920673L;
#Transient
#ManagedProperty(value = "#{settings}")
private Settings settings;
#Id
private Date create_date;
private Integer person_ID;
public Date getCreate_date() {
return create_date;
}
public void setCreate_date(Date create_date) {
this.create_date = create_date;
}
public Integer getPerson_ID() {
return person_ID;
}
public void setPerson_ID(Integer person_ID) {
this.person_ID = person_ID;
try
{
Settings.PWKITEM = (Pwk)Tools.find(person_ID);
if (Settings.PWKITEM != null) settings.setUserfound(true); /// PROBLEMATIC
}
catch (Exception e)
{
Tools.setErrorMessage("NOT FOUND "+e.getMessage());
}
}
// ManagedProperty settings ---------------------------------------------
public Settings getSettings() {
return settings;
}
public void setSettings(Settings settings) {
this.settings = settings;
}
public void setUserfound (boolean userfound){
settings.setUserfound(userfound);
}
public boolean isUserfound() {
return settings.isUserfound();
}
}
Settings.java:
#SessionScoped
#ManagedBean(name="settings")
public class Settings implements Serializable
{
/**/
private static final long serialVersionUID = 8613411699115714416L;
public static Pwk PWKITEM = new Pwk();
private boolean userfound = false;
public boolean isUserfound() {
return userfound;
}
public void setUserfound(boolean userfound) {
this.userfound = userfound;
}
}
XHTML (ajax call setPerson_ID):
<h:inputText id="persId" value="#{bean.bdeitem.persId}">
<f:ajax event="blur" render="name" execute="#this" />
</h:inputText>
<h:inputText id="name" value="#{bean.pwkitem.name}"/>
Problem is in try/catch:
without the condition, object is found.
when I change the condition for example to if (Settings.PWKITEM != null) System.out.println("HELLO"), HELLO is writen to console.
if i try to add the userfound setter, it is catched ("NOT FOUND").
What I'm doing wrong?
Your question looks seriously confusing. You first show some bean code and then immediately say "I though that is an ajax problem,", before even mentioning any kind of problem. The rest of the question is not much different.
To directly answer the last part of your question though:
Ican't understand, why it find the item an writes the correct name to console, and immediatelly after that, it writes catch exception not found....????
You are accessing Settings statically. The instance you have declared at the class level seems to be useless. It's fully possible that if Tools.find throws an exception and thus no new value is assigned, that there is still an old value in the static Settings.PWKITEM field. There is nothing strange about that.
Do note that the log reads from top to bottom. So it's not that "***" is printed and then the exception is thrown, but the exception is first thrown and "Not Found" is printed, and only thereafter "***" is printed.
Additionally, your approach to all of this looks problematic. Declaring an Entity to also be a (JSF) backing bean is rarely a good idea. Using references to some kind of Service or DAO classes from within an entity is also not always a good idea, but doing this in a method that is supposedly a simple setter for an ID simply looks wrong.
Then using static references is even more wrong and to top if off, using underscores in method and non-static variable names goes against the common Java code convention.