001package net.gdface.thrift.exception;
002
003import java.io.PrintStream;
004import java.io.PrintWriter;
005import java.io.StringWriter;
006
007import com.facebook.swift.codec.ThriftField;
008import com.facebook.swift.codec.ThriftStruct;
009import com.google.common.base.Preconditions;
010
011/**
012 * Runtime exception wrap class<br>
013 * all {@link RuntimeException} threw from service was wrapped to the object<br>
014 * @author guyadong
015 *
016 */
017@ThriftStruct
018public final class ServiceRuntimeException extends RuntimeException{
019    private static final long serialVersionUID = 1L;
020    private int type = 0;
021    private String message;
022    private String causeClass;
023    /** Lazy Initialization field */
024    private volatile String serviceStackTraceMessage;
025    private String causeFields;
026
027    public ServiceRuntimeException(String message) {
028                this(message,null);
029        }
030        public ServiceRuntimeException() {
031                this(null,null);        
032        }
033        public ServiceRuntimeException(Throwable cause) {
034        this(null,cause);
035    }
036    public ServiceRuntimeException(String message, Throwable cause) {
037        super(message,stripRuntimeShell(cause));
038        if(null != message){
039            this.message = message;
040        }else if(null != getCause()){
041            this.message = getCause().getMessage();
042            if(null == this.message){
043                this.message = getCause().toString();
044            }
045        }
046        this.causeClass = null == getCause()? null : getCause().getClass().getName();
047        this.causeFields = null == getCause()? null : getCause().toString();
048        if(getCause() instanceof ServiceRuntimeException){
049            this.causeFields = ((ServiceRuntimeException)getCause()).jsonOfDeclaredFields();    
050        }
051    }
052    /**
053     * @param type exception type
054     * @param cause
055     */
056    public ServiceRuntimeException(int type,Throwable cause) {
057        this(cause);
058        this.type = type;
059    }
060    /** return a JSON string of declared fields,subclass override it */
061    protected String jsonOfDeclaredFields(){
062        return "";
063    }
064    /**
065     * return cause wrapped by {@link RuntimeException}<br>
066     * @param e
067     * @return
068     */
069    private static final Throwable stripRuntimeShell(Throwable e){
070        if(null != e && null !=e.getCause() && e.getClass() == RuntimeException.class){
071            return stripRuntimeShell(e.getCause());
072        }
073        return e;
074    }
075    /**
076     * save error message to {@link #serviceStackTraceMessage} by calling {@link #printStackTrace(PrintWriter)} 
077     * @param cause
078     * @see #printStackTrace(PrintWriter)
079     */
080    private void fillStackTraceMessage(Throwable cause) {
081        if (null != cause) {
082            StringWriter write = new StringWriter(256);
083            PrintWriter pw = new PrintWriter(write);
084            cause.printStackTrace(pw);
085            serviceStackTraceMessage = write.toString();
086        }
087    }
088    /**
089     * print stack trace message from service to {@link System#err}
090     * @see #printStackTrace()
091     */
092    public void printServiceStackTrace() {
093        printServiceStackTrace(System.err);
094    }
095
096    /**
097     * @param s
098     * @see #printServiceStackTrace()
099     * @see #printStackTrace(PrintStream)
100     * @throws NullPointerException s is {@code null}
101     */
102    public void printServiceStackTrace(PrintStream s) {
103        synchronized (Preconditions.checkNotNull(s)) {
104            s.println(serviceStackTraceMessage);
105        }
106    }
107
108    /**
109     * @param s
110     * @see #printServiceStackTrace()
111     * @see #printStackTrace(PrintWriter)
112     * @throws NullPointerException s is {@code null}
113     */
114    public void printServiceStackTrace(PrintWriter s) {
115        synchronized (Preconditions.checkNotNull(s)) {
116            s.println(serviceStackTraceMessage);
117        }
118    }
119    /** return error message from service */
120    @Override
121    @ThriftField(1)
122    public String getMessage() {
123        return message;
124    }
125    @ThriftField
126    public void setMessage(String message) {
127        this.message = message;
128    }
129    /** return cause exception class name */
130    @ThriftField(2)
131    public String getCauseClass() {
132        return causeClass;
133    }
134    @ThriftField
135    public void setCauseClass(String causeClass) {
136        this.causeClass = causeClass;
137    }
138    /** return stack trace message from service */
139    @ThriftField(3)
140    public String getServiceStackTraceMessage() {
141        // Double-checked locking
142        if(null == serviceStackTraceMessage){
143            synchronized(this){
144                if(null == serviceStackTraceMessage){
145                    fillStackTraceMessage(null == getCause()? this : getCause());
146                }
147            }
148        }
149        return serviceStackTraceMessage;
150    }
151    @ThriftField
152    public void setServiceStackTraceMessage(String serviceStackTraceMessage) {
153        this.serviceStackTraceMessage = serviceStackTraceMessage;
154    }
155    /** 
156     * return JSON string of declared field values if cause is subclass of this class 
157     * and override {@code jsonOfDeclaredFields} method, otherwise return empty string
158     * @see {@link #jsonOfDeclaredFields()}
159     */
160    @ThriftField(4)
161    public String getCauseFields() {
162        return causeFields;
163    }
164    @ThriftField
165    public void setCauseFields(String causeFields) {
166        this.causeFields = causeFields;
167    }
168    /** return exception type */
169    @ThriftField(5)
170    public int getType() {
171        return type;
172    }
173    @ThriftField
174    public void setType(int type) {
175        this.type = type;
176    }
177}