/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package mondrian.tui;
import mondrian.olap.*;
import mondrian.olap.Util.ByteMatcher;
import mondrian.rolap.RolapConnectionProperties;
import mondrian.server.StringRepositoryContentFinder;
import mondrian.spi.CatalogLocator;
import mondrian.spi.impl.CatalogLocatorImpl;
import mondrian.util.LockBox;
import mondrian.xmla.*;
import mondrian.xmla.Enumeration;
import mondrian.xmla.impl.*;
import org.apache.log4j.Logger;
import org.eigenbase.xom.*;
import org.eigenbase.xom.Parser;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
import java.util.*;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
/**
* Support for making XMLA requests and looking at the responses.
*
* @author Richard M. Emberson
*/
public class XmlaSupport {
private static final Logger LOGGER = Logger.getLogger(XmlaSupport.class);
public static final String nl = Util.nl;
public static final String SOAP_PREFIX = XmlaConstants.SOAP_PREFIX;
private static final ByteMatcher UTF8_BOM_MATCHER =
new ByteMatcher(
new byte[]{(byte)0xEF, (byte)0xBB, (byte)0xBF});
private static final ByteMatcher UTF16_LE_BOM_MATCHER =
new ByteMatcher(
new byte[]{ (byte)0xFF, (byte)0xFE });
private static final ByteMatcher UTF16_BE_BOM_MATCHER =
new ByteMatcher(
new byte[]{ (byte)0xFE, (byte)0xFF });
private static final ByteMatcher UTF32_LE_BOM_MATCHER =
new ByteMatcher(
new byte[]{ (byte)0xFF, (byte)0xFE, (byte)0x00, (byte)0x00 });
private static final ByteMatcher UTF32_BE_BOM_MATCHER =
new ByteMatcher(
new byte[]{ (byte)0x00, (byte)0x00, (byte)0xFE, (byte)0xFF });
public static final String CATALOG_NAME = "FoodMart";
public static final String DATASOURCE_NAME = "FoodMart";
public static final String DATASOURCE_DESCRIPTION =
"Mondrian FoodMart data source";
public static final String DATASOURCE_INFO =
"Provider=Mondrian;DataSource=" + CATALOG_NAME + ";";
public static final Map<String, String> ENV;
// Setup the Map used to instantiate XMLA template documents.
// Have to see if we need to be able to dynamically change these values.
static {
ENV = new HashMap<String, String>();
ENV.put("catalog", CATALOG_NAME);
ENV.put("datasource", DATASOURCE_INFO);
}
/**
* This is a parameterized XSLT.
* The parameters are:
* "soap" with values "none" or empty
* "content" with values "schemadata", "schema", "data" or empty
* With these setting one can extract from an XMLA SOAP message
* the soap wrapper plus body or simply the body; the complete
* body (schema and data), only the schema of the body, only the
* data of the body or none of the body
*
*/
public static String getXmlaTransform(String xmlaPrefix) {
return
"<?xml version='1.0'?>"
+ "<xsl:stylesheet "
+ " xmlns:xsl='http://www.w3.org/1999/XSL/Transform' "
+ " xmlns:xalan='http://xml.apache.org/xslt'"
+ " xmlns:xsd='http://www.w3.org/2001/XMLSchema'"
+ " xmlns:ROW='urn:schemas-microsoft-com:xml-analysis:rowset'"
+ " xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' "
+ " xmlns:" + xmlaPrefix + "='urn:schemas-microsoft-com:xml-analysis'"
+ " version='1.0'"
+ ">"
+ "<xsl:output method='xml' "
+ " encoding='UTF-8'"
+ " indent='yes' "
+ " xalan:indent-amount='2'/>"
+ "<xsl:param name='content'/>"
+ "<xsl:param name='soap'/>"
+ "<!-- consume '/' and apply -->"
+ "<xsl:template match='/'>"
+ " <xsl:apply-templates/>"
+ "</xsl:template>"
+ "<!-- copy 'Envelope' unless soap==none --> "
+ "<xsl:template match='SOAP-ENV:Envelope'> "
+ " <xsl:choose> "
+ " <xsl:when test=\"$soap='none'\"> "
+ " <xsl:apply-templates/> "
+ " </xsl:when> "
+ " <xsl:otherwise> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:otherwise> "
+ " </xsl:choose> "
+ "</xsl:template> "
+ "<!-- copy 'Header' unless soap==none --> "
+ "<xsl:template match='SOAP-ENV:Header'> "
+ " <xsl:choose> "
+ " <xsl:when test=\"$soap='none'\"> "
+ " <xsl:apply-templates/> "
+ " </xsl:when> "
+ " <xsl:otherwise> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:otherwise> "
+ " </xsl:choose> "
+ "</xsl:template> "
+ "<!-- copy 'Body' unless soap==none --> "
+ "<xsl:template match='SOAP-ENV:Body'> "
+ " <xsl:choose> "
+ " <xsl:when test=\"$soap='none'\"> "
+ " <xsl:apply-templates/> "
+ " </xsl:when> "
+ " <xsl:otherwise> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:otherwise> "
+ " </xsl:choose> "
+ "</xsl:template> "
+ "<!-- copy 'DiscoverResponse' unless soap==none --> "
+ "<xsl:template match='" + xmlaPrefix + ":DiscoverResponse'> "
+ " <xsl:choose> "
+ " <xsl:when test=\"$soap='none'\"> "
+ " <xsl:apply-templates/> "
+ " </xsl:when> "
+ " <xsl:otherwise> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:otherwise> "
+ " </xsl:choose> "
+ "</xsl:template> "
+ "<!-- copy 'return' unless soap==none --> "
+ "<xsl:template match='" + xmlaPrefix + ":return'> "
+ " <xsl:choose> "
+ " <xsl:when test=\"$soap='none'\"> "
+ " <xsl:apply-templates/> "
+ " </xsl:when> "
+ " <xsl:otherwise> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:otherwise> "
+ " </xsl:choose> "
+ "</xsl:template> "
+ "<!-- copy 'root' unless soap==none --> "
+ "<xsl:template match='ROW:root'> "
+ " <xsl:choose> "
+ " <xsl:when test=\"$soap='none'\"> "
+ " <xsl:apply-templates/> "
+ " </xsl:when> "
+ " <xsl:otherwise> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:otherwise > "
+ " </xsl:choose> "
+ "</xsl:template> "
+ "<!-- copy 'schema' if content==schema or schemadata --> "
+ "<xsl:template match='xsd:schema'> "
+ " <xsl:choose> "
+ " <xsl:when test=\"$content='schemadata'\"> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:when> "
+ " <xsl:when test=\"$content='schema'\"> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:when> "
+ " <xsl:otherwise/> "
+ " </xsl:choose> "
+ "</xsl:template> "
+ "<!-- copy 'row' if content==data or schemadata --> "
+ "<xsl:template match='ROW:row'> "
+ " <xsl:choose> "
+ " <xsl:when test=\"$content='schemadata'\"> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:when> "
+ " <xsl:when test=\"$content='data'\"> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ " </xsl:when> "
+ " <xsl:otherwise/> "
+ " </xsl:choose> "
+ "</xsl:template> "
+ "<!-- copy everything else --> "
+ "<xsl:template match='*|@*'> "
+ " <xsl:copy> "
+ " <xsl:apply-templates select='@*|node()'/> "
+ " </xsl:copy> "
+ "</xsl:template> "
+ "</xsl:stylesheet>";
}
/**
* This is the prefix used in xpath and transforms for the xmla rowset
* namespace "urn:schemas-microsoft-com:xml-analysis:rowset".
*/
public static final String ROW_SET_PREFIX = "ROW";
private static CatalogLocator CATALOG_LOCATOR = null;
private static String soapFaultXPath = null;
private static String soapHeaderAndBodyXPath = null;
private static String soapBodyXPath = null;
private static String soapXmlaRootXPath = null;
private static String xmlaRootXPath = null;
/////////////////////////////////////////////////////////////////////////
// xmla help
/////////////////////////////////////////////////////////////////////////
public static CatalogLocator getCatalogLocator() {
if (CATALOG_LOCATOR == null) {
CATALOG_LOCATOR = new CatalogLocatorImpl();
}
return CATALOG_LOCATOR;
}
public static DataSourcesConfig.DataSources getDataSources(
String connectString,
Map<String, String> catalogNameUrls)
throws XOMException
{
String str = getDataSourcesText(connectString, catalogNameUrls);
StringReader dsConfigReader = new StringReader(str);
final Parser xmlParser = XOMUtil.createDefaultParser();
final DOMWrapper def = xmlParser.parse(dsConfigReader);
return new DataSourcesConfig.DataSources(def);
}
public static DataSourcesConfig.DataSources parseDataSources(
String dataSourcesConfigString,
Logger logger)
{
try {
if (dataSourcesConfigString == null) {
logger.warn("XmlaSupport.parseDataSources: null input");
return null;
}
dataSourcesConfigString =
Util.replaceProperties(
dataSourcesConfigString,
Util.toMap(System.getProperties()));
if (logger.isDebugEnabled()) {
logger.debug(
"XmlaSupport.parseDataSources: dataSources="
+ dataSourcesConfigString);
}
final Parser parser =
XOMUtil.createDefaultParser();
final DOMWrapper doc = parser.parse(dataSourcesConfigString);
return new DataSourcesConfig.DataSources(doc);
} catch (XOMException e) {
throw Util.newError(
e,
"Failed to parse data sources config: "
+ dataSourcesConfigString);
}
}
/**
* With a connection string, generate the DataSource xml. Since this
* is used by directly, same process, communicating with XMLA
* Mondrian, the fact that the URL contains "localhost" is not
* important.
*
* @param connectString Connect string
* @param catalogNameUrls array of catalog names, catalog url pairs
*/
public static String getDataSourcesText(
String connectString,
Map<String, String> catalogNameUrls)
{
StringBuilder buf = new StringBuilder(500);
if (false) {
buf.append("<?xml version=\"1.0\"?>");
buf.append(nl);
buf.append("<DataSources>");
buf.append(nl);
buf.append(" <DataSource>");
buf.append(nl);
buf.append(" <DataSourceName>");
buf.append(DATASOURCE_NAME);
buf.append("</DataSourceName>");
buf.append(nl);
buf.append(" <DataSourceDescription>");
buf.append(DATASOURCE_DESCRIPTION);
buf.append("</DataSourceDescription>");
buf.append(nl);
buf.append(" <URL>http://localhost:8080/mondrian/xmla</URL>");
buf.append(nl);
buf.append(" <DataSourceInfo><![CDATA[");
buf.append(connectString);
buf.append("]]></DataSourceInfo>");
buf.append(nl);
buf.append(" <ProviderName>Mondrian</ProviderName>");
buf.append(nl);
buf.append(" <ProviderType>MDP</ProviderType>");
buf.append(nl);
buf.append(
" <AuthenticationMode>Unauthenticated</AuthenticationMode>");
buf.append(nl);
buf.append(" <Catalogs>");
buf.append(nl);
for (Map.Entry<String, String> catalogNameUrl
: catalogNameUrls.entrySet())
{
String name = catalogNameUrl.getKey();
String url = catalogNameUrl.getValue();
buf.append(" <Catalog name='");
buf.append(name);
buf.append("'>");
if (url != null) {
buf.append("<Definition>");
buf.append(url);
buf.append("</Definition>");
}
buf.append("</Catalog>");
}
buf.append(" </Catalogs>");
buf.append(nl);
buf.append(" </DataSource>");
buf.append(nl);
buf.append("</DataSources>");
buf.append(nl);
} else {
buf.append("<?xml version=\"1.0\"?>");
buf.append(nl);
buf.append("<DataSources>");
buf.append(nl);
buf.append(" <DataSource>");
buf.append(nl);
buf.append(" <DataSourceName>");
buf.append(DATASOURCE_NAME);
buf.append("</DataSourceName>");
buf.append(nl);
buf.append(" <DataSourceDescription>");
buf.append(DATASOURCE_DESCRIPTION);
buf.append("</DataSourceDescription>");
buf.append(nl);
buf.append(" <URL>http://localhost:8080/mondrian/xmla</URL>");
buf.append(nl);
buf.append(" <DataSourceInfo><![CDATA[");
buf.append(connectString);
buf.append("]]></DataSourceInfo>");
buf.append(nl);
buf.append(" <ProviderName>Mondrian</ProviderName>");
buf.append(nl);
buf.append(" <ProviderType>MDP</ProviderType>");
buf.append(nl);
buf.append(
" <AuthenticationMode>Unauthenticated</AuthenticationMode>");
buf.append(nl);
buf.append(" <Catalogs>");
for (Map.Entry<String, String> catalogNameUrl
: catalogNameUrls.entrySet())
{
String name = catalogNameUrl.getKey();
String url = catalogNameUrl.getValue();
buf.append(nl);
buf.append(" <Catalog name='");
buf.append(name);
buf.append("'>");
if (url != null) {
buf.append("<Definition>");
buf.append(url);
buf.append("</Definition>");
}
buf.append("</Catalog>");
}
buf.append(nl);
buf.append(" </Catalogs>");
buf.append(nl);
buf.append(" </DataSource>");
buf.append(nl);
buf.append("</DataSources>");
buf.append(nl);
}
String datasources = buf.toString();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"XmlaSupport.getDataSources: datasources=" + datasources);
}
return datasources;
}
public static String getSoapFaultXPath() {
if (XmlaSupport.soapFaultXPath == null) {
StringBuilder buf = new StringBuilder(100);
buf.append('/');
buf.append(SOAP_PREFIX);
buf.append(":Envelope");
buf.append('/');
buf.append(SOAP_PREFIX);
buf.append(":Body");
buf.append('/');
buf.append(SOAP_PREFIX);
buf.append(":Fault");
buf.append("/*");
String xpath = buf.toString();
XmlaSupport.soapFaultXPath = xpath;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"XmlaSupport.getSoapFaultXPath: xpath=" + xpath);
}
}
return XmlaSupport.soapFaultXPath;
}
public static String getSoapHeaderAndBodyXPath() {
if (XmlaSupport.soapHeaderAndBodyXPath == null) {
StringBuilder buf = new StringBuilder(100);
buf.append('/');
buf.append(SOAP_PREFIX);
buf.append(":Envelope");
buf.append("/*");
String xpath = buf.toString();
XmlaSupport.soapHeaderAndBodyXPath = xpath;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"XmlaSupport.getSoapHeaderAndBodyXPath: xpath=" + xpath);
}
}
return XmlaSupport.soapHeaderAndBodyXPath;
}
public static String getSoapBodyXPath() {
if (XmlaSupport.soapBodyXPath == null) {
StringBuilder buf = new StringBuilder(100);
buf.append('/');
buf.append(SOAP_PREFIX);
buf.append(":Envelope");
buf.append('/');
buf.append(SOAP_PREFIX);
buf.append(":Body");
buf.append("/*");
String xpath = buf.toString();
XmlaSupport.soapBodyXPath = xpath;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("XmlaSupport.getSoapBodyXPath: xpath=" + xpath);
}
}
return XmlaSupport.soapBodyXPath;
}
public static String getSoapXmlaRootXPath(String xmlaPrefix) {
if (XmlaSupport.soapXmlaRootXPath == null) {
StringBuilder buf = new StringBuilder(20);
buf.append('/');
buf.append(SOAP_PREFIX);
buf.append(":Envelope");
buf.append('/');
buf.append(SOAP_PREFIX);
buf.append(":Body");
buf.append("/").append(xmlaPrefix).append(":DiscoverResponse");
buf.append("/").append(xmlaPrefix).append(":return");
buf.append('/');
buf.append(ROW_SET_PREFIX);
buf.append(":root");
buf.append("/*");
String xpath = buf.toString();
XmlaSupport.soapXmlaRootXPath = xpath;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"XmlaSupport.getSoapXmlaRootXPath: xpath="
+ xpath);
}
}
return XmlaSupport.soapXmlaRootXPath;
}
public static String getXmlaRootXPath(String xmlaPrefix) {
if (XmlaSupport.xmlaRootXPath == null) {
StringBuilder buf = new StringBuilder(20);
buf.append("/").append(xmlaPrefix).append(":DiscoverResponse");
buf.append("/").append(xmlaPrefix).append(":return");
buf.append('/');
buf.append(ROW_SET_PREFIX);
buf.append(":root");
buf.append("/*");
String xpath = buf.toString();
XmlaSupport.xmlaRootXPath = xpath;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("XmlaSupport.getXmlaRootXPath: xpath=" + xpath);
}
}
return XmlaSupport.xmlaRootXPath;
}
public static Node[] extractNodesFromSoapXmla(byte[] bytes)
throws SAXException, IOException
{
Document doc = XmlUtil.parse(bytes);
return extractNodesFromSoapXmla(doc);
}
public static Node[] extractNodesFromSoapXmla(Document doc)
throws SAXException, IOException
{
final String xmlaPrefix = "xmla";
String xpath = getSoapXmlaRootXPath(xmlaPrefix);
// Note that this is SOAP 1.1 version uri
String[][] nsArray = new String[][] {
{
SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1
},
{
xmlaPrefix, XmlaConstants.NS_XMLA
},
{
ROW_SET_PREFIX, XmlaConstants.NS_XMLA_ROWSET
}
};
return extractNodes(doc, xpath, nsArray);
}
public static Node[] extractNodesFromXmla(byte[] bytes)
throws SAXException, IOException
{
Document doc = XmlUtil.parse(bytes);
return extractNodesFromXmla(doc);
}
public static Node[] extractNodesFromXmla(Document doc)
throws SAXException, IOException
{
final String xmlaPrefix = "xmla";
String xpath = getXmlaRootXPath(xmlaPrefix);
String[][] nsArray = new String[][] {
{
xmlaPrefix, XmlaConstants.NS_XMLA
},
{
ROW_SET_PREFIX, XmlaConstants.NS_XMLA_ROWSET
}
};
return extractNodes(doc, xpath, nsArray);
}
public static Node[] extractFaultNodesFromSoap(byte[] bytes)
throws SAXException, IOException
{
Document doc = XmlUtil.parse(bytes);
return extractFaultNodesFromSoap(doc);
}
public static Node[] extractFaultNodesFromSoap(Document doc)
throws SAXException, IOException
{
String xpath = getSoapFaultXPath();
String[][] nsArray = {
{ SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 },
};
return extractNodes(doc, xpath, nsArray);
}
public static Node[] extractHeaderAndBodyFromSoap(byte[] bytes)
throws SAXException, IOException
{
Document doc = XmlUtil.parse(bytes);
return extractHeaderAndBodyFromSoap(doc);
}
public static Node[] extractHeaderAndBodyFromSoap(Document doc)
throws SAXException, IOException
{
String xpath = getSoapHeaderAndBodyXPath();
String[][] nsArray = {
{ SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 },
};
return extractNodes(doc, xpath, nsArray);
}
public static Document extractBodyFromSoap(Document doc)
throws SAXException, IOException
{
String xpath = getSoapBodyXPath();
String[][] nsArray = new String[][] {
{ SOAP_PREFIX, XmlaConstants.NS_SOAP_ENV_1_1 },
};
Node[] nodes = extractNodes(doc, xpath, nsArray);
return (nodes.length == 1)
? XmlUtil.newDocument(nodes[0], true) : null;
}
/**
* Given a Document and an xpath/namespace-array pair, extract and return
* the Nodes resulting from applying the xpath.
*
* @throws SAXException on error
* @throws IOException on error
*/
public static Node[] extractNodes(
Node node,
String xpath,
String[][] nsArray)
throws SAXException, IOException
{
Document contextDoc = XmlUtil.createContextDocument(nsArray);
Node[] nodes = XmlUtil.selectAsNodes(node, xpath, contextDoc);
if (LOGGER.isDebugEnabled()) {
StringBuilder buf = new StringBuilder(1024);
buf.append("XmlaSupport.extractNodes: ");
buf.append("nodes.length=");
buf.append(nodes.length);
buf.append(nl);
for (Node n : nodes) {
String str = XmlUtil.toString(n, false);
buf.append(str);
buf.append(nl);
}
LOGGER.debug(buf.toString());
}
return nodes;
}
/////////////////////////////////////////////////////////////////////////
// soap xmla file
/////////////////////////////////////////////////////////////////////////
/**
* Process the given input file as a SOAP-XMLA request.
*
*/
public static byte[] processSoapXmla(
File file,
String connectString,
Map<String, String> catalogNameUrls,
String cbClassName)
throws IOException, ServletException, SAXException
{
String requestText = XmlaSupport.readFile(file);
return
processSoapXmla(
requestText, connectString,
catalogNameUrls, cbClassName, null, null);
}
public static byte[] processSoapXmla(
File file,
String connectString,
Map<String, String> catalogNameUrls,
String cbClassName,
Map<List<String>, Servlet> servletCache)
throws IOException, ServletException, SAXException
{
String requestText = XmlaSupport.readFile(file);
return
processSoapXmla(
requestText, connectString, catalogNameUrls, cbClassName,
null, servletCache);
}
public static byte[] processSoapXmla(
Document doc,
String connectString,
Map<String, String> catalogNameUrls,
String cbClassName,
Role role)
throws IOException, ServletException, SAXException
{
return processSoapXmla(
doc, connectString, catalogNameUrls, cbClassName, role,
null);
}
public static byte[] processSoapXmla(
Document doc,
String connectString,
Map<String, String> catalogNameUrls,
String cbClassName,
Role role,
Map<List<String>, Servlet> servletCache)
throws IOException, ServletException, SAXException
{
String requestText = XmlUtil.toString(doc, false);
return processSoapXmla(
requestText, connectString, catalogNameUrls, cbClassName, role,
servletCache);
}
public static byte[] processSoapXmla(
String requestText,
String connectString,
Map<String, String> catalogNameUrls,
String cbClassName,
Role role)
throws IOException, ServletException, SAXException
{
return
processSoapXmla(
requestText, connectString,
catalogNameUrls, cbClassName, role, null);
}
public static byte[] processSoapXmla(
String requestText,
String connectString,
Map<String, String> catalogNameUrls,
String cbClassName,
Role role,
Map<List<String>, Servlet> servletCache)
throws IOException, ServletException, SAXException
{
// read soap file
String dataSourceText =
XmlaSupport.getDataSourcesText(connectString, catalogNameUrls);
Util.PropertyList propertyList = Util.parseConnectString(connectString);
String roleName =
propertyList.get(RolapConnectionProperties.Role.name());
LockBox.Entry entry = null;
if (role != null) {
// We happen to know that the lock box is shared between servers.
// So we can create any old server; it doesn't need to pertain to
// this particular catalog.
final MondrianServer server = MondrianServer.forId(null);
entry = server.getLockBox().register(role);
roleName = entry.getMoniker();
}
byte[] reqBytes = requestText.getBytes();
MockHttpServletRequest req = new MockHttpServletRequest(reqBytes);
req.setMethod("POST");
req.setContentType("text/xml");
if (roleName != null) {
req.setUserInRole(roleName, true);
}
MockHttpServletResponse res = new MockHttpServletResponse();
res.setCharacterEncoding("UTF-8");
Servlet servlet = getServlet(cbClassName, dataSourceText, servletCache);
servlet.service(req, res);
// Even though it is not used, it is important that entry is in scope
// until after request has returned. Prevents role's lock box entry from
// being garbage collected.
Util.discard(entry);
return res.toByteArray();
}
public static Servlet makeServlet(
String connectString,
Map<String, String> catalogNameUrls,
String cbClassName)
throws IOException, ServletException, SAXException
{
return makeServlet(connectString, catalogNameUrls, cbClassName, null);
}
public static Servlet makeServlet(
String connectString,
Map<String, String> catalogNameUrls,
String cbClassName,
Map<List<String>, Servlet> servletCache)
throws IOException, ServletException, SAXException
{
// Create datasource file and put datasource xml into it.
// Mark it as delete on exit.
String dataSourceText =
XmlaSupport.getDataSourcesText(connectString, catalogNameUrls);
return getServlet(cbClassName, dataSourceText, servletCache);
}
private static Servlet getServlet(
String cbClassName,
String dataSourceText,
Map<List<String>, Servlet> cache)
throws ServletException
{
final List<String> key =
Arrays.asList(
dataSourceText);
Servlet servlet = cache.get(key);
if (servlet != null) {
return servlet;
}
MockServletContext servletContext = new MockServletContext();
MockServletConfig servletConfig = new MockServletConfig(servletContext);
servletConfig.addInitParameter(
XmlaServlet.PARAM_CALLBACKS, cbClassName);
servletConfig.addInitParameter(
XmlaServlet.PARAM_CHAR_ENCODING, "UTF-8");
servletConfig.addInitParameter(
XmlaServlet.PARAM_DATASOURCES_CONFIG,
"inline:" + dataSourceText);
servlet = new MondrianXmlaServlet();
servlet.init(servletConfig);
if (cache != null) {
cache.put(key, servlet);
}
return servlet;
}
public static byte[] processSoapXmla(
File file,
Servlet servlet)
throws IOException, ServletException, SAXException
{
String requestText = XmlaSupport.readFile(file);
return processSoapXmla(requestText, servlet);
}
public static byte[] processSoapXmla(
Document doc,
Servlet servlet)
throws IOException, ServletException, SAXException
{
String requestText = XmlUtil.toString(doc, false);
return processSoapXmla(requestText, servlet);
}
public static byte[] processSoapXmla(
String requestText,
Servlet servlet)
throws IOException, ServletException, SAXException
{
byte[] reqBytes = requestText.getBytes();
// make request
MockHttpServletRequest req = new MockHttpServletRequest(reqBytes);
req.setMethod("POST");
req.setContentType("text/xml");
// make response
MockHttpServletResponse res = new MockHttpServletResponse();
res.setCharacterEncoding("UTF-8");
servlet.service(req, res);
return res.toByteArray();
}
/**
* Check is a byte array containing a SOAP-XMLA response method is valid.
* Schema validation occurs if the XMLA response contains both a content
* and schmema section. This includes both the SOAP elements and the
* SOAP body content, the XMLA response.
*
*/
public static boolean validateSchemaSoapXmla(byte[] bytes)
throws SAXException, IOException,
ParserConfigurationException,
TransformerException
{
return validateEmbeddedSchema(
bytes,
XmlUtil.getSoapXmlaXds2xs("xmla"),
XmlUtil.getSoapXmlaXds2xd("xmla"));
}
/////////////////////////////////////////////////////////////////////////
// xmla file
/////////////////////////////////////////////////////////////////////////
/**
* Processes the given input file as an XMLA request (no SOAP elements).
*/
public static byte[] processXmla(
File file,
String connectString,
Map<String, String> catalogNameUrls)
throws IOException, SAXException, XOMException
{
return
processXmla(
file, connectString, catalogNameUrls,
(Map<List<String>, MondrianServer>)null);
}
public static byte[] processXmla(
File file,
String connectString,
Map<String, String> catalogNameUrls,
Map<List<String>, MondrianServer> cache)
throws IOException, SAXException, XOMException
{
return
processXmla(
file, connectString, catalogNameUrls, null, cache);
}
public static byte[] processXmla(
File file,
String connectString,
Map<String, String> catalogNameUrls,
Role role)
throws IOException, SAXException, XOMException
{
return
processXmla(
file,
connectString,
catalogNameUrls,
role,
null);
}
public static byte[] processXmla(
File file,
String connectString,
Map<String, String> catalogNameUrls,
Role role,
Map<List<String>, MondrianServer> cache)
throws IOException, SAXException, XOMException
{
String requestText = XmlaSupport.readFile(file);
return processXmla(requestText, connectString, catalogNameUrls, cache);
}
public static byte[] processXmla(
String requestText,
String connectString,
Map<String, String> catalogNameUrls)
throws IOException, SAXException, XOMException
{
return
processXmla(
requestText, connectString, catalogNameUrls,
(Map<List<String>, MondrianServer>)null);
}
public static byte[] processXmla(
String requestText,
String connectString,
Map<String, String> catalogNameUrls,
Map<List<String>, MondrianServer> cache)
throws IOException, SAXException, XOMException
{
Document requestDoc = XmlUtil.parseString(requestText);
return
processXmla(
requestDoc, connectString, catalogNameUrls, null, cache);
}
public static byte[] processXmla(
Document requestDoc,
String connectString,
Map<String, String> catalogNameUrls,
Role role)
throws IOException, XOMException
{
return
processXmla(
requestDoc, connectString,
catalogNameUrls, role, null);
}
public static byte[] processXmla(
Document requestDoc,
String connectString,
Map<String, String> catalogNameUrls,
Role role,
Map<List<String>, MondrianServer> cache)
throws IOException, XOMException
{
Element requestElem = requestDoc.getDocumentElement();
return
processXmla(
requestElem, connectString, catalogNameUrls, role, cache);
}
public static byte[] processXmla(
Element requestElem,
String connectString,
Map<String, String> catalogNameUrls,
Role role)
throws IOException, XOMException
{
return
processXmla(
requestElem, connectString,
catalogNameUrls, role, null);
}
public static byte[] processXmla(
Element requestElem,
String connectString,
Map<String, String> catalogNameUrls,
Role role,
Map<List<String>, MondrianServer> cache)
throws IOException, XOMException
{
Util.PropertyList propertyList =
Util.parseConnectString(connectString);
final List<String> cacheKey =
Arrays.asList(
propertyList.toString(),
catalogNameUrls.toString());
MondrianServer server = cache.get(cacheKey);
if (server == null) {
server = MondrianServer.createWithRepository(
new StringRepositoryContentFinder(
getDataSourcesText(
connectString, catalogNameUrls)),
getCatalogLocator());
}
if (cache != null) {
cache.put(
cacheKey,
server);
}
// make request
final XmlaHandler handler =
new XmlaHandler(
(XmlaHandler.ConnectionFactory) server,
"xmla");
String roleName =
propertyList.get(RolapConnectionProperties.Role.name());
LockBox.Entry entry = null;
if (role != null) {
entry = server.getLockBox().register(role);
roleName = entry.getMoniker();
}
XmlaRequest request =
new DefaultXmlaRequest(requestElem, roleName, null, null, null);
Enumeration.ResponseMimeType responseMimeType =
Enumeration.ResponseMimeType.MAP.get(
request.getProperties().get(
PropertyDefinition.ResponseMimeType.name()));
if (responseMimeType == null) {
responseMimeType = Enumeration.ResponseMimeType.SOAP;
}
// make response
ByteArrayOutputStream resBuf = new ByteArrayOutputStream();
XmlaResponse response =
new DefaultXmlaResponse(resBuf, "UTF-8", responseMimeType);
handler.process(request, response);
// Even though it is not used, it is important that entry is in scope
// until after request has returned. Prevents role's lock box entry from
// being garbage collected.
Util.discard(entry);
return resBuf.toByteArray();
}
/**
* Check is a byte array containing a XMLA response method is valid.
* Schema validation occurs if the XMLA response contains both a content
* and schmema section. This should not be used when the byte array
* contains both the SOAP elements and content, but only for the content.
*
*/
public static boolean validateSchemaXmla(byte[] bytes)
throws SAXException, IOException,
ParserConfigurationException,
TransformerException
{
return validateEmbeddedSchema(
bytes,
XmlUtil.getXmlaXds2xs("xmla"),
XmlUtil.getXmlaXds2xd("xmla"));
}
/////////////////////////////////////////////////////////////////////////
// helpers
/////////////////////////////////////////////////////////////////////////
/**
* This validates a SOAP-XMLA response using xpaths to extract the
* schema and data parts. In addition, it does a little surgery on
* the DOMs removing the schema nodes from the XMLA root node.
*/
public static boolean validateSoapXmlaUsingXpath(byte[] bytes)
throws SAXException, IOException
{
if (! XmlUtil.supportsValidation()) {
return false;
}
// Remove the UTF BOM for proper validation.
bytes = removeUtfBom(bytes);
Node[] nodes = extractNodesFromSoapXmla(bytes);
return validateNodes(nodes);
}
private static byte[] removeUtfBom(byte[] s) {
byte[] response = removeUtfBom(s, UTF8_BOM_MATCHER);
if (response != null) {
return response;
}
response = removeUtfBom(s, UTF16_BE_BOM_MATCHER);
if (response != null) {
return response;
}
response = removeUtfBom(s, UTF16_LE_BOM_MATCHER);
if (response != null) {
return response;
}
response = removeUtfBom(s, UTF32_BE_BOM_MATCHER);
if (response != null) {
return response;
}
response = removeUtfBom(s, UTF32_LE_BOM_MATCHER);
if (response != null) {
return response;
}
return s;
}
private static byte[] removeUtfBom(byte[] s, ByteMatcher matcher) {
byte[] firstBytes = new byte[matcher.key.length];
System.arraycopy(s, 0, firstBytes, 0, matcher.key.length);
if (s.length >= matcher.key.length
&& matcher.match(firstBytes) == 0)
{
byte[] result = new byte[s.length - matcher.key.length];
System.arraycopy(s, 0, result, 0, s.length - matcher.key.length);
return result;
}
return null;
}
/**
* This validates a XMLA response using xpaths to extract the
* schema and data parts. In addition, it does a little surgery on
* the DOMs removing the schema nodes from the XMLA root node.
*
*/
public static boolean validateXmlaUsingXpath(byte[] bytes)
throws SAXException, IOException
{
if (! XmlUtil.supportsValidation()) {
return false;
}
// Remove the UTF BOM for proper validation.
bytes = removeUtfBom(bytes);
Node[] nodes = extractNodesFromXmla(bytes);
return validateNodes(nodes);
}
/**
* Validate Nodes with throws an error if validation was attempted but
* failed, returns true if validation was successful and false if
* validation was not tried.
*
* @return true if validation succeeded, false if validation was not tried
*/
public static boolean validateNodes(Node[] nodes)
throws SAXException, IOException
{
if (! XmlUtil.supportsValidation()) {
return false;
}
if (nodes.length == 0) {
// no nodes
return false;
} else if (nodes.length == 1) {
// only data or schema but not both
return false;
} else if (nodes.length > 2) {
// TODO: error
return false;
}
Node schemaNode = nodes[0];
Node rowNode = nodes[1];
// This is the "root" node that contains both the schemaNode and
// the rowNode.
Node rootNode = rowNode.getParentNode();
// Remove the schemaNode from the root Node.
rootNode.removeChild(schemaNode);
// Convert nodes to Documents.
Document schemaDoc = XmlUtil.newDocument(schemaNode, true);
Document dataDoc = XmlUtil.newDocument(rootNode, true);
String xmlns = XmlUtil.getNamespaceAttributeValue(dataDoc);
String schemaLocationPropertyValue = xmlns + ' ' + "xmlschema";
org.xml.sax.EntityResolver resolver = new XmlUtil.Resolver(schemaDoc);
XmlUtil.validate(dataDoc, schemaLocationPropertyValue, resolver);
return true;
}
/**
* See next method for JavaDoc {@link #validateEmbeddedSchema(org.w3c.dom.Document, String, String)}.
*
*/
public static boolean validateEmbeddedSchema(
byte[] bytes,
String schemaTransform,
String dataTransform)
throws SAXException, IOException,
ParserConfigurationException,
TransformerException,
TransformerConfigurationException
{
if (! XmlUtil.supportsValidation()) {
return false;
}
Document doc = XmlUtil.parse(bytes);
return validateEmbeddedSchema(doc, schemaTransform, dataTransform);
}
/**
* A given Document has both content and an embedded schema (where
* the schema has a single root node and the content has a single
* root node - they are not interwoven). A single xsl transform is
* provided to extract the schema part of the Document and another
* xsl transform is provided to extract the content part and then
* the content is validated against the schema.
* <p>
* If the content is valid, then nothing happens, but if the content
* is not valid an execption is thrown (currently a RuntimeException).
* <p>
* When Mondrian moves to Java 5 or includes the JAXP 1.3 jar, then
* there is a utility in JAXP that does something like this (but allows
* for multiple schema/content parts).
*
*/
public static boolean validateEmbeddedSchema(
Document doc,
String schemaTransform,
String dataTransform)
throws SAXException, IOException,
ParserConfigurationException,
TransformerException,
TransformerConfigurationException
{
if (! XmlUtil.supportsValidation()) {
return false;
}
Node dataDoc = XmlUtil.transform(
doc,
new BufferedReader(new StringReader(dataTransform)));
if (dataDoc == null) {
LOGGER.debug("XmlaSupport.validateEmbeddedSchema: dataDoc is null");
return false;
}
if (! dataDoc.hasChildNodes()) {
LOGGER.debug(
"XmlaSupport.validateEmbeddedSchema: dataDoc has no children");
return false;
}
String dataStr = XmlUtil.toString(dataDoc, false);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"XmlaSupport.validateEmbeddedSchema: dataDoc:\n=" + dataStr);
}
if (! (dataDoc instanceof Document)) {
LOGGER.warn(
"XmlaSupport.validateEmbeddedSchema: dataDoc not Document");
return false;
}
Node schemaDoc = XmlUtil.transform(
doc,
new BufferedReader(new StringReader(schemaTransform)));
if (schemaDoc == null) {
LOGGER.debug(
"XmlaSupport.validateEmbeddedSchema: schemaDoc is null");
return false;
}
if (! schemaDoc.hasChildNodes()) {
LOGGER.debug(
"XmlaSupport.validateEmbeddedSchema: "
+ "schemaDoc has no children");
return false;
}
String schemaStr = XmlUtil.toString(schemaDoc, false);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"XmlaSupport.validateEmbeddedSchema: schemaDoc:\n="
+ schemaStr);
}
if (! (schemaDoc instanceof Document)) {
LOGGER.warn(
"XmlaSupport.validateEmbeddedSchema: schemaDoc not Document");
return false;
}
String xmlns = XmlUtil.getNamespaceAttributeValue((Document)dataDoc);
String schemaLocationPropertyValue = xmlns + ' ' + "xmlschema";
org.xml.sax.EntityResolver resolver = new XmlUtil.Resolver(schemaStr);
XmlUtil.validate(dataStr, schemaLocationPropertyValue, resolver);
return true;
}
public static Document transformSoapXmla(
Document doc, String[][] namevalueParameters, String ns)
throws SAXException, IOException,
ParserConfigurationException,
TransformerException
{
Node node = XmlUtil.transform(
doc,
new BufferedReader(new StringReader(getXmlaTransform(ns))),
namevalueParameters);
return (node instanceof Document) ? (Document) node : null;
}
/**
* Reads a file line by line, adds a '\n' after each line and
* returns in a String.
*
*/
public static String readFile(File file) throws IOException {
StringBuilder buf = new StringBuilder(1024);
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
buf.append(line);
buf.append('\n');
}
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception ignored) {
}
}
}
return buf.toString();
}
private XmlaSupport() {
}
}
// End XmlaSupport.java