MvcUriComponentsBuilder and inccorect binding in Spring MVC - java

Code
I have my Controller method like this:
#RequestMapping("/{userId}/{configId}")
def edit(#PathVariable("userId") User user,
#PathVariable("configId") Configuration configuration) {
/* some code here*/
}
When I call this method from my browser it works very well, and user, and configuration args bind from DataBase by their id.
Problem
But, when I used MvcUriComponentsBuilder class, I got some exceptions for incorrect arguments type in the expected method.
MvcUriComponentsBuilder.fromMethodName(MyController.class, "edit", 1, 2).build()
Exception
java.lang.IllegalArgumentException: source to convert from must be an instance of #org.springframework.web.bind.annotation.PathVariable User; instead it was a java.lang.Long
Faced some body with similar problem? Are there any solution?
NOTE: I'm currently using Spring Web MVC 4.1.8.RELEASE
A bit more descriptions
I'm using MvcUriComponentsBuilder in Thymeleaf template like this:
th:action="${#mvc.url('CC#edit').arg(0, configuration.access.id).arg(1, configuration.id).build()}
As docs says that in arg method should be passed native method arg (in my example should passed User and Configuration instances). But, in my opinion better solution is using long values (for example) and then autobind it to object as it expected (as like it works over browser call)
Additional info!
I listened to #Sotirios Delimanolis, and use this function as it described in it docs
th:action="${#mvc.url('CC#edit').arg(0, configuration.access.user).arg(1, configuration).build()
Passed arguments are correct in this example.
BUT, I have gotten exception AGAIN!
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type #org.springframework.web.bind.annotation.PathVariable #org.springframework.web.bind.annotation.ModelAttribute ua.smartsteamshop.web.app.domain.User to type java.lang.String
It is looks like a bug! Guys, I need your help! This question should be discussed!

Related

Logback add custom field/fields in all the logs

For a Spring application I want to add a custom field to the log.
Currently I use the default format but I want to add a custom field (category field) which should be present in all the logs:
W:Action D:2022-01-10 23:21:03.285 L:INFO C:c.h.l.LoggingDemoApplication F:StartupInfoLogger.java(61) Fn:logStarted T:main R: - Hello World
What are the best solution to add a custom field to the logback log?
What I studied until now are the following possible solutions:
Use marker. The disadvantage with this is that it's not scalable: if in future you need another custom field can't add another marker. Further based on some other posts the marker is best suited to mark special logs that need to be handle differently.
Use MDC.
Also using this it seems not the best solution because:
It keeps the context so if there are multiple log statements in the same function, before each logger.info() there should be MDC.put("category", "action")
The code becomes to verbose.
Create a custom convertor (link). Get the arguments from the ILoggingEvent, get argument of 0. If this is the same type as category enum, then use it. The call for this is like logger.info("Message here: {} {} {}", CatEnum.Action.getValue(), msg1, msg2, msg3).
Create some static method in which the final format is generated.
Pattern is similar to: <pattern>%m%n</pattern>
To log, something like this should be used: logger.info(customFormatter.fmtLog(CatEnum.Action.getValue(), msg)). The returned value of fmtLog should be all the info from default logging + the category field.
Are there any built in solutions to add a custom field?

Override ModelAttribute with Path Variable in Spring Framework

I'm developing API using Spring Framework and faced a problem that can be solved by simply adding a necessary logic to every place I have it, but I think that there might be an elegant solution to fix it.
I have the following method in my controller:
#GetMapping("/user/{userId}/permissions")
public List<PermissionDto> list(#PathVariable long userId,
#ModelAttribute #Valid PermissionCriteria criteria) {
return permissionService.list(criteria);
}
The thing is that in dto I have a field called userId. It's made not to have a lot of arguments going to the method of the service. But, I want this user id to be set exactly from path since I use the URL that specifies that we are adding permission exactly to specific user resource. It's doable by making addition line that uses setter in the criteria and sets the value of userId. However, now I should never forget to add this line every time I have a case like that. That's why I decided to move it to InitBinder:
#InitBinder(PERMISSIONS_CRITERIA_NAME)
public void permissionsCriteriaInitBinder(WebDataBinder binder) {
PermissionsCriteria criteria = (PermissionsCriteria) binder.getTarget();
Optional.ofNullable(requestHelper.getUserId())
.map(Long::parseLong)
.ifPresent(criteria::setUserId);
}
It works fine. The user ID is set from the path. However, If I specify request parameter and path variable at the same time, even though userId is set from the path in init binder, it's overridden afterwards before it goes to the controller method. So, this one doesn't solve all the issues.
What I want to find, is someplace where the logic can be put to apply to both init binder(I need it for validation) and controller method. Maybe there is a special type of hook or interceptor or at least something to implement to satisfy this conditions?

Passing array with in GAE Endpoints

