Package mondrian.rolap

Source Code of mondrian.rolap.RolapSchema$Pool

/*
// $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapSchema.java#4 $
// 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) 2001-2002 Kana Software, Inc.
// Copyright (C) 2001-2010 Julian Hyde and others
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
//
// jhyde, 26 July, 2001
*/

package mondrian.rolap;

import java.io.*;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

import javax.sql.DataSource;

import mondrian.olap.*;
import mondrian.olap.fun.*;
import mondrian.olap.type.MemberType;
import mondrian.olap.type.NumericType;
import mondrian.olap.type.StringType;
import mondrian.olap.type.Type;
import mondrian.resource.MondrianResource;
import mondrian.rolap.aggmatcher.AggTableManager;
import mondrian.rolap.aggmatcher.JdbcSchema;
import mondrian.spi.*;

import org.apache.log4j.Logger;
import org.apache.commons.vfs.*;

import org.eigenbase.xom.*;
import org.eigenbase.xom.Parser;
import org.olap4j.impl.Olap4jUtil;

/**
* A <code>RolapSchema</code> is a collection of {@link RolapCube}s and
* shared {@link RolapDimension}s. It is shared betweeen {@link
* RolapConnection}s. It caches {@link MemberReader}s, etc.
*
* @see RolapConnection
* @author jhyde
* @since 26 July, 2001
* @version $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapSchema.java#4 $
*/
public class RolapSchema implements Schema {
    private static final Logger LOGGER = Logger.getLogger(RolapSchema.class);

    private static final Set<Access> schemaAllowed =
        Olap4jUtil.enumSetOf(Access.NONE, Access.ALL, Access.ALL_DIMENSIONS);

    private static final Set<Access> cubeAllowed =
        Olap4jUtil.enumSetOf(Access.NONE, Access.ALL);

    private static final Set<Access> dimensionAllowed =
        Olap4jUtil.enumSetOf(Access.NONE, Access.ALL);

    private static final Set<Access> hierarchyAllowed =
        Olap4jUtil.enumSetOf(Access.NONE, Access.ALL, Access.CUSTOM);

    private static final Set<Access> memberAllowed =
        Olap4jUtil.enumSetOf(Access.NONE, Access.ALL);

    private String name;

    /**
     * Internal use only.
     */
    private final RolapConnection internalConnection;
    /**
     * Holds cubes in this schema.
     */
    private final Map<String, RolapCube> mapNameToCube;
    /**
     * Maps {@link String shared hierarchy name} to {@link MemberReader}.
     * Shared between all statements which use this connection.
     */
    private final Map<String, MemberReader> mapSharedHierarchyToReader;

    /**
     * Maps {@link String names of shared hierarchies} to {@link
     * RolapHierarchy the canonical instance of those hierarchies}.
     */
    private final Map<String, RolapHierarchy> mapSharedHierarchyNameToHierarchy;
    /**
     * The default role for connections to this schema.
     */
    private RoleImpl defaultRole;

    private final String md5Bytes;

    /**
     * A schema's aggregation information
     */
    private AggTableManager aggTableManager;

    /**
     * This is basically a unique identifier for this RolapSchema instance
     * used it its equals and hashCode methods.
     */
    private String key;

    /**
     * Maps {@link String names of roles} to {@link Role roles with those names}.
     */
    private final Map<String, Role> mapNameToRole;

    /**
     * Maps {@link String names of sets} to {@link NamedSet named sets}.
     */
    private final Map<String, NamedSet> mapNameToSet =
        new HashMap<String, NamedSet>();

    /**
     * Table containing all standard MDX functions, plus user-defined functions
     * for this schema.
     */
    private FunTable funTable;

    private MondrianDef.Schema xmlSchema;

    final List<RolapSchemaParameter > parameterList =
        new ArrayList<RolapSchemaParameter >();

    private Date schemaLoadDate;

    private DataSourceChangeListener dataSourceChangeListener;

    /**
     * Map containing column cardinality. The combination of
     * Mondrianef.Relation and MondrianDef.Expression uniquely
     * identifies a relational expression(e.g. a column) specified
     * in the xml schema.
     */
    private final Map<
        MondrianDef.Relation,
        Map<MondrianDef.Expression, Integer>>
        relationExprCardinalityMap;

    /**
     * List of warnings. Populated when a schema is created by a connection
     * that has
     * {@link mondrian.rolap.RolapConnectionProperties#Ignore Ignore}=true.
     */
    private final List<Exception> warningList = new ArrayList<Exception>();
    private Map<String, Annotation> annotationMap;

    /**
     * This is a unique schema instance id which will be used
     * to inform clients when the schema has changed.
     *
     * <p>Expect a different ID for each Mondrian instance node.
     */
    private final String id;

    /**
     * This is ONLY called by other constructors (and MUST be called
     * by them) and NEVER by the Pool.
     *
     * @param key Key
     * @param connectInfo Connect properties
     * @param dataSource Data source
     * @param md5Bytes MD5 hash
     */
    private RolapSchema(
        final String key,
        final Util.PropertyList connectInfo,
        final DataSource dataSource,
        final String md5Bytes)
    {
        this.id = UUID.randomUUID().toString();
        this.key = key;
        this.md5Bytes = md5Bytes;
        // the order of the next two lines is important
        this.defaultRole = createDefaultRole();
        this.internalConnection =
            new RolapConnection(connectInfo, this, dataSource);

        this.mapSharedHierarchyNameToHierarchy =
            new HashMap<String, RolapHierarchy>();
        this.mapSharedHierarchyToReader = new HashMap<String, MemberReader>();
        this.mapNameToCube = new HashMap<String, RolapCube>();
        this.mapNameToRole = new HashMap<String, Role>();
        this.aggTableManager = new AggTableManager(this);
        this.dataSourceChangeListener =
            createDataSourceChangeListener(connectInfo);
        this.relationExprCardinalityMap =
            new HashMap<
                MondrianDef.Relation,
                Map<MondrianDef.Expression, Integer>>();
    }

    /**
     * Create RolapSchema given the MD5 hash, catalog name and string (content)
     * and the connectInfo object.
     *
     * @param md5Bytes may be null
     * @param catalogUrl URL of catalog
     * @param catalogStr may be null
     * @param connectInfo Connection properties
     */
    private RolapSchema(
            final String key,
            final String md5Bytes,
            final String catalogUrl,
            final String catalogStr,
            final Util.PropertyList connectInfo,
            final DataSource dataSource)
    {
        this(key, connectInfo, dataSource, md5Bytes);
        load(catalogUrl, catalogStr);
    }

