Skip to content

Commit

Permalink
MONDRIAN: fixes to xmla servlet
Browse files Browse the repository at this point in the history
[git-p4: depot-paths = "//open/mondrian/": change = 14566]
  • Loading branch information
Michele Rossi committed Aug 25, 2011
1 parent 1011089 commit 514225c
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 110 deletions.
27 changes: 27 additions & 0 deletions src/main/mondrian/xmla/XmlaHandler.java
Expand Up @@ -25,6 +25,8 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
Expand Down Expand Up @@ -73,6 +75,31 @@ public static XmlaExtra getExtra(OlapConnection connection) {
} catch (SQLException e) {
// Connection cannot provide an XmlaExtra. Fall back and give a
// default implementation.
} catch (UndeclaredThrowableException ute) {
//
// Note: this is necessary because we use a dynamic proxy for the
// connection.
// I could not catch and un-wrap the Undeclared Throwable within
// the proxy.
// The exception comes out here and I couldn't find any better
// ways to deal with it.
//
// The undeclared throwable contains an Invocation Target Exception
// which in turns contains the real exception thrown by the "unwrap"
// method, for example OlapException.
//

Throwable cause = ute.getCause();
if (cause instanceof InvocationTargetException) {
cause = cause.getCause();
}

// this maintains the original behaviour: don't catch exceptions
// that are not subclasses of SQLException

if (! (cause instanceof SQLException)) {
throw ute;
}
}
return new XmlaExtraImpl();
}
Expand Down
197 changes: 89 additions & 108 deletions src/main/mondrian/xmla/impl/DefaultXmlaServlet.java
Expand Up @@ -9,21 +9,46 @@
*/
package mondrian.xmla.impl;

import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.HashMap;
import java.util.Map;

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.*;

import mondrian.xmla.*;
import mondrian.xmla.Enumeration;
import mondrian.xmla.SaxWriter;
import mondrian.xmla.XmlaConstants;
import mondrian.xmla.XmlaException;
import mondrian.xmla.XmlaRequest;
import mondrian.xmla.XmlaRequestCallback;
import mondrian.xmla.XmlaResponse;
import mondrian.xmla.XmlaServlet;
import mondrian.xmla.XmlaUtil;

