I have worked on several Spring MVC projects where the validation could be done very simply like such :
CONTROLLER
#RequestMapping(value = {"/newHeightUnit"}, method = RequestMethod.POST)
public String saveHeightUnit(#Valid HeightUnit heightUnit, BindingResult result, ModelMap model)
{
boolean hasCustomErrors = validate(result, heightUnit);
if ((hasCustomErrors) || (result.hasErrors()))
{
setPermissions(model);
return "heightUnitDataAccess";
}
heightUnitService.save(heightUnit);
session.setAttribute("successMessage", "Successfully added height unit \"" + heightUnit.getName() + "\"!");
return "redirect:/heightUnits/list";
}
private boolean validate(BindingResult result, HeightUnit heightUnit)
{
boolean hasCustomErrors = false;
if (heightUnitService.nameExists(heightUnit))
{
FieldError error = new FieldError("heightUnit", "name", heightUnit.getName(), false, null, null,
heightUnit.getName() + " already exists!");
result.addError(error);
hasCustomErrors = true;
}
return hasCustomErrors;
}
This would validate the entity against whatever validation annotation it had (#NotNull, #Size, #Digits, etc).
How can the same be achieved in JavaFX? I have 9 entities all with their validation annotations as I was doing in my MVC projects. I am using Spring with what you could call a view / service / dao structure. I do not use FXML at all, my UI components are all generated in pure Java and I intend for it to stay that way.
How can I use the validation annotations on my entities in a similarly friendly approach to that of Spring MVC?
Clarifications
Just for reference, this is how my entities are currently saved. There is currently no validation of the user's inputs whatsoever when they are added but everything works perfectly fine. My entities are all annotated and ready to go and i'm just looking to learn how to integrate the good ol' #Valid into the mix:
#Override
public void saveEntity()
{
TextField nameField = (TextField)formFields.get(0);
try
{
Category newCategory = new Category(null, nameField.getText(), new Date(), null);
categoryService.save(newCategory);
}
catch (Exception ex)
{
logger.error("Error adding category : " + ex.getMessage());
}
}
Thanks!
So i ended up with a pretty clean result. First off i ended up with a validator class that looks something like this :
public class EntityValidator
{
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
public Set<ConstraintViolation<Category>> validateCategory(Category category)
{
return validator.validate(category);
}
}
I am using Spring to make this class available for autowiring :
#Bean
public EntityValidator entityValidator()
{
return new EntityValidator();
}
The bean validation goes something like this :
TextField nameField = (TextField)formFields.get(0);
try
{
Category newCategory = new Category(null, nameField.getText(), new Date(), null);
Set<ConstraintViolation<Category>> errors = validator.validateCategory(newCategory);
if (errors.isEmpty())
{
categoryService.save(newCategory);
close();
}
else
{
showErrorMessages(errors);
}
}
catch (Exception ex)
{
logger.error("Error adding category : " + ex.getMessage());
}
The showErrorMessages method just takes the error Set and displays the first error in an error dialog. Since i am using validation groups, there is never more than one error in the Set so this all looks pretty clean. It will never be as simple as doing it from a controller in a web project but i'm pretty happy with the result overall.
Cheers
Related
I am trying to get data by multiple data from database on the basis of multiple Ids using Spring boot.
Basically it is a GET call which takes request parameters as a list of IDs and return response accordingly. IDs are unique in database
Url : api/details/1a,2a,3b
I am getting response as:
Get(value = "api/details/{Ids})
{
[id="1a",name="Raj", interest="Football"],
[id="2a",name="Tom", interest="Cricket"]
[id="3b",name="Kane", interest="Baseball"]
}
It is fine. But when i am giving a wrong Id, I am getting response as:
Url : api/details/xyz,abc,3b
{
null,
null,
[id="3b",name="Kane", interest="Baseball"]
}
I am expecting that instead of null it show say that the ID is not present along with Status code. Something like
{
2-Not found,3-Not Found,
id="3b",name="Kane", hobby="Baseball,
}
My controller class is like:
#RequestMapping(value = "api/details{Ids}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Game>>
getMultipleDetails(#PathVariable("Idss") String Idss) {
HttpHeaders headers = new HttpHeaders();
List<String> ids = Arrays.asList(Idss.split(","));
List<Game> list = new ArrayList<>();
Game details= null;
for (String id : ids) {
details= da.getMultipleDetails(id);
list.add(devices);
}
if (details== null) {
throw new RuntimeException(HttpStatus.NOT_FOUND.toString());
}
return new ResponseEntity<List<Game>>(list, headers, HttpStatus.OK);
}
}
My repository class is like:
public Device getMultipleDetails(String id) {
Game details= null;
try {
details= jdbcTemplate.queryForObject("SELECT * FROM table_name WHERE Id = ?",new DeviceRowMapper(), id);
} catch (Exception e) {
// Log the system generated Id
String systemRefId = String.valueOf(System.currentTimeMillis());
LOGGER.error(systemRefId, e);
//throw new DatabaseException(systemRefId, e);
}
return details;
}
Game is my model class that conatins id, name, hobby
As you're setting the ResponseEntity<List<Game>> you should only return a List with Game objects inside.
Not sure why you want to return the failed ones in the same List but as a workaround I will set id of the not found and, in the fields name and Game I will set 'Not found' instead of returning null objects. For example:
public Device getMultipleDetails(String id) {
Game details = new Game();
try {
details= jdbcTemplate.queryForObject("SELECT * FROM table_name WHERE Id = ?",new DeviceRowMapper(), id);
//If details is not null but it's empty
if (StringUtils.IsEmpty(details.getId())) {
details.setId(id);
details.setName("Not Found");
details.setGame("Not Found");
}
} catch (Exception e) {
// Log the system generated Id
String systemRefId = String.valueOf(System.currentTimeMillis());
LOGGER.error(systemRefId, e);
//If details is null it will trow null pointer exception
details = new Game();
details.setId(id);
details.setName("Not Found");
details.setGame("Not Found");
}
return details;
}
I strongly recommend you to rename the field Game in you Game class. A field should not duplicate the name of its containing class.
It's confusing to have a class member with the same name (case differences aside) as its enclosing class. This is particularly so when you consider the common practice of naming a class instance for the class itself.
Best practice dictates that any field or member with the same name as the enclosing class be renamed to be more descriptive of the particular aspect of the class it represents or holds.
I would recommend to rename it to something like typeOfGame for example.
You should manage the empty objects, and manage the message also, the code should be like this, because if not, only the last detail is the one evaluated, thats why the exception is not raised.
for (String id : ids) {
details= da.getMultipleDetails(id);
list.add(devices);
if (details== null) {
throw new RuntimeException(HttpStatus.NOT_FOUND.toString());
}
}
I am using spring-cloud-starter (ie.. spring boot with all the microservices features). When I create hystrix method in a component annotated using the javanica #HystrixCommand, follow the directions on the javanica github site (https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica) to make that method run async, regardless of whether I use their 'Future<>' or Reactive execution 'Observable<>', nothing runs/executes and I get
java.lang.ClassCastException: springbootdemo.EricComponent$1 cannot be cast to springbootdemo.Eric whenever I attempt to pull the result (in the case of Future<>) or get a callback (in case of Reactive Execution .. and println's dont trigger so it really didnt run).
public class Application { ...
}
#RestController
#RequestMapping(value = "/makebunchofcalls/{num}")
class EricController { ..
#RequestMapping(method={RequestMethod.POST})
ArrayList<Eric> doCalls(#PathVariable Integer num) throws IOException {
ArrayList<Eric> ale = new ArrayList<Eric>(num);
for (int i =0; i<num; i++) {
rx.Observable<Eric> oe = this.ericComponent.doRestTemplateCallAsync(i);
oe.subscribe(new Action1<Eric>() {
#Override
public void call(Eric e) { // AT RUNTIME, ClassCastException
ale.add(e);
}
});
}
return ale;
}
#Component
class EricComponent { ...
// async version =========== using reactive execution via rx library from netflix ==============
#HystrixCommand(fallbackMethod = "defaultRestTemplateCallAsync", commandKey = "dogeAsync")
public rx.Observable<Eric> doRestTemplateCallAsync(int callNum) {
return new ObservableResult<Eric>() {
#Override
public Eric invoke() { // NEVER CALLED
try {
ResponseEntity<String> result = restTemplate.getForEntity("http://doges/doges/24232/photos", String.class); // actually make a call
System.out.println("*************** call successfull: " + new Integer(callNum).toString() + " *************");
} catch (Exception ex) {
System.out.println("=============== call " + new Integer(callNum).toString() + " not successfull: " + ex.getMessage() + " =============");
}
return new Eric(new Integer(callNum).toString(), "ok");
}
};
}
public rx.Observable<Eric> defaultRestTemplateCallAsync(int callNum) {
return new ObservableResult<Eric>() {
#Override
public Eric invoke() {
System.out.println("!!!!!!!!!!!!! call bombed " + new Integer(callNum).toString() + "!!!!!!!!!!!!!");
return new Eric(new Integer(callNum).toString(), "bomb");
}
};
}
}
Why would I be getting back an EricComponent$1 instead of a Eric? btw, Eric is just a simple class with 2 strings... its ommitted.
I am figuring that I must have to explicitly execute, but that alludes me because: 1) Doing it with Future<> the queue() method is not available as the documentation claims and 2) doing it with Observable<> there really isn't a way to execute it that I get.
Do you have the #EnableHystrix annotation on you application class?
The subscribe method is asynchronous and you are trying to populate a list in a synchronous controller method so there may be a problem there. Can you change the subscribe to toBlockingObservable().forEach() and see if that helps?
Update #1
I was able to duplicate. Your default method should not return an Observable<Eric>, just an Eric.
public Eric defaultRestTemplateCallAsync(final int callNum) {
System.out.println("!!!!!!!!!!!!! call bombed " + new Integer(callNum) + "!!!!!!!!!!!!!");
return new Eric(new Integer(callNum).toString(), "bomb");
}
Update #2
See my code here https://github.com/spencergibb/communityanswers/tree/so26372319
Update #3
When I commented out the fallbackMethod attribute, it complained that it couldn't find a public version of EricComponent for AOP. I made EricComponent public static and it worked. A top level class in its own file would work to. My code, linked above, works (assuming the restTemplate call works) and returns n OK.
I'm just starting to learn osgi. Need create application, which provide Search Service. Search Service depends on the platform (SearchServiceLinux, SearchServiceAndroid, SearchServiceXXX ...). Also search service depends on a parameter that the user enters. Parameter is mandatory.
My Search Service Consumer (Then user set the parameter i create new instance of SearchService):
#Component(immediate = true, publicFactory = false)
#Provides(specifications = {TestConsumer.class})
#Instantiate
public class TestConsumer {
#Requires(filter = "(factory.name=package.ISearchService)")
private Factory mFactory;
private ComponentInstance mSearchComponentInstance;
...
public void userSetParameter(String pParameter) {
Properties lProperties = new Properties();
lProperties.put("instance.name", mFactory.getName() + "-" + pParameter);
lProperties.put("Parameter", pParameter);
if (mSearchComponentInstance != null) {
mSearchComponentInstance.dispose();
}
try {
mSearchComponentInstance = mFactory.createComponentInstance(lProperties);
} catch (UnacceptableConfiguration e) {
e.printStackTrace();
} catch (MissingHandlerException e) {
e.printStackTrace();
} catch (ConfigurationException e) {
e.printStackTrace();
}
}
My Search Service:
#Component
#Provides(specifications = {ISearchService.class}, strategy = "SINGLETON")
public class TestServise implements ISearchService{
#ServiceProperty(name = "Parameter", mandatory = true)
private int mParameter;
...
Questions:
1) Is this true structure of the program? #ServiceProperty or #Property more preferable in this case? What is the best practice for OSGI Service which requires parameters from user input? Is it possible to reform the structure of the consumer to use:
#Requires (filter = "need filter for SearchService with Parameter=XXX or create this service")
ISearchService mSearchService;
2) Can be applied in this situation iPOJO Event Admin Handlers?
Consumer:
#Publishes(name = "p1", topics = "userChangeParameter")
private Publisher mPublisher;
public void userChangeParameter(String pParameter) {
Properties lProperties = new Properties();
lProperties.put("Parameter", pParameter);
mPublisher.send(lProperties);
}
Search Service:
#Subscriber(name = "s0", topics = "foo")
public void subscriber(Event pEvent) {
System.out.println("Subscriber : " + pEvent.getProperty("Parameter"));
}
3) What is the best structure to create a service that depends on the parameters entered by the user? Maybe the problem is solved easily by using Apache Felix Subprojects?
I use apache felix 4.2.1.
I would create a service like this:
#Component(
metatype = false)
#SlingServlet(
paths = { "/bin/test/service" }, methods = { "POST" }, extensions = { "json" },
selectors = { "selector1", "selector2"}, generateComponent = false)
public class TestConsumer extends SlingAllMethodsServlet {
//inject all the services here like SearchServiceLinux, etc.
#Reference
private SearchServiceLinux searchServiceLinux;
}
You can use this service like
http://localhost/bin/test/service.seletor1.html
Now based on selector you can decide which class will handle the request means you can decide that seletor1 will be handled by class X and selector2 will be handled by class Y
If parameters are mandatory then I would recommend you to accept only POST on this service and make sure you provide search parameters in POST say parameter name is searchParam, so based on selector you can decide the handler and you can pass searchParam to this handler to generate search results.
Hope this helps.
I've been thinking around the Java feature that evaluates annotation values in compile-time and it seems to really make difficult externalizing annotation values.
However, I am unsure whether it is actually impossible, so I'd appreciate any suggestions or definitive answers on this.
More to the point, I am trying to externalize an annotation value which controls delays between scheduled method calls in Spring, e.g.:
public class SomeClass {
private Properties props;
private static final long delay = 0;
#PostConstruct
public void initializeBean() {
Resource resource = new ClassPathResource("scheduling.properties");
props = PropertiesLoaderUtils.loadProperties(resource);
delay = props.getProperties("delayValue");
}
#Scheduled(fixedDelay = delay)
public void someMethod(){
// perform something
}
}
Suppose that scheduling.properties is on classpath and contains property key delayValue along with its corresponding long value.
Now, this code has obvious compilation errors since we're trying to assign a value to final variable, but that is mandatory, since we can't assign the variable to annotation value, unless it is static final.
Is there any way of getting around this? I've been thinking about Spring's custom annotations, but the root issue remains - how to assign the externalized value to annotation?
Any idea is welcome.
EDIT: A small update - Quartz integration is overkill for this example. We just need a periodic execution with sub-minute resolution and that's all.
The #Scheduled annotation in Spring v3.2.2 has added String parameters to the original 3 long parameters to handle this. fixedDelayString, fixedRateString and initialDelayString are now available too:
#Scheduled(fixedDelayString = "${my.delay.property}")
public void someMethod(){
// perform something
}
Thank you both for your answers, you have provided valuable info which led me to this solution, so I upvoted both answers.
I've opted to make a custom bean post processor and custom #Scheduled annotation.
The code is simple (essentially it is a trivial adaptation of existing Spring code) and I really wonder why they didn't do it like this from the get go. BeanPostProcessor's code count is effectively doubled since I chose to handle the old annotation and the new one.
If you have any suggestion on how to improve this code, I'll be glad to hear it out.
CustomScheduled class (annotation)
#Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CustomScheduled {
String cron() default "";
String fixedDelay() default "";
String fixedRate() default "";
}
CustomScheduledAnnotationBeanPostProcessor class
public class CustomScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, EmbeddedValueResolverAware, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, DisposableBean
{
private static final Logger LOG = LoggerFactory.getLogger(CustomScheduledAnnotationBeanPostProcessor.class);
// omitted code is the same as in ScheduledAnnotationBeanPostProcessor......
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// processes both #Scheduled and #CustomScheduled annotations
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
final Class<?> targetClass = AopUtils.getTargetClass(bean);
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Scheduled oldScheduledAnnotation = AnnotationUtils.getAnnotation(method, Scheduled.class);
if (oldScheduledAnnotation != null) {
LOG.info("#Scheduled found at method {}", method.getName());
Assert.isTrue(void.class.equals(method.getReturnType()), "Only void-returning methods may be annotated with #Scheduled.");
Assert.isTrue(method.getParameterTypes().length == 0, "Only no-arg methods may be annotated with #Scheduled.");
if (AopUtils.isJdkDynamicProxy(bean)) {
try {
// found a #Scheduled method on the target class for this JDK proxy -> is it
// also present on the proxy itself?
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
} catch (SecurityException ex) {
ReflectionUtils.handleReflectionException(ex);
} catch (NoSuchMethodException ex) {
throw new IllegalStateException(String.format(
"#Scheduled method '%s' found on bean target class '%s', " +
"but not found in any interface(s) for bean JDK proxy. Either " +
"pull the method up to an interface or switch to subclass (CGLIB) " +
"proxies by setting proxy-target-class/proxyTargetClass " +
"attribute to 'true'", method.getName(), targetClass.getSimpleName()));
}
}
Runnable runnable = new ScheduledMethodRunnable(bean, method);
boolean processedSchedule = false;
String errorMessage = "Exactly one of 'cron', 'fixedDelay', or 'fixedRate' is required.";
String cron = oldScheduledAnnotation.cron();
if (!"".equals(cron)) {
processedSchedule = true;
if (embeddedValueResolver != null) {
cron = embeddedValueResolver.resolveStringValue(cron);
}
cronTasks.put(runnable, cron);
}
long fixedDelay = oldScheduledAnnotation.fixedDelay();
if (fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
fixedDelayTasks.put(runnable, fixedDelay);
}
long fixedRate = oldScheduledAnnotation.fixedRate();
if (fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
fixedRateTasks.put(runnable, fixedRate);
}
Assert.isTrue(processedSchedule, errorMessage);
}
CustomScheduled newScheduledAnnotation = AnnotationUtils.getAnnotation(method, CustomScheduled.class);
if (newScheduledAnnotation != null) {
LOG.info("#CustomScheduled found at method {}", method.getName());
Assert.isTrue(void.class.equals(method.getReturnType()), "Only void-returning methods may be annotated with #CustomScheduled.");
Assert.isTrue(method.getParameterTypes().length == 0, "Only no-arg methods may be annotated with #CustomScheduled.");
if (AopUtils.isJdkDynamicProxy(bean)) {
try {
// found a #CustomScheduled method on the target class for this JDK proxy -> is it
// also present on the proxy itself?
method = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
} catch (SecurityException ex) {
ReflectionUtils.handleReflectionException(ex);
} catch (NoSuchMethodException ex) {
throw new IllegalStateException(String.format("#CustomScheduled method '%s' found on bean target class '%s', "
+ "but not found in any interface(s) for bean JDK proxy. Either "
+ "pull the method up to an interface or switch to subclass (CGLIB) "
+ "proxies by setting proxy-target-class/proxyTargetClass " + "attribute to 'true'", method.getName(),
targetClass.getSimpleName()));
}
}
Runnable runnable = new ScheduledMethodRunnable(bean, method);
boolean processedSchedule = false;
String errorMessage = "Exactly one of 'cron', 'fixedDelay', or 'fixedRate' is required.";
boolean numberFormatException = false;
String numberFormatErrorMessage = "Delay value is not a number!";
String cron = newScheduledAnnotation.cron();
if (!"".equals(cron)) {
processedSchedule = true;
if (embeddedValueResolver != null) {
cron = embeddedValueResolver.resolveStringValue(cron);
}
cronTasks.put(runnable, cron);
LOG.info("Put cron in tasks map with value {}", cron);
}
// fixedDelay value resolving
Long fixedDelay = null;
String resolverDelayCandidate = newScheduledAnnotation.fixedDelay();
if (!"".equals(resolverDelayCandidate)) {
try {
if (embeddedValueResolver != null) {
resolverDelayCandidate = embeddedValueResolver.resolveStringValue(resolverDelayCandidate);
fixedDelay = Long.valueOf(resolverDelayCandidate);
} else {
fixedDelay = Long.valueOf(newScheduledAnnotation.fixedDelay());
}
} catch (NumberFormatException e) {
numberFormatException = true;
}
}
Assert.isTrue(!numberFormatException, numberFormatErrorMessage);
if (fixedDelay != null && fixedDelay >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
fixedDelayTasks.put(runnable, fixedDelay);
LOG.info("Put fixedDelay in tasks map with value {}", fixedDelay);
}
// fixedRate value resolving
Long fixedRate = null;
String resolverRateCandidate = newScheduledAnnotation.fixedRate();
if (!"".equals(resolverRateCandidate)) {
try {
if (embeddedValueResolver != null) {
fixedRate = Long.valueOf(embeddedValueResolver.resolveStringValue(resolverRateCandidate));
} else {
fixedRate = Long.valueOf(newScheduledAnnotation.fixedRate());
}
} catch (NumberFormatException e) {
numberFormatException = true;
}
}
Assert.isTrue(!numberFormatException, numberFormatErrorMessage);
if (fixedRate != null && fixedRate >= 0) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
fixedRateTasks.put(runnable, fixedRate);
LOG.info("Put fixedRate in tasks map with value {}", fixedRate);
}
Assert.isTrue(processedSchedule, errorMessage);
}
}
});
return bean;
}
}
spring-context.xml config file
<beans...>
<!-- Enables the use of a #CustomScheduled annotation-->
<bean class="org.package.CustomScheduledAnnotationBeanPostProcessor" />
</beans>
Some spring annotations support SpEL.
First:
<context:property-placeholder
location="file:${external.config.location}/application.properties" />
And then, for example:
#Value("${delayValue}")
private int delayValue;
I'm not sure if #Scheduled supports SPeL, though, but in general, that's the approach.
In regard to scheduling, check this post of mine and this related question
A better way to do this is to define the scheduling in xml using the task name space
<context:property-placeholder location="scheduling.properties"/>
<task:scheduled ref="someBean" method="someMethod" fixed-delay="${delayValue}"/>
If you for some reason want to do it with annotation, you need to create an annotation that has another optional attribute where you can specify the property name or better still a property placeholder expression or Spel expression.
#MyScheduled(fixedDelayString="${delay}")
If you want to make this work with annotation rather than bean configuration xml, you can use the following annotations: #Component, #PropertySource with PropertySourcesPlaceholderConfigurer Bean itself, like this:
#Component
#PropertySource({ "classpath:scheduling.properties" })
public class SomeClass {
#Scheduled(fixedDelay = "${delay}")
public void someMethod(){
// perform something
}
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
We can use a field value from other beans. Suppose we have a bean named someBean with a field someValue equal to 10. Then, 10 will be assigned to the field:
#Value("#{someBean.someValue}")
private Integer someBeanValue;
Reference: A Quick Guide to Spring #Value - Baeldung
I have a PropertyEditor in order to translate ids into Persons, with it's setAsText (String text) as follows:
public void setAsText(String text) throws IllegalArgumentException {
try {
int id = Integer.parseInt(text);
Person person = peopleService.get(id);
this.setValue(person);
}
catch (NumberFormatException ex) {
// ...
throw new IllegalArgumentException("Not a number!: " + text);
}
catch (PersonNotFoundExcetion ex) {
// ...
throw new IllegalArgumentException("Impossible to get Person: " + text);
}
}
And my PeopleController has a method as follows:
#RequestMapping("/getPerson")
public void ver (#RequestParam Person person, Model model) {
model.addAttribute (person);
// ...
}
I want to catch the IllegalArgumentException in order to show a friendly message to the user, such as "Sorry, the Person you are looking for isn't here", but I don't know where to do that...
Thanks!
General exception handling can be done in this way:
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAllExceptions(Exception e) {
return "redirect:/error.html"; /* use the correct view name */
}
More specfic you could use BindingResult
#RequestMapping(value = "/datedata", method = RequestMethod.POST)
public String create(
#ModelAttribute("datedata") final DateData datedata,
final BindingResult result) {
if (result.hasErrors()) {
return "datedata/create";
} else {
...
return "myView";
}
}
But I guess this works only for "Forms" (ModelAttribute)
In my humble opinion it is not a good idea to let Spring handle validaten of user input by property editors. I would strongly recommend to use the Form way: Build a command object with a STRING field an use a validator on it.
The exception ought to be caught in the Controller. It should never leak out to the view and end user.
If this is a web app, I'd recommend using the validation and binding API rather than PropertyEditor. That will allow you to return Errors that you can use to tell the UI what needs to be corrected.
Your exception handling needs work. I would not recommend catching an exception and doing nothing other than wrapping it and re-throwing. That's not handling anything or adding new information. It's actually less information as coded.