I am just trying out the first example of GAE Endpoints, I modified the sample API Method to resemble this.
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String[] names) {
My expectation is to receive a array of strings.
Now when I use the Google API Explorer to test this, [https://apis-explorer.appspot.com/apis-explorer/]
it generates API like this
POST https://myprojectid.appspot.com/_ah/api/myApi/v1/sayHi/arg1/arg2/arg3?fields=data
It eventually returns 404 error. Since the endpoint is not recognized.
What am I doing wrong here? In fact explorer shows name as String not String[]. Any help is appreciated!
First things first: does this work when there is a single String parameter? There's some servlet mapping magic that needs to happen to expose endpoints, and if that is not present in the project, things won't work. See this link to make sure your web.xml is as it should be.
Looking at this link, it seems that if your method parameter is a basic type (not a real Java object), and if it is not specifically included in a #Path annotation, there's some uncertainty in what will happen in your Api:
Path parameters are the method parameters included in the path property of the #ApiMethod annotation. If path is unspecified, any parameters not annotated with #Nullable or #DefaultValue will be automatically added to the path (they will be path parameters).
So it seems that by not including "name" in a #Path annotation, the docs don't state what the format of the path will be. The generated descriptor that the Explorer is looking at seems to think the right answer is /names[0]/names[1]/names[2], kind of like C-style varargs. It might be this disconnect that causes your 404 to happen. Can you try by including "name" in a #Path annotation?
Instead of having an array as a parameter of the endpoint method, you should put an object (java bean) which contains an array as a property.
Then you get the object in your method and you just read the property and treat it as an array.
Edit after some more research, following your comment
Indeed when you try to pass an array as a Path parameter it doesn't work. The different elements of your array are added to the URL (as you show in your question) and it generates a 404 Not Found error. The trick is that you should pass this array as a Query parameter and not a Path Parameter. See this doc: https://cloud.google.com/appengine/docs/java/endpoints/parameter-and-return-types#path_parameters
And indeed, if you do something like that it works very well:
#ApiMethod(name = "sayHi",
path = "sayHiWithName")
public MyBean sayHi(#Named("name") String[] names) {
MyBean response = new MyBean();
response.setData("Hi, " + names[0] + names[1]);
return response;
}
Note that the parameter is NOT added to the path (i.e. we don't have a path like sayHiWithName/{name}).

Passing info message to a Wicket page while using PageParameters

Does Wicket somehow allow passing both of the following kinds of params in a PageParameters object? Apparently not?
accountId which is shown in the URL (/account/<ID>)
infoMessage parameter which is not shown in the (bookmarkable) URL
I'm currently using IndexedHybridUrlCodingStrategy for the page in question, and simply trying parameters "0" and "infoMessage" gives this exception:
WicketMessage: Not all parameters were encoded. Make sure all
parameter names are integers in consecutive order starting with zero.
Current parameter names are: [0, infoMessage]
If I change "infoMessage" parameter name into "1", it works, but yields an ugly URL (in this case something like /account/42/Tosite%20108207%20tallennettiin.5) which is not what I want.
Now, the obvious answer perhaps is that infoMessage shouldn't be in PageParameters. But thing is, I tried adding it as normal constructor parameter instead, like so:
public AccountPage(PageParameters parameters, String infoMessage) {
// ...
}
But this approach fails in one important use case. After deleting a persistent "Record" object related to the Account, the following does not load the AccountPage properly (the deleted record is still visible). This code is executed in onClick() of an AjaxFallbackLink.
setResponsePage(new AccountPage(AccountPage.pageParameters(account), message));
On the other hand, my original approach...
setResponsePage(AccountPage.class, AccountPage.pageParameters(account));
... works fine, as it somehow loads the AccountPage "more thoroughly", but, again, I don't know how to pass the infoMessage parameter cleanly.
(AccountPage.pageParameters() above is a simple static utility for creating appropriate PageParameters with "0" = account id. The AccountPage constructor always loads the account from persistence using the ID.)
Any ideas? Perhaps using AjaxFallbackLink partially causes the problem?
Using Wicket 1.4.
From what I see in your question, you try to render both a bookmarkable page and show a feedback message to the user (most probably in a FeedbackPanel), but you don't want that message to be part of the URL.
What you want to do is tell the Session that you have an informational message, and let the feedback panel handle the message.
#Override void onSubmit() {
... save object ...
getSession().info("Object ... has been saved");
setResponsePage(ObjectPage.class, new PageParameters("id="+object.getId()));
}
In this case you tell Wicket to temporarily store a message in the session, until it gets rendered by a feedback panel. This idiom is also known as "flash messages".
You can't use both PageParameters and another parameter as constructor arguments, because Wicket can't create your page instance with such a constructor when the page is requested. Wicket only knows how to instantiate pages with default constructors or pages with a PageParameters parameter.

Webservice problem - methods can't take more than 1 parameter

I'm using IntelliJ IDEA 8 and Axis to set up a webservice that's deployed on Tomcat5.5. The generated wsdl looks like this: http://track.priskick.se/Tracker.wsdl
A method is declared as
public void storeImpressionReport(int siteId, int adId, int zoneId, int count,
int excludeCount) { ... }
and exposed in the webservice. Next, I build the client (also Java) using Axis, but as a runtime call to the method is made with the parameters 0,0,0,0,0, I get this:
Tried to invoke method public void com.xxxxx.xxxx.xxxx.xxxxx.storeImpressionReport(int,int,int,int,int) with arguments java.lang.Integer,null,null,null,null. The arguments do not match the signature.; nested exception is: java.lang.IllegalArgumentException
Reducing the number of parameters of the method to 1 makes it work, however this feels like a pretty silly limitation and strange behaviour. Please help me if you know what might be wrong here - why can't I expose methods and have them take more than one parameter?
=== UPDATE
I now tried generating the client java using wsdl generated from IntelliJ instead of calling the service with the ?wsdl option. This wsdl keeps the correct parameter names, maybe because the generator has access to the source. Now I get
No such operation 'siteId'
AxisFault
These are the relevant files:
http://track.priskick.se/Tracker/TrackerSoapBindingStub.java
http://track.priskick.se/Tracker/TrackerServiceTestCase.java
http://track.priskick.se/Tracker/Tracker_PortType.java
http://track.priskick.se/Tracker/TrackerService.java
http://track.priskick.se/Tracker/TrackerServiceLocator.java
the wsdl used for the client is found at
http://track.priskick.se/Tracker.wsdl
the service is found at
http://stage.klikki.com/services/Tracker
Cheers
Marcus Johansson
Oh the joy. I changed the service style to WRAPPED, and this seems to have solved the problem.

Categories

Resources