Match string using Spring #RequestMapping path expression - java

Is there a way to match a string using the expression language used for the path (or value) variable in the #RequestMapping annotation? For example, given the string
/api/foo/bar/1
the expression
/api/foo/bar/{id}
should match this string (a test should be available that returns a boolean true if the expression matches the given string), and if possible, the captured portion of the string (namely, {id} capturing 1) should also be available.
Essentially, I am looking to use the same mechanism that Spring internally uses for #RequestMapping to check if a URL path (a combination of the context path and path information) matches in the expression language as #RequestMapping. I am doing this matching with a Spring security filter.
Thank you for your help.

I recommend to use aspects for this. With aspects, you simply can annotate a manager interface method with e.g. #LogOnSuccess.
public class UserManager{
...
#LogOnSuccess
public UserDto createUser(UserDto newUser)
return userManager.createUser(newUser);
}
}
Using a #AfterReturning point cut will only invoke a custom action bound to the annotation, if no exception occurred during the method call. This way you only log successful requests. Also it's very easy to use and highly readable.
There are a lot of good tutorials about this: e.g. Spring AOP Example Tutorial

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.http.server.PathContainer;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
#RunWith(JUnit4.class)
public class PathPatternTest {
#Test
public void testTrue() {
Assert.assertTrue(isPatternMatchUri("/users/{userId}", "/users/h49IB7A2B8r3eSg4q"));
Assert.assertTrue(isPatternMatchUri("/users/{userId}", "/users/123456"));
Assert.assertTrue(isPatternMatchUri("/users/{userId}", "/users/xxx?32321321"));
Assert.assertTrue(isPatternMatchUri("/users/{userId}", "/users/1234"));
}
#Test
public void testFalse() {
Assert.assertFalse(isPatternMatchUri("/users/{userId}/{token}", "/user/h49IB7A2B8r3eSg4q"));
Assert.assertFalse(isPatternMatchUri("/users/{userId}", "/users/xxxx/12121"));
Assert.assertFalse(isPatternMatchUri("/{userId}", "/usrs/xxx/WWQQS?"));
}
/***
* is pattern match the request uri.
*/
public boolean isPatternMatchUri(String pattern, String url) {
PathPatternParser pathPatternParser = new PathPatternParser();
PathPattern pathPattern = pathPatternParser.parse(pattern);
PathContainer pathContainer = PathContainer.parsePath(url);
return pathPattern.matches(pathContainer);
}
}

Related

Java Spring : how can I encapsulate annotations within an other annotation?

Is it possible to encapsulate an annotation within an other annotation with values?
I have a lot of end points on my API and would like to automate the required roles / permissions for the end point access.
Here is the current code :
#RestController
#RequestMapping("/health")
public class HealthController {
#GetMapping("/isAlive")
#PreAuthorize("hasAnyAuthority('ISALIVE', 'HEALTH_ENDPOINT')")
public String isAlive() {
return "Server is up and running";
}
#GetMapping("/hello")
#PreAuthorize("hasAnyAuthority('HELLO', 'HEALTH_ENDPOINT')")
public String hello() {
return "Hello";
}
}
I have 2 authorities per end point, the first is the name of the mapping and method and the second is the name of the class.
In this example there is only 2 different end points with makes it easy but the finished product will have in the hundreds and doing all of the authorities by hand is going to be error-prone and not very efficient.
This is the desired result :
#RestController
#RequestMapping("/health")
public class HealthController {
#GetMapping("/isAlive")
#MyAuthorisationAnnotation
public String isAlive() {
return "Server is up and running";
}
#GetMapping("/hello")
#MyAuthorisationAnnotation
public String hello() {
return "Hello";
}
}
Where #MyAuthorisationAnnotation would give the right parameters to the #PreAuthorize annotation.
Is it possible?
Is it possible to encapsulate an annotation within an other annotation
with values?
An annotation can have another annotation as a member.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAuthorizationAnnotation {
PreAuthorize value() default #PreAuthorize("hasAnyAuthority('HELLO', 'HEALTH_ENDPOINT')");
}
However I don't think this helps. I am not familiar with Spring, but I think this post solves your problem, because it seems that #PreAuthorize can be applied to annotations so you can leverage transitivity if you use the checked annotation in a method.
Solution's Post