import org.olap4j.impl.Olap4jUtil;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
* Default implementation of XML/A servlet.
Expand All @@ -42,25 +67,13 @@ public abstract class DefaultXmlaServlet extends XmlaServlet {
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 DocumentBuilderFactory domFactory = null;

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>();
Expand All @@ -71,12 +84,9 @@ public void init(ServletConfig servletConfig) throws ServletException {
this.requireAuthenticatedSessions =
Boolean.parseBoolean(
servletConfig.getInitParameter(REQUIRE_AUTHENTICATED_SESSIONS));
this.reuseSessionIds =
Boolean.parseBoolean(
servletConfig.getInitParameter(REUSE_SESSION_IDS));
}

protected DocumentBuilderFactory getDocumentBuilderFactory() {
protected static DocumentBuilderFactory getDocumentBuilderFactory() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
Expand Down Expand Up @@ -237,6 +247,7 @@ protected void handleSoapHeader(
if ((hdrElem == null) || (! hdrElem.hasChildNodes())) {
return;
}

String encoding = response.getCharacterEncoding();

byte[] bytes = null;
Expand Down Expand Up @@ -275,16 +286,16 @@ protected void handleSoapHeader(
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);
}
// [MROSSI] TODO we'd need to inject the HttpServletRequest
// into this method context.put("remote_host",
// request.getRemoteHost());

context.put(CONTEXT_XMLA_PASSWORD, passwordStr);

if ("".equals(passwordStr) || null == passwordStr) {
LOGGER.error(
LOGGER.warn(
"Security header for user [" + userNameStr
+ "] provided without password");
}
Expand Down Expand Up @@ -316,7 +327,6 @@ protected void handleSoapHeader(

String sessionIdStr;
if (localName.equals(XMLA_BEGIN_SESSION)) {
// generate SessionId
sessionIdStr = generateSessionId(context);

context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
Expand All @@ -325,34 +335,28 @@ protected void handleSoapHeader(
CONTEXT_XMLA_SESSION_STATE_BEGIN);

} else if (localName.equals(XMLA_SESSION)) {
// extract the SessionId attrs value and put into
// context
sessionIdStr = getSessionId(e, context);
sessionIdStr = getSessionIdFromRequest(e, context);

context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
context.put(
CONTEXT_XMLA_SESSION_STATE,
CONTEXT_XMLA_SESSION_STATE_WITHIN);

} else if (localName.equals(XMLA_END_SESSION)) {
// extract the SessionId attrs value and put into
// context
sessionIdStr = getSessionId(e, context);
SessionInfo sessionInfo = getSessionInfo(sessionIdStr);

SessionInfo sessionInfo =
getSessionInfo(sessionIdStr);
if (sessionInfo != null) {
context.put(CONTEXT_XMLA_USERNAME, sessionInfo.user);
context.put(
CONTEXT_XMLA_PASSWORD, sessionInfo.password);
CONTEXT_XMLA_PASSWORD,
sessionInfo.password);
}

context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
context.put(
CONTEXT_XMLA_SESSION_STATE,
CONTEXT_XMLA_SESSION_STATE_WITHIN);

} else if (localName.equals(XMLA_END_SESSION)) {
sessionIdStr = getSessionIdFromRequest(e, context);
context.put(
CONTEXT_XMLA_SESSION_STATE,
CONTEXT_XMLA_SESSION_STATE_END);

// remove session info
removeSessionInfo(sessionIdStr);
} else {
// error
String msg =
Expand Down Expand Up @@ -390,6 +394,7 @@ protected void handleSoapHeader(
"New authenticated session; storing credentials ["
+ username + "/********] for session id ["
+ sessionId + "]");

saveSessionInfo(
username,
password,
Expand All @@ -416,60 +421,26 @@ protected void handleSoapHeader(
}
}

private void removeSessionInfo(String sessionIdStr) {
synchronized (sessionInfos) {
sessionInfos.remove(sessionIdStr);
}
}

protected String generateSessionId(Map<String, Object> context) {

for (XmlaRequestCallback callback : getCallbacks()) {
final String sessionId = callback.generateSessionId(context);
if (sessionId != null) {
return sessionId;
}
}
/*
* Fallback to the default session ID generation algorithm.
*/
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);
}


// Generate a pseudo-random new session ID.
return Long.toString(17L * System.nanoTime()
+
3L * System.currentTimeMillis(), 35);
}

protected String getSessionId(Element e, Map<String, Object> context)

private static String getSessionIdFromRequest(Element e,
Map<String, Object> context)
throws Exception
{
// extract the SessionId attrs value and put into context
Expand All @@ -482,16 +453,17 @@ protected String getSessionId(Element e, Map<String, Object> context)
+ XMLA_SESSION_ID
+ " attribute");
}
String value = attr.getValue();
if (value == null) {

String sessionId = attr.getValue();
if (sessionId == null) {
throw new SAXException(
"Invalid XML/A message: "
+ XMLA_SESSION
+ " Header element with "
+ XMLA_SESSION_ID
+ " attribute but no attribute value");
}
return value;
return sessionId;
}

protected void handleSoapBody(
Expand Down Expand Up @@ -842,18 +814,27 @@ protected void handleFault(
}

private SessionInfo getSessionInfo(String sessionId) {
SessionInfo sessionInfo;
if (sessionId == null) {
return null;
}

SessionInfo sessionInfo = null;

synchronized (sessionInfos) {
sessionInfo = sessionInfos.get(sessionId);
}

if (sessionInfo == null) {
LOGGER.error(
"No login credentials for found for session [" + sessionId
+ "]");
"No login credentials for found for session ["
+
sessionId + "]");
} else {
LOGGER.debug(
"Found existing credentials for session id ["
+ sessionId + "], username=[" + sessionInfo.user + "]");
"Found credentials for session id ["
+ sessionId
+ "], username=[" + sessionInfo.user
+ "] in servlet cache");
}
return sessionInfo;
}
Expand All @@ -873,30 +854,30 @@ private SessionInfo saveSessionInfo(
// but without a password.)
if (password != null && password.length() > 0) {
sessionInfo =
new SessionInfo(sessionId, username, password);
new SessionInfo(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);
sessionInfo = new SessionInfo(username, password);
sessionInfos.put(sessionId, sessionInfo);
}
return sessionInfo;
}
}

/**
* Holds authentication credentials of a XMLA session.
*/
private static class SessionInfo {
final String id;
final String user;
final String password;

public SessionInfo(String id, String user, String password) {
this.id = id;
public SessionInfo(String user, String password)
{
this.user = user;
this.password = password;
}
}
}

// End DefaultXmlaServlet.java

0 comments on commit 514225c

Please sign in to comment.