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