Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement @Attribute Injection. #5547

Merged
merged 41 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
eac6431
skeleton code for @Attribute injection.
chickenchickenlove Mar 29, 2024
2801cdd
Implement Attribute Injection : Apply review
chickenchickenlove Apr 1, 2024
889d887
make test code for Attribute injection.
chickenchickenlove Apr 1, 2024
94a93af
remove duplicate code from AnnotatedValueResolverTest
chickenchickenlove Apr 1, 2024
42ef9a2
Implement Attribute Injection : Apply review2
chickenchickenlove Apr 2, 2024
ab8a08a
Implement Attribute Injection : Apply review3
chickenchickenlove Apr 3, 2024
3fac84e
Implement Attribute Injection : Apply review3. remove useless comments.
chickenchickenlove Apr 3, 2024
f3f8b74
Fix lint, Apply review
chickenchickenlove Apr 3, 2024
821e18f
Add user docs
chickenchickenlove Apr 3, 2024
dce9520
Update core/src/main/java/com/linecorp/armeria/server/annotation/Attr…
chickenchickenlove Apr 5, 2024
fc5771a
Update core/src/main/java/com/linecorp/armeria/server/annotation/Attr…
chickenchickenlove Apr 5, 2024
f017563
Update core/src/main/java/com/linecorp/armeria/server/annotation/Attr…
chickenchickenlove Apr 5, 2024
1927e46
Update core/src/main/java/com/linecorp/armeria/server/annotation/Attr…
chickenchickenlove Apr 5, 2024
ee7a466
Use @default.class and fix link of java docs.
chickenchickenlove Apr 5, 2024
65868b7
Merge branch 'main' into 240329-try1
chickenchickenlove Apr 5, 2024
1d0b6c7
fix lint error
chickenchickenlove Apr 5, 2024
1877117
Update site/src/pages/docs/server-annotated-service.mdx
chickenchickenlove Apr 9, 2024
bdb0088
Update site/src/pages/docs/server-annotated-service.mdx
chickenchickenlove Apr 9, 2024
29543f9
Apply comment to server-annotated-service docs.
chickenchickenlove Apr 9, 2024
957378f
Update core/src/main/java/com/linecorp/armeria/server/annotation/Attr…
chickenchickenlove Apr 12, 2024
2893d3d
modify misstypo
chickenchickenlove Apr 12, 2024
c70c2fb
remove findName().
chickenchickenlove Apr 12, 2024
c1de466
Throw ClassCastException on AttributeResolver.
chickenchickenlove Apr 12, 2024
e6632fa
modify miss typoe
chickenchickenlove Apr 17, 2024
065e43a
Apply code review
chickenchickenlove Apr 17, 2024
e80075f
apply code review
chickenchickenlove Apr 18, 2024
5874745
apply review
chickenchickenlove Apr 18, 2024
c6519ab
apply review
chickenchickenlove Apr 18, 2024
bb087a7
apply review
chickenchickenlove Apr 18, 2024
4560943
apply review
chickenchickenlove Apr 22, 2024
0dd3e77
Update core/src/main/java/com/linecorp/armeria/server/annotation/Attr…
chickenchickenlove Apr 23, 2024
65bc886
throw immedetely if valued is failed to be cast.
chickenchickenlove Apr 23, 2024
c27629b
Update core/src/main/java/com/linecorp/armeria/internal/server/annota…
chickenchickenlove Apr 24, 2024
3943f70
Update core/src/main/java/com/linecorp/armeria/internal/server/annota…
chickenchickenlove Apr 24, 2024
3df348b
Update core/src/test/java/com/linecorp/armeria/internal/server/annota…
chickenchickenlove Apr 24, 2024
7230534
apply review
chickenchickenlove Apr 24, 2024
72d26b2
fix lint error.
chickenchickenlove Apr 24, 2024
8f3631c
Update core/src/main/java/com/linecorp/armeria/server/annotation/Attr…
chickenchickenlove May 2, 2024
9013388
apply review
chickenchickenlove May 2, 2024
588c8bb
Merge branch 'main' into 240329-try1
chickenchickenlove May 7, 2024
70d76cb
fix lint error
chickenchickenlove May 7, 2024
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 @@ -25,20 +25,19 @@
import com.google.common.base.Ascii;
import com.google.common.base.CaseFormat;

import com.linecorp.armeria.server.annotation.Attribute;
import com.linecorp.armeria.server.annotation.Header;
import com.linecorp.armeria.server.annotation.Param;

