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 (DOT) 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 (DOT) 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;
> \n...Message truncated due to length...