001    /*
002    // $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapSchema.java#4 $
003    // This software is subject to the terms of the Eclipse Public License v1.0
004    // Agreement, available at the following URL:
005    // http://www.eclipse.org/legal/epl-v10.html.
006    // Copyright (C) 2001-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2010 Julian Hyde and others
008    // All Rights Reserved.
009    // You must accept the terms of that agreement to use this software.
010    //
011    // jhyde, 26 July, 2001
012    */
013    
014    package mondrian.rolap;
015    
016    import java.io.*;
017    import java.lang.ref.SoftReference;
018    import java.lang.reflect.Constructor;
019    import java.lang.reflect.InvocationTargetException;
020    import java.security.MessageDigest;
021    import java.security.NoSuchAlgorithmException;
022    import java.util.*;
023    
024    import javax.sql.DataSource;
025    
026    import mondrian.olap.*;
027    import mondrian.olap.fun.*;
028    import mondrian.olap.type.MemberType;
029    import mondrian.olap.type.NumericType;
030    import mondrian.olap.type.StringType;
031    import mondrian.olap.type.Type;
032    import mondrian.resource.MondrianResource;
033    import mondrian.rolap.aggmatcher.AggTableManager;
034    import mondrian.rolap.aggmatcher.JdbcSchema;
035    import mondrian.spi.*;
036    
037    import org.apache.log4j.Logger;
038    import org.apache.commons.vfs.*;
039    
040    import org.eigenbase.xom.*;
041    import org.eigenbase.xom.Parser;
042    import org.olap4j.impl.Olap4jUtil;
043    
044    /**
045     * A <code>RolapSchema</code> is a collection of {@link RolapCube}s and
046     * shared {@link RolapDimension}s. It is shared betweeen {@link
047     * RolapConnection}s. It caches {@link MemberReader}s, etc.
048     *
049     * @see RolapConnection
050     * @author jhyde
051     * @since 26 July, 2001
052     * @version $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapSchema.java#4 $
053     */
054    public class RolapSchema implements Schema {
055        private static final Logger LOGGER = Logger.getLogger(RolapSchema.class);
056    
057        private static final Set<Access> schemaAllowed =
058            Olap4jUtil.enumSetOf(Access.NONE, Access.ALL, Access.ALL_DIMENSIONS);
059    
060        private static final Set<Access> cubeAllowed =
061            Olap4jUtil.enumSetOf(Access.NONE, Access.ALL);
062    
063        private static final Set<Access> dimensionAllowed =
064            Olap4jUtil.enumSetOf(Access.NONE, Access.ALL);
065    
066        private static final Set<Access> hierarchyAllowed =
067            Olap4jUtil.enumSetOf(Access.NONE, Access.ALL, Access.CUSTOM);
068    
069        private static final Set<Access> memberAllowed =
070            Olap4jUtil.enumSetOf(Access.NONE, Access.ALL);
071    
072        private String name;
073    
074        /**
075         * Internal use only.
076         */
077        private final RolapConnection internalConnection;
078        /**
079         * Holds cubes in this schema.
080         */
081        private final Map<String, RolapCube> mapNameToCube;
082        /**
083         * Maps {@link String shared hierarchy name} to {@link MemberReader}.
084         * Shared between all statements which use this connection.
085         */
086        private final Map<String, MemberReader> mapSharedHierarchyToReader;
087    
088        /**
089         * Maps {@link String names of shared hierarchies} to {@link
090         * RolapHierarchy the canonical instance of those hierarchies}.
091         */
092        private final Map<String, RolapHierarchy> mapSharedHierarchyNameToHierarchy;
093        /**
094         * The default role for connections to this schema.
095         */
096        private RoleImpl defaultRole;
097    
098        private final String md5Bytes;
099    
100        /**
101         * A schema's aggregation information
102         */
103        private AggTableManager aggTableManager;
104    
105        /**
106         * This is basically a unique identifier for this RolapSchema instance
107         * used it its equals and hashCode methods.
108         */
109        private String key;
110    
111        /**
112         * Maps {@link String names of roles} to {@link Role roles with those names}.
113         */
114        private final Map<String, Role> mapNameToRole;
115    
116        /**
117         * Maps {@link String names of sets} to {@link NamedSet named sets}.
118         */
119        private final Map<String, NamedSet> mapNameToSet =
120            new HashMap<String, NamedSet>();
121    
122        /**
123         * Table containing all standard MDX functions, plus user-defined functions
124         * for this schema.
125         */
126        private FunTable funTable;
127    
128        private MondrianDef.Schema xmlSchema;
129    
130        final List<RolapSchemaParameter > parameterList =
131            new ArrayList<RolapSchemaParameter >();
132    
133        private Date schemaLoadDate;
134    
135        private DataSourceChangeListener dataSourceChangeListener;
136    
137        /**
138         * Map containing column cardinality. The combination of
139         * Mondrianef.Relation and MondrianDef.Expression uniquely
140         * identifies a relational expression(e.g. a column) specified
141         * in the xml schema.
142         */
143        private final Map<
144            MondrianDef.Relation,
145            Map<MondrianDef.Expression, Integer>>
146            relationExprCardinalityMap;
147    
148        /**
149         * List of warnings. Populated when a schema is created by a connection
150         * that has
151         * {@link mondrian.rolap.RolapConnectionProperties#Ignore Ignore}=true.
152         */
153        private final List<Exception> warningList = new ArrayList<Exception>();
154        private Map<String, Annotation> annotationMap;
155    
156        /**
157         * This is a unique schema instance id which will be used
158         * to inform clients when the schema has changed.
159         *
160         * <p>Expect a different ID for each Mondrian instance node.
161         */
162        private final String id;
163    
164        /**
165         * This is ONLY called by other constructors (and MUST be called
166         * by them) and NEVER by the Pool.
167         *
168         * @param key Key
169         * @param connectInfo Connect properties
170         * @param dataSource Data source
171         * @param md5Bytes MD5 hash
172         */
173        private RolapSchema(
174            final String key,
175            final Util.PropertyList connectInfo,
176            final DataSource dataSource,
177            final String md5Bytes)
178        {
179            this.id = UUID.randomUUID().toString();
180            this.key = key;
181            this.md5Bytes = md5Bytes;
182            // the order of the next two lines is important
183            this.defaultRole = createDefaultRole();
184            this.internalConnection =
185                new RolapConnection(connectInfo, this, dataSource);
186    
187            this.mapSharedHierarchyNameToHierarchy =
188                new HashMap<String, RolapHierarchy>();
189            this.mapSharedHierarchyToReader = new HashMap<String, MemberReader>();
190            this.mapNameToCube = new HashMap<String, RolapCube>();
191            this.mapNameToRole = new HashMap<String, Role>();
192            this.aggTableManager = new AggTableManager(this);
193            this.dataSourceChangeListener =
194                createDataSourceChangeListener(connectInfo);
195            this.relationExprCardinalityMap =
196                new HashMap<
197                    MondrianDef.Relation,
198                    Map<MondrianDef.Expression, Integer>>();
199        }
200    
201        /**
202         * Create RolapSchema given the MD5 hash, catalog name and string (content)
203         * and the connectInfo object.
204         *
205         * @param md5Bytes may be null
206         * @param catalogUrl URL of catalog
207         * @param catalogStr may be null
208         * @param connectInfo Connection properties
209         */
210        private RolapSchema(
211                final String key,
212                final String md5Bytes,
213                final String catalogUrl,
214                final String catalogStr,
215                final Util.PropertyList connectInfo,
216                final DataSource dataSource)
217        {
218            this(key, connectInfo, dataSource, md5Bytes);
219            load(catalogUrl, catalogStr);
220        }
221    
222        private RolapSchema(
223                final String key,
224                final String catalogUrl,
225                final Util.PropertyList connectInfo,
226                final DataSource dataSource)
227        {
228            this(key, connectInfo, dataSource, null);
229            load(catalogUrl, null);
230        }
231    
232        protected void finalCleanUp() {
233            if (aggTableManager != null) {
234                aggTableManager.finalCleanUp();
235                aggTableManager = null;
236            }
237        }
238    
239        protected void finalize() throws Throwable {
240            super.finalize();
241            finalCleanUp();
242        }
243    
244        public boolean equals(Object o) {
245            if (!(o instanceof RolapSchema)) {
246                return false;
247            }
248            RolapSchema other = (RolapSchema) o;
249            return other.key.equals(key);
250        }
251    
252        public int hashCode() {
253            return key.hashCode();
254        }
255    
256        protected Logger getLogger() {
257            return LOGGER;
258        }
259    
260        /**
261         * Method called by all constructors to load the catalog into DOM and build
262         * application mdx and sql objects.
263         *
264         * @param catalogUrl URL of catalog
265         * @param catalogStr Text of catalog, or null
266         */
267        protected void load(String catalogUrl, String catalogStr) {
268            try {
269                final Parser xmlParser = XOMUtil.createDefaultParser();
270    
271                final DOMWrapper def;
272                if (catalogStr == null) {
273                    InputStream in = null;
274                    try {
275                        in = Util.readVirtualFile(catalogUrl);
276                        def = xmlParser.parse(in);
277                    } finally {
278                        if (in != null) {
279                            in.close();
280                        }
281                    }
282    
283                    if (getLogger().isDebugEnabled()) {
284                        try {
285                            StringBuilder buf = new StringBuilder(1000);
286                            InputStream debugIn = Util.readVirtualFile(catalogUrl);
287                            int n;
288                            while ((n = debugIn.read()) != -1) {
289                                buf.append((char) n);
290                            }
291                            getLogger().debug(
292                                "RolapSchema.load: content: \n" + buf.toString());
293                        } catch (java.io.IOException ex) {
294                            getLogger().debug("RolapSchema.load: ex=" + ex);
295                        }
296                    }
297    
298                } else {
299                    if (getLogger().isDebugEnabled()) {
300                        getLogger().debug(
301                            "RolapSchema.load: catalogStr: \n" + catalogStr);
302                    }
303    
304                    def = xmlParser.parse(catalogStr);
305                }
306    
307                xmlSchema = new MondrianDef.Schema(def);
308    
309                if (getLogger().isDebugEnabled()) {
310                    StringWriter sw = new StringWriter(4096);
311                    PrintWriter pw = new PrintWriter(sw);
312                    pw.println("RolapSchema.load: dump xmlschema");
313                    xmlSchema.display(pw, 2);
314                    pw.flush();
315                    getLogger().debug(sw.toString());
316                }
317    
318                load(xmlSchema);
319            } catch (XOMException e) {
320                throw Util.newError(e, "while parsing catalog " + catalogUrl);
321            } catch (FileSystemException e) {
322                throw Util.newError(e, "while parsing catalog " + catalogUrl);
323            } catch (IOException e) {
324                throw Util.newError(e, "while parsing catalog " + catalogUrl);
325            }
326    
327            aggTableManager.initialize();
328            setSchemaLoadDate();
329        }
330    
331        private void setSchemaLoadDate() {
332            schemaLoadDate = new Date();
333        }
334    
335        public Date getSchemaLoadDate() {
336            return schemaLoadDate;
337        }
338    
339        public List<Exception> getWarnings() {
340            return Collections.unmodifiableList(warningList);
341        }
342    
343        RoleImpl getDefaultRole() {
344            return defaultRole;
345        }
346    
347        public MondrianDef.Schema getXMLSchema() {
348            return xmlSchema;
349        }
350    
351        public String getName() {
352            Util.assertPostcondition(name != null, "return != null");
353            Util.assertPostcondition(name.length() > 0, "return.length() > 0");
354            return name;
355        }
356    
357        /**
358         * Returns this schema instance unique ID.
359         * @return A string representing the schema ID.
360         */
361        public String getId() {
362            return this.id;
363        }
364    
365        public Map<String, Annotation> getAnnotationMap() {
366            return annotationMap;
367        }
368    
369        /**
370         * Returns this schema's SQL dialect.
371         *
372         * <p>NOTE: This method is not cheap. The implementation gets a connection
373         * from the connection pool.
374         *
375         * @return dialect
376         */
377        public Dialect getDialect() {
378            DataSource dataSource = getInternalConnection().getDataSource();
379            return DialectManager.createDialect(dataSource, null);
380        }
381    
382        private void load(MondrianDef.Schema xmlSchema) {
383            this.name = xmlSchema.name;
384            if (name == null || name.equals("")) {
385                throw Util.newError("<Schema> name must be set");
386            }
387    
388            this.annotationMap =
389                RolapHierarchy.createAnnotationMap(xmlSchema.annotations);
390            // Validate user-defined functions. Must be done before we validate
391            // calculated members, because calculated members will need to use the
392            // function table.
393            final Map<String, UserDefinedFunction> mapNameToUdf =
394                new HashMap<String, UserDefinedFunction>();
395            for (MondrianDef.UserDefinedFunction udf
396                : xmlSchema.userDefinedFunctions)
397            {
398                defineFunction(mapNameToUdf, udf.name, udf.className);
399            }
400            final RolapSchemaFunctionTable funTable =
401                new RolapSchemaFunctionTable(mapNameToUdf.values());
402            funTable.init();
403            this.funTable = funTable;
404    
405            // Validate public dimensions.
406            for (MondrianDef.Dimension xmlDimension : xmlSchema.dimensions) {
407                if (xmlDimension.foreignKey != null) {
408                    throw MondrianResource.instance()
409                        .PublicDimensionMustNotHaveForeignKey.ex(
410                        xmlDimension.name);
411                }
412            }
413    
414            // Create parameters.
415            Set<String> parameterNames = new HashSet<String>();
416            for (MondrianDef.Parameter xmlParameter : xmlSchema.parameters) {
417                String name = xmlParameter.name;
418                if (!parameterNames.add(name)) {
419                    throw MondrianResource.instance().DuplicateSchemaParameter.ex(
420                        name);
421                }
422                Type type;
423                if (xmlParameter.type.equals("String")) {
424                    type = new StringType();
425                } else if (xmlParameter.type.equals("Numeric")) {
426                    type = new NumericType();
427                } else {
428                    type = new MemberType(null, null, null, null);
429                }
430                final String description = xmlParameter.description;
431                final boolean modifiable = xmlParameter.modifiable;
432                String defaultValue = xmlParameter.defaultValue;
433                RolapSchemaParameter param =
434                    new RolapSchemaParameter(
435                        this, name, defaultValue, description, type, modifiable);
436                Util.discard(param);
437            }
438    
439            // Create cubes.
440            for (MondrianDef.Cube xmlCube : xmlSchema.cubes) {
441                if (xmlCube.isEnabled()) {
442                    RolapCube cube = new RolapCube(this, xmlSchema, xmlCube, true);
443                    Util.discard(cube);
444                }
445            }
446    
447            // Create virtual cubes.
448            for (MondrianDef.VirtualCube xmlVirtualCube : xmlSchema.virtualCubes) {
449                if (xmlVirtualCube.isEnabled()) {
450                    RolapCube cube =
451                        new RolapCube(this, xmlSchema, xmlVirtualCube, true);
452                    Util.discard(cube);
453                }
454            }
455    
456            // Create named sets.
457            for (MondrianDef.NamedSet xmlNamedSet : xmlSchema.namedSets) {
458                mapNameToSet.put(xmlNamedSet.name, createNamedSet(xmlNamedSet));
459            }
460    
461            // Create roles.
462            for (MondrianDef.Role xmlRole : xmlSchema.roles) {
463                Role role = createRole(xmlRole);
464                mapNameToRole.put(xmlRole.name, role);
465            }
466    
467            // Set default role.
468            if (xmlSchema.defaultRole != null) {
469                Role role = lookupRole(xmlSchema.defaultRole);
470                if (role == null) {
471                    error(
472                        "Role '" + xmlSchema.defaultRole + "' not found",
473                        locate(xmlSchema, "defaultRole"));
474                } else {
475                    // At this stage, the only roles in mapNameToRole are
476                    // RoleImpl roles so it is safe to case.
477                    defaultRole = (RoleImpl) role;
478                }
479            }
480        }
481    
482        /**
483         * Returns the location of an element or attribute in an XML document.
484         *
485         * <p>TODO: modify eigenbase-xom parser to return position info
486         *
487         * @param node Node
488         * @param attributeName Attribute name, or null
489         * @return Location of node or attribute in an XML document
490         */
491        XmlLocation locate(ElementDef node, String attributeName) {
492            return null;
493        }
494    
495        /**
496         * Reports an error. If we are tolerant of errors
497         * (see {@link mondrian.rolap.RolapConnectionProperties#Ignore}), adds
498         * it to the stack, overwise throws. A thrown exception will typically
499         * abort the attempt to create the exception.
500         *
501         * @param message Message
502         * @param xmlLocation Location of XML element or attribute that caused
503         * the error, or null
504         */
505        void error(
506            String message,
507            XmlLocation xmlLocation)
508        {
509            final RuntimeException ex = new RuntimeException(message);
510            if (internalConnection != null
511                && "true".equals(
512                internalConnection.getProperty(
513                    RolapConnectionProperties.Ignore.name())))
514            {
515                warningList.add(ex);
516            } else {
517                throw ex;
518            }
519        }
520    
521        private NamedSet createNamedSet(MondrianDef.NamedSet xmlNamedSet) {
522            final String formulaString = xmlNamedSet.getFormula();
523            final Exp exp;
524            try {
525                exp = getInternalConnection().parseExpression(formulaString);
526            } catch (Exception e) {
527                throw MondrianResource.instance().NamedSetHasBadFormula.ex(
528                        xmlNamedSet.name, e);
529            }
530            final Formula formula =
531                new Formula(
532                    new Id(
533                        new Id.Segment(
534                            xmlNamedSet.name,
535                            Id.Quoting.UNQUOTED)),
536                    exp);
537            return formula.getNamedSet();
538        }
539    
540        private Role createRole(MondrianDef.Role xmlRole) {
541            if (xmlRole.union != null) {
542                if (xmlRole.schemaGrants != null
543                    && xmlRole.schemaGrants.length > 0)
544                {
545                    throw MondrianResource.instance().RoleUnionGrants.ex();
546                }
547                List<Role> roleList = new ArrayList<Role>();
548                for (MondrianDef.RoleUsage roleUsage : xmlRole.union.roleUsages) {
549                    final Role role = mapNameToRole.get(roleUsage.roleName);
550                    if (role == null) {
551                        throw MondrianResource.instance().UnknownRole.ex(
552                            roleUsage.roleName);
553                    }
554                    roleList.add(role);
555                }
556                return RoleImpl.union(roleList);
557            }
558            RoleImpl role = new RoleImpl();
559            for (MondrianDef.SchemaGrant schemaGrant : xmlRole.schemaGrants) {
560                role.grant(this, getAccess(schemaGrant.access, schemaAllowed));
561                for (MondrianDef.CubeGrant cubeGrant : schemaGrant.cubeGrants) {
562                    RolapCube cube = lookupCube(cubeGrant.cube);
563                    if (cube == null) {
564                        throw Util.newError(
565                            "Unknown cube '" + cubeGrant.cube + "'");
566                    }
567                    role.grant(cube, getAccess(cubeGrant.access, cubeAllowed));
568                    final SchemaReader schemaReader = cube.getSchemaReader(null);
569                    for (MondrianDef.DimensionGrant dimensionGrant
570                        : cubeGrant.dimensionGrants)
571                    {
572                        Dimension dimension = (Dimension)
573                            schemaReader.lookupCompound(
574                                cube,
575                                Util.parseIdentifier(dimensionGrant.dimension),
576                                true,
577                                Category.Dimension);
578                        role.grant(
579                            dimension,
580                            getAccess(dimensionGrant.access, dimensionAllowed));
581                    }
582                    for (MondrianDef.HierarchyGrant hierarchyGrant
583                        : cubeGrant.hierarchyGrants)
584                    {
585                        Hierarchy hierarchy = (Hierarchy)
586                            schemaReader.lookupCompound(
587                                cube,
588                                Util.parseIdentifier(hierarchyGrant.hierarchy),
589                                true,
590                                Category.Hierarchy);
591                        final Access hierarchyAccess =
592                            getAccess(hierarchyGrant.access, hierarchyAllowed);
593                        Level topLevel = null;
594                        if (hierarchyGrant.topLevel != null) {
595                            if (hierarchyAccess != Access.CUSTOM) {
596                                throw Util.newError(
597                                    "You may only specify 'topLevel' if "
598                                    + "access='custom'");
599                            }
600                            topLevel = (Level) schemaReader.lookupCompound(
601                                cube,
602                                Util.parseIdentifier(hierarchyGrant.topLevel),
603                                true,
604                                Category.Level);
605                        }
606                        Level bottomLevel = null;
607                        if (hierarchyGrant.bottomLevel != null) {
608                            if (hierarchyAccess != Access.CUSTOM) {
609                                throw Util.newError(
610                                    "You may only specify 'bottomLevel' if "
611                                    + "access='custom'");
612                            }
613                            bottomLevel = (Level) schemaReader.lookupCompound(
614                                cube,
615                                Util.parseIdentifier(hierarchyGrant.bottomLevel),
616                                true,
617                                Category.Level);
618                        }
619                        Role.RollupPolicy rollupPolicy;
620                        if (hierarchyGrant.rollupPolicy != null) {
621                            try {
622                                rollupPolicy =
623                                    Role.RollupPolicy.valueOf(
624                                        hierarchyGrant.rollupPolicy.toUpperCase());
625                            } catch (IllegalArgumentException e) {
626                                throw Util.newError(
627                                    "Illegal rollupPolicy value '"
628                                    + hierarchyGrant.rollupPolicy
629                                    + "'");
630                            }
631                        } else {
632                            rollupPolicy = Role.RollupPolicy.FULL;
633                        }
634                        role.grant(
635                            hierarchy, hierarchyAccess, topLevel, bottomLevel,
636                            rollupPolicy);
637                        for (MondrianDef.MemberGrant memberGrant
638                            : hierarchyGrant.memberGrants)
639                        {
640                            if (hierarchyAccess != Access.CUSTOM) {
641                                throw Util.newError(
642                                    "You may only specify <MemberGrant> if "
643                                    + "<Hierarchy> has access='custom'");
644                            }
645                            final boolean ignoreInvalidMembers =
646                                MondrianProperties.instance().IgnoreInvalidMembers
647                                    .get();
648                            Member member = schemaReader.getMemberByUniqueName(
649                                Util.parseIdentifier(memberGrant.member),
650                                !ignoreInvalidMembers);
651                            if (member == null) {
652                                // They asked to ignore members that don't exist
653                                // (e.g. [Store].[USA].[Foo]), so ignore this grant
654                                // too.
655                                assert ignoreInvalidMembers;
656                                continue;
657                            }
658                            if (member.getHierarchy() != hierarchy) {
659                                throw Util.newError(
660                                    "Member '" + member
661                                    + "' is not in hierarchy '" + hierarchy + "'");
662                            }
663                            role.grant(
664                                member,
665                                getAccess(memberGrant.access, memberAllowed));
666                        }
667                    }
668                }
669            }
670            role.makeImmutable();
671            return role;
672        }
673    
674        private Access getAccess(String accessString, Set<Access> allowed) {
675            final Access access = Access.valueOf(accessString.toUpperCase());
676            if (allowed.contains(access)) {
677                return access; // value is ok
678            }
679            throw Util.newError("Bad value access='" + accessString + "'");
680        }
681    
682        public Dimension createDimension(Cube cube, String xml) {
683            MondrianDef.CubeDimension xmlDimension;
684            try {
685                final Parser xmlParser = XOMUtil.createDefaultParser();
686                final DOMWrapper def = xmlParser.parse(xml);
687                final String tagName = def.getTagName();
688                if (tagName.equals("Dimension")) {
689                    xmlDimension = new MondrianDef.Dimension(def);
690                } else if (tagName.equals("DimensionUsage")) {
691                    xmlDimension = new MondrianDef.DimensionUsage(def);
692                } else {
693                    throw new XOMException(
694                        "Got <" + tagName
695                        + "> when expecting <Dimension> or <DimensionUsage>");
696                }
697            } catch (XOMException e) {
698                throw Util.newError(
699                    e,
700                    "Error while adding dimension to cube '" + cube
701                    + "' from XML [" + xml + "]");
702            }
703            return ((RolapCube) cube).createDimension(xmlDimension, xmlSchema);
704        }
705    
706        public Cube createCube(String xml) {
707            RolapCube cube;
708            try {
709                final Parser xmlParser = XOMUtil.createDefaultParser();
710                final DOMWrapper def = xmlParser.parse(xml);
711                final String tagName = def.getTagName();
712                if (tagName.equals("Cube")) {
713                    // Create empty XML schema, to keep the method happy. This is
714                    // okay, because there are no forward-references to resolve.
715                    final MondrianDef.Schema xmlSchema = new MondrianDef.Schema();
716                    MondrianDef.Cube xmlDimension = new MondrianDef.Cube(def);
717                    cube = new RolapCube(this, xmlSchema, xmlDimension, false);
718                } else if (tagName.equals("VirtualCube")) {
719                    // Need the real schema here.
720                    MondrianDef.Schema xmlSchema = getXMLSchema();
721                    MondrianDef.VirtualCube xmlDimension =
722                            new MondrianDef.VirtualCube(def);
723                    cube = new RolapCube(this, xmlSchema, xmlDimension, false);
724                } else {
725                    throw new XOMException(
726                        "Got <" + tagName + "> when expecting <Cube>");
727                }
728            } catch (XOMException e) {
729                throw Util.newError(
730                    e,
731                    "Error while creating cube from XML [" + xml + "]");
732            }
733            return cube;
734        }
735    
736        /**
737         * A collection of schemas, identified by their connection properties
738         * (catalog name, JDBC URL, and so forth).
739         *
740         * <p>To lookup a schema, call <code>Pool.instance().{@link #get}</code>.
741         */
742        static class Pool {
743            private final MessageDigest md;
744    
745            private static Pool pool = new Pool();
746    
747            private Map<String, SoftReference<RolapSchema>> mapUrlToSchema =
748                new HashMap<String, SoftReference<RolapSchema>>();
749    
750    
751            private Pool() {
752                // Initialize the MD5 digester.
753                try {
754                    md = MessageDigest.getInstance("MD5");
755                } catch (NoSuchAlgorithmException e) {
756                    throw new RuntimeException(e);
757                }
758            }
759    
760            static Pool instance() {
761                return pool;
762            }
763    
764            /**
765             * Creates an MD5 hash of String.
766             *
767             * @param value String to create one way hash upon.
768             * @return MD5 hash.
769             */
770            private synchronized String encodeMD5(final String value) {
771                md.reset();
772                final byte[] bytes = md.digest(value.getBytes());
773                return (bytes != null) ? new String(bytes) : null;
774            }
775    
776            synchronized RolapSchema get(
777                final String catalogUrl,
778                final String connectionKey,
779                final String jdbcUser,
780                final String dataSourceStr,
781                final Util.PropertyList connectInfo)
782            {
783                return get(
784                    catalogUrl,
785                    connectionKey,
786                    jdbcUser,
787                    dataSourceStr,
788                    null,
789                    connectInfo);
790            }
791    
792            synchronized RolapSchema get(
793                final String catalogUrl,
794                final DataSource dataSource,
795                final Util.PropertyList connectInfo)
796            {
797                return get(
798                    catalogUrl,
799                    null,
800                    null,
801                    null,
802                    dataSource,
803                    connectInfo);
804            }
805    
806            private RolapSchema get(
807                final String catalogUrl,
808                final String connectionKey,
809                final String jdbcUser,
810                final String dataSourceStr,
811                final DataSource dataSource,
812                final Util.PropertyList connectInfo)
813            {
814                String key =
815                    (dataSource == null)
816                    ? makeKey(catalogUrl, connectionKey, jdbcUser, dataSourceStr)
817                    : makeKey(catalogUrl, dataSource);
818    
819                RolapSchema schema = null;
820    
821                String dynProcName = connectInfo.get(
822                    RolapConnectionProperties.DynamicSchemaProcessor.name());
823    
824                String catalogStr = connectInfo.get(
825                    RolapConnectionProperties.CatalogContent.name());
826                if (catalogUrl == null && catalogStr == null) {
827                    throw MondrianResource.instance()
828                        .ConnectStringMandatoryProperties.ex(
829                        RolapConnectionProperties.Catalog.name(),
830                        RolapConnectionProperties.CatalogContent.name());
831                }
832    
833                // If CatalogContent is specified in the connect string, ignore
834                // everything else. In particular, ignore the dynamic schema
835                // processor.
836                if (catalogStr != null) {
837                    dynProcName = null;
838                    // REVIEW: Are we including enough in the key to make it
839                    // unique?
840                    key = catalogStr;
841                }
842    
843                final boolean useContentChecksum =
844                    Boolean.parseBoolean(
845                        connectInfo.get(
846                            RolapConnectionProperties.UseContentChecksum.name()));
847    
848                // Use the schema pool unless "UseSchemaPool" is explicitly false.
849                final boolean useSchemaPool =
850                    Boolean.parseBoolean(
851                        connectInfo.get(
852                            RolapConnectionProperties.UseSchemaPool.name(),
853                            "true"));
854    
855                // If there is a dynamic processor registered, use it. This
856                // implies there is not MD5 based caching, but, as with the previous
857                // implementation, if the catalog string is in the connectInfo
858                // object as catalog content then it is used.
859                if (! Util.isEmpty(dynProcName)) {
860                    assert catalogStr == null;
861    
862                    try {
863                        @SuppressWarnings("unchecked")
864                        final Class<DynamicSchemaProcessor> clazz =
865                            (Class<DynamicSchemaProcessor>)
866                                Class.forName(dynProcName);
867                        final Constructor<DynamicSchemaProcessor> ctor =
868                            clazz.getConstructor();
869                        final DynamicSchemaProcessor dynProc = ctor.newInstance();
870                        catalogStr = dynProc.processSchema(catalogUrl, connectInfo);
871                    } catch (Exception e) {
872                        throw Util.newError(
873                            e,
874                            "loading DynamicSchemaProcessor " + dynProcName);
875                    }
876    
877                    if (LOGGER.isDebugEnabled()) {
878                        LOGGER.debug(
879                            "Pool.get: create schema \"" + catalogUrl
880                            + "\" using dynamic processor");
881                    }
882                }
883    
884                if (!useSchemaPool) {
885                    schema = new RolapSchema(
886                        key,
887                        null,
888                        catalogUrl,
889                        catalogStr,
890                        connectInfo,
891                        dataSource);
892    
893                } else if (useContentChecksum) {
894                    // Different catalogUrls can actually yield the same
895                    // catalogStr! So, we use the MD5 as the key as well as
896                    // the key made above - its has two entries in the
897                    // mapUrlToSchema Map. We must then also during the
898                    // remove operation make sure we remove both.
899    
900                    String md5Bytes = null;
901                    try {
902                        if (catalogStr == null) {
903                            // Use VFS to get the content
904                            InputStream in = null;
905                            try {
906                                in = Util.readVirtualFile(catalogUrl);
907                                StringBuilder buf = new StringBuilder(1000);
908                                int n;
909                                while ((n = in.read()) != -1) {
910                                    buf.append((char) n);
911                                }
912                                catalogStr = buf.toString();
913                            } finally {
914                                if (in != null) {
915                                    in.close();
916                                }
917                            }
918                        }
919    
920                        md5Bytes = encodeMD5(catalogStr);
921                    } catch (Exception ex) {
922                        // Note, can not throw an Exception from this method
923                        // but just to show that all is not well in Mudville
924                        // we print stack trace (for now - better to change
925                        // method signature and throw).
926                        ex.printStackTrace();
927                    }
928    
929                    if (md5Bytes != null) {
930                        SoftReference<RolapSchema> ref =
931                            mapUrlToSchema.get(md5Bytes);
932                        if (ref != null) {
933                            schema = ref.get();
934                            if (schema == null) {
935                                // clear out the reference since schema is null
936                                mapUrlToSchema.remove(key);
937                                mapUrlToSchema.remove(md5Bytes);
938                            }
939                        }
940                    }
941    
942                    if (schema == null
943                        || md5Bytes == null
944                        || schema.md5Bytes == null
945                        || ! schema.md5Bytes.equals(md5Bytes))
946                    {
947                        schema = new RolapSchema(
948                            key,
949                            md5Bytes,
950                            catalogUrl,
951                            catalogStr,
952                            connectInfo,
953                            dataSource);
954    
955                        SoftReference<RolapSchema> ref =
956                            new SoftReference<RolapSchema>(schema);
957                        if (md5Bytes != null) {
958                            mapUrlToSchema.put(md5Bytes, ref);
959                        }
960                        mapUrlToSchema.put(key, ref);
961    
962                        if (LOGGER.isDebugEnabled()) {
963                            LOGGER.debug(
964                                "Pool.get: create schema \"" + catalogUrl
965                                + "\" with MD5");
966                        }
967    
968                    } else if (LOGGER.isDebugEnabled()) {
969                        LOGGER.debug(
970                            "Pool.get: schema \"" + catalogUrl
971                            + "\" exists already with MD5");
972                    }
973    
974                } else {
975                    SoftReference<RolapSchema> ref = mapUrlToSchema.get(key);
976                    if (ref != null) {
977                        schema = ref.get();
978                        if (schema == null) {
979                            // clear out the reference since schema is null
980                            mapUrlToSchema.remove(key);
981                        }
982                    }
983    
984                    if (schema == null) {
985                        if (catalogStr == null) {
986                            schema = new RolapSchema(
987                                key,
988                                catalogUrl,
989                                connectInfo,
990                                dataSource);
991                        } else {
992                            schema = new RolapSchema(
993                                key,
994                                null,
995                                catalogUrl,
996                                catalogStr,
997                                connectInfo,
998                                dataSource);
999                        }
1000    
1001                        mapUrlToSchema.put(
1002                            key,
1003                            new SoftReference<RolapSchema>(schema));
1004    
1005                        if (LOGGER.isDebugEnabled()) {
1006                            LOGGER.debug(
1007                                "Pool.get: create schema \"" + catalogUrl + "\"");
1008                        }
1009    
1010                    } else if (LOGGER.isDebugEnabled()) {
1011                        LOGGER.debug(
1012                            "Pool.get: schema \"" + catalogUrl
1013                            + "\" exists already ");
1014                    }
1015                }
1016                return schema;
1017            }
1018    
1019            synchronized void remove(
1020                final String catalogUrl,
1021                final String connectionKey,
1022                final String jdbcUser,
1023                final String dataSourceStr)
1024            {
1025                final String key = makeKey(
1026                    catalogUrl,
1027                    connectionKey,
1028                    jdbcUser,
1029                    dataSourceStr);
1030                if (LOGGER.isDebugEnabled()) {
1031                    LOGGER.debug(
1032                        "Pool.remove: schema \"" + catalogUrl
1033                        + "\" and datasource string \"" + dataSourceStr + "\"");
1034                }
1035                remove(key);
1036            }
1037    
1038            synchronized void remove(
1039                final String catalogUrl,
1040                final DataSource dataSource)
1041            {
1042                final String key = makeKey(catalogUrl, dataSource);
1043                if (LOGGER.isDebugEnabled()) {
1044                    LOGGER.debug(
1045                        "Pool.remove: schema \"" + catalogUrl
1046                        + "\" and datasource object");
1047                }
1048                remove(key);
1049            }
1050    
1051            synchronized void remove(RolapSchema schema) {
1052                if (schema != null) {
1053                    if (LOGGER.isDebugEnabled()) {
1054                        LOGGER.debug(
1055                            "Pool.remove: schema \"" + schema.name
1056                            + "\" and datasource object");
1057                    }
1058                    remove(schema.key);
1059                }
1060            }
1061    
1062            private void remove(String key) {
1063                SoftReference<RolapSchema> ref = mapUrlToSchema.get(key);
1064                if (ref != null) {
1065                    RolapSchema schema = ref.get();
1066                    if (schema != null) {
1067                        if (schema.md5Bytes != null) {
1068                            mapUrlToSchema.remove(schema.md5Bytes);
1069                        }
1070                        schema.finalCleanUp();
1071                    }
1072                }
1073                mapUrlToSchema.remove(key);
1074            }
1075    
1076            synchronized void clear() {
1077                if (LOGGER.isDebugEnabled()) {
1078                    LOGGER.debug("Pool.clear: clearing all RolapSchemas");
1079                }
1080    
1081                for (SoftReference<RolapSchema> ref : mapUrlToSchema.values()) {
1082                    if (ref != null) {
1083                        RolapSchema schema = ref.get();
1084                        if (schema != null) {
1085                            schema.finalCleanUp();
1086                        }
1087                    }
1088                }
1089                mapUrlToSchema.clear();
1090                JdbcSchema.clearAllDBs();
1091            }
1092    
1093            /**
1094             * This returns an iterator over a copy of the RolapSchema's container.
1095             *
1096             * @return Iterator over RolapSchemas
1097             */
1098            synchronized Iterator<RolapSchema> getRolapSchemas() {
1099                List<RolapSchema> list = new ArrayList<RolapSchema>();
1100                for (Iterator<SoftReference<RolapSchema>> it =
1101                    mapUrlToSchema.values().iterator(); it.hasNext();)
1102                {
1103                    SoftReference<RolapSchema> ref = it.next();
1104                    RolapSchema schema = ref.get();
1105                    // Schema is null if already garbage collected
1106                    if (schema != null) {
1107                        list.add(schema);
1108                    } else {
1109                        // We will remove the stale reference
1110                        try {
1111                            it.remove();
1112                        } catch (Exception ex) {
1113                            // Should not happen, so
1114                            // warn but otherwise ignore
1115                            LOGGER.warn(ex);
1116                        }
1117                    }
1118                }
1119                return list.iterator();
1120            }
1121    
1122            synchronized boolean contains(RolapSchema rolapSchema) {
1123                return mapUrlToSchema.containsKey(rolapSchema.key);
1124            }
1125    
1126    
1127            /**
1128             * Creates a key with which to identify a schema in the cache.
1129             */
1130            private static String makeKey(
1131                final String catalogUrl,
1132                final String connectionKey,
1133                final String jdbcUser,
1134                final String dataSourceStr)
1135            {
1136                final StringBuilder buf = new StringBuilder(100);
1137    
1138                appendIfNotNull(buf, catalogUrl);
1139                appendIfNotNull(buf, connectionKey);
1140                appendIfNotNull(buf, jdbcUser);
1141                appendIfNotNull(buf, dataSourceStr);
1142    
1143                return buf.toString();
1144            }
1145    
1146            /**
1147             * Creates a key with which to identify a schema in the cache.
1148             */
1149            private static String makeKey(
1150                final String catalogUrl,
1151                final DataSource dataSource)
1152            {
1153                final StringBuilder buf = new StringBuilder(100);
1154    
1155                appendIfNotNull(buf, catalogUrl);
1156                buf.append('.');
1157                buf.append("external#");
1158                buf.append(System.identityHashCode(dataSource));
1159    
1160                return buf.toString();
1161            }
1162    
1163            private static void appendIfNotNull(StringBuilder buf, String s) {
1164                if (s != null) {
1165                    if (buf.length() > 0) {
1166                        buf.append('.');
1167                    }
1168                    buf.append(s);
1169                }
1170            }
1171        }
1172    
1173        public static Iterator<RolapSchema> getRolapSchemas() {
1174            return Pool.instance().getRolapSchemas();
1175        }
1176    
1177        public static boolean cacheContains(RolapSchema rolapSchema) {
1178            return Pool.instance().contains(rolapSchema);
1179        }
1180    
1181        public Cube lookupCube(final String cube, final boolean failIfNotFound) {
1182            RolapCube mdxCube = lookupCube(cube);
1183            if (mdxCube == null && failIfNotFound) {
1184                throw MondrianResource.instance().MdxCubeNotFound.ex(cube);
1185            }
1186            return mdxCube;
1187        }
1188    
1189        /**
1190         * Finds a cube called 'cube' in the current catalog, or return null if no
1191         * cube exists.
1192         */
1193        protected RolapCube lookupCube(final String cubeName) {
1194            return mapNameToCube.get(Util.normalizeName(cubeName));
1195        }
1196    
1197        /**
1198         * Returns an xmlCalculatedMember called 'calcMemberName' in the
1199         * cube called 'cubeName' or return null if no calculatedMember or
1200         * xmlCube by those name exists.
1201         */
1202        protected MondrianDef.CalculatedMember lookupXmlCalculatedMember(
1203            final String calcMemberName,
1204            final String cubeName)
1205        {
1206            List<Id.Segment> nameParts = Util.parseIdentifier(calcMemberName);
1207            for (final MondrianDef.Cube cube : xmlSchema.cubes) {
1208                if (Util.equalName(cube.name, cubeName)) {
1209                    for (final MondrianDef.CalculatedMember calculatedMember
1210                            : cube.calculatedMembers)
1211                    {
1212                        if (Util.equalName(
1213                                calculatedMember.dimension, nameParts.get(0).name)
1214                            && Util.equalName(
1215                                calculatedMember.name,
1216                                nameParts.get(nameParts.size() - 1).name))
1217                        {
1218                            return calculatedMember;
1219                        }
1220                    }
1221                }
1222            }
1223            return null;
1224        }
1225    
1226        public List<RolapCube> getCubesWithStar(RolapStar star) {
1227            List<RolapCube> list = new ArrayList<RolapCube>();
1228            for (RolapCube cube : mapNameToCube.values()) {
1229                if (star == cube.getStar()) {
1230                    list.add(cube);
1231                }
1232            }
1233            return list;
1234        }
1235    
1236        /**
1237         * Adds a cube to the cube name map.
1238         * @see #lookupCube(String)
1239         */
1240        protected void addCube(final RolapCube cube) {
1241            mapNameToCube.put(
1242                    Util.normalizeName(cube.getName()),
1243                    cube);
1244        }
1245    
1246        public boolean removeCube(final String cubeName) {
1247            final RolapCube cube =
1248                mapNameToCube.remove(Util.normalizeName(cubeName));
1249            return cube != null;
1250        }
1251    
1252        public Cube[] getCubes() {
1253            Collection<RolapCube> cubes = mapNameToCube.values();
1254            return cubes.toArray(new RolapCube[cubes.size()]);
1255        }
1256    
1257        public List<RolapCube> getCubeList() {
1258            return new ArrayList<RolapCube>(mapNameToCube.values());
1259        }
1260    
1261        public Hierarchy[] getSharedHierarchies() {
1262            Collection<RolapHierarchy> hierarchies =
1263                mapSharedHierarchyNameToHierarchy.values();
1264            return hierarchies.toArray(new RolapHierarchy[hierarchies.size()]);
1265        }
1266    
1267        RolapHierarchy getSharedHierarchy(final String name) {
1268            return mapSharedHierarchyNameToHierarchy.get(name);
1269        }
1270    
1271        public NamedSet getNamedSet(String name) {
1272            return mapNameToSet.get(name);
1273        }
1274    
1275        public Role lookupRole(final String role) {
1276            return mapNameToRole.get(role);
1277        }
1278    
1279        public Set<String> roleNames() {
1280            return mapNameToRole.keySet();
1281        }
1282    
1283        public FunTable getFunTable() {
1284            return funTable;
1285        }
1286    
1287        public Parameter[] getParameters() {
1288            return parameterList.toArray(
1289                    new Parameter[parameterList.size()]);
1290        }
1291    
1292        /**
1293         * Defines a user-defined function in this table.
1294         *
1295         * <p>If the function is not valid, throws an error.
1296         *
1297         * @param name Name of the function.
1298         * @param className Name of the class which implements the function.
1299         *   The class must implement {@link mondrian.spi.UserDefinedFunction}
1300         *   (otherwise it is a user-error).
1301         */
1302        private void defineFunction(
1303            Map<String, UserDefinedFunction> mapNameToUdf,
1304            String name,
1305            String className)
1306        {
1307            // Lookup class.
1308            final Class<UserDefinedFunction> klass;
1309            try {
1310                klass = (Class<UserDefinedFunction>) Class.forName(className);
1311            } catch (ClassNotFoundException e) {
1312                throw MondrianResource.instance().UdfClassNotFound.ex(
1313                    name,
1314                    className);
1315            }
1316            // Instantiate UDF by calling correct constructor.
1317            final UserDefinedFunction udf = Util.createUdf(klass, name);
1318            // Validate function.
1319            validateFunction(udf);
1320            // Check for duplicate.
1321            UserDefinedFunction existingUdf = mapNameToUdf.get(name);
1322            if (existingUdf != null) {
1323                throw MondrianResource.instance().UdfDuplicateName.ex(name);
1324            }
1325            mapNameToUdf.put(name, udf);
1326        }
1327    
1328        /**
1329         * Throws an error if a user-defined function does not adhere to the
1330         * API.
1331         */
1332        private void validateFunction(final UserDefinedFunction udf) {
1333            // Check that the name is not null or empty.
1334            final String udfName = udf.getName();
1335            if (udfName == null || udfName.equals("")) {
1336                throw Util.newInternal(
1337                    "User-defined function defined by class '"
1338                    + udf.getClass() + "' has empty name");
1339            }
1340            // It's OK for the description to be null.
1341            final String description = udf.getDescription();
1342            Util.discard(description);
1343            final Type[] parameterTypes = udf.getParameterTypes();
1344            for (int i = 0; i < parameterTypes.length; i++) {
1345                Type parameterType = parameterTypes[i];
1346                if (parameterType == null) {
1347                    throw Util.newInternal(
1348                        "Invalid user-defined function '"
1349                        + udfName + "': parameter type #" + i + " is null");
1350                }
1351            }
1352            // It's OK for the reserved words to be null or empty.
1353            final String[] reservedWords = udf.getReservedWords();
1354            Util.discard(reservedWords);
1355            // Test that the function returns a sensible type when given the FORMAL
1356            // types. It may still fail when we give it the ACTUAL types, but it's
1357            // impossible to check that now.
1358            final Type returnType = udf.getReturnType(parameterTypes);
1359            if (returnType == null) {
1360                throw Util.newInternal(
1361                    "Invalid user-defined function '"
1362                    + udfName + "': return type is null");
1363            }
1364            final Syntax syntax = udf.getSyntax();
1365            if (syntax == null) {
1366                throw Util.newInternal(
1367                    "Invalid user-defined function '"
1368                    + udfName + "': syntax is null");
1369            }
1370        }
1371    
1372        /**
1373         * Gets a {@link MemberReader} with which to read a hierarchy. If the
1374         * hierarchy is shared (<code>sharedName</code> is not null), looks up
1375         * a reader from a cache, or creates one if necessary.
1376         *
1377         * <p>Synchronization: thread safe
1378         */
1379        synchronized MemberReader createMemberReader(
1380            final String sharedName,
1381            final RolapHierarchy hierarchy,
1382            final String memberReaderClass)
1383        {
1384            MemberReader reader;
1385            if (sharedName != null) {
1386                reader = mapSharedHierarchyToReader.get(sharedName);
1387                if (reader == null) {
1388                    reader = createMemberReader(hierarchy, memberReaderClass);
1389                    // share, for other uses of the same shared hierarchy
1390                    if (false) {
1391                        mapSharedHierarchyToReader.put(sharedName, reader);
1392                    }
1393    /*
1394    System.out.println("RolapSchema.createMemberReader: "+
1395    "add to sharedHierName->Hier map"+
1396    " sharedName=" + sharedName +
1397    ", hierarchy=" + hierarchy.getName() +
1398    ", hierarchy.dim=" + hierarchy.getDimension().getName()
1399    );
1400    if (mapSharedHierarchyNameToHierarchy.containsKey(sharedName)) {
1401    System.out.println("RolapSchema.createMemberReader: CONTAINS NAME");
1402    } else {
1403                    mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy);
1404    }
1405    */
1406                    if (! mapSharedHierarchyNameToHierarchy.containsKey(
1407                        sharedName))
1408                    {
1409                        mapSharedHierarchyNameToHierarchy.put(
1410                            sharedName, hierarchy);
1411                    }
1412                    //mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy);
1413                } else {
1414    //                final RolapHierarchy sharedHierarchy = (RolapHierarchy)
1415    //                        mapSharedHierarchyNameToHierarchy.get(sharedName);
1416    //                final RolapDimension sharedDimension = (RolapDimension)
1417    //                        sharedHierarchy.getDimension();
1418    //                final RolapDimension dimension =
1419    //                    (RolapDimension) hierarchy.getDimension();
1420    //                Util.assertTrue(
1421    //                        dimension.getGlobalOrdinal() ==
1422    //                        sharedDimension.getGlobalOrdinal());
1423                }
1424            } else {
1425                reader = createMemberReader(hierarchy, memberReaderClass);
1426            }
1427            return reader;
1428        }
1429    
1430        /**
1431         * Creates a {@link MemberReader} with which to Read a hierarchy.
1432         */
1433        private MemberReader createMemberReader(
1434            final RolapHierarchy hierarchy,
1435            final String memberReaderClass)
1436        {
1437            if (memberReaderClass != null) {
1438                Exception e2;
1439                try {
1440                    Properties properties = null;
1441                    Class<?> clazz = Class.forName(memberReaderClass);
1442                    Constructor<?> constructor = clazz.getConstructor(
1443                        RolapHierarchy.class,
1444                        Properties.class);
1445                    Object o = constructor.newInstance(hierarchy, properties);
1446                    if (o instanceof MemberReader) {
1447                        return (MemberReader) o;
1448                    } else if (o instanceof MemberSource) {
1449                        return new CacheMemberReader((MemberSource) o);
1450                    } else {
1451                        throw Util.newInternal(
1452                            "member reader class " + clazz
1453                            + " does not implement " + MemberSource.class);
1454                    }
1455                } catch (ClassNotFoundException e) {
1456                    e2 = e;
1457                } catch (NoSuchMethodException e) {
1458                    e2 = e;
1459                } catch (InstantiationException e) {
1460                    e2 = e;
1461                } catch (IllegalAccessException e) {
1462                    e2 = e;
1463                } catch (InvocationTargetException e) {
1464                    e2 = e;
1465                }
1466                throw Util.newInternal(
1467                    e2,
1468                    "while instantiating member reader '" + memberReaderClass);
1469            } else {
1470                SqlMemberSource source = new SqlMemberSource(hierarchy);
1471                if (hierarchy.getDimension().isHighCardinality()) {
1472                    LOGGER.debug(
1473                        "High cardinality for " + hierarchy.getDimension());
1474                    return new NoCacheMemberReader(source);
1475                } else {
1476                    LOGGER.debug(
1477                        "Normal cardinality for " + hierarchy.getDimension());
1478                    return new SmartMemberReader(source);
1479                }
1480            }
1481        }
1482    
1483        public SchemaReader getSchemaReader() {
1484            return new RolapSchemaReader(defaultRole, this);
1485        }
1486    
1487        /**
1488         * Creates a {@link DataSourceChangeListener} with which to detect changes to datasources.
1489         */
1490        private DataSourceChangeListener createDataSourceChangeListener(
1491            Util.PropertyList connectInfo)
1492        {
1493            DataSourceChangeListener changeListener = null;
1494    
1495            // If CatalogContent is specified in the connect string, ignore
1496            // everything else. In particular, ignore the dynamic schema
1497            // processor.
1498            String dataSourceChangeListenerStr = connectInfo.get(
1499                RolapConnectionProperties.DataSourceChangeListener.name());
1500    
1501            if (! Util.isEmpty(dataSourceChangeListenerStr)) {
1502                try {
1503                    Class<?> clazz = Class.forName(dataSourceChangeListenerStr);
1504                    Constructor<?> constructor = clazz.getConstructor();
1505                    changeListener =
1506                        (DataSourceChangeListener) constructor.newInstance();
1507    
1508    /*
1509                    final Class<DataSourceChangeListener> clazz =
1510                        (Class<DataSourceChangeListener>)
1511                            Class.forName(dataSourceChangeListenerStr);
1512                    final Constructor<DataSourceChangeListener> ctor =
1513                        clazz.getConstructor();
1514                    changeListener = ctor.newInstance();
1515    */
1516                    changeListener =
1517                        (DataSourceChangeListener) constructor.newInstance();
1518                } catch (Exception e) {
1519                    throw Util.newError(
1520                        e,
1521                        "loading DataSourceChangeListener "
1522                        + dataSourceChangeListenerStr);
1523                }
1524    
1525                if (LOGGER.isDebugEnabled()) {
1526                    LOGGER.debug(
1527                        "RolapSchema.createDataSourceChangeListener: "
1528                        + "create datasource change listener \""
1529                        + dataSourceChangeListenerStr);
1530                }
1531            }
1532            return changeListener;
1533        }
1534    
1535    
1536        /**
1537         * Connection for purposes of parsing and validation. Careful! It won't
1538         * have the correct locale or access-control profile.
1539         */
1540        public RolapConnection getInternalConnection() {
1541            return internalConnection;
1542        }
1543    
1544        /**
1545         * Returns the cached cardinality for the column.
1546         * The cache is stored in the schema so that queries on different
1547         * cubes can share them.
1548         * @return the cardinality map
1549         */
1550        Integer getCachedRelationExprCardinality(
1551            MondrianDef.Relation relation,
1552            MondrianDef.Expression columnExpr)
1553        {
1554            Integer card = null;
1555            synchronized (relationExprCardinalityMap) {
1556                Map<MondrianDef.Expression, Integer> exprCardinalityMap =
1557                    relationExprCardinalityMap.get(relation);
1558                if (exprCardinalityMap != null) {
1559                    card = exprCardinalityMap.get(columnExpr);
1560                }
1561            }
1562            return card;
1563        }
1564    
1565        /**
1566         * Sets the cardinality for a given column in cache.
1567         *
1568         * @param relation the relation associated with the column expression
1569         * @param columnExpr the column expression to cache the cardinality for
1570         * @param cardinality the cardinality for the column expression
1571         */
1572        void putCachedRelationExprCardinality(
1573            MondrianDef.Relation relation,
1574            MondrianDef.Expression columnExpr,
1575            Integer cardinality)
1576        {
1577            synchronized (relationExprCardinalityMap) {
1578                Map<MondrianDef.Expression, Integer> exprCardinalityMap =
1579                    relationExprCardinalityMap.get(relation);
1580                if (exprCardinalityMap == null) {
1581                    exprCardinalityMap =
1582                        new HashMap<MondrianDef.Expression, Integer>();
1583                    relationExprCardinalityMap.put(relation, exprCardinalityMap);
1584                }
1585                exprCardinalityMap.put(columnExpr, cardinality);
1586            }
1587        }
1588    
1589        private RoleImpl createDefaultRole() {
1590            RoleImpl role = new RoleImpl();
1591            role.grant(this, Access.ALL);
1592            role.makeImmutable();
1593            return role;
1594        }
1595    
1596        private RolapStar makeRolapStar(final MondrianDef.Relation fact) {
1597            DataSource dataSource = getInternalConnection().getDataSource();
1598            return new RolapStar(this, dataSource, fact);
1599        }
1600    
1601        /**
1602         * <code>RolapStarRegistry</code> is a registry for {@link RolapStar}s.
1603         */
1604        class RolapStarRegistry {
1605            private final Map<String, RolapStar> stars =
1606                new HashMap<String, RolapStar>();
1607    
1608            RolapStarRegistry() {
1609            }
1610    
1611            /**
1612             * Looks up a {@link RolapStar}, creating it if it does not exist.
1613             *
1614             * <p> {@link RolapStar.Table#addJoin} works in a similar way.
1615             */
1616            synchronized RolapStar getOrCreateStar(
1617                final MondrianDef.Relation fact)
1618            {
1619                String factTableName = fact.toString();
1620                RolapStar star = stars.get(factTableName);
1621                if (star == null) {
1622                    star = makeRolapStar(fact);
1623                    stars.put(factTableName, star);
1624                }
1625                return star;
1626            }
1627    
1628            synchronized RolapStar getStar(final String factTableName) {
1629                return stars.get(factTableName);
1630            }
1631    
1632            synchronized Collection<RolapStar> getStars() {
1633                return stars.values();
1634            }
1635        }
1636    
1637        private RolapStarRegistry rolapStarRegistry = new RolapStarRegistry();
1638    
1639        public RolapStarRegistry getRolapStarRegistry() {
1640            return rolapStarRegistry;
1641        }
1642    
1643        /**
1644         * Function table which contains all of the user-defined functions in this
1645         * schema, plus all of the standard functions.
1646         */
1647        static class RolapSchemaFunctionTable extends FunTableImpl {
1648            private final List<UserDefinedFunction> udfList;
1649    
1650            RolapSchemaFunctionTable(Collection<UserDefinedFunction> udfs) {
1651                udfList = new ArrayList<UserDefinedFunction>(udfs);
1652            }
1653    
1654            public void defineFunctions(Builder builder) {
1655                final FunTable globalFunTable = GlobalFunTable.instance();
1656                for (String reservedWord : globalFunTable.getReservedWords()) {
1657                    builder.defineReserved(reservedWord);
1658                }
1659                for (Resolver resolver : globalFunTable.getResolvers()) {
1660                    builder.define(resolver);
1661                }
1662                for (UserDefinedFunction udf : udfList) {
1663                    builder.define(new UdfResolver(udf));
1664                }
1665            }
1666        }
1667    
1668        public RolapStar getStar(final String factTableName) {
1669            return getRolapStarRegistry().getStar(factTableName);
1670        }
1671    
1672        public Collection<RolapStar> getStars() {
1673            return getRolapStarRegistry().getStars();
1674        }
1675    
1676        /**
1677         * Checks whether there are modifications in the aggregations cache.
1678         */
1679        public void checkAggregateModifications() {
1680            for (RolapStar star : getStars()) {
1681                star.checkAggregateModifications();
1682            }
1683        }
1684    
1685        /**
1686         * Pushes all modifications of the aggregations to global cache,
1687         * so other queries can start using the new cache
1688         */
1689        public void pushAggregateModificationsToGlobalCache() {
1690            for (RolapStar star : getStars()) {
1691                star.pushAggregateModificationsToGlobalCache();
1692            }
1693        }
1694    
1695        final RolapNativeRegistry nativeRegistry = new RolapNativeRegistry();
1696    
1697        RolapNativeRegistry getNativeRegistry() {
1698            return nativeRegistry;
1699        }
1700    
1701        /**
1702         * @return Returns the dataSourceChangeListener.
1703         */
1704        public DataSourceChangeListener getDataSourceChangeListener() {
1705            return dataSourceChangeListener;
1706        }
1707    
1708        /**
1709         * @param dataSourceChangeListener The dataSourceChangeListener to set.
1710         */
1711        public void setDataSourceChangeListener(
1712            DataSourceChangeListener dataSourceChangeListener)
1713        {
1714            this.dataSourceChangeListener = dataSourceChangeListener;
1715        }
1716    
1717        /**
1718         * Location of a node in an XML document.
1719         */
1720        private interface XmlLocation {
1721        }
1722    }
1723    
1724    // End RolapSchema.java