blob: 4bb533e584b65f1005ffa0a2c0fda987d9ec7563 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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.
*/
package org.apache.hugegraph.api.filter;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Singleton;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.config.ServerOptions;
import org.glassfish.hk2.api.MultiException;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.api.API;
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.exception.HugeGremlinException;
import org.apache.hugegraph.exception.NotFoundException;
import com.codahale.metrics.annotation.Timed;
import com.google.common.collect.ImmutableMap;
public class ExceptionFilter {
private static final int BAD_REQUEST_ERROR =
Response.Status.BAD_REQUEST.getStatusCode();
private static final int NOT_FOUND_ERROR =
Response.Status.NOT_FOUND.getStatusCode();
private static final int INTERNAL_SERVER_ERROR =
Response.Status.INTERNAL_SERVER_ERROR.getStatusCode();
public static class TracedExceptionMapper extends API {
private static boolean forcedTrace = false;
@Context
private jakarta.inject.Provider<HugeConfig> configProvider;
protected boolean trace() {
if (forcedTrace) {
return true;
}
HugeConfig config = this.configProvider.get();
if (config == null) {
return false;
}
return config.get(ServerOptions.ALLOW_TRACE);
}
}
@Path("exception/trace")
@Singleton
@Tag(name = "TracedExceptionAPI")
public static class TracedExceptionAPI extends API {
@GET
@Timed
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed({"admin"})
public Object get() {
return ImmutableMap.of("trace", TracedExceptionMapper.forcedTrace);
}
@PUT
@Timed
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON_WITH_CHARSET)
@RolesAllowed({"admin"})
public Object trace(boolean trace) {
TracedExceptionMapper.forcedTrace = trace;
return ImmutableMap.of("trace", TracedExceptionMapper.forcedTrace);
}
}
@Provider
public static class HugeExceptionMapper
extends TracedExceptionMapper
implements ExceptionMapper<HugeException> {
@Override
public Response toResponse(HugeException exception) {
return Response.status(BAD_REQUEST_ERROR)
.type(MediaType.APPLICATION_JSON)
.entity(formatException(exception, this.trace()))
.build();
}
}
@Provider
public static class IllegalArgumentExceptionMapper
extends TracedExceptionMapper
implements ExceptionMapper<IllegalArgumentException> {
@Override
public Response toResponse(IllegalArgumentException exception) {
return Response.status(BAD_REQUEST_ERROR)
.type(MediaType.APPLICATION_JSON)
.entity(formatException(exception, this.trace()))
.build();
}
}
@Provider
public static class NotFoundExceptionMapper
extends TracedExceptionMapper
implements ExceptionMapper<NotFoundException> {
@Override
public Response toResponse(NotFoundException exception) {
return Response.status(NOT_FOUND_ERROR)
.type(MediaType.APPLICATION_JSON)
.entity(formatException(exception, this.trace()))
.build();
}
}
@Provider
public static class NoSuchElementExceptionMapper
extends TracedExceptionMapper
implements ExceptionMapper<NoSuchElementException> {
@Override
public Response toResponse(NoSuchElementException exception) {
return Response.status(NOT_FOUND_ERROR)
.type(MediaType.APPLICATION_JSON)
.entity(formatException(exception, this.trace()))
.build();
}
}
@Provider
public static class WebApplicationExceptionMapper
extends TracedExceptionMapper
implements ExceptionMapper<WebApplicationException> {
@Override
public Response toResponse(WebApplicationException exception) {
Response response = exception.getResponse();
if (response.hasEntity()) {
return response;
}
MultivaluedMap<String, Object> headers = response.getHeaders();
boolean trace = this.trace(response.getStatus());
response = Response.status(response.getStatus())
.type(MediaType.APPLICATION_JSON)
.entity(formatException(exception, trace))
.build();
response.getHeaders().putAll(headers);
return response;
}
private boolean trace(int status) {
return this.trace() && status == INTERNAL_SERVER_ERROR;
}
}
@Provider
public static class HugeGremlinExceptionMapper
extends TracedExceptionMapper
implements ExceptionMapper<HugeGremlinException> {
@Override
public Response toResponse(HugeGremlinException exception) {
return Response.status(exception.statusCode())
.type(MediaType.APPLICATION_JSON)
.entity(formatGremlinException(exception,
this.trace()))
.build();
}
}
@Provider
public static class AssertionErrorMapper extends TracedExceptionMapper
implements ExceptionMapper<AssertionError> {
@Override
public Response toResponse(AssertionError exception) {
return Response.status(INTERNAL_SERVER_ERROR)
.type(MediaType.APPLICATION_JSON)
.entity(formatException(exception, true))
.build();
}
}
@Provider
public static class UnknownExceptionMapper extends TracedExceptionMapper
implements ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable exception) {
if (exception instanceof MultiException &&
((MultiException) exception).getErrors().size() == 1) {
exception = ((MultiException) exception).getErrors().get(0);
}
return Response.status(INTERNAL_SERVER_ERROR)
.type(MediaType.APPLICATION_JSON)
.entity(formatException(exception, this.trace()))
.build();
}
}
public static String formatException(Throwable exception, boolean trace) {
String clazz = exception.getClass().toString();
String message = exception.getMessage() != null ?
exception.getMessage() : "";
String cause = exception.getCause() != null ?
exception.getCause().toString() : "";
JsonObjectBuilder json = Json.createObjectBuilder()
.add("exception", clazz)
.add("message", message)
.add("cause", cause);
if (trace) {
JsonArrayBuilder traces = Json.createArrayBuilder();
for (StackTraceElement i : exception.getStackTrace()) {
traces.add(i.toString());
}
json.add("trace", traces);
}
return json.build().toString();
}
public static String formatGremlinException(HugeGremlinException exception,
boolean trace) {
Map<String, Object> map = exception.response();
String message = (String) map.get("message");
String exClassName = (String) map.get("Exception-Class");
@SuppressWarnings("unchecked")
List<String> exceptions = (List<String>) map.get("exceptions");
String stackTrace = (String) map.get("stackTrace");
message = message != null ? message : "";
exClassName = exClassName != null ? exClassName : "";
String cause = exceptions != null ? exceptions.toString() : "";
JsonObjectBuilder json = Json.createObjectBuilder()
.add("exception", exClassName)
.add("message", message)
.add("cause", cause);
if (trace && stackTrace != null) {
JsonArrayBuilder traces = Json.createArrayBuilder();
for (String part : StringUtils.split(stackTrace, '\n')) {
traces.add(part);
}
json.add("trace", traces);
}
return json.build().toString();
}
}