AspectJ: Pointcut to declare and retrieve an annotation of a method's parameter

I have read the following valuable links:
Spring AOP pointcut for annotated argument
How to write an Aspect pointcut based on an annotated parameter
AspectJ pointcut expression match parameter annotations at any position
Consider this request for a setter method
public void setSomething(#ParameterLevel(name="abc") String something){
this.something = something;
}
I have the following and works fine:
#Pointcut("execution(* *.*(#somepackage.ParameterLevel (*)))")
void parameterLevel01() {}
Now I want retrieve the #ParameterLevel annotation through a method's parameter such as the following:
#Pointcut("execution(* *.*(#somepackage.ParameterLevel (*)))")
void parameterLevel01(ParameterLevel parameterLevel) {} <--To be used directly in the advice method
The purpose is use the Annotation directly how a parameter in the advice method
Something similar such as:
#within(classLevel) for #ClassLevel in:
#ClassLevel
public class SomeClass {
...
}
#annotation(methodLevel) for #MethodLevel in:
#MethodLevel
public void somethingToDo(){
...
}
How accomplish this goal. Is possible? I am working with AspectJ 1.9.6
No matter if you use .., #MyAnnotation (*), .. or just #MyAnnotation (*), which only removes the ambiguity of possibly multiple matches, there is no direct way to bind a method argument annotation to an advice argument, only the method argument itself. This has not changed in AspectJ. You would have seen it mentioned in the release notes otherwise, because it would be a new feature.
So you will have to use the method from my other two answers which you have already linked to in your question, i.e. iterating over parameter types and annotations manually.
Somewhat off-topic, there is a very old Bugzilla ticket #233718 which is about binding multiple matched (annotated) parameters, but not about binding their annotations. It came up in a recent discussion I had with AspectJ maintainer Andy Clement. But even if this was implemented one day, it would not solve your problem.
I think you can take it from here and adapt my solution from the linked questions to your needs. Feel free to let me know if you have any follow-up questions about that, but it should be pretty straightforward. You might be able to optimise because you know the exact parameter position (think array index), if you feel so inclined, i.e. you don't need to iterate over all parameters.
Update: Here is a little MCVE for you. It is based on this answer and has been simplified to assume the annotation is always on the first parameter and the first parameter only.
Please learn what an MCVE is and provide one by yourself next time because it is your job, not mine. This was your free shot.
Marker annotation + driver application:
package de.scrum_master.app;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
#Retention(RUNTIME)
public #interface ParameterLevel {
String name();
}
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new Application().doSomething("foo");
}
public void doSomething(#ParameterLevel(name="abc") String string) {}
}
Aspect:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.SoftException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import de.scrum_master.app.ParameterLevel;
#Aspect
public class ParameterLevelAspect {
#Before("execution(public * *(#de.scrum_master.app.ParameterLevel (*))) && args(string)")
public void beforeAdvice(JoinPoint thisJoinPoint, String string) {
System.out.println(thisJoinPoint + " -> " + string);
MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
String methodName = signature.getMethod().getName();
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
Annotation[] annotations;
try {
annotations = thisJoinPoint.getTarget().getClass()
.getMethod(methodName, parameterTypes)
.getParameterAnnotations()[0];
} catch (NoSuchMethodException | SecurityException e) {
throw new SoftException(e);
}
ParameterLevel parameterLevel = null;
for (Annotation annotation : annotations) {
if (annotation.annotationType() == ParameterLevel.class) {
parameterLevel = (ParameterLevel) annotation;
break;
}
}
assert parameterLevel != null;
System.out.println(" " + parameterLevel + " -> " + parameterLevel.name());
}
}
Console log:
execution(void de.scrum_master.app.Application.doSomething(String)) -> foo
#de.scrum_master.app.ParameterLevel(name="abc") -> abc

Replacing an annotation with another annotation during compile time in Spring?

