Package org.olap4j.driver.xmla

Source Code of org.olap4j.driver.xmla.XmlaOlap4jConnection

/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// Copyright (C) 2007-2011 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
package org.olap4j.driver.xmla;

import org.olap4j.*;

import static org.olap4j.driver.xmla.XmlaOlap4jUtil.*;

import org.olap4j.driver.xmla.proxy.*;
import org.olap4j.impl.*;
import org.olap4j.mdx.ParseTreeWriter;
import org.olap4j.mdx.SelectNode;
import org.olap4j.mdx.parser.*;
import org.olap4j.mdx.parser.impl.DefaultMdxParserImpl;
import org.olap4j.metadata.*;
import org.olap4j.metadata.Database.AuthenticationMode;
import org.olap4j.metadata.Database.ProviderType;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.util.Map.*;
import java.util.regex.*;

/**
* Implementation of {@link org.olap4j.OlapConnection}
* for XML/A providers.
*
* <p>This class has sub-classes which implement JDBC 3.0 and JDBC 4.0 APIs;
* it is instantiated using {@link Factory#newConnection}.</p>
*
* @author jhyde
* @version $Id: XmlaOlap4jConnection.java 431 2011-03-24 15:14:50Z lucboudreau $
* @since May 23, 2007
*/
abstract class XmlaOlap4jConnection implements OlapConnection {
    /**
     * Handler for errors.
     */
    final XmlaHelper helper = new XmlaHelper();

    /**
     * <p>Current database.
     */
    private XmlaOlap4jDatabase olap4jDatabase;

    /**
     * <p>Current schema.
     */
    private XmlaOlap4jCatalog olap4jCatalog;

    /**
     * <p>Current schema.
     */
    private XmlaOlap4jSchema olap4jSchema;

    final XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData;

    private static final String CONNECT_STRING_PREFIX = "jdbc:xmla:";

    final XmlaOlap4jDriver driver;

    final Factory factory;

    final XmlaOlap4jProxy proxy;

    private boolean closed = false;

    /**
     * URL of the HTTP server to which to send XML requests.
     */
    final XmlaOlap4jServerInfos serverInfos;

    private Locale locale;

    /**
     * Name of the catalog to which the user wishes to bind
     * this connection. This value can be set through the JDBC URL
     * or via {@link XmlaOlap4jConnection#setCatalog(String)}
     */
    private String catalogName;

    /**
     * Name of the schema to which the user wishes to bind
     * this connection to. This value can also be set through the
     * JDBC URL or via {@link XmlaOlap4jConnection#setSchema(String)}
     */
    private String schemaName;

    /**
     * Name of the role that this connection impersonates.
     */
    private String roleName;

    /**
     * Name of the database to which the user wishes to bind
     * this connection. This value can be set through the JDBC URL
     * or via {@link XmlaOlap4jConnection#setCatalog(String)}
     */
    private String databaseName;

    private boolean autoCommit;
    private boolean readOnly;

    /**
     * Root of the metadata hierarchy of this connection.
     */
    private NamedList<XmlaOlap4jDatabase> olapDatabases;

    private final URL serverUrlObject;

    /**
     * This is a private property used for development only.
     * Enabling it makes the connection print out all queries
     * to {@link System#out}
     */
    private static final boolean DEBUG = false;

    /**
     * Creates an Olap4j connection an XML/A provider.
     *
     * <p>This method is intentionally package-protected. The public API
     * uses the traditional JDBC {@link java.sql.DriverManager}.
     * See {@link org.olap4j.driver.xmla.XmlaOlap4jDriver} for more details.
     *
     * <p>Note that this constructor should make zero non-trivial calls, which
     * could cause deadlocks due to java.sql.DriverManager synchronization
     * issues.
     *
     * @pre acceptsURL(url)
     *
     * @param factory Factory
     * @param driver Driver
     * @param proxy Proxy object which receives XML requests
     * @param url Connect-string URL
     * @param info Additional properties
     * @throws java.sql.SQLException if there is an error
     */
    XmlaOlap4jConnection(
        Factory factory,
        XmlaOlap4jDriver driver,
        XmlaOlap4jProxy proxy,
        String url,
        Properties info)
        throws SQLException
    {
        if (!acceptsURL(url)) {
            // This is not a URL we can handle.
            // DriverManager should not have invoked us.
            throw new AssertionError(
                "does not start with '" + CONNECT_STRING_PREFIX + "'");
        }

        this.factory = factory;
        this.driver = driver;
        this.proxy = proxy;

        final Map<String, String> map = parseConnectString(url, info);

        this.databaseName =
            map.get(XmlaOlap4jDriver.Property.DATABASE.name());

        this.catalogName =
            map.get(XmlaOlap4jDriver.Property.CATALOG.name());

        this.schemaName =
            map.get(XmlaOlap4jDriver.Property.SCHEMA.name());

        // Set URL of HTTP server.
        final String serverUrl =
            map.get(XmlaOlap4jDriver.Property.SERVER.name());
        if (serverUrl == null) {
            throw getHelper().createException(
                "Connection property '"
                + XmlaOlap4jDriver.Property.SERVER.name()
                + "' must be specified");
        }
        try {
            this.serverUrlObject = new URL(serverUrl);
        } catch (MalformedURLException e) {
            throw getHelper().createException(e);
        }

        // Initialize the SOAP cache if needed
        initSoapCache(map);

        this.serverInfos =
            new XmlaOlap4jServerInfos() {
                private String sessionId = null;
                public String getUsername() {
                    return map.get("user");
                }
                public String getPassword() {
                    return map.get("password");
                }
                public URL getUrl() {
                    return serverUrlObject;
                }
                public String getSessionId() {
                    return sessionId;
                }
                public void setSessionId(String sessionId) {
                    this.sessionId = sessionId;
                }
            };

        this.olap4jDatabaseMetaData =
            factory.newDatabaseMetaData(this);

        this.olapDatabases =
            new DeferredNamedListImpl<XmlaOlap4jDatabase>(
                XmlaOlap4jConnection.MetadataRequest.DISCOVER_DATASOURCES,
                new XmlaOlap4jConnection.Context(
                    this,
                    this.olap4jDatabaseMetaData,
                    null, null, null, null, null, null),
                new XmlaOlap4jConnection.DatabaseHandler(),
                null);
    }

    /**
     * Returns the error-handler
     * @return Error-handler
     */
    private XmlaHelper getHelper() {
        return helper;
    }

    /**
     * Initializes a cache object and configures it if cache
     * parameters were specified in the jdbc url.
     *
     * @param map The parameters from the jdbc url.
     * @throws OlapException Thrown when there is an error encountered
     * while creating the cache.
     */
    private void initSoapCache(Map<String, String> map) throws OlapException {
        //  Test if a SOAP cache class was defined
        if (map.containsKey(XmlaOlap4jDriver.Property.CACHE.name()
            .toUpperCase()))
        {
            // Create a properties object to pass to the proxy
            // so it can configure it's cache
            Map<String, String> props = new HashMap<String, String>();
            //  Iterate over map entries to find those related to
            //  the cache config
            for (Entry<String, String> entry : map.entrySet()) {
                // Check if the current entry relates to cache config.
                if (entry.getKey().startsWith(
                    XmlaOlap4jDriver.Property.CACHE.name() + "."))
                {
                    props.put(entry.getKey().substring(
                        XmlaOlap4jDriver.Property.CACHE.name()
                        .length() + 1), entry.getValue());
                }
            }

            // Init the cache
            ((XmlaOlap4jCachedProxy) this.proxy).setCache(map, props);
        }
    }



    static Map<String, String> parseConnectString(String url, Properties info) {
        String x = url.substring(CONNECT_STRING_PREFIX.length());
        Map<String, String> map =
            ConnectStringParser.parseConnectString(x);
        for (Map.Entry<String, String> entry : toMap(info).entrySet()) {
            map.put(entry.getKey(), entry.getValue());
        }
        return map;
    }

    static boolean acceptsURL(String url) {
        return url.startsWith(CONNECT_STRING_PREFIX);
    }

    public OlapStatement createStatement() {
        return new XmlaOlap4jStatement(this);
    }

    public PreparedStatement prepareStatement(String sql) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public CallableStatement prepareCall(String sql) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public String nativeSQL(String sql) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setAutoCommit(boolean autoCommit) throws SQLException {
        this.autoCommit = autoCommit;
    }

    public boolean getAutoCommit() throws SQLException {
        return autoCommit;
    }

    public void commit() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void rollback() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void close() throws SQLException {
        closed = true;
    }

    public boolean isClosed() throws SQLException {
        return closed;
    }

    public OlapDatabaseMetaData getMetaData() {
        return olap4jDatabaseMetaData;
    }

    public void setReadOnly(boolean readOnly) throws SQLException {
        this.readOnly = readOnly;
    }

    public boolean isReadOnly() throws SQLException {
        return readOnly;
    }

    public void setDatabase(String databaseName) throws OlapException {
        if (databaseName == null) {
            throw new OlapException("Database name cannot be null.");
        }
        this.olap4jDatabase =
            (XmlaOlap4jDatabase) getOlapDatabases().get(databaseName);
        if (this.olap4jDatabase == null) {
            throw new OlapException(
                "No database named "
                + databaseName
                + " could be found.");
        }
        this.databaseName = databaseName;
        this.olap4jCatalog = null;
        this.olap4jSchema = null;
    }

    public String getDatabase() throws OlapException {
        return getOlapDatabase().getName();
    }

