Match request path against #PathVariable string? - java

On my controller class I have a typical #RequestMapping annotation with placeholders for #PathVariable annotations:
#RequestMapping("/customer/{id}/{whatever}")
public class CustomerController {
#GetMapping
public Customer getCustomer(#PathVariable final int id, #PathVariable final String whatever) {...}
}
Now I need to match a #RequestMapping pattern-string like /customer/{id}/{whatever} against a real path like /customer/1234/xyz. As a result I need to know that id is 1234 and whatever is xyz.
Are there any utility methods which can do this? (I need a generic solution, not the regular expression for the path above.)

I tried to solve it with regular expressions but I think it is not very stable and will fail for special cases:
protected static Map<String, String> matchPath(String pathPattern, String path) {
final Matcher matcher = Pattern.compile("\\{([^\\/\\}]+)\\}").matcher(pathPattern);
List<String> keys = matcher.results().map(x -> x.group(1)).toList();
final String newRegex = matcher.replaceAll("([^\\/\\}]+)");
final Matcher newMatcher = Pattern.compile(newRegex).matcher(path);
newMatcher.find();
final List<String> values = IntStream.rangeClosed(1, newMatcher.groupCount()).mapToObj(i -> newMatcher.group(i)).toList();
return IntStream.range(0, keys.size()).boxed().collect(Collectors.toMap(keys::get, values::get));
}
Test:
#Test
void matchPath() {
final Map<String, String> results = matchPath("/customer/{id}/{whatever}", "/customer/1234/xyz");
assertThat(results.size(), is(2));
assertThat(results.get("id"), is("1234"));
assertThat(results.get("whatever"), is("xyz"));
}
First I find all "keys" and replace the {...} placeholders with the "([^\\/\\}]+)" expression.
This gives me a new regular expression which I use to match find the values.
But I am not very happy with this handmade solution...

Can you explain more about what you are exactly trying to do...?
Because as per my understanding u don't wanna use #pathvariable annotation.
Also, you can do the same thing with #Requestparam annotation.
Like below:
/customer?id=1234&whatever=xyz
And in the controller you can use as #RequestParam(name = "id" int id) and #RequestParam(name = "whatever" String whatever)

Related

spring boot requestMapping #RequestMapping(value = "/{userId:\\d+}", method = RequestMethod.DELETE)

#RequestMapping(value = "/{userId:\\d+}", method = RequestMethod.DELETE)
public void delete(#PathVariable Long userId) {
try{
this.authorService.delete(userId);
}catch(Exception e){
throw new RuntimeException("delete error");
}
}
Anybody know what url should I match for this definition "/{userId:\d+}", could you give me an example, like "/userid=1", is this right?
I guess that definition like this "/{userId:\d+}" , using regular expression in url to make sure it pass a number parameter.I am not sure about that , if anybody knows it please give me a link for further learning, thank you!
No, that expression maps /1 for example, all the digits.
The syntax {varName:regex} declares a URI variable with a regular expressions with the syntax {varName:regex} — e.g. given URL "/spring-web-3.0.5 .jar", the below method extracts the name, version, and file extension:
#GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(#PathVariable String version, #PathVariable String ext) {
// ...
}
Check the complete doc here
It will match any digit. For example,
/1, /11, /123.
/{userId:\\d+} ===> map one or more digits after / to variable userId.
Regular expression for one or more digits is \d+, but since you are using it as a string you need to escape it using another \.

Huge constructor for messages/names definition

