Jersey: forbid unspecified parameters - java

I'm developing REST API with Jersey as JAX-RS implementation.
In every resource I explicitly define expected parameters:
#GET
#Path("/someData")
public Response getSomeData(
#QueryParam("id") final Long id,
#QueryParam("name") final String name) {
...
}
There are a number of fixed parameters, which are common for all resources (e.g. "locale").
Is there any way (I'm ok with introducing Jersey-specific dependencies) I can forbid any parameters that belong neither to method parameters nor to the common parameters?
So for example if user invokes
/api/resource/someData?id=10&locale=en - he gets the data, but if he invokes
/api/resource/someData?id=10&locale=en&fakeParam=AAA - status 400 is returned, with content stating that fakeParam is unknown parameter.
Currently second request is processed the same way as the first one, and fakeParam is simply ignored.
I think described validation will help users of my API to spot bugs earlier.

I don't know of any way to do this with JAX-RS but you could easily roll your own solution. This is a bit cumbersome but you could do something like:
#Path("/api")
public class Service {
#Context
UriInfo uriInfo;
ImmutableSet<String> commonParams = ImmutableSet.of("locale");
#GET
#Path("validate")
#Produces(MediaType.APPLICATION_JSON)
public String validate(#QueryParam("foo") String param) {
Set<String> validParams = newHashSet(commonParams);
class Local {};
for (Annotation[] annotations: Local.class.getEnclosingMethod().getParameterAnnotations()) {
for (Annotation annotation: annotations) {
if (annotation instanceof QueryParam) {
validParams.add(((QueryParam)annotation).value());
}
}
}
if (!difference(uriInfo.getQueryParameters().keySet(), validParams).isEmpty()) {
//throw an unknown parameter exception
}
return "hello";
}
And if you're using Guice or some other AOP tool with Jersey you could probably put this into an aspect s.t. you wouldn't have to add boilerplate to every method you want to validate.

Related

Hystrix fallback method best practice

Ok so I couldn't find any helpful materials on this topic, a big chunk of articles I found had one method that was annotated with #HystrixCommand and had defined a fallback method.
The other solution I found was using #DefaultProperties(defaultFallback = "fallbackMethod") but the problem with this is that the methods need to have compatible return types.
Unfortunately for me in my service I have many methods with completely different signatures and also I need to get hold of the throwable (in docs it is mentioned that you cannot have any parameters for a default fallback method). The methods look something like this:
#Service
#RequiredArgsConstructor
public class MyService {
private final FeignClient feignClient;
#Override
public String methodA(final CustomObjectA o, final String entity) {
...
}
#Override
public String methodB(final String collection, final Map<String, Object> requestBody) {
...
}
#Override
public String methodC(final String collection, final String id, final Map<String, Object> requestBody) {
...
}
}
And ofc I have more than 3 methods def in the service...
The thing I really want to avoid is making 20 hystrix default fallback methods.
Is there a way where I could def a standard fallback for all methods, no matter what the signatures they have, or am I stuck with defining a fallback method for every single method?
Thanks in advance!!
You will have to implement a fall back for each method.
However using the FallbackFactory might make this easier and allow each method to call one reusable method.
Maybe you don't really want hystrix fallbacks if they are the same for each method. All try catch might solve the same problem.
Let me share the code snippet used in my project.
To call an api like http://www.baidu.com/xxx, you have below steps to follow.
1.Api Definition (fallback = WebServiceApiFallback.class)
#Component
#FeignClient(value = "webServiceApi", configuration = FeignConfiguration.class, fallback = WebServiceApiFallback.class)
public interface WebServiceApi {
#Headers(value = {"Content-Type: application/json", "Accept-Encoding: gzip,deflate"})
#GetMapping(value = "/xxx")
BaseResponse<YourResponse> xxx(YourRequest request);
2.Fallback Definition
#Component
public class WebServiceApiFallback implements WebServiceApi {
#Override
public BaseResponse<YourResponse> xxx(YourRequest request) {
// Your Fallback Code here, when api request failed.
}
3.api host configuration, maybe application.properties...
webServiceApi.ribbon.listOfServers=http://www.baidu.com
4.use it
#Autowired
private WebServiceApi webServiceApi;
For any api, you can just define you request and response, and feign will do the request、 encode、and decode.
[Ref] https://github.com/spring-cloud/spring-cloud-netflix/issues/762

Spring Data Rest validation for request with dynamic body

I am newbie to Spring related technologies.
I choose Spring Data Rest to implement a web annotation server. According to the standard, an annotation should be represented by JSON-LD, which means you can't bind the request to any of your domain object as the field names are changeable. (In C#, it is ok to bind it to dynamic). You just need to convert it to some-defined type before persisting to db.
Before converting it, I want to validate the request body.
I use:
#Service
public class AnnotationValidator implements Validator{
#Autowired
private Processor ldProcessor;
#Override
public boolean supports(Class<?> aClass) {
return AnnotationDocument.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
Object processedAnnotation;
try {
processedAnnotation = ldProcessor.extractAnnotationModel(o);
} catch (JsonLdError jsonLdError) {
jsonLdError.printStackTrace();
}
}
}
In validate method, the Object o does not represent the request body. Indeed, it tries to cast the request body to AnnotationDocument, so I cannot validate it.
Finally, my question is:
How can I process the pure request body and check its fields?
I solved my problem by creating #RepositoryRestController but I think it should be simpler way.

Grizzly REST - POSTs are always 404

In my endpoint, I have some methods with #GET and some methods with #POST. #GETs are working fine, but #POSTs always return 404.
Here is some part from the endpoint's interface:
public interface TestEndpoint {
#GET
#Path("/ping")
Response ping();
#POST
#Path("/weather/{iata}/{pointType}")
Response updateWeather(#PathParam("iata") String iataCode,
#PathParam("pointType") String pointType,
String datapointJson);
#POST
#Path("/airport/{iata}/{lat}/{long}")
Response addAirport(#PathParam("iata") String iata,
#PathParam("lat") String latString,
#PathParam("long") String longString);
#GET
#Path("/exit")
Response exit();
}
Here is the server initialization part:
public class TestServer {
private static final String BASE_URL = "http://localhost:9090/";
public static void main(String[] args) {
try {
final ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(TestEndpointImpl.class);
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URL), resourceConfig, false);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
server.shutdownNow();
}));
HttpServerProbe probe = new HttpServerProbe.Adapter() {
public void onRequestReceiveEvent(HttpServerFilter filter, Connection connection, Request request) {
System.out.println(request.getRequestURI());
}
};
server.getServerConfiguration().getMonitoringConfig().getWebServerConfig().addProbes(probe);
server.start();
Thread.currentThread().join();
server.shutdown();
} catch (IOException | InterruptedException ex) {
Logger.getLogger(TestServer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
where, TestEndpointImpl is an implementation of TestEndpoint (as the name implies) with class-level annotation #Path("/collect").
When I perform GET requests, it works fine. But POSTs are problematic. Corresponding methods are not called.
As a side note, probe prints both GET and POST requests as expected, so I am sure that requests reach the server and paths are ok.
Is there any suggestion?
EDIT: Some snippet from the implementation:
#Path("/collect")
public class TestEndpointImpl implements TestEndpoint {
...
#Override
public Response updateWeather(#PathParam("iata") String iataCode, #PathParam("pointType") String pointType,
String datapointJson) {
System.out.println("TRACE: " + datapointJson);
// do something and return a Response
}
...
}
The registered probe prints /collect/weather/BOS/wind, but updateWeather is not called.
Short answer
Copy the #POST and the #Path annotations to the method implementation. It will do the trick.
Long answer
The section regarding annotation inheritance of the JAX-RS 2.0 specification (the specification which Jersey is the reference implementation) is pretty clear. See the quote below:
3.6 Annotation Inheritance
JAX-RS annotations may be used on the methods and method parameters of a super-class or an implemented interface. Such annotations are inherited by a corresponding sub-class or implementation class method provided that the method and its parameters do not have any JAX-RS annotations of their own. Annotations on a super-class take precedence over those on an implemented interface. The precedence over conflicting annotations defined in multiple implemented interfaces is implementation specific. Note that inheritance of class or interface annotations is not supported.
If a subclass or implementation method has any JAX-RS annotations then all of the annotations on the superclass or interface method are ignored. E.g.:
public interface ReadOnlyAtomFeed {
#GET
#Produces("application/atom+xml")
Feed getFeed();
}
#Path("feed")
public class ActivityLog implements ReadOnlyAtomFeed {
public Feed getFeed() {...}
}
In the above, ActivityLog.getFeed inherits the #GET and #Produces annotations from the interface. Conversely:
#Path("feed")
public class ActivityLog implements ReadOnlyAtomFeed {
#Produces("application/atom+xml")
public Feed getFeed() {...}
}
In the above, the #GET annotation on ReadOnlyAtomFeed.getFeed is not inherited by ActivityLog.getFeed and it would require its own request method designator since it redefines the #Produces annotation.
For consistency with other Java EE specifications, it is recommended to always repeat annotations instead of relying on annotation inheritance.
That can also happen if the url is not in the correct format; for example you could have sent a request without the correct path parameters.

Equivalent of Spring MVC #ResponseStatus(HttpStatus.CREATED) in Jersey?

What's the Jersey equivalent of this Spring MVC code? I need the response to return 201 along with the resource URL, following successful POST:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
Widget create(#RequestBody #Valid Widget wid) {
return service.create(wid);
}
This is the shortest example I found in Jersey. Is it required to build the response manually for successful POST/201?
#POST #Path("widget")
Response create(#RequestBody #Valid Widget wid) {
return Response
.status(Response.Status.CREATED)
.entity("new widget created")
.header("Location","http://localhost:7001/widget"+wid)
.build();
}
Example of comment, per request of OP:
I don't think there is an equivalent, but personally, I like creating my own response. I have more control. Also there is a Response.created(...), this will automatically set the status. It accepts the URI or String as an argument, and sets the location header with that argument. Also You can use UriInfo to getAbsolutePathBuilder() then just append the created id. That's generally the way I go about it.
#Path("/widgets")
public class WidgetResource {
#Inject
WidgetService widgetService;
#POST
#Consumes(...)
public Response createWidget(#Context UriInfo uriInfo, Widget widget) {
Widget created = widgetService.createWidget(widget);
UriBuilder builder = uriInfo.getAbsolutePathBuilder();
URI uri = builder.path(created.getId()).build();
return Response.created(uri).build();
}
}
This is the general pattern I use for my create methods. The collection path will be the absolute path obtained from uriInfo.getAbsolutePath(Builder), then you just append the created id to the path. So if the collection path is http://blah.com/widgets, and the id is someId, then the location header will be Location: http://blah.com/widgets/someId (which is the location of the new resource), and the status will get set to 201 Created
Response.created(..) returns Response.ResponseBuilder, just like Response.status, so you can do the usual method chaining. There are a number of static method on Response that have default settings, like ok, noContent. Just do through the API. Their names pretty much match up with the status name.
I don't think there is an annotation like that in Jersey. You could create one using Name Binding.
Basically, you create an annotation and add the #NameBinding meta-annotation:
#NameBinding
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ResponseStatusCreated {}
Next you create an filter which will override the status.
#ResponseStatusCreated
#Provider
class StatusCreatedFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
responseContext.setStatusInfo(Response.Status.CREATED)
String location = "..."; // set based on responseContext.getEntity()
// or any other properties
responseContext.getHeaders().putSingle("Location", location);
}
}
Then use the same annotation on your resource methods.
#POST
#Path("widget")
#ResponseStatusCreated
Object create(#RequestBody #Valid Widget wid) {
return ... // return whatever you need to build the
// correct header fields in the filter
}
You could also make it more generic by creating an annotation that will accept the status as an argument, i.e. #ResponseStatus(Status.CREATED) and get the status in the filter using responseContext.getAnnotations().

is it possible to call one jax-rs method from another?

suppose i have some jax-rs resource class:
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class ResourceA {
#GET
public Something get(#Context UriInfo uriInfo) {
if (...) {
//how to get to ResourceB ?
}
}
}
and i want to conditionally redirect the call to some other jax-rs resource:
public class ResourceB {
#GET
#Path("{identifier}")
public Other get(#PathParam("identifier")String someArg) {
}
}
how do i do this?
note that i dont want this to be visible to the client (so no http redirects) and generally the resource methods i want to redirect to dont share the same signature (they may have path params etc as in the example i gave).
im running jersey 2.6 under apache tomcat (its a spring app, if thats any help)
EDIT - im looking for a jax-rs equivalent of servlet forward. i dont want to do an extra http hop or worry abour instantiating resource classes myself
You can get it using ResourceContext as follows:
#Context
ResourceContext resourceContext;
This will inject the ResourceContext into your Resource. You then get the resource you want using:
ResourceB b = resourceContext.getResource(ResourceB.class);
The Javadoc for ResourceContext is here. You can find a similar question here
I'm not aware of any possibility to do this from a resource method, but if it fits your use case, what you could do is implement your redirect logic in a pre matching request filter, for example like so:
#Provider
#PreMatching
public class RedirectFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) {
UriInfo uriInfo = requestContext.getUriInfo();
String prefix = "/redirect";
String path = uriInfo.getRequestUri().getPath();
if (path.startsWith(prefix)) {
String newPath = path.substring(prefix.length());
URI newRequestURI = uriInfo.getBaseUriBuilder().path(newPath).build();
requestContext.setRequestUri(newRequestURI);
}
}
}
This will redirect every request to /redirect/some/resource to /some/resource (or whatever you pass to requestContext.setRequestUri()) internally, before the resource method has been matched to the request and is executed and without http redirects or an additional internal http request.

Categories