001 /* 002 // $Id: //open/mondrian/src/main/mondrian/tui/XmlaSupport.java#15 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 2005-2008 Julian Hyde and others 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 011 package mondrian.tui; 012 013 import mondrian.spi.CatalogLocator; 014 import mondrian.spi.impl.CatalogLocatorImpl; 015 import mondrian.xmla.DataSourcesConfig; 016 import mondrian.olap.Util; 017 import mondrian.olap.Role; 018 import mondrian.xmla.XmlaConstants; 019 import mondrian.xmla.XmlaHandler; 020 import mondrian.xmla.XmlaRequest; 021 import mondrian.xmla.XmlaResponse; 022 import mondrian.xmla.XmlaServlet; 023 import mondrian.xmla.impl.DefaultXmlaServlet; 024 import mondrian.xmla.impl.DefaultXmlaRequest; 025 import mondrian.xmla.impl.DefaultXmlaResponse; 026 import mondrian.rolap.RolapConnectionProperties; 027 import org.eigenbase.xom.DOMWrapper; 028 import org.eigenbase.xom.Parser; 029 import org.eigenbase.xom.XOMUtil; 030 import org.eigenbase.xom.XOMException; 031 import org.apache.log4j.Logger; 032 import org.w3c.dom.Element; 033 import org.w3c.dom.Node; 034 import org.w3c.dom.Document; 035 import org.xml.sax.SAXException; 036 037 import java.io.ByteArrayOutputStream; 038 import java.io.File; 039 import java.io.FileReader; 040 import java.io.StringReader; 041 import java.io.BufferedReader; 042 import java.io.OutputStream; 043 import java.io.FileOutputStream; 044 import java.io.IOException; 045 import java.util.HashMap; 046 import java.util.Map; 047 import javax.servlet.Servlet; 048 import javax.servlet.ServletException; 049 import javax.xml.parsers.ParserConfigurationException; 050 import javax.xml.transform.TransformerException; 051 import javax.xml.transform.TransformerConfigurationException; 052 053 /** 054 * This files provide support for making XMLA requests and looking at 055 * the responses. 056 * 057 * @author Richard M. Emberson 058 * @version $Id: //open/mondrian/src/main/mondrian/tui/XmlaSupport.java#15 $ 059 */ 060 public class XmlaSupport { 061 private static final Logger LOGGER = Logger.getLogger(XmlaSupport.class); 062 063 public static final String nl = Util.nl; 064 public static final String SOAP_PREFIX = XmlaConstants.SOAP_PREFIX; 065 066 public static final String CATALOG_NAME = "FoodMart"; 067 public static final String DATASOURCE_NAME = "MondrianFoodMart"; 068 public static final String DATASOURCE_DESCRIPTION = 069 "Mondrian FoodMart data source"; 070 public static final String DATASOURCE_INFO = 071 "Provider=Mondrian;DataSource=MondrianFoodMart;"; 072 073 public static final Map<String, String> ENV; 074 075 // Setup the Map used to instantiate XMLA template documents. 076 // Have to see if we need to be able to dynamically change these values. 077 static { 078 ENV = new HashMap<String, String>(); 079 ENV.put("catalog", CATALOG_NAME); 080 ENV.put("datasource", DATASOURCE_INFO); 081 } 082 083 /** 084 * This is a parameterized XSLT. 085 * The parameters are: 086 * "soap" with values "none" or empty 087 * "content" with values "schemadata", "schema", "data" or empty 088 * With these setting one can extract from an XMLA SOAP message 089 * the soap wrapper plus body or simply the body; the complete 090 * body (schema and data), only the schema of the body, only the 091 * data of the body or none of the body 092 * 093 */ 094 public static String getXmlaTransform(String xmlaPrefix) { 095 return 096 "<?xml version='1.0'?>" + 097 "<xsl:stylesheet " + 098 " xmlns:xsl='http://www.w3.org/1999/XSL/Transform' " + 099 " xmlns:xalan='http://xml.apache.org/xslt'" + 100 " xmlns:xsd='http://www.w3.org/2001/XMLSchema'" + 101 " xmlns:ROW='urn:schemas-microsoft-com:xml-analysis:rowset'" + 102 " xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' " + 103 " xmlns:" + xmlaPrefix + "='urn:schemas-microsoft-com:xml-analysis'" + 104 " version='1.0'" + 105 ">" + 106 "<xsl:output method='xml' " + 107 " encoding='UTF-8'" + 108 " indent='yes' " + 109 " xalan:indent-amount='2'/>" + 110 "<xsl:param name='content'/>" + 111 "<xsl:param name='soap'/>" + 112 "<!-- consume '/' and apply -->" + 113 "<xsl:template match='/'>" + 114 " <xsl:apply-templates/>" + 115 "</xsl:template>" + 116 "<!-- copy 'Envelope' unless soap==none --> " + 117 "<xsl:template match='SOAP-ENV:Envelope'> " + 118 " <xsl:choose> " + 119 " <xsl:when test=\"$soap='none'\"> " + 120 " <xsl:apply-templates/> " + 121 " </xsl:when> " + 122 " <xsl:otherwise> " + 123 " <xsl:copy> " + 124 " <xsl:apply-templates select='@*|node()'/> " + 125 " </xsl:copy> " + 126 " </xsl:otherwise> " + 127 " </xsl:choose> " + 128 "</xsl:template> " + 129 "<!-- copy 'Header' unless soap==none --> " + 130 "<xsl:template match='SOAP-ENV:Header'> " + 131 " <xsl:choose> " + 132 " <xsl:when test=\"$soap='none'\"> " + 133 " <xsl:apply-templates/> " + 134 " </xsl:when> " + 135 " <xsl:otherwise> " + 136 " <xsl:copy> " + 137 " <xsl:apply-templates select='@*|node()'/> " + 138 " </xsl:copy> " + 139 " </xsl:otherwise> " + 140 " </xsl:choose> " + 141 "</xsl:template> " + 142 "<!-- copy 'Body' unless soap==none --> " + 143 "<xsl:template match='SOAP-ENV:Body'> " + 144 " <xsl:choose> " + 145 " <xsl:when test=\"$soap='none'\"> " + 146 " <xsl:apply-templates/> " + 147 " </xsl:when> " + 148 " <xsl:otherwise> " + 149 " <xsl:copy> " + 150 " <xsl:apply-templates select='@*|node()'/> " + 151 " </xsl:copy> " + 152 " </xsl:otherwise> " + 153 " </xsl:choose> " + 154 "</xsl:template> " + 155 "<!-- copy 'DiscoverResponse' unless soap==none --> " + 156 "<xsl:template match='" + xmlaPrefix + ":DiscoverResponse'> " + 157 " <xsl:choose> " + 158 " <xsl:when test=\"$soap='none'\"> " + 159 " <xsl:apply-templates/> " + 160 " </xsl:when> " + 161 " <xsl:otherwise> " + 162 " <xsl:copy> " + 163 " <xsl:apply-templates select='@*|node()'/> " + 164 " </xsl:copy> " + 165 " </xsl:otherwise> " + 166 " </xsl:choose> " + 167 "</xsl:template> " + 168 "<!-- copy 'return' unless soap==none --> " + 169 "<xsl:template match='" + xmlaPrefix + ":return'> " + 170 " <xsl:choose> " + 171 " <xsl:when test=\"$soap='none'\"> " + 172 " <xsl:apply-templates/> " + 173 " </xsl:when> " + 174 " <xsl:otherwise> " + 175 " <xsl:copy> " + 176 " <xsl:apply-templates select='@*|node()'/> " + 177 " </xsl:copy> " + 178 " </xsl:otherwise> " + 179 " </xsl:choose> " + 180 "</xsl:template> " + 181 "<!-- copy 'root' unless soap==none --> " + 182 "<xsl:template match='ROW:root'> " + 183 " <xsl:choose> " + 184 " <xsl:when test=\"$soap='none'\"> " + 185 " <xsl:apply-templates/> " + 186 " </xsl:when> " + 187 " <xsl:otherwise> " + 188 " <xsl:copy> " + 189 " <xsl:apply-templates select='@*|node()'/> " + 190 " </xsl:copy> " + 191 " </xsl:otherwise > " + 192 " </xsl:choose> " + 193 "</xsl:template> " + 194 "<!-- copy 'schema' if content==schema or schemadata --> " + 195 "<xsl:template match='xsd:schema'> " + 196 " <xsl:choose> " + 197 " <xsl:when test=\"$content='schemadata'\"> " + 198 " <xsl:copy> " + 199 " <xsl:apply-templates select='@*|node()'/> " + 200 " </xsl:copy> " + 201 " </xsl:when> " + 202 " <xsl:when test=\"$content='schema'\"> " + 203 " <xsl:copy> " + 204 " <xsl:apply-templates select='@*|node()'/> " + 205 " </xsl:copy> " + 206 " </xsl:when> " + 207 " <xsl:otherwise/> " + 208 " </xsl:choose> " + 209 "</xsl:template> " + 210 "<!-- copy 'row' if content==data or schemadata --> " + 211 "<xsl:template match='ROW:row'> " + 212 " <xsl:choose> " + 213 " <xsl:when test=\"$content='schemadata'\"> " + 214 " <xsl:copy> " + 215 " <xsl:apply-templates select='@*|node()'/> " + 216 " </xsl:copy> " + 217 " </xsl:when> " + 218 " <xsl:when test=\"$content='data'\"> " + 219 " <xsl:copy> " + 220 " <xsl:apply-templates select='@*|node()'/> " + 221 " </xsl:copy> " + 222 " </xsl:when> " + 223 " <xsl:otherwise/> " + 224 " </xsl:choose> " + 225 "</xsl:template> " + 226 "<!-- copy everything else --> " + 227 "<xsl:template match='*|@*'> " + 228 " <xsl:copy> " + 229 " <xsl:apply-templates select='@*|node()'/> " + 230 " </xsl:copy> " + 231 "</xsl:template> " + 232 "</xsl:stylesheet>"; 233 } 234 235 /** 236 * This is the prefix used in xpath and transforms for the xmla rowset 237 * namespace "urn:schemas-microsoft-com:xml-analysis:rowset". 238 */ 239 public static final String ROW_SET_PREFIX = "ROW"; 240 241 private static CatalogLocator CATALOG_LOCATOR = null; 242 private static String soapFaultXPath = null; 243 private static String soapHeaderAndBodyXPath = null; 244 private static String soapBodyXPath = null; 245 private static String soapXmlaRootXPath = null; 246 private static String xmlaRootXPath = null; 247 248 249 ///////////////////////////////////////////////////////////////////////// 250 // xmla help 251 ///////////////////////////////////////////////////////////////////////// 252 public static CatalogLocator getCatalogLocator() { 253 if (CATALOG_LOCATOR == null) { 254 CATALOG_LOCATOR = new CatalogLocatorImpl(); 255 } 256 return CATALOG_LOCATOR; 257 } 258 259 public static DataSourcesConfig.DataSources getDataSources( 260 String connectString, 261 Map<String, String> catalogNameUrls) 262 throws XOMException 263 { 264 String str = getDataSourcesText(connectString, catalogNameUrls); 265 StringReader dsConfigReader = new StringReader(str); 266 267 final Parser xmlParser = XOMUtil.createDefaultParser(); 268 final DOMWrapper def = xmlParser.parse(dsConfigReader); 269 270 DataSourcesConfig.DataSources datasources = 271 new DataSourcesConfig.DataSources(def); 272 273 return datasources; 274 } 275 276 /** 277 * With a connection string, generate the DataSource xml. Since this 278 * is used by directly, same process, communicating with XMLA 279 * Mondrian, the fact that the URL contains "localhost" is not 280 * important. 281 * 282 * @param connectString Connect string 283 * @param catalogNameUrls array of catalog names, catalog url pairs 284 */ 285 public static String getDataSourcesText( 286 String connectString, 287 Map<String, String> catalogNameUrls) { 288 StringBuilder buf = new StringBuilder(500); 289 buf.append("<?xml version=\"1.0\"?>"); 290 buf.append(nl); 291 buf.append("<DataSources>"); 292 buf.append(nl); 293 buf.append(" <DataSource>"); 294 buf.append(nl); 295 buf.append(" <DataSourceName>"); 296 buf.append(DATASOURCE_NAME); 297 buf.append("</DataSourceName>"); 298 buf.append(nl); 299 buf.append(" <DataSourceDescription>"); 300 buf.append(DATASOURCE_DESCRIPTION); 301 buf.append("</DataSourceDescription>"); 302 buf.append(nl); 303 buf.append(" <URL>http://localhost:8080/mondrian/xmla</URL>"); 304 buf.append(nl); 305 buf.append(" <DataSourceInfo><![CDATA["); 306 buf.append(connectString); 307 buf.append("]]></DataSourceInfo>"); 308 buf.append(nl); 309 310 buf.append(" <ProviderName>Mondrian</ProviderName>"); 311 buf.append(nl); 312 buf.append(" <ProviderType>MDP</ProviderType>"); 313 buf.append(nl); 314 buf.append(" <AuthenticationMode>Unauthenticated</AuthenticationMode>"); 315 buf.append(nl); 316 buf.append(" <Catalogs>"); 317 buf.append(nl); 318 for (Map.Entry<String,String> catalogNameUrl : catalogNameUrls.entrySet()) { 319 String name = catalogNameUrl.getKey(); 320 String url = catalogNameUrl.getValue(); 321 buf.append(" <Catalog name='"); 322 buf.append(name); 323 buf.append("'><Definition>"); 324 buf.append(url); 325 buf.append("</Definition></Catalog>"); 326 } 327 buf.append(" </Catalogs>"); 328 buf.append(nl); 329 buf.append(" </DataSource>"); 330 buf.append(nl); 331 buf.append("</DataSources>"); 332 buf.append(nl); 333 String datasources = buf.toString(); 334 if (LOGGER.isDebugEnabled()) { 335 LOGGER.debug("XmlaSupport.getDataSources: datasources="+ 336 datasources); 337 } 338 return datasources; 339 } 340 public static String getSoapFaultXPath() { 341 if (XmlaSupport.soapFaultXPath == null) { 342 StringBuilder buf = new StringBuilder(100); 343 buf.append('/'); 344 buf.append(SOAP_PREFIX); 345 buf.append(":Envelope"); 346 buf.append('/'); 347 buf.append(SOAP_PREFIX); 348 buf.append(":Body"); 349 buf.append('/'); 350 buf.append(SOAP_PREFIX); 351 buf.append(":Fault"); 352 buf.append("/*"); 353 String xpath = buf.toString(); 354 XmlaSupport.soapFaultXPath = xpath; 355 356 if (LOGGER.isDebugEnabled()) { 357 LOGGER.debug( 358 "XmlaSupport.getSoapFaultXPath: xpath="+ xpath); 359 } 360 } 361 return XmlaSupport.soapFaultXPath; 362 } 363 364 public static String getSoapHeaderAndBodyXPath() { 365 if (XmlaSupport.soapHeaderAndBodyXPath == null) { 366 StringBuilder buf = new StringBuilder(100); 367 buf.append('/'); 368 buf.append(SOAP_PREFIX); 369 buf.append(":Envelope"); 370 buf.append("/*"); 371 String xpath = buf.toString(); 372 XmlaSupport.soapHeaderAndBodyXPath = xpath; 373 374 if (LOGGER.isDebugEnabled()) { 375 LOGGER.debug( 376 "XmlaSupport.getSoapHeaderAndBodyXPath: xpath="+ xpath); 377 } 378 } 379 return XmlaSupport.soapHeaderAndBodyXPath; 380 } 381 public static String getSoapBodyXPath() { 382 if (XmlaSupport.soapBodyXPath == null) { 383 StringBuilder buf = new StringBuilder(100); 384 buf.append('/'); 385 buf.append(SOAP_PREFIX); 386 buf.append(":Envelope"); 387 buf.append('/'); 388 buf.append(SOAP_PREFIX); 389 buf.append(":Body"); 390 buf.append("/*"); 391 String xpath = buf.toString(); 392 XmlaSupport.soapBodyXPath = xpath; 393 394 if (LOGGER.isDebugEnabled()) { 395 LOGGER.debug("XmlaSupport.getSoapBodyXPath: xpath="+ xpath); 396 } 397 } 398 return XmlaSupport.soapBodyXPath; 399 } 400 401 public static String getSoapXmlaRootXPath(String xmlaPrefix) { 402 if (XmlaSupport.soapXmlaRootXPath == null) { 403 StringBuilder buf = new StringBuilder(20); 404 buf.append('/'); 405 buf.append(SOAP_PREFIX); 406 buf.append(":Envelope"); 407 buf.append('/'); 408 buf.append(SOAP_PREFIX); 409 buf.append(":Body"); 410 buf.append("/").append(xmlaPrefix).append(":DiscoverResponse"); 411 buf.append("/").append(xmlaPrefix).append(":return"); 412 buf.append('/'); 413 buf.append(ROW_SET_PREFIX); 414 buf.append(":root"); 415 buf.append("/*"); 416 String xpath = buf.toString(); 417 XmlaSupport.soapXmlaRootXPath = xpath; 418 419 if (LOGGER.isDebugEnabled()) { 420 LOGGER.debug("XmlaSupport.getSoapXmlaRootXPath: xpath="+ xpath); 421 } 422 } 423 return XmlaSupport.soapXmlaRootXPath; 424 } 425 426 public static String getXmlaRootXPath(String xmlaPrefix) { 427 if (XmlaSupport.xmlaRootXPath == null) { 428 StringBuilder buf = new StringBuilder(20); 429 buf.append("/").append(xmlaPrefix).append(":DiscoverResponse"); 430 buf.append("/").append(xmlaPrefix).append(":return"); 431 buf.append('/'); 432 buf.append(ROW_SET_PREFIX); 433 buf.append(":root"); 434 buf.append("/*"); 435 String xpath = buf.toString(); 436 XmlaSupport.xmlaRootXPath = xpath; 437 438 if (LOGGER.isDebugEnabled()) { 439 LOGGER.debug("XmlaSupport.getXmlaRootXPath: xpath="+ xpath); 440 } 441 } 442 return XmlaSupport.xmlaRootXPath; 443 } 444 445 446 447 public static Node[] extractNodesFromSoapXmla(byte[] bytes) 448 throws SAXException, IOException { 449 Document doc = XmlUtil.parse(bytes); 450 return extractNodesFromSoapXmla(doc); 451 } 452 453 public static Node[] extractNodesFromSoapXmla(Document doc) 454 throws SAXException, IOException { 455 456 final String xmlaPrefix = "xmla"; 457 String xpath = getSoapXmlaRootXPath(xmlaPrefix); 458 459 // Note that this is SOAP 1.1 version uri 460 String[][] nsArray = new String[][] { 461 { SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 }, 462 { xmlaPrefix, XmlaConstants.NS_XMLA }, 463 { ROW_SET_PREFIX, XmlaConstants.NS_XMLA_ROWSET } 464 }; 465 466 return extractNodes(doc, xpath, nsArray); 467 468 } 469 470 public static Node[] extractNodesFromXmla(byte[] bytes) 471 throws SAXException, IOException { 472 Document doc = XmlUtil.parse(bytes); 473 return extractNodesFromXmla(doc); 474 } 475 476 public static Node[] extractNodesFromXmla(Document doc) 477 throws SAXException, IOException { 478 479 final String xmlaPrefix = "xmla"; 480 String xpath = getXmlaRootXPath(xmlaPrefix); 481 482 String[][] nsArray = new String[][] { 483 {xmlaPrefix, XmlaConstants.NS_XMLA }, 484 { ROW_SET_PREFIX, XmlaConstants.NS_XMLA_ROWSET } 485 }; 486 487 return extractNodes(doc, xpath, nsArray); 488 } 489 490 public static Node[] extractFaultNodesFromSoap(byte[] bytes) 491 throws SAXException, IOException { 492 Document doc = XmlUtil.parse(bytes); 493 return extractFaultNodesFromSoap(doc); 494 } 495 public static Node[] extractFaultNodesFromSoap(Document doc) 496 throws SAXException, IOException { 497 String xpath = getSoapFaultXPath(); 498 499 String[][] nsArray = new String[][] { 500 { SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 }, 501 }; 502 503 Node[] nodes = extractNodes(doc, xpath, nsArray); 504 return nodes; 505 } 506 507 public static Node[] extractHeaderAndBodyFromSoap(byte[] bytes) 508 throws SAXException, IOException { 509 Document doc = XmlUtil.parse(bytes); 510 return extractHeaderAndBodyFromSoap(doc); 511 } 512 public static Node[] extractHeaderAndBodyFromSoap(Document doc) 513 throws SAXException, IOException { 514 String xpath = getSoapHeaderAndBodyXPath(); 515 516 String[][] nsArray = new String[][] { 517 { SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 }, 518 }; 519 520 Node[] nodes = extractNodes(doc, xpath, nsArray); 521 return nodes; 522 } 523 public static Document extractBodyFromSoap(Document doc) 524 throws SAXException, IOException { 525 String xpath = getSoapBodyXPath(); 526 527 String[][] nsArray = new String[][] { 528 { SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 }, 529 }; 530 531 Node[] nodes = extractNodes(doc, xpath, nsArray); 532 return (nodes.length == 1) 533 ? XmlUtil.newDocument(nodes[0], true) : null; 534 } 535 536 /** 537 * Given a Document and an xpath/namespace-array pair, extract and return 538 * the Nodes resulting from applying the xpath. 539 * 540 * @throws SAXException 541 * @throws IOException 542 */ 543 public static Node[] extractNodes( 544 Node node, String xpath, String[][] nsArray) 545 throws SAXException, IOException { 546 547 Document contextDoc = XmlUtil.createContextDocument(nsArray); 548 Node[] nodes = XmlUtil.selectAsNodes(node, xpath, contextDoc); 549 550 if (LOGGER.isDebugEnabled()) { 551 StringBuilder buf = new StringBuilder(1024); 552 buf.append("XmlaSupport.extractNodes: "); 553 buf.append("nodes.length="); 554 buf.append(nodes.length); 555 buf.append(nl); 556 for (Node n : nodes) { 557 String str = XmlUtil.toString(n, false); 558 buf.append(str); 559 buf.append(nl); 560 } 561 LOGGER.debug(buf.toString()); 562 } 563 564 return nodes; 565 } 566 ///////////////////////////////////////////////////////////////////////// 567 // soap xmla file 568 ///////////////////////////////////////////////////////////////////////// 569 /** 570 * Process the given input file as a SOAP-XMLA request. 571 * 572 */ 573 public static byte[] processSoapXmla( 574 File file, 575 String connectString, 576 Map<String, String> catalogNameUrls, 577 String cbClassName) 578 throws IOException, ServletException, SAXException 579 { 580 String requestText = XmlaSupport.readFile(file); 581 return processSoapXmla( 582 requestText, connectString, catalogNameUrls, cbClassName); 583 } 584 585 public static byte[] processSoapXmla( 586 Document doc, 587 String connectString, 588 Map<String, String> catalogNameUrls, 589 String cbClassName) 590 throws IOException, ServletException, SAXException 591 { 592 String requestText = XmlUtil.toString(doc, false); 593 return processSoapXmla( 594 requestText, connectString, catalogNameUrls, cbClassName); 595 } 596 597 public static byte[] processSoapXmla( 598 String requestText, 599 String connectString, 600 Map<String, String> catalogNameUrls, 601 String cbClassName) 602 throws IOException, ServletException, SAXException 603 { 604 // read soap file 605 File dsFile = null; 606 try { 607 608 609 // Create datasource file and put datasource xml into it. 610 // Mark it as delete on exit. 611 String dataSourceText = 612 XmlaSupport.getDataSourcesText(connectString, catalogNameUrls); 613 614 dsFile = File.createTempFile("datasources.xml", null); 615 616 OutputStream out = new FileOutputStream(dsFile); 617 out.write(dataSourceText.getBytes()); 618 out.flush(); 619 620 // Glean the role for the request from the connect string. (Ideally, 621 // we would do the reverse: read the role from an HTTP param in the 622 // request, and modify the connect string accordingly.) 623 Util.PropertyList propertyList = 624 Util.parseConnectString(connectString); 625 String roleName = 626 propertyList.get(RolapConnectionProperties.Role.name()); 627 628 byte[] reqBytes = requestText.getBytes(); 629 // make request 630 MockHttpServletRequest req = new MockHttpServletRequest(reqBytes); 631 req.setMethod("POST"); 632 req.setContentType("text/xml"); 633 if (roleName != null) { 634 req.setUserInRole(roleName, true); 635 } 636 637 // make response 638 MockHttpServletResponse res = new MockHttpServletResponse(); 639 res.setCharacterEncoding("UTF-8"); 640 641 // process 642 MockServletContext servletContext = new MockServletContext(); 643 MockServletConfig servletConfig = new MockServletConfig(servletContext); 644 645 servletConfig.addInitParameter(XmlaServlet.PARAM_CALLBACKS, cbClassName); 646 servletConfig.addInitParameter( 647 XmlaServlet.PARAM_CHAR_ENCODING, "UTF-8"); 648 servletConfig.addInitParameter( 649 XmlaServlet.PARAM_DATASOURCES_CONFIG, dsFile.toURL().toString()); 650 651 Servlet servlet = new DefaultXmlaServlet(); 652 servlet.init(servletConfig); 653 servlet.service(req, res); 654 655 return res.toByteArray(); 656 } finally { 657 if (dsFile != null) { 658 dsFile.delete(); 659 } 660 } 661 } 662 663 public static Servlet makeServlet( 664 String connectString, 665 Map<String, String> catalogNameUrls, 666 String cbClassName) 667 throws IOException, ServletException, SAXException 668 { 669 // Create datasource file and put datasource xml into it. 670 // Mark it as delete on exit. 671 String dataSourceText = 672 XmlaSupport.getDataSourcesText(connectString, catalogNameUrls); 673 674 File dsFile = File.createTempFile("datasources.xml", null); 675 676 ////////////////////////////////////////////////////////// 677 // NOTE: this is ok for CmdRunner or JUnit testing but 678 // deleteOnExit is NOT good for production 679 ////////////////////////////////////////////////////////// 680 dsFile.deleteOnExit(); 681 682 OutputStream out = new FileOutputStream(dsFile); 683 out.write(dataSourceText.getBytes()); 684 out.flush(); 685 686 // process 687 MockServletContext servletContext = new MockServletContext(); 688 MockServletConfig servletConfig = new MockServletConfig(servletContext); 689 servletConfig.addInitParameter(XmlaServlet.PARAM_CALLBACKS, cbClassName); 690 servletConfig.addInitParameter( 691 XmlaServlet.PARAM_CHAR_ENCODING, "UTF-8"); 692 servletConfig.addInitParameter( 693 XmlaServlet.PARAM_DATASOURCES_CONFIG, dsFile.toURL().toString()); 694 695 Servlet servlet = new DefaultXmlaServlet(); 696 servlet.init(servletConfig); 697 698 return servlet; 699 } 700 public static byte[] processSoapXmla(File file, Servlet servlet) 701 throws IOException, ServletException, SAXException { 702 703 String requestText = XmlaSupport.readFile(file); 704 return processSoapXmla(requestText, servlet); 705 } 706 public static byte[] processSoapXmla(Document doc, Servlet servlet) 707 throws IOException, ServletException, SAXException { 708 709 String requestText = XmlUtil.toString(doc, false); 710 return processSoapXmla(requestText, servlet); 711 } 712 public static byte[] processSoapXmla(String requestText, Servlet servlet) 713 throws IOException, ServletException, SAXException { 714 715 byte[] reqBytes = requestText.getBytes(); 716 // make request 717 MockHttpServletRequest req = new MockHttpServletRequest(reqBytes); 718 req.setMethod("POST"); 719 req.setContentType("text/xml"); 720 721 // make response 722 MockHttpServletResponse res = new MockHttpServletResponse(); 723 res.setCharacterEncoding("UTF-8"); 724 725 servlet.service(req, res); 726 727 return res.toByteArray(); 728 } 729 730 731 732 /** 733 * Check is a byte array containing a SOAP-XMLA response method is valid. 734 * Schema validation occurs if the XMLA response contains both a content 735 * and schmema section. This includes both the SOAP elements and the 736 * SOAP body content, the XMLA response. 737 * 738 */ 739 public static boolean validateSchemaSoapXmla(byte[] bytes) 740 throws SAXException, IOException, 741 ParserConfigurationException, 742 TransformerException { 743 744 return validateEmbeddedSchema( 745 bytes, 746 XmlUtil.getSoapXmlaXds2xs("xmla"), 747 XmlUtil.getSoapXmlaXds2xd("xmla")); 748 } 749 750 751 ///////////////////////////////////////////////////////////////////////// 752 // xmla file 753 ///////////////////////////////////////////////////////////////////////// 754 755 /** 756 * Processes the given input file as an XMLA request (no SOAP elements). 757 */ 758 public static byte[] processXmla( 759 File file, 760 String connectString, 761 Map<String, String> catalogNameUrls) 762 throws IOException, SAXException, XOMException 763 { 764 return processXmla(file, connectString, catalogNameUrls, null); 765 } 766 public static byte[] processXmla( 767 File file, 768 String connectString, 769 Map<String, String> catalogNameUrls, 770 Role role) 771 throws IOException, SAXException, XOMException 772 { 773 String requestText = XmlaSupport.readFile(file); 774 return processXmla(requestText, connectString, catalogNameUrls, role); 775 } 776 777 public static byte[] processXmla( 778 String requestText, 779 String connectString, 780 Map<String, String> catalogNameUrls) 781 throws IOException, SAXException, XOMException 782 { 783 return processXmla(requestText, connectString, catalogNameUrls, null); 784 } 785 public static byte[] processXmla( 786 String requestText, 787 String connectString, 788 Map<String, String> catalogNameUrls, 789 Role role) 790 throws IOException, SAXException, XOMException 791 { 792 Document requestDoc = XmlUtil.parseString(requestText); 793 return processXmla(requestDoc, connectString, catalogNameUrls, role); 794 } 795 796 public static byte[] processXmla( 797 Document requestDoc, 798 String connectString, 799 Map<String, String> catalogNameUrls) 800 throws IOException, XOMException 801 { 802 Element requestElem = requestDoc.getDocumentElement(); 803 return processXmla(requestElem, connectString, catalogNameUrls, null); 804 } 805 806 public static byte[] processXmla( 807 Document requestDoc, 808 String connectString, 809 Map<String, String> catalogNameUrls, 810 Role role) 811 throws IOException, XOMException 812 { 813 Element requestElem = requestDoc.getDocumentElement(); 814 return processXmla(requestElem, connectString, catalogNameUrls, role); 815 } 816 public static byte[] processXmla( 817 Element requestElem, 818 String connectString, 819 Map<String, String> catalogNameUrls) 820 throws IOException, XOMException 821 { 822 return processXmla(requestElem, connectString, catalogNameUrls, null); 823 } 824 825 public static byte[] processXmla( 826 Element requestElem, 827 String connectString, 828 Map<String, String> catalogNameUrls, 829 Role role) 830 throws IOException, XOMException 831 { 832 // make request 833 CatalogLocator cl = getCatalogLocator(); 834 DataSourcesConfig.DataSources dataSources = 835 getDataSources(connectString, catalogNameUrls); 836 XmlaHandler handler = new XmlaHandler(dataSources, cl, "xmla"); 837 Util.PropertyList propertyList = Util.parseConnectString(connectString); 838 String roleName = 839 propertyList.get(RolapConnectionProperties.Role.name()); 840 841 XmlaRequest request = null; 842 if (role != null) { 843 request = new DefaultXmlaRequest(requestElem, role); 844 } else if (roleName != null) { 845 request = new DefaultXmlaRequest(requestElem, roleName); 846 } else { 847 request = new DefaultXmlaRequest(requestElem); 848 } 849 850 // make response 851 ByteArrayOutputStream resBuf = new ByteArrayOutputStream(); 852 XmlaResponse response = new DefaultXmlaResponse(resBuf, "UTF-8"); 853 854 handler.process(request, response); 855 856 return resBuf.toByteArray(); 857 } 858 859 /** 860 * Check is a byte array containing a XMLA response method is valid. 861 * Schema validation occurs if the XMLA response contains both a content 862 * and schmema section. This should not be used when the byte array 863 * contains both the SOAP elements and content, but only for the content. 864 * 865 */ 866 public static boolean validateSchemaXmla(byte[] bytes) 867 throws SAXException, IOException, 868 ParserConfigurationException, 869 TransformerException { 870 871 return validateEmbeddedSchema(bytes, 872 XmlUtil.getXmlaXds2xs("xmla"), 873 XmlUtil.getXmlaXds2xd("xmla")); 874 } 875 876 ///////////////////////////////////////////////////////////////////////// 877 // helpers 878 ///////////////////////////////////////////////////////////////////////// 879 880 /** 881 * This validates a SOAP-XMLA response using xpaths to extract the 882 * schema and data parts. In addition, it does a little surgery on 883 * the DOMs removing the schema nodes from the XMLA root node. 884 */ 885 public static boolean validateSoapXmlaUsingXpath(byte[] bytes) 886 throws SAXException, IOException { 887 888 if (! XmlUtil.supportsValidation()) { 889 return false; 890 } 891 Node[] nodes = extractNodesFromSoapXmla(bytes); 892 return validateNodes(nodes); 893 } 894 895 /** 896 * This validates a XMLA response using xpaths to extract the 897 * schema and data parts. In addition, it does a little surgery on 898 * the DOMs removing the schema nodes from the XMLA root node. 899 * 900 */ 901 public static boolean validateXmlaUsingXpath(byte[] bytes) 902 throws SAXException, IOException { 903 904 if (! XmlUtil.supportsValidation()) { 905 return false; 906 } 907 Node[] nodes = extractNodesFromXmla(bytes); 908 return validateNodes(nodes); 909 } 910 911 /** 912 * Validate Nodes with throws an error if validation was attempted but 913 * failed, returns true if validation was successful and false if 914 * validation was not tried. 915 * 916 * @return true if validation succeeded, false if validation was not tried 917 */ 918 public static boolean validateNodes(Node[] nodes) 919 throws SAXException, IOException { 920 921 if (! XmlUtil.supportsValidation()) { 922 return false; 923 } 924 if (nodes.length == 0) { 925 // no nodes 926 return false; 927 } else if (nodes.length == 1) { 928 // only data or schema but not both 929 return false; 930 } else if (nodes.length > 2) { 931 // TODO: error 932 return false; 933 } 934 935 Node schemaNode = nodes[0]; 936 Node rowNode = nodes[1]; 937 938 // This is the "root" node that contains both the schemaNode and 939 // the rowNode. 940 Node rootNode = rowNode.getParentNode(); 941 // Remove the schemaNode from the root Node. 942 rootNode.removeChild(schemaNode); 943 944 // Convert nodes to Documents. 945 Document schemaDoc = XmlUtil.newDocument(schemaNode , true); 946 Document dataDoc = XmlUtil.newDocument(rootNode , true); 947 948 String xmlns = XmlUtil.getNamespaceAttributeValue(dataDoc); 949 String schemaLocationPropertyValue = xmlns + ' ' + "xmlschema"; 950 org.xml.sax.EntityResolver resolver = new XmlUtil.Resolver(schemaDoc); 951 XmlUtil.validate(dataDoc, schemaLocationPropertyValue, resolver); 952 953 return true; 954 } 955 956 957 /** 958 * See next method for JavaDoc {@link #validateEmbeddedSchema(org.w3c.dom.Document, String, String)}. 959 * 960 */ 961 public static boolean validateEmbeddedSchema( 962 byte[] bytes, 963 String schemaTransform, 964 String dataTransform) 965 throws SAXException, IOException, 966 ParserConfigurationException, 967 TransformerException, 968 TransformerConfigurationException { 969 970 if (! XmlUtil.supportsValidation()) { 971 return false; 972 } 973 974 Document doc = XmlUtil.parse(bytes); 975 return validateEmbeddedSchema(doc, schemaTransform, dataTransform); 976 } 977 978 /** 979 * A given Document has both content and an embedded schema (where 980 * the schema has a single root node and the content has a single 981 * root node - they are not interwoven). A single xsl transform is 982 * provided to extract the schema part of the Document and another 983 * xsl transform is provided to extract the content part and then 984 * the content is validated against the schema. 985 * <p> 986 * If the content is valid, then nothing happens, but if the content 987 * is not valid an execption is thrown (currently a RuntimeException). 988 * <p> 989 * When Mondrian moves to Java 5 or includes the JAXP 1.3 jar, then 990 * there is a utility in JAXP that does something like this (but allows 991 * for multiple schema/content parts). 992 * 993 */ 994 public static boolean validateEmbeddedSchema( 995 Document doc, 996 String schemaTransform, 997 String dataTransform) 998 throws SAXException, IOException, 999 ParserConfigurationException, 1000 TransformerException, 1001 TransformerConfigurationException { 1002 1003 if (! XmlUtil.supportsValidation()) { 1004 return false; 1005 } 1006 1007 Node dataDoc = XmlUtil.transform(doc, 1008 new BufferedReader(new StringReader(dataTransform))); 1009 if (dataDoc == null) { 1010 LOGGER.debug("XmlaSupport.validateEmbeddedSchema: dataDoc is null"); 1011 return false; 1012 } 1013 if (! dataDoc.hasChildNodes()) { 1014 LOGGER.debug("XmlaSupport.validateEmbeddedSchema: dataDoc has no children"); 1015 return false; 1016 } 1017 String dataStr = XmlUtil.toString(dataDoc, false); 1018 if (LOGGER.isDebugEnabled()) { 1019 LOGGER.debug("XmlaSupport.validateEmbeddedSchema: dataDoc:\n=" + dataStr); 1020 } 1021 if (! (dataDoc instanceof Document)) { 1022 LOGGER.warn("XmlaSupport.validateEmbeddedSchema: dataDoc not Document"); 1023 return false; 1024 } 1025 1026 1027 Node schemaDoc = XmlUtil.transform(doc, 1028 new BufferedReader(new StringReader(schemaTransform))); 1029 if (schemaDoc == null) { 1030 LOGGER.debug("XmlaSupport.validateEmbeddedSchema: schemaDoc is null"); 1031 return false; 1032 } 1033 if (! schemaDoc.hasChildNodes()) { 1034 LOGGER.debug("XmlaSupport.validateEmbeddedSchema: schemaDoc has no children"); 1035 return false; 1036 } 1037 String schemaStr = XmlUtil.toString(schemaDoc, false); 1038 if (LOGGER.isDebugEnabled()) { 1039 LOGGER.debug("XmlaSupport.validateEmbeddedSchema: schemaDoc:\n=" + schemaStr); 1040 } 1041 if (! (schemaDoc instanceof Document)) { 1042 LOGGER.warn("XmlaSupport.validateEmbeddedSchema: schemaDoc not Document"); 1043 return false; 1044 } 1045 1046 1047 String xmlns = XmlUtil.getNamespaceAttributeValue((Document)dataDoc); 1048 String schemaLocationPropertyValue = xmlns + ' ' + "xmlschema"; 1049 org.xml.sax.EntityResolver resolver = new XmlUtil.Resolver(schemaStr); 1050 XmlUtil.validate(dataStr, schemaLocationPropertyValue, resolver); 1051 1052 return true; 1053 } 1054 1055 public static Document transformSoapXmla( 1056 Document doc, String[][] namevalueParameters, String ns) 1057 throws SAXException, IOException, 1058 ParserConfigurationException, 1059 TransformerException 1060 { 1061 Node node = XmlUtil.transform( 1062 doc, 1063 new BufferedReader(new StringReader(getXmlaTransform(ns))), 1064 namevalueParameters); 1065 1066 return (node instanceof Document) ? (Document) node : null; 1067 } 1068 1069 1070 /** 1071 * Reads a file line by line, adds a '\n' after each line and 1072 * returns in a String. 1073 * 1074 */ 1075 public static String readFile(File file) throws IOException { 1076 StringBuilder buf = new StringBuilder(1024); 1077 BufferedReader reader = null; 1078 try { 1079 reader = new BufferedReader(new FileReader(file)); 1080 String line; 1081 while ((line = reader.readLine()) != null) { 1082 buf.append(line); 1083 buf.append('\n'); 1084 } 1085 } finally { 1086 if (reader != null) { 1087 try { 1088 reader.close(); 1089 } catch (Exception ignored) { 1090 } 1091 } 1092 } 1093 1094 return buf.toString(); 1095 } 1096 1097 1098 private XmlaSupport() { 1099 } 1100 } 1101 1102 // End XmlaSupport.java