Skip to content

Commit

Permalink
Merge pull request #653 from i-Cell-Mobilsoft-Open-Source/feature/652…
Browse files Browse the repository at this point in the history
…-enhanced-grpc-server-exception-listener

Feature/652 enhanced grpc server exception listener
  • Loading branch information
rombow authored Apr 17, 2024
2 parents 64f85a1 + d9c0e5d commit 22fa57e
Show file tree
Hide file tree
Showing 15 changed files with 601 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*-
* #%L
* Coffee
* %%
* Copyright (C) 2020 - 2024 i-Cell Mobilsoft Zrt.
* %%
* 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
*
* http://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.
* #L%
*/
package hu.icellmobilsoft.coffee.grpc.api.metadata;

import io.grpc.Metadata;
import io.grpc.Metadata.Key;

/**
* Grpc Header metadata constants
*
* @author Imre Scheffer
* @since 2.7.0
*/
public interface IGrpcHeader {

/**
* Language Metadata header key
*/
Metadata.Key<String> HEADER_LANGUAGE = Key.of("X-LANGUAGE", Metadata.ASCII_STRING_MARSHALLER);

/**
* Logging, global transaction session id header key
*/
Metadata.Key<String> HEADER_SID = Metadata.Key.of("X-SID", Metadata.ASCII_STRING_MARSHALLER);

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import com.google.rpc.Status;

import hu.icellmobilsoft.coffee.se.logging.Logger;
import io.grpc.Metadata;

/**
* The ExceptionHandler class serves to handle exceptions in gRPC services using ExceptionMappers, similar to how they are used in JAX-RS
Expand Down Expand Up @@ -96,22 +97,24 @@ public static ExceptionHandler getInstance() {
*
* @param <E>
* Generic type of the exception
* @param requestHeaders
* Grpc request metadata
* @param t
* the exception to be handled
* @return the corresponding {@link StatusResponse}
*/
public <E extends Throwable> StatusResponse handle(E t) {
public <E extends Throwable> StatusResponse handle(Metadata requestHeaders, E t) {
if (t instanceof GrpcRuntimeExceptionWrapper && ((GrpcRuntimeExceptionWrapper) t).getWrapped() != null) {
return handleStatus(((GrpcRuntimeExceptionWrapper) t).getWrapped());
return handleStatus(requestHeaders, ((GrpcRuntimeExceptionWrapper) t).getWrapped());
}
return handleStatus(t);
return handleStatus(requestHeaders, t);
}

private <E extends Throwable> StatusResponse handleStatus(E t) {
private <E extends Throwable> StatusResponse handleStatus(Metadata requestHeaders, E t) {
try {
List<Bean<?>> mapperBeans = getExceptionMapperBeans(t.getClass());
for (Bean<?> exceptionMapperBean : mapperBeans) {
Status status = handleByBean(t, exceptionMapperBean);
Status status = handleByBean(requestHeaders, t, exceptionMapperBean);
// ha nem null visszaadjuk, ha null jön priority szerint a következő, esetleg az exception super class-ára írt
if (status != null) {
return StatusResponse.of(status, t);
Expand All @@ -121,17 +124,18 @@ private <E extends Throwable> StatusResponse handleStatus(E t) {
LOG.error("Error occurred in ExceptionHandler - " + e.getMessage(), e);
return buildInternalErrorStatus(t, e.getMessage());
}
return buildInternalErrorStatus(t, "Could not find ExceptionMapper");
String reason = MessageFormat.format("Could not find " + ExceptionMapper.class.getName() + " CDI implementation for [{0}]", t.getClass());
return buildInternalErrorStatus(t, reason);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private <E extends Throwable> Status handleByBean(E t, Bean<?> exceptionMapperBean) {
private <E extends Throwable> Status handleByBean(Metadata requestHeaders, E t, Bean<?> exceptionMapperBean) {
Instance<ExceptionMapper> instance = (Instance<ExceptionMapper>) CDI.current().select(exceptionMapperBean.getBeanClass());
if (instance.isResolvable()) {
ExceptionMapper exceptionMapper = null;
try {
exceptionMapper = instance.get();
Status status = exceptionMapper.toStatus(t);
Status status = exceptionMapper.toStatus(requestHeaders, t);
if (status != null) {
return status;
}
Expand Down Expand Up @@ -179,11 +183,13 @@ private StatusResponse buildInternalErrorStatus(Throwable throwableNotHandled, S
// ha exception mapper elszáll, akkor internal server error.
Status.Builder statusBuilder = Status.newBuilder();

// ha nincs ExceptionHandler:
// 1. INTERNAL status kod
statusBuilder.setCode(Code.INTERNAL.getNumber());
statusBuilder.setMessage("Could not handle error - " + throwableNotHandled.getMessage());
statusBuilder.addDetails(Any.pack(ErrorInfo.newBuilder() //
.setReason(reason) //
.setDomain(throwableNotHandled.getClass().toString()).build()));
// 2. valaszolunk eredeti hibaval
statusBuilder.setMessage(throwableNotHandled.getMessage());
// 3. ErrorInfo-ba pakoljuk a reszleteket
statusBuilder.addDetails(Any.pack(ErrorInfo.newBuilder().setReason(reason).setDomain(throwableNotHandled.getClass().getName()).build()));
return StatusResponse.of(statusBuilder.build(), throwableNotHandled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import com.google.rpc.Status;

import io.grpc.Metadata;

/**
* ExceptionMapper is an interface for mapping an exception of type E to a gRPC {@link com.google.rpc.Status}.
* <p>
Expand All @@ -29,17 +31,20 @@
* @param <E>
* The type of exception to be mapped to a gRPC Status
* @author mark.petrenyi
* @author Imre Scheffer
* @since 2.1.0
*/
public interface ExceptionMapper<E extends Throwable> {

/**
* Maps an exception of type E to a gRPC Status.
*
* @param requestHeaders
* Incoming Grpc request headers
* @param e
* The exception to be mapped
* @return The gRPC Status resulting from the mapping
*/
Status toStatus(E e);
Status toStatus(Metadata requestHeaders, E e);

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ public class StatusResponse {
private final Metadata metadata;

private StatusResponse(com.google.rpc.Status statusProto) {
status = Status.fromCodeValue(statusProto.getCode());
status = Status.fromCodeValue(statusProto.getCode()).withDescription(statusProto.getMessage());
metadata = toMetadata(statusProto);
}

private StatusResponse(com.google.rpc.Status statusProto, Throwable cause) {
status = Status.fromCodeValue(statusProto.getCode()).withCause(cause);
status = Status.fromCodeValue(statusProto.getCode()).withDescription(statusProto.getMessage()).withCause(cause);
metadata = toMetadata(statusProto);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public void onMessage(ReqT message) {
try {
super.onMessage(message);
} catch (Throwable e) {
StatusResponse status = ExceptionHandler.getInstance().handle(e);
StatusResponse status = ExceptionHandler.getInstance().handle(headers, e);
serverCall.close(status.getStatus(), status.getMetadata());
}
}
Expand All @@ -92,7 +92,7 @@ public void onHalfClose() {
try {
super.onHalfClose();
} catch (Throwable e) {
StatusResponse status = ExceptionHandler.getInstance().handle(e);
StatusResponse status = ExceptionHandler.getInstance().handle(headers, e);
serverCall.close(status.getStatus(), status.getMetadata());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;

import hu.icellmobilsoft.coffee.dto.common.LogConstants;
import hu.icellmobilsoft.coffee.grpc.api.metadata.IGrpcHeader;
import hu.icellmobilsoft.coffee.grpc.server.log.GrpcLogging;
import hu.icellmobilsoft.coffee.rest.log.annotation.LogSpecifier;
import hu.icellmobilsoft.coffee.rest.log.annotation.LogSpecifiers;
Expand Down Expand Up @@ -72,7 +72,7 @@ public ServerRequestInterceptor() {
@Override
public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata headers, ServerCallHandler<ReqT, RespT> next) {

String extSessionIdHeader = headers.get(Metadata.Key.of(LogConstants.LOG_SESSION_ID, Metadata.ASCII_STRING_MARSHALLER));
String extSessionIdHeader = headers.get(IGrpcHeader.HEADER_SID);
String extSessionId = StringUtils.isNotBlank(extSessionIdHeader) ? extSessionIdHeader : RandomUtil.generateId();
Context context = Context.current().withValue(GrpcLogging.CONTEXT_KEY_SESSIONID, extSessionId);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*-
* #%L
* Coffee
* %%
* Copyright (C) 2020 - 2024 i-Cell Mobilsoft Zrt.
* %%
* 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
*
* http://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.
* #L%
*/
package hu.icellmobilsoft.coffee.grpc.server.mapper;

import java.io.Serializable;
import java.util.Locale;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import com.google.rpc.LocalizedMessage;
import com.google.rpc.LocalizedMessage.Builder;

/**
* Default implementation for translating exceptions to status.
*
* @author Imre Scheffer
* @since 2.7.0
*/
@ApplicationScoped
public class DefaultGrpcExceptionTranslator implements IGrpcExceptionTranslator {

/**
* Default Locale for Grpc message translation
*/
public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;

@Inject
private hu.icellmobilsoft.coffee.module.localization.LocalizedMessage localizedMessage;

/**
* Default constructor, constructs a new object.
*/
public DefaultGrpcExceptionTranslator() {
super();
}

@Override
public Builder toLocalizedMessage(Locale locale, Enum<?> faultType, Serializable... messageArguments) {
LocalizedMessage.Builder lmBuilder = LocalizedMessage.newBuilder();
if (faultType != null) {
Locale returnLocale = locale == null ? DEFAULT_LOCALE : locale;
// localizedMessage.message(faultType, messageArguments) jelenleg nem megfelelo,
// mert REST headerbol olvassa a nyelvesito kulcsot es itt most nincs request scope sem
String translatedMessage = localizedMessage.messageByLanguage(returnLocale.getLanguage(),
"{" + faultType.getClass().getName() + "." + faultType.name() + "}", messageArguments);
lmBuilder.setLocale(returnLocale.toString()).setMessage(translatedMessage);
}
return lmBuilder;
}
}
Loading

0 comments on commit 22fa57e

Please sign in to comment.