I am using Swagger annotations over my controller parameters. So, I end up with annotations like #ApiParam(name="default name", value="this is a default value"). I think this is quite verbose. I would like to change it to something like #Foo. I want to know if there's a way to replace #Foo with #ApiParam during compile time. Also, since I am using Spring, I think I have to consider the annotation processing order in Spring, as well. I mean I shouldn't replace #ApiParam with #Foo after Swagger or Spring picks it up. Is there any way to do this?
In simpler words, I have the same annotation with the same parameters used 5 times. Basically, I want to replace them with some custom annotation.
I know I have to show what I have already tried, but I have no clue where to even start.
Also, the question is not related to Swagger, it is just an example. I want to replace one annotation with another during compile time, so that the one picked up by Spring won't be the one I have put on the source code, but the one I have replaced.
If I understand what you are asking for, this is possible without compile-time annotation processing. It's not pretty and it might be more complexity than it's worth, but here's one way to do it.
Here's a custom annotation I made that is used for my shorthand #ApiParam.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface GameIdParam {
String name() default "My Game ID";
String value() default "The integer ID of a particular game";
}
You can define whatever properties in #ApiParam that you wish to override. Then you can use Springfox's Extension Framework to implement a custom handler for the new annotation.
import com.google.common.base.Optional;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.Example;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.spring.web.DescriptionResolver;
import springfox.documentation.swagger.readers.parameter.ApiParamParameterBuilder;
import java.util.function.Predicate;
import static java.util.Optional.ofNullable;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.pluginDoesApply;
import static springfox.documentation.swagger.readers.parameter.Examples.examples;
#Component
public class ShorthandAnnotationPlugin extends ApiParamParameterBuilder {
private final DescriptionResolver descriptions;
private final EnumTypeDeterminer enumTypeDeterminer;
#Autowired
public ShorthandAnnotationPlugin(
DescriptionResolver descriptions,
EnumTypeDeterminer enumTypeDeterminer) {
super(descriptions, enumTypeDeterminer);
this.descriptions = descriptions;
this.enumTypeDeterminer = enumTypeDeterminer;
}
#Override
public void apply(ParameterContext context) {
Optional<GameIdParam> gameIdParam = context.resolvedMethodParameter().findAnnotation(GameIdParam.class);
if (gameIdParam.isPresent()) {
GameIdParam annotation = gameIdParam.get();
// Instantiate an ApiParam so we can take default values for attributes we didn't override.
ApiParam parentAnnotation = AnnotationUtils.synthesizeAnnotation(ApiParam.class);
context.parameterBuilder().name(ofNullable(annotation.name())
.filter(((Predicate<String>) String::isEmpty).negate()).orElse(null))
.description(ofNullable(descriptions.resolve(annotation.value()))
.filter(((Predicate<String>) String::isEmpty).negate()).orElse(null))
.parameterAccess(ofNullable(parentAnnotation.access())
.filter(((Predicate<String>) String::isEmpty).negate())
.orElse(null))
.defaultValue(ofNullable(parentAnnotation.defaultValue())
.filter(((Predicate<String>) String::isEmpty).negate())
.orElse(null))
.allowMultiple(parentAnnotation.allowMultiple())
.allowEmptyValue(parentAnnotation.allowEmptyValue())
.required(parentAnnotation.required())
.scalarExample(new Example(parentAnnotation.example()))
.complexExamples(examples(parentAnnotation.examples()))
.hidden(parentAnnotation.hidden())
.collectionFormat(parentAnnotation.collectionFormat())
.order(SWAGGER_PLUGIN_ORDER);
}
}
#Override
public boolean supports(DocumentationType documentationType) {
return pluginDoesApply(documentationType);
}
}
I used Springfox's ApiParamParameterBuilder as an example.
Now, I can use my #GameIdParam
#PostMapping("/{gameId}/info")
public String play(#GameIdParam #PathVariable int gameId) // ...
This pattern could be generalized to work with a series of custom shorthand annotations. It's not pretty and it introduces another level of indirection that people who know Springfox Swagger won't be familiar with.
Hope that helps! Good luck!

In the context of SpringMVC, how to have web services that provide different JSON representation of a same class?

I have a data class, something like this:
public class Person {
private String name;
private Long code;
// corresponding getters and setters
}
I want to write two web services that provide two different JSON representation of Person. For example, one of them provide {"name":"foo"} but the other one {"name":"foo", "code":"123"}.
As a more complicated scenario, suppose that Person has a reference property, for example address. Address has its own properties as well and I expect that both of my web services consider this property but each of which do this in their own manner.
How should my SpringMVC views be like?
Please note that I'm new to SpringMVC. So give me a sample code beside your answer, please.
UPDATE 1: After few days, all answers push me to solve the problem in controllers or by annotating the data classes. But I want to do this in views, without any more java codes. Can I do it in JSP files or thymeleaf templates or even in .properties files?
UPDATE 2: I found json-taglib. But somehow it is left out of new upgrades. Is there any similar solution?
You're using Spring-MVC so Jackson is in charge of JSON serialize and deserializing.
In this case, you can use #JsonInclude(Include.NON_NULL) to ignore null field during serialization.
public class Person {
#JsonInclude(Include.NON_NULL)
private String name;
#JsonInclude(Include.NON_NULL)
private Long code;
// corresponding getters and setters
}
If your name or code is null then it is excluded from output JSON
So if you pass code as null, your ouput JSON will look like {"name":"foo"}
When creating JSon with Spring MVC the "view renderer", by default, is Jackson. There is no need to use things like JSP or other view technology. What you want to do, is to tell Jackson how to render an object for a given situation. Multiple options are available, but I would suggest to use projections.
An example:
#RestController
#RequestMapping(value = "person")
public class PersonController {
private final ProjectionFactory projectionFactory;
public PersonController(ProjectionFactory projectionFactory) {
this.projectionFactory = projectionFactory;
}
#GetMapping("...")
public PersonBase getPerson(..., #RequestParam(value = "view", required = false, defaultValue = "base") String view) {
...
if(view.equals("extended")) {
return projectionFactory.createProjection(PersonExtended.class, person);
} else {
return projectionFactory.createProjection(PersonBase.class, person);
}
}
}
public interface PersonBase {
String getName();
}
public interface PersonExtended extends PersonBase {
Long getCode;
}
The view layer of your application are the projection classes (put then in one package, the view package if you wish).
A Controller can choose what view to render, or you could make the result dynamic as in the example.
Your question on how to render the address could be solved with another projection like this:
public interface PersonWithAddress extends PersonExtended {
AddressProjection getAddress();
}
public interface AddressProjection {
String getStreetName();
String getZipcode();
...
}
You can look for dynamic filtering of fields using MappingJacksonValue.
The filter allows you to serialize fields that meet custom criteria.
You can check my sample code here:
package com.github.tddiaz.jsonresponsefiltering;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import jdk.nashorn.internal.objects.annotations.Getter;
import lombok.Data;
import lombok.NonNull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#RestController
class Controller {
#GetMapping("/filter")
public ResponseEntity filter() {
final Response response = new Response("value1", "value2", "value3");
//ignore field3; will only return values of field1 and field2
final SimpleBeanPropertyFilter beanPropertyFilter = SimpleBeanPropertyFilter.filterOutAllExcept("field1", "field2");
final FilterProvider filterProvider = new SimpleFilterProvider().addFilter("responseFilter", beanPropertyFilter);
final MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(response);
mappingJacksonValue.setFilters(filterProvider);
return ResponseEntity.ok(mappingJacksonValue);
}
}
#JsonFilter("responseFilter")
#Data
class Response {
#NonNull
private String field1, field2, field3;
}
}
Use Projection for expose in diferent manners your json, and ResourceProcessor if you need to add more info to this model through projection, e.g another DB table perhaps.
Based on your use case, just call the controller of your choice from the jsp/js page ...For e.g. Let's say Admin is the user then call AdminController else call User Controller ...this can be done using a simple if/else condition...you can also look into Proxy Design Pattern but that depends on the use case
I recommend you to use JSON.stringify(value[, replacer[, space]]) function on frontend. I have given an example below. You have a write a custom function according to your requirements on the specific view.
Eg. The below example ignores null values. Here I have written editperson function which removes null values.
The function has two input parameters i.e. key and value. Write your logic according to the keys and values which you want to remove or change.
var springperson = { "name":"foo","code":null }
console.log(springperson); // person recieved from spring
function editjson(key, value){
if (value !== null) return value
}
var editperson = JSON.stringify(springperson, editjson); // String representation of person
var personjson=JSON.parse(editperson); // JSON object representation of person
console.log(personjson); // person as required by the view
Comment if you have any issues.

