<commit_before>/*
// 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 <NAME>
// 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.w3c.dom.*;
import org.xml.sax.SAXException;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
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$
 * @since May 23, 2007
 */
abstract class XmlaOlap4jConnection implements OlapConnection {
    /**
     * Handler for errors.
     */
    final XmlaHelper helper = new XmlaHelper();

    /**
     * <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 URL serverUrl;

    private Locale locale;

    /**
     * Name of the current catalog to which this connection is bound to,
     * as specified by the server.
     */
    private String nativeCatalogName;

    /**
     * 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 role that this connection impersonates.
     */
    private String roleName;

    /**
     * Provider name.
     */
    private String providerName;

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

    /**
     * Holds the database name as specified by the server. Necessary because
     * some servers (such as Mondrian) return both the provider
     * name and the database name in their response.
     *
     * <p>It's this value that we use inside queries, and not the JDBC
     * query value.
     */
    private String nativeDatabaseName;

    private boolean autoCommit;
    private boolean readOnly;

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

    /**
     * Name of the "DATA_SOURCE_NAME" column returned from
     * {@link org.olap4j.OlapDatabaseMetaData#getDatasources()}.
     */
    private static final String DATA_SOURCE_NAME = "DATA_SOURCE_NAME";

    /**
     * Name of the "PROVIDER_NAME" column returned from
     * {@link org.olap4j.OlapDatabaseMetaData#getDatasources()}.
     */
    private static final String PROVIDER_NAME = "PROVIDER_NAME";

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

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

        this.providerName =
            map.get(XmlaOlap4jDriver.Property.Provider.name());

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

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

        // Set URL of HTTP server.
        String serverUrl = map.get(XmlaOlap4jDriver.Property.Server.name());
        if (serverUrl == null) {
            throw getHelper().createException(
                "Connection property '"
                + XmlaOlap4jDriver.Property.Server.name()
                + "' must be specified");
        }

        // Basic authentication. Make sure the credentials passed as standard
        // JDBC parameters override any credentials already included in the URL
        // as part of the standard URL scheme.
        if (map.containsKey("user") && map.containsKey("password")) {
            serverUrl = serverUrl.replaceFirst(
                ":\\/\\/([^@]*@){0,1}",
                "://"
                + map.get("user")
                + ":"
                + map.get("password")
                + "@");
        }

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

        try {
            this.serverUrl = new URL(serverUrl);
        } catch (MalformedURLException e) {
            throw getHelper().createException(
                "Error while creating connection", e);
        }

