Jersey and Odata Key Path Param format - jersey

I have a RESTful api using Jersey right now, and am converting it to be OData standard compliant. There are a few things I have not converted yet, but will get there, and is not important at this moment. One of the things I need to convert that is important is the key path params. Odata has the standard of making the key wrapped in parenthesis. So in this example myapi.com/product(1) - is the OData call to get a product whose id is 1. Currently that is possible in my system with this myapi.com/product/1
When I add the parenthesis to the path parameter I get a 404 error. My class level path is #Path("/product") and my method level path is #Path("({id})"), and use to be #Path("/{id}"). I've tried adding the parenthesis as part of the variable planning to strip them off in the method, and I've tried formatting the id with some regex #Path("{id : regex stuff}"), and neither works.
If I make my method path parameter like this #Path"/({id})") - so the call is myapi.com/product/(1), it works fine. The parenthesis is not the issue obviously. It seems the Jersey splits the uri into chunks using the forward slashes for the routing, and sense there is no forward slash between the id an root resource name, then nothing is found. It makes sense.
Is there a way to change Jerseys method of matching uri strings with some regex or something? Has anyone used Jersey with Odata? I would rather not use odata4j just for the resolution to this issue, it seems like there should be a way to get this to work.
What I did:
Based on Pavel Bucek's answer I did implement a ContainrRequestFilter independently to the filter I use for security. In my case I didn't look to see if existed, I just tried to do the replace.
try
{
String uriString = request.getRequestUri().toString();
uriString = uriString.replaceAll("(\(|\)\/?)", "/");
request.setUris(request.getBaseUri(), new URI(uriString));
} catch (final Exception e)
{
}
return request;