    public Database getOlapDatabase() throws OlapException {
        if (this.olap4jDatabase == null) {
            if (this.databaseName == null) {
                List<Database> databases = getOlapDatabases();
                if (databases.size() == 0) {
                    throw new OlapException("No database found.");
                }
                this.olap4jDatabase = (XmlaOlap4jDatabase) databases.get(0);
                this.databaseName = this.olap4jDatabase.getName();
                this.olap4jCatalog = null;
                this.olap4jSchema = null;
            } else {
                this.olap4jDatabase =
                    (XmlaOlap4jDatabase) getOlapDatabases()
                        .get(this.databaseName);
                this.olap4jCatalog = null;
                this.olap4jSchema = null;
                if (this.olap4jDatabase == null) {
                    throw new OlapException(
                        "No database named "
                        + this.databaseName
                        + " could be found.");
                }
            }
        }
        return olap4jDatabase;
    }

    public NamedList<Database> getOlapDatabases() throws OlapException {
        return Olap4jUtil.cast(this.olapDatabases);
    }

    public void setCatalog(String catalogName) throws OlapException {
        if (catalogName == null) {
            throw new OlapException("Catalog name cannot be null.");
        }
        this.olap4jCatalog =
            (XmlaOlap4jCatalog) getOlapCatalogs().get(catalogName);
        if (this.olap4jCatalog == null) {
            throw new OlapException(
                "No catalog named "
                + catalogName
                + " could be found.");
        }
        this.catalogName = catalogName;
        this.olap4jSchema = null;
    }

    public String getCatalog() throws OlapException {
        return getOlapCatalog().getName();
    }

    public Catalog getOlapCatalog() throws OlapException {
        if (this.olap4jCatalog == null) {
            final Database database = getOlapDatabase();
            if (this.catalogName == null) {
                if (database.getCatalogs().size() == 0) {
                    throw new OlapException(
                        "No catalogs could be found.");
                }
                this.olap4jCatalog =
                    (XmlaOlap4jCatalog) database.getCatalogs().get(0);
                this.catalogName = this.olap4jCatalog.getName();
                this.olap4jSchema = null;
            } else {
                this.olap4jCatalog =
                    (XmlaOlap4jCatalog) database.getCatalogs()
                        .get(this.catalogName);
                if (this.olap4jCatalog == null) {
                    throw new OlapException(
                        "No catalog named " + this.catalogName
                        + " could be found.");
                }
                this.olap4jSchema = null;
            }
        }
        return olap4jCatalog;
    }

    public NamedList<Catalog> getOlapCatalogs() throws OlapException {
        return getOlapDatabase().getCatalogs();
    }

    public String getSchema() throws OlapException {
        return getOlapSchema().getName();
    }

    public void setSchema(String schemaName) throws OlapException {
        if (schemaName == null) {
            throw new OlapException("Schema name cannot be null.");
        }
        final Catalog catalog = getOlapCatalog();
        this.olap4jSchema =
            (XmlaOlap4jSchema) catalog.getSchemas().get(schemaName);
        if (this.olap4jSchema == null) {
            throw new OlapException(
                    "No schema named " + schemaName
                    + " could be found in catalog "
                    + catalog.getName());
        }
        this.schemaName = schemaName;
    }

    public synchronized Schema getOlapSchema()
        throws OlapException
    {
        if (this.olap4jSchema == null) {
            final Catalog catalog = getOlapCatalog();
            if (this.schemaName == null) {
                if (catalog.getSchemas().size() == 0) {
                    throw new OlapException(
                        "No schemas could be found.");
                }
                this.olap4jSchema =
                    (XmlaOlap4jSchema) catalog.getSchemas().get(0);
            } else {
                this.olap4jSchema =
                    (XmlaOlap4jSchema) catalog.getSchemas()
                        .get(this.schemaName);
                if (this.olap4jSchema == null) {
                    throw new OlapException(
                        "No schema named " + this.schemaName
                        + " could be found.");
                }
            }
        }
        return olap4jSchema;
    }

    public NamedList<Schema> getOlapSchemas() throws OlapException {
        return getOlapCatalog().getSchemas();
    }

    public void setTransactionIsolation(int level) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getTransactionIsolation() throws SQLException {
        return TRANSACTION_NONE;
    }

    public SQLWarning getWarnings() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void clearWarnings() throws SQLException {
        // this driver does not support warnings, so nothing to do
    }

    public Statement createStatement(
        int resultSetType, int resultSetConcurrency) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public PreparedStatement prepareStatement(
        String sql,
        int resultSetType,
        int resultSetConcurrency) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public CallableStatement prepareCall(
        String sql,
        int resultSetType,
        int resultSetConcurrency) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public Map<String, Class<?>> getTypeMap() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setHoldability(int holdability) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getHoldability() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Savepoint setSavepoint() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Savepoint setSavepoint(String name) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void rollback(Savepoint savepoint) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Statement createStatement(
        int resultSetType,
        int resultSetConcurrency,
        int resultSetHoldability) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public PreparedStatement prepareStatement(
        String sql,
        int resultSetType,
        int resultSetConcurrency,
        int resultSetHoldability) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public CallableStatement prepareCall(
        String sql,
        int resultSetType,
        int resultSetConcurrency,
        int resultSetHoldability) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public PreparedStatement prepareStatement(
        String sql, int autoGeneratedKeys) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public PreparedStatement prepareStatement(
        String sql, int columnIndexes[]) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    public PreparedStatement prepareStatement(
        String sql, String columnNames[]) throws SQLException
    {
        throw new UnsupportedOperationException();
    }

    // implement Wrapper

    public <T> T unwrap(Class<T> iface) throws SQLException {
        if (iface.isInstance(this)) {
            return iface.cast(this);
        }
        throw getHelper().createException("does not implement '" + iface + "'");
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(this);
    }

    // implement OlapConnection

    public PreparedOlapStatement prepareOlapStatement(
        String mdx)
        throws OlapException
    {
        return factory.newPreparedStatement(mdx, this);
    }

    public MdxParserFactory getParserFactory() {
        return new MdxParserFactory() {
            public MdxParser createMdxParser(OlapConnection connection) {
                return new DefaultMdxParserImpl();
            }

            public MdxValidator createMdxValidator(OlapConnection connection) {
                return new XmlaOlap4jMdxValidator(connection);
            }
        };
    }

    public static Map<String, String> toMap(final Properties properties) {
        return new AbstractMap<String, String>() {
            public Set<Entry<String, String>> entrySet() {
                return Olap4jUtil.cast(properties.entrySet());
            }
        };
    }

    /**
     * Returns the URL which was used to create this connection.
     *
     * @return URL
     */
    String getURL() {
        throw Olap4jUtil.needToImplement(this);
    }

    public void setLocale(Locale locale) {
        if (locale == null) {
            throw new IllegalArgumentException("locale must not be null");
        }
        this.locale = locale;
    }

    public Locale getLocale() {
        if (locale == null) {
            return Locale.getDefault();
        }
        return locale;
    }

    public void setRoleName(String roleName) throws OlapException {
        this.roleName = roleName;
    }

    public String getRoleName() {
        return roleName;
    }

    public List<String> getAvailableRoleNames() {
        // List of available roles is not known. Could potentially add an XMLA
        // call to populate this list if useful to a client.
        return null;
    }

    public Scenario createScenario() {
        throw new UnsupportedOperationException();
    }

    public void setScenario(Scenario scenario) {
        throw new UnsupportedOperationException();
    }

    public Scenario getScenario() {
        throw new UnsupportedOperationException();
    }

    <T extends Named> void populateList(
        List<T> list,
        Context context,
        MetadataRequest metadataRequest,
        Handler<T> handler,
        Object[] restrictions) throws OlapException
    {
        String request =
            generateRequest(context, metadataRequest, restrictions);
        Element root = executeMetadataRequest(request);
        for (Element o : childElements(root)) {
            if (o.getLocalName().equals("row")) {
                handler.handle(o, context, list);
            }
        }
        handler.sortList(list);
    }

    /**
     * Executes an XMLA metadata request and returns the root element of the
     * response.
     *
     * @param request XMLA request string
     * @return Root element of the response
     * @throws OlapException on error
     */
    Element executeMetadataRequest(String request) throws OlapException {
        byte[] bytes;
        if (DEBUG) {
            System.out.println("********************************************");
            System.out.println("** SENDING REQUEST :");
            System.out.println(request);
        }
        try {
            bytes = proxy.get(serverInfos, request);
        } catch (XmlaOlap4jProxyException e) {
            throw getHelper().createException(
                "This connection encountered an exception while executing a query.",
                e);
        }
        Document doc;
        try {
            doc = parse(bytes);
        } catch (IOException e) {
            throw getHelper().createException(
                "error discovering metadata", e);
        } catch (SAXException e) {
            throw getHelper().createException(
                "error discovering metadata", e);
        }
        // <SOAP-ENV:Envelope>
        //   <SOAP-ENV:Header/>
        //   <SOAP-ENV:Body>
        //     <xmla:DiscoverResponse>
        //       <xmla:return>
        //         <root>
        //           (see below)
        //         </root>
        //       <xmla:return>
        //     </xmla:DiscoverResponse>
        //   </SOAP-ENV:Body>
        // </SOAP-ENV:Envelope>
        final Element envelope = doc.getDocumentElement();
        if (DEBUG) {
            System.out.println("** SERVER RESPONSE :");
            System.out.println(XmlaOlap4jUtil.toString(doc, true));
        }
        assert envelope.getLocalName().equals("Envelope");
        assert envelope.getNamespaceURI().equals(SOAP_NS);
        Element body =
            findChild(envelope, SOAP_NS, "Body");
        Element fault =
            findChild(body, SOAP_NS, "Fault");
        if (fault != null) {
            /*
        <SOAP-ENV:Fault>
            <faultcode>SOAP-ENV:Client.00HSBC01</faultcode>
            <faultstring>XMLA connection datasource not found</faultstring>
            <faultactor>Mondrian</faultactor>
            <detail>
                <XA:error xmlns:XA="http://mondrian.sourceforge.net">
                    <code>00HSBC01</code>
                    <desc>The Mondrian XML: Mondrian Error:Internal
                        error: no catalog named 'LOCALDB'</desc>
                </XA:error>
            </detail>
        </SOAP-ENV:Fault>
             */
            // TODO: log doc to logfile
            throw getHelper().createException(
                "XMLA provider gave exception: "
                + XmlaOlap4jUtil.prettyPrint(fault)
                + "\n"
                + "Request was:\n"
                + request);
        }
        Element discoverResponse =
            findChild(body, XMLA_NS, "DiscoverResponse");
        Element returnElement =
            findChild(discoverResponse, XMLA_NS, "return");
        return findChild(returnElement, ROWSET_NS, "root");
    }

