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

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!

Related

SpringBoot + Spring Cloud Function: Configurable function composition issue

Small question regarding SpringBoot + Spring Cloud function application please.
I have a very simple app which takes a word as input.
The business logic is written via functions. The "difficulty" is: the functions need to be configurable. Meaning, with just an application.properties change + restart, the web app should be able to pick the correct business logic, i.e. the correct function composition to apply to the input, without code change.
Here is an example:
application.properties:
spring.cloud.function.definition=upper
#spring.cloud.function.definition=reverse
#spring.cloud.function.definition=upper|reverse
pom:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.util.function.Function;
#Service
public class ReverseFunction {
#Bean
Function<String, String> reverse() {
return s -> new StringBuilder(s).reverse().toString();
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.util.function.Function;
#Service
public class UpperFunction {
#Bean
Function<String, String> upper() {
return String::toUpperCase;
}
}
And this is the business logic:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.function.Function;
#RestController
public class FunctionController {
#GetMapping("/applyFunctionToWord")
public String applyFunctionToWord(#RequestParam String word) {
Function<String, String> function = ??? //configurable from application.properties
return function.apply(word);
}
}
In tutorials found online and from official documentation from Spring, it is said:
Function Composition
Function composition is a feature of SCF that lets you compose several functions together in a declarative way.
The following example shows how to do so:
--spring.cloud.function.definition=uppercase|reverse
Here, we effectively provided a definition of a single function that is itself a composition of a function named uppercase and a function named reverse. You can also argue that we’ve orchestrated a simple pipeline consisting of running the uppercase function and then sending its output to the reverse function. The term orchestration is important here, and we cover it in more detail later in the post.
https://spring.io/blog/2019/11/04/spring-cloud-stream-composed-functions-or-eip#:~:text=Function%20composition%20is%20a%20feature%20of%20SCF%20that,following%20example%20shows%20how%20to%20do%20so%3A%20--spring.cloud.function.definition%3Duppercase%7Creverse
What I tried:
I did configure the property in application.properties.
And as you can see, I did follow the guide naming the property: spring.cloud.function.definition. What is the correct machanism please?
Am I forced to fallback to something with #Value?
My app is not a Spring Cloud Stream app. It is not based on Kafka. It is just a basic web app where the business logic is a configurable composition functions.
Question:
How to have the webapp take into effect the value configured in spring.cloud.function.definition=, and apply it to the input please?
Thank you
I would assume it should work using RoutingFunction, something like this:
#RestController
public class FunctionController {
#Autowired
RoutingFunction function;
#GetMapping("/applyFunctionToWord")
public String applyFunctionToWord(#RequestParam String word) {
return (String)function.apply(word);
}
}
You need a #Value annotation and some logic like this
#RestController
public class FunctionController {
#Value("${spring.cloud.function.definition}")
String definition;
#Autowired
#Qualifier("reverse");
Function reverse;
#Autowired
#Qualifier("upper");
Function upper;
#GetMapping("/applyFunctionToWord")
public String applyFunctionToWord(#RequestParam String word) {
Function<String, String> function = null
switch (definition) {
case "upper" :
function = upper;
break;
case "reverse" :
function = reverse;
break;
}
return function.apply(word);
}
}

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

Is it possible to to do validation checking at run time other than compile time?

With the following Java code:
public class Bean{
private String value;
public Bean(#NonNull String value) {
//Usually fail-fast validation can be added here if it is needed
this.value = value;
}
public String getValue() {return this.value;}
}
Is it possible to check the constructor argument value by means of the annotation, #NonNull at run time other than compile time? Personally I still did not find any checker-framework, which can do validation checking at run time. However, is it possible to implement an Annotation processor to do run time checking?
You should take a look at #NotNull from javax.validation.constraints.
I use it in my models and it throw a Constraint exception when I try to save a model with a null #NotNull value.
The import is import javax.validation.constraints.NotNull;
If you are using Spring and mongodb, you'll have to configure it so it works, I have found a piece of code somewhere on the Internet (can't remember where), you may use it:
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
#Configuration
public class CustomRepositoryRestConfigurerAdapter {
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
#Bean
public ValidatingMongoEventListener validatingMongoEventListener(
#Qualifier("localValidatorFactoryBean") LocalValidatorFactoryBean lfb
) {
return new ValidatingMongoEventListener(lfb);
}
}
Yes. Lombok's #NonNull is a runtime check which just inserts an if-statement with a throw:
With Lombok
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(#NonNull Person person) {
super("Hello");
this.name = person.getName();
}
}
Vanilla Java
import lombok.NonNull;
public class NonNullExample extends Something {
private String name;
public NonNullExample(#NonNull Person person) {
super("Hello");
if (person == null) {
throw new NullPointerException("person is marked #NonNull but is null");
}
this.name = person.getName();
}
}
Misconception at your end: there is no single answer to your question.
Some annotations, when used on source code like this are mainly targeting compile time. Like some static analysis tool that analyses the data flow to tell you that you are violating "annotated" contracts here or there.
But some annotations are also "meant" to be used at runtime, for example to be used with "beans". Such objects might come in as parameter of a HTTP request, and then you have some framework checking if the content received as JSON for example is actually valid, according to the rules specified via annotations. See this tutorial for some examples.

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.

Match string using Spring #RequestMapping path expression

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);
}
}

Categories