I have extension of org.springframework.validation.Validator.
public class MyValidator implements Validator {
#Override
public void validate(Object target, Errors errors) {
...
}
}
My goal is to pass more than one target to method.
I don't like idea with overload validate method because it smells as bad code:
validate(Object target1, Object target1, Errors errors) or creating map with needed targets.
It will be good to know better approach regarding this case.
I did not try the following code, but it demonstrates a basic idea how one field of the bean could be verified against the other. Hopefully, it will help you
Let's say you have the following form bean
public class MyForm {
private String id;
private List<String> oldIds;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<String> getOldIds() {
return oldIds;
}
public void setOldIds(List<String> oldIds) {
this.oldIds = oldIds;
}
}
and the id property has to be validated against the oldIds object (if i did understand your requirements correctly). To achieve it your need to create a constraint and mark your bean. So, the first is the constraint interface
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyConstraintValidator.class)
#Documented
public #interface MyConstraint {
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
}
next, you need to implement the constraint validator class:
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.beanutils.PropertyUtils;
public class MyConstraintValidator implements
ConstraintValidator<MyConstraint, Object> {
private String firstAttribute;
private String secondAttribute;
#Override
public void initialize(final MyConstraint constraintAnnotation) {
firstAttribute = constraintAnnotation.value()[0];
secondAttribute = constraintAnnotation.value()[1];
}
#Override
public boolean isValid(final Object object,
final ConstraintValidatorContext constraintContext) {
try {
final String id = (String) PropertyUtils.getProperty(object,
firstAttribute);
List<String> oldIds = (List<String>) PropertyUtils.getProperty(
object, secondAttribute);
// do your validation
return true;
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
}
}
finally, apply the created constraint to the form bean
#MyConstraint(value = { "id", "oldIds" })
public class MyForm {
// the code
}
For now, your mark your bean with the #Valid annotation from the javax.validation package or feed it to the validator object
We use a target bean which holds all the data which need to be validated. Something like
private static final class ParamsBean {
String id;
List<String> oldIds;
}
Then we simply cast the object. It's the cleanest possible solution imo, as it does not use generic Map or List of unknown objects (though the casting still is not nice).
i faced with a similar situation where i need to pass more arguments to the validate method so i came up with a idea of my own.in my case i wanted a String to be passed to this method
validate method implemented in the following classes CustomValidatorBean, LocalValidatorFactoryBean, OptionalValidatorFactoryBean, SpringValidatorAdapter
I extended the CustomValidatorBean and called the validate method in super class and it is working perfectly
import javax.validation.Validator;`
import org.apache.commons.lang3.StringUtils;`
import org.springframework.validation.Errors;`
importorg.springframework.validation.beanvalidation.CustomValidatorBean;`
public class MyValidator extends CustomValidatorBean {`
public void myvalidate(Object target,Errors errors,String flag,Profile profile)
{
super.validate(target,errors);
if(StringUtils.isEmpty(profile.name())){
errors.rejectValue("name", "NotBlank.profilereg.name", new Object[] { "name" }, "Missing Required Fields");
}
}
}
Related
Given a POJO in Spring Boot with several dozen fields of type String which is deserialized by Jackson. For demonstration purposes the following example only contains three fields:
#NoArgsConstructor
public class SomeRequest {
#JsonProperty("field_1")
private String field1;
#JsonProperty("field_2")
private String field2;
#JsonProperty("field_3")
private String field3;
}
I'm looking for a way to override the setter method but only for certain fields, i.e. I'd like to avoid repeating the below code for every affected field. This is doable for a handful number of fields but gets tedious for more than a handful.
public setField2(String field2) {
this.field2 = field2 + "?";
}
My idea was to place an annotation on the field like this:
#NoArgsConstructor
public class SomeRequest {
// ...
#JsonProperty("field_2")
#AppendQuestionMark
private String field2;
// ...
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface AppendQuestionMark {
}
But I'm lacking information on how to "implement" the AppendQuestionMark annotation which would override the field's setter method.
Or am I thinking way too complicated?
You can't change the settermethod's body if that's what you are asking. But you can create a method that will take an object (i.e. SomeRequest) as input and check which fields have your Annotation and change the values for those fields as you want.
For example, I created an annotation AppendStr.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface AppendStr {
public String str();;
}
Then I created another class 'AppendStrImpl` that will handle the implementation. I used the following code -
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class AppendStrImpl {
public void changeFields(Object object) throws Exception {
Class<?> clazz = object.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(AppendStr.class)) {
// get the getter method name from the field name
String fieldName = field.getName();
String getterMethodName =
"get" +
fieldName.substring(0, 1).toUpperCase() +
fieldName.substring(1);
Method getterMethod = clazz.getMethod(getterMethodName);
String returnValue = (String) getterMethod.invoke(object);
String setterMethodName = getterMethodName.substring(0, 1).replace("g", "s")
+ getterMethodName.substring(1);
Method setterMethod = clazz.getMethod(setterMethodName, String.class);
setterMethod.invoke(object, returnValue + getAppendingString(field));
System.out.println((String) getterMethod.invoke(object));
}
}
}
private String getAppendingString(Field field) {
return field.getAnnotation(AppendStr.class)
.str();
}
}
And this is my POJO class -
public class POJO {
#AppendStr(str = "?")
private String filed1;
#AppendStr(str = "!")
private String filed2;
private String filed3;
#AppendStr(str = "+")
private String filed4;
// ... getters and setters
}
Then I called this method from the main method -
POJO pojo = new POJO("a", "b", "c", "d");
AppendStrImpl appendStrImpl = new AppendStrImpl();
try {
appendStrImpl.changeFields(pojo);
} catch (Exception e) {
e.printStackTrace();
}
Now you can make this call with hard coding or you can use #Aspect too if you want.
The github link is here.
Instead of creating a new annotation that appends a question mark to one generic string field in your pojo you can use the already present JsonDeserialize annotation over the string fields you are interested:
#Data
#NoArgsConstructor
public class SomeRequest {
#JsonProperty("field_1")
private String field1;
#JsonProperty("field_2")
//here the custom deserializer appends the question mark character
#JsonDeserialize(using = StringAppendQuestionMarkDeserializer.class)
private String field2;
}
In your spring boot project you can register the custom deserializer with the JsonComponent annotation like below:
#JsonComponent
public class StringAppendQuestionMarkDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return node.asText() + "?";
}
}
A spring boot test example using the custom deserializer:
#JsonTest
class CorespringApplicationTests {
#Test
void testDeserialize() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SomeRequest request = mapper.readValue("{\"field_1\":\"value1\",\"field_2\":\"value2\"}", SomeRequest.class);
System.out.println(request); //<-- SomeRequest(field1=value1, field2=value2?)
}
}
Something like the following should do the trick:
#Aspect
#Component
public class AppendQuestionMarkAspect {
#Around("#annotation(AppendQuestionMark)")
public Object appendQuestionMark(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] arguments = joinPoint.getArgs();
return joinPoint.proceed(new Object[] {((String) arguments[0]) + "?"});
}
}
Of course, it would be advisable to check that only one argument exists and that it is, in fact, a String. Or you can also define the pointcut as to be applied only to methods starting with set. But the essence of the code is there.
I'm trying to get Jersey to work with Optional parameters. I have a very simple web service:
#Path("helloworld")
public static class HelloWorldResource {
public static final String CLICHED_MESSAGE = "Hello World!";
#GET
#Produces("text/plain")
public String getHello(#QueryParam("maybe") Optional<String> maybe) {
return CLICHED_MESSAGE;
}
}
And a simple harness:
public static void main(String[] arg) throws IOException {
ResourceConfig config = new ResourceConfig(HelloWorldResource.class);
String baseUri = "http://localhost:8080/api/";
HttpServer server = GrizzlyHttpServerFactory
.createHttpServer(URI.create(baseUri), config, false);
server.start();
}
However I get the following error:
Exception in thread "main" org.glassfish.jersey.server.model.ModelValidationException: Validation of the application resource model has failed during application initialization.
[[FATAL] No injection source found for a parameter of type public java.lang.String com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource.getHello(java.util.Optional) at index 0.; source='ResourceMethod{httpMethod=GET, consumedTypes=[], producedTypes=[text/plain], suspended=false, suspendTimeout=0, suspendTimeoutUnit=MILLISECONDS, invocable=Invocable{handler=ClassBasedMethodHandler{handlerClass=class com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource, handlerConstructors=[org.glassfish.jersey.server.model.HandlerConstructor#a3d9978]}, definitionMethod=public java.lang.String com.mercuria.odyssey.server.GrizllyOptional$HelloWorldResource.getHello(java.util.Optional), parameters=[Parameter [type=class java.util.Optional, source=maybe, defaultValue=null]], responseType=class java.lang.String}, nameBindings=[]}']
at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:555)
at org.glassfish.jersey.server.ApplicationHandler.access$500(ApplicationHandler.java:184)
at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:350)
at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:347)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.processWithException(Errors.java:255)
at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:347)
at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:311)
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.<init>(GrizzlyHttpContainer.java:337)
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:140)
at com.mercuria.odyssey.server.GrizllyOptional.main(GrizllyOptional.java:33)
I presume I need to do something about so that Jersey knows how to handle Optional parameters, but I've no idea what!
So parameter types that are allowed as a #xxxParam, you need to meet one of these requirements:
Be a primitive type
Have a constructor that accepts a single String argument
Have a static method named valueOf() or fromString() that accepts a single String argument (see, for example, Integer.valueOf(String))
Have a registered implementation of ParamConverterProvider JAX-RS extension SPI that returns a ParamConverter instance capable of a "from string" conversion for the type.
Be List<T>, Set<T> or SortedSet<T>, where T satisfies 2, 3 or 4 above. The resulting collection is read-only.
So in this case of Optional, going down the list; it's not a primitive; it doesn't have a String constructor; it doesn't have a static valueOf() or fromString()
So basically, the only option left is to implement a ParamConverter/ParamConverterProvider pair for it. Dropwizard (a framework built on top of Jersey) has a good implementation for it. I will post it here in case the link ever goes dead
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.internal.util.collection.ClassTypePair;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Optional;
import java.util.Set;
#Singleton
public class OptionalParamConverterProvider implements ParamConverterProvider {
private final ServiceLocator locator;
#Inject
public OptionalParamConverterProvider(final ServiceLocator locator) {
this.locator = locator;
}
/**
* {#inheritDoc}
*/
#Override
public <T> ParamConverter<T> getConverter(final Class<T> rawType, final Type genericType, final Annotation[] annotations) {
if (Optional.class.equals(rawType)) {
final List<ClassTypePair> ctps = ReflectionHelper.getTypeArgumentAndClass(genericType);
final ClassTypePair ctp = (ctps.size() == 1) ? ctps.get(0) : null;
if (ctp == null || ctp.rawClass() == String.class) {
return new ParamConverter<T>() {
#Override
public T fromString(final String value) {
return rawType.cast(Optional.ofNullable(value));
}
#Override
public String toString(final T value) {
return value.toString();
}
};
}
final Set<ParamConverterProvider> converterProviders = Providers.getProviders(locator, ParamConverterProvider.class);
for (ParamConverterProvider provider : converterProviders) {
final ParamConverter<?> converter = provider.getConverter(ctp.rawClass(), ctp.type(), annotations);
if (converter != null) {
return new ParamConverter<T>() {
#Override
public T fromString(final String value) {
return rawType.cast(Optional.ofNullable(value).map(s -> converter.fromString(value)));
}
#Override
public String toString(final T value) {
return value.toString();
}
};
}
}
}
return null;
}
}
Note, if you are using a Jersey version 2.26+, instead of injecting ServiceLocator you will use InjectionManager instead. Also the argument that accepts a locator, you will need to change the the manager.
With this class, you just need to register it with your Jersey application.
This is a partial solution, but it seems like DropWizard has a feature specifically to support this:
https://github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/optional/OptionalParamBinder.java
So you can simply use their code:
import io.dropwizard.jersey.optional.*;
class DirtyBinder extends AbstractBinder {
#Override
protected void configure() {
bind(OptionalParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
bind(OptionalDoubleParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
bind(OptionalIntParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
bind(OptionalLongParamConverterProvider.class).to(ParamConverterProvider.class).in(Singleton.class);
}
}
then just add:
config.register(new DirtyBinder());
I’m checking if two fields contain and invalid characters, if so, output an error message.
This works fine when its just one field that is invalid but when both contain invalid characters, I need a single error message with details of both fields.
Example:
Single invalid field = Fields contain invalid characters: account number '6637=958'
Both field invalid = Fields contain invalid characters: account number '6637=958', sort code '01%657'
I can’t change the format of the error messages. Right now, I just use a stream to output the first error message. I would like to keep the solution simple but can only think of brute force ways to combine the messages.
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountDetails {
#Pattern(regexp = "^[-0-9]*$", message="Fields contain invalid characters: account number '${validatedValue}'")
private String accountNumber;
#Pattern(regexp = "^[-0-9]*$", message="Fields contain invalid characters: sort code '${validatedValue}'")
#NotBlank
private String sortCode;
public AccountDetails() {}
public String getSortCode() {
return sortCode;
}
public void setSortCode(String sortCode) {
this.sortCode = sortCode;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
}
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status, WebRequest request) {
String errorMessage = ex.getBindingResult()
.getFieldErrors()
.stream()
.findFirst()
.map(FieldError::getDefaultMessage)
.get();
final BankDetailsValidationModel validationResult = new BankDetailsValidationModel(false, false, errorMessage, "");
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(validationResult);
}
}
This can be done by defining a custom validation at the class level, rather than at the field level.
An example:
You have a Person class containing the fields birthYear and deathYear. The business rule requires the birthYear to be non-null if the deathYear is non-null. Otherwise, both fields can be null; or the deathYear can be null if the birthYear is not null.
In this example, we have a relationship between the two fields - in your case, you do not. But that does not change the basic approach: there is one validation, with one message, summarizing the state of both fields.
The Person class:
#FirstOrBoth(firstField = "year of birth", secondField = "year of death", message = ValidationHandler.FIRST_OR_BOTH_MESSAGE)
public class Person {
private Integer birthYear;
private Integer deathYear;
[...the rest of the class...]
}
The #FirstOrBoth Annotation
The above class uses a custom annotation: #FirstOrBoth:
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target(TYPE)
#Retention(RUNTIME)
#Constraint(validatedBy = FirstOrBothValidator.class)
public #interface FirstOrBoth {
String message() default "Please provide values in both fields, or only in the first (or leave both blank).";
String firstField();
String secondField();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The FirstOrBothValidator Class
The above annotation uses a custom validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class FirstOrBothValidator implements ConstraintValidator<FirstOrBoth, Object> {
#Override
public boolean isValid(Object obj, ConstraintValidatorContext cvc) {
Person person = (Person) obj;
// whatever validation logic you need goes here
// this is just for illustration:
return !(person.getBirthYear() == null && person.getDeathYear() != null);
}
}
As you already know, you can insert data values into your validation message using placeholders.
Final note: You mention that you can’t change the format of the error messages.
This approach assumes you can at least concatenate the two messages into the single message for the class-level validation.
I was reading the Spring docs and found that creating a subclass from ResponseEntityExceptionHandler was a good way on handling exceptions. However, I tried to handle exceptions in a different way, since I need to diff BusinessExceptions from TechnicalExceptions.
Created a bean called BusinessFault which encapsulates the exception details:
BusinessFault.java
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonInclude(value = Include.NON_NULL)
public class BusinessFault {
#JsonProperty(value = "category")
private final String CATEGORY = "Business Failure";
protected String type;
protected String code;
protected String reason;
protected String description;
protected String instruction;
public BusinessFault(String type, String code, String reason) {
this.type = type;
this.code = code;
this.reason = reason;
}
public BusinessFault(String type, String code, String reason, String description, String instruction) {
this.type = type;
this.code = code;
this.reason = reason;
this.description = description;
this.instruction = instruction;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getInstruction() {
return instruction;
}
public void setInstruction(String instruction) {
this.instruction = instruction;
}
public String getCATEGORY() {
return CATEGORY;
}
}
Created a BusinessException class, which do the job by creating a BusinessFault beans through the details passed by its constructor:
BusinessException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
public abstract class BusinessException extends RuntimeException {
private BusinessFault businessFault;
public BusinessException(String type, String code, String reason) {
this.businessFault = new BusinessFault(type, code, reason);
}
public BusinessException(String type, String code, String reason, String description, String instruction) {
this.businessFault = new BusinessFault(type, code, reason, description, instruction);
}
public BusinessException(BusinessFault businessFault) {
this.businessFault = businessFault;
}
public BusinessFault getBusinessFault() {
return businessFault;
}
public void setBusinessFault(BusinessFault businessFault) {
this.businessFault = businessFault;
}
}
Created a specific UserNotFoundException class, which extends from BusinessException class:
UserNotFoundException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
import com.rest.restwebservices.exception.map.ExceptionMap;
public class UserNotFoundException extends BusinessException {
public UserNotFoundException(BusinessFault businessFault) {
super(businessFault);
}
public UserNotFoundException(String reason) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason);
}
public UserNotFoundException(String reason, String description, String instruction) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason, description,
instruction);
}
}
Created a BusinessExceptionHandler, but instead of being a subclass of ResponseEntityExceptionHandler, it's only has a #ControllerAdvice annotation and a method that handles all thrown BusinessExceptions:
BusinessExceptionHandler.java
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.BusinessException;
import com.rest.restwebservices.exception.fault.BusinessFault;
#ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
#ExceptionHandler(BusinessException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, BusinessException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.OK);
}
}
The service layer can throw a UserNotFoundException:
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
public User findById(Long id) {
User user = userRepository.findOne(id);
if (user == null)
throw new UserNotFoundException("The ID " + id + " doesn't behave to any user!");
return user;
}
}
It works fine. But I was wondering if this is a bad practice on handling exceptions?
I've got a little problem with your exception handling. Principally it is absolutely ok to catch runtime exceptions, handle them and send them forth to the client, which is probably someone using your REST service and getting the error response as a JSON object. If you manage to tell him what he did wrong and what he can do about it, great! Of course, it will add some complexity to it, but it is probably easy and comfortable to work with that API.
But think about the backend developers, too, that work with your code. Especially the public User findById(Long id) method in your UserService is obscure. The reason for this is that you made your BusinessException, in particular, the UserNotFoundException unchecked.
If I joined your (backend) team, and I was to write some business logic using that service, I'd be quite sure what I had to expect from that method: I pass a user ID and get back a User object if it was found or null if not. That's why I would write code like that
User user = userService.findById("42A");
if (user == null) {
// create a User or return an error or null or whatever
} else {
// proceed
}
However, I would never know, that the first condition will never be true since you never return null. How should I know that I had to catch an Exception?
Is the compiler telling me to catch it? No, as it is not checked.
Would I look into your source code? Hell, no! Your case is extremely simple. That UserNotFoundException may be raised in another method in another class among hundred lines of code. Sometimes I couldn't look inside it, anyway, as that UserService is just a compiled class in a dependency.
Do I read the JavaDoc? Hahaha. Let's say, 50% of the time I wouldn't, and the other 50% you've forgotten to document it, anyway.
So, the developer has to wait until his code is used (either by a client or in Unit tests) to see that it doesn't work as he intended, forcing him to redesign what he has coded so far. And if your whole API is designed that way, that unchecked exceptions pop out of nowhere, it can be very very annoying, it costs time and money and is so easy to avoid, actually.
I use a similar way to handle exceptions. But in my case, different handlers are managed according to the error status (e.g. an user exists, an user cannot be registered due to some unsatisfied condition, etc.).
You also might add your generic BusinessException for some special cases. Hope it helps you feel better.
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.ResourceNotFoundException;
import com.rest.restwebservices.exception.PreconditionFailedException;
import com.rest.restwebservices.exception.ResourceAlreadyExistsException;
import com.rest.restwebservices.exception.fault.BusinessFault;
#ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceNotFoundException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.NOT_FOUND);
}
#ExceptionHandler(PreconditionFailedException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, PreconditionFailedExceptionex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.PRECONDITION_FAILED);
}
#ExceptionHandler(ResourceAlreadyExistsException.class)
#ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceAlreadyExistsException) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.CONFLICT);
}
}
Suppose I have a JAX-RS web service like this:
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
#Path("/somePath/{id}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class SomeObject {
#PathParam("id")
private String id;
#GET
#Path("/something")
public String something() {
DbObject dbObject = new DbObject(id);
// return something
}
#POST
#Path("/somethingElse")
public void somethingElse(Arg1 arg1, Arg2 arg2) {
DbObject dbObject = new DbObject(id);
// do something else with it
}
...
}
The very first line in almost all my methods is creating my dbObject.
Is there a way to do that immediately after id is set?
Can I do that in the id setter? Will the setId method be called instead of populating the value of the id variable?
Or what other option do I have?
Quoting the #PathParam documentation:
The type of the annotated parameter, field or property must either:
Be PathSegment, the value will be the final segment of the matching part of the path. See UriInfo for a means of retrieving all request path segments.
Be List<javax.ws.rs.core.PathSegment>, the value will be a list of PathSegment corresponding to the path segment(s) that matched the named template parameter. See UriInfo for a means of retrieving all request path segments.
Be a primitive type.
Have a constructor that accepts a single String argument.
Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String)).
Have a registered implementation of ParamConverterProvider JAX-RS extension SPI that returns a ParamConverter instance capable of a "from string" conversion for the type.
If you meet one of the above criteria, you will be able to use:
#PathParam("id")
private DbObject dbObject;
Let's focus in the three last approaches. First, using a constructor with a single String argument:
public class DbObject {
private String id;
public DbObject(String id) {
this.id = id;
}
...
}
Alternatively you can use a valueOf(String) method:
public class DbObject {
private String id;
public DbObject(String id) {
this.id = id;
}
public static DbObject valueOf(String id) {
return new DbObject(id);
}
...
}
Or define a ParamConverterProvider:
#Provider
public class DbObjectParamConverterProvider implements ParamConverterProvider {
#Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType,
Annotation[] annotations) {
if (rawType.getName().equals(DbObject.class.getName())) {
return new ParamConverter<T>() {
#Override
public T fromString(String value) {
return rawType.cast(new DbObject(value));
}
#Override
public String toString(T value) {
return ((DbObject) value).getId();
}
};
}
return null;
}
}