    private RolapSchema(
            final String key,
            final String catalogUrl,
            final Util.PropertyList connectInfo,
            final DataSource dataSource)
    {
        this(key, connectInfo, dataSource, null);
        load(catalogUrl, null);
    }

    protected void finalCleanUp() {
        if (aggTableManager != null) {
            aggTableManager.finalCleanUp();
            aggTableManager = null;
        }
    }

    protected void finalize() throws Throwable {
        super.finalize();
        finalCleanUp();
    }

    public boolean equals(Object o) {
        if (!(o instanceof RolapSchema)) {
            return false;
        }
        RolapSchema other = (RolapSchema) o;
        return other.key.equals(key);
    }

    public int hashCode() {
        return key.hashCode();
    }

    protected Logger getLogger() {
        return LOGGER;
    }

    /**
     * Method called by all constructors to load the catalog into DOM and build
     * application mdx and sql objects.
     *
     * @param catalogUrl URL of catalog
     * @param catalogStr Text of catalog, or null
     */
    protected void load(String catalogUrl, String catalogStr) {
        try {
            final Parser xmlParser = XOMUtil.createDefaultParser();

            final DOMWrapper def;
            if (catalogStr == null) {
                InputStream in = null;
                try {
                    in = Util.readVirtualFile(catalogUrl);
                    def = xmlParser.parse(in);
                } finally {
                    if (in != null) {
                        in.close();
                    }
                }

                if (getLogger().isDebugEnabled()) {
                    try {
                        StringBuilder buf = new StringBuilder(1000);
                        InputStream debugIn = Util.readVirtualFile(catalogUrl);
                        int n;
                        while ((n = debugIn.read()) != -1) {
                            buf.append((char) n);
                        }
                        getLogger().debug(
                            "RolapSchema.load: content: \n" + buf.toString());
                    } catch (java.io.IOException ex) {
                        getLogger().debug("RolapSchema.load: ex=" + ex);
                    }
                }

            } else {
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug(
                        "RolapSchema.load: catalogStr: \n" + catalogStr);
                }

                def = xmlParser.parse(catalogStr);
            }

            xmlSchema = new MondrianDef.Schema(def);

            if (getLogger().isDebugEnabled()) {
                StringWriter sw = new StringWriter(4096);
                PrintWriter pw = new PrintWriter(sw);
                pw.println("RolapSchema.load: dump xmlschema");
                xmlSchema.display(pw, 2);
                pw.flush();
                getLogger().debug(sw.toString());
            }

            load(xmlSchema);
        } catch (XOMException e) {
            throw Util.newError(e, "while parsing catalog " + catalogUrl);
        } catch (FileSystemException e) {
            throw Util.newError(e, "while parsing catalog " + catalogUrl);
        } catch (IOException e) {
            throw Util.newError(e, "while parsing catalog " + catalogUrl);
        }

