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