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