001package net.gdface.thrift.exception;
002
003import java.io.PrintWriter;
004import java.io.StringWriter;
005
006import com.facebook.swift.codec.ThriftField;
007import com.facebook.swift.codec.ThriftStruct;
008
009/**
010 * service exception abstract class<br>
011 * @author guyadong
012 *
013 */
014@ThriftStruct
015public abstract class BaseServiceException extends Exception{
016    private static final long serialVersionUID = 1L;
017    private String message;
018    private String causeClass;
019    /** Lazy Initialization field */
020    private volatile String serviceStackTraceMessage;
021    private String causeFields;
022
023    public BaseServiceException() {
024        this(null,null);
025    }
026    public BaseServiceException(String message) {
027        this(message,null);
028    }
029    public BaseServiceException(Throwable cause) {
030        this(null,cause);
031    }
032    public BaseServiceException(String message, Throwable cause) {
033        super(message,stripRuntimeShell(cause));
034        if(null != message){
035            this.message = message;
036        }else if(null != getCause()){
037            this.message = getCause().getMessage();
038            if(null == this.message){
039                this.message = getCause().toString();
040            }
041        }
042        this.causeClass = null == getCause()? null : getCause().getClass().getName();
043        this.causeFields = null == getCause()? null : getCause().toString();
044        if(getCause() instanceof BaseServiceException){
045            this.causeFields = ((BaseServiceException)getCause()).jsonOfDeclaredFields();    
046        }
047    }
048    /** return a JSON string of declared fields,subclass override it */
049    protected String jsonOfDeclaredFields(){
050        return "";
051    }
052    /**
053     * return cause wrapped by {@link RuntimeException}<br>
054     * @param e
055     * @return
056     */
057    private static final Throwable stripRuntimeShell(Throwable e){
058        if(null != e && null !=e.getCause() && e.getClass() == RuntimeException.class){
059            return stripRuntimeShell(e.getCause());
060        }
061        return e;
062    }
063    /**
064     * save error message to {@link #serviceStackTraceMessage} by calling {@link #printStackTrace(PrintWriter)} 
065     * @param cause
066     * @see #printStackTrace(PrintWriter)
067     */
068    private void fillStackTraceMessage(Throwable cause) {
069        if (null != cause) {
070            StringWriter write = new StringWriter(256);
071            PrintWriter pw = new PrintWriter(write);
072            cause.printStackTrace(pw);
073            serviceStackTraceMessage = write.toString();
074        }
075    }
076
077    /** return error message from service */
078    @Override
079    @ThriftField(1)
080    public String getMessage() {
081        return message;
082    }
083    @ThriftField
084    public void setMessage(String message) {
085        this.message = message;
086    }
087    /** return cause exception class name */
088    @ThriftField(2)
089    public String getCauseClass() {
090        return causeClass;
091    }
092    @ThriftField
093    public void setCauseClass(String causeClass) {
094        this.causeClass = causeClass;
095    }
096    /** return stack trace message from service */
097    @ThriftField(3)
098    public String getServiceStackTraceMessage() {
099        // Double-checked locking
100        if(null == serviceStackTraceMessage){
101            synchronized(this){
102                if(null == serviceStackTraceMessage){
103                    fillStackTraceMessage(null == getCause()? this : getCause());
104                }
105            }
106        }
107        return serviceStackTraceMessage;
108    }
109    @ThriftField
110    public void setServiceStackTraceMessage(String serviceStackTraceMessage) {
111        this.serviceStackTraceMessage = serviceStackTraceMessage;
112    }
113    /** 
114     * return JSON string of declared field values if cause is subclass of this class 
115     * and override {@code jsonOfDeclaredFields} method, otherwise return empty string
116     * @see {@link #jsonOfDeclaredFields()}
117     */
118    @ThriftField(4)
119    public String getCauseFields() {
120        return causeFields;
121    }
122    @ThriftField
123    public void setCauseFields(String causeFields) {
124        this.causeFields = causeFields;
125    }
126}