        aggTableManager.initialize();
        setSchemaLoadDate();
    }

    private void setSchemaLoadDate() {
        schemaLoadDate = new Date();
    }

    public Date getSchemaLoadDate() {
        return schemaLoadDate;
    }

    public List<Exception> getWarnings() {
        return Collections.unmodifiableList(warningList);
    }

    RoleImpl getDefaultRole() {
        return defaultRole;
    }

    public MondrianDef.Schema getXMLSchema() {
        return xmlSchema;
    }

    public String getName() {
        Util.assertPostcondition(name != null, "return != null");
        Util.assertPostcondition(name.length() > 0, "return.length() > 0");
        return name;
    }

    /**
     * Returns this schema instance unique ID.
     * @return A string representing the schema ID.
     */
    public String getId() {
        return this.id;
    }

    public Map<String, Annotation> getAnnotationMap() {
        return annotationMap;
    }

    /**
     * Returns this schema's SQL dialect.
     *
     * <p>NOTE: This method is not cheap. The implementation gets a connection
     * from the connection pool.
     *
     * @return dialect
     */
    public Dialect getDialect() {
        DataSource dataSource = getInternalConnection().getDataSource();
        return DialectManager.createDialect(dataSource, null);
    }

    private void load(MondrianDef.Schema xmlSchema) {
        this.name = xmlSchema.name;
        if (name == null || name.equals("")) {
            throw Util.newError("<Schema> name must be set");
        }

        this.annotationMap =
            RolapHierarchy.createAnnotationMap(xmlSchema.annotations);
        // Validate user-defined functions. Must be done before we validate
        // calculated members, because calculated members will need to use the
        // function table.
        final Map<String, UserDefinedFunction> mapNameToUdf =
            new HashMap<String, UserDefinedFunction>();
        for (MondrianDef.UserDefinedFunction udf
            : xmlSchema.userDefinedFunctions)
        {
            defineFunction(mapNameToUdf, udf.name, udf.className);
        }
        final RolapSchemaFunctionTable funTable =
            new RolapSchemaFunctionTable(mapNameToUdf.values());
        funTable.init();
        this.funTable = funTable;

        // Validate public dimensions.
        for (MondrianDef.Dimension xmlDimension : xmlSchema.dimensions) {
            if (xmlDimension.foreignKey != null) {
                throw MondrianResource.instance()
                    .PublicDimensionMustNotHaveForeignKey.ex(
                    xmlDimension.name);
            }
        }

        // Create parameters.
        Set<String> parameterNames = new HashSet<String>();
        for (MondrianDef.Parameter xmlParameter : xmlSchema.parameters) {
            String name = xmlParameter.name;
            if (!parameterNames.add(name)) {
                throw MondrianResource.instance().DuplicateSchemaParameter.ex(
                    name);
            }
            Type type;
            if (xmlParameter.type.equals("String")) {
                type = new StringType();
            } else if (xmlParameter.type.equals("Numeric")) {
                type = new NumericType();
            } else {
                type = new MemberType(null, null, null, null);
            }
            final String description = xmlParameter.description;
            final boolean modifiable = xmlParameter.modifiable;
            String defaultValue = xmlParameter.defaultValue;
            RolapSchemaParameter param =
                new RolapSchemaParameter(
                    this, name, defaultValue, description, type, modifiable);
            Util.discard(param);
        }

        // Create cubes.
        for (MondrianDef.Cube xmlCube : xmlSchema.cubes) {
            if (xmlCube.isEnabled()) {
                RolapCube cube = new RolapCube(this, xmlSchema, xmlCube, true);
                Util.discard(cube);
            }
        }

        // Create virtual cubes.
        for (MondrianDef.VirtualCube xmlVirtualCube : xmlSchema.virtualCubes) {
            if (xmlVirtualCube.isEnabled()) {
                RolapCube cube =
                    new RolapCube(this, xmlSchema, xmlVirtualCube, true);
                Util.discard(cube);
            }
        }

        // Create named sets.
        for (MondrianDef.NamedSet xmlNamedSet : xmlSchema.namedSets) {
            mapNameToSet.put(xmlNamedSet.name, createNamedSet(xmlNamedSet));
        }

        // Create roles.
        for (MondrianDef.Role xmlRole : xmlSchema.roles) {
            Role role = createRole(xmlRole);
            mapNameToRole.put(xmlRole.name, role);
        }

        // Set default role.
        if (xmlSchema.defaultRole != null) {
            Role role = lookupRole(xmlSchema.defaultRole);
            if (role == null) {
                error(
                    "Role '" + xmlSchema.defaultRole + "' not found",
                    locate(xmlSchema, "defaultRole"));
            } else {
                // At this stage, the only roles in mapNameToRole are
                // RoleImpl roles so it is safe to case.
                defaultRole = (RoleImpl) role;
            }
        }
    }

    /**
     * Returns the location of an element or attribute in an XML document.
     *
     * <p>TODO: modify eigenbase-xom parser to return position info
     *
     * @param node Node
     * @param attributeName Attribute name, or null
     * @return Location of node or attribute in an XML document
     */
    XmlLocation locate(ElementDef node, String attributeName) {
        return null;
    }

    /**
     * Reports an error. If we are tolerant of errors
     * (see {@link mondrian.rolap.RolapConnectionProperties#Ignore}), adds
     * it to the stack, overwise throws. A thrown exception will typically
     * abort the attempt to create the exception.
     *
     * @param message Message
     * @param xmlLocation Location of XML element or attribute that caused
     * the error, or null
     */
    void error(
        String message,
        XmlLocation xmlLocation)
    {
        final RuntimeException ex = new RuntimeException(message);
        if (internalConnection != null
            && "true".equals(
            internalConnection.getProperty(
                RolapConnectionProperties.Ignore.name())))
        {
            warningList.add(ex);
        } else {
            throw ex;
        }
    }

    private NamedSet createNamedSet(MondrianDef.NamedSet xmlNamedSet) {
        final String formulaString = xmlNamedSet.getFormula();
        final Exp exp;
        try {
            exp = getInternalConnection().parseExpression(formulaString);
        } catch (Exception e) {
            throw MondrianResource.instance().NamedSetHasBadFormula.ex(
                    xmlNamedSet.name, e);
        }
        final Formula formula =
            new Formula(
                new Id(
                    new Id.Segment(
                        xmlNamedSet.name,
                        Id.Quoting.UNQUOTED)),
                exp);
        return formula.getNamedSet();
    }

    private Role createRole(MondrianDef.Role xmlRole) {
        if (xmlRole.union != null) {
            if (xmlRole.schemaGrants != null
                && xmlRole.schemaGrants.length > 0)
            {
                throw MondrianResource.instance().RoleUnionGrants.ex();
            }
            List<Role> roleList = new ArrayList<Role>();
            for (MondrianDef.RoleUsage roleUsage : xmlRole.union.roleUsages) {
                final Role role = mapNameToRole.get(roleUsage.roleName);
                if (role == null) {
                    throw MondrianResource.instance().UnknownRole.ex(
                        roleUsage.roleName);
                }
                roleList.add(role);
            }
            return RoleImpl.union(roleList);
        }
        RoleImpl role = new RoleImpl();
        for (MondrianDef.SchemaGrant schemaGrant : xmlRole.schemaGrants) {
            role.grant(this, getAccess(schemaGrant.access, schemaAllowed));
            for (MondrianDef.CubeGrant cubeGrant : schemaGrant.cubeGrants) {
                RolapCube cube = lookupCube(cubeGrant.cube);
                if (cube == null) {
                    throw Util.newError(
                        "Unknown cube '" + cubeGrant.cube + "'");
                }
                role.grant(cube, getAccess(cubeGrant.access, cubeAllowed));
                final SchemaReader schemaReader = cube.getSchemaReader(null);
                for (MondrianDef.DimensionGrant dimensionGrant
                    : cubeGrant.dimensionGrants)
                {
                    Dimension dimension = (Dimension)
                        schemaReader.lookupCompound(
                            cube,
                            Util.parseIdentifier(dimensionGrant.dimension),
                            true,
                            Category.Dimension);
                    role.grant(
                        dimension,
                        getAccess(dimensionGrant.access, dimensionAllowed));
                }
                for (MondrianDef.HierarchyGrant hierarchyGrant
                    : cubeGrant.hierarchyGrants)
                {
                    Hierarchy hierarchy = (Hierarchy)
                        schemaReader.lookupCompound(
                            cube,
                            Util.parseIdentifier(hierarchyGrant.hierarchy),
                            true,
                            Category.Hierarchy);
                    final Access hierarchyAccess =
                        getAccess(hierarchyGrant.access, hierarchyAllowed);
                    Level topLevel = null;
                    if (hierarchyGrant.topLevel != null) {
                        if (hierarchyAccess != Access.CUSTOM) {
                            throw Util.newError(
                                "You may only specify 'topLevel' if "
                                + "access='custom'");
                        }
                        topLevel = (Level) schemaReader.lookupCompound(
                            cube,
                            Util.parseIdentifier(hierarchyGrant.topLevel),
                            true,
                            Category.Level);
                    }
                    Level bottomLevel = null;
                    if (hierarchyGrant.bottomLevel != null) {
                        if (hierarchyAccess != Access.CUSTOM) {
                            throw Util.newError(
                                "You may only specify 'bottomLevel' if "
                                + "access='custom'");
                        }
                        bottomLevel = (Level) schemaReader.lookupCompound(
                            cube,
                            Util.parseIdentifier(hierarchyGrant.bottomLevel),
                            true,
                            Category.Level);
                    }
                    Role.RollupPolicy rollupPolicy;
                    if (hierarchyGrant.rollupPolicy != null) {
                        try {
                            rollupPolicy =
                                Role.RollupPolicy.valueOf(
                                    hierarchyGrant.rollupPolicy.toUpperCase());
                        } catch (IllegalArgumentException e) {
                            throw Util.newError(
                                "Illegal rollupPolicy value '"
                                + hierarchyGrant.rollupPolicy
                                + "'");
                        }
                    } else {
                        rollupPolicy = Role.RollupPolicy.FULL;
                    }
                    role.grant(
                        hierarchy, hierarchyAccess, topLevel, bottomLevel,
                        rollupPolicy);
                    for (MondrianDef.MemberGrant memberGrant
                        : hierarchyGrant.memberGrants)
                    {
                        if (hierarchyAccess != Access.CUSTOM) {
                            throw Util.newError(
                                "You may only specify <MemberGrant> if "
                                + "<Hierarchy> has access='custom'");
                        }
                        final boolean ignoreInvalidMembers =
                            MondrianProperties.instance().IgnoreInvalidMembers
                                .get();
                        Member member = schemaReader.getMemberByUniqueName(
                            Util.parseIdentifier(memberGrant.member),
                            !ignoreInvalidMembers);
                        if (member == null) {
                            // They asked to ignore members that don't exist
                            // (e.g. [Store].[USA].[Foo]), so ignore this grant
                            // too.
                            assert ignoreInvalidMembers;
                            continue;
                        }
                        if (member.getHierarchy() != hierarchy) {
                            throw Util.newError(
                                "Member '" + member
                                + "' is not in hierarchy '" + hierarchy + "'");
                        }
                        role.grant(
                            member,
                            getAccess(memberGrant.access, memberAllowed));
                    }
                }
            }
        }
        role.makeImmutable();
        return role;
    }

    private Access getAccess(String accessString, Set<Access> allowed) {
        final Access access = Access.valueOf(accessString.toUpperCase());
        if (allowed.contains(access)) {
            return access; // value is ok
        }
        throw Util.newError("Bad value access='" + accessString + "'");
    }

    public Dimension createDimension(Cube cube, String xml) {
        MondrianDef.CubeDimension xmlDimension;
        try {
            final Parser xmlParser = XOMUtil.createDefaultParser();
            final DOMWrapper def = xmlParser.parse(xml);
            final String tagName = def.getTagName();
            if (tagName.equals("Dimension")) {
                xmlDimension = new MondrianDef.Dimension(def);
            } else if (tagName.equals("DimensionUsage")) {
                xmlDimension = new MondrianDef.DimensionUsage(def);
            } else {
                throw new XOMException(
                    "Got <" + tagName
                    + "> when expecting <Dimension> or <DimensionUsage>");
            }
        } catch (XOMException e) {
            throw Util.newError(
                e,
                "Error while adding dimension to cube '" + cube
                + "' from XML [" + xml + "]");
        }
        return ((RolapCube) cube).createDimension(xmlDimension, xmlSchema);
    }

    public Cube createCube(String xml) {
        RolapCube cube;
        try {
            final Parser xmlParser = XOMUtil.createDefaultParser();
            final DOMWrapper def = xmlParser.parse(xml);
            final String tagName = def.getTagName();
            if (tagName.equals("Cube")) {
                // Create empty XML schema, to keep the method happy. This is
                // okay, because there are no forward-references to resolve.
                final MondrianDef.Schema xmlSchema = new MondrianDef.Schema();
                MondrianDef.Cube xmlDimension = new MondrianDef.Cube(def);
                cube = new RolapCube(this, xmlSchema, xmlDimension, false);
            } else if (tagName.equals("VirtualCube")) {
                // Need the real schema here.
                MondrianDef.Schema xmlSchema = getXMLSchema();
                MondrianDef.VirtualCube xmlDimension =
                        new MondrianDef.VirtualCube(def);
                cube = new RolapCube(this, xmlSchema, xmlDimension, false);
            } else {
                throw new XOMException(
                    "Got <" + tagName + "> when expecting <Cube>");
            }
        } catch (XOMException e) {
            throw Util.newError(
                e,
                "Error while creating cube from XML [" + xml + "]");
        }
        return cube;
    }

    /**
     * A collection of schemas, identified by their connection properties
     * (catalog name, JDBC URL, and so forth).
     *
     * <p>To lookup a schema, call <code>Pool.instance().{@link #get}</code>.
     */
    static class Pool {
        private final MessageDigest md;

        private static Pool pool = new Pool();

        private Map<String, SoftReference<RolapSchema>> mapUrlToSchema =
            new HashMap<String, SoftReference<RolapSchema>>();


        private Pool() {
            // Initialize the MD5 digester.
            try {
                md = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
        }

        static Pool instance() {
            return pool;
        }

        /**
         * Creates an MD5 hash of String.
         *
         * @param value String to create one way hash upon.
         * @return MD5 hash.
         */
        private synchronized String encodeMD5(final String value) {
            md.reset();
            final byte[] bytes = md.digest(value.getBytes());
            return (bytes != null) ? new String(bytes) : null;
        }

        synchronized RolapSchema get(
            final String catalogUrl,
            final String connectionKey,
            final String jdbcUser,
            final String dataSourceStr,
            final Util.PropertyList connectInfo)
        {
            return get(
                catalogUrl,
                connectionKey,
                jdbcUser,
                dataSourceStr,
                null,
                connectInfo);
        }

        synchronized RolapSchema get(
            final String catalogUrl,
            final DataSource dataSource,
            final Util.PropertyList connectInfo)
        {
            return get(
                catalogUrl,
                null,
                null,
                null,
                dataSource,
                connectInfo);
        }

        private RolapSchema get(
            final String catalogUrl,
            final String connectionKey,
            final String jdbcUser,
            final String dataSourceStr,
            final DataSource dataSource,
            final Util.PropertyList connectInfo)
        {
            String key =
                (dataSource == null)
                ? makeKey(catalogUrl, connectionKey, jdbcUser, dataSourceStr)
                : makeKey(catalogUrl, dataSource);

            RolapSchema schema = null;

            String dynProcName = connectInfo.get(
                RolapConnectionProperties.DynamicSchemaProcessor.name());

            String catalogStr = connectInfo.get(
                RolapConnectionProperties.CatalogContent.name());
            if (catalogUrl == null && catalogStr == null) {
                throw MondrianResource.instance()
                    .ConnectStringMandatoryProperties.ex(
                    RolapConnectionProperties.Catalog.name(),
                    RolapConnectionProperties.CatalogContent.name());
            }

            // If CatalogContent is specified in the connect string, ignore
            // everything else. In particular, ignore the dynamic schema
            // processor.
            if (catalogStr != null) {
                dynProcName = null;
                // REVIEW: Are we including enough in the key to make it
                // unique?
                key = catalogStr;
            }

            final boolean useContentChecksum =
                Boolean.parseBoolean(
                    connectInfo.get(
                        RolapConnectionProperties.UseContentChecksum.name()));

            // Use the schema pool unless "UseSchemaPool" is explicitly false.
            final boolean useSchemaPool =
                Boolean.parseBoolean(
                    connectInfo.get(
                        RolapConnectionProperties.UseSchemaPool.name(),
                        "true"));

            // If there is a dynamic processor registered, use it. This
            // implies there is not MD5 based caching, but, as with the previous
            // implementation, if the catalog string is in the connectInfo
            // object as catalog content then it is used.
            if (! Util.isEmpty(dynProcName)) {
                assert catalogStr == null;

                try {
                    @SuppressWarnings("unchecked")
                    final Class<DynamicSchemaProcessor> clazz =
                        (Class<DynamicSchemaProcessor>)
                            Class.forName(dynProcName);
                    final Constructor<DynamicSchemaProcessor> ctor =
                        clazz.getConstructor();
                    final DynamicSchemaProcessor dynProc = ctor.newInstance();
                    catalogStr = dynProc.processSchema(catalogUrl, connectInfo);
                } catch (Exception e) {
                    throw Util.newError(
                        e,
                        "loading DynamicSchemaProcessor " + dynProcName);
                }

                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(
                        "Pool.get: create schema \"" + catalogUrl
                        + "\" using dynamic processor");
                }
            }

            if (!useSchemaPool) {
                schema = new RolapSchema(
                    key,
                    null,
                    catalogUrl,
                    catalogStr,
                    connectInfo,
                    dataSource);

            } else if (useContentChecksum) {
                // Different catalogUrls can actually yield the same
                // catalogStr! So, we use the MD5 as the key as well as
                // the key made above - its has two entries in the
                // mapUrlToSchema Map. We must then also during the
                // remove operation make sure we remove both.

                String md5Bytes = null;
                try {
                    if (catalogStr == null) {
                        // Use VFS to get the content
                        InputStream in = null;
                        try {
                            in = Util.readVirtualFile(catalogUrl);
                            StringBuilder buf = new StringBuilder(1000);
                            int n;
                            while ((n = in.read()) != -1) {
                                buf.append((char) n);
                            }
                            catalogStr = buf.toString();
                        } finally {
                            if (in != null) {
                                in.close();
                            }
                        }
                    }

                    md5Bytes = encodeMD5(catalogStr);
                } catch (Exception ex) {
                    // Note, can not throw an Exception from this method
                    // but just to show that all is not well in Mudville
                    // we print stack trace (for now - better to change
                    // method signature and throw).
                    ex.printStackTrace();
                }

                if (md5Bytes != null) {
                    SoftReference<RolapSchema> ref =
                        mapUrlToSchema.get(md5Bytes);
                    if (ref != null) {
                        schema = ref.get();
                        if (schema == null) {
                            // clear out the reference since schema is null
                            mapUrlToSchema.remove(key);
                            mapUrlToSchema.remove(md5Bytes);
                        }
                    }
                }

                if (schema == null
                    || md5Bytes == null
                    || schema.md5Bytes == null
                    || ! schema.md5Bytes.equals(md5Bytes))
                {
                    schema = new RolapSchema(
                        key,
                        md5Bytes,
                        catalogUrl,
                        catalogStr,
                        connectInfo,
                        dataSource);

                    SoftReference<RolapSchema> ref =
                        new SoftReference<RolapSchema>(schema);
                    if (md5Bytes != null) {
                        mapUrlToSchema.put(md5Bytes, ref);
                    }
                    mapUrlToSchema.put(key, ref);

                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(
                            "Pool.get: create schema \"" + catalogUrl
                            + "\" with MD5");
                    }

                } else if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(
                        "Pool.get: schema \"" + catalogUrl
                        + "\" exists already with MD5");
                }

            } else {
                SoftReference<RolapSchema> ref = mapUrlToSchema.get(key);
                if (ref != null) {
                    schema = ref.get();
                    if (schema == null) {
                        // clear out the reference since schema is null
                        mapUrlToSchema.remove(key);
                    }
                }

                if (schema == null) {
                    if (catalogStr == null) {
                        schema = new RolapSchema(
                            key,
                            catalogUrl,
                            connectInfo,
                            dataSource);
                    } else {
                        schema = new RolapSchema(
                            key,
                            null,
                            catalogUrl,
                            catalogStr,
                            connectInfo,
                            dataSource);
                    }

                    mapUrlToSchema.put(
                        key,
                        new SoftReference<RolapSchema>(schema));

                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(
                            "Pool.get: create schema \"" + catalogUrl + "\"");
                    }

                } else if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(
                        "Pool.get: schema \"" + catalogUrl
                        + "\" exists already ");
                }
            }
            return schema;
        }

        synchronized void remove(
            final String catalogUrl,
            final String connectionKey,
            final String jdbcUser,
            final String dataSourceStr)
        {
            final String key = makeKey(
                catalogUrl,
                connectionKey,
                jdbcUser,
                dataSourceStr);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(
                    "Pool.remove: schema \"" + catalogUrl
                    + "\" and datasource string \"" + dataSourceStr + "\"");
            }
            remove(key);
        }

        synchronized void remove(
            final String catalogUrl,
            final DataSource dataSource)
        {
            final String key = makeKey(catalogUrl, dataSource);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(
                    "Pool.remove: schema \"" + catalogUrl
                    + "\" and datasource object");
            }
            remove(key);
        }

        synchronized void remove(RolapSchema schema) {
            if (schema != null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(
                        "Pool.remove: schema \"" + schema.name
                        + "\" and datasource object");
                }
                remove(schema.key);
            }
        }

        private void remove(String key) {
            SoftReference<RolapSchema> ref = mapUrlToSchema.get(key);
            if (ref != null) {
                RolapSchema schema = ref.get();
                if (schema != null) {
                    if (schema.md5Bytes != null) {
                        mapUrlToSchema.remove(schema.md5Bytes);
                    }
                    schema.finalCleanUp();
                }
            }
            mapUrlToSchema.remove(key);
        }

        synchronized void clear() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Pool.clear: clearing all RolapSchemas");
            }

            for (SoftReference<RolapSchema> ref : mapUrlToSchema.values()) {
                if (ref != null) {
                    RolapSchema schema = ref.get();
                    if (schema != null) {
                        schema.finalCleanUp();
                    }
                }
            }
            mapUrlToSchema.clear();
            JdbcSchema.clearAllDBs();
        }

        /**
         * This returns an iterator over a copy of the RolapSchema's container.
         *
         * @return Iterator over RolapSchemas
         */
        synchronized Iterator<RolapSchema> getRolapSchemas() {
            List<RolapSchema> list = new ArrayList<RolapSchema>();
            for (Iterator<SoftReference<RolapSchema>> it =
                mapUrlToSchema.values().iterator(); it.hasNext();)
            {
                SoftReference<RolapSchema> ref = it.next();
                RolapSchema schema = ref.get();
                // Schema is null if already garbage collected
                if (schema != null) {
                    list.add(schema);
                } else {
                    // We will remove the stale reference
                    try {
                        it.remove();
                    } catch (Exception ex) {
                        // Should not happen, so
                        // warn but otherwise ignore
                        LOGGER.warn(ex);
                    }
                }
            }
            return list.iterator();
        }

        synchronized boolean contains(RolapSchema rolapSchema) {
            return mapUrlToSchema.containsKey(rolapSchema.key);
        }


        /**
         * Creates a key with which to identify a schema in the cache.
         */
        private static String makeKey(
            final String catalogUrl,
            final String connectionKey,
            final String jdbcUser,
            final String dataSourceStr)
        {
            final StringBuilder buf = new StringBuilder(100);

            appendIfNotNull(buf, catalogUrl);
            appendIfNotNull(buf, connectionKey);
            appendIfNotNull(buf, jdbcUser);
            appendIfNotNull(buf, dataSourceStr);

            return buf.toString();
        }

        /**
         * Creates a key with which to identify a schema in the cache.
         */
        private static String makeKey(
            final String catalogUrl,
            final DataSource dataSource)
        {
            final StringBuilder buf = new StringBuilder(100);

            appendIfNotNull(buf, catalogUrl);
            buf.append('.');
            buf.append("external#");
            buf.append(System.identityHashCode(dataSource));

            return buf.toString();
        }

        private static void appendIfNotNull(StringBuilder buf, String s) {
            if (s != null) {
                if (buf.length() > 0) {
                    buf.append('.');
                }
                buf.append(s);
            }
        }
    }

    public static Iterator<RolapSchema> getRolapSchemas() {
        return Pool.instance().getRolapSchemas();
    }

    public static boolean cacheContains(RolapSchema rolapSchema) {
        return Pool.instance().contains(rolapSchema);
    }

    public Cube lookupCube(final String cube, final boolean failIfNotFound) {
        RolapCube mdxCube = lookupCube(cube);
        if (mdxCube == null && failIfNotFound) {
            throw MondrianResource.instance().MdxCubeNotFound.ex(cube);
        }
        return mdxCube;
    }

    /**
     * Finds a cube called 'cube' in the current catalog, or return null if no
     * cube exists.
     */
    protected RolapCube lookupCube(final String cubeName) {
        return mapNameToCube.get(Util.normalizeName(cubeName));
    }

    /**
     * Returns an xmlCalculatedMember called 'calcMemberName' in the
     * cube called 'cubeName' or return null if no calculatedMember or
     * xmlCube by those name exists.
     */
    protected MondrianDef.CalculatedMember lookupXmlCalculatedMember(
        final String calcMemberName,
        final String cubeName)
    {
        List<Id.Segment> nameParts = Util.parseIdentifier(calcMemberName);
        for (final MondrianDef.Cube cube : xmlSchema.cubes) {
            if (Util.equalName(cube.name, cubeName)) {
                for (final MondrianDef.CalculatedMember calculatedMember
                        : cube.calculatedMembers)
                {
                    if (Util.equalName(
                            calculatedMember.dimension, nameParts.get(0).name)
                        && Util.equalName(
                            calculatedMember.name,
                            nameParts.get(nameParts.size() - 1).name))
                    {
                        return calculatedMember;
                    }
                }
            }
        }
        return null;
    }

    public List<RolapCube> getCubesWithStar(RolapStar star) {
        List<RolapCube> list = new ArrayList<RolapCube>();
        for (RolapCube cube : mapNameToCube.values()) {
            if (star == cube.getStar()) {
                list.add(cube);
            }
        }
        return list;
    }

    /**
     * Adds a cube to the cube name map.
     * @see #lookupCube(String)
     */
    protected void addCube(final RolapCube cube) {
        mapNameToCube.put(
                Util.normalizeName(cube.getName()),
                cube);
    }

    public boolean removeCube(final String cubeName) {
        final RolapCube cube =
            mapNameToCube.remove(Util.normalizeName(cubeName));
        return cube != null;
    }

    public Cube[] getCubes() {
        Collection<RolapCube> cubes = mapNameToCube.values();
        return cubes.toArray(new RolapCube[cubes.size()]);
    }

    public List<RolapCube> getCubeList() {
        return new ArrayList<RolapCube>(mapNameToCube.values());
    }

    public Hierarchy[] getSharedHierarchies() {
        Collection<RolapHierarchy> hierarchies =
            mapSharedHierarchyNameToHierarchy.values();
        return hierarchies.toArray(new RolapHierarchy[hierarchies.size()]);
    }

    RolapHierarchy getSharedHierarchy(final String name) {
        return mapSharedHierarchyNameToHierarchy.get(name);
    }

    public NamedSet getNamedSet(String name) {
        return mapNameToSet.get(name);
    }

    public Role lookupRole(final String role) {
        return mapNameToRole.get(role);
    }

    public Set<String> roleNames() {
        return mapNameToRole.keySet();
    }

    public FunTable getFunTable() {
        return funTable;
    }

    public Parameter[] getParameters() {
        return parameterList.toArray(
                new Parameter[parameterList.size()]);
    }

    /**
     * Defines a user-defined function in this table.
     *
     * <p>If the function is not valid, throws an error.
     *
     * @param name Name of the function.
     * @param className Name of the class which implements the function.
     *   The class must implement {@link mondrian.spi.UserDefinedFunction}
     *   (otherwise it is a user-error).
     */
    private void defineFunction(
        Map<String, UserDefinedFunction> mapNameToUdf,
        String name,
        String className)
    {
        // Lookup class.
        final Class<UserDefinedFunction> klass;
        try {
            klass = (Class<UserDefinedFunction>) Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw MondrianResource.instance().UdfClassNotFound.ex(
                name,
                className);
        }
        // Instantiate UDF by calling correct constructor.
        final UserDefinedFunction udf = Util.createUdf(klass, name);
        // Validate function.
        validateFunction(udf);
        // Check for duplicate.
        UserDefinedFunction existingUdf = mapNameToUdf.get(name);
        if (existingUdf != null) {
            throw MondrianResource.instance().UdfDuplicateName.ex(name);
        }
        mapNameToUdf.put(name, udf);
    }

    /**
     * Throws an error if a user-defined function does not adhere to the
     * API.
     */
    private void validateFunction(final UserDefinedFunction udf) {
        // Check that the name is not null or empty.
        final String udfName = udf.getName();
        if (udfName == null || udfName.equals("")) {
            throw Util.newInternal(
                "User-defined function defined by class '"
                + udf.getClass() + "' has empty name");
        }
        // It's OK for the description to be null.
        final String description = udf.getDescription();
        Util.discard(description);
        final Type[] parameterTypes = udf.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            Type parameterType = parameterTypes[i];
            if (parameterType == null) {
                throw Util.newInternal(
                    "Invalid user-defined function '"
                    + udfName + "': parameter type #" + i + " is null");
            }
        }
        // It's OK for the reserved words to be null or empty.
        final String[] reservedWords = udf.getReservedWords();
        Util.discard(reservedWords);
        // Test that the function returns a sensible type when given the FORMAL
        // types. It may still fail when we give it the ACTUAL types, but it's
        // impossible to check that now.
        final Type returnType = udf.getReturnType(parameterTypes);
        if (returnType == null) {
            throw Util.newInternal(
                "Invalid user-defined function '"
                + udfName + "': return type is null");
        }
        final Syntax syntax = udf.getSyntax();
        if (syntax == null) {
            throw Util.newInternal(
                "Invalid user-defined function '"
                + udfName + "': syntax is null");
        }
    }

    /**
     * Gets a {@link MemberReader} with which to read a hierarchy. If the
     * hierarchy is shared (<code>sharedName</code> is not null), looks up
     * a reader from a cache, or creates one if necessary.
     *
     * <p>Synchronization: thread safe
     */
    synchronized MemberReader createMemberReader(
        final String sharedName,
        final RolapHierarchy hierarchy,
        final String memberReaderClass)
    {
        MemberReader reader;
        if (sharedName != null) {
            reader = mapSharedHierarchyToReader.get(sharedName);
            if (reader == null) {
                reader = createMemberReader(hierarchy, memberReaderClass);
                // share, for other uses of the same shared hierarchy
                if (false) {
                    mapSharedHierarchyToReader.put(sharedName, reader);
                }
/*
System.out.println("RolapSchema.createMemberReader: "+
"add to sharedHierName->Hier map"+
" sharedName=" + sharedName +
", hierarchy=" + hierarchy.getName() +
", hierarchy.dim=" + hierarchy.getDimension().getName()
);
if (mapSharedHierarchyNameToHierarchy.containsKey(sharedName)) {
System.out.println("RolapSchema.createMemberReader: CONTAINS NAME");
} else {
                mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy);
}
*/
                if (! mapSharedHierarchyNameToHierarchy.containsKey(
                    sharedName))
                {
                    mapSharedHierarchyNameToHierarchy.put(
                        sharedName, hierarchy);
                }
                //mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy);
            } else {
//                final RolapHierarchy sharedHierarchy = (RolapHierarchy)
//                        mapSharedHierarchyNameToHierarchy.get(sharedName);
//                final RolapDimension sharedDimension = (RolapDimension)
//                        sharedHierarchy.getDimension();
//                final RolapDimension dimension =
//                    (RolapDimension) hierarchy.getDimension();
//                Util.assertTrue(
//                        dimension.getGlobalOrdinal() ==
//                        sharedDimension.getGlobalOrdinal());
            }
        } else {
            reader = createMemberReader(hierarchy, memberReaderClass);
        }
        return reader;
    }

    /**
     * Creates a {@link MemberReader} with which to Read a hierarchy.
     */
    private MemberReader createMemberReader(
        final RolapHierarchy hierarchy,
        final String memberReaderClass)
    {
        if (memberReaderClass != null) {
            Exception e2;
            try {
                Properties properties = null;
                Class<?> clazz = Class.forName(memberReaderClass);
                Constructor<?> constructor = clazz.getConstructor(
                    RolapHierarchy.class,
                    Properties.class);
                Object o = constructor.newInstance(hierarchy, properties);
                if (o instanceof MemberReader) {
                    return (MemberReader) o;
                } else if (o instanceof MemberSource) {
                    return new CacheMemberReader((MemberSource) o);
                } else {
                    throw Util.newInternal(
                        "member reader class " + clazz
                        + " does not implement " + MemberSource.class);
                }
            } catch (ClassNotFoundException e) {
                e2 = e;
            } catch (NoSuchMethodException e) {
                e2 = e;
            } catch (InstantiationException e) {
                e2 = e;
            } catch (IllegalAccessException e) {
                e2 = e;
            } catch (InvocationTargetException e) {
                e2 = e;
            }
            throw Util.newInternal(
                e2,
                "while instantiating member reader '" + memberReaderClass);
        } else {
            SqlMemberSource source = new SqlMemberSource(hierarchy);
            if (hierarchy.getDimension().isHighCardinality()) {
                LOGGER.debug(
                    "High cardinality for " + hierarchy.getDimension());
                return new NoCacheMemberReader(source);
            } else {
                LOGGER.debug(
                    "Normal cardinality for " + hierarchy.getDimension());
                return new SmartMemberReader(source);
            }
        }
    }

    public SchemaReader getSchemaReader() {
        return new RolapSchemaReader(defaultRole, this);
    }

    /**
     * Creates a {@link DataSourceChangeListener} with which to detect changes to datasources.
     */
    private DataSourceChangeListener createDataSourceChangeListener(
        Util.PropertyList connectInfo)
    {
        DataSourceChangeListener changeListener = null;

        // If CatalogContent is specified in the connect string, ignore
        // everything else. In particular, ignore the dynamic schema
        // processor.
        String dataSourceChangeListenerStr = connectInfo.get(
            RolapConnectionProperties.DataSourceChangeListener.name());

        if (! Util.isEmpty(dataSourceChangeListenerStr)) {
            try {
                Class<?> clazz = Class.forName(dataSourceChangeListenerStr);
                Constructor<?> constructor = clazz.getConstructor();
                changeListener =
                    (DataSourceChangeListener) constructor.newInstance();

/*
                final Class<DataSourceChangeListener> clazz =
                    (Class<DataSourceChangeListener>)
                        Class.forName(dataSourceChangeListenerStr);
                final Constructor<DataSourceChangeListener> ctor =
                    clazz.getConstructor();
                changeListener = ctor.newInstance();
*/
                changeListener =
                    (DataSourceChangeListener) constructor.newInstance();
            } catch (Exception e) {
                throw Util.newError(
                    e,
                    "loading DataSourceChangeListener "
                    + dataSourceChangeListenerStr);
            }

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(
                    "RolapSchema.createDataSourceChangeListener: "
                    + "create datasource change listener \""
                    + dataSourceChangeListenerStr);
            }
        }
        return changeListener;
    }


    /**
     * Connection for purposes of parsing and validation. Careful! It won't
     * have the correct locale or access-control profile.
     */
    public RolapConnection getInternalConnection() {
        return internalConnection;
    }

    /**
     * Returns the cached cardinality for the column.
     * The cache is stored in the schema so that queries on different
     * cubes can share them.
     * @return the cardinality map
     */
    Integer getCachedRelationExprCardinality(
        MondrianDef.Relation relation,
        MondrianDef.Expression columnExpr)
    {
        Integer card = null;
        synchronized (relationExprCardinalityMap) {
            Map<MondrianDef.Expression, Integer> exprCardinalityMap =
                relationExprCardinalityMap.get(relation);
            if (exprCardinalityMap != null) {
                card = exprCardinalityMap.get(columnExpr);
            }
        }
        return card;
    }

    /**
     * Sets the cardinality for a given column in cache.
     *
     * @param relation the relation associated with the column expression
     * @param columnExpr the column expression to cache the cardinality for
     * @param cardinality the cardinality for the column expression
     */
    void putCachedRelationExprCardinality(
        MondrianDef.Relation relation,
        MondrianDef.Expression columnExpr,
        Integer cardinality)
    {
        synchronized (relationExprCardinalityMap) {
            Map<MondrianDef.Expression, Integer> exprCardinalityMap =
                relationExprCardinalityMap.get(relation);
            if (exprCardinalityMap == null) {
                exprCardinalityMap =
                    new HashMap<MondrianDef.Expression, Integer>();
                relationExprCardinalityMap.put(relation, exprCardinalityMap);
            }
            exprCardinalityMap.put(columnExpr, cardinality);
        }
    }

    private RoleImpl createDefaultRole() {
        RoleImpl role = new RoleImpl();
        role.grant(this, Access.ALL);
        role.makeImmutable();
        return role;
    }

    private RolapStar makeRolapStar(final MondrianDef.Relation fact) {
        DataSource dataSource = getInternalConnection().getDataSource();
        return new RolapStar(this, dataSource, fact);
    }

    /**
     * <code>RolapStarRegistry</code> is a registry for {@link RolapStar}s.
     */
    class RolapStarRegistry {
        private final Map<String, RolapStar> stars =
            new HashMap<String, RolapStar>();

        RolapStarRegistry() {
        }

        /**
         * Looks up a {@link RolapStar}, creating it if it does not exist.
         *
         * <p> {@link RolapStar.Table#addJoin} works in a similar way.
         */
        synchronized RolapStar getOrCreateStar(
            final MondrianDef.Relation fact)
        {
            String factTableName = fact.toString();
            RolapStar star = stars.get(factTableName);
            if (star == null) {
                star = makeRolapStar(fact);
                stars.put(factTableName, star);
            }
            return star;
        }

        synchronized RolapStar getStar(final String factTableName) {
            return stars.get(factTableName);
        }

        synchronized Collection<RolapStar> getStars() {
            return stars.values();
        }
    }

    private RolapStarRegistry rolapStarRegistry = new RolapStarRegistry();

    public RolapStarRegistry getRolapStarRegistry() {
        return rolapStarRegistry;
    }

    /**
     * Function table which contains all of the user-defined functions in this
     * schema, plus all of the standard functions.
     */
    static class RolapSchemaFunctionTable extends FunTableImpl {
        private final List<UserDefinedFunction> udfList;

        RolapSchemaFunctionTable(Collection<UserDefinedFunction> udfs) {
            udfList = new ArrayList<UserDefinedFunction>(udfs);
        }

        public void defineFunctions(Builder builder) {
            final FunTable globalFunTable = GlobalFunTable.instance();
            for (String reservedWord : globalFunTable.getReservedWords()) {
                builder.defineReserved(reservedWord);
            }
            for (Resolver resolver : globalFunTable.getResolvers()) {
                builder.define(resolver);
            }
            for (UserDefinedFunction udf : udfList) {
                builder.define(new UdfResolver(udf));
            }
        }
    }

    public RolapStar getStar(final String factTableName) {
        return getRolapStarRegistry().getStar(factTableName);
    }

    public Collection<RolapStar> getStars() {
        return getRolapStarRegistry().getStars();
    }

    /**
     * Checks whether there are modifications in the aggregations cache.
     */
    public void checkAggregateModifications() {
        for (RolapStar star : getStars()) {
            star.checkAggregateModifications();
        }
    }

    /**
     * Pushes all modifications of the aggregations to global cache,
     * so other queries can start using the new cache
     */
    public void pushAggregateModificationsToGlobalCache() {
        for (RolapStar star : getStars()) {
            star.pushAggregateModificationsToGlobalCache();
        }
    }

    final RolapNativeRegistry nativeRegistry = new RolapNativeRegistry();

    RolapNativeRegistry getNativeRegistry() {
        return nativeRegistry;
    }

    /**
     * @return Returns the dataSourceChangeListener.
     */
    public DataSourceChangeListener getDataSourceChangeListener() {
        return dataSourceChangeListener;
    }

    /**
     * @param dataSourceChangeListener The dataSourceChangeListener to set.
     */
    public void setDataSourceChangeListener(
        DataSourceChangeListener dataSourceChangeListener)
    {
        this.dataSourceChangeListener = dataSourceChangeListener;
    }

    /**
     * Location of a node in an XML document.
     */
    private interface XmlLocation {
    }
}

// End RolapSchema.java
TOP

Related Classes of mondrian.rolap.RolapSchema$Pool

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.