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) 2005-2012 Pentaho and others 008// All Rights Reserved. 009*/ 010package mondrian.tui; 011 012import mondrian.olap.*; 013import mondrian.olap.Util.ByteMatcher; 014import mondrian.rolap.RolapConnectionProperties; 015import mondrian.server.StringRepositoryContentFinder; 016import mondrian.spi.CatalogLocator; 017import mondrian.spi.impl.CatalogLocatorImpl; 018import mondrian.util.LockBox; 019import mondrian.xmla.*; 020import mondrian.xmla.Enumeration; 021import mondrian.xmla.impl.*; 022 023import org.apache.log4j.Logger; 024 025import org.eigenbase.xom.*; 026import org.eigenbase.xom.Parser; 027 028import org.w3c.dom.*; 029import org.xml.sax.SAXException; 030 031import java.io.*; 032import java.util.*; 033 034import javax.servlet.Servlet; 035import javax.servlet.ServletException; 036import javax.xml.parsers.ParserConfigurationException; 037import javax.xml.transform.TransformerConfigurationException; 038import javax.xml.transform.TransformerException; 039 040/** 041 * Support for making XMLA requests and looking at the responses. 042 * 043 * @author Richard M. Emberson 044 */ 045public class XmlaSupport { 046 private static final Logger LOGGER = Logger.getLogger(XmlaSupport.class); 047 048 public static final String nl = Util.nl; 049 public static final String SOAP_PREFIX = XmlaConstants.SOAP_PREFIX; 050 051 private static final ByteMatcher UTF8_BOM_MATCHER = 052 new ByteMatcher( 053 new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF}); 054 055 private static final ByteMatcher UTF16_LE_BOM_MATCHER = 056 new ByteMatcher( 057 new byte[]{ (byte)0xFF, (byte)0xFE }); 058 059 private static final ByteMatcher UTF16_BE_BOM_MATCHER = 060 new ByteMatcher( 061 new byte[]{ (byte)0xFE, (byte)0xFF }); 062 063 private static final ByteMatcher UTF32_LE_BOM_MATCHER = 064 new ByteMatcher( 065 new byte[]{ (byte)0xFF, (byte)0xFE, (byte)0x00, (byte)0x00 }); 066 067 private static final ByteMatcher UTF32_BE_BOM_MATCHER = 068 new ByteMatcher( 069 new byte[]{ (byte)0x00, (byte)0x00, (byte)0xFE, (byte)0xFF }); 070 071 public static final String CATALOG_NAME = "FoodMart"; 072 public static final String DATASOURCE_NAME = "FoodMart"; 073 public static final String DATASOURCE_DESCRIPTION = 074 "Mondrian FoodMart data source"; 075 public static final String DATASOURCE_INFO = 076 "Provider=Mondrian;DataSource=" + CATALOG_NAME + ";"; 077 078 public static final Map<String, String> ENV; 079 080 // Setup the Map used to instantiate XMLA template documents. 081 // Have to see if we need to be able to dynamically change these values. 082 static { 083 ENV = new HashMap<String, String>(); 084 ENV.put("catalog", CATALOG_NAME); 085 ENV.put("datasource", DATASOURCE_INFO); 086 } 087 088 /** 089 * This is a parameterized XSLT. 090 * The parameters are: 091 * "soap" with values "none" or empty 092 * "content" with values "schemadata", "schema", "data" or empty 093 * With these setting one can extract from an XMLA SOAP message 094 * the soap wrapper plus body or simply the body; the complete 095 * body (schema and data), only the schema of the body, only the 096 * data of the body or none of the body 097 * 098 */ 099 public static String getXmlaTransform(String xmlaPrefix) { 100 return 101 "<?xml version='1.0'?>" 102 + "<xsl:stylesheet " 103 + " xmlns:xsl='http://www.w3.org/1999/XSL/Transform' " 104 + " xmlns:xalan='http://xml.apache.org/xslt'" 105 + " xmlns:xsd='http://www.w3.org/2001/XMLSchema'" 106 + " xmlns:ROW='urn:schemas-microsoft-com:xml-analysis:rowset'" 107 + " xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' " 108 + " xmlns:" + xmlaPrefix + "='urn:schemas-microsoft-com:xml-analysis'" 109 + " version='1.0'" 110 + ">" 111 + "<xsl:output method='xml' " 112 + " encoding='UTF-8'" 113 + " indent='yes' " 114 + " xalan:indent-amount='2'/>" 115 + "<xsl:param name='content'/>" 116 + "<xsl:param name='soap'/>" 117 + "<!-- consume '/' and apply -->" 118 + "<xsl:template match='/'>" 119 + " <xsl:apply-templates/>" 120 + "</xsl:template>" 121 + "<!-- copy 'Envelope' unless soap==none --> " 122 + "<xsl:template match='SOAP-ENV:Envelope'> " 123 + " <xsl:choose> " 124 + " <xsl:when test=\"$soap='none'\"> " 125 + " <xsl:apply-templates/> " 126 + " </xsl:when> " 127 + " <xsl:otherwise> " 128 + " <xsl:copy> " 129 + " <xsl:apply-templates select='@*|node()'/> " 130 + " </xsl:copy> " 131 + " </xsl:otherwise> " 132 + " </xsl:choose> " 133 + "</xsl:template> " 134 + "<!-- copy 'Header' unless soap==none --> " 135 + "<xsl:template match='SOAP-ENV:Header'> " 136 + " <xsl:choose> " 137 + " <xsl:when test=\"$soap='none'\"> " 138 + " <xsl:apply-templates/> " 139 + " </xsl:when> " 140 + " <xsl:otherwise> " 141 + " <xsl:copy> " 142 + " <xsl:apply-templates select='@*|node()'/> " 143 + " </xsl:copy> " 144 + " </xsl:otherwise> " 145 + " </xsl:choose> " 146 + "</xsl:template> " 147 + "<!-- copy 'Body' unless soap==none --> " 148 + "<xsl:template match='SOAP-ENV:Body'> " 149 + " <xsl:choose> " 150 + " <xsl:when test=\"$soap='none'\"> " 151 + " <xsl:apply-templates/> " 152 + " </xsl:when> " 153 + " <xsl:otherwise> " 154 + " <xsl:copy> " 155 + " <xsl:apply-templates select='@*|node()'/> " 156 + " </xsl:copy> " 157 + " </xsl:otherwise> " 158 + " </xsl:choose> " 159 + "</xsl:template> " 160 + "<!-- copy 'DiscoverResponse' unless soap==none --> " 161 + "<xsl:template match='" + xmlaPrefix + ":DiscoverResponse'> " 162 + " <xsl:choose> " 163 + " <xsl:when test=\"$soap='none'\"> " 164 + " <xsl:apply-templates/> " 165 + " </xsl:when> " 166 + " <xsl:otherwise> " 167 + " <xsl:copy> " 168 + " <xsl:apply-templates select='@*|node()'/> " 169 + " </xsl:copy> " 170 + " </xsl:otherwise> " 171 + " </xsl:choose> " 172 + "</xsl:template> " 173 + "<!-- copy 'return' unless soap==none --> " 174 + "<xsl:template match='" + xmlaPrefix + ":return'> " 175 + " <xsl:choose> " 176 + " <xsl:when test=\"$soap='none'\"> " 177 + " <xsl:apply-templates/> " 178 + " </xsl:when> " 179 + " <xsl:otherwise> " 180 + " <xsl:copy> " 181 + " <xsl:apply-templates select='@*|node()'/> " 182 + " </xsl:copy> " 183 + " </xsl:otherwise> " 184 + " </xsl:choose> " 185 + "</xsl:template> " 186 + "<!-- copy 'root' unless soap==none --> " 187 + "<xsl:template match='ROW:root'> " 188 + " <xsl:choose> " 189 + " <xsl:when test=\"$soap='none'\"> " 190 + " <xsl:apply-templates/> " 191 + " </xsl:when> " 192 + " <xsl:otherwise> " 193 + " <xsl:copy> " 194 + " <xsl:apply-templates select='@*|node()'/> " 195 + " </xsl:copy> " 196 + " </xsl:otherwise > " 197 + " </xsl:choose> " 198 + "</xsl:template> " 199 + "<!-- copy 'schema' if content==schema or schemadata --> " 200 + "<xsl:template match='xsd:schema'> " 201 + " <xsl:choose> " 202 + " <xsl:when test=\"$content='schemadata'\"> " 203 + " <xsl:copy> " 204 + " <xsl:apply-templates select='@*|node()'/> " 205 + " </xsl:copy> " 206 + " </xsl:when> " 207 + " <xsl:when test=\"$content='schema'\"> " 208 + " <xsl:copy> " 209 + " <xsl:apply-templates select='@*|node()'/> " 210 + " </xsl:copy> " 211 + " </xsl:when> " 212 + " <xsl:otherwise/> " 213 + " </xsl:choose> " 214 + "</xsl:template> " 215 + "<!-- copy 'row' if content==data or schemadata --> " 216 + "<xsl:template match='ROW:row'> " 217 + " <xsl:choose> " 218 + " <xsl:when test=\"$content='schemadata'\"> " 219 + " <xsl:copy> " 220 + " <xsl:apply-templates select='@*|node()'/> " 221 + " </xsl:copy> " 222 + " </xsl:when> " 223 + " <xsl:when test=\"$content='data'\"> " 224 + " <xsl:copy> " 225 + " <xsl:apply-templates select='@*|node()'/> " 226 + " </xsl:copy> " 227 + " </xsl:when> " 228 + " <xsl:otherwise/> " 229 + " </xsl:choose> " 230 + "</xsl:template> " 231 + "<!-- copy everything else --> " 232 + "<xsl:template match='*|@*'> " 233 + " <xsl:copy> " 234 + " <xsl:apply-templates select='@*|node()'/> " 235 + " </xsl:copy> " 236 + "</xsl:template> " 237 + "</xsl:stylesheet>"; 238 } 239 240 /** 241 * This is the prefix used in xpath and transforms for the xmla rowset 242 * namespace "urn:schemas-microsoft-com:xml-analysis:rowset". 243 */ 244 public static final String ROW_SET_PREFIX = "ROW"; 245 246 private static CatalogLocator CATALOG_LOCATOR = null; 247 private static String soapFaultXPath = null; 248 private static String soapHeaderAndBodyXPath = null; 249 private static String soapBodyXPath = null; 250 private static String soapXmlaRootXPath = null; 251 private static String xmlaRootXPath = null; 252 253 254 ///////////////////////////////////////////////////////////////////////// 255 // xmla help 256 ///////////////////////////////////////////////////////////////////////// 257 public static CatalogLocator getCatalogLocator() { 258 if (CATALOG_LOCATOR == null) { 259 CATALOG_LOCATOR = new CatalogLocatorImpl(); 260 } 261 return CATALOG_LOCATOR; 262 } 263 264 public static DataSourcesConfig.DataSources getDataSources( 265 String connectString, 266 Map<String, String> catalogNameUrls) 267 throws XOMException 268 { 269 String str = getDataSourcesText(connectString, catalogNameUrls); 270 StringReader dsConfigReader = new StringReader(str); 271 272 final Parser xmlParser = XOMUtil.createDefaultParser(); 273 final DOMWrapper def = xmlParser.parse(dsConfigReader); 274 275 return new DataSourcesConfig.DataSources(def); 276 } 277 278 public static DataSourcesConfig.DataSources parseDataSources( 279 String dataSourcesConfigString, 280 Logger logger) 281 { 282 try { 283 if (dataSourcesConfigString == null) { 284 logger.warn("XmlaSupport.parseDataSources: null input"); 285 return null; 286 } 287 dataSourcesConfigString = 288 Util.replaceProperties( 289 dataSourcesConfigString, 290 Util.toMap(System.getProperties())); 291 292 if (logger.isDebugEnabled()) { 293 logger.debug( 294 "XmlaSupport.parseDataSources: dataSources=" 295 + dataSourcesConfigString); 296 } 297 final Parser parser = 298 XOMUtil.createDefaultParser(); 299 final DOMWrapper doc = parser.parse(dataSourcesConfigString); 300 return new DataSourcesConfig.DataSources(doc); 301 } catch (XOMException e) { 302 throw Util.newError( 303 e, 304 "Failed to parse data sources config: " 305 + dataSourcesConfigString); 306 } 307 } 308 309 /** 310 * With a connection string, generate the DataSource xml. Since this 311 * is used by directly, same process, communicating with XMLA 312 * Mondrian, the fact that the URL contains "localhost" is not 313 * important. 314 * 315 * @param connectString Connect string 316 * @param catalogNameUrls array of catalog names, catalog url pairs 317 */ 318 public static String getDataSourcesText( 319 String connectString, 320 Map<String, String> catalogNameUrls) 321 { 322 StringBuilder buf = new StringBuilder(500); 323 if (false) { 324 buf.append("<?xml version=\"1.0\"?>"); 325 buf.append(nl); 326 buf.append("<DataSources>"); 327 buf.append(nl); 328 buf.append(" <DataSource>"); 329 buf.append(nl); 330 buf.append(" <DataSourceName>"); 331 buf.append(DATASOURCE_NAME); 332 buf.append("</DataSourceName>"); 333 buf.append(nl); 334 buf.append(" <DataSourceDescription>"); 335 buf.append(DATASOURCE_DESCRIPTION); 336 buf.append("</DataSourceDescription>"); 337 buf.append(nl); 338 buf.append(" <URL>http://localhost:8080/mondrian/xmla</URL>"); 339 buf.append(nl); 340 buf.append(" <DataSourceInfo><![CDATA["); 341 buf.append(connectString); 342 buf.append("]]></DataSourceInfo>"); 343 buf.append(nl); 344 345 buf.append(" <ProviderName>Mondrian</ProviderName>"); 346 buf.append(nl); 347 buf.append(" <ProviderType>MDP</ProviderType>"); 348 buf.append(nl); 349 buf.append( 350 " <AuthenticationMode>Unauthenticated</AuthenticationMode>"); 351 buf.append(nl); 352 buf.append(" <Catalogs>"); 353 buf.append(nl); 354 for (Map.Entry<String, String> catalogNameUrl 355 : catalogNameUrls.entrySet()) 356 { 357 String name = catalogNameUrl.getKey(); 358 String url = catalogNameUrl.getValue(); 359 buf.append(" <Catalog name='"); 360 buf.append(name); 361 buf.append("'>"); 362 if (url != null) { 363 buf.append("<Definition>"); 364 buf.append(url); 365 buf.append("</Definition>"); 366 } 367 buf.append("</Catalog>"); 368 } 369 buf.append(" </Catalogs>"); 370 buf.append(nl); 371 buf.append(" </DataSource>"); 372 buf.append(nl); 373 buf.append("</DataSources>"); 374 buf.append(nl); 375 } else { 376 buf.append("<?xml version=\"1.0\"?>"); 377 buf.append(nl); 378 buf.append("<DataSources>"); 379 buf.append(nl); 380 buf.append(" <DataSource>"); 381 buf.append(nl); 382 buf.append(" <DataSourceName>"); 383 buf.append(DATASOURCE_NAME); 384 buf.append("</DataSourceName>"); 385 buf.append(nl); 386 buf.append(" <DataSourceDescription>"); 387 buf.append(DATASOURCE_DESCRIPTION); 388 buf.append("</DataSourceDescription>"); 389 buf.append(nl); 390 buf.append(" <URL>http://localhost:8080/mondrian/xmla</URL>"); 391 buf.append(nl); 392 buf.append(" <DataSourceInfo><![CDATA["); 393 buf.append(connectString); 394 buf.append("]]></DataSourceInfo>"); 395 buf.append(nl); 396 397 buf.append(" <ProviderName>Mondrian</ProviderName>"); 398 buf.append(nl); 399 buf.append(" <ProviderType>MDP</ProviderType>"); 400 buf.append(nl); 401 buf.append( 402 " <AuthenticationMode>Unauthenticated</AuthenticationMode>"); 403 buf.append(nl); 404 buf.append(" <Catalogs>"); 405 for (Map.Entry<String, String> catalogNameUrl 406 : catalogNameUrls.entrySet()) 407 { 408 String name = catalogNameUrl.getKey(); 409 String url = catalogNameUrl.getValue(); 410 411 buf.append(nl); 412 buf.append(" <Catalog name='"); 413 buf.append(name); 414 buf.append("'>"); 415 if (url != null) { 416 buf.append("<Definition>"); 417 buf.append(url); 418 buf.append("</Definition>"); 419 } 420 buf.append("</Catalog>"); 421 } 422 423 buf.append(nl); 424 buf.append(" </Catalogs>"); 425 buf.append(nl); 426 buf.append(" </DataSource>"); 427 buf.append(nl); 428 buf.append("</DataSources>"); 429 buf.append(nl); 430 } 431 String datasources = buf.toString(); 432 if (LOGGER.isDebugEnabled()) { 433 LOGGER.debug( 434 "XmlaSupport.getDataSources: datasources=" + datasources); 435 } 436 return datasources; 437 } 438 439 public static String getSoapFaultXPath() { 440 if (XmlaSupport.soapFaultXPath == null) { 441 StringBuilder buf = new StringBuilder(100); 442 buf.append('/'); 443 buf.append(SOAP_PREFIX); 444 buf.append(":Envelope"); 445 buf.append('/'); 446 buf.append(SOAP_PREFIX); 447 buf.append(":Body"); 448 buf.append('/'); 449 buf.append(SOAP_PREFIX); 450 buf.append(":Fault"); 451 buf.append("/*"); 452 String xpath = buf.toString(); 453 XmlaSupport.soapFaultXPath = xpath; 454 455 if (LOGGER.isDebugEnabled()) { 456 LOGGER.debug( 457 "XmlaSupport.getSoapFaultXPath: xpath=" + xpath); 458 } 459 } 460 return XmlaSupport.soapFaultXPath; 461 } 462 463 public static String getSoapHeaderAndBodyXPath() { 464 if (XmlaSupport.soapHeaderAndBodyXPath == null) { 465 StringBuilder buf = new StringBuilder(100); 466 buf.append('/'); 467 buf.append(SOAP_PREFIX); 468 buf.append(":Envelope"); 469 buf.append("/*"); 470 String xpath = buf.toString(); 471 XmlaSupport.soapHeaderAndBodyXPath = xpath; 472 473 if (LOGGER.isDebugEnabled()) { 474 LOGGER.debug( 475 "XmlaSupport.getSoapHeaderAndBodyXPath: xpath=" + xpath); 476 } 477 } 478 return XmlaSupport.soapHeaderAndBodyXPath; 479 } 480 public static String getSoapBodyXPath() { 481 if (XmlaSupport.soapBodyXPath == null) { 482 StringBuilder buf = new StringBuilder(100); 483 buf.append('/'); 484 buf.append(SOAP_PREFIX); 485 buf.append(":Envelope"); 486 buf.append('/'); 487 buf.append(SOAP_PREFIX); 488 buf.append(":Body"); 489 buf.append("/*"); 490 String xpath = buf.toString(); 491 XmlaSupport.soapBodyXPath = xpath; 492 493 if (LOGGER.isDebugEnabled()) { 494 LOGGER.debug("XmlaSupport.getSoapBodyXPath: xpath=" + xpath); 495 } 496 } 497 return XmlaSupport.soapBodyXPath; 498 } 499 500 public static String getSoapXmlaRootXPath(String xmlaPrefix) { 501 if (XmlaSupport.soapXmlaRootXPath == null) { 502 StringBuilder buf = new StringBuilder(20); 503 buf.append('/'); 504 buf.append(SOAP_PREFIX); 505 buf.append(":Envelope"); 506 buf.append('/'); 507 buf.append(SOAP_PREFIX); 508 buf.append(":Body"); 509 buf.append("/").append(xmlaPrefix).append(":DiscoverResponse"); 510 buf.append("/").append(xmlaPrefix).append(":return"); 511 buf.append('/'); 512 buf.append(ROW_SET_PREFIX); 513 buf.append(":root"); 514 buf.append("/*"); 515 String xpath = buf.toString(); 516 XmlaSupport.soapXmlaRootXPath = xpath; 517 518 if (LOGGER.isDebugEnabled()) { 519 LOGGER.debug( 520 "XmlaSupport.getSoapXmlaRootXPath: xpath=" 521 + xpath); 522 } 523 } 524 return XmlaSupport.soapXmlaRootXPath; 525 } 526 527 public static String getXmlaRootXPath(String xmlaPrefix) { 528 if (XmlaSupport.xmlaRootXPath == null) { 529 StringBuilder buf = new StringBuilder(20); 530 buf.append("/").append(xmlaPrefix).append(":DiscoverResponse"); 531 buf.append("/").append(xmlaPrefix).append(":return"); 532 buf.append('/'); 533 buf.append(ROW_SET_PREFIX); 534 buf.append(":root"); 535 buf.append("/*"); 536 String xpath = buf.toString(); 537 XmlaSupport.xmlaRootXPath = xpath; 538 539 if (LOGGER.isDebugEnabled()) { 540 LOGGER.debug("XmlaSupport.getXmlaRootXPath: xpath=" + xpath); 541 } 542 } 543 return XmlaSupport.xmlaRootXPath; 544 } 545 546 public static Node[] extractNodesFromSoapXmla(byte[] bytes) 547 throws SAXException, IOException 548 { 549 Document doc = XmlUtil.parse(bytes); 550 return extractNodesFromSoapXmla(doc); 551 } 552 553 public static Node[] extractNodesFromSoapXmla(Document doc) 554 throws SAXException, IOException 555 { 556 final String xmlaPrefix = "xmla"; 557 String xpath = getSoapXmlaRootXPath(xmlaPrefix); 558 559 // Note that this is SOAP 1.1 version uri 560 String[][] nsArray = new String[][] { 561 { 562 SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 563 }, 564 { 565 xmlaPrefix, XmlaConstants.NS_XMLA 566 }, 567 { 568 ROW_SET_PREFIX, XmlaConstants.NS_XMLA_ROWSET 569 } 570 }; 571 572 return extractNodes(doc, xpath, nsArray); 573 } 574 575 public static Node[] extractNodesFromXmla(byte[] bytes) 576 throws SAXException, IOException 577 { 578 Document doc = XmlUtil.parse(bytes); 579 return extractNodesFromXmla(doc); 580 } 581 582 public static Node[] extractNodesFromXmla(Document doc) 583 throws SAXException, IOException 584 { 585 final String xmlaPrefix = "xmla"; 586 String xpath = getXmlaRootXPath(xmlaPrefix); 587 588 String[][] nsArray = new String[][] { 589 { 590 xmlaPrefix, XmlaConstants.NS_XMLA 591 }, 592 { 593 ROW_SET_PREFIX, XmlaConstants.NS_XMLA_ROWSET 594 } 595 }; 596 597 return extractNodes(doc, xpath, nsArray); 598 } 599 600 public static Node[] extractFaultNodesFromSoap(byte[] bytes) 601 throws SAXException, IOException 602 { 603 Document doc = XmlUtil.parse(bytes); 604 return extractFaultNodesFromSoap(doc); 605 } 606 607 public static Node[] extractFaultNodesFromSoap(Document doc) 608 throws SAXException, IOException 609 { 610 String xpath = getSoapFaultXPath(); 611 612 String[][] nsArray = { 613 { SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 }, 614 }; 615 616 return extractNodes(doc, xpath, nsArray); 617 } 618 619 public static Node[] extractHeaderAndBodyFromSoap(byte[] bytes) 620 throws SAXException, IOException 621 { 622 Document doc = XmlUtil.parse(bytes); 623 return extractHeaderAndBodyFromSoap(doc); 624 } 625 626 public static Node[] extractHeaderAndBodyFromSoap(Document doc) 627 throws SAXException, IOException 628 { 629 String xpath = getSoapHeaderAndBodyXPath(); 630 631 String[][] nsArray = { 632 { SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 }, 633 }; 634 635 return extractNodes(doc, xpath, nsArray); 636 } 637 638 public static Document extractBodyFromSoap(Document doc) 639 throws SAXException, IOException 640 { 641 String xpath = getSoapBodyXPath(); 642 643 String[][] nsArray = new String[][] { 644 { SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 }, 645 }; 646 647 Node[] nodes = extractNodes(doc, xpath, nsArray); 648 return (nodes.length == 1) 649 ? XmlUtil.newDocument(nodes[0], true) : null; 650 } 651 652 /** 653 * Given a Document and an xpath/namespace-array pair, extract and return 654 * the Nodes resulting from applying the xpath. 655 * 656 * @throws SAXException on error 657 * @throws IOException on error 658 */ 659 public static Node[] extractNodes( 660 Node node, 661 String xpath, 662 String[][] nsArray) 663 throws SAXException, IOException 664 { 665 Document contextDoc = XmlUtil.createContextDocument(nsArray); 666 Node[] nodes = XmlUtil.selectAsNodes(node, xpath, contextDoc); 667 668 if (LOGGER.isDebugEnabled()) { 669 StringBuilder buf = new StringBuilder(1024); 670 buf.append("XmlaSupport.extractNodes: "); 671 buf.append("nodes.length="); 672 buf.append(nodes.length); 673 buf.append(nl); 674 for (Node n : nodes) { 675 String str = XmlUtil.toString(n, false); 676 buf.append(str); 677 buf.append(nl); 678 } 679 LOGGER.debug(buf.toString()); 680 } 681 682 return nodes; 683 } 684 ///////////////////////////////////////////////////////////////////////// 685 // soap xmla file 686 ///////////////////////////////////////////////////////////////////////// 687 /** 688 * Process the given input file as a SOAP-XMLA request. 689 * 690 */ 691 public static byte[] processSoapXmla( 692 File file, 693 String connectString, 694 Map<String, String> catalogNameUrls, 695 String cbClassName) 696 throws IOException, ServletException, SAXException 697 { 698 String requestText = XmlaSupport.readFile(file); 699 return 700 processSoapXmla( 701 requestText, connectString, 702 catalogNameUrls, cbClassName, null, null); 703 } 704 705 public static byte[] processSoapXmla( 706 File file, 707 String connectString, 708 Map<String, String> catalogNameUrls, 709 String cbClassName, 710 Map<List<String>, Servlet> servletCache) 711 throws IOException, ServletException, SAXException 712 { 713 String requestText = XmlaSupport.readFile(file); 714 return 715 processSoapXmla( 716 requestText, connectString, catalogNameUrls, cbClassName, 717 null, servletCache); 718 } 719 720 public static byte[] processSoapXmla( 721 Document doc, 722 String connectString, 723 Map<String, String> catalogNameUrls, 724 String cbClassName, 725 Role role) 726 throws IOException, ServletException, SAXException 727 { 728 return processSoapXmla( 729 doc, connectString, catalogNameUrls, cbClassName, role, 730 null); 731 } 732 733 public static byte[] processSoapXmla( 734 Document doc, 735 String connectString, 736 Map<String, String> catalogNameUrls, 737 String cbClassName, 738 Role role, 739 Map<List<String>, Servlet> servletCache) 740 throws IOException, ServletException, SAXException 741 { 742 String requestText = XmlUtil.toString(doc, false); 743 return processSoapXmla( 744 requestText, connectString, catalogNameUrls, cbClassName, role, 745 servletCache); 746 } 747 748 public static byte[] processSoapXmla( 749 String requestText, 750 String connectString, 751 Map<String, String> catalogNameUrls, 752 String cbClassName, 753 Role role) 754 throws IOException, ServletException, SAXException 755 { 756 return 757 processSoapXmla( 758 requestText, connectString, 759 catalogNameUrls, cbClassName, role, null); 760 } 761 762 public static byte[] processSoapXmla( 763 String requestText, 764 String connectString, 765 Map<String, String> catalogNameUrls, 766 String cbClassName, 767 Role role, 768 Map<List<String>, Servlet> servletCache) 769 throws IOException, ServletException, SAXException 770 { 771 // read soap file 772 String dataSourceText = 773 XmlaSupport.getDataSourcesText(connectString, catalogNameUrls); 774 Util.PropertyList propertyList = Util.parseConnectString(connectString); 775 String roleName = 776 propertyList.get(RolapConnectionProperties.Role.name()); 777 778 LockBox.Entry entry = null; 779 if (role != null) { 780 // We happen to know that the lock box is shared between servers. 781 // So we can create any old server; it doesn't need to pertain to 782 // this particular catalog. 783 final MondrianServer server = MondrianServer.forId(null); 784 785 entry = server.getLockBox().register(role); 786 roleName = entry.getMoniker(); 787 } 788 789 byte[] reqBytes = requestText.getBytes(); 790 MockHttpServletRequest req = new MockHttpServletRequest(reqBytes); 791 req.setMethod("POST"); 792 req.setContentType("text/xml"); 793 if (roleName != null) { 794 req.setUserInRole(roleName, true); 795 } 796 MockHttpServletResponse res = new MockHttpServletResponse(); 797 res.setCharacterEncoding("UTF-8"); 798 Servlet servlet = getServlet(cbClassName, dataSourceText, servletCache); 799 servlet.service(req, res); 800 801 // Even though it is not used, it is important that entry is in scope 802 // until after request has returned. Prevents role's lock box entry from 803 // being garbage collected. 804 Util.discard(entry); 805 806 return res.toByteArray(); 807 } 808 809 public static Servlet makeServlet( 810 String connectString, 811 Map<String, String> catalogNameUrls, 812 String cbClassName) 813 throws IOException, ServletException, SAXException 814 { 815 return makeServlet(connectString, catalogNameUrls, cbClassName, null); 816 } 817 818 public static Servlet makeServlet( 819 String connectString, 820 Map<String, String> catalogNameUrls, 821 String cbClassName, 822 Map<List<String>, Servlet> servletCache) 823 throws IOException, ServletException, SAXException 824 { 825 // Create datasource file and put datasource xml into it. 826 // Mark it as delete on exit. 827 String dataSourceText = 828 XmlaSupport.getDataSourcesText(connectString, catalogNameUrls); 829 830 return getServlet(cbClassName, dataSourceText, servletCache); 831 } 832 833 private static Servlet getServlet( 834 String cbClassName, 835 String dataSourceText, 836 Map<List<String>, Servlet> cache) 837 throws ServletException 838 { 839 final List<String> key = 840 Arrays.asList( 841 dataSourceText); 842 Servlet servlet = cache.get(key); 843 if (servlet != null) { 844 return servlet; 845 } 846 MockServletContext servletContext = new MockServletContext(); 847 MockServletConfig servletConfig = new MockServletConfig(servletContext); 848 servletConfig.addInitParameter( 849 XmlaServlet.PARAM_CALLBACKS, cbClassName); 850 servletConfig.addInitParameter( 851 XmlaServlet.PARAM_CHAR_ENCODING, "UTF-8"); 852 servletConfig.addInitParameter( 853 XmlaServlet.PARAM_DATASOURCES_CONFIG, 854 "inline:" + dataSourceText); 855 servlet = new MondrianXmlaServlet(); 856 servlet.init(servletConfig); 857 if (cache != null) { 858 cache.put(key, servlet); 859 } 860 return servlet; 861 } 862 863 public static byte[] processSoapXmla( 864 File file, 865 Servlet servlet) 866 throws IOException, ServletException, SAXException 867 { 868 String requestText = XmlaSupport.readFile(file); 869 return processSoapXmla(requestText, servlet); 870 } 871 872 public static byte[] processSoapXmla( 873 Document doc, 874 Servlet servlet) 875 throws IOException, ServletException, SAXException 876 { 877 String requestText = XmlUtil.toString(doc, false); 878 return processSoapXmla(requestText, servlet); 879 } 880 881 public static byte[] processSoapXmla( 882 String requestText, 883 Servlet servlet) 884 throws IOException, ServletException, SAXException 885 { 886 byte[] reqBytes = requestText.getBytes(); 887 // make request 888 MockHttpServletRequest req = new MockHttpServletRequest(reqBytes); 889 req.setMethod("POST"); 890 req.setContentType("text/xml"); 891 892 // make response 893 MockHttpServletResponse res = new MockHttpServletResponse(); 894 res.setCharacterEncoding("UTF-8"); 895 896 servlet.service(req, res); 897 898 return res.toByteArray(); 899 } 900 901 902 903 /** 904 * Check is a byte array containing a SOAP-XMLA response method is valid. 905 * Schema validation occurs if the XMLA response contains both a content 906 * and schmema section. This includes both the SOAP elements and the 907 * SOAP body content, the XMLA response. 908 * 909 */ 910 public static boolean validateSchemaSoapXmla(byte[] bytes) 911 throws SAXException, IOException, 912 ParserConfigurationException, 913 TransformerException 914 { 915 return validateEmbeddedSchema( 916 bytes, 917 XmlUtil.getSoapXmlaXds2xs("xmla"), 918 XmlUtil.getSoapXmlaXds2xd("xmla")); 919 } 920 921 922 ///////////////////////////////////////////////////////////////////////// 923 // xmla file 924 ///////////////////////////////////////////////////////////////////////// 925 926 /** 927 * Processes the given input file as an XMLA request (no SOAP elements). 928 */ 929 public static byte[] processXmla( 930 File file, 931 String connectString, 932 Map<String, String> catalogNameUrls) 933 throws IOException, SAXException, XOMException 934 { 935 return 936 processXmla( 937 file, connectString, catalogNameUrls, 938 (Map<List<String>, MondrianServer>)null); 939 } 940 941 public static byte[] processXmla( 942 File file, 943 String connectString, 944 Map<String, String> catalogNameUrls, 945 Map<List<String>, MondrianServer> cache) 946 throws IOException, SAXException, XOMException 947 { 948 return 949 processXmla( 950 file, connectString, catalogNameUrls, null, cache); 951 } 952 953 public static byte[] processXmla( 954 File file, 955 String connectString, 956 Map<String, String> catalogNameUrls, 957 Role role) 958 throws IOException, SAXException, XOMException 959 { 960 return 961 processXmla( 962 file, 963 connectString, 964 catalogNameUrls, 965 role, 966 null); 967 } 968 969 public static byte[] processXmla( 970 File file, 971 String connectString, 972 Map<String, String> catalogNameUrls, 973 Role role, 974 Map<List<String>, MondrianServer> cache) 975 throws IOException, SAXException, XOMException 976 { 977 String requestText = XmlaSupport.readFile(file); 978 return processXmla(requestText, connectString, catalogNameUrls, cache); 979 } 980 981 public static byte[] processXmla( 982 String requestText, 983 String connectString, 984 Map<String, String> catalogNameUrls) 985 throws IOException, SAXException, XOMException 986 { 987 return 988 processXmla( 989 requestText, connectString, catalogNameUrls, 990 (Map<List<String>, MondrianServer>)null); 991 } 992 993 public static byte[] processXmla( 994 String requestText, 995 String connectString, 996 Map<String, String> catalogNameUrls, 997 Map<List<String>, MondrianServer> cache) 998 throws IOException, SAXException, XOMException 999 { 1000 Document requestDoc = XmlUtil.parseString(requestText); 1001 return 1002 processXmla( 1003 requestDoc, connectString, catalogNameUrls, null, cache); 1004 } 1005 1006 public static byte[] processXmla( 1007 Document requestDoc, 1008 String connectString, 1009 Map<String, String> catalogNameUrls, 1010 Role role) 1011 throws IOException, XOMException 1012 { 1013 return 1014 processXmla( 1015 requestDoc, connectString, 1016 catalogNameUrls, role, null); 1017 } 1018 1019 public static byte[] processXmla( 1020 Document requestDoc, 1021 String connectString, 1022 Map<String, String> catalogNameUrls, 1023 Role role, 1024 Map<List<String>, MondrianServer> cache) 1025 throws IOException, XOMException 1026 { 1027 Element requestElem = requestDoc.getDocumentElement(); 1028 return 1029 processXmla( 1030 requestElem, connectString, catalogNameUrls, role, cache); 1031 } 1032 1033 public static byte[] processXmla( 1034 Element requestElem, 1035 String connectString, 1036 Map<String, String> catalogNameUrls, 1037 Role role) 1038 throws IOException, XOMException 1039 { 1040 return 1041 processXmla( 1042 requestElem, connectString, 1043 catalogNameUrls, role, null); 1044 } 1045 1046 public static byte[] processXmla( 1047 Element requestElem, 1048 String connectString, 1049 Map<String, String> catalogNameUrls, 1050 Role role, 1051 Map<List<String>, MondrianServer> cache) 1052 throws IOException, XOMException 1053 { 1054 Util.PropertyList propertyList = 1055 Util.parseConnectString(connectString); 1056 final List<String> cacheKey = 1057 Arrays.asList( 1058 propertyList.toString(), 1059 catalogNameUrls.toString()); 1060 MondrianServer server = cache.get(cacheKey); 1061 if (server == null) { 1062 server = MondrianServer.createWithRepository( 1063 new StringRepositoryContentFinder( 1064 getDataSourcesText( 1065 connectString, catalogNameUrls)), 1066 getCatalogLocator()); 1067 } 1068 if (cache != null) { 1069 cache.put( 1070 cacheKey, 1071 server); 1072 } 1073 1074 // make request 1075 final XmlaHandler handler = 1076 new XmlaHandler( 1077 (XmlaHandler.ConnectionFactory) server, 1078 "xmla"); 1079 1080 String roleName = 1081 propertyList.get(RolapConnectionProperties.Role.name()); 1082 1083 LockBox.Entry entry = null; 1084 if (role != null) { 1085 entry = server.getLockBox().register(role); 1086 roleName = entry.getMoniker(); 1087 } 1088 1089 XmlaRequest request = 1090 new DefaultXmlaRequest(requestElem, roleName, null, null, null); 1091 1092 Enumeration.ResponseMimeType responseMimeType = 1093 Enumeration.ResponseMimeType.MAP.get( 1094 request.getProperties().get( 1095 PropertyDefinition.ResponseMimeType.name())); 1096 if (responseMimeType == null) { 1097 responseMimeType = Enumeration.ResponseMimeType.SOAP; 1098 } 1099 1100 // make response 1101 ByteArrayOutputStream resBuf = new ByteArrayOutputStream(); 1102 XmlaResponse response = 1103 new DefaultXmlaResponse(resBuf, "UTF-8", responseMimeType); 1104 1105 handler.process(request, response); 1106 1107 // Even though it is not used, it is important that entry is in scope 1108 // until after request has returned. Prevents role's lock box entry from 1109 // being garbage collected. 1110 Util.discard(entry); 1111 1112 return resBuf.toByteArray(); 1113 } 1114 1115 /** 1116 * Check is a byte array containing a XMLA response method is valid. 1117 * Schema validation occurs if the XMLA response contains both a content 1118 * and schmema section. This should not be used when the byte array 1119 * contains both the SOAP elements and content, but only for the content. 1120 * 1121 */ 1122 public static boolean validateSchemaXmla(byte[] bytes) 1123 throws SAXException, IOException, 1124 ParserConfigurationException, 1125 TransformerException 1126 { 1127 return validateEmbeddedSchema( 1128 bytes, 1129 XmlUtil.getXmlaXds2xs("xmla"), 1130 XmlUtil.getXmlaXds2xd("xmla")); 1131 } 1132 1133 ///////////////////////////////////////////////////////////////////////// 1134 // helpers 1135 ///////////////////////////////////////////////////////////////////////// 1136 1137 /** 1138 * This validates a SOAP-XMLA response using xpaths to extract the 1139 * schema and data parts. In addition, it does a little surgery on 1140 * the DOMs removing the schema nodes from the XMLA root node. 1141 */ 1142 public static boolean validateSoapXmlaUsingXpath(byte[] bytes) 1143 throws SAXException, IOException 1144 { 1145 if (! XmlUtil.supportsValidation()) { 1146 return false; 1147 } 1148 1149 // Remove the UTF BOM for proper validation. 1150 bytes = removeUtfBom(bytes); 1151 1152 Node[] nodes = extractNodesFromSoapXmla(bytes); 1153 return validateNodes(nodes); 1154 } 1155 1156 private static byte[] removeUtfBom(byte[] s) { 1157 byte[] response = removeUtfBom(s, UTF8_BOM_MATCHER); 1158 if (response != null) { 1159 return response; 1160 } 1161 response = removeUtfBom(s, UTF16_BE_BOM_MATCHER); 1162 if (response != null) { 1163 return response; 1164 } 1165 response = removeUtfBom(s, UTF16_LE_BOM_MATCHER); 1166 if (response != null) { 1167 return response; 1168 } 1169 response = removeUtfBom(s, UTF32_BE_BOM_MATCHER); 1170 if (response != null) { 1171 return response; 1172 } 1173 response = removeUtfBom(s, UTF32_LE_BOM_MATCHER); 1174 if (response != null) { 1175 return response; 1176 } 1177 return s; 1178 } 1179 1180 private static byte[] removeUtfBom(byte[] s, ByteMatcher matcher) { 1181 byte[] firstBytes = new byte[matcher.key.length]; 1182 System.arraycopy(s, 0, firstBytes, 0, matcher.key.length); 1183 if (s.length >= matcher.key.length 1184 && matcher.match(firstBytes) == 0) 1185 { 1186 byte[] result = new byte[s.length - matcher.key.length]; 1187 System.arraycopy(s, 0, result, 0, s.length - matcher.key.length); 1188 return result; 1189 } 1190 return null; 1191 } 1192 1193 /** 1194 * This validates a XMLA response using xpaths to extract the 1195 * schema and data parts. In addition, it does a little surgery on 1196 * the DOMs removing the schema nodes from the XMLA root node. 1197 * 1198 */ 1199 public static boolean validateXmlaUsingXpath(byte[] bytes) 1200 throws SAXException, IOException 1201 { 1202 if (! XmlUtil.supportsValidation()) { 1203 return false; 1204 } 1205 1206 // Remove the UTF BOM for proper validation. 1207 bytes = removeUtfBom(bytes); 1208 1209 Node[] nodes = extractNodesFromXmla(bytes); 1210 return validateNodes(nodes); 1211 } 1212 1213 /** 1214 * Validate Nodes with throws an error if validation was attempted but 1215 * failed, returns true if validation was successful and false if 1216 * validation was not tried. 1217 * 1218 * @return true if validation succeeded, false if validation was not tried 1219 */ 1220 public static boolean validateNodes(Node[] nodes) 1221 throws SAXException, IOException 1222 { 1223 if (! XmlUtil.supportsValidation()) { 1224 return false; 1225 } 1226 if (nodes.length == 0) { 1227 // no nodes 1228 return false; 1229 } else if (nodes.length == 1) { 1230 // only data or schema but not both 1231 return false; 1232 } else if (nodes.length > 2) { 1233 // TODO: error 1234 return false; 1235 } 1236 1237 Node schemaNode = nodes[0]; 1238 Node rowNode = nodes[1]; 1239 1240 // This is the "root" node that contains both the schemaNode and 1241 // the rowNode. 1242 Node rootNode = rowNode.getParentNode(); 1243 // Remove the schemaNode from the root Node. 1244 rootNode.removeChild(schemaNode); 1245 1246 // Convert nodes to Documents. 1247 Document schemaDoc = XmlUtil.newDocument(schemaNode, true); 1248 Document dataDoc = XmlUtil.newDocument(rootNode, true); 1249 1250 String xmlns = XmlUtil.getNamespaceAttributeValue(dataDoc); 1251 String schemaLocationPropertyValue = xmlns + ' ' + "xmlschema"; 1252 org.xml.sax.EntityResolver resolver = new XmlUtil.Resolver(schemaDoc); 1253 XmlUtil.validate(dataDoc, schemaLocationPropertyValue, resolver); 1254 1255 return true; 1256 } 1257 1258 1259 /** 1260 * See next method for JavaDoc {@link #validateEmbeddedSchema(org.w3c.dom.Document, String, String)}. 1261 * 1262 */ 1263 public static boolean validateEmbeddedSchema( 1264 byte[] bytes, 1265 String schemaTransform, 1266 String dataTransform) 1267 throws SAXException, IOException, 1268 ParserConfigurationException, 1269 TransformerException, 1270 TransformerConfigurationException 1271 { 1272 if (! XmlUtil.supportsValidation()) { 1273 return false; 1274 } 1275 1276 Document doc = XmlUtil.parse(bytes); 1277 return validateEmbeddedSchema(doc, schemaTransform, dataTransform); 1278 } 1279 1280 /** 1281 * A given Document has both content and an embedded schema (where 1282 * the schema has a single root node and the content has a single 1283 * root node - they are not interwoven). A single xsl transform is 1284 * provided to extract the schema part of the Document and another 1285 * xsl transform is provided to extract the content part and then 1286 * the content is validated against the schema. 1287 * <p> 1288 * If the content is valid, then nothing happens, but if the content 1289 * is not valid an execption is thrown (currently a RuntimeException). 1290 * <p> 1291 * When Mondrian moves to Java 5 or includes the JAXP 1.3 jar, then 1292 * there is a utility in JAXP that does something like this (but allows 1293 * for multiple schema/content parts). 1294 * 1295 */ 1296 public static boolean validateEmbeddedSchema( 1297 Document doc, 1298 String schemaTransform, 1299 String dataTransform) 1300 throws SAXException, IOException, 1301 ParserConfigurationException, 1302 TransformerException, 1303 TransformerConfigurationException 1304 { 1305 if (! XmlUtil.supportsValidation()) { 1306 return false; 1307 } 1308 1309 Node dataDoc = XmlUtil.transform( 1310 doc, 1311 new BufferedReader(new StringReader(dataTransform))); 1312 if (dataDoc == null) { 1313 LOGGER.debug("XmlaSupport.validateEmbeddedSchema: dataDoc is null"); 1314 return false; 1315 } 1316 if (! dataDoc.hasChildNodes()) { 1317 LOGGER.debug( 1318 "XmlaSupport.validateEmbeddedSchema: dataDoc has no children"); 1319 return false; 1320 } 1321 String dataStr = XmlUtil.toString(dataDoc, false); 1322 if (LOGGER.isDebugEnabled()) { 1323 LOGGER.debug( 1324 "XmlaSupport.validateEmbeddedSchema: dataDoc:\n=" + dataStr); 1325 } 1326 if (! (dataDoc instanceof Document)) { 1327 LOGGER.warn( 1328 "XmlaSupport.validateEmbeddedSchema: dataDoc not Document"); 1329 return false; 1330 } 1331 1332 1333 Node schemaDoc = XmlUtil.transform( 1334 doc, 1335 new BufferedReader(new StringReader(schemaTransform))); 1336 if (schemaDoc == null) { 1337 LOGGER.debug( 1338 "XmlaSupport.validateEmbeddedSchema: schemaDoc is null"); 1339 return false; 1340 } 1341 if (! schemaDoc.hasChildNodes()) { 1342 LOGGER.debug( 1343 "XmlaSupport.validateEmbeddedSchema: " 1344 + "schemaDoc has no children"); 1345 return false; 1346 } 1347 String schemaStr = XmlUtil.toString(schemaDoc, false); 1348 if (LOGGER.isDebugEnabled()) { 1349 LOGGER.debug( 1350 "XmlaSupport.validateEmbeddedSchema: schemaDoc:\n=" 1351 + schemaStr); 1352 } 1353 if (! (schemaDoc instanceof Document)) { 1354 LOGGER.warn( 1355 "XmlaSupport.validateEmbeddedSchema: schemaDoc not Document"); 1356 return false; 1357 } 1358 1359 1360 String xmlns = XmlUtil.getNamespaceAttributeValue((Document)dataDoc); 1361 String schemaLocationPropertyValue = xmlns + ' ' + "xmlschema"; 1362 org.xml.sax.EntityResolver resolver = new XmlUtil.Resolver(schemaStr); 1363 XmlUtil.validate(dataStr, schemaLocationPropertyValue, resolver); 1364 1365 return true; 1366 } 1367 1368 public static Document transformSoapXmla( 1369 Document doc, String[][] namevalueParameters, String ns) 1370 throws SAXException, IOException, 1371 ParserConfigurationException, 1372 TransformerException 1373 { 1374 Node node = XmlUtil.transform( 1375 doc, 1376 new BufferedReader(new StringReader(getXmlaTransform(ns))), 1377 namevalueParameters); 1378 1379 return (node instanceof Document) ? (Document) node : null; 1380 } 1381 1382 1383 /** 1384 * Reads a file line by line, adds a '\n' after each line and 1385 * returns in a String. 1386 * 1387 */ 1388 public static String readFile(File file) throws IOException { 1389 StringBuilder buf = new StringBuilder(1024); 1390 BufferedReader reader = null; 1391 try { 1392 reader = new BufferedReader(new FileReader(file)); 1393 String line; 1394 while ((line = reader.readLine()) != null) { 1395 buf.append(line); 1396 buf.append('\n'); 1397 } 1398 } finally { 1399 if (reader != null) { 1400 try { 1401 reader.close(); 1402 } catch (Exception ignored) { 1403 } 1404 } 1405 } 1406 1407 return buf.toString(); 1408 } 1409 1410 1411 private XmlaSupport() { 1412 } 1413} 1414 1415// End XmlaSupport.java