Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ public String getFullyQualifiedName() {
}

@Override
public boolean hasAttribute(String timeout) {
return false;
public boolean hasAttribute(String attribute) {
return getAttributes().containsKey(attribute);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.regex.Pattern;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -131,7 +132,8 @@ void allChecksSucceed(@TempDir Path tempDir) throws IOException {
assertThat(preconditionVerificationResult.getResults().get(0).getState()).isEqualTo(PreconditionCheck.ResultState.PASSED);
assertThat(preconditionVerificationResult.getResults().get(0).getMessage()).isEqualTo("Found pom.xml.");
assertThat(preconditionVerificationResult.getResults().get(1).getState()).isEqualTo(PreconditionCheck.ResultState.PASSED);
assertThat(preconditionVerificationResult.getResults().get(1).getMessage()).isEqualTo("'sbm.gitSupportEnabled' is 'true', changes will be committed to branch [master] after each recipe.");

assertThat(preconditionVerificationResult.getResults().get(1).getMessage()).matches("'sbm\\.gitSupportEnabled' is 'true', changes will be committed to branch \\[(master|main)\\] after each recipe\\.");
assertThat(preconditionVerificationResult.getResults().get(2).getState()).isEqualTo(PreconditionCheck.ResultState.PASSED);
assertThat(preconditionVerificationResult.getResults().get(2).getMessage()).isEqualTo("Found required source dir 'src/main/java'.");
assertThat(preconditionVerificationResult.getResults().get(3).getState()).isEqualTo(PreconditionCheck.ResultState.PASSED);
Expand Down
8 changes: 8 additions & 0 deletions components/sbm-recipes-spring-cloud/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.sbm</groupId>
<artifactId>recipe-test-support</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions components/sbm-support-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2021 - 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.sbm.boot.web.api;

import org.springframework.sbm.java.api.*;

import java.util.List;
import java.util.stream.Collectors;

/**
* Provides an API for classes annotated with {@code RestController}.
*
* @author Fabian Krüger
*/
public record RestControllerBean(JavaSource js, Type restControllerType) {
private static final RestMethodMapper restMethodMapper = new RestMethodMapper();
public static final List<String> SPRING_REST_METHOD_ANNOTATIONS = SpringRestMethodAnnotation
.getAll()
.stream()
.map(SpringRestMethodAnnotation::getFullyQualifiedName)
.toList();

/**
* Return the list of methods annotated with any of the Spring Boot request mapping annotations like
* {@code @RequestMapping} or {@code @GetMapping}.
*/
public List<RestMethod> getRestMethods() {
return this.restControllerType.getMethods().stream()
.filter(this::isRestMethod)
.map(Method.class::cast)
.map(restMethodMapper::map)
.collect(Collectors.toList());
}

private boolean isRestMethod(Method method) {
return method.getAnnotations().stream()
.anyMatch(methodAnnotation -> SPRING_REST_METHOD_ANNOTATIONS.contains(methodAnnotation.getFullyQualifiedName()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2021 - 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.sbm.boot.web.api;

import lombok.Builder;
import org.springframework.sbm.java.api.Method;
import org.springframework.sbm.java.api.MethodParam;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;
import java.util.Optional;

/**
* Record of request mapping annotation data (e.g. {@code RequestMapping} or {@code GetMapping}).
*
* @param path the list of mapped paths
* @param name
* @param method
* @param params
* @param headers the list of headers
* @param consumes the effective media types
* @param produces the effective media types
* @param methodReference the method
* @param returnType the fully qualified of the return type
* @param requestBodyParameterType the fully qualified name of parameter annotated with {@RequestBody}
*
* @author Fabian Krüger
*/
@Builder
public record RestMethod(
List<String> path,
String name,
List<RequestMethod> method,
List<String> params,
List<String> headers,
List<String> consumes,
List<String> produces,
Method methodReference,
String returnType,
Optional<MethodParam> requestBodyParameterType) {

public Optional<MethodParam> requestBodyParameterType() {
if(requestBodyParameterType == null) return Optional.empty();
return requestBodyParameterType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright 2021 - 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.sbm.boot.web.api;

import org.jetbrains.annotations.NotNull;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.springframework.http.MediaType;
import org.springframework.sbm.java.api.Annotation;
import org.springframework.sbm.java.api.Expression;
import org.springframework.sbm.java.api.Method;
import org.springframework.sbm.java.impl.OpenRewriteExpression;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static java.util.function.Predicate.not;
import static org.springframework.sbm.boot.web.api.SpringRestMethodAnnotation.*;

class RestMethodMapper {

public static final List<String> SPRING_REST_METHOD_ANNOTATIONS = SpringRestMethodAnnotation.getAll().stream().map(SpringRestMethodAnnotation::getFullyQualifiedName)
.collect(Collectors.toList());

public RestMethod map(Method method) {
return mapToRestMethod(method);
}

private RestMethod mapToRestMethod(Method method) {
RestMethod.RestMethodBuilder builder = RestMethod.builder();
Annotation annotation = getRestMethodAnnotation(method);
builder.methodReference(method);
buildRestMethod(method, annotation, builder);
buildRequestBodyType(method, builder);
return builder.build();
}

private void buildRequestBodyType(Method method, RestMethod.RestMethodBuilder builder) {
builder.requestBodyParameterType(method.getParams().stream()
.filter(p -> p.containsAnnotation(Pattern.compile(".*\\.RequestBody")))
.findFirst());
}

private Annotation getRestMethodAnnotation(Method method) {
return method.getAnnotations().stream()
.filter(methodAnnotations -> SPRING_REST_METHOD_ANNOTATIONS.stream().anyMatch(fqName -> methodAnnotations.getFullyQualifiedName().equals(fqName)))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(String.format("The provided method '%s' has no Spring Request Mapping annpotation.", method.getName())));
}

private void buildRestMethod(Method method, Annotation annotation, RestMethod.RestMethodBuilder builder) {
String name = "name";
if(annotation.hasAttribute(name)) {
mapNameAttribute(annotation, builder, name);
}
extractExistingAnnotationAttribute(annotation, "value", values -> builder.path(values));
extractExistingAnnotationAttribute(annotation, "path", paths -> builder.path(paths));
extractExistingAnnotationAttribute(annotation, "params", params -> builder.params(params));
extractExistingAnnotationAttribute(annotation, "consumes", consumes -> builder.consumes(consumes));
extractExistingAnnotationAttribute(annotation, "produces", produces -> builder.produces(produces));
extractExistingAnnotationAttribute(annotation, "headers", headers -> builder.headers(headers));
mapMethodAttribute(annotation, builder);

if(!annotation.hasAttribute("consumes")) {
builder.consumes(List.of(MediaType.APPLICATION_JSON_VALUE));
}
if(!annotation.hasAttribute("produces")) {
builder.produces(List.of(MediaType.APPLICATION_JSON_VALUE));
}

String fullyQualifiedReturnType = ((JavaType.Class) method
.getMethodDecl()
.getReturnTypeExpression()
.getType()).getFullyQualifiedName();

builder.returnType(fullyQualifiedReturnType);
}

private void extractExistingAnnotationAttribute(Annotation annotation, String attribute, Consumer<List<String>> valueConsumer) {
if(annotation.hasAttribute(attribute)) {
extractAnnotationAttribute(annotation, attribute, valueConsumer);
}
}

private void extractAnnotationAttribute(Annotation annotation, String attribute, Consumer<List<String>> consumer) {
Expression expression = annotation.getAttributes().get(attribute);
List<String> values = handleExpression(expression);
consumer.accept(values);
}

private void mapNameAttribute(Annotation annotation, RestMethod.RestMethodBuilder builder, String name) {
Expression value = annotation.getAttributes().get(name);
String s = value.getAssignmentRightSide().printVariable();
builder.name(s);
}

private void mapMethodAttribute(Annotation annotation, RestMethod.RestMethodBuilder builder) {
if(annotation.hasAttribute("method")) {
List<String> methods = handleExpression(annotation.getAttribute("method"));
builder.method(methods.stream().map(m -> RequestMethod.valueOf(m)).collect(Collectors.toList()));
}
findAndMapMethodAttribute(annotation, builder, GET_MAPPING.getFullyQualifiedName(), RequestMethod.GET);
findAndMapMethodAttribute(annotation, builder, POST_MAPPING.getFullyQualifiedName(), RequestMethod.POST);
findAndMapMethodAttribute(annotation, builder, PUT_MAPPING.getFullyQualifiedName(), RequestMethod.PUT);
findAndMapMethodAttribute(annotation, builder, DELETE_MAPPING.getFullyQualifiedName(), RequestMethod.DELETE);
findAndMapMethodAttribute(annotation, builder, PATCH_MAPPING.getFullyQualifiedName(), RequestMethod.PATCH);
}

private void findAndMapMethodAttribute(Annotation annotation, RestMethod.RestMethodBuilder builder, String requestNapping, RequestMethod requestMethod) {
if(annotation.getFullyQualifiedName().equals(requestNapping)) {
builder.method(List.of(requestMethod));
}
}

private List<String> handleLiteral(org.openrewrite.java.tree.Expression wrapped) {
J.Literal literal = J.Literal.class.cast(wrapped);
return List.of((String)literal.getValue());
}

@NotNull
private List<String> handleArray(org.openrewrite.java.tree.Expression expression) {
J.NewArray array = J.NewArray.class.cast(expression);
List<String> elements = array
.getInitializer()
.stream()
.map(e -> this.handleExpression(e))
.filter(not(List::isEmpty))
.map(s -> s.get(0))
.collect(Collectors.toList());
return elements;
}

private List<String> handleExpression(Expression value) {
OpenRewriteExpression openRewriteExpression = OpenRewriteExpression.class.cast(value);
org.openrewrite.java.tree.Expression wrapped = openRewriteExpression.getWrapped();
return handleExpression(wrapped);
}

private List<String> handleExpression(org.openrewrite.java.tree.Expression expression) {
List<String> elements = new ArrayList<>();
if(J.Literal.class.isInstance(expression)) {
elements = handleLiteral(expression);
}
else if(J.NewArray.class.isInstance(expression)) {
elements = handleArray(expression);
}
else if(J.Assignment.class.isInstance(expression)) {
J.Assignment assignment = J.Assignment.class.cast(expression);
org.openrewrite.java.tree.Expression assignment1 = assignment.getAssignment();
if(J.NewArray.class.isInstance(assignment1)) {
elements = handleArray(assignment1);
}
else if(J.FieldAccess.class.isInstance(assignment.getAssignment())) {
elements = List.of(staticFieldReference(assignment.getAssignment()));
}
} else if(J.FieldAccess.class.isInstance(expression)) {
elements = List.of(staticFieldReference(expression));
}
return elements;
}

private String staticFieldReference(org.openrewrite.java.tree.Expression expression) {
J.FieldAccess fieldAccess = J.FieldAccess.class.cast(expression);
String fqName = ((JavaType.Class) fieldAccess.getTarget().getType()).getFullyQualifiedName();
Class<?> aClass = null;
String methodName = fieldAccess.getName().getSimpleName();
try {
aClass = Class.forName(fqName);
Object o = aClass.getDeclaredField(methodName).get(null);
return o.toString();
} catch (NoSuchFieldException | ClassNotFoundException | IllegalAccessException e) {
throw new RuntimeException(String.format("Exception while attempting to resolve the value for constant '%s.%s'", aClass, methodName));
}
}
}
Loading