1   /*
2    * PRELYTIS.
3    * Copyright 2007, PRELYTIS S.A., and individual contributors
4    * as indicated by the @author tags. See the copyright.txt file in the
5    * distribution for a full listing of individual contributors.
6    *
7    * This is free software; you can redistribute it and/or modify it
8    * under the terms of the GNU Lesser General Public License as
9    * published by the Free Software Foundation; either version 2.1 of
10   * the License, or (at your option) any later version.
11   *
12   * This software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this software; if not, write to the Free
19   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21   */
22  package org.jdbc4olap.xmla;
23  
24  import java.net.URL;
25  import java.sql.DriverPropertyInfo;
26  import java.sql.SQLException;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.Properties;
30  import java.util.logging.Level;
31  import java.util.logging.Logger;
32  import java.io.ByteArrayOutputStream;
33  import java.io.PrintWriter;
34  
35  import javax.xml.soap.Detail;
36  import javax.xml.soap.MessageFactory;
37  import javax.xml.soap.MimeHeaders;
38  import javax.xml.soap.Name;
39  import javax.xml.soap.SOAPBody;
40  import javax.xml.soap.SOAPBodyElement;
41  import javax.xml.soap.SOAPConnection;
42  import javax.xml.soap.SOAPConnectionFactory;
43  import javax.xml.soap.SOAPElement;
44  import javax.xml.soap.SOAPEnvelope;
45  import javax.xml.soap.SOAPException;
46  import javax.xml.soap.SOAPFactory;
47  import javax.xml.soap.SOAPFault;
48  import javax.xml.soap.SOAPHeader;
49  import javax.xml.soap.SOAPMessage;
50  import javax.xml.soap.SOAPPart;
51  
52  import org.w3c.dom.NodeList;
53  import org.w3c.dom.Node;
54  
55  /**
56   * @author <a href="mailto:fsiberchicot@jdbc4olap.org">Florian SIBERCHICOT</a>
57   * @author Dan Rollo
58   */
59  public class XmlaConn {
60  
61      public static final int STANDARD_SERVER = 0;
62      private static final int MS_SSAS_SERVER = 1;
63      public static final int SAP_BW_SERVER = 2;
64      public static final int HYPERION_ESSBASE_SERVER = 3;
65      private static final int MONDRIAN_SERVER = 4;
66  
67      private static final String MEASURE_TYPE = "2";
68  
69      public static final String PROPERTY_NAME_IS_DRIVER_DEBUG = "isDriverDebug";
70  
71      private final URL endpoint;
72      private final XmlaLogin login;
73      private String requestType;
74      private final int serverType;
75      private String dataSourceInfo;
76      private String databaseProductName;
77      private boolean hierarchiesSupport=false;
78      private final PropertyManager propertyManager;
79      private final HashMap<String, String> measuresNamesCache;
80      private Logger log = Logger.getLogger(Jdbc4OlapConstants.JDBC4OLAP_LOG);
81  
82  
83      /**
84       * Package visible no-arg constructor for unit testing only.
85       *
86       * @param serverType server type for unit test
87       * @param login      mock login object
88       */
89      XmlaConn(final int serverType, final XmlaLogin login) {
90          endpoint = null;
91          this.login = login;
92          this.serverType = serverType;
93          propertyManager = null;
94          measuresNamesCache = null;
95      }
96  
97      public XmlaConn(final URL endpoint, final Properties info) throws SQLException {
98  
99          this.endpoint = endpoint;
100         login = new XmlaLogin(info);
101         if (login.getUserName() == null
102                 && (endpoint != null && !endpoint.toExternalForm().contains("@"))) {
103             log.warning("WARNING: Login info missing?");
104         }
105 
106         measuresNamesCache = new HashMap<String, String>();
107 
108         NodeList rows = discoverDatasource();
109 
110         Node item = rows.item(0);
111         NodeList nl = item.getChildNodes();
112         dataSourceInfo = "";
113         String dataSourceName= "";
114         databaseProductName = "";
115         String dataSourceDescription = "";
116         XmlaHelper helper = new XmlaHelper();
117         for (int i = 0; i < nl.getLength(); i++) {
118             org.w3c.dom.Node n = nl.item(i);
119             final String nodeName = n.getNodeName();
120             final String nodeTextContent = helper.getTextContent(n);
121             if (nodeName.equals("DataSourceName")) {
122                 dataSourceName = nodeTextContent;
123             } else if (nodeName.equals("ProviderName")) {
124                 databaseProductName = nodeTextContent;
125             } else if (nodeName.equals("DataSourceDescription")) {
126                 dataSourceDescription = nodeTextContent;
127             } else if (nodeName.equals("DataSourceInfo")) {
128                 dataSourceInfo = nodeTextContent;
129             }
130         }
131 
132         final String upperProductName = databaseProductName.toUpperCase();
133         if (upperProductName.contains("MICROSOFT") || upperProductName.startsWith("MS")) {
134             serverType = MS_SSAS_SERVER;
135         } else if (upperProductName.contains("MONDRIAN")) {
136             serverType = MONDRIAN_SERVER;
137         } else if (upperProductName.contains("SAP")) {
138             serverType = SAP_BW_SERVER;
139         } else if (upperProductName.contains("ESSBASE")) {
140             serverType = HYPERION_ESSBASE_SERVER;
141         } else {
142             throw new SQLException("Unknown XML/A provider");
143         }
144         if (serverType == SAP_BW_SERVER) {
145             dataSourceInfo = "Provider=" + databaseProductName + ";DataSource=" + dataSourceDescription;
146         } else if (serverType != HYPERION_ESSBASE_SERVER) {
147             dataSourceInfo = dataSourceName;
148         }
149 
150         propertyManager = new StandardPropertyManager(this, dataSourceInfo, info);
151 
152         if (serverType != HYPERION_ESSBASE_SERVER) {
153 	        rows = discoverSchemaRowsets();
154 	        for (int i = 0; i < rows.getLength(); i++) {
155 	            item = rows.item(i);
156 	            nl = item.getChildNodes();
157 	
158 	            for (int j = 0; j < nl.getLength(); j++) {
159 	                org.w3c.dom.Node node = nl.item(j);
160 	                final String nodeName = node.getNodeName();
161 	                final String nodeTextContent = helper.getTextContent(node);
162 	
163 	                if (nodeName.equals("SchemaName")
164 	                        && nodeTextContent.equalsIgnoreCase("MDSCHEMA_HIERARCHIES")) {
165 	
166 	                    hierarchiesSupport = true;
167 	                    break;
168 	                }
169 	            }
170 	
171 	            // no need to keep looking
172 	            if (hierarchiesSupport) {
173 	                break;
174 	            }
175 	        }
176         }
177     }
178 
179     public DriverPropertyInfo[] getDriverPropertyInfo() throws SQLException {
180         return propertyManager.getDriverPropertyInfo();
181     }
182 
183     public URL getEndpoint() {
184         return endpoint;
185     }
186 
187     public XmlaLogin getLogin() {
188         return login;
189     }
190 
191     public String getDatabaseProductName() throws SQLException {
192         return databaseProductName == null ? propertyManager.getDatabaseProductName() : databaseProductName;
193     }
194 
195     public String getDatabaseProductVersion() throws SQLException {
196         return propertyManager.getDatabaseProductVersion();
197     }
198 
199     private String getRequestType() {
200         return requestType;
201     }
202 
203     private void setRequestType(final String type) {
204         requestType = type;
205     }
206 
207     private void checkReply(final SOAPMessage message, final SOAPMessage reply, final SOAPBody sb) throws SQLException {
208         if (sb.hasFault()) {
209             SOAPFault sf = sb.getFault();
210             StringBuffer errorMsg = new StringBuffer();
211             errorMsg.append(sf.getFaultCode()).append(" : ").append(sf.getFaultString());
212             if (sf.getFaultActor() != null) {
213                 errorMsg.append(" caused by ").append(sf.getFaultActor());
214             }
215             errorMsg.append(".\n");
216             Detail detail = sf.getDetail();
217             if (detail != null) {
218                 try {
219                     final SOAPPart sp = reply.getSOAPPart();
220                     final SOAPEnvelope envelope = sp.getEnvelope();
221                     errorMsg.append(" Details : ");
222                     Iterator<SOAPElement> errorsIt;
223                     switch (serverType) {
224                         case MONDRIAN_SERVER:
225                             final Name mondrianErrorName = envelope.createName("error", "XA", "http://mondrian.sourceforge.net");
226                             final Name mondrianCodeName = envelope.createName("code", "", "");
227                             final Name mondrianDescriptionName = envelope.createName("desc", "", "");
228                             errorsIt = detail.getChildElements(mondrianErrorName);
229                             while (errorsIt.hasNext()) {
230                                 SOAPElement error = errorsIt.next();
231                                 SOAPElement code = (SOAPElement) error.getChildElements(mondrianCodeName).next();
232                                 errorMsg.append(code.getValue()).append(" : ");
233                                 SOAPElement desc = (SOAPElement) error.getChildElements(mondrianDescriptionName).next();
234                                 errorMsg.append(desc.getValue());
235                                 errorMsg.append(".\n");
236                             }
237                             break;
238                         case MS_SSAS_SERVER:
239                         case HYPERION_ESSBASE_SERVER:
240                             final Name msErrorName = envelope.createName("Error", "", "");
241                             final Name msCodeName = envelope.createName("ErrorCode", "", "");
242                             final Name msDescriptionName = envelope.createName("Description", "", "");
243                             final Name msSourceName = envelope.createName("Source", "", "");
244                             errorsIt = detail.getChildElements(msErrorName);
245                             while (errorsIt.hasNext()) {
246                                 SOAPElement error = errorsIt.next();
247                                 errorMsg.append(error.getAttributeValue(msCodeName)).append(" : ");
248                                 errorMsg.append(error.getAttributeValue(msDescriptionName));
249                                 errorMsg.append(" ( ").append(error.getAttributeValue(msSourceName)).append(" ) ");
250                                 errorMsg.append(".\n");
251                             }
252                             break;
253                     }
254                 } catch (SOAPException se) {
255                 }
256             }
257             Logger log = Logger.getLogger(Jdbc4OlapConstants.JDBC4OLAP_LOG);
258             log.info(errorMsg.toString());
259             throw new SQLException(errorMsg.toString());
260         }
261     }
262 
263 
264     private static final SOAPConnectionFactory SOAP_CONNECTION_FACTORY;
265 
266     static {
267         try {
268             SOAP_CONNECTION_FACTORY = SOAPConnectionFactory.newInstance();
269         } catch (SOAPException e) {
270             throw new RuntimeException("Error initing SOAPConnectionFactory.", e);
271         }
272     }
273 
274     static final SOAPFactory SOAP_FACTORY;
275 
276     static {
277         try {
278             SOAP_FACTORY = SOAPFactory.newInstance();
279         } catch (SOAPException e) {
280             throw new RuntimeException("Error initing SOAPFactory.", e);
281         }
282     }
283 
284     private SOAPElement discover(final XmlaProperties properties, final XmlaRestrictions restrictions) throws SQLException {
285         try {
286             final SOAPConnection connection = SOAP_CONNECTION_FACTORY.createConnection();
287             final SOAPMessage message = getSoapMessage();
288             final SOAPBody body = message.getSOAPBody();
289             final SOAPBodyElement discover = body.addBodyElement(SOAP_FACTORY.createName("Discover", "", "urn:schemas-microsoft-com:xml-analysis"));
290             discover.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");
291             final SOAPElement requestType = discover.addChildElement(SOAP_FACTORY.createName("RequestType"));
292             requestType.addTextNode(getRequestType());
293 
294             discover.addChildElement(restrictions.getXMLA());
295             properties.setProperty("Content", "SchemaData");
296             properties.setProperty("Format", "Tabular");
297             discover.addChildElement(properties.getXMLA());
298             addHeaderUserPwd(login, message);
299             message.saveChanges();
300             Logger log = Logger.getLogger(Jdbc4OlapConstants.JDBC4OLAP_LOG);
301             if (log.isLoggable(Level.FINE)) {
302                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
303                 message.writeTo(baos);
304                 log.fine(baos.toString());
305             }
306             final SOAPMessage reply;
307             try {
308                 reply = connection.call(message, endpoint);
309             } catch (SOAPException e) {
310                 log.info("Error in soap call: " + e.getLocalizedMessage());
311                 throw new SQLException("Error in soap call: " + e.getLocalizedMessage() + addCauseAndTrace(e));
312             }
313             if (log.isLoggable(Level.FINE)) {
314                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
315                 reply.writeTo(baos);
316                 log.fine(baos.toString());
317             }
318             final SOAPPart sp = reply.getSOAPPart();
319             final SOAPEnvelope se = sp.getEnvelope();
320             final SOAPBody sb = se.getBody();
321             checkReply(message, reply, sb);
322             return sb;
323         } catch (Exception e) {
324             throw new SQLException(e.getLocalizedMessage() + addCauseAndTrace(e));
325         }
326     }
327 
328     static void addHeaderUserPwd(final XmlaLogin login, final SOAPMessage message) {
329         if (login != null
330                 && login.getUserName() != null
331                 // don't add http auth header if username = "",
332                 // this allows only use of saaj style URL login (http://USER:PASSWORDD@HOST:PORT/...)
333                 && !"".equals(login.getUserName())) {
334             final MimeHeaders hd = message.getMimeHeaders();
335             hd.addHeader("Authorization", "Basic " + login.getAuthorization());
336         }
337     }
338 
339     public NodeList discoverProperties() throws SQLException {
340         setRequestType("DISCOVER_PROPERTIES");
341         XmlaProperties properties = new XmlaProperties();
342         XmlaRestrictions restrictions = new XmlaRestrictions();
343         final SOAPElement reply = discover(properties, restrictions);
344         XmlaHelper helper = new XmlaHelper();
345         return helper.getElementsByTagName(reply, "row");
346     }
347 
348     NodeList discoverDatasource() throws SQLException {
349         setRequestType("DISCOVER_DATASOURCES");
350         XmlaProperties properties = new XmlaProperties();
351         XmlaRestrictions restrictions = new XmlaRestrictions();
352         restrictions.setProviderType("MDP");
353         final SOAPElement reply = discover(properties, restrictions);
354         XmlaHelper helper = new XmlaHelper();
355         return helper.getElementsByTagName(reply, "row");
356     }
357 
358     NodeList discoverSchemaRowsets() throws SQLException {
359         setRequestType("DISCOVER_SCHEMA_ROWSETS");
360         final XmlaProperties properties = new XmlaProperties();
361         final XmlaRestrictions restrictions = new XmlaRestrictions();
362         restrictions.setProviderType("MDP");
363         final SOAPElement reply = discover(properties, restrictions);
364         XmlaHelper helper = new XmlaHelper();
365         return helper.getElementsByTagName(reply, "row");
366     }
367 
368     public NodeList getCatalogsNodeList() throws SQLException {
369         setRequestType("DBSCHEMA_CATALOGS");
370         final XmlaProperties props = propertyManager.getXmlaProperties();
371         props.setProperty("DataSourceInfo", dataSourceInfo);
372         final XmlaRestrictions restrictions = new XmlaRestrictions();
373         final SOAPElement reply = discover(props, restrictions);
374         XmlaHelper helper = new XmlaHelper();
375         return helper.getElementsByTagName(reply, "CATALOG_NAME");
376     }
377 
378     public NodeList getCubesNodeList(final String catalog) throws SQLException {
379         setRequestType("MDSCHEMA_CUBES");
380         final XmlaProperties props = propertyManager.getXmlaProperties();
381         final XmlaRestrictions restrictions = new XmlaRestrictions();
382         restrictions.setCatalog(catalog);
383         propertyManager.setCatalog(catalog);
384         final SOAPElement reply = discover(props, restrictions);
385         XmlaHelper helper = new XmlaHelper();
386         return helper.getElementsByTagName(reply, "CUBE_NAME");
387     }
388 
389     public NodeList getTablesNodeList(final String catalog, final String cube, final String table) throws SQLException {
390         XmlaRestrictions restrictions = new XmlaRestrictions();
391         restrictions.setCatalog(catalog);
392         restrictions.setCubeName(cube);
393         if (hierarchiesSupport) {
394             setRequestType("MDSCHEMA_HIERARCHIES");
395             restrictions.setHierarchyUniqueName(table);
396         } else {
397             setRequestType("MDSCHEMA_DIMENSIONS");
398             restrictions.setDimensionUniqueName(table);
399         }
400         propertyManager.setCatalog(catalog);
401         try {
402             final SOAPElement reply = discover(propertyManager.getXmlaProperties(), restrictions);
403             XmlaHelper helper = new XmlaHelper();
404             return helper.getElementsByTagName(reply, "row");
405         } catch (SQLException e) {
406             Logger log = Logger.getLogger(Jdbc4OlapConstants.JDBC4OLAP_LOG);
407             log.info("Error getting tables for: catalog: " + catalog + ", cube: " + cube
408                     + ", table: " + table);
409             log.log(Level.FINE, "Error getting tables", e);
410             return null;
411         }
412     }
413 
414     public NodeList getLevelsNodeList(final String catalog, final String cube, final String table, final String level) throws SQLException {
415         setRequestType("MDSCHEMA_LEVELS");
416         final XmlaRestrictions restrictions = new XmlaRestrictions();
417         restrictions.setCatalog(catalog);
418         restrictions.setCubeName(cube);
419         restrictions.setLevelUniqueName(level);
420         if (hierarchiesSupport) {
421             restrictions.setHierarchyUniqueName(table);
422         } else {
423             restrictions.setDimensionUniqueName(table);
424         }
425         propertyManager.setCatalog(catalog);
426 
427         final SOAPElement reply = discover(propertyManager.getXmlaProperties(), restrictions);
428         XmlaHelper helper = new XmlaHelper();
429         return helper.getElementsByTagName(reply, "row");
430     }
431 
432     public NodeList getMembersNodeList(final String catalog, final String cube, final String table, final String level, final String member) throws SQLException {
433         setRequestType("MDSCHEMA_MEMBERS");
434         final XmlaRestrictions restrictions = new XmlaRestrictions();
435         restrictions.setCatalog(catalog);
436         restrictions.setCubeName(cube);
437         restrictions.setLevelUniqueName(level);
438         restrictions.setMemberUniqueName(member);
439         if (hierarchiesSupport) {
440             restrictions.setHierarchyUniqueName(table);
441         } else {
442             restrictions.setDimensionUniqueName(table);
443         }
444         propertyManager.setCatalog(catalog);
445 
446         final SOAPElement reply = discover(propertyManager.getXmlaProperties(), restrictions);
447         XmlaHelper helper = new XmlaHelper();
448         return helper.getElementsByTagName(reply, "row");
449     }
450 
451     /*
452      public NodeList getMeasuresNodeList(final String catalog) throws SQLException {
453          setRequestType("MDSCHEMA_MEASURES");
454          final XmlaRestrictions restrictions = new XmlaRestrictions();
455          restrictions.setCatalog(catalog);
456          propertyManager.setCatalog(catalog);
457          final SOAPElement reply = discover(propertyManager.getXmlaProperties(), restrictions);
458          return null;
459  //        XmlaHelper helper = new XmlaHelper();
460  //        return helper.getElementsByTagName(reply,"row");
461  //        return reply.getElementsByTagName("row");
462      }
463       */
464 
465     public String getTableUniqueNameProperty() {
466         if (hierarchiesSupport) {
467             return "HIERARCHY_UNIQUE_NAME";
468         } else {
469             return "DIMENSION_UNIQUE_NAME";
470         }
471     }
472 
473     public String getTableNameProperty() {
474         if (hierarchiesSupport) {
475             return "HIERARCHY_CAPTION";
476         } else {
477             return "DIMENSION_NAME";
478         }
479     }
480 
481 
482     public String getMeasureName(final String catalog, final String cube) throws SQLException {
483         String measure = measuresNamesCache.get(catalog);
484         if (measure == null) {
485             final NodeList rows = getTablesNodeList(catalog, cube, null);
486 
487             XmlaHelper helper = new XmlaHelper();
488             for (int i = 0; i < rows.getLength(); i++) {
489                 final Node item = rows.item(i);
490                 String table = "";
491                 String dimType = "";
492                 final NodeList nl = item.getChildNodes();
493                 for (int j = 0; j < nl.getLength(); j++) {
494                     org.w3c.dom.Node node = nl.item(j);
495                     final String nodeName = node.getNodeName();
496                     final String nodeTextContent = helper.getTextContent(node);
497 
498                     if (nodeName.equals(getTableUniqueNameProperty())) {
499                         table = nodeTextContent;
500                     } else if (nodeName.equals("DIMENSION_TYPE")) {
501                         dimType = nodeTextContent;
502                     }
503                 }
504                 if (dimType.equals(MEASURE_TYPE)) {
505                     if (measure != null && !measure.equals(table)) {
506                         throw new SQLException("Different Measure names found in cubes.");
507                     }
508                     measure = table;
509                 }
510             }
511             measuresNamesCache.put(catalog, measure);
512         }
513         return measure;
514     }
515 
516 
517     public SOAPMessage execute(final String catalog, final String request) throws SQLException {
518         try {
519             final SOAPConnection connection = SOAP_CONNECTION_FACTORY.createConnection();
520             final SOAPMessage message = getSoapMessage();
521             final SOAPBody body = message.getSOAPBody();
522             final SOAPBodyElement execute = body.addBodyElement(SOAP_FACTORY.createName("Execute", "", "urn:schemas-microsoft-com:xml-analysis"));
523             execute.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");
524             final SOAPElement command = execute.addChildElement(SOAP_FACTORY.createName("Command"));
525             final SOAPElement statement = command.addChildElement(SOAP_FACTORY.createName("Statement"));
526             statement.addTextNode(request);
527             propertyManager.setCatalog(catalog);
528             final XmlaProperties properties = propertyManager.getXmlaProperties();
529             properties.setProperty("Format", "Multidimensional");
530             properties.setProperty("Content", "Data");
531             properties.setProperty("AxisFormat", "TupleFormat");
532 
533             execute.addChildElement(properties.getXMLA());
534             properties.setProperty("AxisFormat", "");
535 
536             addHeaderUserPwd(login, message);
537 
538             message.saveChanges();
539             Logger log = Logger.getLogger(Jdbc4OlapConstants.JDBC4OLAP_LOG);
540             if (log.isLoggable(Level.FINE)) {
541                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
542                 message.writeTo(baos);
543                 log.fine(baos.toString());
544             }
545             final SOAPMessage reply = connection.call(message, endpoint);
546             if (log.isLoggable(Level.FINE)) {
547                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
548                 reply.writeTo(baos);
549                 log.fine(baos.toString());
550             }
551             final SOAPPart sp = reply.getSOAPPart();
552             final SOAPEnvelope se = sp.getEnvelope();
553             final SOAPBody sb = se.getBody();
554             checkReply(message, reply, sb);
555             return reply;
556         } catch (Exception e) {
557             throw new SQLException(e.getMessage() + addCauseAndTrace(e));
558         }
559     }
560 
561     public int getServerType() {
562         return serverType;
563     }
564 
565     private static final MessageFactory MESSAGE_FACTORY;
566 
567     static {
568         try {
569             MESSAGE_FACTORY = MessageFactory.newInstance();
570         } catch (SOAPException e) {
571             throw new RuntimeException("Error initing MessageFactory.", e);
572         }
573     }
574 
575     private SOAPMessage getSoapMessage() throws SOAPException {
576         SOAPMessage message = MESSAGE_FACTORY.createMessage();
577         SOAPHeader soapHeader = message.getSOAPHeader();
578         soapHeader.detachNode();
579         return message;
580     }
581 
582 
583     /**
584      * @param throwable the throwable who's stack trace we want to extract
585      * @return the stack trace of the given throwable
586      * @deprecated Only exists until a better logging approach is implemented.
587      */
588     private static String getStackTrace(final Throwable throwable) {
589         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
590         final PrintWriter pw = new PrintWriter(baos);
591         try {
592             throwable.printStackTrace(pw);
593         } finally {
594             pw.close();
595         }
596         return baos.toString();
597     }
598 
599     /**
600      * @param throwable the throwable who's cause we want to extract
601      * @return the cause of the given throwable
602      * @deprecated Only exists until a better logging approach is implemented.
603      */
604     private static String addCauseAndTrace(final Throwable throwable) {
605         final Throwable cause = throwable.getCause();
606         return "\nCaused by: " + (cause == null ? "unknown"
607                 : cause.getLocalizedMessage() + "\nCause Stack Trace: " + getStackTrace(cause));
608     }
609 
610 }