I want to allow name and message customisation in both my lexer and parsers. The parser/lexer must be able to select a specific message or name, e.g.:
messageManager.tokenName.eofToken
Here, eofToken would be a String. tokenName would be a set of names for lexer tokens and messageManager would be a structure like { parserMessage, lexerMessage, contextName, tokenName }
However, I want the message customisation very directly constructed. I don't want something like:
TokenName tokenName = new TokenName();
tokenName.eofToken = "end of file";
tokenName.identifier = "identifier";
tokenName.keyword = "keyword";
tokenName.regExpLiteral = "regexp' literal";
// much more...
I want something like:
new TokenName(
"end of file",
"identifier",
"keyword",
...
)
I know we just need to define parameters and assign them to the corresponding variables, but I don't want a huge constructor like the one I had in my previous parser:
public TokenNames(
String booleanLiteral,
String eofToken,
String identifier,
String punctuator,
String keyword,
String numericLiteral,
String nullLiteral,
String regExpLiteral,
String stringLiteral,
String xmlName,
String xmlMarkup,
String xmlPunctuator,
String xmlTagCharacters,
String xmlText
)
{
this.booleanLiteral = booleanLiteral;
this.eofToken = eofToken;
this.identifier = identifier;
this.punctuator = punctuator;
this.keyword = keyword;
this.numericLiteral = numericLiteral;
this.nullLiteral = nullLiteral;
this.regExpLiteral = regExpLiteral;
this.stringLiteral = stringLiteral;
this.xmlName = xmlName;
this.xmlMarkup = xmlMarkup;
this.xmlPunctuator = xmlPunctuator;
this.xmlTagCharacters = xmlTagCharacters;
this.xmlText = xmlText;
}
I believe it's possible with arrays or varargs (more readable). How to?
Define class like this:
public class Token {
private String booleanLiteral;
private String eofToken;
...
public Token withBooleanLiteral(String booleanLiteral) {
this.booleanLiteral = booleanLiteral;
return this;
}
public Token withEofToken(String eofToken) {
this.eofToken = eofToken;
return this;
}
...
}
You'll get
Token token = new Token()
.withBooleanLiteral("something");
Check out Lombok library and #Wither annotation. It does everything for you.
Of course, the builder pattern is the most obvious solution in here (and the correct one).
But I would like to draw your attention to the fact there is a lot of fields that can be encapsulated by their own classes.
The following fields can be collected into an XMLDescription class:
String xmlName;
String xmlMarkup;
String xmlPunctuator;
String xmlTagCharacters;
String xmlText;
The next ones can be grouped by a LiteralDescription class:
String numericLiteral;
String nullLiteral;
String regExpLiteral;
String stringLiteral;
Think the problem over once more: if there is a chance to shorten a number of the fields to 3 (an extreme bound according to good practices), the constructor can be used instead of the builder.
I believe it's possible with arrays or varargs (more readable).
Please, don't do that - it's an error-prone approach. You are coupling an index of the array with a corresponding field. Such code, hard to maintain and document, causes an API user
to read a documentation if any provided,
to poke around in the sources if no provided,
to follow to any API change believing that nothing is changed.

How to create REST API with optional parameters?

