001 /* 002 // This software is subject to the terms of the Common Public License 003 // Agreement, available at the following URL: 004 // http://www.opensource.org/licenses/cpl.html. 005 // Copyright (C) 2003-2008 Julian Hyde 006 // All Rights Reserved. 007 // You must accept the terms of that agreement to use this software. 008 */ 009 package mondrian.xmla; 010 011 import mondrian.calc.ResultStyle; 012 import mondrian.olap.*; 013 import mondrian.olap.Connection; 014 import mondrian.olap.DriverManager; 015 import mondrian.rolap.*; 016 import mondrian.rolap.sql.SqlQuery; 017 import mondrian.rolap.agg.CellRequest; 018 import mondrian.spi.CatalogLocator; 019 import mondrian.xmla.impl.DefaultSaxWriter; 020 021 import org.apache.log4j.Logger; 022 import org.xml.sax.SAXException; 023 024 import javax.sql.DataSource; 025 import java.math.BigDecimal; 026 import java.math.BigInteger; 027 import java.sql.*; 028 import java.util.*; 029 import java.io.StringWriter; 030 import java.io.PrintWriter; 031 032 033 /** 034 * An <code>XmlaHandler</code> responds to XML for Analysis (XML/A) requests. 035 * 036 * @author jhyde, Gang Chen 037 * @version $Id: //open/mondrian/src/main/mondrian/xmla/XmlaHandler.java#58 $ 038 * @since 27 April, 2003 039 */ 040 public class XmlaHandler implements XmlaConstants { 041 private static final Logger LOGGER = Logger.getLogger(XmlaHandler.class); 042 043 private final Map<String, DataSourcesConfig.DataSource> dataSourcesMap; 044 private final List<String> drillThruColumnNames = new ArrayList<String>(); 045 private final CatalogLocator catalogLocator; 046 private final String prefix; 047 048 private enum SetType { 049 ROW_SET, 050 MD_DATA_SET 051 } 052 053 private static final String EMPTY_ROW_SET_XML_SCHEMA = 054 computeEmptyXsd(SetType.ROW_SET); 055 056 private static final String MD_DATA_SET_XML_SCHEMA = 057 computeXsd(SetType.MD_DATA_SET); 058 059 private static final String EMPTY_MD_DATA_SET_XML_SCHEMA = 060 computeEmptyXsd(SetType.MD_DATA_SET); 061 062 private static final String NS_XML_SQL = "urn:schemas-microsoft-com:xml-sql"; 063 064 // 065 // Some xml schema data types. 066 // 067 public static final String XSD_BOOLEAN = "xsd:boolean"; 068 public static final String XSD_STRING = "xsd:string"; 069 public static final String XSD_UNSIGNED_INT = "xsd:unsignedInt"; 070 071 public static final String XSD_BYTE = "xsd:byte"; 072 public static final byte XSD_BYTE_MAX_INCLUSIVE = 127; 073 public static final byte XSD_BYTE_MIN_INCLUSIVE = -128; 074 075 public static final String XSD_SHORT = "xsd:short"; 076 public static final short XSD_SHORT_MAX_INCLUSIVE = 32767; 077 public static final short XSD_SHORT_MIN_INCLUSIVE = -32768; 078 079 public static final String XSD_INT = "xsd:int"; 080 public static final int XSD_INT_MAX_INCLUSIVE = 2147483647; 081 public static final int XSD_INT_MIN_INCLUSIVE = -2147483648; 082 083 public static final String XSD_LONG = "xsd:long"; 084 public static final long XSD_LONG_MAX_INCLUSIVE = 9223372036854775807L; 085 public static final long XSD_LONG_MIN_INCLUSIVE = -9223372036854775808L; 086 087 // xsd:double: IEEE 64-bit floating-point 088 public static final String XSD_DOUBLE = "xsd:double"; 089 090 // xsd:decimal: Decimal numbers (BigDecimal) 091 public static final String XSD_DECIMAL = "xsd:decimal"; 092 093 // xsd:integer: Signed integers of arbitrary length (BigInteger) 094 public static final String XSD_INTEGER = "xsd:integer"; 095 096 public static boolean isValidXsdInt(long l) { 097 return (l <= XSD_INT_MAX_INCLUSIVE) && (l >= XSD_INT_MIN_INCLUSIVE); 098 } 099 100 /** 101 * Takes a DataType String (null, Integer, Numeric or non-null) 102 * and Value Object (Integer, Double, String, other) and 103 * canonicalizes them to XSD data type and corresponding object. 104 * <p> 105 * If the input DataType is Integer, then it attempts to return 106 * an XSD_INT with value java.lang.Integer (and failing that an 107 * XSD_LONG (java.lang.Long) or XSD_INTEGER (java.math.BigInteger)). 108 * Worst case is the value loses precision with any integral 109 * representation and must be returned as a decimal type (Double 110 * or java.math.BigDecimal). 111 * <p> 112 * If the input DataType is Decimal, then it attempts to return 113 * an XSD_DOUBLE with value java.lang.Double (and failing that an 114 * XSD_DECIMAL (java.math.BigDecimal)). 115 */ 116 static class ValueInfo { 117 118 /** 119 * Returns XSD_INT, XSD_DOUBLE, XSD_STRING or null. 120 * 121 * @param dataType null, Integer, Numeric or non-null. 122 * @return Returns the suggested XSD type for a given datatype 123 */ 124 static String getValueTypeHint(final String dataType) { 125 if (dataType != null) { 126 return (dataType.equals("Integer")) 127 ? XSD_INT 128 : ((dataType.equals("Numeric")) 129 ? XSD_DOUBLE 130 : XSD_STRING); 131 } else { 132 return null; 133 } 134 } 135 136 String valueType; 137 Object value; 138 boolean isDecimal; 139 140 ValueInfo(final String dataType, final Object inputValue) { 141 final String valueTypeHint = getValueTypeHint(dataType); 142 143 // This is a hint: should it be a string, integer or decimal type. 144 // In the following, if the hint is integer, then there is 145 // an attempt that the value types 146 // be XSD_INT, XST_LONG, or XSD_INTEGER (but they could turn 147 // out to be XSD_DOUBLE or XSD_DECIMAL if precision is loss 148 // with the integral formats). It the hint is a decimal type 149 // (double, float, decimal), then a XSD_DOUBLE or XSD_DECIMAL 150 // is returned. 151 if (valueTypeHint != null) { 152 // The value type is a hint. If the value can be 153 // converted to the data type without precision loss, ok; 154 // otherwise value data type must be adjusted. 155 156 if (valueTypeHint.equals(XSD_STRING)) { 157 // For String types, nothing to do. 158 this.valueType = valueTypeHint; 159 this.value = inputValue; 160 this.isDecimal = false; 161 162 } else if (valueTypeHint.equals(XSD_INT)) { 163 // If valueTypeHint is XSD_INT, then see if value can be 164 // converted to (first choice) integer, (second choice), 165 // long and (last choice) BigInteger - otherwise must 166 // use double/decimal. 167 168 // Most of the time value ought to be an Integer so 169 // try it first 170 if (inputValue instanceof Integer) { 171 // For integer, its already the right type 172 this.valueType = valueTypeHint; 173 this.value = inputValue; 174 this.isDecimal = false; 175 176 } else if (inputValue instanceof Byte) { 177 this.valueType = valueTypeHint; 178 this.value = inputValue; 179 this.isDecimal = false; 180 181 } else if (inputValue instanceof Short) { 182 this.valueType = valueTypeHint; 183 this.value = inputValue; 184 this.isDecimal = false; 185 186 } else if (inputValue instanceof Long) { 187 // See if it can be an integer or long 188 long lval = (Long) inputValue; 189 setValueAndType(lval); 190 191 } else if (inputValue instanceof BigInteger) { 192 BigInteger bi = (BigInteger) inputValue; 193 // See if it can be an integer or long 194 long lval = bi.longValue(); 195 if (bi.equals(BigInteger.valueOf(lval))) { 196 // It can be converted from BigInteger to long 197 // without loss of precision. 198 setValueAndType(lval); 199 } else { 200 // It can not be converted to a long. 201 this.valueType = XSD_INTEGER; 202 this.value = inputValue; 203 this.isDecimal = false; 204 } 205 206 } else if (inputValue instanceof Float) { 207 Float f = (Float) inputValue; 208 // See if it can be an integer or long 209 long lval = f.longValue(); 210 if (f.equals(new Float(lval))) { 211 // It can be converted from double to long 212 // without loss of precision. 213 setValueAndType(lval); 214 215 } else { 216 // It can not be converted to a long. 217 this.valueType = XSD_DOUBLE; 218 this.value = inputValue; 219 this.isDecimal = true; 220 } 221 222 } else if (inputValue instanceof Double) { 223 Double d = (Double) inputValue; 224 // See if it can be an integer or long 225 long lval = d.longValue(); 226 if (d.equals(new Double(lval))) { 227 // It can be converted from double to long 228 // without loss of precision. 229 setValueAndType(lval); 230 231 } else { 232 // It can not be converted to a long. 233 this.valueType = XSD_DOUBLE; 234 this.value = inputValue; 235 this.isDecimal = true; 236 } 237 238 } else if (inputValue instanceof BigDecimal) { 239 // See if it can be an integer or long 240 BigDecimal bd = (BigDecimal) inputValue; 241 try { 242 // Can it be converted to a long 243 // Throws ArithmeticException on conversion failure. 244 // The following line is only available in 245 // Java5 and above: 246 //long lval = bd.longValueExact(); 247 long lval = bd.longValue(); 248 249 setValueAndType(lval); 250 251 } catch (ArithmeticException ex) { 252 // No, it can not be converted to long 253 254 try { 255 // Can it be an integer 256 BigInteger bi = bd.toBigIntegerExact(); 257 this.valueType = XSD_INTEGER; 258 this.value = bi; 259 this.isDecimal = false; 260 261 } catch (ArithmeticException ex1) { 262 // OK, its a decimal 263 this.valueType = XSD_DECIMAL; 264 this.value = inputValue; 265 this.isDecimal = true; 266 } 267 } 268 269 } else if (inputValue instanceof Number) { 270 // Don't know what Number type we have here. 271 // Note: this could result in precision loss. 272 this.value = ((Number) inputValue).longValue(); 273 this.valueType = valueTypeHint; 274 this.isDecimal = false; 275 276 } else { 277 // Who knows what we are dealing with, 278 // hope for the best?!? 279 this.valueType = valueTypeHint; 280 this.value = inputValue; 281 this.isDecimal = false; 282 } 283 284 } else if (valueTypeHint.equals(XSD_DOUBLE)) { 285 // The desired type is double. 286 287 // Most of the time value ought to be an Double so 288 // try it first 289 if (inputValue instanceof Double) { 290 // For Double, its already the right type 291 this.valueType = valueTypeHint; 292 this.value = inputValue; 293 this.isDecimal = true; 294 295 } else if (inputValue instanceof Byte || 296 inputValue instanceof Short || 297 inputValue instanceof Integer || 298 inputValue instanceof Long) { 299 // Convert from byte/short/integer/long to double 300 this.value = ((Number) inputValue).doubleValue(); 301 this.valueType = valueTypeHint; 302 this.isDecimal = true; 303 304 } else if (inputValue instanceof Float) { 305 this.value = inputValue; 306 this.valueType = valueTypeHint; 307 this.isDecimal = true; 308 309 } else if (inputValue instanceof BigDecimal) { 310 BigDecimal bd = (BigDecimal) inputValue; 311 double dval = bd.doubleValue(); 312 // make with same scale as Double 313 try { 314 BigDecimal bd2 = 315 Util.makeBigDecimalFromDouble(dval); 316 // Can it be a double 317 // Must use compareTo - see BigDecimal.equals 318 if (bd.compareTo(bd2) == 0) { 319 this.valueType = XSD_DOUBLE; 320 this.value = dval; 321 } else { 322 this.valueType = XSD_DECIMAL; 323 this.value = inputValue; 324 } 325 } catch (NumberFormatException ex) { 326 this.valueType = XSD_DECIMAL; 327 this.value = inputValue; 328 } 329 this.isDecimal = true; 330 331 } else if (inputValue instanceof BigInteger) { 332 // What should be done here? Convert ot BigDecimal 333 // and see if it can be a double or not? 334 // See if there is loss of precision in the convertion? 335 // Don't know. For now, just keep it a integral 336 // value. 337 BigInteger bi = (BigInteger) inputValue; 338 // See if it can be an integer or long 339 long lval = bi.longValue(); 340 if (bi.equals(BigInteger.valueOf(lval))) { 341 // It can be converted from BigInteger to long 342 // without loss of precision. 343 setValueAndType(lval); 344 } else { 345 // It can not be converted to a long. 346 this.valueType = XSD_INTEGER; 347 this.value = inputValue; 348 this.isDecimal = true; 349 } 350 351 } else if (inputValue instanceof Number) { 352 // Don't know what Number type we have here. 353 // Note: this could result in precision loss. 354 this.value = ((Number) inputValue).doubleValue(); 355 this.valueType = valueTypeHint; 356 this.isDecimal = true; 357 358 } else { 359 // Who knows what we are dealing with, 360 // hope for the best?!? 361 this.valueType = valueTypeHint; 362 this.value = inputValue; 363 this.isDecimal = true; 364 } 365 } 366 } else { 367 // There is no valueType "hint", so just get it from the value. 368 if (inputValue instanceof String) { 369 this.valueType = XSD_STRING; 370 this.value = inputValue; 371 this.isDecimal = false; 372 373 } else if (inputValue instanceof Integer) { 374 this.valueType = XSD_INT; 375 this.value = inputValue; 376 this.isDecimal = false; 377 378 } else if (inputValue instanceof Byte) { 379 Byte b = (Byte) inputValue; 380 this.valueType = XSD_INT; 381 this.value = b.intValue(); 382 this.isDecimal = false; 383 384 } else if (inputValue instanceof Short) { 385 Short s = (Short) inputValue; 386 this.valueType = XSD_INT; 387 this.value = s.intValue(); 388 this.isDecimal = false; 389 390 } else if (inputValue instanceof Long) { 391 // See if it can be an integer or long 392 setValueAndType((Long) inputValue); 393 394 } else if (inputValue instanceof BigInteger) { 395 BigInteger bi = (BigInteger) inputValue; 396 // See if it can be an integer or long 397 long lval = bi.longValue(); 398 if (bi.equals(BigInteger.valueOf(lval))) { 399 // It can be converted from BigInteger to long 400 // without loss of precision. 401 setValueAndType(lval); 402 } else { 403 // It can not be converted to a long. 404 this.valueType = XSD_INTEGER; 405 this.value = inputValue; 406 this.isDecimal = false; 407 } 408 409 } else if (inputValue instanceof Float) { 410 this.valueType = XSD_DOUBLE; 411 this.value = inputValue; 412 this.isDecimal = true; 413 414 } else if (inputValue instanceof Double) { 415 this.valueType = XSD_DOUBLE; 416 this.value = inputValue; 417 this.isDecimal = true; 418 419 } else if (inputValue instanceof BigDecimal) { 420 // See if it can be a double 421 BigDecimal bd = (BigDecimal) inputValue; 422 double dval = bd.doubleValue(); 423 // make with same scale as Double 424 try { 425 BigDecimal bd2 = 426 Util.makeBigDecimalFromDouble(dval); 427 // Can it be a double 428 // Must use compareTo - see BigDecimal.equals 429 if (bd.compareTo(bd2) == 0) { 430 this.valueType = XSD_DOUBLE; 431 this.value = dval; 432 } else { 433 this.valueType = XSD_DECIMAL; 434 this.value = inputValue; 435 } 436 } catch (NumberFormatException ex) { 437 this.valueType = XSD_DECIMAL; 438 this.value = inputValue; 439 } 440 this.isDecimal = true; 441 442 } else if (inputValue instanceof Number) { 443 // Don't know what Number type we have here. 444 // Note: this could result in precision loss. 445 this.value = ((Number) inputValue).longValue(); 446 this.valueType = XSD_LONG; 447 this.isDecimal = false; 448 449 } else { 450 // Who knows what we are dealing with, 451 // hope for the best?!? 452 this.valueType = XSD_STRING; 453 this.value = inputValue; 454 this.isDecimal = false; 455 } 456 } 457 } 458 private void setValueAndType(long lval) { 459 if (! isValidXsdInt(lval)) { 460 // No, it can not be a integer, must be a long 461 this.valueType = XSD_LONG; 462 this.value = lval; 463 } else { 464 // Its an integer. 465 this.valueType = XSD_INT; 466 this.value = (int) lval; 467 } 468 this.isDecimal = false; 469 } 470 } 471 472 473 474 private static String computeXsd(SetType setType) { 475 final StringWriter sw = new StringWriter(); 476 SaxWriter writer = new DefaultSaxWriter(new PrintWriter(sw), 3); 477 writeDatasetXmlSchema(writer, setType); 478 writer.flush(); 479 return sw.toString(); 480 } 481 482 private static String computeEmptyXsd(SetType setType) { 483 final StringWriter sw = new StringWriter(); 484 SaxWriter writer = new DefaultSaxWriter(new PrintWriter(sw), 3); 485 writeEmptyDatasetXmlSchema(writer, setType); 486 writer.flush(); 487 return sw.toString(); 488 } 489 490 private static interface QueryResult { 491 public void unparse(SaxWriter res) throws SAXException; 492 } 493 494 /** 495 * Creates an <code>XmlaHandler</code>. 496 * 497 * @param dataSources Data sources 498 * @param catalogLocator Catalog locator 499 * @param prefix XML Namespace. Typical value is "xmla", but a value of 500 * "cxmla" works around an Internet Explorer 7 bug 501 */ 502 public XmlaHandler( 503 DataSourcesConfig.DataSources dataSources, 504 CatalogLocator catalogLocator, 505 String prefix) 506 { 507 this.catalogLocator = catalogLocator; 508 assert prefix != null; 509 this.prefix = prefix; 510 Map<String, DataSourcesConfig.DataSource> map = 511 new HashMap<String, DataSourcesConfig.DataSource>(); 512 if (dataSources != null) { 513 for (DataSourcesConfig.DataSource ds : dataSources.dataSources) { 514 if (map.containsKey(ds.getDataSourceName())) { 515 // This is not an XmlaException 516 throw Util.newError( 517 "duplicated data source name '" + 518 ds.getDataSourceName() + "'"); 519 } 520 // Set parent pointers. 521 for (DataSourcesConfig.Catalog catalog : ds.catalogs.catalogs) { 522 catalog.setDataSource(ds); 523 } 524 map.put(ds.getDataSourceName(), ds); 525 } 526 } 527 dataSourcesMap = Collections.unmodifiableMap(map); 528 } 529 530 public Map<String, DataSourcesConfig.DataSource> getDataSourceEntries() { 531 return dataSourcesMap; 532 } 533 534 /** 535 * Processes a request. 536 * 537 * @param request XML request, for example, "<SOAP-ENV:Envelope ...>". 538 * @param response Destination for response 539 * @throws XmlaException on error 540 */ 541 public void process(XmlaRequest request, XmlaResponse response) 542 throws XmlaException { 543 int method = request.getMethod(); 544 long start = System.currentTimeMillis(); 545 546 switch (method) { 547 case METHOD_DISCOVER: 548 discover(request, response); 549 break; 550 case METHOD_EXECUTE: 551 execute(request, response); 552 break; 553 default: 554 throw new XmlaException( 555 CLIENT_FAULT_FC, 556 HSB_BAD_METHOD_CODE, 557 HSB_BAD_METHOD_FAULT_FS, 558 new IllegalArgumentException( 559 "Unsupported XML/A method: " + method)); 560 } 561 if (LOGGER.isDebugEnabled()) { 562 long end = System.currentTimeMillis(); 563 LOGGER.debug("XmlaHandler.process: time = " + (end - start)); 564 LOGGER.debug("XmlaHandler.process: " + Util.printMemory()); 565 } 566 } 567 568 private void checkFormat(XmlaRequest request) throws XmlaException { 569 // Check response's rowset format in request 570 final Map<String, String> properties = request.getProperties(); 571 if (request.isDrillThrough()) { 572 final String formatName = 573 properties.get(PropertyDefinition.Format.name()); 574 Enumeration.Format format = 575 valueOf( 576 Enumeration.Format.class, 577 formatName, 578 null); 579 if (format != Enumeration.Format.Tabular) { 580 throw new XmlaException( 581 CLIENT_FAULT_FC, 582 HSB_DRILL_THROUGH_FORMAT_CODE, 583 HSB_DRILL_THROUGH_FORMAT_FAULT_FS, 584 new UnsupportedOperationException( 585 "<Format>: only 'Tabular' allowed when drilling through")); 586 } 587 } else { 588 final String formatName = 589 properties.get(PropertyDefinition.Format.name()); 590 if (formatName != null) { 591 Enumeration.Format format = valueOf( 592 Enumeration.Format.class, formatName, null); 593 if (format != Enumeration.Format.Multidimensional && 594 format != Enumeration.Format.Tabular) { 595 throw new UnsupportedOperationException( 596 "<Format>: only 'Multidimensional', 'Tabular' currently supported"); 597 } 598 } 599 final String axisFormatName = 600 properties.get(PropertyDefinition.AxisFormat.name()); 601 if (axisFormatName != null) { 602 Enumeration.AxisFormat axisFormat = valueOf( 603 Enumeration.AxisFormat.class, axisFormatName, null); 604 605 if (axisFormat != Enumeration.AxisFormat.TupleFormat) { 606 throw new UnsupportedOperationException( 607 "<AxisFormat>: only 'TupleFormat' currently supported"); 608 } 609 } 610 } 611 } 612 613 private void execute(XmlaRequest request, XmlaResponse response) 614 throws XmlaException { 615 616 final Map<String, String> properties = request.getProperties(); 617 final String contentName = 618 properties.get(PropertyDefinition.Content.name()); 619 // default value is SchemaData 620 Enumeration.Content content = 621 valueOf(Enumeration.Content.class, contentName, CONTENT_DEFAULT); 622 623 // Handle execute 624 QueryResult result; 625 if (request.isDrillThrough()) { 626 String tabFields = 627 properties.get(PropertyDefinition.TableFields.name()); 628 if (tabFields != null && tabFields.length() > 0) { 629 // Presence of TABLE_FIELDS property initiates advanced 630 // drill-through. 631 result = executeColumnQuery(request); 632 } else { 633 result = executeDrillThroughQuery(request); 634 } 635 } else { 636 result = executeQuery(request); 637 } 638 639 SaxWriter writer = response.getWriter(); 640 writer.startDocument(); 641 642 writer.startElement(prefix + ":ExecuteResponse", new String[] { 643 "xmlns:" + prefix, NS_XMLA}); 644 writer.startElement(prefix + ":return"); 645 boolean rowset = 646 request.isDrillThrough() || 647 Enumeration.Format.Tabular.name().equals( 648 request.getProperties().get( 649 PropertyDefinition.Format.name())); 650 writer.startElement("root", new String[] { 651 "xmlns", 652 result == null ? NS_XMLA_EMPTY : 653 rowset ? NS_XMLA_ROWSET : 654 NS_XMLA_MDDATASET, 655 "xmlns:xsi", NS_XSI, 656 "xmlns:xsd", NS_XSD, 657 "xmlns:EX", NS_XMLA_EX, 658 }); 659 660 if ((content == Enumeration.Content.Schema) 661 || (content == Enumeration.Content.SchemaData)) { 662 if (result != null) { 663 if (result instanceof MDDataSet_Tabular) { 664 MDDataSet_Tabular tabResult = (MDDataSet_Tabular) result; 665 tabResult.metadata(writer); 666 } else if (rowset) { 667 ((TabularRowSet) result).metadata(writer); 668 } else { 669 writer.verbatim(MD_DATA_SET_XML_SCHEMA); 670 } 671 } else { 672 if (rowset) { 673 writer.verbatim(EMPTY_ROW_SET_XML_SCHEMA); 674 } else { 675 writer.verbatim(EMPTY_MD_DATA_SET_XML_SCHEMA); 676 } 677 } 678 } 679 680 try { 681 if ((content == Enumeration.Content.Data) 682 || (content == Enumeration.Content.SchemaData)) { 683 if (result != null) { 684 result.unparse(writer); 685 } 686 } 687 } catch (XmlaException xex) { 688 throw xex; 689 } catch (Throwable t) { 690 throw new XmlaException( 691 SERVER_FAULT_FC, 692 HSB_EXECUTE_UNPARSE_CODE, 693 HSB_EXECUTE_UNPARSE_FAULT_FS, 694 t); 695 } finally { 696 writer.endElement(); // root 697 writer.endElement(); // return 698 writer.endElement(); // ExecuteResponse 699 } 700 701 writer.endDocument(); 702 } 703 704 /** 705 * Computes the XML Schema for a dataset. 706 * 707 * @param writer SAX writer 708 * @param settype rowset or dataset? 709 * @see RowsetDefinition#writeRowsetXmlSchema(SaxWriter) 710 */ 711 static void writeDatasetXmlSchema(SaxWriter writer, SetType settype) { 712 String setNsXmla = (settype == SetType.ROW_SET) 713 ? XmlaConstants.NS_XMLA_ROWSET 714 : XmlaConstants.NS_XMLA_MDDATASET; 715 716 writer.startElement("xsd:schema", new String[] { 717 "xmlns:xsd", XmlaConstants.NS_XSD, 718 "targetNamespace", setNsXmla, 719 "xmlns", setNsXmla, 720 "xmlns:xsi", XmlaConstants.NS_XSI, 721 "xmlns:sql", NS_XML_SQL, 722 "elementFormDefault", "qualified" 723 }); 724 725 // MemberType 726 727 writer.startElement("xsd:complexType", new String[] { 728 "name", "MemberType" 729 }); 730 writer.startElement("xsd:sequence"); 731 writer.element("xsd:element", new String[] { 732 "name", "UName", 733 "type", XSD_STRING 734 }); 735 writer.element("xsd:element", new String[] { 736 "name", "Caption", 737 "type", XSD_STRING 738 }); 739 writer.element("xsd:element", new String[] { 740 "name", "LName", 741 "type", XSD_STRING 742 }); 743 writer.element("xsd:element", new String[] { 744 "name", "LNum", 745 "type", XSD_UNSIGNED_INT 746 }); 747 writer.element("xsd:element", new String[] { 748 "name", "DisplayInfo", 749 "type", XSD_UNSIGNED_INT 750 }); 751 writer.startElement("xsd:sequence", new String[] { 752 "maxOccurs", "unbounded", 753 "minOccurs", "0", 754 }); 755 writer.element("xsd:any", new String[] { 756 "processContents", "lax", 757 "maxOccurs", "unbounded", 758 }); 759 writer.endElement(); // xsd:sequence 760 writer.endElement(); // xsd:sequence 761 writer.element("xsd:attribute", new String[] { 762 "name", "Hierarchy", 763 "type", XSD_STRING 764 }); 765 writer.endElement(); // xsd:complexType name="MemberType" 766 767 // PropType 768 769 writer.startElement("xsd:complexType", new String[] { 770 "name", "PropType", 771 }); 772 writer.element("xsd:attribute", new String[] { 773 "name", "name", 774 "type", XSD_STRING 775 }); 776 writer.endElement(); // xsd:complexType name="PropType" 777 778 // TupleType 779 780 writer.startElement("xsd:complexType", new String[] { 781 "name", "TupleType" 782 }); 783 writer.startElement("xsd:sequence", new String[] { 784 "maxOccurs", "unbounded" 785 }); 786 writer.element("xsd:element", new String[] { 787 "name", "Member", 788 "type", "MemberType", 789 }); 790 writer.endElement(); // xsd:sequence 791 writer.endElement(); // xsd:complexType name="TupleType" 792 793 // MembersType 794 795 writer.startElement("xsd:complexType", new String[] { 796 "name", "MembersType" 797 }); 798 writer.startElement("xsd:sequence", new String[] { 799 "maxOccurs", "unbounded", 800 }); 801 writer.element("xsd:element", new String[] { 802 "name", "Member", 803 "type", "MemberType", 804 }); 805 writer.endElement(); // xsd:sequence 806 writer.element("xsd:attribute", new String[] { 807 "name", "Hierarchy", 808 "type", XSD_STRING 809 }); 810 writer.endElement(); // xsd:complexType 811 812 // TuplesType 813 814 writer.startElement("xsd:complexType", new String[] { 815 "name", "TuplesType" 816 }); 817 writer.startElement("xsd:sequence", new String[] { 818 "maxOccurs", "unbounded", 819 }); 820 writer.element("xsd:element", new String[] { 821 "name", "Tuple", 822 "type", "TupleType", 823 }); 824 writer.endElement(); // xsd:sequence 825 writer.endElement(); // xsd:complexType 826 827 // CrossProductType 828 829 writer.startElement("xsd:complexType", new String[] { 830 "name", "CrossProductType", 831 }); 832 writer.startElement("xsd:sequence"); 833 writer.startElement("xsd:choice", new String[] { 834 "minOccurs", "0", 835 "maxOccurs", "unbounded", 836 }); 837 writer.element("xsd:element", new String[] { 838 "name", "Members", 839 "type", "MembersType" 840 }); 841 writer.element("xsd:element", new String[] { 842 "name", "Tuples", 843 "type", "TuplesType" 844 }); 845 writer.endElement(); // xsd:choice 846 writer.endElement(); // xsd:sequence 847 writer.element("xsd:attribute", new String[] { 848 "name", "Size", 849 "type", XSD_UNSIGNED_INT 850 }); 851 writer.endElement(); // xsd:complexType 852 853 // OlapInfo 854 855 writer.startElement("xsd:complexType", new String[] { 856 "name", "OlapInfo", 857 }); 858 writer.startElement("xsd:sequence"); 859 860 { // <CubeInfo> 861 writer.startElement("xsd:element", new String[] { 862 "name", "CubeInfo" 863 }); 864 writer.startElement("xsd:complexType"); 865 writer.startElement("xsd:sequence"); 866 867 { // <Cube> 868 writer.startElement("xsd:element", new String[] { 869 "name", "Cube", 870 "maxOccurs", "unbounded" 871 }); 872 writer.startElement("xsd:complexType"); 873 writer.startElement("xsd:sequence"); 874 875 writer.element("xsd:element", new String[] { 876 "name", "CubeName", 877 "type", XSD_STRING 878 }); 879 880 writer.endElement(); // xsd:sequence 881 writer.endElement(); // xsd:complexType 882 writer.endElement(); // xsd:element name=Cube 883 } 884 885 writer.endElement(); // xsd:sequence 886 writer.endElement(); // xsd:complexType 887 writer.endElement(); // xsd:element name=CubeInfo 888 } 889 { // <AxesInfo> 890 writer.startElement("xsd:element", new String[] { 891 "name", "AxesInfo" 892 }); 893 writer.startElement("xsd:complexType"); 894 writer.startElement("xsd:sequence"); 895 { // <AxisInfo> 896 writer.startElement("xsd:element", new String[] { 897 "name", "AxisInfo", 898 "maxOccurs", "unbounded" 899 }); 900 writer.startElement("xsd:complexType"); 901 writer.startElement("xsd:sequence"); 902 903 { // <HierarchyInfo> 904 writer.startElement("xsd:element", new String[] { 905 "name", "HierarchyInfo", 906 "minOccurs", "0", 907 "maxOccurs", "unbounded" 908 }); 909 writer.startElement("xsd:complexType"); 910 writer.startElement("xsd:sequence"); 911 writer.startElement("xsd:sequence", new String[] { 912 "maxOccurs", "unbounded" 913 }); 914 writer.element("xsd:element", new String[] { 915 "name", "UName", 916 "type", "PropType" 917 }); 918 writer.element("xsd:element", new String[] { 919 "name", "Caption", 920 "type", "PropType" 921 }); 922 writer.element("xsd:element", new String[] { 923 "name", "LName", 924 "type", "PropType" 925 }); 926 writer.element("xsd:element", new String[] { 927 "name", "LNum", 928 "type", "PropType" 929 }); 930 writer.element("xsd:element", new String[] { 931 "name", "DisplayInfo", 932 "type", "PropType", 933 "minOccurs", "0", 934 "maxOccurs", "unbounded" 935 }); 936 if (false) writer.element("xsd:element", new String[] { 937 "name", "PARENT_MEMBER_NAME", 938 "type", "PropType", 939 "minOccurs", "0", 940 "maxOccurs", "unbounded" 941 }); 942 writer.endElement(); // xsd:sequence 943 944 // This is the Depth element for JPivot?? 945 writer.startElement("xsd:sequence"); 946 writer.element("xsd:any", new String[] { 947 "processContents", "lax", 948 "minOccurs", "0", 949 "maxOccurs", "unbounded" 950 }); 951 writer.endElement(); // xsd:sequence 952 953 writer.endElement(); // xsd:sequence 954 writer.element("xsd:attribute", new String[] { 955 "name", "name", 956 "type", XSD_STRING, 957 "use", "required" 958 }); 959 writer.endElement(); // xsd:complexType 960 writer.endElement(); // xsd:element name=HierarchyInfo 961 } 962 writer.endElement(); // xsd:sequence 963 writer.element("xsd:attribute", new String[] { 964 "name", "name", 965 "type", XSD_STRING 966 }); 967 writer.endElement(); // xsd:complexType 968 writer.endElement(); // xsd:element name=AxisInfo 969 } 970 writer.endElement(); // xsd:sequence 971 writer.endElement(); // xsd:complexType 972 writer.endElement(); // xsd:element name=AxesInfo 973 } 974 975 // CellInfo 976 977 { // <CellInfo> 978 writer.startElement("xsd:element", new String[] { 979 "name", "CellInfo" 980 }); 981 writer.startElement("xsd:complexType"); 982 writer.startElement("xsd:sequence"); 983 writer.startElement("xsd:sequence", new String[] { 984 "minOccurs", "0", 985 "maxOccurs", "unbounded" 986 }); 987 writer.startElement("xsd:choice"); 988 writer.element("xsd:element", new String[] { 989 "name", "Value", 990 "type", "PropType" 991 }); 992 writer.element("xsd:element", new String[] { 993 "name", "FmtValue", 994 "type", "PropType" 995 }); 996 writer.element("xsd:element", new String[] { 997 "name", "BackColor", 998 "type", "PropType" 999 }); 1000 writer.element("xsd:element", new String[] { 1001 "name", "ForeColor", 1002 "type", "PropType" 1003 }); 1004 writer.element("xsd:element", new String[] { 1005 "name", "FontName", 1006 "type", "PropType" 1007 }); 1008 writer.element("xsd:element", new String[] { 1009 "name", "FontSize", 1010 "type", "PropType" 1011 }); 1012 writer.element("xsd:element", new String[] { 1013 "name", "FontFlags", 1014 "type", "PropType" 1015 }); 1016 writer.element("xsd:element", new String[] { 1017 "name", "FormatString", 1018 "type", "PropType" 1019 }); 1020 writer.element("xsd:element", new String[] { 1021 "name", "NonEmptyBehavior", 1022 "type", "PropType" 1023 }); 1024 writer.element("xsd:element", new String[] { 1025 "name", "SolveOrder", 1026 "type", "PropType" 1027 }); 1028 writer.element("xsd:element", new String[] { 1029 "name", "Updateable", 1030 "type", "PropType" 1031 }); 1032 writer.element("xsd:element", new String[] { 1033 "name", "Visible", 1034 "type", "PropType" 1035 }); 1036 writer.element("xsd:element", new String[] { 1037 "name", "Expression", 1038 "type", "PropType" 1039 }); 1040 writer.endElement(); // xsd:choice 1041 writer.endElement(); // xsd:sequence 1042 writer.startElement("xsd:sequence", new String[] { 1043 "maxOccurs", "unbounded", 1044 "minOccurs", "0" 1045 }); 1046 writer.element("xsd:any", new String[] { 1047 "processContents", "lax", 1048 "maxOccurs", "unbounded" 1049 }); 1050 writer.endElement(); // xsd:sequence 1051 writer.endElement(); // xsd:sequence 1052 writer.endElement(); // xsd:complexType 1053 writer.endElement(); // xsd:element name=CellInfo 1054 } 1055 1056 writer.endElement(); // xsd:sequence 1057 writer.endElement(); // xsd:complexType 1058 1059 // Axes 1060 1061 writer.startElement("xsd:complexType", new String[] { 1062 "name", "Axes" 1063 }); 1064 writer.startElement("xsd:sequence", new String[] { 1065 "maxOccurs", "unbounded" 1066 }); 1067 { // <Axis> 1068 writer.startElement("xsd:element", new String[] { 1069 "name", "Axis" 1070 }); 1071 writer.startElement("xsd:complexType"); 1072 writer.startElement("xsd:choice", new String[] { 1073 "minOccurs", "0", 1074 "maxOccurs", "unbounded" 1075 }); 1076 writer.element("xsd:element", new String[] { 1077 "name", "CrossProduct", 1078 "type", "CrossProductType" 1079 }); 1080 writer.element("xsd:element", new String[] { 1081 "name", "Tuples", 1082 "type", "TuplesType" 1083 }); 1084 writer.element("xsd:element", new String[] { 1085 "name", "Members", 1086 "type", "MembersType" 1087 }); 1088 writer.endElement(); // xsd:choice 1089 writer.element("xsd:attribute", new String[] { 1090 "name", "name", 1091 "type", XSD_STRING 1092 }); 1093 writer.endElement(); // xsd:complexType 1094 } 1095 writer.endElement(); // xsd:element 1096 writer.endElement(); // xsd:sequence 1097 writer.endElement(); // xsd:complexType 1098 1099 // CellData 1100 1101 writer.startElement("xsd:complexType", new String[] { 1102 "name", "CellData" 1103 }); 1104 writer.startElement("xsd:sequence"); 1105 { // <Cell> 1106 writer.startElement("xsd:element", new String[] { 1107 "name", "Cell", 1108 "minOccurs", "0", 1109 "maxOccurs", "unbounded" 1110 }); 1111 writer.startElement("xsd:complexType"); 1112 writer.startElement("xsd:sequence", new String[] { 1113 "maxOccurs", "unbounded" 1114 }); 1115 writer.startElement("xsd:choice"); 1116 writer.element("xsd:element", new String[] { 1117 "name", "Value" 1118 }); 1119 writer.element("xsd:element", new String[] { 1120 "name", "FmtValue", 1121 "type", XSD_STRING 1122 }); 1123 writer.element("xsd:element", new String[] { 1124 "name", "BackColor", 1125 "type", XSD_UNSIGNED_INT 1126 }); 1127 writer.element("xsd:element", new String[] { 1128 "name", "ForeColor", 1129 "type", XSD_UNSIGNED_INT 1130 }); 1131 writer.element("xsd:element", new String[] { 1132 "name", "FontName", 1133 "type", XSD_STRING 1134 }); 1135 writer.element("xsd:element", new String[] { 1136 "name", "FontSize", 1137 "type", "xsd:unsignedShort" 1138 }); 1139 writer.element("xsd:element", new String[] { 1140 "name", "FontFlags", 1141 "type", XSD_UNSIGNED_INT 1142 }); 1143 writer.element("xsd:element", new String[] { 1144 "name", "FormatString", 1145 "type", XSD_STRING 1146 }); 1147 writer.element("xsd:element", new String[] { 1148 "name", "NonEmptyBehavior", 1149 "type", "xsd:unsignedShort" 1150 }); 1151 writer.element("xsd:element", new String[] { 1152 "name", "SolveOrder", 1153 "type", XSD_UNSIGNED_INT 1154 }); 1155 writer.element("xsd:element", new String[] { 1156 "name", "Updateable", 1157 "type", XSD_UNSIGNED_INT 1158 }); 1159 writer.element("xsd:element", new String[] { 1160 "name", "Visible", 1161 "type", XSD_UNSIGNED_INT 1162 }); 1163 writer.element("xsd:element", new String[] { 1164 "name", "Expression", 1165 "type", XSD_STRING 1166 }); 1167 writer.endElement(); // xsd:choice 1168 writer.endElement(); // xsd:sequence 1169 writer.element("xsd:attribute", new String[] { 1170 "name", "CellOrdinal", 1171 "type", XSD_UNSIGNED_INT, 1172 "use", "required" 1173 }); 1174 writer.endElement(); // xsd:complexType 1175 writer.endElement(); // xsd:element name=Cell 1176 } 1177 writer.endElement(); // xsd:sequence 1178 writer.endElement(); // xsd:complexType 1179 1180 { // <root> 1181 writer.startElement("xsd:element", new String[] { 1182 "name", "root" 1183 }); 1184 writer.startElement("xsd:complexType"); 1185 writer.startElement("xsd:sequence", new String[] { 1186 "maxOccurs", "unbounded" 1187 }); 1188 writer.element("xsd:element", new String[] { 1189 "name", "OlapInfo", 1190 "type", "OlapInfo" 1191 }); 1192 writer.element("xsd:element", new String[] { 1193 "name", "Axes", 1194 "type", "Axes" 1195 }); 1196 writer.element("xsd:element", new String[] { 1197 "name", "CellData", 1198 "type", "CellData" 1199 }); 1200 writer.endElement(); // xsd:sequence 1201 writer.endElement(); // xsd:complexType 1202 writer.endElement(); // xsd:element name=root 1203 } 1204 1205 writer.endElement(); // xsd:schema 1206 } 1207 1208 static void writeEmptyDatasetXmlSchema(SaxWriter writer, SetType setType) { 1209 String setNsXmla = XmlaConstants.NS_XMLA_ROWSET; 1210 writer.startElement("xsd:schema", new String[] { 1211 "xmlns:xsd", XmlaConstants.NS_XSD, 1212 "targetNamespace", setNsXmla, 1213 "xmlns", setNsXmla, 1214 "xmlns:xsi", XmlaConstants.NS_XSI, 1215 "xmlns:sql", NS_XML_SQL, 1216 "elementFormDefault", "qualified" 1217 }); 1218 1219 writer.element("xsd:element", new String[] { 1220 "name", "root" 1221 }); 1222 1223 writer.endElement(); // xsd:schema 1224 } 1225 1226 private QueryResult executeDrillThroughQuery(XmlaRequest request) 1227 throws XmlaException { 1228 1229 checkFormat(request); 1230 1231 DataSourcesConfig.DataSource ds = getDataSource(request); 1232 DataSourcesConfig.Catalog dsCatalog = getCatalog(request, ds, true); 1233 String roleName = request.getRoleName(); 1234 Role role = request.getRole(); 1235 1236 final Connection connection = getConnection(dsCatalog, role, roleName); 1237 1238 final String statement = request.getStatement(); 1239 final Query query = connection.parseQuery(statement); 1240 query.setResultStyle(ResultStyle.LIST); 1241 final Result result = connection.execute(query); 1242 Cell dtCell = result.getCell(new int[] {0, 0}); 1243 1244 if (!dtCell.canDrillThrough()) { 1245 throw new XmlaException( 1246 SERVER_FAULT_FC, 1247 HSB_DRILL_THROUGH_NOT_ALLOWED_CODE, 1248 HSB_DRILL_THROUGH_NOT_ALLOWED_FAULT_FS, 1249 Util.newError("Cannot do DrillThrough operation on the cell")); 1250 } 1251 1252 String dtSql = dtCell.getDrillThroughSQL(true); 1253 java.sql.Connection sqlConn = null; 1254 1255 try { 1256 final Map<String, String> properties = request.getProperties(); 1257 final String advancedFlag = 1258 properties.get(PropertyDefinition.AdvancedFlag.name()); 1259 if ("true".equals(advancedFlag)) { 1260 final Position position = result.getAxes()[0].getPositions().get(0); 1261 Member[] members = position.toArray(new Member[position.size()]); 1262 1263 final CellRequest cellRequest = 1264 RolapAggregationManager.makeRequest(members); 1265 List<MondrianDef.Relation> relationList = 1266 new ArrayList<MondrianDef.Relation>(); 1267 final RolapStar.Table factTable = 1268 cellRequest.getMeasure().getStar().getFactTable(); 1269 MondrianDef.Relation relation = factTable.getRelation(); 1270 relationList.add(relation); 1271 1272 for (RolapStar.Table table : factTable.getChildren()) { 1273 relationList.add(table.getRelation()); 1274 } 1275 List<String> truncatedTableList = new ArrayList<String>(); 1276 sqlConn = connection.getDataSource().getConnection(); 1277 Statement stmt = null; 1278 try { 1279 stmt = sqlConn.createStatement(); 1280 List<List<String>> fields = new ArrayList<List<String>>(); 1281 1282 Map<String, List<String>> tableFieldMap = 1283 new HashMap<String, List<String>>(); 1284 for (MondrianDef.Relation relation1 : relationList) { 1285 final String tableName = relation1.toString(); 1286 List<String> fieldNameList = new ArrayList<String>(); 1287 // FIXME: Quote table name 1288 dtSql = "SELECT * FROM " + tableName + " WHERE 1=2"; 1289 ResultSet rs = stmt.executeQuery(dtSql); 1290 ResultSetMetaData rsMeta = rs.getMetaData(); 1291 for (int j = 1; j <= rsMeta.getColumnCount(); j++) { 1292 String colName = rsMeta.getColumnName(j); 1293 boolean colNameExists = false; 1294 for (List<String> prvField : fields) { 1295 if (prvField.contains(colName)) { 1296 colNameExists = true; 1297 break; 1298 } 1299 } 1300 if (!colNameExists) { 1301 fieldNameList.add(rsMeta.getColumnName(j)); 1302 } 1303 } 1304 fields.add(fieldNameList); 1305 String truncatedTableName = 1306 tableName.substring(tableName.lastIndexOf(".") + 1); 1307 truncatedTableList.add(truncatedTableName); 1308 tableFieldMap.put(truncatedTableName, fieldNameList); 1309 } 1310 return new TabularRowSet(tableFieldMap, truncatedTableList); 1311 } finally { 1312 if (stmt != null) { 1313 try { 1314 stmt.close(); 1315 } catch (SQLException ignored) { 1316 } 1317 } 1318 } 1319 } else { 1320 int count = -1; 1321 if (MondrianProperties.instance().EnableTotalCount.booleanValue()) { 1322 count = dtCell.getDrillThroughCount(); 1323 } 1324 1325 if (LOGGER.isDebugEnabled()) { 1326 LOGGER.debug("drill through sql: " + dtSql); 1327 } 1328 int resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE; 1329 int resultSetConcurrency = ResultSet.CONCUR_READ_ONLY; 1330 SqlQuery.Dialect dialect = 1331 ((RolapSchema) connection.getSchema()).getDialect(); 1332 if (!dialect.supportsResultSetConcurrency( 1333 resultSetType, resultSetConcurrency)) { 1334 // downgrade to non-scroll cursor, since we can 1335 // fake absolute() via forward fetch 1336 resultSetType = ResultSet.TYPE_FORWARD_ONLY; 1337 } 1338 SqlStatement stmt2 = 1339 RolapUtil.executeQuery( 1340 connection.getDataSource(), dtSql, -1, 1341 "XmlaHandler.executeDrillThroughQuery", 1342 "Error in drill through", 1343 resultSetType, resultSetConcurrency); 1344 return new TabularRowSet( 1345 stmt2, request.drillThroughMaxRows(), 1346 request.drillThroughFirstRowset(), count, 1347 resultSetType); 1348 } 1349 } catch (XmlaException xex) { 1350 throw xex; 1351 } catch (SQLException sqle) { 1352 throw new XmlaException( 1353 SERVER_FAULT_FC, 1354 HSB_DRILL_THROUGH_SQL_CODE, 1355 HSB_DRILL_THROUGH_SQL_FAULT_FS, 1356 Util.newError(sqle, "Error in drill through")); 1357 } catch (RuntimeException e) { 1358 throw new XmlaException( 1359 SERVER_FAULT_FC, 1360 HSB_DRILL_THROUGH_SQL_CODE, 1361 HSB_DRILL_THROUGH_SQL_FAULT_FS, 1362 e); 1363 } finally { 1364 if (sqlConn != null) { 1365 try { 1366 sqlConn.close(); 1367 } catch (SQLException ignored) { 1368 } 1369 } 1370 } 1371 } 1372 1373 static class Column { 1374 private final String name; 1375 private final String encodedName; 1376 private final String xsdType; 1377 1378 Column(String name, int type) { 1379 this.name = name; 1380 1381 // replace invalid XML element name, like " ", with "_x0020_" in 1382 // column headers, otherwise will generate a badly-formatted xml 1383 // doc. 1384 this.encodedName = XmlaUtil.encodeElementName(name); 1385 this.xsdType = sqlToXsdType(type); 1386 } 1387 } 1388 1389 static class TabularRowSet implements QueryResult { 1390 private final List<Column> columns = new ArrayList<Column>(); 1391 private final List<Object[]> rows; 1392 private int totalCount; 1393 1394 /** 1395 * Creates a TabularRowSet based upon a SQL statement result. 1396 * 1397 * <p>Closes the SqlStatement when it is done. 1398 * 1399 * @param stmt SqlStatement 1400 * @param maxRows Maximum row count 1401 * @param firstRowset Ordinal of row to skip to (1-based), or 0 to 1402 * start from beginning 1403 * @param totalCount Total number of rows. If >= 0, writes the 1404 * "totalCount" attribute. 1405 * @param resultSetType Type of ResultSet, for example 1406 * {@link ResultSet#TYPE_FORWARD_ONLY}. 1407 */ 1408 public TabularRowSet( 1409 SqlStatement stmt, 1410 int maxRows, 1411 int firstRowset, 1412 int totalCount, 1413 int resultSetType) 1414 { 1415 this.totalCount = totalCount; 1416 ResultSet rs = stmt.getResultSet(); 1417 try { 1418 ResultSetMetaData md = rs.getMetaData(); 1419 int columnCount = md.getColumnCount(); 1420 1421 // populate column defs 1422 for (int i = 0; i < columnCount; i++) { 1423 columns.add( 1424 new Column( 1425 md.getColumnLabel(i + 1), 1426 md.getColumnType(i + 1))); 1427 } 1428 1429 // skip to first rowset specified in request 1430 int firstRow = (firstRowset <= 0 ? 1 : firstRowset); 1431 if (resultSetType == ResultSet.TYPE_FORWARD_ONLY) { 1432 for (int i = 0; i < firstRow; ++i) { 1433 if (!rs.next()) { 1434 break; 1435 } 1436 } 1437 } else { 1438 rs.absolute(firstRow); 1439 } 1440 1441 // populate data 1442 rows = new ArrayList<Object[]>(); 1443 maxRows = (maxRows <= 0 ? Integer.MAX_VALUE : maxRows); 1444 do { 1445 Object[] row = new Object[columnCount]; 1446 for (int i = 0; i < columnCount; i++) { 1447 row[i] = rs.getObject(i + 1); 1448 } 1449 rows.add(row); 1450 } while (rs.next() && --maxRows > 0); 1451 } catch (SQLException e) { 1452 throw stmt.handle(e); 1453 } finally { 1454 stmt.close(); 1455 } 1456 } 1457 1458 /** 1459 * Alternate constructor for advanced drill-through. 1460 * 1461 * @param tableFieldMap Map from table name to a list of the names of 1462 * the fields in the table 1463 * @param tableList List of table names 1464 */ 1465 public TabularRowSet( 1466 Map<String, List<String>> tableFieldMap, List<String> tableList) 1467 { 1468 for (String tableName : tableList) { 1469 List<String> fieldNames = tableFieldMap.get(tableName); 1470 for (String fieldName : fieldNames) { 1471 columns.add( 1472 new Column( 1473 tableName + "." + fieldName, 1474 Types.VARCHAR)); // don't know the real type 1475 } 1476 } 1477 1478 rows = new ArrayList<Object[]>(); 1479 Object[] row = new Object[columns.size()]; 1480 for (int k = 0; k < row.length; k++) { 1481 row[k] = k; 1482 } 1483 rows.add(row); 1484 } 1485 1486 public void unparse(SaxWriter writer) throws SAXException { 1487 // write total count row if enabled 1488 if (totalCount >= 0) { 1489 String countStr = Integer.toString(totalCount); 1490 writer.startElement("row"); 1491 for (Column column : columns) { 1492 writer.startElement(column.encodedName); 1493 writer.characters(countStr); 1494 writer.endElement(); 1495 } 1496 writer.endElement(); // row 1497 } 1498 1499 for (Object[] row : rows) { 1500 writer.startElement("row"); 1501 for (int i = 0; i < row.length; i++) { 1502 writer.startElement(columns.get(i).encodedName); 1503 Object value = row[i]; 1504 if (value == null) { 1505 writer.characters("null"); 1506 } else { 1507 String valueString = value.toString(); 1508 if (value instanceof Number) { 1509 valueString = 1510 XmlaUtil.normalizeNumericString(valueString); 1511 } 1512 writer.characters(valueString); 1513 } 1514 writer.endElement(); 1515 } 1516 writer.endElement(); // row 1517 } 1518 } 1519 1520 public TabularRowSet(ResultSet rs) throws SQLException { 1521 ResultSetMetaData md = rs.getMetaData(); 1522 int columnCount = md.getColumnCount(); 1523 1524 // populate column definitions 1525 for (int i = 0; i < columnCount; i++) { 1526 columns.add( 1527 new Column( 1528 md.getColumnName(i + 1), 1529 md.getColumnType(i + 1))); 1530 } 1531 1532 // populate data 1533 rows = new ArrayList<Object[]>(); 1534 while (rs.next()) { 1535 Object[] row = new Object[columnCount]; 1536 for (int i = 0; i < columnCount; i++) { 1537 row[i] = rs.getObject(i + 1); 1538 } 1539 rows.add(row); 1540 } 1541 } 1542 1543 /** 1544 * Writes the tabular drillthrough schema 1545 * 1546 * @param writer Writer 1547 */ 1548 public void metadata(SaxWriter writer) { 1549 writer.startElement("xsd:schema", new String[] { 1550 "xmlns:xsd", XmlaConstants.NS_XSD, 1551 "targetNamespace", NS_XMLA_ROWSET, 1552 "xmlns", NS_XMLA_ROWSET, 1553 "xmlns:xsi", XmlaConstants.NS_XSI, 1554 "xmlns:sql", NS_XML_SQL, 1555 "elementFormDefault", "qualified" 1556 }); 1557 1558 { // <root> 1559 writer.startElement("xsd:element", new String[] { 1560 "name", "root" 1561 }); 1562 writer.startElement("xsd:complexType"); 1563 writer.startElement("xsd:sequence"); 1564 writer.element("xsd:element", new String[] { 1565 "maxOccurs", "unbounded", 1566 "minOccurs", "0", 1567 "name", "row", 1568 "type", "row" 1569 }); 1570 writer.endElement(); // xsd:sequence 1571 writer.endElement(); // xsd:complexType 1572 writer.endElement(); // xsd:element name=root 1573 } 1574 1575 { // xsd:simpleType name="uuid" 1576 writer.startElement("xsd:simpleType", new String[] { 1577 "name", "uuid" 1578 }); 1579 writer.startElement("xsd:restriction", new String[] { 1580 "base", XSD_STRING 1581 }); 1582 writer.element("xsd:pattern", new String[] { 1583 "value", "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" 1584 }); 1585 writer.endElement(); // xsd:restriction 1586 writer.endElement(); // xsd:simpleType 1587 } 1588 1589 { // xsd:complexType name="row" 1590 writer.startElement("xsd:complexType", new String[] { 1591 "name", "row" 1592 }); 1593 writer.startElement("xsd:sequence"); 1594 for (Column column : columns) { 1595 writer.element( 1596 "xsd:element", new String[] { 1597 "minOccurs", "0", 1598 "name", column.encodedName, 1599 "sql:field", column.name, 1600 "type", column.xsdType 1601 }); 1602 } 1603 1604 writer.endElement(); // xsd:sequence 1605 writer.endElement(); // xsd:complexType 1606 } 1607 writer.endElement(); // xsd:schema 1608 } 1609 } 1610 1611 /** 1612 * Converts a SQL type to XSD type. 1613 * 1614 * @param sqlType SQL type 1615 * @return XSD type 1616 */ 1617 private static String sqlToXsdType(int sqlType) { 1618 switch (sqlType) { 1619 // Integer 1620 case Types.INTEGER: 1621 case Types.BIGINT: 1622 case Types.SMALLINT: 1623 case Types.TINYINT: 1624 return XSD_INTEGER; 1625 case Types.NUMERIC: 1626 return XSD_DECIMAL; 1627 // Real 1628 case Types.DOUBLE: 1629 case Types.FLOAT: 1630 return XSD_DOUBLE; 1631 // Date and time 1632 case Types.TIME: 1633 case Types.TIMESTAMP: 1634 case Types.DATE: 1635 return XSD_STRING; 1636 // Other 1637 default: 1638 return XSD_STRING; 1639 } 1640 } 1641 1642 private QueryResult executeQuery(XmlaRequest request) 1643 throws XmlaException { 1644 final String statement = request.getStatement(); 1645 1646 if (LOGGER.isDebugEnabled()) { 1647 LOGGER.debug("mdx: \"" + statement + "\""); 1648 } 1649 1650 if ((statement == null) || (statement.length() == 0)) { 1651 return null; 1652 } else { 1653 checkFormat(request); 1654 1655 DataSourcesConfig.DataSource ds = getDataSource(request); 1656 DataSourcesConfig.Catalog dsCatalog = getCatalog(request, ds, true); 1657 String roleName = request.getRoleName(); 1658 Role role = request.getRole(); 1659 1660 final Connection connection = 1661 getConnection(dsCatalog, role, roleName); 1662 1663 final Query query; 1664 try { 1665 query = connection.parseQuery(statement); 1666 query.setResultStyle(ResultStyle.LIST); 1667 } catch (XmlaException ex) { 1668 throw ex; 1669 } catch (Exception ex) { 1670 throw new XmlaException( 1671 CLIENT_FAULT_FC, 1672 HSB_PARSE_QUERY_CODE, 1673 HSB_PARSE_QUERY_FAULT_FS, 1674 ex); 1675 } 1676 final Result result; 1677 try { 1678 result = connection.execute(query); 1679 } catch (XmlaException ex) { 1680 throw ex; 1681 } catch (Exception ex) { 1682 throw new XmlaException( 1683 SERVER_FAULT_FC, 1684 HSB_EXECUTE_QUERY_CODE, 1685 HSB_EXECUTE_QUERY_FAULT_FS, 1686 ex); 1687 } 1688 1689 final String formatName = request.getProperties().get( 1690 PropertyDefinition.Format.name()); 1691 Enumeration.Format format = valueOf(Enumeration.Format.class, formatName, 1692 null); 1693 1694 if (format == Enumeration.Format.Multidimensional) { 1695 return new MDDataSet_Multidimensional(result); 1696 } else { 1697 return new MDDataSet_Tabular(result); 1698 } 1699 } 1700 } 1701 1702 static abstract class MDDataSet implements QueryResult { 1703 protected final Result result; 1704 1705 protected static final String[] cellProps = new String[] { 1706 "Value", 1707 "FmtValue", 1708 "FormatString"}; 1709 1710 protected static final String[] cellPropLongs = new String[] { 1711 Property.VALUE.name, 1712 Property.FORMATTED_VALUE.name, 1713 Property.FORMAT_STRING.name}; 1714 1715 protected static final String[] defaultProps = new String[] { 1716 "UName", 1717 "Caption", 1718 "LName", 1719 "LNum", 1720 "DisplayInfo", 1721 // Not in spec nor generated by SQL Server 1722 // "Depth" 1723 }; 1724 protected static final Map<String, String> longPropNames = new HashMap<String, String>(); 1725 1726 static { 1727 longPropNames.put("UName", Property.MEMBER_UNIQUE_NAME.name); 1728 longPropNames.put("Caption", Property.MEMBER_CAPTION.name); 1729 longPropNames.put("LName", Property.LEVEL_UNIQUE_NAME.name); 1730 longPropNames.put("LNum", Property.LEVEL_NUMBER.name); 1731 longPropNames.put("DisplayInfo", Property.DISPLAY_INFO.name); 1732 } 1733 1734 protected MDDataSet(Result result) { 1735 this.result = result; 1736 } 1737 } 1738 1739 static class MDDataSet_Multidimensional extends MDDataSet { 1740 private List<Hierarchy> slicerAxisHierarchies; 1741 1742 protected MDDataSet_Multidimensional(Result result) { 1743 super(result); 1744 } 1745 1746 public void unparse(SaxWriter writer) throws SAXException { 1747 olapInfo(writer); 1748 axes(writer); 1749 cellData(writer); 1750 } 1751 1752 private void olapInfo(SaxWriter writer) { 1753 // What are all of the cube's hierachies 1754 Cube cube = result.getQuery().getCube(); 1755 List<Dimension> unseenDimensionList = 1756 new ArrayList<Dimension>( 1757 Arrays.asList(cube.getDimensions())); 1758 1759 writer.startElement("OlapInfo"); 1760 writer.startElement("CubeInfo"); 1761 writer.startElement("Cube"); 1762 writer.startElement("CubeName"); 1763 writer.characters(result.getQuery().getCube().getName()); 1764 writer.endElement(); 1765 writer.endElement(); 1766 writer.endElement(); // CubeInfo 1767 1768 // create AxesInfo for axes 1769 // ----------- 1770 writer.startElement("AxesInfo"); 1771 final Axis[] axes = result.getAxes(); 1772 final QueryAxis[] queryAxes = result.getQuery().getAxes(); 1773 //axisInfo(writer, result.getSlicerAxis(), "SlicerAxis"); 1774 List<Hierarchy> axisHierarchyList = new ArrayList<Hierarchy>(); 1775 for (int i = 0; i < axes.length; i++) { 1776 List<Hierarchy> hiers = 1777 axisInfo(writer, axes[i], queryAxes[i], "Axis" + i); 1778 axisHierarchyList.addAll(hiers); 1779 } 1780 // Remove all seen dimensions. 1781 for (Hierarchy hier1 : axisHierarchyList) { 1782 unseenDimensionList.remove(hier1.getDimension()); 1783 } 1784 1785 /////////////////////////////////////////////// 1786 // create AxesInfo for slicer axes 1787 // 1788 // The slicer axes contains the default hierarchy of each dimension 1789 // not seen on another axis. 1790 List<Hierarchy> hierarchies = new ArrayList<Hierarchy>(); 1791 for (Dimension dimension : unseenDimensionList) { 1792 hierarchies.add(dimension.getHierarchy()); 1793 } 1794 writer.startElement("AxisInfo", 1795 new String[] { "name", "SlicerAxis"}); 1796 final QueryAxis slicerAxis = result.getQuery().getSlicerAxis(); 1797 writeHierarchyInfo(writer, hierarchies, getProps(slicerAxis)); 1798 writer.endElement(); // AxisInfo 1799 slicerAxisHierarchies = hierarchies; 1800 // 1801 /////////////////////////////////////////////// 1802 1803 1804 writer.endElement(); // AxesInfo 1805 // ----------- 1806 writer.startElement("CellInfo"); 1807 if (shouldReturnCellProperty(Property.VALUE.getName())) { 1808 writer.element("Value", new String[] { 1809 "name", "VALUE"}); 1810 } 1811 if (shouldReturnCellProperty(Property.FORMATTED_VALUE.getName())) { 1812 writer.element("FmtValue", new String[] { 1813 "name", "FORMATTED_VALUE"}); 1814 } 1815 1816 if (shouldReturnCellProperty(Property.FORMAT_STRING.getName())) { 1817 writer.element("FormatString", new String[] { 1818 "name", "FORMAT_STRING"}); 1819 } 1820 writer.endElement(); // CellInfo 1821 // ----------- 1822 writer.endElement(); // OlapInfo 1823 } 1824 1825 private List<Hierarchy> axisInfo( 1826 SaxWriter writer, 1827 Axis axis, 1828 QueryAxis queryAxis, 1829 String axisName) { 1830 1831 writer.startElement("AxisInfo", new String[] { "name", axisName}); 1832 1833 List<Hierarchy> hierarchies; 1834 Iterator<Position> it = axis.getPositions().iterator(); 1835 if (it.hasNext()) { 1836 final Position position = it.next(); 1837 hierarchies = new ArrayList<Hierarchy>(); 1838 for (Member member : position) { 1839 hierarchies.add(member.getHierarchy()); 1840 } 1841 } else { 1842 hierarchies = Collections.emptyList(); 1843 //final QueryAxis queryAxis = this.result.getQuery().axes[i]; 1844 // TODO: 1845 } 1846 String[] props = getProps(queryAxis); 1847 writeHierarchyInfo(writer, hierarchies, props); 1848 1849 writer.endElement(); // AxisInfo 1850 1851 return hierarchies; 1852 } 1853 1854 1855 private void writeHierarchyInfo( 1856 SaxWriter writer, 1857 List<Hierarchy> hierarchies, 1858 String[] props) { 1859 1860 for (Hierarchy hierarchy : hierarchies) { 1861 writer.startElement( 1862 "HierarchyInfo", new String[]{ 1863 "name", hierarchy.getName() 1864 }); 1865 for (final String prop : props) { 1866 writer.element( 1867 prop, getAttributes(prop, hierarchy)); 1868 } 1869 writer.endElement(); // HierarchyInfo 1870 } 1871 } 1872 1873 private String[] getAttributes(String prop, Hierarchy hierarchy) { 1874 String actualPropName = getPropertyName(prop); 1875 List<String> values = new ArrayList<String>(); 1876 values.add("name"); 1877 values.add(hierarchy.getUniqueName() + "." + 1878 Util.quoteMdxIdentifier(actualPropName)); 1879 if (longPropNames.get(prop) == null) { 1880 //Adding type attribute to the optional properties 1881 values.add("type"); 1882 values.add(getXsdType(actualPropName)); 1883 } 1884 return values.toArray(new String[values.size()]); 1885 } 1886 1887 private String getXsdType(String prop) { 1888 final Property property = Property.lookup(prop, false); 1889 if (property != null) { 1890 Property.Datatype datatype = property.getType(); 1891 switch (datatype) { 1892 case TYPE_NUMERIC: 1893 return RowsetDefinition.Type.UnsignedInteger.columnType; 1894 case TYPE_BOOLEAN: 1895 return RowsetDefinition.Type.Boolean.columnType; 1896 } 1897 } 1898 return RowsetDefinition.Type.String.columnType; 1899 } 1900 1901 private String getPropertyName(String prop) { 1902 String actualPropertyName = longPropNames.get(prop); 1903 if (actualPropertyName == null) { 1904 return prop; 1905 } 1906 return actualPropertyName; 1907 } 1908 1909 private void axes(SaxWriter writer) { 1910 writer.startElement("Axes"); 1911 //axis(writer, result.getSlicerAxis(), "SlicerAxis"); 1912 final Axis[] axes = result.getAxes(); 1913 final QueryAxis[] queryAxes = result.getQuery().getAxes(); 1914 for (int i = 0; i < axes.length; i++) { 1915 final String[] props = getProps(queryAxes[i]); 1916 axis(writer, axes[i], props, "Axis" + i); 1917 } 1918 1919 //////////////////////////////////////////// 1920 // now generate SlicerAxis information 1921 // 1922 List<Hierarchy> hierarchies = slicerAxisHierarchies; 1923 writer.startElement("Axis", new String[] { "name", "SlicerAxis"}); 1924 writer.startElement("Tuples"); 1925 writer.startElement("Tuple"); 1926 1927 Map<String, Integer> memberMap = new HashMap<String, Integer>(); 1928 Member positionMember; 1929 Axis slicerAxis = result.getSlicerAxis(); 1930 if (slicerAxis.getPositions() != null && 1931 slicerAxis.getPositions().size() > 0) { 1932 final Position pos0 = slicerAxis.getPositions().get(0); 1933 int i = 0; 1934 for (Member member : pos0) { 1935 memberMap.put(member.getHierarchy().getName(), i++); 1936 } 1937 } 1938 1939 final QueryAxis slicerQueryAxis = result.getQuery().getSlicerAxis(); 1940 final List<Member> slicerMembers = 1941 result.getSlicerAxis().getPositions().get(0); 1942 for (Hierarchy hierarchy : hierarchies) { 1943 // Find which member is on the slicer. If it's not explicitly 1944 // there, use the default member. 1945 Member member = hierarchy.getDefaultMember(); 1946 final Integer indexPosition = 1947 memberMap.get(hierarchy.getName()); 1948 if (indexPosition != null) { 1949 positionMember = 1950 slicerAxis.getPositions().get(0).get(indexPosition); 1951 } else { 1952 positionMember = null; 1953 } 1954 for (Member slicerMember : slicerMembers) { 1955 if (slicerMember.getHierarchy().equals(hierarchy)) { 1956 member = slicerMember; 1957 break; 1958 } 1959 } 1960 1961 if (member != null) { 1962 if (positionMember != null) { 1963 writeMember( 1964 writer, positionMember, null, 1965 slicerAxis.getPositions().get(0), indexPosition, 1966 getProps(slicerQueryAxis)); 1967 } else { 1968 slicerAxis(writer, member, getProps(slicerQueryAxis)); 1969 } 1970 } else { 1971 LOGGER.warn( 1972 "Can not create SlicerAxis: " + 1973 "null default member for Hierarchy " + 1974 hierarchy.getUniqueName()); 1975 } 1976 } 1977 1978 // 1979 //////////////////////////////////////////// 1980 1981 writer.endElement(); // Tuple 1982 writer.endElement(); // Tuples 1983 writer.endElement(); // Axis 1984 1985 1986 1987 writer.endElement(); // Axes 1988 } 1989 1990 private String[] getProps(QueryAxis queryAxis) { 1991 if (queryAxis == null) { 1992 return defaultProps; 1993 } 1994 Id[] dimensionProperties = queryAxis.getDimensionProperties(); 1995 if (dimensionProperties.length == 0) { 1996 return defaultProps; 1997 } 1998 String[] props = new String[defaultProps.length + dimensionProperties.length]; 1999 System.arraycopy(defaultProps, 0, props, 0, defaultProps.length); 2000 for (int i = 0; i < dimensionProperties.length; i++) { 2001 props[defaultProps.length + i] = 2002 dimensionProperties[i].toStringArray()[0]; 2003 } 2004 return props; 2005 } 2006 2007 private void axis(SaxWriter writer, Axis axis, String[] props, String axisName) { 2008 writer.startElement("Axis", new String[] { "name", axisName}); 2009 writer.startElement("Tuples"); 2010 2011 List<Position> positions = axis.getPositions(); 2012 Iterator<Position> pit = positions.iterator(); 2013 Position prevPosition = null; 2014 Position position = pit.hasNext() ? pit.next() : null; 2015 Position nextPosition = pit.hasNext() ? pit.next() : null; 2016 while (position != null) { 2017 writer.startElement("Tuple"); 2018 int k = 0; 2019 for (Member member : position) { 2020 writeMember( 2021 writer, member, prevPosition, nextPosition, k++, props); 2022 } 2023 writer.endElement(); // Tuple 2024 prevPosition = position; 2025 position = nextPosition; 2026 nextPosition = pit.hasNext() ? pit.next() : null; 2027 } 2028 writer.endElement(); // Tuples 2029 writer.endElement(); // Axis 2030 } 2031 2032 private void writeMember( 2033 SaxWriter writer, 2034 Member member, 2035 Position prevPosition, 2036 Position nextPosition, 2037 int k, 2038 String[] props) 2039 { 2040 writer.startElement("Member", new String[] { 2041 "Hierarchy", member.getHierarchy().getName()}); 2042 for (String prop : props) { 2043 Object value; 2044 String propLong = longPropNames.get(prop); 2045 if (propLong == null) { 2046 propLong = prop; 2047 } 2048 if (propLong.equals(Property.DISPLAY_INFO.name)) { 2049 Integer childrenCard = (Integer) member 2050 .getPropertyValue(Property.CHILDREN_CARDINALITY.name); 2051 value = calculateDisplayInfo(prevPosition, 2052 nextPosition, 2053 member, k, childrenCard); 2054 } else if (propLong.equals(Property.DEPTH.name)) { 2055 value = member.getDepth(); 2056 } else { 2057 value = member.getPropertyValue(propLong); 2058 } 2059 if (value != null) { 2060 writer.startElement(prop); // Properties 2061 writer.characters(value.toString()); 2062 writer.endElement(); // Properties 2063 } 2064 } 2065 writer.endElement(); // Member 2066 } 2067 2068 private void slicerAxis( 2069 SaxWriter writer, Member member, String[] props) { 2070 writer.startElement("Member", new String[] { 2071 "Hierarchy", member.getHierarchy().getName()}); 2072 for (String prop : props) { 2073 Object value; 2074 String propLong = longPropNames.get(prop); 2075 if (propLong == null) { 2076 propLong = prop; 2077 } 2078 if (propLong.equals(Property.DISPLAY_INFO.name)) { 2079 Integer childrenCard = 2080 (Integer) member 2081 .getPropertyValue(Property.CHILDREN_CARDINALITY.name); 2082 // NOTE: don't know if this is correct for 2083 // SlicerAxis 2084 int displayInfo = 0xffff & childrenCard; 2085 /* 2086 int displayInfo = 2087 calculateDisplayInfo((j == 0 ? null : positions[j - 1]), 2088 (j + 1 == positions.length ? null : positions[j + 1]), 2089 member, k, childrenCard.intValue()); 2090 */ 2091 value = displayInfo; 2092 } else if (propLong.equals(Property.DEPTH.name)) { 2093 value = member.getDepth(); 2094 } else { 2095 value = member.getPropertyValue(propLong); 2096 } 2097 if (value != null) { 2098 writer.startElement(prop); // Properties 2099 writer.characters(value.toString()); 2100 writer.endElement(); // Properties 2101 } 2102 } 2103 writer.endElement(); // Member 2104 } 2105 2106 private int calculateDisplayInfo( 2107 Position prevPosition, Position nextPosition, 2108 Member currentMember, int memberOrdinal, int childrenCount) 2109 { 2110 int displayInfo = 0xffff & childrenCount; 2111 2112 if (nextPosition != null) { 2113 String currentUName = currentMember.getUniqueName(); 2114 Member nextMember = nextPosition.get(memberOrdinal); 2115 String nextParentUName = nextMember.getParentUniqueName(); 2116 if (currentUName.equals(nextParentUName)) { 2117 displayInfo |= 0x10000; 2118 } 2119 } 2120 if (prevPosition != null) { 2121 String currentParentUName = currentMember.getParentUniqueName(); 2122 Member prevMember = prevPosition.get(memberOrdinal); 2123 String prevParentUName = prevMember.getParentUniqueName(); 2124 if (currentParentUName != null && 2125 currentParentUName.equals(prevParentUName)) { 2126 displayInfo |= 0x20000; 2127 } 2128 } 2129 return displayInfo; 2130 } 2131 2132 private void cellData(SaxWriter writer) { 2133 writer.startElement("CellData"); 2134 final int axisCount = result.getAxes().length; 2135 int[] pos = new int[axisCount]; 2136 int[] cellOrdinal = new int[] {0}; 2137 2138 Evaluator evaluator = RolapUtil.createEvaluator(result.getQuery()); 2139 int axisOrdinal = axisCount - 1; 2140 recurse(writer, pos, axisOrdinal, evaluator, cellOrdinal); 2141 2142 writer.endElement(); // CellData 2143 } 2144 private void recurse(SaxWriter writer, int[] pos, 2145 int axisOrdinal, Evaluator evaluator, int[] cellOrdinal) { 2146 if (axisOrdinal < 0) { 2147 emitCell(writer, pos, evaluator, cellOrdinal[0]++); 2148 2149 } else { 2150 Axis axis = result.getAxes()[axisOrdinal]; 2151 List<Position> positions = axis.getPositions(); 2152 int i = 0; 2153 for (Position position : positions) { 2154 pos[axisOrdinal] = i; 2155 evaluator.setContext(position); 2156 recurse(writer, pos, axisOrdinal - 1, evaluator, cellOrdinal); 2157 i++; 2158 } 2159 } 2160 } 2161 private void emitCell(SaxWriter writer, int[] pos, 2162 Evaluator evaluator, int ordinal) { 2163 Cell cell = result.getCell(pos); 2164 if (cell.isNull() && ordinal != 0) { 2165 // Ignore null cell like MS AS, except for Oth ordinal 2166 return; 2167 } 2168 2169 writer.startElement("Cell", new String[] { 2170 "CellOrdinal", Integer.toString(ordinal)}); 2171 for (int i = 0; i < cellProps.length; i++) { 2172 String cellPropLong = cellPropLongs[i]; 2173 Object value = cell.getPropertyValue(cellPropLong); 2174 2175 /* 2176 if (value != null && shouldReturnCellProperty(cellPropLong)) { 2177 if (cellPropLong.equals(Property.VALUE.name)) { 2178 String valueType = deduceValueType(evaluator, value); 2179 writer.startElement(cellProps[i], new String[] {"xsi:type", valueType}); 2180 } else { 2181 writer.startElement(cellProps[i]); 2182 } 2183 2184 String valueString = value.toString(); 2185 2186 if (cellPropLong.equals(Property.VALUE.name) && 2187 value instanceof Number) { 2188 valueString = XmlaUtil.normalizeNumericString(valueString); 2189 } 2190 2191 writer.characters(valueString); 2192 writer.endElement(); 2193 } 2194 */ 2195 if (value == null) { 2196 continue; 2197 } 2198 if (! shouldReturnCellProperty(cellPropLong)) { 2199 continue; 2200 } 2201 boolean isDecimal = false; 2202 2203 if (cellPropLong.equals(Property.VALUE.name)) { 2204 if (cell.isNull()) { 2205 // Return cell without value as in case of AS2005 2206 continue; 2207 } 2208 final String dataType = (String) 2209 evaluator.getProperty(Property.DATATYPE.getName(), null); 2210 final ValueInfo vi = new ValueInfo(dataType, value); 2211 final String valueType = vi.valueType; 2212 value = vi.value; 2213 isDecimal = vi.isDecimal; 2214 2215 writer.startElement(cellProps[i], 2216 new String[] {"xsi:type", valueType}); 2217 } else { 2218 writer.startElement(cellProps[i]); 2219 } 2220 String valueString = value.toString(); 2221 2222 if (isDecimal) { 2223 valueString = XmlaUtil.normalizeNumericString(valueString); 2224 } 2225 2226 writer.characters(valueString); 2227 writer.endElement(); 2228 } 2229 writer.endElement(); // Cell 2230 } 2231 2232 private boolean shouldReturnCellProperty(String cellPropLong) { 2233 Query query = result.getQuery(); 2234 return query.isCellPropertyEmpty() || 2235 query.hasCellProperty(cellPropLong); 2236 } 2237 } 2238 2239 static abstract class ColumnHandler { 2240 protected final String name; 2241 protected final String encodedName; 2242 2243 protected ColumnHandler(String name) { 2244 this.name = name; 2245 this.encodedName = XmlaUtil.encodeElementName(this.name); 2246 } 2247 2248 abstract void write(SaxWriter writer, Cell cell, Member[] members); 2249 abstract void metadata(SaxWriter writer); 2250 } 2251 2252 2253 /** 2254 * Callback to handle one column, representing the combination of a 2255 * level and a property (e.g. [Store].[Store State].[MEMBER_UNIQUE_NAME]) 2256 * in a flattened dataset. 2257 */ 2258 static class CellColumnHandler extends ColumnHandler { 2259 2260 CellColumnHandler(String name) { 2261 super(name); 2262 } 2263 2264 public void metadata(SaxWriter writer) { 2265 writer.element("xsd:element", new String[] { 2266 "minOccurs", "0", 2267 "name", encodedName, 2268 "sql:field", name, 2269 }); 2270 } 2271 2272 public void write( 2273 SaxWriter writer, Cell cell, Member[] members) { 2274 if (cell.isNull()) { 2275 return; 2276 } 2277 Object value = cell.getValue(); 2278 /* 2279 String valueString = value.toString(); 2280 String valueType = deduceValueType(cell, value); 2281 2282 writer.startElement(encodedName, new String[] { 2283 "xsi:type", valueType}); 2284 if (value instanceof Number) { 2285 valueString = XmlaUtil.normalizeNumericString(valueString); 2286 } 2287 writer.characters(valueString); 2288 writer.endElement(); 2289 */ 2290 final String dataType = (String) 2291 cell.getPropertyValue(Property.DATATYPE.getName()); 2292 2293 final ValueInfo vi = new ValueInfo(dataType, value); 2294 final String valueType = vi.valueType; 2295 value = vi.value; 2296 boolean isDecimal = vi.isDecimal; 2297 2298 String valueString = value.toString(); 2299 2300 writer.startElement(encodedName, new String[] { 2301 "xsi:type", valueType}); 2302 if (isDecimal) { 2303 valueString = XmlaUtil.normalizeNumericString(valueString); 2304 } 2305 writer.characters(valueString); 2306 writer.endElement(); 2307 } 2308 } 2309 2310 /** 2311 * Callback to handle one column, representing the combination of a 2312 * level and a property (e.g. [Store].[Store State].[MEMBER_UNIQUE_NAME]) 2313 * in a flattened dataset. 2314 */ 2315 static class MemberColumnHandler extends ColumnHandler { 2316 private final String property; 2317 private final Level level; 2318 private final int memberOrdinal; 2319 2320 public MemberColumnHandler( 2321 String property, Level level, int memberOrdinal) { 2322 super(level.getUniqueName() + "." + 2323 Util.quoteMdxIdentifier(property)); 2324 this.property = property; 2325 this.level = level; 2326 this.memberOrdinal = memberOrdinal; 2327 } 2328 2329 public void metadata(SaxWriter writer) { 2330 writer.element("xsd:element", new String[] { 2331 "minOccurs", "0", 2332 "name", encodedName, 2333 "sql:field", name, 2334 "type", XSD_STRING 2335 }); 2336 } 2337 2338 public void write( 2339 SaxWriter writer, Cell cell, Member[] members) { 2340 Member member = members[memberOrdinal]; 2341 final int depth = level.getDepth(); 2342 if (member.getDepth() < depth) { 2343 // This column deals with a level below the current member. 2344 // There is no value to write. 2345 return; 2346 } 2347 while (member.getDepth() > depth) { 2348 member = member.getParentMember(); 2349 } 2350 final Object propertyValue = member.getPropertyValue(property); 2351 if (propertyValue == null) { 2352 return; 2353 } 2354 2355 writer.startElement(encodedName); 2356 writer.characters(propertyValue.toString()); 2357 writer.endElement(); 2358 } 2359 } 2360 2361 static class MDDataSet_Tabular extends MDDataSet { 2362 private final boolean empty; 2363 private final int[] pos; 2364 private final int axisCount; 2365 private int cellOrdinal; 2366 2367 private static final Id[] MemberCaptionIdArray = { 2368 new Id(new Id.Segment(Property.MEMBER_CAPTION.name, Id.Quoting.QUOTED)) 2369 }; 2370 private final Member[] members; 2371 private final ColumnHandler[] columnHandlers; 2372 2373 public MDDataSet_Tabular(Result result) { 2374 super(result); 2375 final Axis[] axes = result.getAxes(); 2376 axisCount = axes.length; 2377 pos = new int[axisCount]; 2378 2379 // Count dimensions, and deduce list of levels which appear on 2380 // non-COLUMNS axes. 2381 boolean empty = false; 2382 int dimensionCount = 0; 2383 for (int i = axes.length - 1; i > 0; i--) { 2384 Axis axis = axes[i]; 2385 if (axis.getPositions().size() == 0) { 2386 // If any axis is empty, the whole data set is empty. 2387 empty = true; 2388 continue; 2389 } 2390 dimensionCount += axis.getPositions().get(0).size(); 2391 } 2392 this.empty = empty; 2393 2394 // Build a list of the lowest level used on each non-COLUMNS axis. 2395 Level[] levels = new Level[dimensionCount]; 2396 List<ColumnHandler> columnHandlerList = new ArrayList<ColumnHandler>(); 2397 int memberOrdinal = 0; 2398 if (!empty) { 2399 for (int i = axes.length - 1; i > 0; i--) { 2400 final Axis axis = axes[i]; 2401 final QueryAxis queryAxis = result.getQuery().getAxes()[i]; 2402 final int z0 = memberOrdinal; // save ordinal so can rewind 2403 final List<Position> positions = axis.getPositions(); 2404 int jj = 0; 2405 for (Position position : positions) { 2406 memberOrdinal = z0; // rewind to start 2407 for (Member member : position) { 2408 if (jj == 0 || 2409 member.getLevel().getDepth() > 2410 levels[memberOrdinal].getDepth()) { 2411 levels[memberOrdinal] = member.getLevel(); 2412 } 2413 memberOrdinal++; 2414 } 2415 jj++; 2416 } 2417 2418 // Now we know the lowest levels on this axis, add 2419 // properties. 2420 Id[] dimProps = queryAxis.getDimensionProperties(); 2421 if (dimProps.length == 0) { 2422 dimProps = MemberCaptionIdArray; 2423 } 2424 for (int j = z0; j < memberOrdinal; j++) { 2425 Level level = levels[j]; 2426 for (int k = 0; k <= level.getDepth(); k++) { 2427 final Level level2 = 2428 level.getHierarchy().getLevels()[k]; 2429 if (level2.isAll()) { 2430 continue; 2431 } 2432 for (Id dimProp : dimProps) { 2433 columnHandlerList.add( 2434 new MemberColumnHandler( 2435 dimProp.toStringArray()[0], 2436 level2, 2437 j)); 2438 } 2439 } 2440 } 2441 } 2442 } 2443 this.members = new Member[memberOrdinal + 1]; 2444 2445 // Deduce the list of column headings. 2446 if (axes.length > 0) { 2447 Axis columnsAxis = axes[0]; 2448 for (Position position : columnsAxis.getPositions()) { 2449 String name = null; 2450 int j = 0; 2451 for (Member member : position) { 2452 if (j == 0) { 2453 name = member.getUniqueName(); 2454 } else { 2455 name = name + "." + member.getUniqueName(); 2456 } 2457 j++; 2458 } 2459 columnHandlerList.add( 2460 new CellColumnHandler(name)); 2461 } 2462 } 2463 2464 this.columnHandlers = 2465 columnHandlerList.toArray( 2466 new ColumnHandler[columnHandlerList.size()]); 2467 } 2468 2469 public void metadata(SaxWriter writer) { 2470 // ADOMD wants a XSD even a void one. 2471 // if (empty) { 2472 // return; 2473 // } 2474 2475 writer.startElement("xsd:schema", new String[] { 2476 "xmlns:xsd", XmlaConstants.NS_XSD, 2477 "targetNamespace", NS_XMLA_ROWSET, 2478 "xmlns", NS_XMLA_ROWSET, 2479 "xmlns:xsi", XmlaConstants.NS_XSI, 2480 "xmlns:sql", NS_XML_SQL, 2481 "elementFormDefault", "qualified" 2482 }); 2483 2484 { // <root> 2485 writer.startElement("xsd:element", new String[] { 2486 "name", "root" 2487 }); 2488 writer.startElement("xsd:complexType"); 2489 writer.startElement("xsd:sequence"); 2490 writer.element("xsd:element", new String[] { 2491 "maxOccurs", "unbounded", 2492 "minOccurs", "0", 2493 "name", "row", 2494 "type", "row", 2495 }); 2496 writer.endElement(); // xsd:sequence 2497 writer.endElement(); // xsd:complexType 2498 writer.endElement(); // xsd:element name=root 2499 } 2500 2501 { // xsd:simpleType name="uuid" 2502 writer.startElement("xsd:simpleType", new String[] { 2503 "name", "uuid", 2504 }); 2505 writer.startElement("xsd:restriction", new String[] { 2506 "base", XSD_STRING 2507 }); 2508 writer.element("xsd:pattern", new String[] { 2509 "value", "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" 2510 }); 2511 writer.endElement(); // xsd:restriction 2512 writer.endElement(); // xsd:simpleType 2513 } 2514 2515 { // xsd:complexType name="row" 2516 writer.startElement("xsd:complexType", new String[] { 2517 "name", "row", 2518 }); 2519 writer.startElement("xsd:sequence"); 2520 for (ColumnHandler columnHandler : columnHandlers) { 2521 columnHandler.metadata(writer); 2522 } 2523 writer.endElement(); // xsd:sequence 2524 writer.endElement(); // xsd:complexType 2525 } 2526 writer.endElement(); // xsd:schema 2527 } 2528 2529 public void unparse(SaxWriter writer) throws SAXException { 2530 if (empty) { 2531 return; 2532 } 2533 cellData(writer); 2534 } 2535 2536 private void cellData(SaxWriter writer) throws SAXException { 2537 cellOrdinal = 0; 2538 iterate(writer); 2539 } 2540 2541 /** 2542 * Iterates over the resust writing tabular rows. 2543 * 2544 * @param writer Writer 2545 * @throws org.xml.sax.SAXException on error 2546 */ 2547 private void iterate(SaxWriter writer) throws SAXException { 2548 switch (axisCount) { 2549 case 0: 2550 // For MDX like: SELECT FROM Sales 2551 emitCell(writer, result.getCell(pos)); 2552 return; 2553 default: 2554 // throw new SAXException("Too many axes: " + axisCount); 2555 iterate(writer, axisCount - 1, 0); 2556 break; 2557 } 2558 } 2559 2560 private void iterate(SaxWriter writer, int axis, final int xxx) { 2561 final List<Position> positions = 2562 result.getAxes()[axis].getPositions(); 2563 int axisLength = axis == 0 ? 1 : positions.size(); 2564 2565 for (int i = 0; i < axisLength; i++) { 2566 final Position position = positions.get(i); 2567 int ho = xxx; 2568 for (int j = 0; 2569 j < position.size() && ho < members.length; 2570 j++, ho++) 2571 { 2572 members[ho] = position.get(j); 2573 } 2574 2575 ++cellOrdinal; 2576 Util.discard(cellOrdinal); 2577 2578 if (axis >= 2) { 2579 iterate(writer, axis - 1, ho); 2580 } else { 2581 2582 writer.startElement("row");//abrimos la fila 2583 pos[axis] = i; //coordenadas: fila i 2584 pos[0] = 0; //coordenadas (0,i): columna 0 2585 for (ColumnHandler columnHandler : columnHandlers) { 2586 if (columnHandler instanceof MemberColumnHandler) { 2587 columnHandler.write(writer, null, members); 2588 } else if (columnHandler instanceof CellColumnHandler) { 2589 columnHandler.write(writer, result.getCell(pos), null); 2590 pos[0]++;// next col. 2591 } 2592 } 2593 writer.endElement();//cerramos la fila 2594 } 2595 } 2596 } 2597 2598 private void emitCell(SaxWriter writer, Cell cell) { 2599 ++cellOrdinal; 2600 Util.discard(cellOrdinal); 2601 2602 // Ignore empty cells. 2603 final Object cellValue = cell.getValue(); 2604 if (cellValue == null) { 2605 return; 2606 } 2607 2608 writer.startElement("row"); 2609 for (ColumnHandler columnHandler : columnHandlers) { 2610 columnHandler.write(writer, cell, members); 2611 } 2612 writer.endElement(); 2613 } 2614 } 2615 2616 private void discover(XmlaRequest request, XmlaResponse response) 2617 throws XmlaException { 2618 2619 final RowsetDefinition rowsetDefinition = 2620 RowsetDefinition.valueOf(request.getRequestType()); 2621 Rowset rowset = rowsetDefinition.getRowset(request, this); 2622 2623 final String formatName = 2624 request.getProperties().get(PropertyDefinition.Format.name()); 2625 Enumeration.Format format = 2626 valueOf( 2627 Enumeration.Format.class, 2628 formatName, 2629 Enumeration.Format.Tabular); 2630 if (format != Enumeration.Format.Tabular) { 2631 throw new XmlaException( 2632 CLIENT_FAULT_FC, 2633 HSB_DISCOVER_FORMAT_CODE, 2634 HSB_DISCOVER_FORMAT_FAULT_FS, 2635 new UnsupportedOperationException("<Format>: only 'Tabular' allowed in Discover method type")); 2636 } 2637 final String contentName = 2638 request.getProperties().get(PropertyDefinition.Content.name()); 2639 // default value is SchemaData 2640 Enumeration.Content content = 2641 valueOf(Enumeration.Content.class, contentName, CONTENT_DEFAULT); 2642 2643 SaxWriter writer = response.getWriter(); 2644 writer.startDocument(); 2645 2646 writer.startElement(prefix + ":DiscoverResponse", new String[] { 2647 "xmlns:" + prefix, NS_XMLA}); 2648 writer.startElement(prefix + ":return"); 2649 writer.startElement("root", new String[] { 2650 "xmlns", NS_XMLA_ROWSET, 2651 "xmlns:xsi", NS_XSI, 2652 "xmlns:xsd", NS_XSD, 2653 "xmlns:EX", NS_XMLA_EX 2654 }); 2655 2656 if ((content == Enumeration.Content.Schema) 2657 || (content == Enumeration.Content.SchemaData)) { 2658 rowset.rowsetDefinition.writeRowsetXmlSchema(writer); 2659 } 2660 2661 try { 2662 if ((content == Enumeration.Content.Data) 2663 || (content == Enumeration.Content.SchemaData)) { 2664 rowset.unparse(response); 2665 } 2666 } catch (XmlaException xex) { 2667 throw xex; 2668 } catch (Throwable t) { 2669 throw new XmlaException( 2670 SERVER_FAULT_FC, 2671 HSB_DISCOVER_UNPARSE_CODE, 2672 HSB_DISCOVER_UNPARSE_FAULT_FS, 2673 t); 2674 2675 } finally { 2676 // keep the tags balanced, even if there's an error 2677 writer.endElement(); 2678 writer.endElement(); 2679 writer.endElement(); 2680 } 2681 2682 writer.endDocument(); 2683 } 2684 2685 /** 2686 * Returns enum constant of the specified enum type with the given name. 2687 * 2688 * @param enumType Enumerated type 2689 * @param name Name of constant 2690 * @param defaultValue Default value if constant is not found 2691 * @return Value, or null if name is null or value does not exist 2692 */ 2693 private <E extends Enum<E>> E valueOf( 2694 Class<E> enumType, 2695 String name, E defaultValue) 2696 { 2697 if (name == null) { 2698 return defaultValue; 2699 } else { 2700 try { 2701 return Enum.valueOf(enumType, name); 2702 } catch (IllegalArgumentException e) { 2703 return defaultValue; 2704 } 2705 } 2706 } 2707 2708 /** 2709 * Gets a Connection given a catalog (and implicitly the catalog's data 2710 * source) and a user role. 2711 * 2712 * @param catalog Catalog 2713 * @param role User role 2714 * @param roleName User role name 2715 * @return Connection 2716 * @throws XmlaException If error occurs 2717 */ 2718 protected Connection getConnection( 2719 final DataSourcesConfig.Catalog catalog, 2720 final Role role, 2721 final String roleName) 2722 throws XmlaException { 2723 DataSourcesConfig.DataSource ds = catalog.getDataSource(); 2724 2725 Util.PropertyList connectProperties = 2726 Util.parseConnectString(catalog.getDataSourceInfo()); 2727 2728 String catalogUrl = catalogLocator.locate(catalog.definition); 2729 2730 if (LOGGER.isDebugEnabled()) { 2731 if (catalogUrl == null) { 2732 LOGGER.debug("XmlaHandler.getConnection: catalogUrl is null"); 2733 } else { 2734 LOGGER.debug("XmlaHandler.getConnection: catalogUrl=" + catalogUrl); 2735 } 2736 } 2737 2738 connectProperties.put( 2739 RolapConnectionProperties.Catalog.name(), catalogUrl); 2740 2741 // Checking access 2742 if (!DataSourcesConfig.DataSource.AUTH_MODE_UNAUTHENTICATED 2743 .equalsIgnoreCase( 2744 ds.getAuthenticationMode()) && 2745 (role == null) && (roleName == null)) 2746 { 2747 throw new XmlaException( 2748 CLIENT_FAULT_FC, 2749 HSB_ACCESS_DENIED_CODE, 2750 HSB_ACCESS_DENIED_FAULT_FS, 2751 new SecurityException( 2752 "Access denied for data source needing authentication")); 2753 } 2754 2755 // Role in request overrides role in connect string, if present. 2756 if (roleName != null) { 2757 connectProperties.put( 2758 RolapConnectionProperties.Role.name(), roleName); 2759 } 2760 2761 RolapConnection conn = (RolapConnection) DriverManager.getConnection( 2762 connectProperties, null); 2763 2764 if (role != null) { 2765 conn.setRole(role); 2766 } 2767 2768 if (LOGGER.isDebugEnabled()) { 2769 if (conn == null) { 2770 LOGGER.debug("XmlaHandler.getConnection: returning connection null"); 2771 } else { 2772 LOGGER.debug("XmlaHandler.getConnection: returning connection not null"); 2773 } 2774 } 2775 return conn; 2776 } 2777 2778 /** 2779 * Returns the DataSource associated with the request property or null if 2780 * one was not specified. 2781 * 2782 * @param request Request 2783 * @return DataSource for this request 2784 * @throws XmlaException If error occurs 2785 */ 2786 public DataSourcesConfig.DataSource getDataSource(XmlaRequest request) 2787 throws XmlaException 2788 { 2789 Map<String, String> properties = request.getProperties(); 2790 final String dataSourceInfo = 2791 properties.get(PropertyDefinition.DataSourceInfo.name()); 2792 if (!dataSourcesMap.containsKey(dataSourceInfo)) { 2793 throw new XmlaException( 2794 CLIENT_FAULT_FC, 2795 HSB_CONNECTION_DATA_SOURCE_CODE, 2796 HSB_CONNECTION_DATA_SOURCE_FAULT_FS, 2797 Util.newError("no data source is configured with name '" + 2798 dataSourceInfo + "'")); 2799 } 2800 if (LOGGER.isDebugEnabled()) { 2801 LOGGER.debug("XmlaHandler.getDataSource: dataSourceInfo=" + 2802 dataSourceInfo); 2803 } 2804 2805 final DataSourcesConfig.DataSource ds = 2806 dataSourcesMap.get(dataSourceInfo); 2807 if (LOGGER.isDebugEnabled()) { 2808 if (ds == null) { 2809 // TODO: this if a failure situation 2810 LOGGER.debug("XmlaHandler.getDataSource: ds is null"); 2811 } else { 2812 LOGGER.debug("XmlaHandler.getDataSource: ds.dataSourceInfo=" + 2813 ds.getDataSourceInfo()); 2814 } 2815 } 2816 return ds; 2817 } 2818 2819 /** 2820 * Get the DataSourcesConfig.Catalog with the given catalog name from the 2821 * DataSource's catalogs if there is a match and otherwise return null. 2822 * 2823 * @param ds DataSource 2824 * @param catalogName Catalog name 2825 * @return DataSourcesConfig.Catalog or null 2826 */ 2827 public DataSourcesConfig.Catalog getCatalog( 2828 DataSourcesConfig.DataSource ds, 2829 String catalogName) 2830 { 2831 DataSourcesConfig.Catalog[] catalogs = ds.catalogs.catalogs; 2832 if (catalogName == null) { 2833 // if there is no catalog name - its optional and there is 2834 // only one, then return it. 2835 if (catalogs.length == 1) { 2836 return catalogs[0]; 2837 } 2838 } else { 2839 for (DataSourcesConfig.Catalog dsCatalog : catalogs) { 2840 if (catalogName.equals(dsCatalog.name)) { 2841 return dsCatalog; 2842 } 2843 } 2844 } 2845 return null; 2846 } 2847 2848 /** 2849 * Get array of DataSourcesConfig.Catalog returning only one entry if the 2850 * catalog was specified as a property in the request or all catalogs 2851 * associated with the Datasource if there was no catalog property. 2852 * 2853 * @param request Request 2854 * @param ds DataSource 2855 * @return Array of DataSourcesConfig.Catalog 2856 */ 2857 public DataSourcesConfig.Catalog[] getCatalogs( 2858 XmlaRequest request, 2859 DataSourcesConfig.DataSource ds) { 2860 2861 Map<String, String> properties = request.getProperties(); 2862 final String catalogName = 2863 properties.get(PropertyDefinition.Catalog.name()); 2864 if (catalogName != null) { 2865 DataSourcesConfig.Catalog dsCatalog = getCatalog(ds, catalogName); 2866 return new DataSourcesConfig.Catalog[] { dsCatalog }; 2867 } else { 2868 // no catalog specified in Properties so return them all 2869 return ds.catalogs.catalogs; 2870 } 2871 } 2872 2873 /** 2874 * Returns the DataSourcesConfig.Catalog associated with the 2875 * catalog name that is part of the request properties or 2876 * null if there is no catalog with that name. 2877 * 2878 * @param request Request 2879 * @param ds DataSource 2880 * @param required Whether to throw an error if catalog name is not 2881 * specified 2882 * 2883 * @return DataSourcesConfig Catalog or null 2884 * @throws XmlaException If error occurs 2885 */ 2886 public DataSourcesConfig.Catalog getCatalog( 2887 XmlaRequest request, 2888 DataSourcesConfig.DataSource ds, 2889 boolean required) 2890 throws XmlaException 2891 { 2892 Map<String, String> properties = request.getProperties(); 2893 final String catalogName = 2894 properties.get(PropertyDefinition.Catalog.name()); 2895 DataSourcesConfig.Catalog dsCatalog = getCatalog(ds, catalogName); 2896 if (dsCatalog == null) { 2897 if (catalogName == null) { 2898 if (required) { 2899 throw new XmlaException( 2900 CLIENT_FAULT_FC, 2901 HSB_CONNECTION_DATA_SOURCE_CODE, 2902 HSB_CONNECTION_DATA_SOURCE_FAULT_FS, 2903 Util.newError("catalog not specified")); 2904 } 2905 return null; 2906 } 2907 throw new XmlaException( 2908 CLIENT_FAULT_FC, 2909 HSB_CONNECTION_DATA_SOURCE_CODE, 2910 HSB_CONNECTION_DATA_SOURCE_FAULT_FS, 2911 Util.newError("no catalog named '" + catalogName + "'")); 2912 } 2913 return dsCatalog; 2914 } 2915 2916 private TabularRowSet executeColumnQuery(XmlaRequest request) 2917 throws XmlaException 2918 { 2919 checkFormat(request); 2920 2921 DataSourcesConfig.DataSource ds = getDataSource(request); 2922 DataSourcesConfig.Catalog dsCatalog = getCatalog(request, ds, true); 2923 String roleName = request.getRoleName(); 2924 Role role = request.getRole(); 2925 2926 final Connection connection = getConnection(dsCatalog, role, roleName); 2927 2928 final String statement = request.getStatement(); 2929 final Query query = connection.parseQuery(statement); 2930 query.setResultStyle(ResultStyle.LIST); 2931 final Result result = connection.execute(query); 2932 Cell dtCell = result.getCell(new int[] {0, 0}); 2933 2934 if (!dtCell.canDrillThrough()) { 2935 throw new XmlaException( 2936 SERVER_FAULT_FC, 2937 HSB_DRILL_THROUGH_NOT_ALLOWED_CODE, 2938 HSB_DRILL_THROUGH_NOT_ALLOWED_FAULT_FS, 2939 Util.newError("Cannot do DillThrough operation on the cell")); 2940 } 2941 2942 final Map<String, String> properties = request.getProperties(); 2943 String dtSql = dtCell.getDrillThroughSQL(true); 2944 2945 int index = dtSql.indexOf("from"); 2946 String whereClause = " " + dtSql.substring(index); 2947 final String fieldNames = properties.get(PropertyDefinition.TableFields.name()); 2948 StringTokenizer st = new StringTokenizer(fieldNames, ","); 2949 drillThruColumnNames.clear(); 2950 while (st.hasMoreTokens()) { 2951 drillThruColumnNames.add(st.nextToken()); 2952 } 2953 2954 // Create Select Clause 2955 StringBuilder buf = new StringBuilder("select "); 2956 int k = -1; 2957 for (String drillThruColumnName : drillThruColumnNames) { 2958 if (++k > 0) { 2959 buf.append(","); 2960 } 2961 buf.append(drillThruColumnName); 2962 } 2963 buf.append(' '); 2964 buf.append(whereClause); 2965 dtSql = buf.toString(); 2966 2967 DataSource dataSource = connection.getDataSource(); 2968 try { 2969 int count = -1; 2970 if (MondrianProperties.instance().EnableTotalCount.booleanValue()) { 2971 String temp = dtSql.toUpperCase(); 2972 int fromOff = temp.indexOf("FROM"); 2973 buf.setLength(0); 2974 buf.append("select count(*) "); 2975 buf.append(dtSql.substring(fromOff)); 2976 2977 String countSql = buf.toString(); 2978 2979 if (LOGGER.isDebugEnabled()) { 2980 LOGGER.debug("Advanced drill through counting sql: " + countSql); 2981 } 2982 SqlStatement smt = 2983 RolapUtil.executeQuery( 2984 dataSource, countSql, -1, 2985 "XmlaHandler.executeColumnQuery", 2986 "Advanced drill-through", 2987 ResultSet.TYPE_SCROLL_INSENSITIVE, 2988 ResultSet.CONCUR_READ_ONLY); 2989 2990 try { 2991 ResultSet rs = smt.getResultSet(); 2992 if (rs.next()) { 2993 count = rs.getInt(1); 2994 ++smt.rowCount; 2995 } 2996 } catch (SQLException e) { 2997 smt.handle(e); 2998 } finally { 2999 smt.close(); 3000 } 3001 } 3002 3003 if (LOGGER.isDebugEnabled()) { 3004 LOGGER.debug("Advanced drill through sql: " + dtSql); 3005 } 3006 SqlStatement stmt = 3007 RolapUtil.executeQuery( 3008 dataSource, dtSql, -1, "XmlaHandler.executeColumnQuery", 3009 "Advanced drill-through", 3010 ResultSet.TYPE_SCROLL_INSENSITIVE, 3011 ResultSet.CONCUR_READ_ONLY); 3012 3013 return new TabularRowSet( 3014 stmt, request.drillThroughMaxRows(), 3015 request.drillThroughFirstRowset(), count, 3016 ResultSet.TYPE_FORWARD_ONLY); 3017 3018 } catch (XmlaException xex) { 3019 throw xex; 3020 } catch (RuntimeException rte) { 3021 throw new XmlaException( 3022 SERVER_FAULT_FC, 3023 HSB_DRILL_THROUGH_SQL_CODE, 3024 HSB_DRILL_THROUGH_SQL_FAULT_FS, 3025 rte); 3026 } 3027 } 3028 3029 public static void main(String[] args) { 3030 for (RowsetDefinition def : RowsetDefinition.values()) { 3031 System.out.print(" " + def.name() + "("); 3032 int k = 0; 3033 for (RowsetDefinition.Column column : def.columnDefinitions) { 3034 if (k++ == 0) { 3035 System.out.println(); 3036 } else { 3037 System.out.println(","); 3038 } 3039 System.out.print(" new MetadataColumn(\"" + column.name + "\")"); 3040 } 3041 System.out.println("),"); 3042 } 3043 } 3044 } 3045 3046 // End XmlaHandler.java