        this.olap4jDatabaseMetaData =
            factory.newDatabaseMetaData(this);
    }

    /**
     * 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().toUpperCase()
                    + "."))
                {
                    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 void setDatabase(String databaseName) throws OlapException {
        this.databaseName = databaseName;
        this.nativeCatalogName = null;
    }
    public String getDatabase() throws OlapException {
     // If we already know it, return it.
        if (this.nativeDatabaseName != null) {
            return this.nativeDatabaseName;
        }

        ResultSet rSet = null;
        try {
            // We need to query for it
            rSet = this.olap4jDatabaseMetaData.getDatabases();

            // Check if the user requested a particular one.
            if (this.databaseName != null || this.providerName != null) {
                // We iterate through the databases
                while (rSet.next()) {
                    // Get current values
                    String currentDatasource = rSet.getString(DATA_SOURCE_NAME);
                    String currentProvider = rSet.getString(PROVIDER_NAME);

                    // If database and provider match, we got it.
                    // If database matches but no provider is specified, we
                    // got it.
                    // If provider matches but no database specified, we
                    // consider it good.
                    if (currentDatasource.equals(this.databaseName)
                        && currentProvider.equals(this.providerName)
                        || currentDatasource.equals(this.databaseName)
                        && this.providerName == null
                        || currentProvider.equals(this.providerName)
                        && this.databaseName == null)
                    {
                        // Got it
                        this.nativeDatabaseName = currentDatasource;
                        break;
                    }
                }
            } else {
                // Use first
                if (rSet.first()) {
                    this.nativeDatabaseName =
                        rSet.getString(DATA_SOURCE_NAME);
                }
            }

            // Throws exception to the client.  Tells that there are
            // no database corresponding to the search criteria.
            if (this.nativeDatabaseName == null) {
                throw getHelper().createException(
                    "No database could be found on the server.");
            }

            return this.nativeDatabaseName;
        } catch (OlapException e) {
            throw e;
        } catch (SQLException e) {
            throw getHelper().createException(
                "An exception occured while trying to resolve the database name.",
                e);
        } finally {
            try {
                if (rSet != null) {
                    rSet.close();
                }
            } catch (SQLException e) {
                // ignore
            }
        }
    }

    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 NamedList<Catalog> getCatalogs() {
        return Olap4jUtil.cast(olap4jDatabaseMetaData.getCatalogObjects());
    }

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

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

    public void setCatalog(String catalog) throws OlapException {
        this.catalogName = catalog;
        this.nativeCatalogName = null;
    }

    public String getCatalog() throws OlapException {
        if (this.nativeCatalogName == null) {
            if (catalogName != null) {
                this.nativeCatalogName = catalogName;
            } else if (olap4jDatabaseMetaData.getOlapCatalogs().size() == 0) {
                throw new OlapException(
                    "There is no catalog available to query against.");
            } else {
                this.nativeCatalogName =
                    olap4jDatabaseMetaData.getOlapCatalogs().get(0).getName();
            }
        }
        return nativeCatalogName;
    }

    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);
            }
        };
    }

    @Deprecated
    /*
     * This function must either be removed and the tests adapted or
     * be made package private.
     */
    public synchronized org.olap4j.metadata.Schema getSchema()
        throws OlapException
    {
        // initializes the olap4jSchema if necessary
        if (this.olap4jSchema == null) {
            final XmlaOlap4jCatalog catalog =
                this.olap4jDatabaseMetaData.getCatalogObjects().get(
                    getCatalog());
            this.olap4jSchema = catalog.schemas.get(0);
        }
        return olap4jSchema;
    }

    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(serverUrl, request);
        } catch (IOException e) {
            /*
             * FIXME This type of exception should not reach this point.
             * It was maintained because some other proxy implementations
             * exists out there that still throw an IOException arround.
             * This was a bad design which we will fix at some point but not
             * before the 1.0 release.
             */
            throw getHelper().createException(e);
        } 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 schema. (Note: What XMLA
        // calls a catalog, JDBC calls a schema.)
        if (context.olap4jSchema != null) {
            requestCatalogName = context.olap4jSchema.getName();
        }
        if (requestCatalogName == null
            && metadataRequest.requiresCatalogName())
        {
            requestCatalogName = context.olap4jConnection.getSchema().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<commit_msg>fix bug (that was already fixed) for catalog restriction in XMLA requests (for ssas / palo)<commit_after>/*
// 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 <NAME>
// 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.w3c.dom.*;
import org.xml.sax.SAXException;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
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$
 * @since May 23, 2007
 */
abstract class XmlaOlap4jConnection implements OlapConnection {
    /**
     * Handler for errors.
     */
    final XmlaHelper helper = new XmlaHelper();

    /**
     * <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 URL serverUrl;

    private Locale locale;

    /**
     * Name of the current catalog to which this connection is bound to,
     * as specified by the server.
     */
    private String nativeCatalogName;

    /**
     * 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 role that this connection impersonates.
     */
    private String roleName;

    /**
     * Provider name.
     */
    private String providerName;

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

    /**
     * Holds the database name as specified by the server. Necessary because
     * some servers (such as Mondrian) return both the provider
     * name and the database name in their response.
     *
     * <p>It's this value that we use inside queries, and not the JDBC
     * query value.
     */
    private String nativeDatabaseName;

    private boolean autoCommit;
    private boolean readOnly;

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

    /**
     * Name of the "DATA_SOURCE_NAME" column returned from
     * {@link org.olap4j.OlapDatabaseMetaData#getDatasources()}.
     */
    private static final String DATA_SOURCE_NAME = "DATA_SOURCE_NAME";

    /**
     * Name of the "PROVIDER_NAME" column returned from
     * {@link org.olap4j.OlapDatabaseMetaData#getDatasources()}.
     */
    private static final String PROVIDER_NAME = "PROVIDER_NAME";

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

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

        this.providerName =
            map.get(XmlaOlap4jDriver.Property.Provider.name());

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

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

        // Set URL of HTTP server.
        String serverUrl = map.get(XmlaOlap4jDriver.Property.Server.name());
        if (serverUrl == null) {
            throw getHelper().createException(
                "Connection property '"
                + XmlaOlap4jDriver.Property.Server.name()
                + "' must be specified");
        }

        // Basic authentication. Make sure the credentials passed as standard
        // JDBC parameters override any credentials already included in the URL
        // as part of the standard URL scheme.
        if (map.containsKey("user") && map.containsKey("password")) {
            serverUrl = serverUrl.replaceFirst(
                ":\\/\\/([^@]*@){0,1}",
                "://"
                + map.get("user")
                + ":"
                + map.get("password")
                + "@");
        }

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

        try {
            this.serverUrl = new URL(serverUrl);
        } catch (MalformedURLException e) {
            throw getHelper().createException(
                "Error while creating connection", e);
        }

        this.olap4jDatabaseMetaData =
            factory.newDatabaseMetaData(this);
    }

    /**
     * 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().toUpperCase()
                    + "."))
                {
                    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 void setDatabase(String databaseName) throws OlapException {
        this.databaseName = databaseName;
        this.nativeCatalogName = null;
    }
    public String getDatabase() throws OlapException {
     // If we already know it, return it.
        if (this.nativeDatabaseName != null) {
            return this.nativeDatabaseName;
        }

        ResultSet rSet = null;
        try {
            // We need to query for it
            rSet = this.olap4jDatabaseMetaData.getDatabases();

            // Check if the user requested a particular one.
            if (this.databaseName != null || this.providerName != null) {
                // We iterate through the databases
                while (rSet.next()) {
                    // Get current values
                    String currentDatasource = rSet.getString(DATA_SOURCE_NAME);
                    String currentProvider = rSet.getString(PROVIDER_NAME);

                    // If database and provider match, we got it.
                    // If database matches but no provider is specified, we
                    // got it.
                    // If provider matches but no database specified, we
                    // consider it good.
                    if (currentDatasource.equals(this.databaseName)
                        && currentProvider.equals(this.providerName)
                        || currentDatasource.equals(this.databaseName)
                        && this.providerName == null
                        || currentProvider.equals(this.providerName)
                        && this.databaseName == null)
                    {
                        // Got it
                        this.nativeDatabaseName = currentDatasource;
                        break;
                    }
                }
            } else {
                // Use first
                if (rSet.first()) {
                    this.nativeDatabaseName =
                        rSet.getString(DATA_SOURCE_NAME);
                }
            }

            // Throws exception to the client.  Tells that there are
            // no database corresponding to the search criteria.
            if (this.nativeDatabaseName == null) {
                throw getHelper().createException(
                    "No database could be found on the server.");
            }

            return this.nativeDatabaseName;
        } catch (OlapException e) {
            throw e;
        } catch (SQLException e) {
            throw getHelper().createException(
                "An exception occured while trying to resolve the database name.",
                e);
        } finally {
            try {
                if (rSet != null) {
                    rSet.close();
                }
            } catch (SQLException e) {
                // ignore
            }
        }
    }

    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 NamedList<Catalog> getCatalogs() {
        return Olap4jUtil.cast(olap4jDatabaseMetaData.getCatalogObjects());
    }

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

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

    public void setCatalog(String catalog) throws OlapException {
        this.catalogName = catalog;
        this.nativeCatalogName = null;
    }

    public String getCatalog() throws OlapException {
        if (this.nativeCatalogName == null) {
            if (catalogName != null) {
                this.nativeCatalogName = catalogName;
            } else if (olap4jDatabaseMetaData.getOlapCatalogs().size() == 0) {
                throw new OlapException(
                    "There is no catalog available to query against.");
            } else {
                this.nativeCatalogName =
                    olap4jDatabaseMetaData.getOlapCatalogs().get(0).getName();
            }
        }
        return nativeCatalogName;
    }

    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);
            }
        };
    }

    @Deprecated
    /*
     * This function must either be removed and the tests adapted or
     * be made package private.
     */
    public synchronized org.olap4j.metadata.Schema getSchema()
        throws OlapException
    {
        // initializes the olap4jSchema if necessary
        if (this.olap4jSchema == null) {
            final XmlaOlap4jCatalog catalog =
                this.olap4jDatabaseMetaData.getCatalogObjects().get(
                    getCatalog());
            this.olap4jSchema = catalog.schemas.get(0);
        }
        return olap4jSchema;
    }

    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(serverUrl, request);
        } catch (IOException e) {
            /*
             * FIXME This type of exception should not reach this point.
             * It was maintained because some other proxy implementations
             * exists out there that still throw an IOException arround.
             * This was a bad design which we will fix at some point but not
             * before the 1.0 release.
             */
            throw getHelper().createException(e);
        } 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.getMetaData().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
{'fraction_non_alphanumeric': 0.09045021159435068, 'fraction_numerical': 0.0038069987593262974, 'mean_word_length': 1.9311547275082195, 'pattern_counts': {'":': 6, '<': 166, '<?xml version=': 2, '>': 169, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 42}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6811080', 'n_tokens_mistral': 15336, 'n_tokens_neox': 14758, 'n_words': 4289}