    /**
     * Generates a metadata request.
     *
     * <p>The list of restrictions must have even length. Even elements must
     * be a string (the name of the restriction); odd elements must be either
     * a string (the value of the restriction) or a list of strings (multiple
     * values of the restriction)
     *
     * @param context Context
     * @param metadataRequest Metadata request
     * @param restrictions List of restrictions
     * @return XMLA SOAP request as a string.
     *
     * @throws OlapException when the query depends on a datasource name but
     * the one specified doesn't exist at the url, or there are no default
     * datasource (should use the first one)
     */
    public String generateRequest(
        Context context,
        MetadataRequest metadataRequest,
        Object[] restrictions) throws OlapException
    {
        final String content = "Data";
        final String encoding = proxy.getEncodingCharsetName();
        final StringBuilder buf =
            new StringBuilder(
                "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n"
                + "<SOAP-ENV:Envelope\n"
                + "    xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"\n"
                + "    SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
                + "  <SOAP-ENV:Body>\n"
                + "    <Discover xmlns=\"urn:schemas-microsoft-com:xml-analysis\"\n"
                + "        SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
                + "    <RequestType>");

        buf.append(metadataRequest.name());
        buf.append(
            "</RequestType>\n"
            + "    <Restrictions>\n"
            + "      <RestrictionList>\n");
        String restrictedCatalogName = null;
        if (restrictions.length > 0) {
            if (restrictions.length % 2 != 0) {
                throw new IllegalArgumentException();
            }
            for (int i = 0; i < restrictions.length; i += 2) {
                final String restriction = (String) restrictions[i];
                final Object o = restrictions[i + 1];
                if (o instanceof String) {
                    buf.append("<").append(restriction).append(">");
                    final String value = (String) o;
                    xmlEncode(buf, value);
                    buf.append("</").append(restriction).append(">");

                    // To remind ourselves to generate a <Catalog> restriction
                    // if the request supports it.
                    if (restriction.equals("CATALOG_NAME")) {
                        restrictedCatalogName = value;
                    }
                } else {
                    //noinspection unchecked
                    List<String> valueList = (List<String>) o;
                    for (String value : valueList) {
                        buf.append("<").append(restriction).append(">");
                        xmlEncode(buf, value);
                        buf.append("</").append(restriction).append(">");
                    }
                }
            }
        }
        buf.append(
            "      </RestrictionList>\n"
            + "    </Restrictions>\n"
            + "    <Properties>\n"
            + "      <PropertyList>\n");

        // Add the datasource node only if this request requires it.
        if (metadataRequest.requiresDatasourceName()) {
            buf.append("        <DataSourceInfo>");
            xmlEncode(buf, context.olap4jConnection.getDatabase());
            buf.append("</DataSourceInfo>\n");
        }

        String requestCatalogName = null;
        if (restrictedCatalogName != null
            && restrictedCatalogName.length() > 0)
        {
            requestCatalogName = restrictedCatalogName;
        }

        // If the request requires catalog name, and one wasn't specified in the
        // restrictions, use the connection's current catalog.
        if (context.olap4jCatalog != null) {
            requestCatalogName = context.olap4jCatalog.getName();
        }
        if (requestCatalogName == null
            && metadataRequest.requiresCatalogName())
        {
            List<Catalog> catalogs =
                context.olap4jConnection.getOlapCatalogs();
            if (catalogs.size() > 0) {
                requestCatalogName = catalogs.get(0).getName();
            }
        }

        // Add the catalog node only if this request has specified it as a
        // restriction.
        //
        // For low-level objects like cube, the restriction is optional; you can
        // specify null to not restrict, "" to match cubes whose catalog name is
        // empty, or a string (not interpreted as a wild card). (See
        // OlapDatabaseMetaData.getCubes API doc for more details.) We assume
        // that the request provides the restriction only if it is valid.
        //
        // For high level objects like data source and catalog, the catalog
        // restriction does not make sense.
        if (requestCatalogName != null
            && metadataRequest.allowsCatalogName())
        {
            if (getOlapCatalogs()
                .get(requestCatalogName) == null)
            {
                throw new OlapException(
                    "No catalog named " + requestCatalogName
                    + " exist on the server.");
            }
            buf.append("        <Catalog>");
            xmlEncode(buf, requestCatalogName);
            buf.append("</Catalog>\n");
        }

        buf.append("        <Content>");
        xmlEncode(buf, content);
        buf.append(
            "</Content>\n"
            + "      </PropertyList>\n"
            + "    </Properties>\n"
            + "  </Discover>\n"
            + "</SOAP-ENV:Body>\n"
            + "</SOAP-ENV:Envelope>");
        return buf.toString();
    }

    /**
     * Encodes a string for use in an XML CDATA section.
     *
     * @param value Value to be xml encoded
     * @param buf Buffer to append to
     */
    private static void xmlEncode(StringBuilder buf, String value) {
        final int n = value.length();
        for (int i = 0; i < n; ++i) {
            char c = value.charAt(i);
            switch (c) {
            case '&':
                buf.append("&amp;");
                break;
            case '<':
                buf.append("&lt;");
                break;
            case '>':
                buf.append("&gt;");
                break;
            case '"':
                buf.append("&quot;");
                break;
            case '\'':
                buf.append("&apos;");
                break;
            default:
                buf.append(c);
            }
        }
    }

