[Mondrian] Extend XMLA servlet -- changes checked in

Julian Hyde jhyde at pentaho.com
Mon Jul 25 11:44:14 EDT 2011


Michele,

I finally checked in your changes to extend the XMLA servlet (see below).
Sorry it took me so long, but it's a great step forward for the XMLA server
architecture. Thanks for the contribution.

I simplified your code a little: I converted
DiscoverDatasourcesPreConfiguredResponse into a Map<String, Object>, because
the class name was longer than its information content. And I converted your
DelegatingOlapConnection into a reflection proxy (it wouldn't worked with
both JDBC 3 and 4 if I'd kept it as is). 

One further thing would be useful. Can you write some documentation on "How
to deploy an XMLA servlet". Among other things, describe the servlet
properties you have added. If you write it as an email, I can convert into a
wiki page. Eventually it will be part of the doc for the
"olap4j-xmla-server" project (when we finally refactor it out of the
mondrian code base).

Luc,

I know you needed this change to be checked in for XMLA work. Enjoy. If you
have questions, send them to the dev list.

Julian


-----Original Message-----
From: Julian Hyde [mailto:jhyde at users.sourceforge.net] 
Sent: Friday, July 22, 2011 11:24 PM
To: Ajit Joglekar; Aaron Phillips; Andreas Voss; Ezequiel Cuellar; Eric
McDermid; Julian Hyde; John V. Sichi; Mat Lowery; Matt Campbell; Rushan
Chen; Robin Tharappel; Will Gorman
Subject: Eigenbase perforce change 14479 for review

http://p4web.eigenbase.org/@md=d&c=6PU@/14479?ac=10

Change 14479 by jhyde at jhyde.marmite1 on 2011/07/22 23:23:21

	MONDRIAN: Extend XMLA servlet to support requests from Excel 2008.
Requests may
	    now contain a session id, username, and password. If username
and password
	    are only passed on the first request of the session, their
values are
	    remembered from earlier requests. Contributed by Michele Rossi.

Affected files ...

... //open/mondrian/src/main/mondrian/server/MondrianServerImpl.java#4 edit
... //open/mondrian/src/main/mondrian/tui/XmlaSupport.java#28 edit
... //open/mondrian/src/main/mondrian/xmla/Rowset.java#38 edit
... //open/mondrian/src/main/mondrian/xmla/RowsetDefinition.java#83 edit
... //open/mondrian/src/main/mondrian/xmla/XmlaConstants.java#14 edit
... //open/mondrian/src/main/mondrian/xmla/XmlaHandler.java#77 edit
... //open/mondrian/src/main/mondrian/xmla/XmlaRequest.java#12 edit
... //open/mondrian/src/main/mondrian/xmla/XmlaServlet.java#41 edit
... //open/mondrian/src/main/mondrian/xmla/XmlaUtil.java#32 edit
... //open/mondrian/src/main/mondrian/xmla/impl/DefaultSaxWriter.java#13
edit
... //open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaRequest.java#20
edit
... //open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaServlet.java#31
edit
... //open/mondrian/src/main/mondrian/xmla/impl/Olap4jXmlaServlet.java#2
edit
... //open/mondrian/testsrc/main/mondrian/rolap/NonEmptyTest.java#150 edit
... //open/mondrian/testsrc/main/mondrian/xmla/test/XmlaTest.java#24 edit

Differences ...

==== //open/mondrian/src/main/mondrian/server/MondrianServerImpl.java#4
(ktext) ====

2c2
< // $Id: //open/mondrian/src/main/mondrian/server/MondrianServerImpl.java#3
$
---
> // $Id: //open/mondrian/src/main/mondrian/server/MondrianServerImpl.java#4
$
17a18
> 
28c29
<  * @version $Id:
//open/mondrian/src/main/mondrian/server/MondrianServerImpl.java#3 $
---
>  * @version $Id:
//open/mondrian/src/main/mondrian/server/MondrianServerImpl.java#4 $
210a212,217
> 
>     public Map<String, Object>
getPreConfiguredDiscoverDatasourcesResponse() {
>         // No pre-configured response; XMLA servlet will connect to get
>         // data source info.
>         return null;
>     }

==== //open/mondrian/src/main/mondrian/tui/XmlaSupport.java#28 (ktext) ====

2c2
< // $Id: //open/mondrian/src/main/mondrian/tui/XmlaSupport.java#27 $
---
> // $Id: //open/mondrian/src/main/mondrian/tui/XmlaSupport.java#28 $
50c50
<  * @version $Id: //open/mondrian/src/main/mondrian/tui/XmlaSupport.java#27
$
---
>  * @version $Id: //open/mondrian/src/main/mondrian/tui/XmlaSupport.java#28
$
921c921,922
<         XmlaRequest request = new DefaultXmlaRequest(requestElem,
roleName);
---
>         XmlaRequest request =
>             new DefaultXmlaRequest(requestElem, roleName, null, null,
null);

==== //open/mondrian/src/main/mondrian/xmla/Rowset.java#38 (ktext) ====

2c2
< // $Id: //open/mondrian/src/main/mondrian/xmla/Rowset.java#37 $
---
> // $Id: //open/mondrian/src/main/mondrian/xmla/Rowset.java#38 $
6c6
< // Copyright (C) 2003-2010 Julian Hyde
---
> // Copyright (C) 2003-2011 Julian Hyde
9,10d8
< //
< // jhyde, May 2, 2003
33c31
<  * @version $Id: //open/mondrian/src/main/mondrian/xmla/Rowset.java#37 $
---
>  * @version $Id: //open/mondrian/src/main/mondrian/xmla/Rowset.java#38 $

==== //open/mondrian/src/main/mondrian/xmla/RowsetDefinition.java#83 (ktext)
====

2c2
< // $Id: //open/mondrian/src/main/mondrian/xmla/RowsetDefinition.java#82 $
---
> // $Id: //open/mondrian/src/main/mondrian/xmla/RowsetDefinition.java#83 $
24a25
> import java.lang.reflect.InvocationTargetException;
26d26
< import java.lang.reflect.InvocationTargetException;
44c44
<  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/RowsetDefinition.java#82 $
---
>  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/RowsetDefinition.java#83 $
1587,1588c1587,1598
<             final XmlaHandler.XmlaExtra extra = getExtra(connection);
<             for (Map<String, Object> ds :
extra.getDataSources(connection)) {
---
>             if (needConnection()) {
>                 final XmlaHandler.XmlaExtra extra = getExtra(connection);
>                 for (Map<String, Object> ds :
extra.getDataSources(connection))
>                 {
>                     Row row = new Row();
>                     for (Column column : columns) {
>                         row.set(column.name, ds.get(column.name));
>                     }
>                     addRow(row, rows);
>                 }
>             } else {
>                 // using pre-configured discover datasources response
1589a1600,1602
>                 Map<String, Object> map =
>                     this.handler.connectionFactory
>                         .getPreConfiguredDiscoverDatasourcesResponse();
1591c1604
<                     row.set(column.name, ds.get(column.name));
---
>                     row.set(column.name, map.get(column.name));
1596a1610,1617
>         @Override
>         protected boolean needConnection() {
>             // If the olap connection factory has a pre configured
response,
>             // we don't need to connect to find metadata. This is good.
>             return this.handler.connectionFactory
>                        .getPreConfiguredDiscoverDatasourcesResponse() ==
null;
>         }
> 
6434a6456,6467
> 
>         public String getUsername() {
>             return request.getUsername();
>         }
> 
>         public String getPassword() {
>             return request.getPassword();
>         }
> 
>         public String getSessionId() {
>             return request.getSessionId();
>         }

==== //open/mondrian/src/main/mondrian/xmla/XmlaConstants.java#14 (ktext)
====

2c2
< // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaConstants.java#13 $
---
> // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaConstants.java#14 $
6c6
< // Copyright (C) 2005-2010 Julian Hyde
---
> // Copyright (C) 2005-2011 Julian Hyde
12d11
< 
17c16
<  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/XmlaConstants.java#13 $
---
>  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/XmlaConstants.java#14 $
49a49,50
>     public static final String NS_SOAP_SECEXT =
>         "http://schemas.xmlsoap.org/ws/2002/04/secext";
65c66
< 
---
>     public static final String XMLA_SECURITY           = "Security";

==== //open/mondrian/src/main/mondrian/xmla/XmlaHandler.java#77 (ktext) ====

1a2
> // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaHandler.java#77 $
40c41
<  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/XmlaHandler.java#76 $
---
>  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/XmlaHandler.java#77 $
45a47,56
>     /**
>      * Name of property used by JDBC to hold user name.
>      */
>     private static final String JDBC_USER = "user";
> 
>     /**
>      * Name of property used by JDBC to hold password.
>      */
>     private static final String JDBC_PASSWORD = "password";
> 
61a73,79
>     /**
>      * Returns a new OlapConnection opened with the credentials specified
in the
>      * XMLA request or an existing connection if one can be found
associated
>      * with the request session id.
>      *
>      * @param request Request
>      */
63,65c81,109
<         final Map<String, String> properties = request.getProperties();
<         final String dataSourceInfo =
<             properties.get(PropertyDefinition.DataSourceInfo.name());
---
>         String sessionId = request.getSessionId();
>         if (sessionId == null) {
>             // With a Simba O2X Client session ID is only null when
>             // serving "discover datasources".
>             //
>             // Let's have a magic ID for the non-authenticated session.
>             //
>             // REVIEW: Security hole?
>             sessionId = "<no_session>";
>         }
>         LOGGER.debug(
>             "Creating new connection for user [" + request.getUsername()
>             + "] and session [" + sessionId + "]");
> 
>         Properties props = new Properties();
>         if (request.getUsername() != null) {
>             props.put(JDBC_USER, request.getUsername());
>         }
> 
>         if (request.getPassword() != null) {
>             props.put(JDBC_PASSWORD, request.getPassword());
>         }
> 
>         // [MROSSI] getConnection does not take a dataSourceInfo. I think
>         // it was a bug.
>         //
>         // String dataSourceInfo =
>         //   properties.get(PropertyDefinition.DataSourceInfo.name());
>         //
67,72c111,112
<             properties.get(PropertyDefinition.Catalog.name());
<         String roleName = request.getRoleName();
< 
<         // REVIEW: Should we pass request properties to getConnection?
<         return getConnection(
<             dataSourceInfo, catalog, roleName, new Properties());
---
>
request.getProperties().get(PropertyDefinition.Catalog.name());
>         return getConnection(catalog, null, request.getRoleName(), props);
3122a3163,3177
> 
>         /**
>          * Returns a map of property name-value pairs with which to
populate
>          * the response to the DISCOVER_DATASOURCES request.
>          *
>          * <p>Properties correspond to the columns of that request:
>          * ""DataSourceName", et cetera.</p>
>          *
>          * <p>Returns null if there is no pre-configured response; in
>          * which case, the driver will have to connect to get a
response.</p>
>          *
>          * @return Column names and values for the DISCOVER_DATASOURCES
>          * response
>          */
>         Map<String, Object> getPreConfiguredDiscoverDatasourcesResponse();

==== //open/mondrian/src/main/mondrian/xmla/XmlaRequest.java#12 (ktext) ====

2c2
< // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaRequest.java#11 $
---
> // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaRequest.java#12 $
6c6
< // Copyright (C) 2005-2010 Julian Hyde
---
> // Copyright (C) 2005-2011 Julian Hyde
20c20
<  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/XmlaRequest.java#11 $
---
>  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/XmlaRequest.java#12 $
61a62,87
> 
>     /**
>      * The username to use to open the underlying olap4j connection.
>      * Can be null.
>      */
>     String getUsername();
> 
>     /**
>      * The password to use to open the underlying olap4j connection.
>      * Can be null.
>      */
>     String getPassword();
> 
>     /**
>      * Returns the id of the session this request belongs to.
>      *
>      * <p>Not necessarily the same as the HTTP session: the SOAP request
>      * contains its own session information.</p>
>      *
>      * <p>The session id is used to retrieve existing olap connections.
And
>      * username / password only need to be passed on the first request in
a
>      * session.</p>
>      *
>      * @return Id of the session
>      */
>     String getSessionId();

==== //open/mondrian/src/main/mondrian/xmla/XmlaServlet.java#41 (ktext) ====

2c2
< // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaServlet.java#40 $
---
> // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaServlet.java#41 $
9,10d8
< //
< // jhyde, May 2, 2003
28c26
<  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/XmlaServlet.java#40 $
---
>  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/XmlaServlet.java#41 $
34d31
< 
39c36
<             "OptionalDataSourceConfig";
---
>         "OptionalDataSourceConfig";
225c222
<                     || contentType.indexOf("text/xml") == -1)
---
>                     || !contentType.contains("text/xml"))

==== //open/mondrian/src/main/mondrian/xmla/XmlaUtil.java#32 (ktext) ====

2c2
< // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaUtil.java#31 $
---
> // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaUtil.java#32 $
9,10d8
< //
< // jhyde, May 2, 2003
41c39
<  * @version $Id: //open/mondrian/src/main/mondrian/xmla/XmlaUtil.java#31 $
---
>  * @version $Id: //open/mondrian/src/main/mondrian/xmla/XmlaUtil.java#32 $
316a315,323
> 
>                 public Map<String, Object>
>                 getPreConfiguredDiscoverDatasourcesResponse()
>                 {
>                     // This method should not be used by the olap4j xmla
>                     // servlet. For the mondrian xmla servlet we don't
provide
>                     // the "pre configured discover datasources" feature.
>                     return null;
>                 }
349a357,368
> 
>             public String getUsername() {
>                 return null;
>             }
> 
>             public String getPassword() {
>                 return null;
>             }
> 
>             public String getSessionId() {
>                 return null;
>             }

==== //open/mondrian/src/main/mondrian/xmla/impl/DefaultSaxWriter.java#13
(ktext) ====

2c2
< // $Id:
//open/mondrian/src/main/mondrian/xmla/impl/DefaultSaxWriter.java#12 $
---
> // $Id:
//open/mondrian/src/main/mondrian/xmla/impl/DefaultSaxWriter.java#13 $
6c6
< // Copyright (C) 2005-2010 Julian Hyde
---
> // Copyright (C) 2005-2011 Julian Hyde
201a202
>         assert tagName != null;

==== //open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaRequest.java#20
(ktext) ====

2c2
< // $Id:
//open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaRequest.java#19 $
---
> // $Id:
//open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaRequest.java#20 $
6c6
< // Copyright (C) 2005-2010 Julian Hyde
---
> // Copyright (C) 2005-2011 Julian Hyde
50c50,59
<     public DefaultXmlaRequest(final Element xmlaRoot, final String
roleName)
---
>     private final String username;
>     private final String password;
>     private final String sessionId;
> 
>     public DefaultXmlaRequest(
>         final Element xmlaRoot,
>         final String roleName,
>         final String username,
>         final String password,
>         final String sessionId)
54a64,70
>         this.username = username;
>         this.password = password;
>         this.sessionId = sessionId;
>     }
> 
>     public String getSessionId() {
>         return sessionId;
57c73,79
<     /* Interface implmentation */
---
>     public String getUsername() {
>         return username;
>     }
> 
>     public String getPassword() {
>         return password;
>     }

==== //open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaServlet.java#31
(ktext) ====

2c2
< // $Id:
//open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaServlet.java#30 $
---
> // $Id:
//open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaServlet.java#31 $
14,18c14,15
< import java.nio.channels.Channels;
< import java.nio.channels.ReadableByteChannel;
< import java.nio.channels.WritableByteChannel;
< import java.util.Map;
< import java.util.List;
---
> import java.nio.channels.*;
> import java.util.*;
20,26c17,19
< import javax.servlet.ServletConfig;
< import javax.servlet.ServletException;
< import javax.servlet.http.HttpServletRequest;
< import javax.servlet.http.HttpServletResponse;
< import javax.xml.parsers.DocumentBuilder;
< import javax.xml.parsers.DocumentBuilderFactory;
< import javax.xml.parsers.ParserConfigurationException;
---
> import javax.servlet.*;
> import javax.servlet.http.*;
> import javax.xml.parsers.*;
28a22
> import mondrian.xmla.Enumeration;
30,36c24,26
< import org.w3c.dom.Attr;
< import org.w3c.dom.Document;
< import org.w3c.dom.Node;
< import org.w3c.dom.NodeList;
< import org.w3c.dom.Element;
< import org.xml.sax.InputSource;
< import org.xml.sax.SAXException;
---
> import org.olap4j.impl.Olap4jUtil;
> import org.w3c.dom.*;
> import org.xml.sax.*;
41a32
>  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/impl/DefaultXmlaServlet.java#31 $
46a38,54
>     /**
>      * Servlet config parameter that determines whether the xmla servlet
>      * requires authenticated sessions.
>      */
>     private static final String REQUIRE_AUTHENTICATED_SESSIONS =
>         "requireAuthenticatedSessions";
> 
>     /**
>      * Simba O2X for some reason attempts to create many new xmla sessions
>      * during the same user interactions. We can mitigate that by creating
a
>      * session id which is a direct hash function of the credentials
supplied
>      * and the remote host IP.
>      */
>     private static final String REUSE_SESSION_IDS = "reuseSessionIds";
>     private static final String CONTEXT_XMLA_USERNAME = "username";
>     private static final String CONTEXT_XMLA_PASSWORD = "password";
> 
48a57,69
>     private boolean requireAuthenticatedSessions = false;
>     private boolean reuseSessionIds = false;
> 
>     /**
>      * Session properties, keyed by session ID. Currently just username
and
>      * password.
>      *
>      * <p>NOTE: There is no mechanism to remove entries from this map,
>      * so it will get larger if the server is up for a long time.</p>
>      */
>     private final Map<String, SessionInfo> sessionInfos =
>         new HashMap<String, SessionInfo>();
> 
51c72,78
<         domFactory = getDocumentBuilderFactory();
---
>         this.domFactory = getDocumentBuilderFactory();
>         this.requireAuthenticatedSessions =
>             Boolean.parseBoolean(
>
servletConfig.getInitParameter(REQUIRE_AUTHENTICATED_SESSIONS));
>         this.reuseSessionIds =
>             Boolean.parseBoolean(
>                 servletConfig.getInitParameter(REUSE_SESSION_IDS));
189c216,218
<      * See if there is a "mustUnderstand" header element.
---
>      * {@inheritDoc}
>      *
>      * <p>See if there is a "mustUnderstand" header element.
191,194c220,223
<      * add to context Map.
<      * <p>
<      * Excel 2000 and Excel XP generate both a BeginSession, Session and
<      * EndSession mustUnderstand==1
---
>      * add to context Map.</p>
>      *
>      * <p>Excel 2000 and Excel XP generate both a BeginSession, Session
and
>      * EndSession mustUnderstand=1
196c225
<      * Header elements and a NamespaceCompatibility mustUnderstand==0
---
>      * Header elements and a NamespaceCompatibility mustUnderstand=0
198c227
<      * namespace. Here we handle only the session Header elements
---
>      * namespace. Here we handle only the session Header elements.
199a229
>      * <p>We also handle the Security element.</p>
217a248,249
>             boolean authenticatedSession = false;
>             boolean beginSession = false;
220,221c252,256
<                 if (n instanceof Element) {
<                     Element e = (Element) n;
---
>                 if (!(n instanceof Element)) {
>                     continue;
>                 }
>                 Element e = (Element) n;
>                 String localName = e.getLocalName();
223,226c258,283
<                     // does the Element have a mustUnderstand attribute
<                     Attr attr =
e.getAttributeNode(SOAP_MUST_UNDERSTAND_ATTR);
<                     if (attr == null) {
<                         continue;
---
>                 if (localName.equals(XMLA_SECURITY)
>                     && NS_SOAP_SECEXT.equals(e.getNamespaceURI()))
>                 {
>                     // Example:
>                     //
>                     // <Security
xmlns="http://schemas.xmlsoap.org/ws/2002/04/secext">
>                     //   <UsernameToken>
>                     //     <Username>MICHELE</Username>
>                     //     <Password Type="PasswordText">ROSSI</Password>
>                     //   </UsernameToken>
>                     // </Security>
>                     // <BeginSession mustUnderstand="1"
>                     //   xmlns="urn:schemas-microsoft-com:xml-analysis" />
>                     NodeList childNodes = e.getChildNodes();
>                     Element userNameToken = (Element) childNodes.item(1);
>                     NodeList userNamePassword =
userNameToken.getChildNodes();
>                     Element username = (Element) userNamePassword.item(1);
>                     Element password = (Element) userNamePassword.item(3);
>                     String userNameStr =
>                         username.getChildNodes().item(0).getNodeValue();
>                     context.put(CONTEXT_XMLA_USERNAME, userNameStr);
>                     String passwordStr = "";
>                     if (password.getChildNodes().item(0) != null) {
>                         passwordStr =
>
password.getChildNodes().item(0).getNodeValue();
>                         context.put(CONTEXT_XMLA_PASSWORD, passwordStr);
228,233c285,291
<                     // Is its value "1"
<                     String mustUnderstandValue = attr.getValue();
<                     if ((mustUnderstandValue == null)
<                         || (!mustUnderstandValue.equals("1")))
<                     {
<                         continue;
---
>                     // [MROSSI] TODO we'd need to inject the
HttpServletRequest
>                     // into this method context.put("remote_host",
>                     // request.getRemoteHost());
>                     if ("".equals(passwordStr) || null == passwordStr) {
>                         LOGGER.error(
>                             "Security header for user [" + userNameStr
>                             + "] provided without password");
234a293,295
>                     authenticatedSession = true;
>                     continue;
>                 }
236c297,317
<                     // We've got a mustUnderstand attribute
---
>                 // Make sure Element has mustUnderstand=1 attribute.
>                 Attr attr = e.getAttributeNode(SOAP_MUST_UNDERSTAND_ATTR);
>                 boolean mustUnderstandValue =
>                     attr != null
>                     && attr.getValue() != null
>                     && attr.getValue().equals("1");
> 
>                 if (!mustUnderstandValue) {
>                     continue;
>                 }
> 
>                 // Is it an XMLA element
>                 if (!NS_XMLA.equals(e.getNamespaceURI())) {
>                     continue;
>                 }
>                 // So, an XMLA mustUnderstand-er
>                 // Do we know what to do with it
>                 // We understand:
>                 //    BeginSession
>                 //    Session
>                 //    EndSession
238,247c319,322
<                     // Is it an XMLA element
<                     if (! NS_XMLA.equals(e.getNamespaceURI())) {
<                         continue;
<                     }
<                     // So, an XMLA mustUnderstand-er
<                     // Do we know what to do with it
<                     // We understand:
<                     //    BeginSession
<                     //    Session
<                     //    EndSession
---
>                 String sessionIdStr;
>                 if (localName.equals(XMLA_BEGIN_SESSION)) {
>                     // generate SessionId
>                     sessionIdStr = generateSessionId(context);
249,252c324,327
<                     String sessionIdStr;
<                     String localName = e.getLocalName();
<                     if (localName.equals(XMLA_BEGIN_SESSION)) {
<                         // generate SessionId
---
>                     context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
>                     context.put(
>                         CONTEXT_XMLA_SESSION_STATE,
>                         CONTEXT_XMLA_SESSION_STATE_BEGIN);
254c329,332
<                         sessionIdStr = generateSessionId(context);
---
>                 } else if (localName.equals(XMLA_SESSION)) {
>                     // extract the SessionId attrs value and put into
>                     // context
>                     sessionIdStr = getSessionId(e, context);
256,259c334,337
<                         context.put(CONTEXT_XMLA_SESSION_ID,
sessionIdStr);
<                         context.put(
<                             CONTEXT_XMLA_SESSION_STATE,
<                             CONTEXT_XMLA_SESSION_STATE_BEGIN);
---
>                     context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
>                     context.put(
>                         CONTEXT_XMLA_SESSION_STATE,
>                         CONTEXT_XMLA_SESSION_STATE_WITHIN);
261,264c339,342
<                     } else if (localName.equals(XMLA_SESSION)) {
<                         // extract the SessionId attrs value and put into
<                         // context
<                         sessionIdStr = getSessionId(e, context);
---
>                 } else if (localName.equals(XMLA_END_SESSION)) {
>                     // extract the SessionId attrs value and put into
>                     // context
>                     sessionIdStr = getSessionId(e, context);
266c344,347
<                         context.put(CONTEXT_XMLA_SESSION_ID,
sessionIdStr);
---
>                     SessionInfo sessionInfo =
>                         getSessionInfo(sessionIdStr);
>                     if (sessionInfo != null) {
>                         context.put(CONTEXT_XMLA_USERNAME,
sessionInfo.user);
268,269c349,370
<                             CONTEXT_XMLA_SESSION_STATE,
<                             CONTEXT_XMLA_SESSION_STATE_WITHIN);
---
>                             CONTEXT_XMLA_PASSWORD, sessionInfo.password);
>                     }
>                     context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
>                     context.put(
>                         CONTEXT_XMLA_SESSION_STATE,
>                         CONTEXT_XMLA_SESSION_STATE_END);
> 
>                     // remove session info
>                     removeSessionInfo(sessionIdStr);
>                 } else {
>                     // error
>                     String msg =
>                         "Invalid XML/A message: Unknown "
>                         + "\"mustUnderstand\" XMLA Header element \""
>                         + localName
>                         + "\"";
>                     throw new XmlaException(
>                         MUST_UNDERSTAND_FAULT_FC,
>                         HSH_MUST_UNDERSTAND_CODE,
>                         HSH_MUST_UNDERSTAND_FAULT_FS,
>                         new RuntimeException(msg));
>                 }
271,274c372,381
<                     } else if (localName.equals(XMLA_END_SESSION)) {
<                         // extract the SessionId attrs value and put into
<                         // context
<                         sessionIdStr = getSessionId(e, context);
---
>                 StringBuilder buf = new StringBuilder(100);
>                 buf.append("<Session ");
>                 buf.append(XMLA_SESSION_ID);
>                 buf.append("=\"");
>                 buf.append(sessionIdStr);
>                 buf.append("\" ");
>                 buf.append("xmlns=\"");
>                 buf.append(NS_XMLA);
>                 buf.append("\" />");
>                 bytes = buf.toString().getBytes(encoding);
276,279c383,389
<                         context.put(CONTEXT_XMLA_SESSION_ID,
sessionIdStr);
<                         context.put(
<                             CONTEXT_XMLA_SESSION_STATE,
<                             CONTEXT_XMLA_SESSION_STATE_END);
---
>                 if (authenticatedSession) {
>                     String username =
>                         (String) context.get(CONTEXT_XMLA_USERNAME);
>                     String password =
>                         (String) context.get(CONTEXT_XMLA_PASSWORD);
>                     String sessionId =
>                         (String) context.get(CONTEXT_XMLA_SESSION_ID);
281,287c391,400
<                     } else {
<                         // error
<                         String msg =
<                             "Invalid XML/A message: Unknown "
<                             + "\"mustUnderstand\" XMLA Header element \""
<                             + localName
<                             + "\"";
---
>                     LOGGER.debug(
>                         "New authenticated session; storing credentials ["
>                         + username + "/********] for session id ["
>                         + sessionId + "]");
>                     saveSessionInfo(
>                         username,
>                         password,
>                         sessionId);
>                 } else {
>                     if (beginSession && requireAuthenticatedSessions) {
289,292c402,405
<                             MUST_UNDERSTAND_FAULT_FC,
<                             HSH_MUST_UNDERSTAND_CODE,
<                             HSH_MUST_UNDERSTAND_FAULT_FS,
<                             new RuntimeException(msg));
---
>                             XmlaConstants.CLIENT_FAULT_FC,
>                             XmlaConstants.CHH_AUTHORIZATION_CODE,
>                             XmlaConstants.CHH_AUTHORIZATION_FAULT_FS,
>                             new Exception("Session Credentials NOT
PROVIDED"));
294,304d406
< 
<                     StringBuilder buf = new StringBuilder(100);
<                     buf.append("<Session ");
<                     buf.append(XMLA_SESSION_ID);
<                     buf.append("=\"");
<                     buf.append(sessionIdStr);
<                     buf.append("\" ");
<                     buf.append("xmlns=\"");
<                     buf.append(NS_XMLA);
<                     buf.append("\" />");
<                     bytes = buf.toString().getBytes(encoding);
318a421,426
>     private void removeSessionInfo(String sessionIdStr) {
>         synchronized (sessionInfos) {
>             sessionInfos.remove(sessionIdStr);
>         }
>     }
> 
326,329c434,468
<             // what to do here, should Mondrian generate a Session Id?
<             // TODO: Maybe Mondrian ought to generate all Session Ids and
<             // not the callback.
<             return "";
---
>             if (reuseSessionIds) {
>                 // This is how we use the same session id (key to retrieve
a
>                 // connection) for requests with the same username coming
from
>                 // the same remote host. The password can't be part of the
>                 // string because if you use Simba and you don't tick
"save
>                 // password" in the login dialog then Simba only sends the
>                 // password with the first session that it opens - after
which
>                 // it sends something like the following:
>                 //
>                 // <Header>
>                 //   <Security
xmlns="http://schemas.xmlsoap.org/ws/2002/04/secext">
>                 //        <UsernameToken>
>                 //            <Username>michele</Username>
>                 //            <Password Type="PasswordText"/>
>                 //        </UsernameToken>
>                 //    </Security>
>                 //    <BeginSession
>                 //      xmlns="urn:schemas-microsoft-com:xml-analysis"
>                 //      mustUnderstand="1"/>
>                 // </Header>
>                 //
>                 // Note the Password element with no value in it.
>                 String sessionString =
>                     "session_"
>                     + context.get(CONTEXT_XMLA_USERNAME)
>                     + "_"
>                     + "_"
>                     + context.get("remote_host");
>                 return Long.toString(sessionString.hashCode(), 35);
>             } else {
>                 // Generate a semi-random new session ID.
>                 return Long.toString(
>                     -17L * System.nanoTime() + 11L *
System.currentTimeMillis(),
>                     35);
>             }
367c506
<             Element hdrElem = requestSoapParts[0];
---
>             Element hdrElem = requestSoapParts[0]; // not used
391c530,535
<             XmlaRequest xmlaReq = new DefaultXmlaRequest(xmlaReqElem,
roleName);
---
>             String username = (String) context.get(CONTEXT_XMLA_USERNAME);
>             String password = (String) context.get(CONTEXT_XMLA_PASSWORD);
>             String sessionId = (String)
context.get(CONTEXT_XMLA_SESSION_ID);
>             XmlaRequest xmlaReq =
>                 new DefaultXmlaRequest(
>                     xmlaReqElem, roleName, username, password, sessionId);
698a843,899
> 
>     private SessionInfo getSessionInfo(String sessionId) {
>         SessionInfo sessionInfo;
>         synchronized (sessionInfos) {
>             sessionInfo = sessionInfos.get(sessionId);
>         }
>         if (sessionInfo == null) {
>             LOGGER.error(
>                 "No login credentials for found for session [" + sessionId
>                 + "]");
>         } else {
>             LOGGER.debug(
>                 "Found existing credentials for session id ["
>                 + sessionId + "], username=[" + sessionInfo.user + "]");
>         }
>         return sessionInfo;
>     }
> 
>     private SessionInfo saveSessionInfo(
>         String username,
>         String password,
>         String sessionId)
>     {
>         synchronized (sessionInfos) {
>             SessionInfo sessionInfo = sessionInfos.get(sessionId);
>             if (sessionInfo != null
>                 && Olap4jUtil.equal(sessionInfo.user, username))
>             {
>                 // Overwrite the password, but only if it is non-empty.
>                 // (Sometimes Simba sends the credentials object again
>                 // but without a password.)
>                 if (password != null && password.length() > 0) {
>                     sessionInfo =
>                         new SessionInfo(sessionId, username, password);
>                     sessionInfos.put(sessionId, sessionInfo);
>                 }
>             } else {
>                 // A credentials object was stored against the provided
session
>                 // ID but the username didn't match, so create a new
holder.
>                 sessionInfo = new SessionInfo(sessionId, username,
password);
>                 sessionInfos.put(sessionId, sessionInfo);
>             }
>             return sessionInfo;
>         }
>     }
> 
>     private static class SessionInfo {
>         final String id;
>         final String user;
>         final String password;
> 
>         public SessionInfo(String id, String user, String password) {
>             this.id = id;
>             this.user = user;
>             this.password = password;
>         }
>     }

==== //open/mondrian/src/main/mondrian/xmla/impl/Olap4jXmlaServlet.java#2
(ktext) ====

1a2
> // $Id:
//open/mondrian/src/main/mondrian/xmla/impl/Olap4jXmlaServlet.java#2 $
14a16,19
> import org.apache.commons.dbcp.BasicDataSource;
> import org.apache.commons.dbcp.DelegatingConnection;
> import org.apache.log4j.Logger;
> 
19a25
> import java.lang.reflect.InvocationHandler;
21a28
> import java.lang.reflect.Proxy;
23d29
< import java.sql.DriverManager;
25,27c31
< import java.util.LinkedHashMap;
< import java.util.Map;
< import java.util.Properties;
---
> import java.util.*;
32c36
<  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/impl/Olap4jXmlaServlet.java#1 $
---
>  * @version $Id:
//open/mondrian/src/main/mondrian/xmla/impl/Olap4jXmlaServlet.java#2 $
36a41,43
>     private static final Logger LOGGER =
>         Logger.getLogger(Olap4jXmlaServlet.class);
> 
45a53,80
>     private static final String
>         OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_RESPONSE =
>         "OlapDriverUsePreConfiguredDiscoverDatasourcesResponse";
> 
>     private static final String
OLAP_DRIVER_IDLE_CONNECTIONS_TIMEOUT_MINUTES =
>         "OlapDriverIdleConnectionsTimeoutMinutes";
> 
>     private static final String
>         OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_PREFIX =
>         "OlapDriverDiscoverDatasources.";
> 
>     /**
>      * Name of property used by JDBC to hold user name.
>      */
>     private static final String JDBC_USER = "user";
> 
>     /**
>      * Name of property used by JDBC to hold password.
>      */
>     private static final String JDBC_PASSWORD = "password";
> 
>     /** idle connections are cleaned out after 5 minutes by default */
>     private static final int DEFAULT_IDLE_CONNECTIONS_TIMEOUT_MS =
>         5 * 60 * 1000;
> 
>     private static final String OLAP_DRIVER_MAX_NUM_CONNECTIONS_PER_USER =
>         "OlapDriverMaxNumConnectionsPerUser";
> 
90a126,148
>         final String olap4jUsePreConfiguredDiscoverDatasourcesRes =
>             servletConfig.getInitParameter(
>                 OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_RESPONSE);
>         boolean hardcodedDiscoverDatasources =
>             olap4jUsePreConfiguredDiscoverDatasourcesRes != null
>             && Boolean.parseBoolean(
>                 olap4jUsePreConfiguredDiscoverDatasourcesRes);
> 
>         final String idleConnTimeoutStr =
>             servletConfig.getInitParameter(
>                 OLAP_DRIVER_IDLE_CONNECTIONS_TIMEOUT_MINUTES);
>         final int idleConnectionsCleanupTimeoutMs =
>             idleConnTimeoutStr != null
>             ? Integer.parseInt(idleConnTimeoutStr) * 60 * 1000
>             : DEFAULT_IDLE_CONNECTIONS_TIMEOUT_MS;
> 
>         final String maxNumConnPerUserStr =
>             servletConfig.getInitParameter(
>                 OLAP_DRIVER_MAX_NUM_CONNECTIONS_PER_USER);
>         int maxNumConnectionsPerUser =
>             maxNumConnPerUserStr != null
>             ? Integer.parseInt(maxNumConnPerUserStr)
>             : 1;
96c154,162
<             return new Olap4jConnectionFactory(
---
>             final Map<String, Object> ddhcRes;
>             if (hardcodedDiscoverDatasources) {
>                 ddhcRes =
>
getDiscoverDatasourcesPreConfiguredResponse(servletConfig);
>             } else {
>                 ddhcRes = null;
>             }
> 
>             return new Olap4jPoolingConnectionFactory(
99c165,168
<                 connectionProperties);
---
>                 connectionProperties,
>                 idleConnectionsCleanupTimeoutMs,
>                 maxNumConnectionsPerUser,
>                 ddhcRes);
111,116c180,210
<     /**
<      * A {@link mondrian.xmla.XmlaHandler.ConnectionFactory}
implementation that
<      * creates a {@link org.olap4j.OlapConnection} from the specified
olap4j
<      * driver.
<      */
<     private static class Olap4jConnectionFactory
---
>     private static Map<String, Object>
>     getDiscoverDatasourcesPreConfiguredResponse(
>         ServletConfig servletConfig)
>     {
>         final Map<String, Object> map = new LinkedHashMap<String,
Object>();
>         foo(map, "DataSourceName", servletConfig, "dataSourceName");
>         foo(
>             map, "DataSourceDescription",
>             servletConfig, "dataSourceDescription");
>         foo(map, "URL", servletConfig, "url");
>         foo(map, "DataSourceInfo", servletConfig, "dataSourceInfo");
>         foo(map, "ProviderName", servletConfig, "providerName");
>         foo(map, "ProviderType", servletConfig, "providerType");
>         foo(map, "AuthenticationMode", servletConfig,
"authenticationMode");
>         return map;
>     }
> 
>     private static void foo(
>         Map<String, Object> map,
>         String targetProp,
>         ServletConfig servletConfig,
>         String sourceProp)
>     {
>         final String value =
>             servletConfig.getInitParameter(
>                 OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_PREFIX
>                 + sourceProp);
>         map.put(targetProp, value);
>     }
> 
>     private static class Olap4jPoolingConnectionFactory
120c214,220
<         private final Map<String, String> connectionProperties;
---
>         private final Properties connProperties;
>         private final Map<String, Object> discoverDatasourcesResponse;
>         private final String olap4jDriverClassName;
>         private final Map<String, BasicDataSource> datasourcesPool =
>             new HashMap<String, BasicDataSource>();
>         private final int idleConnectionsCleanupTimeoutMs;
>         private final int maxPerUserConnectionCount;
123c223
<          * Creates an Olap4jConnectionFactory.
---
>          * Creates an Olap4jPoolingConnectionFactory.
127a228,233
>          * @param maxPerUserConnectionCount max number of connections to
create
>          *     for every different username
>          * @param idleConnectionsCleanupTimeoutMs pooled connections
inactive
>          *     for longer than this period of time can be cleaned up
>          * @param discoverDatasourcesResponse Pre-configured response to
>          *     DISCOVER_DATASOURCES request, or null
130c236
<         public Olap4jConnectionFactory(
---
>         public Olap4jPoolingConnectionFactory(
133c239,242
<             final Map<String, String> connectionProperties)
---
>             final Map<String, String> connectionProperties,
>             final int idleConnectionsCleanupTimeoutMs,
>             final int maxPerUserConnectionCount,
>             final Map<String, Object> discoverDatasourcesResponse)
136a246,249
>             this.maxPerUserConnectionCount = maxPerUserConnectionCount;
>             this.idleConnectionsCleanupTimeoutMs =
>                 idleConnectionsCleanupTimeoutMs;
>             this.olap4jDriverClassName = olap4jDriverClassName;
138c251,279
<             this.connectionProperties = connectionProperties;
---
>             this.connProperties = new Properties();
>             this.connProperties.putAll(connectionProperties);
>             this.discoverDatasourcesResponse =
discoverDatasourcesResponse;
> 
>             // Create an eviction task that runs for all our
BasicDataSource
>             // instances; this saves threads compared to having an evictor
>             // thread per pool.
>             Timer connectionEvictionsTimer = new Timer();
>             TimerTask evictionTask = new TimerTask() {
>                 public void run() {
>                     synchronized (datasourcesPool) {
>                         for (BasicDataSource bds :
datasourcesPool.values()) {
>                             try {
>                                 bds.getConnectionPool().evict();
>                             } catch (Exception e) {
>                                 LOGGER.error(
>                                     "Exception [" + e
>                                     + "] while running evict on [" + bds
>                                     + "]");
>                             }
>                         }
>                     }
>                 }
>             };
>             // Run the eviction task every minute.
>             //
>             // REVIEW: Is the timer task ever shut down? It should be shut
down
>             // on Servlet.destroy()?
>             connectionEvictionsTimer.schedule(evictionTask, 60000, 60 *
1000);
142,145c283,286
<             final String catalog,
<             final String schema,
<             final String roleName,
<             final Properties props)
---
>             String catalog,
>             String schema,
>             String roleName,
>             Properties props)
148,153c289,327
<             final Properties properties = new Properties();
<             properties.putAll(connectionProperties);
<             Connection connection =
<                 DriverManager.getConnection(
<                     olap4jDriverConnectionString,
<                     properties);
---
>             final String user = props.getProperty(JDBC_USER);
>             final String pwd = props.getProperty(JDBC_PASSWORD);
> 
>             // note: this works also for un-authenticated connections;
they will
>             // simply all be created by the same BasicDataSource object
>             final String dataSourceKey = user + "_" + pwd;
> 
>             BasicDataSource bds;
>             synchronized (datasourcesPool) {
>                 bds = datasourcesPool.get(dataSourceKey);
>                 if (bds == null) {
>                     bds = new BasicDataSource() {
>                         {
>                             connectionProperties.putAll(connProperties);
>                         }
>                     };
>                     bds.setDefaultReadOnly(true);
>                     bds.setDriverClassName(olap4jDriverClassName);
>                     bds.setPassword(pwd);
>                     bds.setUsername(user);
>                     bds.setUrl(olap4jDriverConnectionString);
>                     bds.setPoolPreparedStatements(false);
>                     bds.setMaxIdle(maxPerUserConnectionCount);
>                     bds.setMaxActive(maxPerUserConnectionCount);
>                     bds.setMinEvictableIdleTimeMillis(
>                         idleConnectionsCleanupTimeoutMs);
>                     bds.setAccessToUnderlyingConnectionAllowed(true);
>                     bds.setInitialSize(1);
> 
>                     if (catalog != null) {
>                         bds.setDefaultCatalog(catalog);
>                     }
>                     datasourcesPool.put(dataSourceKey, bds);
>                 }
>             }
> 
>             Connection connection = bds.getConnection();
>             DelegatingConnection dc = (DelegatingConnection) connection;
>             Connection underlyingOlapConnection =
dc.getInnermostDelegate();
155c329
<                 unwrap(connection, OlapConnection.class);
---
>                 unwrap(underlyingOlapConnection, OlapConnection.class);
156a331,336
>             if (LOGGER.isDebugEnabled()) {
>                 LOGGER.debug(
>                     "Obtained connection object [" + olapConnection
>                     + "] (ext pool wrapper " + connection + ") for key "
>                     + dataSourceKey);
>             }
166c346,352
<             return olapConnection;
---
> 
>             return createDelegatingOlapConnection(connection,
olapConnection);
>         }
> 
>         public Map<String, Object>
getPreConfiguredDiscoverDatasourcesResponse()
>         {
>             return discoverDatasourcesResponse;
238a425,458
> 
>     /**
>      * Returns something that implements {@link OlapConnection} but still
>      * behaves as the wrapper returned by the connection pool.
>      *
>      * <p>In other words we want the "close" method to play nice and do
all the
>      * pooling actions while we want all the olap methods to execute
directly on
>      * the un-wrapped OlapConnection object.
>      */
>     private static OlapConnection createDelegatingOlapConnection(
>         final Connection connection,
>         final OlapConnection olapConnection)
>     {
>         return (OlapConnection) Proxy.newProxyInstance(
>             olapConnection.getClass().getClassLoader(),
>             new Class[] {OlapConnection.class},
>             new InvocationHandler() {
>                 public Object invoke(
>                     Object proxy,
>                     Method method,
>                     Object[] args)
>                     throws Throwable
>                 {
>                     if (OlapConnection.class.isAssignableFrom(
>                             method.getClass()))
>                     {
>                         return method.invoke(olapConnection, args);
>                     } else {
>                         return method.invoke(connection, args);
>                     }
>                 }
>             }
>         );
>     }

==== //open/mondrian/testsrc/main/mondrian/rolap/NonEmptyTest.java#150
(ktext) ====

1a2
> // $Id: //open/mondrian/testsrc/main/mondrian/rolap/NonEmptyTest.java#150
$
42c43
<  * @version $Id:
//open/mondrian/testsrc/main/mondrian/rolap/NonEmptyTest.java#149 $
---
>  * @version $Id:
//open/mondrian/testsrc/main/mondrian/rolap/NonEmptyTest.java#150 $
4851a4853,4893
>    }
> 
>    public void testBugY() throws Exception {
>         final String sqlOracle =
>             "select \"store\".\"store_country\" as \"c0\""
>             + " from \"store\" \"store\""
>             + " group by \"store\".\"store_country\" order by
\"store\".\"store_country\" ASC";
>         SqlPattern[] patterns = {
>                 new SqlPattern(
>                     Dialect.DatabaseProduct.ORACLE,
>                     sqlOracle,
>                     sqlOracle.length())
>             };
> 
>         final String query =
>             "With Set [*NATIVE_CJ_SET] as
'NonEmptyCrossJoin([*BASE_MEMBERS_Product],[*BASE_MEMBERS_Fact
Attribute])'\n"
>             + "Set [*SORTED_ROW_AXIS] as
'Order([*CJ_ROW_AXIS],[Product].CurrentMember.OrderKey,BASC,[Store
Country].CurrentMember.OrderKey,BASC)'\n"
>             + "Set [*BASE_MEMBERS_Product] as '[Product].[Product
Family].Members'\n"
>             + "Set [*BASE_MEMBERS_Measures] as
'{[Measures].[*FORMATTED_MEASURE_0]}'\n"
>             + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET],
{([Product].currentMember,[Store Country].currentMember)})'\n"
>             + "Set [*BASE_MEMBERS_Fact Attribute] as '{[Store
Country].[USA]}'\n"
>             + "Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]'\n"
>             + "Member [Measures].[*FORMATTED_MEASURE_0] as
'[Measures].[Unit Sales]', FORMAT_STRING = 'Standard', SOLVE_ORDER=400\n"
>             + "Select [*BASE_MEMBERS_Measures] on columns, Non Empty
[*SORTED_ROW_AXIS] on rows\n"
>             + "From [Sales]";
> 
>         final TestContext context =
>             TestContext.createSubstitutingCube(
>                 "Sales",
>                 "<DimensionUsage name=\"Store\" highCardinality=\"true\"
source=\"Store\" foreignKey=\"store_id\"/>",
>                 null,
>                 null,
>                 null);
> 
>         assertQuerySqlOrNot(
>             context,
>             query,
>             patterns,
>             true,
>             true,
>             true);

==== //open/mondrian/testsrc/main/mondrian/xmla/test/XmlaTest.java#24
(ktext) ====

2c2
< // $Id: //open/mondrian/testsrc/main/mondrian/xmla/test/XmlaTest.java#23 $
---
> // $Id: //open/mondrian/testsrc/main/mondrian/xmla/test/XmlaTest.java#24 $
6c6
< // Copyright (C) 2005-2010 Julian Hyde
---
> // Copyright (C) 2005-2011 Julian Hyde
43c43
<  * @version $Id:
//open/mondrian/testsrc/main/mondrian/xmla/test/XmlaTest.java#23 $
---
>  * @version $Id:
//open/mondrian/testsrc/main/mondrian/xmla/test/XmlaTest.java#24 $
146c146,147
<         XmlaRequest request = new DefaultXmlaRequest(requestElem, null);
---
>         XmlaRequest request =
>             new DefaultXmlaRequest(requestElem, null, null, null, null);



More information about the Mondrian mailing list