diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/ClientInterceptorsConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/ClientInterceptorsConfigurer.java index 67a02321..a4b2e5c8 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/client/ClientInterceptorsConfigurer.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/ClientInterceptorsConfigurer.java @@ -37,7 +37,7 @@ public class ClientInterceptorsConfigurer implements InitializingBean { private final ApplicationContext applicationContext; - private List globalInterceptors; + private List globalInterceptors = new ArrayList<>(); public ClientInterceptorsConfigurer(ApplicationContext applicationContext) { this.applicationContext = applicationContext; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java index c260f40b..e5e3a8bf 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java @@ -55,7 +55,7 @@ public class DefaultGrpcChannelFactory> private final ClientInterceptorsConfigurer interceptorsConfigurer; - private ClientInterceptorFilter interceptorFilter; + private @Nullable ClientInterceptorFilter interceptorFilter; private ChannelCredentialsProvider credentials = ChannelCredentialsProvider.INSECURE; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcClientFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcClientFactory.java index e44c1583..b4d3edfd 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcClientFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcClientFactory.java @@ -24,8 +24,11 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -39,6 +42,7 @@ import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; import org.springframework.grpc.internal.ClasspathScanner; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -59,7 +63,7 @@ public class GrpcClientFactory implements ApplicationContextAware { private Map, StubFactory> factories = new LinkedHashMap<>(); - private ApplicationContext context; + private @Nullable ApplicationContext context; static { DEFAULT_FACTORIES.add((Class>) BlockingStubFactory.class); @@ -74,6 +78,10 @@ public void setApplicationContext(ApplicationContext applicationContext) throws this.context = applicationContext; } + private ApplicationContext requireNonNullContext() { + return Objects.requireNonNull(this.context, "ApplicationContext is required"); + } + public T getClient(String target, Class type, Class factory) { @SuppressWarnings("unchecked") StubFactory stubs = (StubFactory) findFactory(factory, type); @@ -84,7 +92,7 @@ public T getClient(String target, Class type, Class factory) { private StubFactory findFactory(Class factoryType, Class type) { if (this.factories.isEmpty()) { List> factories = new ArrayList<>(); - for (StubFactory factory : this.context.getBeansOfType(StubFactory.class).values()) { + for (StubFactory factory : this.requireNonNullContext().getBeansOfType(StubFactory.class).values()) { factories.add(factory); } AnnotationAwareOrderComparator.sort(factories); @@ -96,7 +104,9 @@ private StubFactory findFactory(Class factoryType, Class type) { continue; } this.factories.put(factory, - (StubFactory) this.context.getAutowireCapableBeanFactory().createBean(factory)); + (StubFactory) this.requireNonNullContext() + .getAutowireCapableBeanFactory() + .createBean(factory)); } } StubFactory factory = findFactory(this.factories, factoryType, type); @@ -107,7 +117,8 @@ private StubFactory findFactory(Class factoryType, Class type) { return factory; } - private static Class findDefaultFactory(BeanDefinitionRegistry registry, Class factoryType, Class type) { + private static @Nullable Class findDefaultFactory(BeanDefinitionRegistry registry, + @Nullable Class factoryType, Class type) { if (factoryType != null && factoryType != UnspecifiedStubFactory.class) { return supports(factoryType, type) ? factoryType : null; } @@ -131,6 +142,8 @@ private static Set> locateFactoryTypes(BeanDefinitionRegistry registry) beanDefinition = (AbstractBeanDefinition) registry.getBeanDefinition(FACTORIES_BEAN_DEFINITION_NAME); @SuppressWarnings("unchecked") Set> factories = (Set>) beanDefinition.getAttribute("factories"); + Assert.notEmpty(factories, + "The %s bean requires a 'factories' attribute".formatted(FACTORIES_BEAN_DEFINITION_NAME)); return factories; } @@ -152,7 +165,7 @@ public static HashSet> findStubFactoryTypes(BeanDefinitionRegistry regi return factories; } - private static Class resolveBeanClass(BeanDefinition beanDefinition) { + private static @Nullable Class resolveBeanClass(BeanDefinition beanDefinition) { if (beanDefinition instanceof AbstractBeanDefinition rootBeanDefinition) { if (rootBeanDefinition.hasBeanClass()) { return rootBeanDefinition.getBeanClass(); @@ -161,6 +174,7 @@ private static Class resolveBeanClass(BeanDefinition beanDefinition) { return null; } + @SuppressWarnings("NullAway") private static boolean supports(Class factory, Class type) { // To avoid needing to instantiate the factory we use reflection to check for a // static supports() method. If it exists we call it. @@ -186,7 +200,7 @@ private static boolean supports(Class factory, Class type) { return supports; } - private static StubFactory findFactory(Map, StubFactory> values, Class factoryType, + private static @Nullable StubFactory findFactory(Map, StubFactory> values, Class factoryType, Class type) { StubFactory factory = null; if (factoryType != null && factoryType != UnspecifiedStubFactory.class) { @@ -204,7 +218,7 @@ private static StubFactory findFactory(Map, StubFactory> values, } private GrpcChannelFactory channels() { - return this.context.getBean(GrpcChannelFactory.class); + return this.requireNonNullContext().getBean(GrpcChannelFactory.class); } public static void register(BeanDefinitionRegistry registry, GrpcClientRegistrationSpec spec) { @@ -230,8 +244,8 @@ public static void register(BeanDefinitionRegistry registry, GrpcClientRegistrat } } - public record GrpcClientRegistrationSpec(String prefix, Class> factory, String target, - Class[] types, String[] packages) { + public record GrpcClientRegistrationSpec(String prefix, @Nullable Class> factory, + String target, Class[] types, String[] packages) { private static ClasspathScanner SCANNER = new ClasspathScanner(); @@ -276,7 +290,7 @@ public GrpcClientRegistrationSpec(String prefix, String target, Class[] types this(prefix, UnspecifiedStubFactory.class, target, types, new String[0]); } - public GrpcClientRegistrationSpec factory(Class> factory) { + public GrpcClientRegistrationSpec factory(@Nullable Class> factory) { return new GrpcClientRegistrationSpec(this.prefix, factory, this.target, this.types, this.packages); } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/aot/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/aot/package-info.java new file mode 100644 index 00000000..702d6cc2 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/aot/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * AOT (Ahead-Of-Time) processing for gRPC clients. + */ +@NullMarked +package org.springframework.grpc.client.aot; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/interceptor/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/interceptor/package-info.java new file mode 100644 index 00000000..2120528b --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/interceptor/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * gRPC client interceptors. + */ +@NullMarked +package org.springframework.grpc.client.interceptor; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/interceptor/security/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/interceptor/security/package-info.java new file mode 100644 index 00000000..3bd97585 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/interceptor/security/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * Security-related gRPC client interceptors. + */ +@NullMarked +package org.springframework.grpc.client.interceptor.security; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/client/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/client/package-info.java new file mode 100644 index 00000000..18787032 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/client/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * gRPC client infrastructure. + */ +@NullMarked +package org.springframework.grpc.client; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/internal/ClasspathScanner.java b/spring-grpc-core/src/main/java/org/springframework/grpc/internal/ClasspathScanner.java index b3c9a785..17a6ec6e 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/internal/ClasspathScanner.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/internal/ClasspathScanner.java @@ -24,6 +24,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.context.ResourceLoaderAware; @@ -49,9 +50,9 @@ public class ClasspathScanner implements ResourceLoaderAware { private String resourcePattern = DEFAULT_RESOURCE_PATTERN; - private ResourcePatternResolver resourcePatternResolver; + private @Nullable ResourcePatternResolver resourcePatternResolver; - private MetadataReaderFactory metadataReaderFactory; + private @Nullable MetadataReaderFactory metadataReaderFactory; @Override public void setResourceLoader(ResourceLoader resourceLoader) { diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/internal/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/internal/package-info.java new file mode 100644 index 00000000..9bc2d030 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/internal/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * Internal utilities and helpers. + */ +@NullMarked +package org.springframework.grpc.internal; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java index 6f80c988..d932a6c5 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/DefaultGrpcServerFactory.java @@ -66,15 +66,15 @@ public class DefaultGrpcServerFactory> implements Grp private final List> serverBuilderCustomizers; - private KeyManagerFactory keyManager; + private @Nullable KeyManagerFactory keyManager; - private TrustManagerFactory trustManager; + private @Nullable TrustManagerFactory trustManager; - private ClientAuth clientAuth; + private @Nullable ClientAuth clientAuth; - private ServerServiceDefinitionFilter serviceFilter; + private @Nullable ServerServiceDefinitionFilter serviceFilter; - private ServerInterceptorFilter interceptorFilter; + private @Nullable ServerInterceptorFilter interceptorFilter; public DefaultGrpcServerFactory(String address, List> serverBuilderCustomizers) { this.address = address; @@ -82,7 +82,8 @@ public DefaultGrpcServerFactory(String address, List> } public DefaultGrpcServerFactory(String address, List> serverBuilderCustomizers, - KeyManagerFactory keyManager, TrustManagerFactory trustManager, ClientAuth clientAuth) { + @Nullable KeyManagerFactory keyManager, @Nullable TrustManagerFactory trustManager, + @Nullable ClientAuth clientAuth) { this(address, serverBuilderCustomizers); this.keyManager = keyManager; this.trustManager = trustManager; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java index fb544e87..7562f60e 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/NettyGrpcServerFactory.java @@ -21,6 +21,8 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; +import org.jspecify.annotations.Nullable; + import io.grpc.TlsServerCredentials.ClientAuth; import io.grpc.netty.NettyServerBuilder; import io.netty.channel.MultiThreadIoEventLoopGroup; @@ -38,8 +40,9 @@ public class NettyGrpcServerFactory extends DefaultGrpcServerFactory { public NettyGrpcServerFactory(String address, - List> serverBuilderCustomizers, KeyManagerFactory keyManager, - TrustManagerFactory trustManager, ClientAuth clientAuth) { + List> serverBuilderCustomizers, + @Nullable KeyManagerFactory keyManager, @Nullable TrustManagerFactory trustManager, + @Nullable ClientAuth clientAuth) { super(address, serverBuilderCustomizers, keyManager, trustManager, clientAuth); } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java index 98c87f06..5a493470 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/ShadedNettyGrpcServerFactory.java @@ -21,6 +21,8 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; +import org.jspecify.annotations.Nullable; + import io.grpc.TlsServerCredentials.ClientAuth; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; import io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup; @@ -37,8 +39,9 @@ public class ShadedNettyGrpcServerFactory extends DefaultGrpcServerFactory { public ShadedNettyGrpcServerFactory(String address, - List> serverBuilderCustomizers, KeyManagerFactory keyManager, - TrustManagerFactory trustManager, ClientAuth clientAuth) { + List> serverBuilderCustomizers, + @Nullable KeyManagerFactory keyManager, @Nullable TrustManagerFactory trustManager, + @Nullable ClientAuth clientAuth) { super(address, serverBuilderCustomizers, keyManager, trustManager, clientAuth); } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/CompositeGrpcExceptionHandler.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/CompositeGrpcExceptionHandler.java index cf169529..3391298a 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/CompositeGrpcExceptionHandler.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/CompositeGrpcExceptionHandler.java @@ -16,6 +16,8 @@ package org.springframework.grpc.server.exception; +import org.jspecify.annotations.Nullable; + import io.grpc.StatusException; public class CompositeGrpcExceptionHandler implements GrpcExceptionHandler { @@ -27,7 +29,7 @@ public CompositeGrpcExceptionHandler(GrpcExceptionHandler... exceptionHandlers) } @Override - public StatusException handleException(Throwable exception) { + public @Nullable StatusException handleException(Throwable exception) { for (GrpcExceptionHandler exceptionHandler : this.exceptionHandlers) { StatusException status = exceptionHandler.handleException(exception); if (status != null) { diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandledServerCall.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandledServerCall.java index 4d1cf88e..16e60e95 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandledServerCall.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandledServerCall.java @@ -35,13 +35,15 @@ protected GrpcExceptionHandledServerCall(ServerCall delegate, GrpcE @Override public void close(Status status, Metadata trailers) { if (status.getCode() == Status.Code.UNKNOWN && status.getCause() != null) { - final Throwable cause = status.getCause(); - final StatusException statusException = this.exceptionHandler.handleException(cause); - Metadata statusExceptionTrailers = statusException.getTrailers(); - if (statusExceptionTrailers != null) { - trailers.merge(statusExceptionTrailers); + StatusException statusException = this.exceptionHandler.handleException(status.getCause()); + if (statusException != null) { + Metadata statusExceptionTrailers = statusException.getTrailers(); + if (statusExceptionTrailers != null) { + trailers.merge(statusExceptionTrailers); + } + status = statusException.getStatus(); } - super.close(statusException.getStatus(), trailers); + super.close(status, trailers); } else { super.close(status, trailers); diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandler.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandler.java index ca30b9ce..154c25ca 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandler.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandler.java @@ -16,6 +16,8 @@ package org.springframework.grpc.server.exception; +import org.jspecify.annotations.Nullable; + import io.grpc.StatusException; /** @@ -33,6 +35,7 @@ public interface GrpcExceptionHandler { * @return the status to return to the client, or {@code null} if the exception cannot * be classified */ + @Nullable StatusException handleException(Throwable exception); } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java index 0159a4b5..76946335 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/GrpcExceptionHandlerInterceptor.java @@ -18,6 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -49,6 +50,8 @@ @Order(Ordered.HIGHEST_PRECEDENCE) public class GrpcExceptionHandlerInterceptor implements ServerInterceptor { + private final Log logger = LogFactory.getLog(getClass()); + private final GrpcExceptionHandler exceptionHandler; public GrpcExceptionHandlerInterceptor(GrpcExceptionHandler exceptionHandler) { @@ -68,19 +71,21 @@ public GrpcExceptionHandlerInterceptor(GrpcExceptionHandler exceptionHandler) { public Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { Listener listener; - FallbackHandler handler = new FallbackHandler(this.exceptionHandler); - final GrpcExceptionHandledServerCall exceptionHandledServerCall = new GrpcExceptionHandledServerCall<>( - call, handler); + FallbackHandler fallbackHandler = new FallbackHandler(this.exceptionHandler); + GrpcExceptionHandledServerCall exceptionHandledServerCall = new GrpcExceptionHandledServerCall<>( + call, fallbackHandler); try { listener = next.startCall(exceptionHandledServerCall, headers); } catch (Throwable t) { - exceptionHandledServerCall.close(handler.handleException(t).getStatus(), headers(t)); - listener = new Listener<>() { + this.logger.trace("Failed to start exception handler call", t); + StatusException statusEx = fallbackHandler.handleException(t); + exceptionHandledServerCall.close(statusEx != null ? statusEx.getStatus() : Status.fromThrowable(t), + headers(t)); + return new Listener<>() { }; - return listener; } - return new ExceptionHandlerListener<>(listener, exceptionHandledServerCall, handler); + return new ExceptionHandlerListener<>(listener, exceptionHandledServerCall, fallbackHandler); } private static Metadata headers(Throwable t) { @@ -90,11 +95,13 @@ private static Metadata headers(Throwable t) { static class ExceptionHandlerListener extends SimpleForwardingServerCallListener { + private final Log logger = LogFactory.getLog(getClass()); + private ServerCall call; private GrpcExceptionHandler exceptionHandler; - volatile private Throwable exception; + volatile private @Nullable Throwable exception; ExceptionHandlerListener(ServerCall.Listener delegate, ServerCall call, GrpcExceptionHandler exceptionHandler) { @@ -144,14 +151,18 @@ public void onHalfClose() { private void handle(Throwable t) { this.exception = t; - StatusException status = Status.fromThrowable(t).asException(); + StatusException statusEx = Status.fromThrowable(t).asException(); try { - status = this.exceptionHandler.handleException(t); + statusEx = this.exceptionHandler.handleException(t); } catch (Throwable e) { + this.logger.trace("Handler unable to handle exception", t); + } + if (statusEx == null) { + statusEx = Status.fromThrowable(t).asException(); } try { - this.call.close(status.getStatus(), headers(status)); + this.call.close(statusEx.getStatus(), headers(statusEx)); } catch (Throwable e) { throw new IllegalStateException("Failed to close the call", e); @@ -171,7 +182,7 @@ static class FallbackHandler implements GrpcExceptionHandler { } @Override - public StatusException handleException(Throwable exception) { + public @Nullable StatusException handleException(Throwable exception) { StatusException status = this.exceptionHandler.handleException(exception); if (status == null) { if (logger.isDebugEnabled()) { diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/ReactiveStubBeanDefinitionRegistrar.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/ReactiveStubBeanDefinitionRegistrar.java index 4cf69c72..b322a22a 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/ReactiveStubBeanDefinitionRegistrar.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/ReactiveStubBeanDefinitionRegistrar.java @@ -17,6 +17,9 @@ package org.springframework.grpc.server.exception; import java.lang.reflect.Method; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; @@ -63,18 +66,22 @@ static class ReactiveStubBeanFactoryPostProcessor */ public static final String BEAN_NAME = ReactiveStubBeanFactoryPostProcessor.class.getName(); - private CompositeGrpcExceptionHandler handler; + private @Nullable CompositeGrpcExceptionHandler handler; - private ApplicationContext context; + private @Nullable ApplicationContext context; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } + private ApplicationContext requireNonNullContext() { + return Objects.requireNonNull(this.context, "Context must not be null"); + } + private Throwable onErrorMap(Throwable throwable) { if (this.handler == null) { - GrpcExceptionHandler[] handlers = this.context.getAutowireCapableBeanFactory() + GrpcExceptionHandler[] handlers = requireNonNullContext().getAutowireCapableBeanFactory() .getBeanProvider(GrpcExceptionHandler.class) .orderedStream() .toArray(GrpcExceptionHandler[]::new); @@ -86,7 +93,7 @@ private Throwable onErrorMap(Throwable throwable) { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) { - if (this.context.getBeanNamesForType(GrpcExceptionHandler.class).length == 0) { + if (requireNonNullContext().getBeanNamesForType(GrpcExceptionHandler.class).length == 0) { return; } for (String name : factory.getBeanNamesForType(BindableService.class)) { diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/package-info.java new file mode 100644 index 00000000..4d9c3a5b --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/exception/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * Exception handling for gRPC servers. + */ +@NullMarked +package org.springframework.grpc.server.exception; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/GrpcServerLifecycle.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/GrpcServerLifecycle.java index 2e6220e8..3596ce6e 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/GrpcServerLifecycle.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/GrpcServerLifecycle.java @@ -25,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.SmartLifecycle; @@ -50,7 +51,7 @@ public class GrpcServerLifecycle implements SmartLifecycle { private final ApplicationEventPublisher eventPublisher; - private Server server; + private @Nullable Server server; /** * Creates a new GrpcServerLifecycle. diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/package-info.java index 4e8d3ec8..c0d1b557 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/package-info.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/lifecycle/package-info.java @@ -1,5 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + /** * gRPC server events. */ - +@NullMarked package org.springframework.grpc.server.lifecycle; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/package-info.java new file mode 100644 index 00000000..96ada6b2 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * gRPC server infrastructure. + */ +@NullMarked +package org.springframework.grpc.server; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/AuthenticationProcessInterceptor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/AuthenticationProcessInterceptor.java index 15bf7b2a..1d8b9b6c 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/AuthenticationProcessInterceptor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/AuthenticationProcessInterceptor.java @@ -22,6 +22,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; @@ -82,7 +83,8 @@ public Listener interceptCall(ServerCall call, AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); } Authentication authentication = user; - if (!this.authorizationManager.authorize(() -> authentication, context).isGranted()) { + AuthorizationResult authResult = this.authorizationManager.authorize(() -> authentication, context); + if (authResult == null || !authResult.isGranted()) { if (user instanceof AnonymousAuthenticationToken) { throw new BadCredentialsException("not authenticated"); } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/BearerTokenAuthenticationExtractor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/BearerTokenAuthenticationExtractor.java index 4f4b7980..6407adab 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/BearerTokenAuthenticationExtractor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/BearerTokenAuthenticationExtractor.java @@ -18,6 +18,8 @@ import java.util.Locale; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; @@ -38,7 +40,7 @@ public class BearerTokenAuthenticationExtractor implements GrpcAuthenticationExtractor { @Override - public Authentication extract(Metadata headers, Attributes attributes, MethodDescriptor method) { + public @Nullable Authentication extract(Metadata headers, Attributes attributes, MethodDescriptor method) { String auth = headers.get(GrpcSecurity.AUTHORIZATION_KEY); if (auth == null) { return null; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcAuthenticationExtractor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcAuthenticationExtractor.java index bfba6c44..e3031e5a 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcAuthenticationExtractor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcAuthenticationExtractor.java @@ -16,6 +16,8 @@ package org.springframework.grpc.server.security; +import org.jspecify.annotations.Nullable; + import org.springframework.security.core.Authentication; import io.grpc.Attributes; @@ -24,6 +26,7 @@ public interface GrpcAuthenticationExtractor { + @Nullable Authentication extract(Metadata headers, Attributes attributes, MethodDescriptor method); } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcSecurity.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcSecurity.java index f2c89643..582509fe 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcSecurity.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/GrpcSecurity.java @@ -18,6 +18,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @@ -75,11 +78,11 @@ public final class GrpcSecurity */ public static final int CONTEXT_FILTER_ORDER = 0; - private AuthenticationManager authenticationManager; + private @Nullable AuthenticationManager authenticationManager; private List authenticationExtractors = new ArrayList<>(); - private AuthorizationManager authorizationManager; + private @Nullable AuthorizationManager authorizationManager; public GrpcSecurity(ObjectPostProcessor objectPostProcessor, AuthenticationManagerBuilder authenticationBuilder, ApplicationContext context) { @@ -103,6 +106,7 @@ private ApplicationContext getContext() { @Override protected AuthenticationProcessInterceptor performBuild() { + Objects.requireNonNull(this.authorizationManager, "AuthorizationManager is required"); if (this.authenticationManager != null) { setSharedObject(AuthenticationManager.class, this.authenticationManager); } @@ -198,7 +202,8 @@ private static class CompositeAuthenticationExtractor implements GrpcAuthenticat } @Override - public Authentication extract(Metadata headers, Attributes attributes, MethodDescriptor method) { + public @Nullable Authentication extract(Metadata headers, Attributes attributes, + MethodDescriptor method) { for (GrpcAuthenticationExtractor extractor : this.extractors) { Authentication authentication = extractor.extract(headers, attributes, method); if (authentication != null) { diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/HttpBasicAuthenticationExtractor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/HttpBasicAuthenticationExtractor.java index 05b6c44e..b2deb7b4 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/HttpBasicAuthenticationExtractor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/HttpBasicAuthenticationExtractor.java @@ -20,6 +20,8 @@ import java.util.Base64; import java.util.Locale; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -39,7 +41,7 @@ public class HttpBasicAuthenticationExtractor implements GrpcAuthenticationExtractor { @Override - public Authentication extract(Metadata headers, Attributes attributes, MethodDescriptor method) { + public @Nullable Authentication extract(Metadata headers, Attributes attributes, MethodDescriptor method) { String auth = headers.get(GrpcSecurity.AUTHORIZATION_KEY); if (auth == null) { return null; @@ -51,7 +53,7 @@ public Authentication extract(Metadata headers, Attributes attributes, MethodDes return extract(auth); } - private Authentication extract(String auth) { + private @Nullable Authentication extract(String auth) { String[] parts = new String(Base64.getDecoder().decode(auth), StandardCharsets.UTF_8).split(":"); if (parts.length != 2) { return null; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/HttpBasicConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/HttpBasicConfigurer.java index 88547d68..e0449b13 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/HttpBasicConfigurer.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/HttpBasicConfigurer.java @@ -16,6 +16,8 @@ package org.springframework.grpc.server.security; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.SecurityBuilder; @@ -30,7 +32,7 @@ public final class HttpBasicConfigurer jwtAuthenticationConverter; + private @Nullable Converter jwtAuthenticationConverter; JwtConfigurer(ApplicationContext context) { this.context = context; @@ -138,6 +141,7 @@ JwtDecoder getJwtDecoder() { return this.decoder; } + @Nullable AuthenticationProvider getAuthenticationProvider() { if (this.authenticationManager != null) { return null; @@ -162,17 +166,17 @@ public class OpaqueTokenConfigurer { private final ApplicationContext context; - private AuthenticationManager authenticationManager; + private @Nullable AuthenticationManager authenticationManager; - private String introspectionUri; + private @Nullable String introspectionUri; - private String clientId; + private @Nullable String clientId; - private String clientSecret; + private @Nullable String clientSecret; - private Supplier introspector; + private @Nullable Supplier introspector; - private OpaqueTokenAuthenticationConverter authenticationConverter; + private @Nullable OpaqueTokenAuthenticationConverter authenticationConverter; OpaqueTokenConfigurer(ApplicationContext context) { this.context = context; @@ -226,6 +230,7 @@ OpaqueTokenIntrospector getIntrospector() { return this.context.getBean(OpaqueTokenIntrospector.class); } + @Nullable OpaqueTokenAuthenticationConverter getAuthenticationConverter() { if (this.authenticationConverter != null) { return this.authenticationConverter; @@ -236,6 +241,7 @@ OpaqueTokenAuthenticationConverter getAuthenticationConverter() { return null; } + @Nullable AuthenticationProvider getAuthenticationProvider() { if (this.authenticationManager != null) { return null; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/PreAuthConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/PreAuthConfigurer.java index 7c75edb6..ab122912 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/PreAuthConfigurer.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/PreAuthConfigurer.java @@ -16,6 +16,8 @@ package org.springframework.grpc.server.security; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.security.config.annotation.SecurityBuilder; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; @@ -32,7 +34,7 @@ public final class PreAuthConfigurer authorizationManager; + private @Nullable AuthorizationManager authorizationManager; - public AuthorizedCall(CallMatcher matcher) { + AuthorizedCall(CallMatcher matcher) { this.matcher = matcher; } @@ -169,7 +170,9 @@ public RequestMapperAuthorizationManager(List authorizedCalls, AuthorizationResult result = new AuthorizationDecision(false); for (AuthorizedCall authorizedCall : this.authorizedCalls) { if (authorizedCall.matcher.matches(context)) { - result = authorizedCall.authorizationManager.authorize(authentication, context); + result = Objects + .requireNonNull(authorizedCall.authorizationManager, "AuthorizationManager is required") + .authorize(authentication, context); break; } } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SecurityGrpcExceptionHandler.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SecurityGrpcExceptionHandler.java index b92b8f94..b701f592 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SecurityGrpcExceptionHandler.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SecurityGrpcExceptionHandler.java @@ -18,6 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.grpc.server.exception.GrpcExceptionHandler; import org.springframework.security.access.AccessDeniedException; @@ -31,7 +32,7 @@ public class SecurityGrpcExceptionHandler implements GrpcExceptionHandler { private static final Log logger = LogFactory.getLog(SecurityGrpcExceptionHandler.class); @Override - public StatusException handleException(Throwable exception) { + public @Nullable StatusException handleException(Throwable exception) { if (exception instanceof AuthenticationException) { if (logger.isDebugEnabled()) { logger.error("Failed to authenticate", exception); diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SslContextPreAuthenticationExtractor.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SslContextPreAuthenticationExtractor.java index 66a4fedd..ed4f7e67 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SslContextPreAuthenticationExtractor.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/SslContextPreAuthenticationExtractor.java @@ -20,15 +20,19 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.net.ssl.SSLSession; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; +import org.springframework.util.Assert; import io.grpc.Attributes; import io.grpc.Grpc; @@ -37,6 +41,8 @@ public class SslContextPreAuthenticationExtractor implements GrpcAuthenticationExtractor { + private static final Log logger = LogFactory.getLog(SslContextPreAuthenticationExtractor.class); + private X509PrincipalExtractor principalExtractor; public SslContextPreAuthenticationExtractor() { @@ -48,18 +54,22 @@ public SslContextPreAuthenticationExtractor(X509PrincipalExtractor principalExtr } @Override - public Authentication extract(Metadata headers, Attributes attributes, MethodDescriptor method) { + public @Nullable Authentication extract(Metadata headers, Attributes attributes, MethodDescriptor method) { SSLSession session = attributes.get(Grpc.TRANSPORT_ATTR_SSL_SESSION); if (session != null) { + @Nullable X509Certificate[] certificates = initCertificates(session); if (certificates != null) { - return new PreAuthenticatedAuthenticationToken( - this.principalExtractor.extractPrincipal(certificates[0]), certificates[0]); + Assert.notEmpty(certificates, "Must contain at least 1 non-null certificate"); + X509Certificate certificate = Objects.requireNonNull(certificates[0], "Certificate must not be null"); + return new PreAuthenticatedAuthenticationToken(this.principalExtractor.extractPrincipal(certificate), + certificate); } } return null; } + @SuppressWarnings("NullAway") @Nullable private static X509Certificate[] initCertificates(SSLSession session) { Certificate[] certificates; @@ -67,6 +77,7 @@ private static X509Certificate[] initCertificates(SSLSession session) { certificates = session.getPeerCertificates(); } catch (Throwable ex) { + logger.trace("Failed to get peer cert from session", ex); return null; } diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/package-info.java new file mode 100644 index 00000000..5537ff4f --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/security/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * Security infrastructure for gRPC servers. + */ +@NullMarked +package org.springframework.grpc.server.security; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/DefaultGrpcServiceConfigurer.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/DefaultGrpcServiceConfigurer.java index a1b9e868..eab9559f 100644 --- a/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/DefaultGrpcServiceConfigurer.java +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/DefaultGrpcServiceConfigurer.java @@ -44,7 +44,7 @@ public class DefaultGrpcServiceConfigurer implements GrpcServiceConfigurer, Init private final ApplicationContext applicationContext; - private List globalInterceptors; + private List globalInterceptors = new ArrayList<>(); public DefaultGrpcServiceConfigurer(ApplicationContext applicationContext) { this.applicationContext = applicationContext; @@ -52,7 +52,7 @@ public DefaultGrpcServiceConfigurer(ApplicationContext applicationContext) { @Override public void afterPropertiesSet() { - this.globalInterceptors = findGlobalInterceptors(); + this.globalInterceptors.addAll(findGlobalInterceptors()); } @Override @@ -67,7 +67,7 @@ private List findGlobalInterceptors() { } private ServerServiceDefinition bindInterceptors(BindableService bindableService, - @Nullable GrpcServiceInfo serviceInfo, GrpcServerFactory serverFactory) { + @Nullable GrpcServiceInfo serviceInfo, @Nullable GrpcServerFactory serverFactory) { var serviceDef = bindableService.bindService(); // Add and filter global interceptors first diff --git a/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/package-info.java b/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/package-info.java new file mode 100644 index 00000000..7f2eba68 --- /dev/null +++ b/spring-grpc-core/src/main/java/org/springframework/grpc/server/service/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025-present 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. + */ + +/** + * gRPC service discovery and configuration. + */ +@NullMarked +package org.springframework.grpc.server.service; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-grpc-docs/src/main/java/org/springframework/grpc/internal/ConfigurationPropertiesAsciidocGenerator.java b/spring-grpc-docs/src/main/java/org/springframework/grpc/internal/ConfigurationPropertiesAsciidocGenerator.java index 4d1c1d8c..c99b8193 100644 --- a/spring-grpc-docs/src/main/java/org/springframework/grpc/internal/ConfigurationPropertiesAsciidocGenerator.java +++ b/spring-grpc-docs/src/main/java/org/springframework/grpc/internal/ConfigurationPropertiesAsciidocGenerator.java @@ -42,6 +42,7 @@ * @author Marcin Grzejszczak * @author Chris Bono */ +@SuppressWarnings("NullAway") public class ConfigurationPropertiesAsciidocGenerator { public static void main(String... args) { diff --git a/spring-grpc-server-spring-boot-autoconfigure/src/main/java/org/springframework/boot/grpc/server/autoconfigure/GrpcServerFactoryConfigurations.java b/spring-grpc-server-spring-boot-autoconfigure/src/main/java/org/springframework/boot/grpc/server/autoconfigure/GrpcServerFactoryConfigurations.java index f32b7d7b..22c8f960 100644 --- a/spring-grpc-server-spring-boot-autoconfigure/src/main/java/org/springframework/boot/grpc/server/autoconfigure/GrpcServerFactoryConfigurations.java +++ b/spring-grpc-server-spring-boot-autoconfigure/src/main/java/org/springframework/boot/grpc/server/autoconfigure/GrpcServerFactoryConfigurations.java @@ -17,6 +17,7 @@ package org.springframework.boot.grpc.server.autoconfigure; import java.util.List; +import java.util.Objects; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; @@ -169,8 +170,9 @@ InProcessGrpcServerFactory inProcessGrpcServerFactory(GrpcServerProperties prope var mapper = new InProcessServerFactoryPropertyMapper(properties); List> builderCustomizers = List .of(mapper::customizeServerBuilder, serverBuilderCustomizers::customize); - InProcessGrpcServerFactory factory = new InProcessGrpcServerFactory(properties.getInprocess().getName(), - builderCustomizers); + String inProcessName = Objects.requireNonNull(properties.getInprocess().getName(), + "The InProcess name property must be set"); + InProcessGrpcServerFactory factory = new InProcessGrpcServerFactory(inProcessName, builderCustomizers); factory.setInterceptorFilter(interceptorFilter.getIfAvailable()); factory.setServiceFilter(serviceFilter.getIfAvailable()); applyServerFactoryCustomizers(customizers, factory);