    // ~ inner classes --------------------------------------------------------
    static class DatabaseHandler
        extends HandlerImpl<XmlaOlap4jDatabase>
    {
        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jDatabase> list)
        {
            String dsName =
                XmlaOlap4jUtil.stringElement(row, "DataSourceName");
            String dsDesc =
                XmlaOlap4jUtil.stringElement(row, "DataSourceDescription");
            String url =
                XmlaOlap4jUtil.stringElement(row, "URL");
            String dsInfo =
                XmlaOlap4jUtil.stringElement(row, "DataSourceInfo");
            String providerName =
                XmlaOlap4jUtil.stringElement(row, "ProviderName");
            StringTokenizer st =
                new StringTokenizer(
                    XmlaOlap4jUtil.stringElement(row, "ProviderType"), ",");
            List<ProviderType> pTypeList =
                new ArrayList<ProviderType>();
            while (st.hasMoreTokens()) {
                pTypeList.add(ProviderType.valueOf(st.nextToken()));
            }
            st = new StringTokenizer(
                XmlaOlap4jUtil.stringElement(row, "AuthenticationMode"), ",");
            List<AuthenticationMode> aModeList =
                new ArrayList<AuthenticationMode>();
            while (st.hasMoreTokens()) {
                aModeList.add(AuthenticationMode.valueOf(st.nextToken()));
            }
            list.add(
                new XmlaOlap4jDatabase(
                    context.olap4jConnection,
                    dsName,
                    dsDesc,
                    providerName,
                    url,
                    dsInfo,
                    pTypeList,
                    aModeList));
        }
    }

    static class CatalogHandler
        extends HandlerImpl<XmlaOlap4jCatalog>
    {
        private final XmlaOlap4jDatabase database;
        public CatalogHandler(XmlaOlap4jDatabase database) {
            this.database = database;
        }
        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jCatalog> list)
        {
            /*
            Example:

                    <row>
                        <CATALOG_NAME>FoodMart</CATALOG_NAME>
                        <DESCRIPTION>No description available</DESCRIPTION>
                        <ROLES>California manager,No HR Cube</ROLES>
                    </row>
             */
            String catalogName =
                XmlaOlap4jUtil.stringElement(row, "CATALOG_NAME");
            // Unused: DESCRIPTION, ROLES
            list.add(
                new XmlaOlap4jCatalog(
                    context.olap4jDatabaseMetaData,
                    database,
                    catalogName));
        }
    }

    static class CubeHandler extends HandlerImpl<XmlaOlap4jCube> {
        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jCube> list)
            throws OlapException
        {
            /*
            Example:

                    <row>
                        <CATALOG_NAME>FoodMart</CATALOG_NAME>
                        <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                        <CUBE_NAME>HR</CUBE_NAME>
                        <CUBE_TYPE>CUBE</CUBE_TYPE>
                        <IS_DRILLTHROUGH_ENABLED>true</IS_DRILLTHROUGH_ENABLED>
                        <IS_WRITE_ENABLED>false</IS_WRITE_ENABLED>
                        <IS_LINKABLE>false</IS_LINKABLE>
                        <IS_SQL_ENABLED>false</IS_SQL_ENABLED>
                        <DESCRIPTION>FoodMart Schema - HR Cube</DESCRIPTION>
                    </row>
             */
            // Unused: CATALOG_NAME, SCHEMA_NAME, CUBE_TYPE,
            //   IS_DRILLTHROUGH_ENABLED, IS_WRITE_ENABLED, IS_LINKABLE,
            //   IS_SQL_ENABLED
            String cubeName = stringElement(row, "CUBE_NAME");
            String description = stringElement(row, "DESCRIPTION");
            list.add(
                new XmlaOlap4jCube(
                    context.olap4jSchema, cubeName, description));
        }
    }

    static class DimensionHandler extends HandlerImpl<XmlaOlap4jDimension> {
        private final XmlaOlap4jCube cubeForCallback;

        public DimensionHandler(XmlaOlap4jCube dimensionsByUname) {
            this.cubeForCallback = dimensionsByUname;
        }
        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jDimension> list)
        {
            /*
            Example:

            <row>
                <CATALOG_NAME>FoodMart</CATALOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <CUBE_NAME>HR</CUBE_NAME>
                <DIMENSION_NAME>Department</DIMENSION_NAME>
                <DIMENSION_UNIQUE_NAME>[Department]</DIMENSION_UNIQUE_NAME>
                <DIMENSION_CAPTION>Department</DIMENSION_CAPTION>
                <DIMENSION_ORDINAL>6</DIMENSION_ORDINAL>
                <DIMENSION_TYPE>3</DIMENSION_TYPE>
                <DIMENSION_CARDINALITY>13</DIMENSION_CARDINALITY>
                <DEFAULT_HIERARCHY>[Department]</DEFAULT_HIERARCHY>
                <DESCRIPTION>HR Cube - Department Dimension</DESCRIPTION>
                <IS_VIRTUAL>false</IS_VIRTUAL>
                <IS_READWRITE>false</IS_READWRITE>
                <DIMENSION_UNIQUE_SETTINGS>0</DIMENSION_UNIQUE_SETTINGS>
                <DIMENSION_IS_VISIBLE>true</DIMENSION_IS_VISIBLE>
            </row>

             */
            final String dimensionName =
                stringElement(row, "DIMENSION_NAME");
            final String dimensionUniqueName =
                stringElement(row, "DIMENSION_UNIQUE_NAME");
            final String dimensionCaption =
                stringElement(row, "DIMENSION_CAPTION");
            final String description =
                stringElement(row, "DESCRIPTION");
            final int dimensionType =
                integerElement(row, "DIMENSION_TYPE");
            final Dimension.Type type =
                Dimension.Type.getDictionary().forOrdinal(dimensionType);
            final String defaultHierarchyUniqueName =
                stringElement(row, "DEFAULT_HIERARCHY");
            final Integer dimensionOrdinal =
                integerElement(row, "DIMENSION_ORDINAL");
            XmlaOlap4jDimension dimension = new XmlaOlap4jDimension(
                    context.olap4jCube, dimensionUniqueName, dimensionName,
                    dimensionCaption, description, type,
                    defaultHierarchyUniqueName,
                    dimensionOrdinal == null ? 0 : dimensionOrdinal);
            list.add(dimension);
            if (dimensionOrdinal != null) {
                Collections.sort(
                    list,
                    new Comparator<XmlaOlap4jDimension> () {
                        public int compare(
                                XmlaOlap4jDimension d1,
                                XmlaOlap4jDimension d2)
                        {
                            if (d1.getOrdinal() == d2.getOrdinal()) {
                                return 0;
                            } else if (d1.getOrdinal() > d2.getOrdinal()) {
                                return 1;
                            } else {
                                return -1;
                            }
                        }
                });
            }
            this.cubeForCallback.dimensionsByUname.put(
                dimension.getUniqueName(),
                dimension);
        }
    }

    static class HierarchyHandler extends HandlerImpl<XmlaOlap4jHierarchy> {
        private final XmlaOlap4jCube cubeForCallback;
        public HierarchyHandler(XmlaOlap4jCube cubeForCallback) {
            this.cubeForCallback = cubeForCallback;
        }
        public void handle(
            Element row, Context context, List<XmlaOlap4jHierarchy> list)
            throws OlapException
        {
            /*
            Example:

            <row>
                <CATALOG_NAME>FoodMart</CATALOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <CUBE_NAME>Sales</CUBE_NAME>
                <DIMENSION_UNIQUE_NAME>[Customers]</DIMENSION_UNIQUE_NAME>
                <HIERARCHY_NAME>Customers</HIERARCHY_NAME>
                <HIERARCHY_UNIQUE_NAME>[Customers]</HIERARCHY_UNIQUE_NAME>
                <HIERARCHY_CAPTION>Customers</HIERARCHY_CAPTION>
                <DIMENSION_TYPE>3</DIMENSION_TYPE>
                <HIERARCHY_CARDINALITY>10407</HIERARCHY_CARDINALITY>
                <DEFAULT_MEMBER>[Customers].[All Customers]</DEFAULT_MEMBER>
                <ALL_MEMBER>[Customers].[All Customers]</ALL_MEMBER>
                <DESCRIPTION>Sales Cube - Customers Hierarchy</DESCRIPTION>
                <STRUCTURE>0</STRUCTURE>
                <IS_VIRTUAL>false</IS_VIRTUAL>
                <IS_READWRITE>false</IS_READWRITE>
                <DIMENSION_UNIQUE_SETTINGS>0</DIMENSION_UNIQUE_SETTINGS>
                <DIMENSION_IS_VISIBLE>true</DIMENSION_IS_VISIBLE>
                <HIERARCHY_ORDINAL>9</HIERARCHY_ORDINAL>
                <DIMENSION_IS_SHARED>true</DIMENSION_IS_SHARED>
                <PARENT_CHILD>false</PARENT_CHILD>
            </row>

             */

            final String hierarchyUniqueName =
                stringElement(row, "HIERARCHY_UNIQUE_NAME");
            // SAP BW doesn't return a HIERARCHY_NAME attribute,
            // so try to use the unique name instead
            final String hierarchyName =
                stringElement(row, "HIERARCHY_NAME") == null
                ? (hierarchyUniqueName != null
                        ? hierarchyUniqueName.replaceAll("^\\[", "")
                             .replaceAll("\\]$", "")
                        : null)
                : stringElement(row, "HIERARCHY_NAME");
            final String hierarchyCaption =
                stringElement(row, "HIERARCHY_CAPTION");
            final String description =
                stringElement(row, "DESCRIPTION");
            final String allMember =
                stringElement(row, "ALL_MEMBER");
            final String defaultMemberUniqueName =
                stringElement(row, "DEFAULT_MEMBER");
            XmlaOlap4jHierarchy hierarchy = new XmlaOlap4jHierarchy(
                context.getDimension(row),
                hierarchyUniqueName,
                hierarchyName,
                hierarchyCaption,
                description,
                allMember != null,
                defaultMemberUniqueName);
            list.add(hierarchy);
            cubeForCallback.hierarchiesByUname.put(
                hierarchy.getUniqueName(),
                hierarchy);
        }
    }

    static class LevelHandler extends HandlerImpl<XmlaOlap4jLevel> {
        public static final int MDLEVEL_TYPE_CALCULATED = 0x0002;
        private final XmlaOlap4jCube cubeForCallback;

        public LevelHandler(XmlaOlap4jCube cubeForCallback) {
            this.cubeForCallback = cubeForCallback;
        }

        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jLevel> list)
        {
            /*
            Example:

            <row>
                <CATALOG_NAME>FoodMart</CATALOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <CUBE_NAME>Sales</CUBE_NAME>
                <DIMENSION_UNIQUE_NAME>[Customers]</DIMENSION_UNIQUE_NAME>
                <HIERARCHY_UNIQUE_NAME>[Customers]</HIERARCHY_UNIQUE_NAME>
                <LEVEL_NAME>(All)</LEVEL_NAME>
                <LEVEL_UNIQUE_NAME>[Customers].[(All)]</LEVEL_UNIQUE_NAME>
                <LEVEL_CAPTION>(All)</LEVEL_CAPTION>
                <LEVEL_NUMBER>0</LEVEL_NUMBER>
                <LEVEL_CARDINALITY>1</LEVEL_CARDINALITY>
                <LEVEL_TYPE>1</LEVEL_TYPE>
                <CUSTOM_ROLLUP_SETTINGS>0</CUSTOM_ROLLUP_SETTINGS>
                <LEVEL_UNIQUE_SETTINGS>3</LEVEL_UNIQUE_SETTINGS>
                <LEVEL_IS_VISIBLE>true</LEVEL_IS_VISIBLE>
                <DESCRIPTION>Sales Cube - Customers Hierarchy - (All)
                Level</DESCRIPTION>
            </row>

             */

            final String levelUniqueName =
                stringElement(row, "LEVEL_UNIQUE_NAME");
            // SAP BW doesn't return a HIERARCHY_NAME attribute,
            // so try to use the unique name instead
            final String levelName =
                stringElement(row, "LEVEL_NAME") == null
                    ? (levelUniqueName != null
                            ? levelUniqueName.replaceAll("^\\[", "")
                                    .replaceAll("\\]$", "")
                            : null)
                    : stringElement(row, "LEVEL_NAME");
            final String levelCaption =
                stringElement(row, "LEVEL_CAPTION");
            final String description =
                stringElement(row, "DESCRIPTION");
            final int levelNumber =
                integerElement(row, "LEVEL_NUMBER");
            final Integer levelTypeCode = integerElement(row, "LEVEL_TYPE");
            final Level.Type levelType =
                Level.Type.getDictionary().forOrdinal(levelTypeCode);
            boolean calculated = (levelTypeCode & MDLEVEL_TYPE_CALCULATED) != 0;
            final int levelCardinality =
                integerElement(row, "LEVEL_CARDINALITY");
            XmlaOlap4jLevel level = new XmlaOlap4jLevel(
                context.getHierarchy(row), levelUniqueName, levelName,
                levelCaption, description, levelNumber, levelType,
                calculated, levelCardinality);
            list.add(level);
            cubeForCallback.levelsByUname.put(
                level.getUniqueName(),
                level);
        }
    }

    static class MeasureHandler extends HandlerImpl<XmlaOlap4jMeasure> {
        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jMeasure> list)
            throws OlapException
        {
            /*
            Example:
            <row>
                <CATALOG_NAME>FoodMart</CATALOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <CUBE_NAME>Sales</CUBE_NAME>
                <MEASURE_NAME>Profit</MEASURE_NAME>
                <MEASURE_UNIQUE_NAME>[Measures].[Profit]</MEASURE_UNIQUE_NAME>
                <MEASURE_CAPTION>Profit</MEASURE_CAPTION>
                <MEASURE_AGGREGATOR>127</MEASURE_AGGREGATOR>
                <DATA_TYPE>130</DATA_TYPE>
                <MEASURE_IS_VISIBLE>true</MEASURE_IS_VISIBLE>
                <DESCRIPTION>Sales Cube - Profit Member</DESCRIPTION>
            </row>

             */
            final String measureName =
                stringElement(row, "MEASURE_NAME");
            final String measureUniqueName =
                stringElement(row, "MEASURE_UNIQUE_NAME");
            final String measureCaption =
                stringElement(row, "MEASURE_CAPTION");
            final String description =
                stringElement(row, "DESCRIPTION");
            final Measure.Aggregator measureAggregator =
                Measure.Aggregator.getDictionary().forOrdinal(
                    integerElement(
                        row, "MEASURE_AGGREGATOR"));
            final Datatype datatype;
            Datatype ordinalDatatype =
                Datatype.getDictionary().forName(
                    stringElement(row, "DATA_TYPE"));
            if (ordinalDatatype == null) {
                datatype = Datatype.getDictionary().forOrdinal(
                    integerElement(row, "DATA_TYPE"));
            } else {
                datatype = ordinalDatatype;
            }
            final boolean measureIsVisible =
                booleanElement(row, "MEASURE_IS_VISIBLE");

            final Member member =
                context.getCube(row).getMetadataReader()
                    .lookupMemberByUniqueName(
                        measureUniqueName);

            if (member == null) {
                throw new OlapException(
                    "The server failed to resolve a member with the same unique name as a measure named "
                    + measureUniqueName);
            }

            list.add(
                new XmlaOlap4jMeasure(
                    (XmlaOlap4jLevel)member.getLevel(), measureUniqueName,
                    measureName, measureCaption, description, null,
                    measureAggregator, datatype, measureIsVisible,
                    member.getOrdinal()));
        }

        public void sortList(List<XmlaOlap4jMeasure> list) {
            Collections.sort(
                list,
                new Comparator<XmlaOlap4jMeasure>() {
                    public int compare(
                        XmlaOlap4jMeasure o1,
                        XmlaOlap4jMeasure o2)
                    {
                        return o1.getOrdinal() - o2.getOrdinal();
                    }
                }
            );
        }
    }

    static class MemberHandler extends HandlerImpl<XmlaOlap4jMember> {

        /**
         * Collection of nodes to ignore because they represent standard
         * built-in properties of Members.
         */
        private static final Set<String> EXCLUDED_PROPERTY_NAMES =
            new HashSet<String>(
                Arrays.asList(
                    Property.StandardMemberProperty.CATALOG_NAME.name(),
                    Property.StandardMemberProperty.CUBE_NAME.name(),
                    Property.StandardMemberProperty.DIMENSION_UNIQUE_NAME
                        .name(),
                    Property.StandardMemberProperty.HIERARCHY_UNIQUE_NAME
                        .name(),
                    Property.StandardMemberProperty.LEVEL_UNIQUE_NAME.name(),
                    Property.StandardMemberProperty.PARENT_LEVEL.name(),
                    Property.StandardMemberProperty.PARENT_COUNT.name(),
                    Property.StandardMemberProperty.MEMBER_KEY.name(),
                    Property.StandardMemberProperty.IS_PLACEHOLDERMEMBER.name(),
                    Property.StandardMemberProperty.IS_DATAMEMBER.name(),
                    Property.StandardMemberProperty.LEVEL_NUMBER.name(),
                    Property.StandardMemberProperty.MEMBER_ORDINAL.name(),
                    Property.StandardMemberProperty.MEMBER_UNIQUE_NAME.name(),
                    Property.StandardMemberProperty.MEMBER_NAME.name(),
                    Property.StandardMemberProperty.PARENT_UNIQUE_NAME.name(),
                    Property.StandardMemberProperty.MEMBER_TYPE.name(),
                    Property.StandardMemberProperty.MEMBER_CAPTION.name(),
                    Property.StandardMemberProperty.CHILDREN_CARDINALITY.name(),
                    Property.StandardMemberProperty.DEPTH.name()));

        /**
         * Cached value returned by the {@link Member.Type#values} method, which
         * calls {@link Class#getEnumConstants()} and unfortunately clones an
         * array every time.
         */
        private static final Member.Type[] MEMBER_TYPE_VALUES =
            Member.Type.values();

        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jMember> list)
        {
            /*
            Example:

            <row>
                <CATALOG_NAME>FoodMart</CATALOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <CUBE_NAME>Sales</CUBE_NAME>
                <DIMENSION_UNIQUE_NAME>[Gender]</DIMENSION_UNIQUE_NAME>
                <HIERARCHY_UNIQUE_NAME>[Gender]</HIERARCHY_UNIQUE_NAME>
                <LEVEL_UNIQUE_NAME>[Gender].[Gender]</LEVEL_UNIQUE_NAME>
                <LEVEL_NUMBER>1</LEVEL_NUMBER>
                <MEMBER_ORDINAL>1</MEMBER_ORDINAL>
                <MEMBER_NAME>F</MEMBER_NAME>
                <MEMBER_UNIQUE_NAME>[Gender].[F]</MEMBER_UNIQUE_NAME>
                <MEMBER_TYPE>1</MEMBER_TYPE>
                <MEMBER_CAPTION>F</MEMBER_CAPTION>
                <CHILDREN_CARDINALITY>0</CHILDREN_CARDINALITY>
                <PARENT_LEVEL>0</PARENT_LEVEL>
                <PARENT_UNIQUE_NAME>[Gender].[All Gender]</PARENT_UNIQUE_NAME>
                <PARENT_COUNT>1</PARENT_COUNT>
                <DEPTH>1</DEPTH>          <!-- mondrian-specific -->
            </row>

             */
            if (false) {
            int levelNumber =
                integerElement(
                    row,
                    Property.StandardMemberProperty.LEVEL_NUMBER.name());
            }
            int memberOrdinal =
                integerElement(
                    row,
                    Property.StandardMemberProperty.MEMBER_ORDINAL.name());
            String memberUniqueName =
                stringElement(
                    row,
                    Property.StandardMemberProperty.MEMBER_UNIQUE_NAME.name());
            String memberName =
                stringElement(
                    row,
                    Property.StandardMemberProperty.MEMBER_NAME.name());
            String parentUniqueName =
                stringElement(
                    row,
                    Property.StandardMemberProperty.PARENT_UNIQUE_NAME.name());
            Member.Type memberType =
                MEMBER_TYPE_VALUES[
                    integerElement(
                        row,
                        Property.StandardMemberProperty.MEMBER_TYPE.name())];
            String memberCaption =
                stringElement(
                    row,
                    Property.StandardMemberProperty.MEMBER_CAPTION.name());
            int childrenCardinality =
                integerElement(
                    row,
                    Property.StandardMemberProperty.CHILDREN_CARDINALITY
                        .name());

            // Gather member property values into a temporary map, so we can
            // create the member with all properties known. XmlaOlap4jMember
            // uses an ArrayMap for property values and it is not efficient to
            // add entries to the map one at a time.
            final XmlaOlap4jLevel level = context.getLevel(row);
            final Map<Property, Object> map =
                new HashMap<Property, Object>();
            addUserDefinedDimensionProperties(row, level, map);

            // Usually members have the same depth as their level. (Ragged and
            // parent-child hierarchies are an exception.) Only store depth for
            // the unusual ones.
            final Integer depth =
                integerElement(
                    row,
                    Property.StandardMemberProperty.DEPTH.name());
            if (depth != null
                && depth.intValue() != level.getDepth())
            {
                map.put(
                    Property.StandardMemberProperty.DEPTH,
                    depth);
            }

            // If this member is a measure, we want to return an object that
            // implements the Measure interface to all API calls. But we also
            // need to retrieve the properties that occur in MDSCHEMA_MEMBERS
            // that are not available in MDSCHEMA_MEASURES, so we create a
            // member for internal use.
            XmlaOlap4jMember member =
                new XmlaOlap4jMember(
                    level, memberUniqueName, memberName,
                    memberCaption, "", parentUniqueName, memberType,
                    childrenCardinality, memberOrdinal, map);
            list.add(member);
        }

        private void addUserDefinedDimensionProperties(
            Element row,
            XmlaOlap4jLevel level,
            Map<Property, Object> map)
        {
            NodeList nodes = row.getChildNodes();
            for (int i = 0; i < nodes.getLength(); i++) {
                Node node = nodes.item(i);
                if (EXCLUDED_PROPERTY_NAMES.contains(node.getLocalName())) {
                    continue;
                }
                for (Property property : level.getProperties()) {
                    if (property instanceof XmlaOlap4jProperty
                        && property.getName().equalsIgnoreCase(
                        node.getLocalName()))
                    {
                        map.put(property, node.getTextContent());
                    }
                }
            }
        }
    }

    static class NamedSetHandler extends HandlerImpl<XmlaOlap4jNamedSet> {
        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jNamedSet> list)
        {
            /*
            Example:

            <row>
                <CATALOG_NAME>FoodMart</CATALOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <CUBE_NAME>Warehouse</CUBE_NAME>
                <SET_NAME>[Top Sellers]</SET_NAME>
                <SCOPE>1</SCOPE>
            </row>

             */
            final String setName =
                stringElement(row, "SET_NAME");
            list.add(
                new XmlaOlap4jNamedSet(
                    context.getCube(row), setName));
        }
    }

    static class SchemaHandler extends HandlerImpl<XmlaOlap4jSchema> {
        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jSchema> list)
            throws OlapException
        {
            /*
            Example:

            <row>
                <CATALOG_NAME>LOCALDB</CATLAOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <SCHEMA_OWNER>dbo</SCHEMA_OWNER>
            </row>
             */
            String schemaName = stringElement(row, "SCHEMA_NAME");
            list.add(
                new XmlaOlap4jSchema(
                    context.getCatalog(row),
                    (schemaName == null) ? "" : schemaName));
        }
    }

    static class CatalogSchemaHandler extends HandlerImpl<XmlaOlap4jSchema> {

        private String catalogName;

        public CatalogSchemaHandler(String catalogName) {
            super();
            if (catalogName == null) {
                throw new RuntimeException(
                    "The CatalogSchemaHandler handler requires a catalog "
                    + "name.");
            }
            this.catalogName = catalogName;
        }

        public void handle(
            Element row,
            Context context,
            List<XmlaOlap4jSchema> list)
            throws OlapException
        {
            /*
            Example:

            <row>
                <CATALOG_NAME>CatalogName</CATLAOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <SCHEMA_OWNER>dbo</SCHEMA_OWNER>
            </row>
             */

            // We are looking for a schema name from the cubes query restricted
            // on the catalog name. Some servers don't support nor include the
            // SCHEMA_NAME column in its response. If it's null, we convert it
            // to an empty string as to not cause problems later on.
            final String schemaName = stringElement(row, "SCHEMA_NAME");
            final String catalogName = stringElement(row, "CATALOG_NAME");
            final String schemaName2 = (schemaName == null) ? "" : schemaName;
            if (this.catalogName.equals(catalogName)
                && ((NamedList<XmlaOlap4jSchema>)list).get(schemaName2) == null)
            {
                list.add(
                    new XmlaOlap4jSchema(
                        context.getCatalog(row), schemaName2));
            }
        }
    }

    static class PropertyHandler extends HandlerImpl<XmlaOlap4jProperty> {

        public void handle(
            Element row,
            Context context, List<XmlaOlap4jProperty> list) throws OlapException
        {
            /*
            Example:

            <row>
                <CATALOG_NAME>FoodMart</CATALOG_NAME>
                <SCHEMA_NAME>FoodMart</SCHEMA_NAME>
                <CUBE_NAME>HR</CUBE_NAME>
                <DIMENSION_UNIQUE_NAME>[Store]</DIMENSION_UNIQUE_NAME>
                <HIERARCHY_UNIQUE_NAME>[Store]</HIERARCHY_UNIQUE_NAME>
                <LEVEL_UNIQUE_NAME>[Store].[Store Name]</LEVEL_UNIQUE_NAME>
                <PROPERTY_NAME>Store Manager</PROPERTY_NAME>
                <PROPERTY_CAPTION>Store Manager</PROPERTY_CAPTION>
                <PROPERTY_TYPE>1</PROPERTY_TYPE>
                <DATA_TYPE>130</DATA_TYPE>
                <PROPERTY_CONTENT_TYPE>0</PROPERTY_CONTENT_TYPE>
                <DESCRIPTION>HR Cube - Store Hierarchy - Store
                    Name Level - Store Manager Property</DESCRIPTION>
            </row>
             */
            String description = stringElement(row, "DESCRIPTION");
            String uniqueName = stringElement(row, "DESCRIPTION");
            String caption = stringElement(row, "PROPERTY_CAPTION");
            String name = stringElement(row, "PROPERTY_NAME");
            Datatype datatype;

            Datatype ordinalDatatype =
                Datatype.getDictionary().forName(
                    stringElement(row, "DATA_TYPE"));
            if (ordinalDatatype == null) {
                datatype = Datatype.getDictionary().forOrdinal(
                    integerElement(row, "DATA_TYPE"));
            } else {
                datatype = ordinalDatatype;
            }

            final Integer contentTypeOrdinal =
                integerElement(row, "PROPERTY_CONTENT_TYPE");
            Property.ContentType contentType =
                contentTypeOrdinal == null
                    ? null
                    : Property.ContentType.getDictionary().forOrdinal(
                        contentTypeOrdinal);
            int propertyType = integerElement(row, "PROPERTY_TYPE");
            Set<Property.TypeFlag> type =
                Property.TypeFlag.getDictionary().forMask(propertyType);
            list.add(
                new XmlaOlap4jProperty(
                    uniqueName, name, caption, description, datatype, type,
                    contentType));
        }
    }

    /**
     * Callback for converting XMLA results into metadata elements.
     */
    interface Handler<T extends Named> {
        /**
         * Converts an XML element from an XMLA result set into a metadata
         * element and appends it to a list of metadata elements.
         *
         * @param row XMLA element
         *
         * @param context Context (schema, cube, dimension, etc.) that the
         * request was executed in and that the element will belong to
         *
         * @param list List of metadata elements to append new metadata element
         *
         * @throws OlapException on error
         */
        void handle(
            Element row,
            Context context,
            List<T> list) throws OlapException;

        /**
         * Sorts a list of metadata elements.
         *
         * <p>For most element types, the order returned by XMLA is correct, and
         * this method will no-op.
         *
         * @param list List of metadata elements
         */
        void sortList(List<T> list);
    }

    static abstract class HandlerImpl<T extends Named> implements Handler<T> {
        public void sortList(List<T> list) {
            // do nothing - assume XMLA returned list in correct order
        }
    }

    static class Context {
        final XmlaOlap4jConnection olap4jConnection;
        final XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData;
        final XmlaOlap4jCatalog olap4jCatalog;
        final XmlaOlap4jSchema olap4jSchema;
        final XmlaOlap4jCube olap4jCube;
        final XmlaOlap4jDimension olap4jDimension;
        final XmlaOlap4jHierarchy olap4jHierarchy;
        final XmlaOlap4jLevel olap4jLevel;

        /**
         * Creates a Context.
         *
         * @param olap4jConnection Connection (must not be null)
         * @param olap4jDatabaseMetaData DatabaseMetaData (may be null)
         * @param olap4jCatalog Catalog (may be null if DatabaseMetaData is
         * null)
         * @param olap4jSchema Schema (may be null if Catalog is null)
         * @param olap4jCube Cube (may be null if Schema is null)
         * @param olap4jDimension Dimension (may be null if Cube is null)
         * @param olap4jHierarchy Hierarchy (may be null if Dimension is null)
         * @param olap4jLevel Level (may be null if Hierarchy is null)
         */
        Context(
            XmlaOlap4jConnection olap4jConnection,
            XmlaOlap4jDatabaseMetaData olap4jDatabaseMetaData,
            XmlaOlap4jCatalog olap4jCatalog,
            XmlaOlap4jSchema olap4jSchema,
            XmlaOlap4jCube olap4jCube,
            XmlaOlap4jDimension olap4jDimension,
            XmlaOlap4jHierarchy olap4jHierarchy,
            XmlaOlap4jLevel olap4jLevel)
        {
            this.olap4jConnection = olap4jConnection;
            this.olap4jDatabaseMetaData = olap4jDatabaseMetaData;
            this.olap4jCatalog = olap4jCatalog;
            this.olap4jSchema = olap4jSchema;
            this.olap4jCube = olap4jCube;
            this.olap4jDimension = olap4jDimension;
            this.olap4jHierarchy = olap4jHierarchy;
            this.olap4jLevel = olap4jLevel;
            assert (olap4jDatabaseMetaData != null || olap4jCatalog == null)
                && (olap4jCatalog != null || olap4jSchema == null)
                && (olap4jSchema != null || olap4jCube == null)
                && (olap4jCube != null || olap4jDimension == null)
                && (olap4jDimension != null || olap4jHierarchy == null)
                && (olap4jHierarchy != null || olap4jLevel == null);
        }

        /**
         * Shorthand way to create a Context at Cube level or finer.
         *
         * @param olap4jCube Cube (must not be null)
         * @param olap4jDimension Dimension (may be null)
         * @param olap4jHierarchy Hierarchy (may be null if Dimension is null)
         * @param olap4jLevel Level (may be null if Hierarchy is null)
         */
        Context(
            XmlaOlap4jCube olap4jCube,
            XmlaOlap4jDimension olap4jDimension,
            XmlaOlap4jHierarchy olap4jHierarchy,
            XmlaOlap4jLevel olap4jLevel)
        {
            this(
                olap4jCube.olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData
                    .olap4jConnection,
                olap4jCube.olap4jSchema.olap4jCatalog.olap4jDatabaseMetaData,
                olap4jCube.olap4jSchema.olap4jCatalog,
                olap4jCube.olap4jSchema,
                olap4jCube,
                olap4jDimension,
                olap4jHierarchy,
                olap4jLevel);
        }

        /**
         * Shorthand way to create a Context at Level level.
         *
         * @param olap4jLevel Level (must not be null)
         */
        Context(XmlaOlap4jLevel olap4jLevel)
        {
            this(
                olap4jLevel.olap4jHierarchy.olap4jDimension.olap4jCube,
                olap4jLevel.olap4jHierarchy.olap4jDimension,
                olap4jLevel.olap4jHierarchy,
                olap4jLevel);
        }

        XmlaOlap4jHierarchy getHierarchy(Element row) {
            if (olap4jHierarchy != null) {
                return olap4jHierarchy;
            }
            final String hierarchyUniqueName =
                stringElement(row, "HIERARCHY_UNIQUE_NAME");
            XmlaOlap4jHierarchy hierarchy =
                getCube(row).hierarchiesByUname.get(hierarchyUniqueName);
            if (hierarchy == null) {
                // Apparently, the code has requested a member that is
                // not queried for yet. We must force the initialization
                // of the dimension tree first.
                final String dimensionUniqueName =
                    stringElement(row, "DIMENSION_UNIQUE_NAME");
                String dimensionName =
                    Olap4jUtil.parseUniqueName(dimensionUniqueName).get(0);
                XmlaOlap4jDimension dimension =
                    getCube(row).dimensions.get(dimensionName);
                dimension.getHierarchies().size();
                // Now we attempt to resolve again
                hierarchy =
                    getCube(row).hierarchiesByUname.get(hierarchyUniqueName);
            }
            return hierarchy;
        }

        XmlaOlap4jCube getCube(Element row) {
            if (olap4jCube != null) {
                return olap4jCube;
            }
            throw new UnsupportedOperationException(); // todo:
        }

        XmlaOlap4jDimension getDimension(Element row) {
            if (olap4jDimension != null) {
                return olap4jDimension;
            }
            final String dimensionUniqueName =
                stringElement(row, "DIMENSION_UNIQUE_NAME");
            XmlaOlap4jDimension dimension = getCube(row)
                .dimensionsByUname.get(dimensionUniqueName);
            // Apparently, the code has requested a member that is
            // not queried for yet.
            if (dimension == null) {
                final String dimensionName =
                    stringElement(row, "DIMENSION_NAME");
                return getCube(row).dimensions.get(dimensionName);
            }
            return dimension;
        }

        public XmlaOlap4jLevel getLevel(Element row) {
            if (olap4jLevel != null) {
                return olap4jLevel;
            }
            final String levelUniqueName =
                stringElement(row, "LEVEL_UNIQUE_NAME");
            XmlaOlap4jLevel level =
                getCube(row).levelsByUname.get(levelUniqueName);
            if (level == null) {
                // Apparently, the code has requested a member that is
                // not queried for yet. We must force the initialization
                // of the dimension tree first.
                final String dimensionUniqueName =
                    stringElement(row, "DIMENSION_UNIQUE_NAME");
                String dimensionName =
                    Olap4jUtil.parseUniqueName(dimensionUniqueName).get(0);
                XmlaOlap4jDimension dimension =
                    getCube(row).dimensions.get(dimensionName);
                for (Hierarchy hierarchyInit : dimension.getHierarchies()) {
                    hierarchyInit.getLevels().size();
                }
                // Now we attempt to resolve again
                level = getCube(row).levelsByUname.get(levelUniqueName);
            }
            return level;
        }

        public XmlaOlap4jCatalog getCatalog(Element row) throws OlapException {
            if (olap4jCatalog != null) {
                return olap4jCatalog;
            }
            final String catalogName =
                stringElement(row, "CATALOG_NAME");
            return (XmlaOlap4jCatalog) olap4jConnection.getOlapCatalogs().get(
                catalogName);
        }
    }

    enum MetadataRequest {
        DISCOVER_DATASOURCES(
            new MetadataColumn("DataSourceName"),
            new MetadataColumn("DataSourceDescription"),
            new MetadataColumn("URL"),
            new MetadataColumn("DataSourceInfo"),
            new MetadataColumn("ProviderName"),
            new MetadataColumn("ProviderType"),
            new MetadataColumn("AuthenticationMode")),
        DISCOVER_SCHEMA_ROWSETS(
            new MetadataColumn("SchemaName"),
            new MetadataColumn("SchemaGuid"),
            new MetadataColumn("Restrictions"),
            new MetadataColumn("Description")),
        DISCOVER_ENUMERATORS(
            new MetadataColumn("EnumName"),
            new MetadataColumn("EnumDescription"),
            new MetadataColumn("EnumType"),
            new MetadataColumn("ElementName"),
            new MetadataColumn("ElementDescription"),
            new MetadataColumn("ElementValue")),
        DISCOVER_PROPERTIES(
            new MetadataColumn("PropertyName"),
            new MetadataColumn("PropertyDescription"),
            new MetadataColumn("PropertyType"),
            new MetadataColumn("PropertyAccessType"),
            new MetadataColumn("IsRequired"),
            new MetadataColumn("Value")),
        DISCOVER_KEYWORDS(
            new MetadataColumn("Keyword")),
        DISCOVER_LITERALS(
            new MetadataColumn("LiteralName"),
            new MetadataColumn("LiteralValue"),
            new MetadataColumn("LiteralInvalidChars"),
            new MetadataColumn("LiteralInvalidStartingChars"),
            new MetadataColumn("LiteralMaxLength")),
        DBSCHEMA_CATALOGS(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("DESCRIPTION"),
            new MetadataColumn("ROLES"),
            new MetadataColumn("DATE_MODIFIED")),
        DBSCHEMA_COLUMNS(
            new MetadataColumn("TABLE_CATALOG"),
            new MetadataColumn("TABLE_SCHEMA"),
            new MetadataColumn("TABLE_NAME"),
            new MetadataColumn("COLUMN_NAME"),
            new MetadataColumn("ORDINAL_POSITION"),
            new MetadataColumn("COLUMN_HAS_DEFAULT"),
            new MetadataColumn("COLUMN_FLAGS"),
            new MetadataColumn("IS_NULLABLE"),
            new MetadataColumn("DATA_TYPE"),
            new MetadataColumn("CHARACTER_MAXIMUM_LENGTH"),
            new MetadataColumn("CHARACTER_OCTET_LENGTH"),
            new MetadataColumn("NUMERIC_PRECISION"),
            new MetadataColumn("NUMERIC_SCALE")),
        DBSCHEMA_PROVIDER_TYPES(
            new MetadataColumn("TYPE_NAME"),
            new MetadataColumn("DATA_TYPE"),
            new MetadataColumn("COLUMN_SIZE"),
            new MetadataColumn("LITERAL_PREFIX"),
            new MetadataColumn("LITERAL_SUFFIX"),
            new MetadataColumn("IS_NULLABLE"),
            new MetadataColumn("CASE_SENSITIVE"),
            new MetadataColumn("SEARCHABLE"),
            new MetadataColumn("UNSIGNED_ATTRIBUTE"),
            new MetadataColumn("FIXED_PREC_SCALE"),
            new MetadataColumn("AUTO_UNIQUE_VALUE"),
            new MetadataColumn("IS_LONG"),
            new MetadataColumn("BEST_MATCH")),
        DBSCHEMA_TABLES(
            new MetadataColumn("TABLE_CATALOG"),
            new MetadataColumn("TABLE_SCHEMA"),
            new MetadataColumn("TABLE_NAME"),
            new MetadataColumn("TABLE_TYPE"),
            new MetadataColumn("TABLE_GUID"),
            new MetadataColumn("DESCRIPTION"),
            new MetadataColumn("TABLE_PROPID"),
            new MetadataColumn("DATE_CREATED"),
            new MetadataColumn("DATE_MODIFIED")),
        DBSCHEMA_TABLES_INFO(
            new MetadataColumn("TABLE_CATALOG"),
            new MetadataColumn("TABLE_SCHEMA"),
            new MetadataColumn("TABLE_NAME"),
            new MetadataColumn("TABLE_TYPE"),
            new MetadataColumn("TABLE_GUID"),
            new MetadataColumn("BOOKMARKS"),
            new MetadataColumn("BOOKMARK_TYPE"),
            new MetadataColumn("BOOKMARK_DATATYPE"),
            new MetadataColumn("BOOKMARK_MAXIMUM_LENGTH"),
            new MetadataColumn("BOOKMARK_INFORMATION"),
            new MetadataColumn("TABLE_VERSION"),
            new MetadataColumn("CARDINALITY"),
            new MetadataColumn("DESCRIPTION"),
            new MetadataColumn("TABLE_PROPID")),
        DBSCHEMA_SCHEMATA(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("SCHEMA_OWNER")),
        MDSCHEMA_ACTIONS(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("ACTION_NAME"),
            new MetadataColumn("COORDINATE"),
            new MetadataColumn("COORDINATE_TYPE")),
        MDSCHEMA_CUBES(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("CUBE_TYPE"),
            new MetadataColumn("CUBE_GUID"),
            new MetadataColumn("CREATED_ON"),
            new MetadataColumn("LAST_SCHEMA_UPDATE"),
            new MetadataColumn("SCHEMA_UPDATED_BY"),
            new MetadataColumn("LAST_DATA_UPDATE"),
            new MetadataColumn("DATA_UPDATED_BY"),
            new MetadataColumn("IS_DRILLTHROUGH_ENABLED"),
            new MetadataColumn("IS_WRITE_ENABLED"),
            new MetadataColumn("IS_LINKABLE"),
            new MetadataColumn("IS_SQL_ENABLED"),
            new MetadataColumn("DESCRIPTION"),
            new MetadataColumn("CUBE_CAPTION"),
            new MetadataColumn("BASE_CUBE_NAME")),
        MDSCHEMA_DIMENSIONS(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("DIMENSION_NAME"),
            new MetadataColumn("DIMENSION_UNIQUE_NAME"),
            new MetadataColumn("DIMENSION_GUID"),
            new MetadataColumn("DIMENSION_CAPTION"),
            new MetadataColumn("DIMENSION_ORDINAL"),
            new MetadataColumn("DIMENSION_TYPE"),
            new MetadataColumn("DIMENSION_CARDINALITY"),
            new MetadataColumn("DEFAULT_HIERARCHY"),
            new MetadataColumn("DESCRIPTION"),
            new MetadataColumn("IS_VIRTUAL"),
            new MetadataColumn("IS_READWRITE"),
            new MetadataColumn("DIMENSION_UNIQUE_SETTINGS"),
            new MetadataColumn("DIMENSION_MASTER_UNIQUE_NAME"),
            new MetadataColumn("DIMENSION_IS_VISIBLE")),
        MDSCHEMA_FUNCTIONS(
            new MetadataColumn("FUNCTION_NAME"),
            new MetadataColumn("DESCRIPTION"),
            new MetadataColumn("PARAMETER_LIST"),
            new MetadataColumn("RETURN_TYPE"),
            new MetadataColumn("ORIGIN"),
            new MetadataColumn("INTERFACE_NAME"),
            new MetadataColumn("LIBRARY_NAME"),
            new MetadataColumn("CAPTION")),
        MDSCHEMA_HIERARCHIES(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("DIMENSION_UNIQUE_NAME"),
            new MetadataColumn("HIERARCHY_NAME"),
            new MetadataColumn("HIERARCHY_UNIQUE_NAME"),
            new MetadataColumn("HIERARCHY_GUID"),
            new MetadataColumn("HIERARCHY_CAPTION"),
            new MetadataColumn("DIMENSION_TYPE"),
            new MetadataColumn("HIERARCHY_CARDINALITY"),
            new MetadataColumn("DEFAULT_MEMBER"),
            new MetadataColumn("ALL_MEMBER"),
            new MetadataColumn("DESCRIPTION"),
            new MetadataColumn("STRUCTURE"),
            new MetadataColumn("IS_VIRTUAL"),
            new MetadataColumn("IS_READWRITE"),
            new MetadataColumn("DIMENSION_UNIQUE_SETTINGS"),
            new MetadataColumn("DIMENSION_IS_VISIBLE"),
            new MetadataColumn("HIERARCHY_ORDINAL"),
            new MetadataColumn("DIMENSION_IS_SHARED"),
            new MetadataColumn("PARENT_CHILD")),
        MDSCHEMA_LEVELS(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("DIMENSION_UNIQUE_NAME"),
            new MetadataColumn("HIERARCHY_UNIQUE_NAME"),
            new MetadataColumn("LEVEL_NAME"),
            new MetadataColumn("LEVEL_UNIQUE_NAME"),
            new MetadataColumn("LEVEL_GUID"),
            new MetadataColumn("LEVEL_CAPTION"),
            new MetadataColumn("LEVEL_NUMBER"),
            new MetadataColumn("LEVEL_CARDINALITY"),
            new MetadataColumn("LEVEL_TYPE"),
            new MetadataColumn("CUSTOM_ROLLUP_SETTINGS"),
            new MetadataColumn("LEVEL_UNIQUE_SETTINGS"),
            new MetadataColumn("LEVEL_IS_VISIBLE"),
            new MetadataColumn("DESCRIPTION")),
        MDSCHEMA_MEASURES(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("MEASURE_NAME"),
            new MetadataColumn("MEASURE_UNIQUE_NAME"),
            new MetadataColumn("MEASURE_CAPTION"),
            new MetadataColumn("MEASURE_GUID"),
            new MetadataColumn("MEASURE_AGGREGATOR"),
            new MetadataColumn("DATA_TYPE"),
            new MetadataColumn("MEASURE_IS_VISIBLE"),
            new MetadataColumn("LEVELS_LIST"),
            new MetadataColumn("DESCRIPTION")),
        MDSCHEMA_MEMBERS(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("DIMENSION_UNIQUE_NAME"),
            new MetadataColumn("HIERARCHY_UNIQUE_NAME"),
            new MetadataColumn("LEVEL_UNIQUE_NAME"),
            new MetadataColumn("LEVEL_NUMBER"),
            new MetadataColumn("MEMBER_ORDINAL"),
            new MetadataColumn("MEMBER_NAME"),
            new MetadataColumn("MEMBER_UNIQUE_NAME"),
            new MetadataColumn("MEMBER_TYPE"),
            new MetadataColumn("MEMBER_GUID"),
            new MetadataColumn("MEMBER_CAPTION"),
            new MetadataColumn("CHILDREN_CARDINALITY"),
            new MetadataColumn("PARENT_LEVEL"),
            new MetadataColumn("PARENT_UNIQUE_NAME"),
            new MetadataColumn("PARENT_COUNT"),
            new MetadataColumn("TREE_OP"),
            new MetadataColumn("DEPTH")),
        MDSCHEMA_PROPERTIES(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("DIMENSION_UNIQUE_NAME"),
            new MetadataColumn("HIERARCHY_UNIQUE_NAME"),
            new MetadataColumn("LEVEL_UNIQUE_NAME"),
            new MetadataColumn("MEMBER_UNIQUE_NAME"),
            new MetadataColumn("PROPERTY_NAME"),
            new MetadataColumn("PROPERTY_CAPTION"),
            new MetadataColumn("PROPERTY_TYPE"),
            new MetadataColumn("DATA_TYPE"),
            new MetadataColumn("PROPERTY_CONTENT_TYPE"),
            new MetadataColumn("DESCRIPTION")),
        MDSCHEMA_SETS(
            new MetadataColumn("CATALOG_NAME"),
            new MetadataColumn("SCHEMA_NAME"),
            new MetadataColumn("CUBE_NAME"),
            new MetadataColumn("SET_NAME"),
            new MetadataColumn("SCOPE"));

        final List<MetadataColumn> columns;
        final Map<String, MetadataColumn> columnsByName;

        /**
         * Creates a MetadataRequest.
         *
         * @param columns Columns
         */
        MetadataRequest(MetadataColumn... columns) {
            if (name().equals("DBSCHEMA_CATALOGS")) {
                // DatabaseMetaData.getCatalogs() is defined by JDBC not XMLA,
                // so has just one column. Ignore the 4 columns from XMLA.
                columns = new MetadataColumn[] {
                    new MetadataColumn("CATALOG_NAME", "TABLE_CAT")
                };
            } else if (name().equals("DBSCHEMA_SCHEMATA")) {
                // DatabaseMetaData.getCatalogs() is defined by JDBC not XMLA,
                // so has just one column. Ignore the 4 columns from XMLA.
                columns = new MetadataColumn[] {
                    new MetadataColumn("SCHEMA_NAME", "TABLE_SCHEM"),
                    new MetadataColumn("CATALOG_NAME", "TABLE_CAT")
                };
            }
            this.columns = UnmodifiableArrayList.asCopyOf(columns);
            final Map<String, MetadataColumn> map =
                new HashMap<String, MetadataColumn>();
            for (MetadataColumn column : columns) {
                map.put(column.name, column);
            }
            this.columnsByName = Collections.unmodifiableMap(map);
        }

        /**
         * Returns whether this request requires a
         * {@code &lt;DatasourceName&gt;} element.
         *
         * @return whether this request requires a DatasourceName element
         */
        public boolean requiresDatasourceName() {
            return this != DISCOVER_DATASOURCES;
        }

        /**
         * Returns whether this request requires a
         * {@code &lt;CatalogName&gt;} element.
         *
         * @return whether this request requires a CatalogName element
         */
        public boolean requiresCatalogName() {
            // If we don't specifiy CatalogName in the properties of an
            // MDSCHEMA_FUNCTIONS request, Mondrian's XMLA provider will give
            // us the whole set of functions multiplied by the number of
            // catalogs. JDBC (and Mondrian) assumes that functions belong to a
            // catalog whereas XMLA (and SSAS) assume that functions belong to
            // the database. Always specifying a catalog is the easiest way to
            // reconcile them.
            return this == MDSCHEMA_FUNCTIONS;
        }

        /**
         * Returns whether this request allows a
         * {@code &lt;CatalogName&gt;} element in the properties section of the
         * request. Even for requests that allow it, it is usually optional.
         *
         * @return whether this request allows a CatalogName element
         */
        public boolean allowsCatalogName() {
            return true;
        }

        /**
         * Returns the column with a given name, or null if there is no such
         * column.
         *
         * @param name Column name
         * @return Column, or null if not found
         */
        public MetadataColumn getColumn(String name) {
            return columnsByName.get(name);
        }
    }

    private static final Pattern LOWERCASE_PATTERN =
        Pattern.compile(".*[a-z].*");

    static class MetadataColumn {
        final String name;
        final String xmlaName;

        MetadataColumn(String xmlaName, String name) {
            this.xmlaName = xmlaName;
            this.name = name;
        }

        MetadataColumn(String xmlaName) {
            this.xmlaName = xmlaName;
            String name = xmlaName;
            if (LOWERCASE_PATTERN.matcher(name).matches()) {
                name = Olap4jUtil.camelToUpper(name);
            }
            // VALUE is a SQL reserved word
            if (name.equals("VALUE")) {
                name = "PROPERTY_VALUE";
            }
            this.name = name;
        }
    }

    private static class XmlaOlap4jMdxValidator implements MdxValidator {
        private final OlapConnection connection;

        XmlaOlap4jMdxValidator(OlapConnection connection) {
            this.connection = connection;
        }

        public SelectNode validateSelect(
            SelectNode selectNode)
            throws OlapException
        {
            StringWriter sw = new StringWriter();
            selectNode.unparse(new ParseTreeWriter(sw));
            String mdx = sw.toString();
            final XmlaOlap4jConnection olap4jConnection =
                (XmlaOlap4jConnection) connection;
            return selectNode;
        }
    }
}

// End XmlaOlap4jConnection.java
TOP

Related Classes of org.olap4j.driver.xmla.XmlaOlap4jConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.