/**
* This work was created by participants in the DataONE project, and is
* jointly copyrighted by participating institutions in DataONE. For
* more information on DataONE, see our web site at http://dataone.org.
*
* Copyright ${year}
*
* 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 org.dataone.service.exceptions;
import java.io.StringWriter;
import java.util.Set;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* A BaseException is the root of all DataONE service class exception messages.
* Each exception contains a code, which typically corresponds to a high-level
* HTTP error code, a detail_code, which classifies the exception to a more
* constrained subtype of error, and a description of the error. It can also
* contain additional trace information used to debug the problem, which is
* stored as a set of name-value pairs.
*
* @author Matthew Jones
*/
public class BaseException extends Exception {
static Logger logger = Logger.getLogger(BaseException.class.getName());
/** The major error code associated with this exception. */
private int code;
/** The detailed error subcode associated with this exception. */
private String detail_code;
/** The optional PID associated with this exception. */
private String pidString;
/** The optional nodeId associated with this exception */
private String nodeIdString;
/** Additional trace-level debugging information, as name-value pairs. */
private TreeMap trace_information;
public final static int FMT_XML = 0;
public final static int FMT_JSON = 1;
public final static int FMT_HTML = 2;
private DocumentBuilder documentBuilder = null;
private Transformer transformer = null;
private StringWriter strWtr = new StringWriter();
/**
* Construct a BaseException with the given code, detail code, and description.
*
* @param code the code used to classify the exception
* @param detail_code the detailed code for this exception
* @param description the description of this exception
*/
protected BaseException(int code, String detail_code, String description) {
super(description);
this.trace_information = new TreeMap();
this.code = code;
this.detail_code = detail_code;
try {
documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //xml, html, text
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
} catch (ParserConfigurationException ex) {
logger.error(ex.getMessage());
} catch (TransformerConfigurationException ex) {
logger.error(ex.getMessage());
}
}
/**
* Construct a BaseException with the given code, detail code, pid, nodeId,
* description, and trace_information.
*
* @param code the code used to classify the exception
* @param detail_code the detailed code for this exception
* @param pidString: the identifier associated with the exception, and usually with the request
* @param nodeIdString the node identifier associated with the exception
* @param description the description of this exception
* @param trace_information containing a Map of key/value pairs
*/
protected BaseException(int code, String detail_code, String pidString,
String nodeIdString, String description,
TreeMap trace_information) {
this(code, detail_code, description);
this.setPid(pidString);
if (nodeIdString != null) {
this.setNodeId(nodeIdString);
}
if (trace_information == null)
this.trace_information = new TreeMap();
else
this.trace_information = trace_information;
}
/**
* Construct a BaseException with the given code, detail code, description,
* and trace_information.
*
* @param code the code used to classify the exception
* @param detail_code the detailed code for this exception
* @param pidString: the identifier associated with the exception, and usually with the request
* @param description the description of this exception
* @param trace_information containing a Map of key/value pairs
*/
protected BaseException(int code, String detail_code, String pidString, String description,
TreeMap trace_information) {
this(code, detail_code, description);
this.setPid(pidString);
if (trace_information == null)
this.trace_information = new TreeMap();
else
this.trace_information = trace_information;
}
/**
* Construct a BaseException with the given code, detail code, description,
* and trace_information.
*
* @param code the code used to classify the exception
* @param detail_code the detailed code for this exception
* @param description the description of this exception
* @param trace_information containing a Map of key/value pairs
*/
protected BaseException(int code, String detail_code, String description,
TreeMap trace_information) {
this(code, detail_code, description);
this.trace_information = trace_information;
}
/**
* @param code the code to set
*/
protected void setCode(int code) {
this.code = code;
}
/**
* @return the code
*/
public int getCode() {
return code;
}
/**
* @param detail_code the detail_code to set
*/
public void setDetail_code(String detail_code) {
this.detail_code = detail_code;
}
/**
* @return the detail_code
*/
public String getDetail_code() {
return detail_code;
}
public String getPid() {
return pidString;
}
public void setPid(String p) {
this.pidString = p;
}
/**
* Set the node Id string
* @param nodeIdString
*/
public void setNodeId(String nodeIdString) {
this.nodeIdString = nodeIdString;
}
/**
* Get the node Id string
* @return nodeIdString - the identifier of the node that raised the exception
*/
public String getNodeId() {
return this.nodeIdString;
}
/**
* @return the description
*/
public String getDescription() {
return getMessage();
}
/**
* Add new detailed trace information, storing it the value under a known key.
* @param key the key to index the trace value
* @param value that trace value to be stored
*/
public void addTraceDetail(String key, String value) {
trace_information.put(key, value);
}
/**
* Get the value of one of the trace detail parametersm retrieved by its
* key value.
* @param key the key of the trace information to be retrieved
* @return the String key value associated with the key
*/
public String getTraceDetail(String key) {
return trace_information.get(key);
}
/**
* Return a Set representation of all of the keys for the trace values in
* this exception.
* @return Set representation of all of the keys for the trace values
*/
public Set getTraceKeySet() {
return trace_information.keySet();
}
/**
* Serialize the format in XML, JSON, or HTML format. If format is unknown,
* then return in XML format.
* @param format an integer code indicating the format
*/
public String serialize(int format) {
switch(format) {
case FMT_XML:
return serializeXML();
case FMT_JSON:
return serializeJSON();
case FMT_HTML:
return serializeHTML();
default:
return serializeXML();
}
}
/** Serialize the exception in XML format. */
private String serializeXML() {
/* StringBuffer sb = new StringBuffer();
sb.append("\n");
sb.append("\n");
sb.append(" ").append(getDescription()).append("\n");
sb.append(" \n");
for (String key : this.getTraceKeySet()) {
sb.append(" ");
sb.append(trace_information.get(key)).append("\n");
}
sb.append(" \n");
sb.append("\n");
return sb.toString(); */
Document dom = documentBuilder.newDocument();
// create the root node of the dom
Element errorNode = dom.createElement("error");
errorNode.setAttribute("name", getName());
errorNode.setAttribute("detailCode", getDetail_code());
errorNode.setAttribute("errorCode", Integer.toString(getCode()));
if (getPid() != null)
errorNode.setAttribute("pid", getPid());
if (getNodeId() != null)
errorNode.setAttribute("nodeId", getNodeId());
Element description = dom.createElement("description");
description.setTextContent(getDescription());
dom.appendChild(errorNode);
errorNode.appendChild(description);
if (!trace_information.isEmpty()) {
Element traceInformation = dom.createElement("traceInformation");
for (String key : this.getTraceKeySet()) {
Element value = dom.createElement("value");
value.setAttribute("key", key);
value.setTextContent(trace_information.get(key));
traceInformation.appendChild(value);
}
errorNode.appendChild(traceInformation);
}
try {
return this.domToString(dom);
} catch (Exception ex) {
logger.error(ex.getMessage());
return ex.getMessage();
}
}
private String getName() {
String c = this.getClass().getName();
return c.substring(c.lastIndexOf(".")+1);
}
/** Serialize the exception in JSON format.
* TODO: Implement JSON serialization
*/
private String serializeJSON() {
StringBuffer sb = new StringBuffer();
sb.append("{'errorCode': ").append(getCode()).append(",\n");
sb.append(" 'detailCode': ").append(getDetail_code()).append(",\n");
if (getPid() != null)
sb.append(" 'pid': '").append(getPid()).append("',\n");
sb.append(" 'description': '").append(getDescription()).append("',\n");
sb.append(" 'traceInformation': {\n");
for (String key : this.getTraceKeySet()) {
sb.append(" '").append(key).append("': '");
sb.append(trace_information.get(key)).append("',\n");
}
sb.append(" }\n");
sb.append("}\n");
return sb.toString();
}
/** Serialize the exception in HTML format.
* TODO: Implement HTML serialization
*/
private String serializeHTML() {
StringBuffer sb = new StringBuffer();
sb.append("\n\n");
sb.append(" \n");
sb.append("
\n");
sb.append(" - Code
- ").append(getCode()).append("
\n");
sb.append(" - Detail Code
- ").append(getDetail_code()).append("
\n");
sb.append(" - PID
- ").append(getPid()).append("
\n");
sb.append("
\n");
sb.append("
\n");
sb.append(" ").append(getDescription()).append("
\n");
sb.append(" \n");
for (String key : this.getTraceKeySet()) {
sb.append("
").append(key).append("\n");
sb.append(" ").append(trace_information.get(key)).append("\n");
}
sb.append(" \n");
sb.append("\n\n");
return sb.toString();
}
private String domToString(Document document) throws Exception {
String result = null;
if (document != null) {
StreamResult strResult = new StreamResult(strWtr);
transformer.transform(new DOMSource(document.getDocumentElement()), strResult);
result = strResult.getWriter().toString();
}
return result;
}
}