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;
}
}
Related
Have a problem with optimizing search request.
I have search method that accepts parameters in url query like:
http://localhost:8080/api?code.<type>=<value>&name=Test
Example: http://localhost:8080/api?code.phone=9999999999&name=Test
Defined SearchDto:
public class SearchDto {
String name;
List<Code> code;
}
Defined Code class:
public class Code {
String type;
String value;
}
Currently I'm using Map<String,String> as incoming parameter for the method:
#GetMapping("/search")
public ResponseEntity<?> search(final #RequestParam Map<String, String> searchParams) {
return service.search(searchParams);
}
Then manually converting map values for SearchDto class. Is it possible to get rid of Map<String,String> and pass SearchDto directly as argument in controller method?
Passing a json in querystring is actually a bad practice, since it decrease the security and sets limits on the number of parameters you can send to your endpoint.
Technically speaking, you could make everything work by using your DTO as a controller's parameter, then URL encoding the json before you send it to the backend.
The best option, in your case, is to serve an endpoint that listen to a POST request: it is not an error, neither a bad practise, to use POST when performing a search.
you can customize a HandlerMethodArgumentResolver to implement it.
but , if you want a object receive incoming parameter. why not use POST
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Example {
}
public class ExampleArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
Example requestParam = parameter.getParameterAnnotation(Example.class);
return requestParam != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, #Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, #Nullable WebDataBinderFactory binderFactory) throws Exception {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
Map<String, String[]> parameterMap = webRequest.getParameterMap();
Map<String, String> result = CollectionUtils.newLinkedHashMap(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
}
});
//here will return a map object. then you convert map to your object, I don't know how to convert , but you have achieve it.
return o;
}
}
add to container
#Configuration
#EnableWebMvc
public class ExampleMvcConfiguration implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new ExampleArgumentResolver());
}
}
usage
#RestController
public class TestCtrl {
#GetMapping("api")
public Object gg(#Example SearchDto searchDto) {
System.out.println(searchDto);
return "1";
}
#Data
public static class SearchDto {
String name;
List<Code> code;
}
#Data
public static class Code {
String type;
String value;
}
}
Here is a demo.
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 have the following entity:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNER)
public class FilePartner
{
#XmlAttribute(name = EntityConstants.IDENTIFIER, required = true)
#XmlJavaTypeAdapter(RestResourceIdJaxbAdapter.class)
private String identifier;
...
}
Here is the jaxb adapter:
public class RestResourceIdJaxbAdapter extends XmlAdapter<String, String>
{
#Override
public String unmarshal(String v) throws Exception
{
if (v != null && v.contains("/"))
{
// throw new ApiException(Status.BAD_REQUEST, RestErrorMessages.BAD_REQUEST_SUFFIX, "Identifier must not contain slashes");
return v.replaceAll("/", "");
}
return v;
}
#Override
public String marshal(String v) throws Exception
{
return v;
}
}
I have a jaxrs service that accepts POST requests with body FilePartner:
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createPartner(FilePartner partner,
#Context UriInfo uriInfo,
#HeaderParam(HttpHeaders.ACCEPT) String acceptHeader)
throws ApiException
{
...
}
What I want to achieve is to forbid the usage of slashes '/' in the identifier attribute of the FilePartner entity.
Today I am doing this using some jaxb adapter which simply strips all slashes from the id when unmarshalling.
Instead, what I would like is to return an appropriate BAD_REQUEST exception to the user.
I tried throwing exception in the unmarshal method of the jaxb adapter but seems that jaxrs is swallowing it and simply setting my identifier to null.
If we want to override this behavior I think I must create a new #Provider and register a special ValidationEventHandler in the unmarshaller that the javax.ws.rs.ext.MessageBodyReader creates.
Unfortunately, this is impossible unless I define an explicit dependency to a JAX-RS implementation which I want to avoid.
Are there any other options to restrict the usage of slashes in the identifier attribute, without defining an explicit dependency to jersey/resteasy and without handling the restriction in the #POST method of the service?
To your rescue comes ReaderInterceptor
Don't do any special handling using #XmlJavaTypeAdapter. Register a ReaderInterceptor with your Application class (if in jersey2) or in web.xml if earlier.
import java.io.IOException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
javax.ws.rs.ext.ReaderInterceptor
#Provider
public class ValidationInterceptor implements ReaderInterceptor {
public ValidationInterceptor() {
super();
}
#Override
public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext) throws IOException,
WebApplicationException {
Object o = readerInterceptorContext.proceed();
if (o instanceof FilePartner&& ((FilePartner) o).getIndentifier().contains("/")) {
throw new WebApplicationException(Response.status(400)
.entity("Identifier must not contain a slash")
.build());
}
return o;
}
}
And register the interceptor to your Application in override of public Set<Class<?>> getClasses() method something like classes.add(ValidationInterceptor.class);
Hope that helps.
My Spring Controller of Spring JSON application returns a JSONObject. On accessing the url, i am getting 406 error page.
It works when i return String or ArrayList.
Spring Controller:
package com.mkyong.common.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
#Controller
public class JSONController {
#RequestMapping("/test")
#ResponseBody
public JSONObject test() {
try {
JSONObject result = new JSONObject();
result.put("name", "Dade")
.put("age", 23)
.put("married", false);
return result;
} catch (JSONException ex) {
Logger.getLogger(JSONController.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
How can i resolve this issue? Thanks for help. I am new to Spring MVC, couldn't found resolution to this issue in the existing SO answers.
You're trying to manually do something that Spring MVC already it automatically for you. Spring automatically deduces a representation of the returning type and does a converstion. How it does it you can learn from http://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc. In your case its converting to JSON.
It works when i return String or ArrayList
What happens under the hood is that Spring MVC is using Jackson library, to convert the return type to JSON. And since it has no issue converting the String or List type, all works OK.
What happens in the code you've posted is that, Jackson's object mapper is trying to convert JSONObject instance to JSON, and this fails, cause jackson expects a POJO object which JSONObject instance isn't.
To have it work you should simply write your POJO and return it. So something like
public class Person {
private String name;
private Integer age;
private Boolean married;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getMarried() {
return married;
}
public void setMarried(Boolean married) {
this.married = married;
}
}
and have your method changed to
#RequestMapping("/test")
#ResponseBody
public Person test() {
Person person = new Person();
person.setName("Dade");
person.setAge(23);
person.setMarried(false);
return person;
}
For what concerns your error, the same exception you will see in the working example if you for example delete getters and setters, or name them wrongly, an exception happens while trying to convert to a representation and you get a 406 error
I think you need to set headers in #RequestMapping and return HashMap.
#RequestMapping(value = "json", method = RequestMethod.GET, headers = "Accept=application/json")
public #ResponseBody
Map<String, String> helloJson() {
HashMap<String, String> map = new HashMap<String, String>();
map.put("k1", "v1");
map.put("k2", "v2");
map.put("k3", "v3");
return map;
}
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");
}
}
}