1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
57
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
85
86
87
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
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
332
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
453
454
455
456
457
458
459
460
461
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
585
586
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
601
602
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 }