How to create a custom validator in Play Framework 2.0?

Play 1.0 comes with a full featured validation framework base on http://oval.sourceforge.net/.
With the release of 2.0, my custom validators do not work anymore.
How does one create custom validator using Play Framework 2.0 ?
In Play 2.0, the validation framework extends beyond the actual validation of the data as it reaches to:
Annotations - to easily declare validation contraints using the '#' sign
Validators - which actually implements to logic behind the validation
Messages - to display parametrized error messages (i18 compliant)
Finally, HTML helpers - that glue all the previous together
The HTML Helpers are something new to Play 2.0. In 1.x, Play was already pretty good at enforcing a well defined validation framework. It was powerful and easy to use. Yet we still had to wire the HTML form and the validation framework together. This could be a little confusing to the beginner.
With Play 2.0, this is now done automatically.
But let's focus on the answer and provide some guidance: We will create an AllUpperCase validator, that generates an error either when:
the input is not a String
the input is empty
one of the characters is lower-case.
The validator
package myvalidators;
import javax.validation.*;
public class AllUpperCaseValidator
extends play.data.validation.Constraints.Validator<Object>
implements ConstraintValidator<AllUpperCase, Object> {
/* Default error message */
final static public String message = "error.alluppercase";
/**
* Validator init
* Can be used to initialize the validation based on parameters
* passed to the annotation.
*/
public void initialize(AllUpperCase constraintAnnotation) {}
/**
* The validation itself
*/
public boolean isValid(Object object) {
if(object == null)
return false;
if(!(object instanceof String))
return false;
String s = object.toString();
for(char c : s.toCharArray()) {
if(Character.isLetter(c) && Character.isLowerCase(c))
return false;
}
return true;
}
/**
* Constructs a validator instance.
*/
public static play.data.validation.Constraints.Validator<Object> alluppercase() {
return new AllUpperCaseValidator();
}
}
The first thing you may notice is the import: Play 2.0 indeed complies with JSR 303 - Bean Validation Framework. In this context, the validator needs to implement ConstraintValidator. Which in our case translates into the annotation as class AllUpperCase (which we will introduce in a minute) and T as a generic Object.
The validator is straighforward:
We defined the method public boolean isValid(Object object) that returns a boolean, if true the validation passed. There is also an message id and a method that instanciates the validator.
The Annotation
The class below defines an annotation named #AllUpperCase which takes no parameters but enforces the validation defined previously. Providing details related to the annotation framework is outside the scope of this post.
package myvalidators;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import javax.validation.*;
#Target({FIELD})
#Retention(RUNTIME)
#Constraint(validatedBy = AllUpperCaseValidator.class)
#play.data.Form.Display(name="constraint.alluppercase")
public #interface AllUpperCase {
String message() default AllUpperCaseValidator.message;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Note how the anotation glues to the other pieces of the puzzle.
#Constraint, a JSR 303 annotation, links to the validator
#play.data.Form.Display, links the annotation to the play html helpers. Note that the name is important: we are defining a constraint named alluppercase. Play uses this information to call the method public static play.data.validation.Constraints.Validator<Object> alluppercase() on the Validator.
Finally note that the default message is set within the anotation interface.
Usage
We now have our custom validator and annotation
import myvalidators.*;
public static class MyData {
#AllUpperCase
public String name;
}
Describing the usage is outside the scope of this post, please find a working sample at this URL

Categories