final class AnnotatedElementNameUtil {

/**
* Returns the value of the {@link Param} annotation which is specified on the {@code element} if
* Returns the value of the {@link Param} or {@link Attribute} annotation which is specified on the {@code element} if
* the value is not blank. If the value is blank, it returns the name of the specified
* {@code nameRetrievalTarget} object which is an instance of {@link Parameter} or {@link Field}.
* {@code nameRetrievalTarget} object which is an instance of Both {@link Parameter} and {@link Attribute} {} or {@link Field}.
*/
static String findName(Param param, Object nameRetrievalTarget) {
static String findName(Object nameRetrievalTarget, String value) {
requireNonNull(nameRetrievalTarget, "nameRetrievalTarget");

final String value = param.value();
if (DefaultValues.isSpecified(value)) {
checkArgument(!value.isEmpty(), "value is empty.");
return value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -55,6 +56,7 @@
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -64,6 +66,7 @@
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Primitives;

import com.linecorp.armeria.common.AggregatedHttpRequest;
import com.linecorp.armeria.common.Cookie;
Expand All @@ -84,6 +87,7 @@
import com.linecorp.armeria.internal.server.FileAggregatedMultipart;
import com.linecorp.armeria.internal.server.annotation.AnnotatedBeanFactoryRegistry.BeanFactoryId;
import com.linecorp.armeria.server.ServiceRequestContext;
import com.linecorp.armeria.server.annotation.Attribute;
import com.linecorp.armeria.server.annotation.ByteArrayRequestConverterFunction;
import com.linecorp.armeria.server.annotation.Default;
import com.linecorp.armeria.server.annotation.Delimiter;
Expand All @@ -99,6 +103,7 @@
import com.linecorp.armeria.server.docs.DescriptionInfo;

import io.netty.handler.codec.http.HttpConstants;
import io.netty.util.AttributeKey;
import scala.concurrent.ExecutionContext;

final class AnnotatedValueResolver {
Expand Down Expand Up @@ -444,9 +449,16 @@ private static AnnotatedValueResolver of(AnnotatedElement annotatedElement,
requireNonNull(dependencyInjector, "dependencyInjector");

final DescriptionInfo description = findDescription(annotatedElement);

final Attribute attr = annotatedElement.getAnnotation(Attribute.class);
if (attr != null) {
final String name = findName(typeElement, attr.value());
return ofAttribute(name, attr, annotatedElement, typeElement, type, description);
}

final Param param = annotatedElement.getAnnotation(Param.class);
if (param != null) {
final String name = findName(param, typeElement);
final String name = findName(typeElement, param.value());
if (type == File.class || type == Path.class || type == MultipartFile.class) {
return ofFileParam(name, annotatedElement, typeElement, type, description);
}
Expand Down Expand Up @@ -520,6 +532,7 @@ static List<RequestObjectResolver> addToFirstIfExists(List<RequestObjectResolver

private static boolean isAnnotationPresent(AnnotatedElement element) {
return element.isAnnotationPresent(Param.class) ||
element.isAnnotationPresent(Attribute.class) ||
element.isAnnotationPresent(Header.class) ||
element.isAnnotationPresent(RequestObject.class);
}
Expand Down Expand Up @@ -621,6 +634,46 @@ private static AnnotatedValueResolver ofRequestObject(String name, AnnotatedElem
.build();
}

private static AnnotatedValueResolver ofAttribute(String name,
Attribute attr,
AnnotatedElement annotatedElement,
AnnotatedElement typeElement, Class<?> type,
DescriptionInfo description) {

final List<AttributeKey<?>> attrKeys = new ArrayList<>();
trustin marked this conversation as resolved.
Show resolved Hide resolved

final AttributeKey<?> prefixTypeAttrKey = AttributeKey.valueOf(attr.prefix(), name);
attrKeys.add(prefixTypeAttrKey);

final AttributeKey<?> declaredTypeKey = AttributeKey.valueOf(type, name);
attrKeys.add(declaredTypeKey);

if (type.isPrimitive()) {
final Class<?> mustRefType = Primitives.wrap(type);
final AttributeKey<?> mustRefTypeKey = AttributeKey.valueOf(mustRefType, name);
attrKeys.add(mustRefTypeKey);
}

final AttributeKey<Object> objectTypeAttrKey = AttributeKey.valueOf(name);
attrKeys.add(objectTypeAttrKey);

return new Builder(annotatedElement, type, name)
.annotationType(Attribute.class)
.typeElement(typeElement)
.supportDefault(true)
.supportContainer(true)
.description(description)
.resolver(attributeResolver(ctx -> {
for (AttributeKey<?> attrKey : attrKeys) {
final Object value = ctx.context.attr(attrKey);
if (value != null)
return value;
}
return null;
}))
trustin marked this conversation as resolved.
Show resolved Hide resolved
.build();
}

@Nullable
private static AnnotatedValueResolver ofInjectableTypes(String name, AnnotatedElement annotatedElement,
Class<?> type, boolean useBlockingExecutor) {
Expand Down Expand Up @@ -775,6 +828,17 @@ private static AnnotatedValueResolver ofInjectableTypes0(String name, AnnotatedE
};
}

private static BiFunction<AnnotatedValueResolver, ResolverContext, Object>
attributeResolver(Function<ResolverContext, Object> getter) {
return (resolver, ctx) -> {
Object value = getter.apply(ctx);
if (value != null) {
return value;
}
return resolver.defaultOrException();
};
}
trustin marked this conversation as resolved.
Show resolved Hide resolved

/**
* Returns a bean resolver which retrieves a value using request converters. If the target element
* is an annotated bean, a bean factory of the specified {@link BeanFactoryId} will be used for creating an
Expand Down Expand Up @@ -1089,6 +1153,7 @@ private Builder(AnnotatedElement annotatedElement, Type type, String name) {
*/
private Builder annotationType(Class<? extends Annotation> annotationType) {
assert annotationType == Param.class ||
annotationType == Attribute.class ||
annotationType == Header.class ||
annotationType == RequestObject.class : annotationType.getSimpleName();
this.annotationType = annotationType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.linecorp.armeria.server.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.linecorp.armeria.internal.server.annotation.DefaultValues;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Attribute {
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved

String value();
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
Class<?> prefix() default DefaultValues.class;
}
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,21 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.BeforeMethod;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
Expand All @@ -66,13 +71,15 @@
import com.linecorp.armeria.server.RoutingResult;
import com.linecorp.armeria.server.RoutingResultBuilder;
import com.linecorp.armeria.server.ServiceRequestContext;
import com.linecorp.armeria.server.annotation.Attribute;
import com.linecorp.armeria.server.annotation.Default;
import com.linecorp.armeria.server.annotation.Get;
import com.linecorp.armeria.server.annotation.Header;
import com.linecorp.armeria.server.annotation.Param;
import com.linecorp.armeria.server.annotation.RequestObject;

import io.netty.util.AsciiString;
import io.netty.util.AttributeKey;

class AnnotatedValueResolverTest {

Expand Down Expand Up @@ -149,7 +156,8 @@ void ofMethods() {
try {
final List<AnnotatedValueResolver> elements = AnnotatedValueResolver.ofServiceMethod(
method, pathParams, objectResolvers, false, noopDependencyInjector, null);
elements.forEach(AnnotatedValueResolverTest::testResolver);
final Map<String, AttributeKey<?>> attrKeyMap = injectAttrKeyToServiceContextForAttributeTest();
elements.forEach(annotatedValueResolver -> testResolver(annotatedValueResolver, attrKeyMap));
} catch (NoAnnotatedParameterException ignored) {
// Ignore this exception because MixedBean class has not annotated method.
}
Expand Down Expand Up @@ -238,6 +246,24 @@ private static <T> void testMethod(Method method, T bean) {
}
}

@SuppressWarnings("unchecked")
private static void testResolver(AnnotatedValueResolver resolver, Map<String, AttributeKey<?>> attrKeys) {
// When
final Object value = resolver.resolve(resolverContext);
logger.debug("Element {}: value {}", resolver, value);

if (resolver.annotationType() == Attribute.class) {
final AttributeKey<?> attrKey = attrKeys.get(resolver.httpElementName());
final Object expectedValue = resolverContext.context().attr(attrKey);

// Then
assertThat(value).isEqualTo(expectedValue);
assertThat(value).isNotNull();
} else {
testResolver(resolver);
}
}

@SuppressWarnings("unchecked")
private static void testResolver(AnnotatedValueResolver resolver) {
final Object value = resolver.resolve(resolverContext);
Expand Down Expand Up @@ -405,6 +431,21 @@ void redundant2(@Param @Default("defaultValue") String var1) {}
@Get("/r3/:var1")
void redundant3(@Param Optional<String> var1) {}

@Get("/attribute-test")
void attributeTest(
@Attribute(value = "primitiveIntWithPrimitivePrefix", prefix = int.class) int primitiveIntWithPrimitivePrefix,
@Attribute(value = "primitiveIntWithoutPrefix") int primitiveIntOutPrefix,
@Attribute(value = "primitiveIntWithReferencePrefix", prefix = Integer.class) int ReferenceIntWithReferencePrefix,
@Attribute(value = "primitiveIntWithoutType") int primitiveIntWithoutType,
@Attribute(value = "referenceIntWithPrimitivePrefix", prefix = int.class) Integer referenceIntWithPrimitivePrefix,
@Attribute(value = "referenceIntWithoutPrefix") Integer referenceIntWithoutPrefix,
@Attribute(value = "referenceIntWithoutType") Integer referenceIntWithoutType,
@Attribute(value = "referenceIntWithReferencePrefix", prefix = Integer.class) Integer referenceIntWithReferencePrefix,
@Attribute(value = "stringCollectionWithPrefix", prefix = List.class) List<String> stringCollectionWithPrefix,
@Attribute(value = "stringCollectionWithoutPrefix") List<String> stringCollectionWithoutPrefix,
@Attribute(value = "stringCollectionWithoutType") List<String> stringCollectionWithoutType) { }


void time(@Param @Default("PT20.345S") Duration duration,
@Param @Default("2007-12-03T10:15:30.00Z") Instant instant,
@Param @Default("2007-12-03") LocalDate localDate,
Expand All @@ -418,6 +459,53 @@ void time(@Param @Default("PT20.345S") Duration duration,
@Param @Default("+01:00:00") ZoneOffset zoneOffset) {}
}

private Map<String, AttributeKey<?>> injectAttrKeyToServiceContextForAttributeTest() {
chickenchickenlove marked this conversation as resolved.
Show resolved Hide resolved

ServiceRequestContext ctx = resolverContext.context();

AttributeKey<Integer> primitiveIntWithPrimitivePrefix = AttributeKey.valueOf(int.class, "primitiveIntWithPrimitivePrefix");
AttributeKey<Integer> primitiveIntWithoutPrefix = AttributeKey.valueOf(int.class, "primitiveIntWithoutPrefix");
AttributeKey<Integer> primitiveIntWithReferencePrefix = AttributeKey.valueOf(int.class, "primitiveIntWithReferencePrefix");
AttributeKey<Integer> primitiveIntWithoutType = AttributeKey.valueOf("primitiveIntWithoutType");
AttributeKey<Integer> referenceIntWithPrimitivePrefix = AttributeKey.valueOf(Integer.class, "referenceIntWithPrimitivePrefix");
AttributeKey<Integer> referenceIntWithoutPrefix = AttributeKey.valueOf(Integer.class, "referenceIntWithoutPrefix");
AttributeKey<Integer> referenceIntWithReferencePrefix = AttributeKey.valueOf(Integer.class, "referenceIntWithReferencePrefix");
AttributeKey<Integer> referenceIntWithoutType = AttributeKey.valueOf(Integer.class, "referenceIntWithoutType");
AttributeKey<List<String>> stringCollectionWithPrefix = AttributeKey.valueOf(List.class, "stringCollectionWithPrefix");
AttributeKey<List<String>> stringCollectionWithoutPrefix = AttributeKey.valueOf(List.class, "stringCollectionWithoutPrefix");
AttributeKey<List<String>> stringCollectionWithoutType = AttributeKey.valueOf(List.class, "stringCollectionWithoutType");

ctx.setAttr(primitiveIntWithPrimitivePrefix, 10);
ctx.setAttr(primitiveIntWithoutPrefix, 10);
ctx.setAttr(primitiveIntWithReferencePrefix, 10);
ctx.setAttr(primitiveIntWithoutType, 10);
ctx.setAttr(referenceIntWithPrimitivePrefix, 10);
ctx.setAttr(referenceIntWithoutPrefix, 10);
ctx.setAttr(referenceIntWithReferencePrefix, 10);
ctx.setAttr(referenceIntWithoutType, 10);

ArrayList<String> testValue = new ArrayList<>();
testValue.add("A");
ctx.setAttr(stringCollectionWithPrefix, testValue);
ctx.setAttr(stringCollectionWithoutPrefix, testValue);
ctx.setAttr(stringCollectionWithoutType, testValue);

Map<String, AttributeKey<?>> expectedCtx = new HashMap<>();
expectedCtx.put("primitiveIntWithPrimitivePrefix", primitiveIntWithPrimitivePrefix);
expectedCtx.put("primitiveIntWithoutPrefix", primitiveIntWithoutPrefix);
expectedCtx.put("primitiveIntWithReferencePrefix", primitiveIntWithReferencePrefix);
expectedCtx.put("primitiveIntWithoutType", primitiveIntWithoutType);
expectedCtx.put("referenceIntWithPrimitivePrefix", referenceIntWithPrimitivePrefix);
expectedCtx.put("referenceIntWithoutPrefix", referenceIntWithoutPrefix);
expectedCtx.put("referenceIntWithReferencePrefix", referenceIntWithReferencePrefix);
expectedCtx.put("referenceIntWithoutType", referenceIntWithoutType);
expectedCtx.put("stringCollectionWithPrefix", stringCollectionWithPrefix);
expectedCtx.put("stringCollectionWithoutPrefix", stringCollectionWithoutPrefix);
expectedCtx.put("stringCollectionWithoutType", stringCollectionWithoutType);

return expectedCtx;
}

interface Bean {
String var1();

Expand Down
Loading