I think that the easiest way how to handle this "protocol" would be introducing ContainerRequestFilter, which would replace "()$" with "/$" in the incoming URI. So you will be able to serve OData and standard REST request in one app.
See http://jersey.java.net/nonav/apidocs/1.11/jersey/com/sun/jersey/spi/container/ContainerRequestFilter.html
Simple filter I used to test this case:
rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
try {
if(request.getRequestUri().toString().endsWith("(1)")) {
request.setUris(
request.getBaseUri(),
new URI(request.getRequestUri().toString().replace("(1)", "/1")));
}
} catch (Exception e) {
}
return request;
}
});
both
curl "http://localhost:9998/helloworld(1)"
curl "http://localhost:9998/helloworld/1"
hit same Resource method now. (Obviously you'll need to improve current filter to be able to handle various values, but it should work for you).

Related

Spring Boot allow Square brackets for nested objects in Request Parameters

I'm using Spring Boot 2.6.7 and org.springdoc:springdoc-openapi-ui:1.6.7 to run swagger ui with OpenApi 3 definition along with my backend.
My Setup
#GetMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
public String getHello(HelloInput input) {
return "";
}
public class HelloInput {
public NestedInput nested;
// getter setter omitted
}
public class NestedInput {
public boolean a = true;
// getter setter omitted
}
This produces the following request when trying it out in swagger ui:
curl -X 'GET' \
'http://localhost:8080/api/hello?nested[a]=true' \
-H 'accept: application/json'
The Problem
However, when I execute this call I get the following Exception:
org.springframework.beans.InvalidPropertyException: Invalid property 'nested[a]' of bean class [com.proj.App.rest.explore.HelloInput]: Property referenced in indexed property path 'nested[a]' is neither an array nor a List nor a Map; returned value was [com.proj.App.rest.explore.NestedInput#68d1c7c4]
What do I need
The square braces notation causes this issue. The same call with dot notation does not cause exceptions:
curl -X 'GET' \
'http://localhost:8080/api/hello?nested.a.=true' \
-H 'accept: application/json'
Is there a way to either:
A: Configure Spring Boot in a way that it accepts the square brackets notation also for object properties and not just maps, lists etc.
OR
B: Configure Swagger UI in a way that it uses the dot notation when constructing the calls?
What did I already try
I already did a lot of research, but could not find other people with this problem.
I found a feature request for Spring to support squared brackets, but it was rejected: https://github.com/spring-projects/spring-framework/issues/20052
I found basically the same question, but the answers seem to be outdated, since the RelaxedDataBinder does not seem to be a part of Spring Boot 2.6.7 anymore: Customize Spring #RequestParam Deserialization for Maps and/or Nested Objects
Other than that, no one else seems to have this problem. Am I completely misunderstanding how Spring Boot handles Request Parameters and this problem occurs because I'm breaking some convention on how to handle GET Requests with many parameters?
I have the same problem and can't find any good solution. I have found only one more hack. So you can reformat request param json with dot-separated fields. It is a little bit inconvenient but works for me.
For example, by default for the controller in question, the request param input form will have json that will look like this:
{
"nested": {
"a": true
}
}
But you can use
{
"nested.a": true
}
to get what you want without any even more hacky changes(as I see this) to swagger-ui or spring components.
CAUTION: Hacky workaround for making swagger ui use dots instead of squared braces (probably breaks in a lot of scenarios)
Swagger Ui offers to define a requestInterceptor, that takes outbound requests and offers the user to change them.
When using Springdoc, swagger ui downloads a swagger-initializer.js for configuration of swagger ui.
This file is generated by an instance of a SwaggerIndexPageTransformer. The default instance already creates a requestInterceptor for CORS requests. I took that code and adapted it to change all requests with the squared braces notation to the dot notation.
For reference see AbstractSwaggerIndexTransformer#addCRSF
Workaround
First define a Configuration that offers a SwaggerIndexTransformer Bean:
#Configuration
public class OpenApiConfig {
#Bean
public SwaggerIndexTransformer swaggerIndexTransformer(
SwaggerUiConfigProperties a,
SwaggerUiOAuthProperties b,
SwaggerUiConfigParameters c,
SwaggerWelcomeCommon d) {
return new CustomSwaggerIndexTransformer(a, b, c, d);
}
}
Next implement the CustomSwaggerIndexTransformer, that now edits the swagger-initializer.js to add a requestInterceptor function, that replaces every [ with a . and removes all ].
public class CustomSwaggerIndexTransformer extends SwaggerIndexPageTransformer {
private static final String PRESETS = "presets: [";
...
#Override
protected String defaultTransformations(InputStream inputStream) throws IOException {
String html = super.defaultTransformations(inputStream);
html = addDottedRequests(html);
return html;
}
protected String addDottedRequests(String html) {
String interceptorFn = """
requestInterceptor: (request) => {
request.url = request.url.replaceAll("[", ".").replaceAll("]", "");
return request;
},
""" + PRESETS;
return html.replace(PRESETS, interceptorFn);
}
}
Limitations
This probably breaks working behavior when swagger ui uses squared braces for Maps, Arrays and Lists
This does not work when CORS is enabled for springdoc, since it also defines a requestInterceptor, in that case the javascript should somehow be incorporated into the CORS requestInterceptor function.

Mirror #RequestPart behavior in WebFlux functional router definitions with different content types

Problem
We're developing a Spring Boot service to upload data to different back end databases. The idea is that, in one multipart/form-data request a user will send a "model" (basically a file) and "modelMetadata" (which is JSON that defines an object of the same name in our code).
We got the below to work in the WebFlux annotated controller syntax, when the user sends the "modelMetadata" in the multipart form with the content-type of "application/json":
#PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
fun saveModel(#RequestPart("modelMetadata") monoModelMetadata: Mono<ModelMetadata>,
#RequestPart("model") monoModel: Mono<FilePart>,
#RequestHeader headers: HttpHeaders) : Mono<ResponseEntity<ModelMetadata>> {
return modelService.saveModel(monoModelMetadata, monoModel, headers)
}
But we can't seem to figure out how to do the same thing in Webflux's functional router definition. Below are the relevant code snippets we have:
#Bean
fun modelRouter() = router {
accept(MediaType.MULTIPART_FORM_DATA).nest {
POST(ROOT, handler::saveModel)
}
}
fun saveModel(r: ServerRequest): Mono<ServerResponse> {
val headers = r.headers().asHttpHeaders()
val monoModelPart = r.multipartData().map { multiValueMap ->
it["model"] // What do we do with this List<Part!> to get a Mono<FilePart>
it["modelMetadata"] // What do we do with this List<Part!> to get a Mono<ModelMetadata>
}
From everything we've read, we should be able to replicate the same functionality found in the annotation controller syntax with the router functional syntax, but this particular aspect doesn't seem to be well documented. Our goal was to move over to use the new functional router syntax since this is a new application we're developing and there are some nice forward thinking features/benefits as described here.
What we've tried
Googling to the ends of the Earth for a relevant example
this is a similar question, but hasn't gained any traction and doesn't relate to our need to create an object from one piece of the multipart request data
this may be close to what we need for uploading the file component of our multipart request data, but doesn't handle the object creation from JSON
Tried looking at the #RequestPart annotation code to see how things are done on that side, there's a nice comment that seems to hint at how they are converting the parts to objects, but we weren't able to figure out where that code lives or any relevant example of how to use an HttpMessageConverter on the ``
the content of the part is passed through an {#link HttpMessageConverter} taking into consideration the 'Content-Type' header of the request part.
Any and all help would be appreciated! Even just some links for us to better understand Part/FilePart types and there role in multipart requests would be helpful!
I was able to come up with a solution to this issue using an autowired ObjectMapper. From the below solution I could turn the modelMetadata and modelPart into Monos to mirror the #RequestPart return types, but that seems ridiculous.
I was also able to solve this by creating a MappingJackson2HttpMessageConverter and turning the metadataDataBuffer into a MappingJacksonInputMessage, but this solution seemed better for our needs.
fun saveModel(r: ServerRequest): Mono<ServerResponse> {
val headers = r.headers().asHttpHeaders()
return r.multipartData().flatMap {
// We're only expecting one Part of each to come through...assuming we understand what these Parts are
if (it.getOrDefault("modelMetadata", listOf()).size == 1 && it.getOrDefault("model", listOf()).size == 1) {
val modelMetadataPart = it["modelMetadata"]!![0]
val modelPart = it["model"]!![0] as FilePart
modelMetadataPart
.content()
.map { metadataDataBuffer ->
// TODO: Only do this if the content is JSON?
objectMapper.readValue(metadataDataBuffer.asInputStream(), ModelMetadata::class.java)
}
.next() // We're only expecting one object to be serialized from the buffer
.flatMap { modelMetadata ->
// Function was updated to work without needing the Mono's of each type
// since we're mapping here
modelService.saveModel(modelMetadata, modelPart, headers)
}
}
else {
// Send bad request response message
}
}
Although this solution works, I feel like it's not as elegant as the one alluded to in the #RequestPart annotation comments. Thus I will accept this as the solution for now, but if someone has a better solution please let us know and I will accept it!

Order in which filters are applied to Inbound ftp adapters in spring integration

I am using spring integration inbound channel adapter as below
inboundAdapter(csf).preserveTimestamp(true)//
.remoteDirectory(feed.getRemoteDirectory())//
.regexFilter(feed.getRegexFilter())// regex expression
.filter(ftpRemoteFileFilter)// remote filter
.deleteRemoteFiles(feed.getDeleteRemoteF
So I am using a remote filter and the out of the box regex filter . I wanted to know what is the order in which the regex filter and the remote filter are applied . From initial analysis looks like the regex filter comes first , can some one tell me the clas where this decision is made so I can be sure .
If there is no way of knowing the only other alternative will be to use the
CompositeFileListFilter .
The code you looking for is in the FtpInboundChannelAdapterSpec and looks like:
#Override
public FtpInboundChannelAdapterSpec regexFilter(String regex) {
return filter(composeFilters(new FtpRegexPatternFileListFilter(regex)));
}
#SuppressWarnings("unchecked")
private CompositeFileListFilter<FTPFile> composeFilters(FileListFilter<FTPFile> fileListFilter) {
CompositeFileListFilter<FTPFile> compositeFileListFilter = new CompositeFileListFilter<>();
compositeFileListFilter.addFilters(fileListFilter,
new FtpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "ftpMessageSource"));
return compositeFileListFilter;
}
So, as you see when you declare regexFilter, it is composed together with the FtpPersistentAcceptOnceFileListFilter to the the CompositeFileListFilter, where regexFilter is definitely first. And it is first because FtpPersistentAcceptOnceFileListFilter is persistence and that wouldn't be good to store files which might not match the regexp afterwards.
If you need some more complicated logic, you really should go CompositeFileListFilter way and inject it via filter() option only. I mean you have to combine your regexpFilter into the CompositeFileListFilter instead of regexFilter().
Note: after moving Java DSL into Core in 5.0, the .filter() option looks like:
public S filter(FileListFilter<F> filter) {
this.synchronizer.setFilter(filter);
return _this();
}
It overrides any previously provided filters, including regexp. That is done to avoid confusing with the chain of .filter() in favor of CompositeFileListFilter or ChainFileListFilter configured externally.

use camel case serialization only for specific actions

I've used WebAPI for a while, and generally set it to use camel case json serialization, which is now rather common and well documented everywhere.
Recently however, working on a much larger project, I came across a more specific requirement: we need to use camel case json serialization, but because of backward compatibility issues with our client scripts, I only want it to happen for specific actions, to avoid breaking other parts of the (extremely large) website.
I figure one option is to have a custom content type, but that then requires client code to specify it.
Is there any other option?
Thanks!
Try this:
public class CamelCasingFilterAttribute : ActionFilterAttribute
{
private JsonMediaTypeFormatter _camelCasingFormatter = new JsonMediaTypeFormatter();
public CamelCasingFilterAttribute()
{
_camelCasingFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
ObjectContent content = actionExecutedContext.Response.Content as ObjectContent;
if (content != null)
{
if (content.Formatter is JsonMediaTypeFormatter)
{
actionExecutedContext.Response.Content = new ObjectContent(content.ObjectType, content.Value, _camelCasingFormatter);
}
}
}
}
Apply this [CamelCasingFilter] attribute to any action you want to camel-case. It will take any JSON response you were about to send back and convert it to use camel casing for the property names instead.

AJAC MVC3 Request object and raw Ajax data, where the heck is it?

If this was a regular post of a form I could go to Request.Form['somevalue'] and get the value. If this was a get with a query string I could go to Request.QueryString["somevalue"] and get the value.
Where is the raw data when you post an ajax request. I need a value out of the raw data string in a filter method.
Any help will be appreciated!!
Edits below:
public class ValidateAntiForgeryId : FilterAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
Guid filterGuid;
Guid.TryParse(filterContext.RequestContext.HttpContext.Request.Form["__sessionId"], out filterGuid);
if (filterGuid == Guid.Empty)
throw new AuthenticationException("Authentication failure");
try {
var cookieGuid = (Guid)filterContext.RequestContext.HttpContext.Items["SessionId"];
} catch {
throw new AuthenticationException("Authentication failure");
}
}
The posted data looks like this:
{"SsnLastFour":"2222","AccountNumber":"B112233","__sessionId":"dca0a504-3c40-4118-ae19-afefb9bfc8bd"}
I need access to the __sessionId chunk inside the filter.
There's nothing magic about AJAX posts. They're just plain old HTTP. That means you have plain old HTTP post values, and/or plainold HTTP Get values.
If you're not seeing them, it probably means you're not actually submitting them.
EDIT:
Two issues you did not include in your original question: 1) That this is JSON, and 2) That this is in an AuthorizationFilter (rather than an action method).
Both change the answers. Since ASP.NET does not natively understand JSON post values, you will have to parse them, via Request.InputStream. MVC3 by default has a JSON model binder, but AuthorizationFilters execute before model binders do, so you will be accessing things prior to the model binders being executed, and as such FormsCollection won't be populated (Request.Form[] won't work either, because as I said, asp.net doesn't natively understand JSON).
You may find that installing JSON.net via nuget may help with this task. Or you might just write a simple parse routine, since you know exactly what you're looking for.
You can accept the parameter values the same way you accept in normal form post.
Ex :
$.get("User/Get", { userId : "24"} ,function(data){
alert(data);
});
or
$("#yourDivId").load("User/Get?userId=23");
Your action method should look like
public ActionResult Get(int userId)
{
// you have value present in userId
if(Request.IsAjax())
{
return View("Partial/MyPartialView");
}
return View();
}
One thing you have to remember is, the parameter name of your action method should be same as of what your parameter/querystring name.
The fitlerContext has an ActionParameters collection which should have the parsed JSON properties (in case that helps). This may be easier than parsing the InputStream.
var sessionId = filterContext.ActionParameters["__sessionId"];

Resources