diff --git a/src/main/java/mondrian/xmla/RowsetDefinition.java b/src/main/java/mondrian/xmla/RowsetDefinition.java index ced6df8..738f1b8 100644 --- a/src/main/java/mondrian/xmla/RowsetDefinition.java +++ b/src/main/java/mondrian/xmla/RowsetDefinition.java @@ -10,6 +10,8 @@ */ package mondrian.xmla; +import mondrian.xmla.XmlaSessionManager.XmlaSession; + import org.olap4j.xmla.server.impl.Composite; import org.olap4j.xmla.server.impl.Util; @@ -62,7 +64,7 @@ public enum RowsetDefinition { * Not supported */ DISCOVER_DATASOURCES( - 0, + 1, "Returns a list of XML for Analysis data sources available on the " + "server or Web Service.", new Column[] { @@ -221,7 +223,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ DISCOVER_PROPERTIES( - 1, + 4, "Returns a list of information and values about the requested " + "properties that are supported by the specified data source " + "provider.", @@ -249,7 +251,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ DISCOVER_KEYWORDS( - 4, + 5, "Returns an XML list of keywords reserved by the provider.", new Column[] { DiscoverKeywordsRowset.Keyword, @@ -270,7 +272,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ DISCOVER_LITERALS( - 5, + 6, "Returns information about literals supported by the provider.", new Column[] { DiscoverLiteralsRowset.LiteralName, @@ -286,6 +288,55 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { } }, + /** + * + */ + DISCOVER_SESSIONS( + 7, + "Returns information about the open sessions on the server.", + new Column[] { + DiscoverSessionsRowset.SessionId, + DiscoverSessionsRowset.SessionCommandCount, + DiscoverSessionsRowset.SessionElapsedTimeMs, + DiscoverSessionsRowset.SessionLastCommand, + DiscoverSessionsRowset.SessionLastCommandElapsedTimeMs, + DiscoverSessionsRowset.SessionLastCommandStartTime, + DiscoverSessionsRowset.SessionLastCommandEndTime, + DiscoverSessionsRowset.SessionStartTime, + DiscoverSessionsRowset.SessionStatus, + }, + new Column[] { + DiscoverSessionsRowset.SessionId, + }) + { + public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { + return new DiscoverSessionsRowset(request, handler); + } + }, + + /** + * + */ + DISCOVER_COMMANDS( + 8, + "Returns information about the currently executing or last executed commands in open sessions on the server.", + new Column[] { + DiscoverCommandsRowset.SessionId, + DiscoverCommandsRowset.SessionCommandCount, + DiscoverCommandsRowset.CommandStartTime, + DiscoverCommandsRowset.CommandElapsedTimeMs, + DiscoverCommandsRowset.CommandText, + DiscoverCommandsRowset.CommandEndTime, + }, + new Column[] { + DiscoverCommandsRowset.SessionId, + }) + { + public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { + return new DiscoverCommandsRowset(request, handler); + } + }, + /** * * @@ -295,7 +346,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ DBSCHEMA_CATALOGS( - 6, + 9, "Identifies the physical attributes associated with catalogs " + "accessible from the provider.", new Column[] { @@ -323,7 +374,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * COLUMN_OLAP_TYPE */ DBSCHEMA_COLUMNS( - 7, null, + 10, null, new Column[] { DbschemaColumnsRowset.TableCatalog, DbschemaColumnsRowset.TableSchema, @@ -359,7 +410,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ DBSCHEMA_PROVIDER_TYPES( - 8, null, + 11, null, new Column[] { DbschemaProviderTypesRowset.TypeName, DbschemaProviderTypesRowset.DataType, @@ -385,7 +436,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { }, DBSCHEMA_SCHEMATA( - 8, null, + 12, null, new Column[] { DbschemaSchemataRowset.CatalogName, DbschemaSchemataRowset.SchemaName, @@ -415,7 +466,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ DBSCHEMA_TABLES( - 9, null, + 13, null, new Column[] { DbschemaTablesRowset.TableCatalog, DbschemaTablesRowset.TableSchema, @@ -450,7 +501,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ DBSCHEMA_TABLES_INFO( - 10, null, + 14, null, new Column[] { DbschemaTablesInfoRowset.TableCatalog, DbschemaTablesInfoRowset.TableSchema, @@ -501,7 +552,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ MDSCHEMA_ACTIONS( - 11, null, new Column[] { + 15, null, new Column[] { MdschemaActionsRowset.CatalogName, MdschemaActionsRowset.SchemaName, MdschemaActionsRowset.CubeName, @@ -545,7 +596,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * ANNOTATIONS */ MDSCHEMA_CUBES( - 12, null, + 16, null, new Column[] { MdschemaCubesRowset.CatalogName, MdschemaCubesRowset.SchemaName, @@ -601,7 +652,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Default restriction is a value of 1. */ MDSCHEMA_DIMENSIONS( - 13, null, + 17, null, new Column[] { MdschemaDimensionsRowset.CatalogName, MdschemaDimensionsRowset.SchemaName, @@ -656,7 +707,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * CAPTION The display caption for the function. */ MDSCHEMA_FUNCTIONS( - 14, null, + 18, null, new Column[] { MdschemaFunctionsRowset.FunctionName, MdschemaFunctionsRowset.Description, @@ -709,7 +760,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * INSTANCE_SELECTION */ MDSCHEMA_HIERARCHIES( - 15, null, + 19, null, new Column[] { MdschemaHierarchiesRowset.CatalogName, MdschemaHierarchiesRowset.SchemaName, @@ -788,7 +839,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * */ MDSCHEMA_LEVELS( - 16, null, + 20, null, new Column[] { MdschemaLevelsRowset.CatalogName, MdschemaLevelsRowset.SchemaName, @@ -854,7 +905,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * DEFAULT_FORMAT_STRING */ MDSCHEMA_MEASURES( - 17, null, + 21, null, new Column[] { MdschemaMeasuresRowset.CatalogName, MdschemaMeasuresRowset.SchemaName, @@ -916,7 +967,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * Not supported */ MDSCHEMA_MEMBERS( - 18, null, + 22, null, new Column[] { MdschemaMembersRowset.CatalogName, MdschemaMembersRowset.SchemaName, @@ -1002,7 +1053,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * PROPERTY_IS_VISIBLE */ MDSCHEMA_PROPERTIES( - 19, null, + 23, null, new Column[] { MdschemaPropertiesRowset.CatalogName, MdschemaPropertiesRowset.SchemaName, @@ -1046,7 +1097,7 @@ public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { * SET_DISPLAY_FOLDER */ MDSCHEMA_SETS( - 20, null, + 24, null, new Column[] { MdschemaSetsRowset.CatalogName, MdschemaSetsRowset.SchemaName, @@ -2110,6 +2161,248 @@ protected void setProperty( } } + static class DiscoverSessionsRowset extends Rowset { + private final Util.Predicate1 sessionIdCond; + private final Util.Predicate1 sessionStatusCond; + private final Util.Predicate1 sessionElapsedTimeMsCond; + + DiscoverSessionsRowset(XmlaRequest request, XmlaHandler handler) { + super(DISCOVER_SESSIONS, request, handler); + sessionIdCond = makeCondition(SessionId); + sessionStatusCond = makeCondition(SessionStatus); + sessionElapsedTimeMsCond = makeCondition(SessionElapsedTimeMs); + } + + private static final Column SessionId = new Column( + "SESSION_ID", + Type.String, + null, + Column.RESTRICTION, + Column.REQUIRED, + "The session unique identifier, as a GUID."); + + private static final Column SessionCommandCount = new Column( + "SESSION_COMMAND_COUNT", + Type.Integer, + null, + Column.NOT_RESTRICTION, + Column.REQUIRED, + "The number of commands that started execution since the beginning of the session."); + + private static final Column SessionElapsedTimeMs = new Column( + "SESSION_ELAPSED_TIME_MS", + Type.UnsignedLong, + null, + Column.RESTRICTION, + Column.REQUIRED, + "Elapsed time, in milliseconds, since the start of the session."); + + private static final Column SessionLastCommand = new Column( + "SESSION_LAST_COMMAND", + Type.String, + null, + Column.NOT_RESTRICTION, + Column.OPTIONAL, + "The text of the current command executing or the last command executed."); + + private static final Column SessionLastCommandElapsedTimeMs = new Column( + "SESSION_LAST_COMMAND_ELAPSED_TIME_MS", + Type.UnsignedLong, + null, + Column.NOT_RESTRICTION, + Column.OPTIONAL, + "The elapsed time, in milliseconds, since the start of SESSION_LAST_COMMAND."); + + private static final Column SessionLastCommandStartTime = new Column( + "SESSION_LAST_COMMAND_START_TIME", + Type.DateTime, + null, + Column.NOT_RESTRICTION, + Column.OPTIONAL, + "The UTC server time at the moment the last command started executing."); + + private static final Column SessionLastCommandEndTime = new Column( + "SESSION_LAST_COMMAND_END_TIME", + Type.DateTime, + null, + Column.NOT_RESTRICTION, + Column.OPTIONAL, + "The UTC server time at the moment the last command finished executing."); + + private static final Column SessionStartTime = new Column( + "SESSION_START_TIME", + Type.DateTime, + null, + Column.NOT_RESTRICTION, + Column.REQUIRED, + "The date and time the session started as UTC time to the server."); + + private static final Column SessionStatus = new Column( + "SESSION_STATUS", + Type.Integer, + null, + Column.RESTRICTION, + Column.REQUIRED, + "The activity status of the session.\n" + + "0 means \"Idle\": No current activity is ongoing.\n" + + "1 means \"Active\": The session is executing some requested task.\n" + + "2 means is \"Blocked\": The session is waiting for some resource to continue executing the suspended task.\n" + + "3 means \"Cancelled\": The session has been tagged as cancelled."); + + @Override + protected boolean needConnection() { + return false; + } + + @Override + protected void populateImpl( + XmlaResponse response, OlapConnection connection, List rows) + throws XmlaException + { + XmlaSessionManager manager = XmlaSessionManager.getInstance(); + Map sessions = manager.getAllSessions(); + long now = System.currentTimeMillis(); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + for (String sessionId : sessions.keySet()) { + if (!sessionIdCond.test(sessionId)) { + continue; + } + XmlaSessionManager.XmlaSession session = sessions.get(sessionId); + int totalCommands = session.getTotalCommandCount(); + int currentCommands = session.getCurrentCommandCount(); + int status; + if (session.isCancelled()) { + status = 3; + } else if (currentCommands == 0) { + status = 0; + } else { + status = 1; + } + if (!sessionStatusCond.test(status)) { + continue; + } + + long started = session.getStarted(); + if (!sessionElapsedTimeMsCond.test(now - started)) { + continue; + } + String lastCommand = session.getLastCommand(); + long lastCommandStarted = session.getLastCommandStarted(); + long lastCommandFinished = session.getLastCommandFinished(); + + Row row = new Row(); + row.set(SessionId.name, sessionId); + row.set(SessionCommandCount.name, totalCommands); + row.set(SessionElapsedTimeMs.name, now - started); + if (lastCommand != null) { + row.set(SessionLastCommand.name, lastCommand); + row.set(SessionLastCommandElapsedTimeMs.name, now - lastCommandStarted); + row.set(SessionLastCommandStartTime.name, formatter.format(new Date(lastCommandStarted))); + } + if (lastCommandFinished != 0L) { + row.set(SessionLastCommandEndTime.name, formatter.format(new Date(lastCommandFinished))); + } + row.set(SessionStartTime.name, formatter.format(new Date(started))); + + + row.set(SessionStatus.name, status); + addRow(row, rows); + } + } + } + + static class DiscoverCommandsRowset extends Rowset { + private final Util.Predicate1 sessionIdCond; + + DiscoverCommandsRowset(XmlaRequest request, XmlaHandler handler) { + super(DISCOVER_COMMANDS, request, handler); + sessionIdCond = makeCondition(SessionId); + } + + private static final Column SessionId = new Column( + "SESSION_SPID", + Type.String, //In the spec, this is an integer, but our session ID's are not integers. + null, + Column.RESTRICTION, + Column.REQUIRED, + "The session ID."); + + private static final Column SessionCommandCount = new Column( + "SESSION_COMMAND_COUNT", + Type.Integer, + null, + Column.NOT_RESTRICTION, + Column.REQUIRED, + "The number of commands executed since the start of the session."); + + private static final Column CommandStartTime = new Column( + "COMMAND_START_TIME", + Type.DateTime, + null, + Column.NOT_RESTRICTION, + Column.REQUIRED, + "The date and time the last command started, expressed as UTC time on the server."); + + private static final Column CommandEndTime = new Column( + "COMMAND_END_TIME", + Type.DateTime, + null, + Column.NOT_RESTRICTION, + Column.OPTIONAL, + "The server UTC date and time when the command finishes its execution."); + + private static final Column CommandElapsedTimeMs = new Column( + "COMMAND_ELAPSED_TIME_MS", + Type.Long, + null, + Column.NOT_RESTRICTION, + Column.REQUIRED, + "The elapsed time, in milliseconds, since the start of the command."); + + private static final Column CommandText = new Column( + "COMMAND_TEXT", + Type.String, + null, + Column.NOT_RESTRICTION, + Column.OPTIONAL, + "The command text."); + + @Override + protected boolean needConnection() { + return false; + } + + @Override + protected void populateImpl( + XmlaResponse response, OlapConnection connection, List rows) + throws XmlaException { + XmlaSessionManager manager = XmlaSessionManager.getInstance(); + Map sessions = manager.getAllSessions(); + long now = System.currentTimeMillis(); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + for (String sessionId : sessions.keySet()) { + if (!sessionIdCond.test(sessionId)) { + continue; + } + XmlaSessionManager.XmlaSession session = sessions.get(sessionId); + int totalCommands = session.getTotalCommandCount(); + for (XmlaSessionManager.XmlaSessionCommand command : session.getAllCommands()) { + Row row = new Row(); + row.set(SessionId.name, sessionId); + row.set(SessionCommandCount.name, totalCommands); + row.set(CommandStartTime.name, formatter.format(new Date(command.getStartTime()))); + // We only track running commands, so there is never a command end time. + //row.set(CommandEndTime.name, "2006-01-25T17:35:32"); + row.set(CommandElapsedTimeMs.name, now - command.getStartTime()); + row.set(CommandText.name, command.getCommand()); + addRow(row, rows); + } + } + } + } + static class DbschemaCatalogsRowset extends Rowset { private final Util.Predicate1 catalogNameCond; @@ -6368,6 +6661,10 @@ public boolean isDrillThrough() { return request.isDrillThrough(); } + public boolean isCancel() { + return request.isCancel(); + } + public String getUsername() { return request.getUsername(); } diff --git a/src/main/java/mondrian/xmla/XmlaConstants.java b/src/main/java/mondrian/xmla/XmlaConstants.java index 565ecab..078f439 100644 --- a/src/main/java/mondrian/xmla/XmlaConstants.java +++ b/src/main/java/mondrian/xmla/XmlaConstants.java @@ -44,6 +44,8 @@ public interface XmlaConstants { public static final String NS_SQL = "urn:schemas-microsoft-com:xml-sql"; public static final String NS_XMLA_EX = "urn:schemas-microsoft-com:xml-analysis:exception"; + public static final String NS_XMLA_MS_EXTENSIONS = + "http://schemas.microsoft.com/analysisservices/2003/engine"; public static final String NS_SOAP_SECEXT = "http://schemas.xmlsoap.org/ws/2002/04/secext"; diff --git a/src/main/java/mondrian/xmla/XmlaHandler.java b/src/main/java/mondrian/xmla/XmlaHandler.java index 8f85bb2..98b04ff 100644 --- a/src/main/java/mondrian/xmla/XmlaHandler.java +++ b/src/main/java/mondrian/xmla/XmlaHandler.java @@ -693,6 +693,10 @@ private void execute( XmlaResponse response) throws XmlaException { + if (request.isCancel()) { + cancelSession(request); + return; + } final Map properties = request.getProperties(); // Default responseMimeType is SOAP. @@ -1318,6 +1322,32 @@ static void writeEmptyDatasetXmlSchema(SaxWriter writer, SetType setType) { writer.endElement(); // xsd:schema } + private void cancelSession(XmlaRequest request) throws XmlaException { + String sessionId = request.getStatement(); + if (!authorizeSessionCancel(sessionId, request)) { + throw new XmlaException( + CLIENT_FAULT_FC, + CHH_AUTHORIZATION_CODE, + CHH_AUTHORIZATION_FAULT_FS, + Util.newInternal( + "Unauthorized request to cancel session " + sessionId)); + } + XmlaSessionManager.getInstance().cancelSession(sessionId); + } + + protected boolean authorizeSessionCancel( + String sessionId, + XmlaRequest request) + { + String[] credentials = + XmlaSessionManager.getInstance().getSessionCredentials(sessionId); + if (credentials == null || credentials.length == 0 || credentials[0] == null) { + return true; + } + return credentials[0].equals(request.getUsername()) && ( + credentials[1] == null || credentials[1].equals(request.getPassword())); + } + private QueryResult executeDrillThroughQuery(XmlaRequest request) throws XmlaException { @@ -1336,16 +1366,19 @@ private QueryResult executeDrillThroughQuery(XmlaRequest request) OlapStatement statement = null; ResultSet resultSet = null; try { + String mdx = request.getStatement(); connection = getConnection(request, Collections.emptyMap()); statement = connection.createStatement(); + XmlaSessionManager.getInstance().addStatementToSession( + request.getSessionId(), statement, mdx); final XmlaHandler.XmlaExtra extra = connectionFactory.getExtra(); final boolean enableRowCount = extra.isTotalCountEnabled(); final int[] rowCountSlot = enableRowCount ? new int[]{0} : null; resultSet = extra.executeDrillthrough( statement, - request.getStatement(), + mdx, advanced, tabFields, rowCountSlot); @@ -1375,6 +1408,8 @@ private QueryResult executeDrillThroughQuery(XmlaRequest request) } } if (statement != null) { + XmlaSessionManager.getInstance().removeStatementFromSession( + request.getSessionId(), statement); try { statement.close(); } catch (SQLException e) { @@ -1658,6 +1693,8 @@ private QueryResult executeQuery(XmlaRequest request) extra.setPreferList(connection); try { statement = connection.prepareOlapStatement(mdx); + XmlaSessionManager.getInstance().addStatementToSession( + request.getSessionId(), statement, mdx); } catch (XmlaException ex) { throw ex; } catch (Exception ex) { @@ -1699,6 +1736,10 @@ private QueryResult executeQuery(XmlaRequest request) ex); } } finally { + if (statement != null) { + XmlaSessionManager.getInstance().removeStatementFromSession( + request.getSessionId(), statement); + } if (!success) { if (cellSet != null) { try { diff --git a/src/main/java/mondrian/xmla/XmlaRequest.java b/src/main/java/mondrian/xmla/XmlaRequest.java index 8afd7dd..98c5e66 100644 --- a/src/main/java/mondrian/xmla/XmlaRequest.java +++ b/src/main/java/mondrian/xmla/XmlaRequest.java @@ -59,6 +59,11 @@ public interface XmlaRequest { */ boolean isDrillThrough(); + /** + * Indicate whether EXECUTE method is a session cancel. + */ + boolean isCancel(); + /** * The username to use to open the underlying olap4j connection. * Can be null. diff --git a/src/main/java/mondrian/xmla/XmlaServlet.java b/src/main/java/mondrian/xmla/XmlaServlet.java index a3650d5..bd70f3b 100644 --- a/src/main/java/mondrian/xmla/XmlaServlet.java +++ b/src/main/java/mondrian/xmla/XmlaServlet.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.sql.SQLException; import java.util.*; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -38,9 +39,11 @@ public abstract class XmlaServlet "OptionalDataSourceConfig"; public static final String PARAM_CHAR_ENCODING = "CharacterEncoding"; public static final String PARAM_CALLBACKS = "Callbacks"; + public static final String PARAM_CANCEL_SQL_STATE = "CancelSqlState"; protected XmlaHandler xmlaHandler = null; protected String charEncoding = null; + protected final HashSet cancelSqlStateSet = new HashSet(); private final List callbackList = new ArrayList(); @@ -95,6 +98,8 @@ public void init(ServletConfig servletConfig) // init: callbacks initCallbacks(servletConfig); + initCancelSqlState(servletConfig); + this.connectionFactory = createConnectionFactory(servletConfig); } @@ -133,6 +138,21 @@ protected final List getCallbacks() { return Collections.unmodifiableList(callbackList); } + private boolean isCancelException(Throwable t) { + if (cancelSqlStateSet == null || cancelSqlStateSet.isEmpty()) { + return false; + } + + if (t instanceof SQLException) { + String sqlState = ((SQLException) t).getSQLState(); + return cancelSqlStateSet.contains(sqlState); + } else if (t.getCause() != null) { + return isCancelException(t.getCause()); + } else { + return false; + } + } + /** * Main entry for HTTP post method * @@ -320,7 +340,11 @@ protected void doPost( responseSoapParts, context); } catch (XmlaException xex) { - LOGGER.error("Errors when handling XML/A message", xex); + if (isCancelException(xex)) { + LOGGER.info("XML/A request canceled", xex); + } else { + LOGGER.error("Errors when handling XML/A message", xex); + } handleFault(response, responseSoapParts, phase, xex); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); @@ -441,6 +465,22 @@ protected void initCharEncodingHandler(ServletConfig servletConfig) { } } + /** + * Initialize possible cancel sql states + */ + protected void initCancelSqlState(ServletConfig servletConfig) { + String paramValue = servletConfig.getInitParameter( + PARAM_CANCEL_SQL_STATE); + if (paramValue != null) { + for (String sqlState : paramValue.split(",")) { + sqlState = sqlState.trim(); + if (sqlState.length() > 0) { + cancelSqlStateSet.add(sqlState); + } + } + } + } + /** * Registers callbacks configured in web.xml. */ diff --git a/src/main/java/mondrian/xmla/XmlaSessionManager.java b/src/main/java/mondrian/xmla/XmlaSessionManager.java new file mode 100644 index 0000000..6baf80a --- /dev/null +++ b/src/main/java/mondrian/xmla/XmlaSessionManager.java @@ -0,0 +1,267 @@ +package mondrian.xmla; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +import org.olap4j.OlapStatement; +import org.olap4j.impl.Olap4jUtil; + +public class XmlaSessionManager { + // Default session time out is 2 hours. + public static final long DEFAULT_SESSION_TIME_OUT = 2 * 60 * 60 * 1000; + private static XmlaSessionManager instance; + /* + * A map of all current sessions by keyed by their sessionId. + */ + private final Map sessionMap = new HashMap(); + private final Timer timer; + private final long sessionTimeOut; + + private XmlaSessionManager(long sessionTimeOut) { + this.sessionTimeOut = sessionTimeOut; + timer = new Timer("SessionTimer", true); + // Check for session time out four times more often than they time out + long period = sessionTimeOut / 4; + timer.schedule( + new TimerTask() { + public void run() { + timeoutSessions(); + } + }, + period, period); + } + public static XmlaSessionManager getInstance() { + if (instance == null) { + instance = new XmlaSessionManager(DEFAULT_SESSION_TIME_OUT); + } + return instance; + } + public static XmlaSessionManager createInstance(long sessionTimeOut) { + instance = new XmlaSessionManager(sessionTimeOut); + return instance; + } + + private XmlaSession getOrCreateSession(String sessionId) { + XmlaSession session; + synchronized (sessionMap) { + session = sessionMap.get(sessionId); + if (session == null) { + session = new XmlaSession(); + sessionMap.put(sessionId, session); + } + } + return session; + } + + public void registerSessionWithCredentials( + String sessionId, String username, String password) + { + synchronized (sessionMap) { + XmlaSession session = sessionMap.get(sessionId); + if (session != null + && Olap4jUtil.equal(session.getUsername(), 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) { + session.setPassword(password); + } + } else { + // A credentials object was stored against the provided session + // ID but the username didn't match, so create a new holder. + session = new XmlaSession(); + session.setUsername(username); + session.setPassword(password); + sessionMap.put(sessionId, session); + } + } + } + public String[] getSessionCredentials(String sessionId) { + XmlaSession session; + synchronized (sessionMap) { + session = sessionMap.get(sessionId); + } + if (session == null) { + return null; + } else { + String[] credentials = new String[2]; + credentials[0] = session.getUsername(); + credentials[1] = session.getPassword(); + return credentials; + } + } + + public void cancelSession(String sessionId) { + XmlaSession session = getOrCreateSession(sessionId); + session.cancel(); + // Do not remove a cancelled session so that we can cancel any new statements. + } + public void addStatementToSession(String sessionId, OlapStatement statement, String command) { + XmlaSession session = getOrCreateSession(sessionId); + session.addStatement(statement, command); + } + public void removeStatementFromSession(String sessionId, OlapStatement statement) { + XmlaSession session; + synchronized (sessionMap) { + session = sessionMap.get(sessionId); + } + if (session != null) { + session.removeStatement(statement); + } + } + public void endSession(String sessionId) { + synchronized (sessionMap) { + sessionMap.remove(sessionId); + } + } + public void timeoutSessions() { + long now = System.currentTimeMillis(); + synchronized (sessionMap) { + for (Iterator iter = sessionMap.values().iterator(); iter.hasNext();) { + XmlaSession session = iter.next(); + if (now - session.lastModified > sessionTimeOut) { + iter.remove(); + } + } + } + } + + public Map getAllSessions() { + Map copiedSessionMap;; + synchronized (sessionMap) { + copiedSessionMap = new HashMap(sessionMap); + } + return copiedSessionMap; + } + + public static class XmlaSession { + private String username; + private String password; + private Map statements; + private String lastCommand; + private boolean isCancelled; + private long started; + private long lastCommandStarted; + private long lastCommandFinished; + private long lastModified; + private int totalCommands; + + public XmlaSession() { + statements = new HashMap(); + isCancelled = false; + started = System.currentTimeMillis(); + totalCommands = 0; + } + public void cancel() { + isCancelled = true; + synchronized (statements) { + for (XmlaSessionCommand command : statements.values()) { + try { + command.getStatement().cancel(); + } catch (SQLException e) { + // ignore + } + } + statements.clear(); + lastModified = System.currentTimeMillis(); + } + } + public void addStatement(OlapStatement statement, String command) { + if (isCancelled) { + try { + statement.cancel(); + } catch (SQLException e) { + // ignore + } + } else { + synchronized (statements) { + lastModified = System.currentTimeMillis(); + statements.put(statement, new XmlaSessionCommand(statement, lastModified, command)); + lastCommandStarted = lastModified; + totalCommands++; + lastCommand = command; + } + } + } + public void removeStatement(OlapStatement statement) { + synchronized (statements) { + statements.remove(statement); + lastModified = System.currentTimeMillis(); + lastCommandFinished = lastModified; + } + } + public Collection getAllCommands() { + synchronized (statements) { + return statements.values(); + } + } + public String getUsername() { + return username; + } + public void setUsername(String username) { + synchronized (statements) { + this.username = username; + lastModified = System.currentTimeMillis(); + } + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + synchronized (statements) { + this.password = password; + lastModified = System.currentTimeMillis(); + } + } + public boolean isCancelled() { + return isCancelled; + } + public long getStarted() { + return started; + } + public long getLastCommandStarted() { + return lastCommandStarted; + } + public long getLastCommandFinished() { + return lastCommandFinished; + } + public int getTotalCommandCount() { + return totalCommands; + } + public int getCurrentCommandCount() { + synchronized (statements) { + return statements.size(); + } + } + public String getLastCommand() { + return lastCommand; + } + } + + public static class XmlaSessionCommand { + private OlapStatement statement; + private long startTime; + private String command; + + public XmlaSessionCommand(OlapStatement statement, long startTime, String command) { + this.statement = statement; + this.startTime = startTime; + this.command = command; + } + public String getCommand() { + return command; + } + public long getStartTime() { + return startTime; + } + public OlapStatement getStatement() { + return statement; + } + } +} diff --git a/src/main/java/mondrian/xmla/XmlaUtil.java b/src/main/java/mondrian/xmla/XmlaUtil.java index 79fc168..d3ee7d9 100644 --- a/src/main/java/mondrian/xmla/XmlaUtil.java +++ b/src/main/java/mondrian/xmla/XmlaUtil.java @@ -374,6 +374,10 @@ public boolean isDrillThrough() { throw new UnsupportedOperationException(); } + public boolean isCancel() { + throw new UnsupportedOperationException(); + } + public Format getFormat() { throw new UnsupportedOperationException(); } diff --git a/src/main/java/mondrian/xmla/impl/DefaultXmlaRequest.java b/src/main/java/mondrian/xmla/impl/DefaultXmlaRequest.java index 9b6f6df..a34df99 100644 --- a/src/main/java/mondrian/xmla/impl/DefaultXmlaRequest.java +++ b/src/main/java/mondrian/xmla/impl/DefaultXmlaRequest.java @@ -42,6 +42,7 @@ public class DefaultXmlaRequest /* EXECUTE content */ private String statement; private boolean drillthrough; + private boolean cancel; /* DISCOVER contnet */ private String requestType; @@ -122,6 +123,13 @@ public boolean isDrillThrough() { return drillthrough; } + public boolean isCancel() { + if (method != Method.EXECUTE) { + throw new IllegalStateException( + "Only METHOD_EXECUTE determines cancel"); + } + return cancel; + } protected final void init(Element xmlaRoot) throws XmlaException { if (NS_XMLA.equals(xmlaRoot.getNamespaceURI())) { @@ -387,24 +395,68 @@ private void initProperties(Element propertiesRoot) throws XmlaException { private void initCommand(Element commandRoot) throws XmlaException { + initCancel(commandRoot); + if (!cancel) { + Element[] childElems = + XmlaUtil.filterChildElements( + commandRoot, + NS_XMLA, + "Statement"); + if (childElems.length != 1) { + StringBuilder buf = new StringBuilder(100); + buf.append(MSG_INVALID_XMLA); + buf.append(": Wrong number of Statement elements: "); + buf.append(childElems.length); + throw new XmlaException( + CLIENT_FAULT_FC, + HSB_BAD_STATEMENT_CODE, + HSB_BAD_STATEMENT_FAULT_FS, + Util.newError(buf.toString())); + } + statement = XmlaUtil.textInElement(childElems[0]).replaceAll("\\r", ""); + drillthrough = statement.toUpperCase().indexOf("DRILLTHROUGH") != -1; + } + } + + private void initCancel(Element commandRoot) throws XmlaException { Element[] childElems = XmlaUtil.filterChildElements( commandRoot, - NS_XMLA, - "Statement"); - if (childElems.length != 1) { + NS_XMLA_MS_EXTENSIONS, + "Cancel"); + if (childElems.length > 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); - buf.append(": Wrong number of Statement elements: "); + buf.append(": Wrong number of Cancel elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_STATEMENT_CODE, HSB_BAD_STATEMENT_FAULT_FS, Util.newError(buf.toString())); + } else if (childElems.length == 1) { + Element cancelRoot = childElems[0]; + childElems = + XmlaUtil.filterChildElements( + cancelRoot, + NS_XMLA_MS_EXTENSIONS, + "SessionID"); + if (childElems.length != 1) { + StringBuilder buf = new StringBuilder(100); + buf.append(MSG_INVALID_XMLA); + buf.append(": Wrong number of SessionID elements: "); + buf.append(childElems.length); + throw new XmlaException( + CLIENT_FAULT_FC, + HSB_BAD_STATEMENT_CODE, + HSB_BAD_STATEMENT_FAULT_FS, + Util.newError(buf.toString())); + } + statement = XmlaUtil.textInElement(childElems[0]); + cancel = true; + } else { + cancel = false; } - statement = XmlaUtil.textInElement(childElems[0]).replaceAll("\\r", ""); - drillthrough = statement.toUpperCase().indexOf("DRILLTHROUGH") != -1; } } diff --git a/src/main/java/mondrian/xmla/impl/DefaultXmlaServlet.java b/src/main/java/mondrian/xmla/impl/DefaultXmlaServlet.java index bc66612..0080c8b 100644 --- a/src/main/java/mondrian/xmla/impl/DefaultXmlaServlet.java +++ b/src/main/java/mondrian/xmla/impl/DefaultXmlaServlet.java @@ -11,8 +11,6 @@ import mondrian.xmla.*; -import org.olap4j.impl.Olap4jUtil; - import org.w3c.dom.*; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -20,7 +18,6 @@ import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.*; -import java.util.HashMap; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -43,24 +40,31 @@ public abstract class DefaultXmlaServlet extends XmlaServlet { */ private static final String REQUIRE_AUTHENTICATED_SESSIONS = "requireAuthenticatedSessions"; + /** + * Servlet config parameter for session time out + */ + private static final String SESSION_TIME_OUT = + "sessionTimeOut"; private DocumentBuilderFactory domFactory = null; private boolean requireAuthenticatedSessions = false; - /** - * Session properties, keyed by session ID. Currently just username and - * password. - */ - private final Map sessionInfos = - new HashMap(); - public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); this.domFactory = getDocumentBuilderFactory(); + this.requireAuthenticatedSessions = Boolean.parseBoolean( servletConfig.getInitParameter(REQUIRE_AUTHENTICATED_SESSIONS)); + long sessionTimeOut; + try { + sessionTimeOut = + Long.parseLong(servletConfig.getInitParameter(SESSION_TIME_OUT)); + } catch (Exception e) { + sessionTimeOut = XmlaSessionManager.DEFAULT_SESSION_TIME_OUT; + } + XmlaSessionManager.createInstance(sessionTimeOut); } protected static DocumentBuilderFactory getDocumentBuilderFactory() { @@ -314,13 +318,13 @@ protected void handleSoapHeader( } else if (localName.equals(XMLA_SESSION)) { sessionIdStr = getSessionIdFromRequest(e, context); - SessionInfo sessionInfo = getSessionInfo(sessionIdStr); + String[] credentials = + XmlaSessionManager.getInstance() + .getSessionCredentials(sessionIdStr); - if (sessionInfo != null) { - context.put(CONTEXT_XMLA_USERNAME, sessionInfo.user); - context.put( - CONTEXT_XMLA_PASSWORD, - sessionInfo.password); + if (credentials != null) { + context.put(CONTEXT_XMLA_USERNAME, credentials[0]); + context.put(CONTEXT_XMLA_PASSWORD, credentials[1]); } context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr); @@ -330,6 +334,7 @@ protected void handleSoapHeader( } else if (localName.equals(XMLA_END_SESSION)) { sessionIdStr = getSessionIdFromRequest(e, context); + XmlaSessionManager.getInstance().endSession(sessionIdStr); context.put( CONTEXT_XMLA_SESSION_STATE, CONTEXT_XMLA_SESSION_STATE_END); @@ -372,10 +377,9 @@ protected void handleSoapHeader( + username + "/********] for session id [" + sessionId + "]"); - saveSessionInfo( - username, - password, - sessionId); + XmlaSessionManager.getInstance() + .registerSessionWithCredentials( + sessionId, username, password); } else { if (beginSession && requireAuthenticatedSessions) { throw new XmlaException( @@ -786,71 +790,5 @@ protected void handleFault( responseSoapParts[1] = osBuf.toByteArray(); } - - private SessionInfo getSessionInfo(String sessionId) { - 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 + "]"); - } else { - LOGGER.debug( - "Found credentials for session id [" - + sessionId - + "], username=[" + sessionInfo.user - + "] in servlet cache"); - } - 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(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(username, password); - sessionInfos.put(sessionId, sessionInfo); - } - return sessionInfo; - } - } - /** - * Holds authentication credentials of a XMLA session. - */ - private static class SessionInfo { - final String user; - final String password; - - public SessionInfo(String user, String password) - { - this.user = user; - this.password = password; - } - } } // End DefaultXmlaServlet.java