/**
* Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG
* 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.
*/
package com.sixt.service.framework.rpc;
import com.google.gson.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* Any error state triggered by interaction with a remote service will result
* in an instance of this class being thrown.
*/
public class RpcCallException extends Exception {
private static final Logger logger = LoggerFactory.getLogger(RpcCallException.class);
private static final String CATEGORY = "category";
private static final String MESSAGE = "message";
private static final String SOURCE = "source";
private static final String CODE = "code";
private static final String DATA = "data";
private static final String RETRIABLE = "retriable";
private static final String DETAIL = "detail";
private static Map<Integer, Category> cache = new HashMap<>();
public enum Category {
BadRequest(400, false), //invalid params or malformed
Unauthorized(401, false), //not logged in
InsufficientPermissions(403, false), //not enough perms
ResourceNotFound(404, false),
InternalServerError(500, true), //unexpected exception
BackendError(501, false), //business logic failure
RequestTimedOut(504, true);
private int httpStatus;
private boolean retriable; //default, can be overridden in the exception instance
Category(int status, boolean retriable) {
this.httpStatus = status;
this.retriable = retriable;
addToCache(status, this);
}
private void addToCache(int status, Category category) {
cache.put(status, category);
}
public int getHttpStatus() {
return httpStatus;
}
public boolean isRetriable() {
return retriable;
}
public static Category fromStatus(int status) {
return cache.get(status);
}
}
private String source;
private Category category;
private String errorCode;
private String message;
private String data;
private boolean retriable;
public RpcCallException(Category category, String message) {
super(); //builds stacktrace
this.category = category;
this.retriable = category.retriable;
this.message = message;
}
public RpcCallException withSource(String source) {
this.source = source;
return this;
}
public RpcCallException withErrorCode(String errorCode) {
this.errorCode = errorCode;
return this;
}
public RpcCallException withData(String data) {
this.data = data;
return this;
}
public RpcCallException withRetriable(boolean retriable) {
this.retriable = retriable;
return this;
}
public String getSource() {
return source;
}
public Category getCategory() {
return category;
}
public String getErrorCode() {
return errorCode;
}
@Override
public String getMessage() {
return message;
}
public String getData() {
return data;
}
public boolean isRetriable() {
return retriable;
}
@Override
public String toString() {
return toJson().toString();
}
public JsonObject toJson() {
JsonObject obj = new JsonObject();
obj.addProperty(CATEGORY, category.getHttpStatus());
obj.addProperty(MESSAGE, message);
obj.addProperty(SOURCE, source);
obj.addProperty(CODE, errorCode);
obj.addProperty(DATA, data);
obj.addProperty(RETRIABLE, retriable);
return obj;
}
public static RpcCallException fromJson(String json) {
try {
JsonParser parser = new JsonParser();
JsonElement rawObject = parser.parse(json);
if (rawObject instanceof JsonObject) {
JsonObject object = (JsonObject) rawObject;
Category category = getCategory(object);
String message = getMessage(object);
RpcCallException retval = new RpcCallException(category, message);
JsonElement element = object.get(SOURCE);
if (element != null && !(element instanceof JsonNull)) {
retval.withSource(element.getAsString());
}
element = object.get(CODE);
if (element != null && !(element instanceof JsonNull)) {
retval.withErrorCode(element.getAsString());
}
element = object.get(DATA);
if (element != null && !(element instanceof JsonNull)) {
retval.withData(element.getAsString());
}
element = object.get(RETRIABLE);
if (element != null && !(element instanceof JsonNull)) {
retval.withRetriable(element.getAsBoolean());
}
return retval;
} else if (rawObject instanceof JsonPrimitive) {
logger.warn("Expected an RpcCallException json object, but received: {}", rawObject.toString());
}
} catch (Exception ex) {
logger.warn("Caught exception parsing RpcCallException: " + json, ex);
}
return null;
}
private static String getMessage(JsonObject object) {
String message = StringUtils.EMPTY;
if (object.has(MESSAGE)) {
message = object.get(MESSAGE).getAsString();
} else if (object.has(DETAIL)) {
message = object.get(DETAIL).getAsString();
}
return message;
}
private static Category getCategory(JsonObject object) {
// if no category can be found we use internal server error
Category category = Category.InternalServerError;
if (object.has(CATEGORY)) {
category = Category.fromStatus(object.get(CATEGORY).getAsInt());
} else if (object.has(CODE)) {
category = Category.fromStatus(object.get(CODE).getAsInt());
}
return category;
}
}