I need to implement an API with these path params.
#Path("/job/{param1}/{optional1}/{optional2}/{param2}")
Can the second and third params by optional? So the client need not pass these, but have to pass the first and last.
If this is not possible, then is it recommended to rearrange the params in this way?
#Path("/job/{param1}/{param2}/{optional1}/{optional2}")
How to provide the optional params?
It might be easier to turn the optional path parameters into query parameters. You can then use #DefaultValue if you need it:
#GET #Path("/job/{param1}/{param2}")
public Response method(#PathParam("param1") String param1,
#PathParam("param2") String param2,
#QueryParam("optional1") String optional1,
#QueryParam("optional2") #DefaultValue("default") String optional2) {
...
}
You can then call it using /job/one/two?optional1=test passing only the optional parameters you need.
You can match the entire path ending in the REST request
#Path("/location/{locationId}{path:.*}")
public Response getLocation(
#PathParam("locationId") int locationId,
#PathParam("path") String path) {
//your code
}
Now the path variable contain entire path after location/{locationId}
You can also use a regular expressions to make the path optional.
#Path("/user/{id}{format:(/format/[^/]+?)?}{encoding:(/encoding/[^/]+?)?}")
public Response getUser(
#PathParam("id") int id,
#PathParam("format") String format,
#PathParam("encoding") String encoding) {
//your code
}
Now if you format and encoding will be optional. You do not give any value they will be empty.
Rearrange the params and try the following:
#Path("/job/{param1}/{param2}{optional1 : (/optional1)?}{optional2 : (/optional2)?}")
public Response myMethod(#PathParam("param1") String param1,
#PathParam("param2") String param2,
#PathParam("optional1") String optional1,
#PathParam("optional2") String optional2) {
...
}
to make request parameter optional set #requestparam to false in controller class
(#RequestParam(required=false)

Getting query parameters with similar prefixes in Jersey [duplicate]

I am using Jersey for Rest and have a method that accepts #QueryParam.
However, the users may send #QueryParam. like this:
contractName# where # is a number from 0-155.
How can I define it in QueryParam (like regex expression)?
You can't specify the regexp. However, you can define a custom Java type to represent that query param and implement your own conversion from String to that type - see http://jersey.java.net/nonav/documentation/latest/jax-rs.html#d4e255 (example 2.15).
I don't think you can do it with QueryParam, but you can get the list of parameters directly:
#GET
public String get(#Context UriInfo ui) {
MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
}
and iterate through that looking for keys that match your regular expression.
#GET
public String get (#QueryParam(value="param") String param){
boolean test =testYourParamWithNativeRegexpTools(param);
if( test==false)return 400;
else //work
.....
}

Java CLI arguments syntax for object initialization

I'm looking for a tool which will allow me use command-line-style (preferably POSIX) strings to initialize an object' properties and attributes.
For example, you'd provide it with String input formatted like so:
String input = "--firstName=John --MiddleName=\"Louis Victor\" --lastName=Smith";
... and it would setFirstName("John"), setMiddleName("Louis Victor") and setLastName("Smith") on a given object. (which could be a JavaBean)
Please note that the input is a single String, not an array String[] as is the case with many popular CLI argument "parsers".
This is all similar to args4j but I couldn't get that to work... and I'm hoping to avoid using #annotations.
Does anyone have code/libraries/tools which could accomplish this?
For your use case, forget regular CLI parsers, you need a custom-tailored solution. If you really have such a simple argument syntax (parameters always begin with --, no occurrences of -- in the parameter values), you can use a simple Guava-based solution like this class:
Parse the String Arguments
public class ArgParser{
// split on (optional whitespace) + "--"
private final Splitter paramSplitter = Splitter.on(
Pattern.compile("\\s*\\-{2}")).omitEmptyStrings();
// find key=value (with optional double quotes around value)
private final Pattern keyValuePattern = Pattern
.compile("(.+?)=\"?(.*?)\"?$");
public Map<String, String> getParamValues(final String posixString){
final Map<String, String> paramValues = Maps.newLinkedHashMap();
Matcher matcher;
for(final String param : paramSplitter.split(posixString)){
matcher = keyValuePattern.matcher(param);
if(!matcher.find()){
throw new IllegalArgumentException("Bad parameter: " + param);
}
paramValues.put(matcher.group(1), matcher.group(2));
}
return paramValues;
}
}
Usage
final String input =
"--firstName=John --middleName=\"Louis Victor\" --lastName=Smith";
System.out.println(new ArgParser().getParamValues(input));
Output
{firstName=John, middleName=Louis Victor, lastName=Smith}
Now you can take the map and use it with a Bean library like commons-beanutils (I prefer the Spring BeanWrapper personally, but that only makes sense if you use Spring anyway)
Define the Bean Class
Any way, I'll use this value holder class:
public class Name{
private String firstName;
private String middleName;
private String lastName;
#Override
public String toString(){
return Objects
.toStringHelper(this)
.add("first name", firstName)
.add("middle name", middleName)
.add("last name", lastName)
.toString();
}
// + getters & setters
}
Set the Bean Properties
Now we'll use BeanUtils.populate(Object, Map) to apply the parameter values, like this:
final String input =
"--firstName=John --middleName=\"Louis Victor\" --lastName=Smith";
final Map<String, String> paramValues =
new ArgParser().getParamValues(input);
final Name name = new Name();
BeanUtils.populate(name, paramValues);
System.out.println(name);
Output:
Name{first name=John, middle name=Louis Victor, last name=Smith}
Caveat: Supported Property Types
BeanUtils.populate() supports setting the following property types:
... String, boolean, int, long, float, and double.
In addition, array setters for these
types (or the corresponding primitive
types) can also be identified.
Source: BeanUtilsBean.populate(Object, Map)
If you need parameter conversion beyond that, you should probably look into using the Spring BeanWrapper after all, it's extremely powerful, has many built-in property editors and you can add custom property editors. Just change the code like this:
final Name name = new Name();
final BeanWrapper wrapper = new BeanWrapperImpl(name);
wrapper.setPropertyValues(paramValues);
Reference:
BeanWrapper
PropertyAccessor.setPropertyValues(Map)
If I understand correctly, you are looking for a Java library to parse POSIX-style command line parameters. I used JSAP some time ago and it was really cool (it was using XML configuration back then).
This
-firstName John -lastName Smith
is no POSIX, you mean
--firstName John --lastName Smith
This may be the reason, why you can't get it working.
Update:
As I look at the example, it doesn't look like it could be the reason.

Categories

Resources