SpringBoot + Spring Cloud Function: Configurable function composition issue - java

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

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

How to execute Java calls to GraphQL in a Spring Boot + GraphQL Java Tools' context?

In a Spring Boot application, we're already having a fully functional GraphQL endpoint, serving .graphqls files via GraphQL Java Tools (we included the graphql-spring-boot-starter dependency) and handling the data resolution through our base Query class implementing GraphQLQueryResolver and subsequent GraphQLResolver's.
For a business need, we have to re-create standard REST API endpoints, so I wonder why not just making calls to GraphQL (instead of having to re-implement "by hand" the data resolution once again)?
And as it's in the same backend application, no need to make HTTP or servlet (ForwardRequest) calls, just call some API's in Java.
The thing is I don't know how to proceed.
I read this example, but it's with basic GraphQL Java (not Tools):
https://www.graphql-java.com/documentation/v9/execution/
I know this should possible because we are allowed to do this in tests:
https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/master/example-graphql-tools/src/test/java/com/graphql/sample/boot/GraphQLToolsSampleApplicationTest.java
But how to do it in regular code? There is not such thing as a GraphQLTemplate.
I also tried to search through examples at:
https://github.com/graphql-java-kickstart/graphql-java-tools/tree/master/example
https://github.com/graphql-java-kickstart/graphql-spring-boot
but found nothing relevant to our need.
Found nothing more in Documentation:
https://www.graphql-java-kickstart.com/tools/
https://www.graphql-java-kickstart.com/spring-boot/
What did I miss? Ideally I'm looking to inject some GraphQLSomething like this:
#RestController
#RequestMapping(path = "api")
public class CompanyController {
#Autowired
private GraphQLSomething graphQLSomething;
#GetMapping("company/{id}")
public ResponseEntity<?> societe(#PathVariable #NotNull Integer id) {
GraphQLSomethingResult result = GraphQLSomething.query("company(id: $id) { id name andsoone }", "{ \"id\": 123456 }").execute(); // not really sure of the GraphQL syntax here, but it'll need some tests...
return result.getDataOrElse();
}
}
Finally found how to do the thing as I wanted:
import java.util.Map;
import java.util.Optional;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.ImmutableMap;
import graphql.ExecutionResult;
import graphql.servlet.core.GraphQLQueryInvoker;
import graphql.servlet.core.internal.GraphQLRequest;
import graphql.servlet.input.GraphQLInvocationInputFactory;
import graphql.servlet.input.GraphQLSingleInvocationInput;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Validated
#RestController
#RequestMapping(path = "api")
public class CompanyController {
#Autowired
private GraphQLInvocationInputFactory invocationInputFactory;
#Autowired
private GraphQLQueryInvoker queryInvoker;
#GetMapping("company/{id}")
public ResponseEntity<?> societe(#PathVariable #NotNull Integer id) {
String query = "query ($id: Int!) { company(id: $id) { id name andsoon } }";
/*
* ImmutableMap is a Guava class; you can build the map (e.g. a HashMap) on your
* own, or simply Map.to(..) in Java 9, or even #PathVariable Map<String,
* Object> variables as the method's parameter instead (but you'll miss the
* validation).
*/
Map<String, Object> variables = ImmutableMap.of("id", id);
GraphQLRequest request = new GraphQLRequest(query, variables, null);
GraphQLSingleInvocationInput invocationInput = invocationInputFactory.create(request);
ExecutionResult result = queryInvoker.query(invocationInput);
/*
* Of course result.getData() can be null here - see also result.isDataPresent()
* - but data/error handling's left to you
*/
Optional<Object> company = Optional.ofNullable(result.getData().get("company"));
return ResponseEntity.of(company);
}
}
FYI, to get the dependencies for the above code, you'll need this:
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-autoconfigure</artifactId>
<version>5.0.2</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-servlet</artifactId>
<version>6.1.3</version>
</dependency>

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.

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