KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > RolapSchema


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapSchema.java#87 $
3 // This software is subject to the terms of the Common Public License
4 // Agreement, available at the following URL:
5 // http://www.opensource.org/licenses/cpl.html.
6 // Copyright (C) 2001-2002 Kana Software, Inc.
7 // Copyright (C) 2001-2007 Julian Hyde and others
8 // All Rights Reserved.
9 // You must accept the terms of that agreement to use this software.
10 //
11 // jhyde, 26 July, 2001
12 */

13
14 package mondrian.rolap;
15 import java.io.*;
16 import java.lang.ref.SoftReference JavaDoc;
17 import java.lang.reflect.Constructor JavaDoc;
18 import java.lang.reflect.InvocationTargetException JavaDoc;
19 import java.lang.reflect.Modifier JavaDoc;
20 import java.net.URL JavaDoc;
21 import java.security.MessageDigest JavaDoc;
22 import java.security.NoSuchAlgorithmException JavaDoc;
23 import java.util.*;
24
25 import javax.sql.DataSource JavaDoc;
26
27 import mondrian.olap.Access;
28 import mondrian.olap.Category;
29 import mondrian.olap.Cube;
30 import mondrian.olap.Dimension;
31 import mondrian.olap.Exp;
32 import mondrian.olap.Formula;
33 import mondrian.olap.FunTable;
34 import mondrian.olap.Hierarchy;
35 import mondrian.olap.Level;
36 import mondrian.olap.Member;
37 import mondrian.olap.MondrianDef;
38 import mondrian.olap.MondrianProperties;
39 import mondrian.olap.NamedSet;
40 import mondrian.olap.Parameter;
41 import mondrian.olap.Role;
42 import mondrian.olap.Schema;
43 import mondrian.olap.SchemaReader;
44 import mondrian.olap.Syntax;
45 import mondrian.olap.Util;
46 import mondrian.olap.fun.*;
47 import mondrian.olap.type.MemberType;
48 import mondrian.olap.type.NumericType;
49 import mondrian.olap.type.StringType;
50 import mondrian.olap.type.Type;
51 import mondrian.resource.MondrianResource;
52 import mondrian.rolap.aggmatcher.AggTableManager;
53 import mondrian.rolap.aggmatcher.JdbcSchema;
54 import mondrian.rolap.sql.SqlQuery;
55 import mondrian.spi.UserDefinedFunction;
56 import mondrian.spi.DataSourceChangeListener;
57
58 import org.apache.log4j.Logger;
59 import org.apache.commons.vfs.*;
60
61 import org.eigenbase.xom.DOMWrapper;
62 import org.eigenbase.xom.Parser;
63 import org.eigenbase.xom.XOMException;
64 import org.eigenbase.xom.XOMUtil;
65
66 /**
67  * A <code>RolapSchema</code> is a collection of {@link RolapCube}s and
68  * shared {@link RolapDimension}s. It is shared betweeen {@link
69  * RolapConnection}s. It caches {@link MemberReader}s, etc.
70  *
71  * @see RolapConnection
72  * @author jhyde
73  * @since 26 July, 2001
74  * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapSchema.java#87 $
75  */

76 public class RolapSchema implements Schema {
77     private static final Logger LOGGER = Logger.getLogger(RolapSchema.class);
78
79     private static final Set<Access> schemaAllowed =
80         Util.enumSetOf(Access.NONE, Access.ALL, Access.ALL_DIMENSIONS);
81
82     private static final Set<Access> cubeAllowed =
83         Util.enumSetOf(Access.NONE, Access.ALL);
84
85     private static final Set<Access> dimensionAllowed =
86         Util.enumSetOf(Access.NONE, Access.ALL);
87
88     private static final Set<Access> hierarchyAllowed =
89         Util.enumSetOf(Access.NONE, Access.ALL, Access.CUSTOM);
90
91     private static final Set<Access> memberAllowed =
92         Util.enumSetOf(Access.NONE, Access.ALL);
93
94     private String JavaDoc name;
95
96     /**
97      * Internal use only.
98      */

99     private final RolapConnection internalConnection;
100     /**
101      * Holds cubes in this schema.
102      */

103     private final Map<String JavaDoc, RolapCube> mapNameToCube;
104     /**
105      * Maps {@link String shared hierarchy name} to {@link MemberReader}.
106      * Shared between all statements which use this connection.
107      */

108     private final Map<String JavaDoc, MemberReader> mapSharedHierarchyToReader;
109
110     /**
111      * Maps {@link String names of shared hierarchies} to {@link
112      * RolapHierarchy the canonical instance of those hierarchies}.
113      */

114     private final Map<String JavaDoc, RolapHierarchy> mapSharedHierarchyNameToHierarchy;
115     /**
116      * The default role for connections to this schema.
117      */

118     private Role defaultRole;
119
120     private final String JavaDoc md5Bytes;
121
122     /**
123      * A schema's aggregation information
124      */

125     private AggTableManager aggTableManager;
126
127     /**
128      * This is basically a unique identifier for this RolapSchema instance
129      * used it its equals and hashCode methods.
130      */

131     private String JavaDoc key;
132     /**
133      * Maps {@link String names of roles} to {@link Role roles with those names}.
134      */

135     private final Map<String JavaDoc, Role> mapNameToRole;
136     /**
137      * Maps {@link String names of sets} to {@link NamedSet named sets}.
138      */

139     private final Map<String JavaDoc, NamedSet> mapNameToSet = new HashMap<String JavaDoc, NamedSet>();
140     /**
141      * Table containing all standard MDX functions, plus user-defined functions
142      * for this schema.
143      */

144     private FunTable funTable;
145
146     private MondrianDef.Schema xmlSchema;
147
148     final List<RolapSchemaParameter > parameterList =
149         new ArrayList<RolapSchemaParameter >();
150
151     private Date schemaLoadDate;
152
153     private DataSourceChangeListener dataSourceChangeListener;
154
155     /**
156      * This is ONLY called by other constructors (and MUST be called
157      * by them) and NEVER by the Pool.
158      *
159      * @param key
160      * @param connectInfo
161      * @param dataSource
162      * @param md5Bytes
163      */

164     private RolapSchema(
165             final String JavaDoc key,
166             final Util.PropertyList connectInfo,
167             final DataSource JavaDoc dataSource,
168             final String JavaDoc md5Bytes) {
169         this.key = key;
170         this.md5Bytes = md5Bytes;
171         // the order of the next two lines is important
172
this.defaultRole = createDefaultRole();
173         this.internalConnection =
174             new RolapConnection(connectInfo, this, dataSource);
175
176         this.mapSharedHierarchyNameToHierarchy = new HashMap<String JavaDoc, RolapHierarchy>();
177         this.mapSharedHierarchyToReader = new HashMap<String JavaDoc, MemberReader>();
178         this.mapNameToCube = new HashMap<String JavaDoc, RolapCube>();
179         this.mapNameToRole = new HashMap<String JavaDoc, Role>();
180         this.aggTableManager = new AggTableManager(this);
181         this.dataSourceChangeListener = createDataSourceChangeListener(connectInfo);
182     }
183
184
185     /**
186      * Create RolapSchema given the MD5 hash, catalog name and string (content)
187      * and the connectInfo object.
188      *
189      * @param md5Bytes may be null
190      * @param catalogUrl URL of catalog
191      * @param catalogStr may be null
192      * @param connectInfo Connection properties
193      */

194     private RolapSchema(
195             final String JavaDoc key,
196             final String JavaDoc md5Bytes,
197             final String JavaDoc catalogUrl,
198             final String JavaDoc catalogStr,
199             final Util.PropertyList connectInfo,
200             final DataSource JavaDoc dataSource)
201     {
202         this(key, connectInfo, dataSource, md5Bytes);
203         load(catalogUrl, catalogStr);
204     }
205
206     private RolapSchema(
207             final String JavaDoc key,
208             final String JavaDoc catalogUrl,
209             final Util.PropertyList connectInfo,
210             final DataSource JavaDoc dataSource)
211     {
212         this(key, connectInfo, dataSource, null);
213         load(catalogUrl, null);
214     }
215
216     protected void finalCleanUp() {
217         if (aggTableManager != null) {
218             aggTableManager.finalCleanUp();
219             aggTableManager = null;
220         }
221     }
222
223     protected void finalize() throws Throwable JavaDoc {
224         finalCleanUp();
225     }
226
227     public boolean equals(Object JavaDoc o) {
228         if (!(o instanceof RolapSchema)) {
229             return false;
230         }
231         RolapSchema other = (RolapSchema) o;
232         return other.key.equals(key);
233     }
234
235     public int hashCode() {
236         return key.hashCode();
237     }
238
239     protected Logger getLogger() {
240         return LOGGER;
241     }
242
243     /**
244      * Method called by all constructors to load the catalog into DOM and build
245      * application mdx and sql objects.
246      *
247      * @param catalogUrl URL of catalog
248      * @param catalogStr Text of catalog, or null
249      */

250     protected void load(String JavaDoc catalogUrl, String JavaDoc catalogStr) {
251         try {
252             final Parser xmlParser = XOMUtil.createDefaultParser();
253
254             final DOMWrapper def;
255             if (catalogStr == null) {
256                 // Treat catalogUrl as an Apache VFS (Virtual File System) URL.
257
// VFS handles all of the usual protocols (http:, file:)
258
// and then some.
259
FileSystemManager fsManager = VFS.getManager();
260                 if (fsManager == null) {
261                     throw Util.newError("Cannot get virtual file system manager");
262                 }
263
264                 // Workaround VFS bug.
265
if (catalogUrl.startsWith("file://localhost")) {
266                     catalogUrl = catalogUrl.substring("file://localhost".length());
267                 }
268                 if (catalogUrl.startsWith("file:")) {
269                     catalogUrl = catalogUrl.substring("file:".length());
270                 }
271
272                 File userDir = new File("").getAbsoluteFile();
273                 FileObject file = fsManager.resolveFile(userDir, catalogUrl);
274                 if (!file.isReadable()) {
275                     throw Util.newError("Virtual file is not readable: " +
276                         catalogUrl);
277                 }
278
279                 FileContent fileContent = file.getContent();
280                 if (fileContent == null) {
281                     throw Util.newError("Cannot get virtual file content: " +
282                         catalogUrl);
283                 }
284                 def = xmlParser.parse(fileContent.getInputStream());
285             } else {
286                 def = xmlParser.parse(catalogStr);
287             }
288
289             xmlSchema = new MondrianDef.Schema(def);
290
291             if (getLogger().isDebugEnabled()) {
292                 StringWriter sw = new StringWriter(4096);
293                 PrintWriter pw = new PrintWriter(sw);
294                 pw.println("RolapSchema.load: dump xmlschema");
295                 xmlSchema.display(pw, 2);
296                 pw.flush();
297                 getLogger().debug(sw.toString());
298             }
299
300             load(xmlSchema);
301
302         } catch (XOMException e) {
303             throw Util.newError(e, "while parsing catalog " + catalogUrl);
304         } catch (FileSystemException e) {
305             throw Util.newError(e, "while parsing catalog " + catalogUrl);
306         }
307
308         aggTableManager.initialize();
309         setSchemaLoadDate();
310     }
311
312     private void setSchemaLoadDate() {
313         schemaLoadDate = new Date();
314     }
315
316     public Date getSchemaLoadDate() {
317         return schemaLoadDate;
318     }
319
320     Role getDefaultRole() {
321         return defaultRole;
322     }
323
324     MondrianDef.Schema getXMLSchema() {
325         return xmlSchema;
326     }
327
328     public String JavaDoc getName() {
329         Util.assertPostcondition(name != null, "return != null");
330         Util.assertPostcondition(name.length() > 0, "return.length() > 0");
331         return name;
332     }
333
334     /**
335      * Returns this schema's SQL dialect.
336      *
337      * <p>NOTE: This method is not cheap. The implementation gets a connection
338      * from the connection pool.
339      */

340     public SqlQuery.Dialect getDialect() {
341         DataSource JavaDoc dataSource = getInternalConnection().getDataSource();
342         return SqlQuery.Dialect.create(dataSource);
343     }
344
345     private void load(MondrianDef.Schema xmlSchema) {
346         this.name = xmlSchema.name;
347         if (name == null || name.equals("")) {
348             throw Util.newError("<Schema> name must be set");
349         }
350
351         // Validate user-defined functions. Must be done before we validate
352
// calculated members, because calculated members will need to use the
353
// function table.
354
final Map<String JavaDoc, UserDefinedFunction> mapNameToUdf =
355             new HashMap<String JavaDoc, UserDefinedFunction>();
356         for (MondrianDef.UserDefinedFunction udf : xmlSchema.userDefinedFunctions) {
357             defineFunction(mapNameToUdf, udf.name, udf.className);
358         }
359         final RolapSchemaFunctionTable funTable =
360                 new RolapSchemaFunctionTable(mapNameToUdf.values());
361         funTable.init();
362         this.funTable = funTable;
363
364         // Validate public dimensions.
365
for (MondrianDef.Dimension xmlDimension : xmlSchema.dimensions) {
366             if (xmlDimension.foreignKey != null) {
367                 throw MondrianResource.instance()
368                     .PublicDimensionMustNotHaveForeignKey.ex(
369                     xmlDimension.name);
370             }
371         }
372
373         // Create parameters.
374
Set<String JavaDoc> parameterNames = new HashSet<String JavaDoc>();
375         for (MondrianDef.Parameter xmlParameter : xmlSchema.parameters) {
376             String JavaDoc name = xmlParameter.name;
377             if (!parameterNames.add(name)) {
378                 throw MondrianResource.instance().DuplicateSchemaParameter.ex(
379                     name);
380             }
381             Type type;
382             if (xmlParameter.type.equals("String")) {
383                 type = new StringType();
384             } else if (xmlParameter.type.equals("Numeric")) {
385                 type = new NumericType();
386             } else {
387                 type = new MemberType(null, null, null, null);
388             }
389             final String JavaDoc description = xmlParameter.description;
390             final boolean modifiable = xmlParameter.modifiable;
391             String JavaDoc defaultValue = xmlParameter.defaultValue;
392             RolapSchemaParameter param =
393                 new RolapSchemaParameter(
394                     this, name, defaultValue, description, type, modifiable);
395             Util.discard(param);
396         }
397
398         // Create cubes.
399
for (MondrianDef.Cube xmlCube : xmlSchema.cubes) {
400             if (xmlCube.isEnabled()) {
401                 RolapCube cube = new RolapCube(this, xmlSchema, xmlCube, true);
402                 cube.validate();
403             }
404         }
405
406         // Create virtual cubes.
407
for (MondrianDef.VirtualCube xmlVirtualCube : xmlSchema.virtualCubes) {
408             if (xmlVirtualCube.isEnabled()) {
409                 RolapCube cube =
410                     new RolapCube(this, xmlSchema, xmlVirtualCube, true);
411                 Util.discard(cube);
412             }
413         }
414
415         // Create named sets.
416
for (MondrianDef.NamedSet xmlNamedSet : xmlSchema.namedSets) {
417             mapNameToSet.put(xmlNamedSet.name, createNamedSet(xmlNamedSet));
418         }
419
420         // Create roles.
421
for (MondrianDef.Role xmlRole : xmlSchema.roles) {
422             Role role = createRole(xmlRole);
423             mapNameToRole.put(xmlRole.name, role);
424         }
425
426         // Set default role.
427
if (xmlSchema.defaultRole != null) {
428             Role role = lookupRole(xmlSchema.defaultRole);
429             if (role == null) {
430                 throw Util.newError("Role '" + xmlSchema.defaultRole + "' not found");
431             }
432             defaultRole = role;
433         }
434     }
435
436     private NamedSet createNamedSet(MondrianDef.NamedSet xmlNamedSet) {
437         final String JavaDoc formulaString = xmlNamedSet.getFormula();
438         final Exp exp;
439         try {
440             exp = getInternalConnection().parseExpression(formulaString);
441         } catch (Exception JavaDoc e) {
442             throw MondrianResource.instance().NamedSetHasBadFormula.ex(
443                     xmlNamedSet.name, e);
444         }
445         final Formula formula = new Formula(
446                 new String JavaDoc[] {xmlNamedSet.name},
447                 exp);
448         return formula.getNamedSet();
449     }
450
451     private Role createRole(MondrianDef.Role xmlRole) {
452         Role role = new Role();
453         for (MondrianDef.SchemaGrant schemaGrant : xmlRole.schemaGrants) {
454             role.grant(this, getAccess(schemaGrant.access, schemaAllowed));
455             for (MondrianDef.CubeGrant cubeGrant : schemaGrant.cubeGrants) {
456                 RolapCube cube = lookupCube(cubeGrant.cube);
457                 if (cube == null) {
458                     throw Util.newError("Unknown cube '" + cube + "'");
459                 }
460                 role.grant(cube, getAccess(cubeGrant.access, cubeAllowed));
461                 final SchemaReader schemaReader = cube.getSchemaReader(null);
462                 for (MondrianDef.DimensionGrant dimensionGrant : cubeGrant.dimensionGrants) {
463                     Dimension dimension = (Dimension)
464                         schemaReader.lookupCompound(
465                             cube, Util.explode(dimensionGrant.dimension), true,
466                             Category.Dimension);
467                     role.grant(
468                         dimension,
469                         getAccess(dimensionGrant.access, dimensionAllowed));
470                 }
471                 for (MondrianDef.HierarchyGrant hierarchyGrant : cubeGrant.hierarchyGrants) {
472                     Hierarchy hierarchy = (Hierarchy)
473                         schemaReader.lookupCompound(
474                             cube, Util.explode(hierarchyGrant.hierarchy), true,
475                             Category.Hierarchy);
476                     final Access hierarchyAccess =
477                         getAccess(hierarchyGrant.access, hierarchyAllowed);
478                     Level topLevel = null;
479                     if (hierarchyGrant.topLevel != null) {
480                         if (hierarchyAccess != Access.CUSTOM) {
481                             throw Util.newError(
482                                 "You may only specify 'topLevel' if access='custom'");
483                         }
484                         topLevel = (Level) schemaReader.lookupCompound(
485                             cube, Util.explode(hierarchyGrant.topLevel), true,
486                             Category.Level);
487                     }
488                     Level bottomLevel = null;
489                     if (hierarchyGrant.bottomLevel != null) {
490                         if (hierarchyAccess != Access.CUSTOM) {
491                             throw Util.newError(
492                                 "You may only specify 'bottomLevel' if access='custom'");
493                         }
494                         bottomLevel = (Level) schemaReader.lookupCompound(
495                             cube, Util.explode(hierarchyGrant.bottomLevel),
496                             true, Category.Level);
497                     }
498                     role.grant(
499                         hierarchy, hierarchyAccess, topLevel, bottomLevel);
500                     for (MondrianDef.MemberGrant memberGrant : hierarchyGrant.memberGrants) {
501                         if (hierarchyAccess != Access.CUSTOM) {
502                             throw Util.newError(
503                                 "You may only specify <MemberGrant> if <Hierarchy> has access='custom'");
504                         }
505                         Member member = schemaReader.getMemberByUniqueName(
506                             Util.explode(memberGrant.member), true);
507                         if (member.getHierarchy() != hierarchy) {
508                             throw Util.newError(
509                                 "Member '" +
510                                     member +
511                                     "' is not in hierarchy '" +
512                                     hierarchy +
513                                     "'");
514                         }
515                         role.grant(
516                             member,
517                             getAccess(memberGrant.access, memberAllowed));
518                     }
519                 }
520             }
521         }
522         role.makeImmutable();
523         return role;
524     }
525
526     private Access getAccess(String JavaDoc accessString, Set<Access> allowed) {
527         final Access access = Access.valueOf(accessString.toUpperCase());
528         if (allowed.contains(access)) {
529             return access; // value is ok
530
}
531         throw Util.newError("Bad value access='" + accessString + "'");
532     }
533
534     public Dimension createDimension(Cube cube, String JavaDoc xml) {
535         MondrianDef.CubeDimension xmlDimension;
536         try {
537             final Parser xmlParser = XOMUtil.createDefaultParser();
538             final DOMWrapper def = xmlParser.parse(xml);
539             final String JavaDoc tagName = def.getTagName();
540             if (tagName.equals("Dimension")) {
541                 xmlDimension = new MondrianDef.Dimension(def);
542             } else if (tagName.equals("DimensionUsage")) {
543                 xmlDimension = new MondrianDef.DimensionUsage(def);
544             } else {
545                 throw new XOMException("Got <" + tagName +
546                         "> when expecting <Dimension> or <DimensionUsage>");
547             }
548         } catch (XOMException e) {
549             throw Util.newError(e, "Error while adding dimension to cube '" +
550                     cube + "' from XML [" + xml + "]");
551         }
552         return ((RolapCube) cube).createDimension(xmlDimension);
553     }
554
555     public Cube createCube(String JavaDoc xml) {
556
557         RolapCube cube;
558         try {
559             final Parser xmlParser = XOMUtil.createDefaultParser();
560             final DOMWrapper def = xmlParser.parse(xml);
561             final String JavaDoc tagName = def.getTagName();
562             if (tagName.equals("Cube")) {
563                 // Create empty XML schema, to keep the method happy. This is
564
// okay, because there are no forward-references to resolve.
565
final MondrianDef.Schema xmlSchema = new MondrianDef.Schema();
566                 MondrianDef.Cube xmlDimension = new MondrianDef.Cube(def);
567                 cube = new RolapCube(this, xmlSchema, xmlDimension, false);
568             } else if (tagName.equals("VirtualCube")) {
569                 // Need the real schema here.
570
MondrianDef.Schema xmlSchema = getXMLSchema();
571                 MondrianDef.VirtualCube xmlDimension =
572                         new MondrianDef.VirtualCube(def);
573                 cube = new RolapCube(this, xmlSchema, xmlDimension, false);
574             } else {
575                 throw new XOMException("Got <" + tagName +
576                     "> when expecting <Cube>");
577             }
578         } catch (XOMException e) {
579             throw Util.newError(e, "Error while creating cube from XML [" +
580                 xml + "]");
581         }
582         return cube;
583     }
584
585 /*
586     public Cube createCube(String xml) {
587         MondrianDef.Cube xmlDimension;
588         try {
589             final Parser xmlParser = XOMUtil.createDefaultParser();
590             final DOMWrapper def = xmlParser.parse(xml);
591             final String tagName = def.getTagName();
592             if (tagName.equals("Cube")) {
593                 xmlDimension = new MondrianDef.Cube(def);
594             } else {
595                 throw new XOMException("Got <" + tagName +
596                     "> when expecting <Cube>");
597             }
598         } catch (XOMException e) {
599             throw Util.newError(e, "Error while creating cube from XML [" +
600                 xml + "]");
601         }
602         // Create empty XML schema, to keep the method happy. This is okay,
603         // because there are no forward-references to resolve.
604         final MondrianDef.Schema xmlSchema = new MondrianDef.Schema();
605         RolapCube cube = new RolapCube(this, xmlSchema, xmlDimension);
606         return cube;
607     }
608 */

609
610     /**
611      * A collection of schemas, identified by their connection properties
612      * (catalog name, JDBC URL, and so forth).
613      *
614      * <p>To lookup a schema, call <code>Pool.instance().{@link #get(String, DataSource, Util.PropertyList)}</code>.
615      */

616     static class Pool {
617         private final MessageDigest JavaDoc md;
618
619         private static Pool pool = new Pool();
620
621         private Map<String JavaDoc, SoftReference JavaDoc<RolapSchema>> mapUrlToSchema =
622             new HashMap<String JavaDoc, SoftReference JavaDoc<RolapSchema>>();
623
624
625         private Pool() {
626             // Initialize the MD5 digester.
627
try {
628                 md = MessageDigest.getInstance("MD5");
629             } catch (NoSuchAlgorithmException JavaDoc e) {
630                 throw new RuntimeException JavaDoc(e);
631             }
632         }
633
634         static Pool instance() {
635             return pool;
636         }
637
638         /**
639          * Creates an MD5 hash of String.
640          *
641          * @param value String to create one way hash upon.
642          * @return MD5 hash.
643          */

644         private synchronized String JavaDoc encodeMD5(final String JavaDoc value) {
645             md.reset();
646             final byte[] bytes = md.digest(value.getBytes());
647             return (bytes != null) ? new String JavaDoc(bytes) : null;
648         }
649
650         synchronized RolapSchema get(
651             final String JavaDoc catalogUrl,
652             final String JavaDoc connectionKey,
653             final String JavaDoc jdbcUser,
654             final String JavaDoc dataSourceStr,
655             final Util.PropertyList connectInfo)
656         {
657             return get(
658                 catalogUrl,
659                 connectionKey,
660                 jdbcUser,
661                 dataSourceStr,
662                 null,
663                 connectInfo);
664         }
665
666         synchronized RolapSchema get(
667             final String JavaDoc catalogUrl,
668             final DataSource JavaDoc dataSource,
669             final Util.PropertyList connectInfo)
670         {
671             return get(
672                 catalogUrl,
673                 null,
674                 null,
675                 null,
676                 dataSource,
677                 connectInfo);
678         }
679
680         private RolapSchema get(
681             final String JavaDoc catalogUrl,
682             final String JavaDoc connectionKey,
683             final String JavaDoc jdbcUser,
684             final String JavaDoc dataSourceStr,
685             final DataSource JavaDoc dataSource,
686             final Util.PropertyList connectInfo)
687         {
688             String JavaDoc key = (dataSource == null) ?
689                 makeKey(catalogUrl, connectionKey, jdbcUser, dataSourceStr) :
690                 makeKey(catalogUrl, dataSource);
691
692             RolapSchema schema = null;
693             boolean hadDynProc = false;
694
695             String JavaDoc dynProcName = connectInfo.get(
696                 RolapConnectionProperties.DynamicSchemaProcessor.name());
697
698             // If CatalogContent is specified in the connect string, ignore
699
// everything else. In particular, ignore the dynamic schema
700
// processor.
701
String JavaDoc catalogStr = connectInfo.get(
702                 RolapConnectionProperties.CatalogContent.name());
703             if (catalogStr != null) {
704                 dynProcName = null;
705                 // REVIEW: Are we including enough in the key to make it
706
// unique?
707
key = catalogStr;
708             }
709
710             // If there is a dynamic processor registered, use it. This
711
// implies there is not MD5 based caching, but, as with the previous
712
// implementation, if the catalog string is in the connectInfo
713
// object as catalog content then it is used.
714
if ( ! Util.isEmpty(dynProcName)) {
715                 assert catalogStr == null;
716
717                 try {
718                     final URL JavaDoc url = new URL JavaDoc(catalogUrl);
719
720                     final Class JavaDoc<DynamicSchemaProcessor> clazz =
721                         (Class JavaDoc<DynamicSchemaProcessor>) Class.forName(dynProcName);
722                     final Constructor JavaDoc<DynamicSchemaProcessor> ctor =
723                         clazz.getConstructor();
724                     final DynamicSchemaProcessor dynProc = ctor.newInstance();
725                     catalogStr = dynProc.processSchema(url, connectInfo);
726                     hadDynProc = true;
727
728                 } catch (Exception JavaDoc e) {
729                     throw Util.newError(e, "loading DynamicSchemaProcessor "
730                         + dynProcName);
731                 }
732
733                 if (LOGGER.isDebugEnabled()) {
734                     String JavaDoc msg = "Pool.get: create schema \"" +
735                         catalogUrl +
736                         "\" using dynamic processor";
737                     LOGGER.debug(msg);
738                 }
739             }
740
741             boolean useContentChecksum = Boolean.parseBoolean(
742                 connectInfo.get(
743                     RolapConnectionProperties.UseContentChecksum.name()));
744             if (useContentChecksum) {
745                 // Different catalogUrls can actually yield the same
746
// catalogStr! So, we use the MD5 as the key as well as
747
// the key made above - its has two entries in the
748
// mapUrlToSchema Map. We must then also during the
749
// remove operation make sure we remove both.
750

751                 String JavaDoc md5Bytes = null;
752                 try {
753                     if (catalogStr == null) {
754                         catalogStr = Util.readURL(catalogUrl);
755                     }
756                     md5Bytes = encodeMD5(catalogStr);
757
758                 } catch (Exception JavaDoc ex) {
759                     // Note, can not throw an Exception from this method
760
// but just to show that all is not well in Mudville
761
// we print stack trace (for now - better to change
762
// method signature and throw).
763
ex.printStackTrace();
764                 }
765
766                 if (md5Bytes != null) {
767                     SoftReference JavaDoc<RolapSchema> ref =
768                         mapUrlToSchema.get(md5Bytes);
769                     if (ref != null) {
770                         schema = ref.get();
771                         if (schema == null) {
772                             // clear out the reference since schema is null
773
mapUrlToSchema.remove(key);
774                             mapUrlToSchema.remove(md5Bytes);
775                         }
776                     }
777                 }
778
779                 if ((schema == null) ||
780                     md5Bytes == null ||
781                     schema.md5Bytes == null ||
782                     ! schema.md5Bytes.equals(md5Bytes)) {
783
784                     schema = new RolapSchema(
785                         key,
786                         md5Bytes,
787                         catalogUrl,
788                         catalogStr,
789                         connectInfo,
790                         dataSource);
791
792                     SoftReference JavaDoc<RolapSchema> ref = new SoftReference JavaDoc<RolapSchema>(schema);
793                     if (md5Bytes != null) {
794                         mapUrlToSchema.put(md5Bytes, ref);
795                     }
796                     mapUrlToSchema.put(key, ref);
797
798                     if (LOGGER.isDebugEnabled()) {
799                         String JavaDoc msg = "Pool.get: create schema \"" +
800                             catalogUrl +
801                             "\" with MD5";
802                         LOGGER.debug(msg);
803                     }
804
805                 } else if (LOGGER.isDebugEnabled()) {
806                     String JavaDoc msg = "Pool.get: schema \"" +
807                         catalogUrl +
808                         "\" exists already with MD5";
809                     LOGGER.debug(msg);
810                 }
811
812             } else if (hadDynProc) {
813                 // If you are using a DynamicSchemaProcessor and don't
814
// specify USE_MD5, then the RolapSchema is NOT cached.
815
schema = new RolapSchema(
816                     key,
817                     null,
818                     catalogUrl,
819                     catalogStr,
820                     connectInfo,
821                     dataSource);
822
823             } else {
824                 SoftReference JavaDoc<RolapSchema> ref = mapUrlToSchema.get(key);
825                 if (ref != null) {
826                     schema = ref.get();
827                     if (schema == null) {
828                         // clear out the reference since schema is null
829
mapUrlToSchema.remove(key);
830                     }
831                 }
832
833                 if (schema == null) {
834                     if (catalogStr == null) {
835                         schema = new RolapSchema(
836                             key,
837                             catalogUrl,
838                             connectInfo,
839                             dataSource);
840                     } else {
841                         schema = new RolapSchema(
842                             key,
843                             null,
844                             catalogUrl,
845                             catalogStr,
846                             connectInfo,
847                             dataSource);
848                     }
849
850                     mapUrlToSchema.put(key, new SoftReference JavaDoc<RolapSchema>(schema));
851
852                     if (LOGGER.isDebugEnabled()) {
853                         String JavaDoc msg = "Pool.get: create schema \"" +
854                             catalogUrl +
855                             "\"";
856                         LOGGER.debug(msg);
857                     }
858
859                 } else if (LOGGER.isDebugEnabled()) {
860                     String JavaDoc msg = "Pool.get: schema \"" +
861                         catalogUrl +
862                         "\" exists already ";
863                     LOGGER.debug(msg);
864                 }
865
866             }
867
868             return schema;
869         }
870
871         synchronized void remove(
872             final String JavaDoc catalogUrl,
873             final String JavaDoc connectionKey,
874             final String JavaDoc jdbcUser,
875             final String JavaDoc dataSourceStr)
876         {
877             final String JavaDoc key = makeKey(
878                 catalogUrl,
879                 connectionKey,
880                 jdbcUser,
881                 dataSourceStr);
882             if (LOGGER.isDebugEnabled()) {
883                 String JavaDoc msg = "Pool.remove: schema \"" +
884                      catalogUrl +
885                     "\" and datasource string \"" +
886                     dataSourceStr +
887                     "\"";
888                 LOGGER.debug(msg);
889             }
890
891             remove(key);
892         }
893
894         synchronized void remove(
895             final String JavaDoc catalogUrl,
896             final DataSource JavaDoc dataSource)
897         {
898             final String JavaDoc key = makeKey(catalogUrl, dataSource);
899             if (LOGGER.isDebugEnabled()) {
900                 String JavaDoc msg = "Pool.remove: schema \"" +
901                     catalogUrl +
902                     "\" and datasource object";
903                 LOGGER.debug(msg);
904             }
905
906             remove(key);
907         }
908
909         private void remove(String JavaDoc key) {
910             SoftReference JavaDoc<RolapSchema> ref = mapUrlToSchema.get(key);
911             if (ref != null) {
912                 RolapSchema schema = ref.get();
913                 if (schema != null) {
914                     if (schema.md5Bytes != null) {
915                         mapUrlToSchema.remove(schema.md5Bytes);
916                     }
917                     schema.finalCleanUp();
918                 }
919             }
920             mapUrlToSchema.remove(key);
921         }
922
923         synchronized void clear() {
924             if (LOGGER.isDebugEnabled()) {
925                 String JavaDoc msg = "Pool.clear: clearing all RolapSchemas";
926                 LOGGER.debug(msg);
927             }
928
929             for (SoftReference JavaDoc<RolapSchema> ref : mapUrlToSchema.values()) {
930                 if (ref != null) {
931                     RolapSchema schema = ref.get();
932                     if (schema != null) {
933                         schema.finalCleanUp();
934                     }
935                 }
936
937             }
938             mapUrlToSchema.clear();
939             JdbcSchema.clearAllDBs();
940         }
941
942         /**
943          * This returns an iterator over a copy of the RolapSchema's container.
944          *
945          * @return Iterator over RolapSchemas.
946          */

947         synchronized Iterator<RolapSchema> getRolapSchemas() {
948             List<RolapSchema> list = new ArrayList<RolapSchema>();
949             for (Iterator<SoftReference JavaDoc<RolapSchema>> it = mapUrlToSchema.values().iterator();
950                     it.hasNext(); ) {
951                 SoftReference JavaDoc<RolapSchema> ref = it.next();
952                 RolapSchema schema = ref.get();
953                 // Schema is null if already garbage collected
954
if (schema != null) {
955                     list.add(schema);
956                 } else {
957                     // We will remove the stale reference
958
try {
959                         it.remove();
960                     } catch (Exception JavaDoc ex) {
961                         // Should not happen, so
962
// warn but otherwise ignore
963
LOGGER.warn(ex);
964                     }
965                 }
966             }
967             return list.iterator();
968         }
969
970         synchronized boolean contains(RolapSchema rolapSchema) {
971             return mapUrlToSchema.containsKey(rolapSchema.key);
972         }
973
974
975         /**
976          * Creates a key with which to identify a schema in the cache.
977          */

978         private static String JavaDoc makeKey(
979             final String JavaDoc catalogUrl,
980             final String JavaDoc connectionKey,
981             final String JavaDoc jdbcUser,
982             final String JavaDoc dataSourceStr)
983         {
984             final StringBuilder JavaDoc buf = new StringBuilder JavaDoc(100);
985
986             appendIfNotNull(buf, catalogUrl);
987             appendIfNotNull(buf, connectionKey);
988             appendIfNotNull(buf, jdbcUser);
989             appendIfNotNull(buf, dataSourceStr);
990
991             final String JavaDoc key = buf.toString();
992             return key;
993         }
994
995         /**
996          * Creates a key with which to identify a schema in the cache.
997          */

998         private static String JavaDoc makeKey(
999             final String JavaDoc catalogUrl,
1000            final DataSource JavaDoc dataSource)
1001        {
1002            final StringBuilder JavaDoc buf = new StringBuilder JavaDoc(100);
1003
1004            appendIfNotNull(buf, catalogUrl);
1005            buf.append('.');
1006            buf.append("external#");
1007            buf.append(System.identityHashCode(dataSource));
1008
1009            final String JavaDoc key = buf.toString();
1010            return key;
1011        }
1012
1013        private static void appendIfNotNull(StringBuilder JavaDoc buf, String JavaDoc s) {
1014            if (s != null) {
1015                if (buf.length() > 0) {
1016                    buf.append('.');
1017                }
1018                buf.append(s);
1019            }
1020        }
1021    }
1022
1023    /**
1024     * @deprecated Use {@link mondrian.olap.CacheControl#flushSchema(String, String, String, String)}.
1025     */

1026    public static void flushSchema(
1027        final String JavaDoc catalogUrl,
1028        final String JavaDoc connectionKey,
1029        final String JavaDoc jdbcUser,
1030        String JavaDoc dataSourceStr)
1031    {
1032        Pool.instance().remove(
1033            catalogUrl,
1034            connectionKey,
1035            jdbcUser,
1036            dataSourceStr);
1037    }
1038
1039    /**
1040     * @deprecated Use {@link mondrian.olap.CacheControl#flushSchema(String, javax.sql.DataSource)}.
1041     */

1042    public static void flushSchema(
1043        final String JavaDoc catalogUrl,
1044        final DataSource JavaDoc dataSource)
1045    {
1046        Pool.instance().remove(catalogUrl, dataSource);
1047    }
1048
1049    /**
1050     * @deprecated Use {@link mondrian.olap.CacheControl#flushSchemaCache()}.
1051     */

1052    public static void clearCache() {
1053        Pool.instance().clear();
1054    }
1055
1056    public static Iterator<RolapSchema> getRolapSchemas() {
1057        return Pool.instance().getRolapSchemas();
1058    }
1059
1060    public static boolean cacheContains(RolapSchema rolapSchema) {
1061        return Pool.instance().contains(rolapSchema);
1062    }
1063
1064    public Cube lookupCube(final String JavaDoc cube, final boolean failIfNotFound) {
1065        RolapCube mdxCube = lookupCube(cube);
1066        if (mdxCube == null && failIfNotFound) {
1067            throw MondrianResource.instance().MdxCubeNotFound.ex(cube);
1068        }
1069        return mdxCube;
1070    }
1071
1072    /**
1073     * Finds a cube called 'cube' in the current catalog, or return null if no
1074     * cube exists.
1075     */

1076    protected RolapCube lookupCube(final String JavaDoc cubeName) {
1077        return mapNameToCube.get(Util.normalizeName(cubeName));
1078    }
1079
1080    /**
1081     * Returns an xmlCalculatedMember called 'calcMemberName' in the
1082     * cube called 'cubeName' or return null if no calculatedMember or
1083     * xmlCube by those name exists.
1084     */

1085    protected MondrianDef.CalculatedMember lookupXmlCalculatedMember(
1086            final String JavaDoc calcMemberName,
1087            final String JavaDoc cubeName) {
1088        String JavaDoc nameParts[] = Util.explode(calcMemberName);
1089        for (final MondrianDef.Cube cube : xmlSchema.cubes) {
1090            if (Util.equalName(cube.name, cubeName)) {
1091                for (final MondrianDef.CalculatedMember calculatedMember : cube.calculatedMembers) {
1092                    if (Util.equalName(
1093                        calculatedMember.dimension, nameParts[0]) &&
1094                        Util.equalName(
1095                            calculatedMember.name,
1096                            nameParts[nameParts.length - 1])) {
1097                        return calculatedMember;
1098                    }
1099                }
1100            }
1101        }
1102        return null;
1103    }
1104
1105    public List<RolapCube> getCubesWithStar(RolapStar star) {
1106        List<RolapCube> list = new ArrayList<RolapCube>();
1107        for (RolapCube cube : mapNameToCube.values()) {
1108            if (star == cube.getStar()) {
1109                list.add(cube);
1110            }
1111        }
1112        return list;
1113    }
1114
1115    /**
1116     * Adds a cube to the cube name map.
1117     * @see #lookupCube(String)
1118     */

1119    protected void addCube(final RolapCube cube) {
1120        mapNameToCube.put(
1121                Util.normalizeName(cube.getName()),
1122                cube);
1123    }
1124
1125    public boolean removeCube(final String JavaDoc cubeName) {
1126        final RolapCube cube =
1127            mapNameToCube.remove(Util.normalizeName(cubeName));
1128        return cube != null;
1129    }
1130
1131    public Cube[] getCubes() {
1132        Collection<RolapCube> cubes = mapNameToCube.values();
1133        return cubes.toArray(new RolapCube[cubes.size()]);
1134    }
1135
1136    public List<RolapCube> getCubeList() {
1137        return new ArrayList<RolapCube>(mapNameToCube.values());
1138    }
1139
1140    public Hierarchy[] getSharedHierarchies() {
1141        Collection<RolapHierarchy> hierarchies =
1142            mapSharedHierarchyNameToHierarchy.values();
1143        return hierarchies.toArray(new RolapHierarchy[hierarchies.size()]);
1144    }
1145
1146    RolapHierarchy getSharedHierarchy(final String JavaDoc name) {
1147/*
1148        RolapHierarchy rh = (RolapHierarchy) mapSharedHierarchyNameToHierarchy.get(name);
1149        if (rh == null) {
1150System.out.println("RolapSchema.getSharedHierarchy: "+
1151" name=" + name +
1152", hierarchy is NULL"
1153);
1154        } else {
1155System.out.println("RolapSchema.getSharedHierarchy: "+
1156" name=" + name +
1157", hierarchy=" + rh.getName()
1158);
1159        }
1160        return rh;
1161*/

1162        return mapSharedHierarchyNameToHierarchy.get(name);
1163    }
1164
1165    public NamedSet getNamedSet(String JavaDoc name) {
1166        return mapNameToSet.get(name);
1167    }
1168
1169    public Role lookupRole(final String JavaDoc role) {
1170        return mapNameToRole.get(role);
1171    }
1172
1173    public Set<String JavaDoc> roleNames() {
1174        return mapNameToRole.keySet();
1175    }
1176
1177    public FunTable getFunTable() {
1178        return funTable;
1179    }
1180
1181    public Parameter[] getParameters() {
1182        return parameterList.toArray(
1183                new Parameter[parameterList.size()]);
1184    }
1185
1186    /**
1187     * Defines a user-defined function in this table.
1188     *
1189     * <p>If the function is not valid, throws an error.
1190     *
1191     * @param name Name of the function.
1192     * @param className Name of the class which implements the function.
1193     * The class must implement {@link mondrian.spi.UserDefinedFunction}
1194     * (otherwise it is a user-error).
1195     */

1196    private void defineFunction(
1197            Map<String JavaDoc, UserDefinedFunction> mapNameToUdf,
1198            String JavaDoc name,
1199            String JavaDoc className) {
1200        // Lookup class.
1201
final Class JavaDoc<?> klass;
1202        try {
1203            klass = Class.forName(className);
1204        } catch (ClassNotFoundException JavaDoc e) {
1205            throw MondrianResource.instance().UdfClassNotFound.ex(name,
1206                    className);
1207        }
1208        // Find a constructor.
1209
Constructor JavaDoc<?> constructor;
1210        Object JavaDoc[] args = {};
1211        // 1. Look for a constructor "public Udf(String name)".
1212
try {
1213            constructor = klass.getConstructor(String JavaDoc.class);
1214            if (Modifier.isPublic(constructor.getModifiers())) {
1215                args = new Object JavaDoc[] {name};
1216            } else {
1217                constructor = null;
1218            }
1219        } catch (NoSuchMethodException JavaDoc e) {
1220            constructor = null;
1221        }
1222        // 2. Otherwise, look for a constructor "public Udf()".
1223
if (constructor == null) {
1224            try {
1225                constructor = klass.getConstructor();
1226                if (Modifier.isPublic(constructor.getModifiers())) {
1227                    args = new Object JavaDoc[] {};
1228                } else {
1229                    constructor = null;
1230                }
1231            } catch (NoSuchMethodException JavaDoc e) {
1232                constructor = null;
1233            }
1234        }
1235        // 3. Else, no constructor suitable.
1236
if (constructor == null) {
1237            throw MondrianResource.instance().UdfClassWrongIface.ex(name,
1238                    className, UserDefinedFunction.class.getName());
1239        }
1240        // Instantiate class.
1241
final UserDefinedFunction udf;
1242        try {
1243            udf = (UserDefinedFunction) constructor.newInstance(args);
1244        } catch (InstantiationException JavaDoc e) {
1245            throw MondrianResource.instance().UdfClassWrongIface.ex(name,
1246                    className, UserDefinedFunction.class.getName());
1247        } catch (IllegalAccessException JavaDoc e) {
1248            throw MondrianResource.instance().UdfClassWrongIface.ex(name,
1249                    className, UserDefinedFunction.class.getName());
1250        } catch (ClassCastException JavaDoc e) {
1251            throw MondrianResource.instance().UdfClassWrongIface.ex(name,
1252                    className, UserDefinedFunction.class.getName());
1253        } catch (InvocationTargetException JavaDoc e) {
1254            throw MondrianResource.instance().UdfClassWrongIface.ex(name,
1255                    className, UserDefinedFunction.class.getName());
1256        }
1257        // Validate function.
1258
validateFunction(udf);
1259        // Check for duplicate.
1260
UserDefinedFunction existingUdf = mapNameToUdf.get(name);
1261        if (existingUdf != null) {
1262            throw MondrianResource.instance().UdfDuplicateName.ex(name);
1263        }
1264        mapNameToUdf.put(name, udf);
1265    }
1266
1267    /**
1268     * Throws an error if a user-defined function does not adhere to the
1269     * API.
1270     */

1271    private void validateFunction(final UserDefinedFunction udf) {
1272        // Check that the name is not null or empty.
1273
final String JavaDoc udfName = udf.getName();
1274        if (udfName == null || udfName.equals("")) {
1275            throw Util.newInternal("User-defined function defined by class '" +
1276                    udf.getClass() + "' has empty name");
1277        }
1278        // It's OK for the description to be null.
1279
final String JavaDoc description = udf.getDescription();
1280        Util.discard(description);
1281        final Type[] parameterTypes = udf.getParameterTypes();
1282        for (int i = 0; i < parameterTypes.length; i++) {
1283            Type parameterType = parameterTypes[i];
1284            if (parameterType == null) {
1285                throw Util.newInternal("Invalid user-defined function '" +
1286                        udfName + "': parameter type #" + i +
1287                        " is null");
1288            }
1289        }
1290        // It's OK for the reserved words to be null or empty.
1291
final String JavaDoc[] reservedWords = udf.getReservedWords();
1292        Util.discard(reservedWords);
1293        // Test that the function returns a sensible type when given the FORMAL
1294
// types. It may still fail when we give it the ACTUAL types, but it's
1295
// impossible to check that now.
1296
final Type returnType = udf.getReturnType(parameterTypes);
1297        if (returnType == null) {
1298            throw Util.newInternal("Invalid user-defined function '" +
1299                    udfName + "': return type is null");
1300        }
1301        final Syntax syntax = udf.getSyntax();
1302        if (syntax == null) {
1303            throw Util.newInternal("Invalid user-defined function '" +
1304                    udfName + "': syntax is null");
1305        }
1306    }
1307
1308    /**
1309     * Gets a {@link MemberReader} with which to read a hierarchy. If the
1310     * hierarchy is shared (<code>sharedName</code> is not null), looks up
1311     * a reader from a cache, or creates one if necessary.
1312     *
1313     * <p>Synchronization: thread safe
1314     */

1315    synchronized MemberReader createMemberReader(
1316        final String JavaDoc sharedName,
1317        final RolapHierarchy hierarchy,
1318        final String JavaDoc memberReaderClass) {
1319
1320        MemberReader reader;
1321        if (sharedName != null) {
1322            reader = mapSharedHierarchyToReader.get(sharedName);
1323            if (reader == null) {
1324                reader = createMemberReader(hierarchy, memberReaderClass);
1325                // share, for other uses of the same shared hierarchy
1326
if (false) mapSharedHierarchyToReader.put(sharedName, reader);
1327/*
1328System.out.println("RolapSchema.createMemberReader: "+
1329"add to sharedHierName->Hier map"+
1330" sharedName=" + sharedName +
1331", hierarchy=" + hierarchy.getName() +
1332", hierarchy.dim=" + hierarchy.getDimension().getName()
1333);
1334if (mapSharedHierarchyNameToHierarchy.containsKey(sharedName)) {
1335System.out.println("RolapSchema.createMemberReader: CONTAINS NAME");
1336} else {
1337                mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy);
1338}
1339*/

1340                if (! mapSharedHierarchyNameToHierarchy.containsKey(sharedName)) {
1341                    mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy);
1342                }
1343                //mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy);
1344
} else {
1345// final RolapHierarchy sharedHierarchy = (RolapHierarchy)
1346
// mapSharedHierarchyNameToHierarchy.get(sharedName);
1347
// final RolapDimension sharedDimension = (RolapDimension)
1348
// sharedHierarchy.getDimension();
1349
// final RolapDimension dimension =
1350
// (RolapDimension) hierarchy.getDimension();
1351
// Util.assertTrue(
1352
// dimension.getGlobalOrdinal() ==
1353
// sharedDimension.getGlobalOrdinal());
1354
}
1355        } else {
1356            reader = createMemberReader(hierarchy, memberReaderClass);
1357        }
1358        return reader;
1359    }
1360
1361    /**
1362     * Creates a {@link MemberReader} with which to Read a hierarchy.
1363     */

1364    private MemberReader createMemberReader(
1365            final RolapHierarchy hierarchy,
1366            final String JavaDoc memberReaderClass) {
1367
1368        if (memberReaderClass != null) {
1369            Exception JavaDoc e2;
1370            try {
1371                Properties properties = null;
1372                Class JavaDoc<?> clazz = Class.forName(memberReaderClass);
1373                Constructor JavaDoc<?> constructor = clazz.getConstructor(
1374                    RolapHierarchy.class,
1375                    Properties.class);
1376                Object JavaDoc o = constructor.newInstance(hierarchy, properties);
1377                if (o instanceof MemberReader) {
1378                    return (MemberReader) o;
1379                } else if (o instanceof MemberSource) {
1380                    return new CacheMemberReader((MemberSource) o);
1381                } else {
1382                    throw Util.newInternal("member reader class " + clazz +
1383                                                    " does not implement " + MemberSource.class);
1384                }
1385            } catch (ClassNotFoundException JavaDoc e) {
1386                e2 = e;
1387            } catch (NoSuchMethodException JavaDoc e) {
1388                e2 = e;
1389            } catch (InstantiationException JavaDoc e) {
1390                e2 = e;
1391            } catch (IllegalAccessException JavaDoc e) {
1392                e2 = e;
1393            } catch (InvocationTargetException JavaDoc e) {
1394                e2 = e;
1395            }
1396            throw Util.newInternal(e2,
1397                    "while instantiating member reader '" + memberReaderClass);
1398        } else {
1399            SqlMemberSource source = new SqlMemberSource(hierarchy);
1400
1401            // The following code is disabled bcause
1402
// counting members is too slow. The test suite
1403
// runs faster without this. So the optimization here
1404
// is not to be too clever!
1405

1406            // Also, the CacheMemberReader is buggy.
1407

1408            int memberCount;
1409            if (false) {
1410                memberCount = source.getMemberCount();
1411            } else {
1412                memberCount = Integer.MAX_VALUE;
1413            }
1414            int largeDimensionThreshold =
1415                    MondrianProperties.instance().LargeDimensionThreshold.get();
1416
1417            return (memberCount > largeDimensionThreshold)
1418                ? new SmartMemberReader(source)
1419                : new CacheMemberReader(source);
1420        }
1421    }
1422
1423    public SchemaReader getSchemaReader() {
1424        return new RolapSchemaReader(defaultRole, this) {
1425            public Cube getCube() {
1426                throw new UnsupportedOperationException JavaDoc();
1427            }
1428        };
1429    }
1430
1431    /**
1432     * Creates a {@link DataSourceChangeListener} with which to detect changes to datasources.
1433     */

1434    private DataSourceChangeListener createDataSourceChangeListener(
1435            Util.PropertyList connectInfo) {
1436
1437        DataSourceChangeListener changeListener = null;
1438
1439        // If CatalogContent is specified in the connect string, ignore
1440
// everything else. In particular, ignore the dynamic schema
1441
// processor.
1442
String JavaDoc dataSourceChangeListenerStr = connectInfo.get(
1443            RolapConnectionProperties.DataSourceChangeListener.name());
1444
1445        if ( ! Util.isEmpty(dataSourceChangeListenerStr)) {
1446            try {
1447
1448                Class JavaDoc<?> clazz = Class.forName(dataSourceChangeListenerStr);
1449                Constructor JavaDoc<?> constructor = clazz.getConstructor();
1450                changeListener = (DataSourceChangeListener)constructor.newInstance();
1451
1452/* final Class<DataSourceChangeListener> clazz =
1453                    (Class<DataSourceChangeListener>) Class.forName(dataSourceChangeListenerStr);
1454                final Constructor<DataSourceChangeListener> ctor =
1455                    clazz.getConstructor();
1456                changeListener = ctor.newInstance(); */

1457
1458            } catch (Exception JavaDoc e) {
1459                throw Util.newError(e, "loading DataSourceChangeListener "
1460                    + dataSourceChangeListenerStr);
1461            }
1462
1463            if (LOGGER.isDebugEnabled()) {
1464                String JavaDoc msg = "RolapSchema.createDataSourceChangeListener: create datasource change listener \"" +
1465                dataSourceChangeListenerStr;
1466
1467                LOGGER.debug(msg);
1468            }
1469        }
1470        return changeListener;
1471    }
1472
1473
1474    /**
1475     * Connection for purposes of parsing and validation. Careful! It won't
1476     * have the correct locale or access-control profile.
1477     */

1478    public RolapConnection getInternalConnection() {
1479        return internalConnection;
1480    }
1481
1482    private Role createDefaultRole() {
1483        Role role = new Role();
1484        role.grant(this, Access.ALL);
1485        role.makeImmutable();
1486        return role;
1487    }
1488
1489    private RolapStar makeRolapStar(final MondrianDef.Relation fact) {
1490        DataSource JavaDoc dataSource = getInternalConnection().getDataSource();
1491        return new RolapStar(this, dataSource, fact);
1492    }
1493
1494    /**
1495     * <code>RolapStarRegistry</code> is a registry for {@link RolapStar}s.
1496     */

1497    class RolapStarRegistry {
1498        private final Map<String JavaDoc, RolapStar> stars = new HashMap<String JavaDoc, RolapStar>();
1499
1500        RolapStarRegistry() {
1501        }
1502
1503        /**
1504         * Looks up a {@link RolapStar}, creating it if it does not exist.
1505         *
1506         * <p> {@link RolapStar.Table#addJoin} works in a similar way.
1507         */

1508        synchronized RolapStar getOrCreateStar(final MondrianDef.Relation fact) {
1509            String JavaDoc factTableName = fact.toString();
1510            RolapStar star = stars.get(factTableName);
1511            if (star == null) {
1512                star = makeRolapStar(fact);
1513                stars.put(factTableName, star);
1514            }
1515            return star;
1516        }
1517        synchronized RolapStar getStar(final String JavaDoc factTableName) {
1518            return stars.get(factTableName);
1519        }
1520        synchronized Collection<RolapStar> getStars() {
1521            return stars.values();
1522        }
1523    }
1524
1525    private RolapStarRegistry rolapStarRegistry = new RolapStarRegistry();
1526
1527    public RolapStarRegistry getRolapStarRegistry() {
1528        return rolapStarRegistry;
1529    }
1530
1531    /**
1532     * Function table which contains all of the user-defined functions in this
1533     * schema, plus all of the standard functions.
1534     */

1535    class RolapSchemaFunctionTable extends FunTableImpl {
1536        private final List<UserDefinedFunction> udfList;
1537
1538        RolapSchemaFunctionTable(Collection<UserDefinedFunction> udfs) {
1539            udfList = new ArrayList<UserDefinedFunction>(udfs);
1540        }
1541
1542        protected void defineFunctions() {
1543            final FunTable globalFunTable = GlobalFunTable.instance();
1544            for (String JavaDoc reservedWord : globalFunTable.getReservedWords()) {
1545                defineReserved(reservedWord);
1546            }
1547            for (Resolver resolver : globalFunTable.getResolvers()) {
1548                define(resolver);
1549            }
1550            for (UserDefinedFunction udf : udfList) {
1551                define(new UdfResolver(udf));
1552            }
1553        }
1554
1555
1556        public List<FunInfo> getFunInfoList() {
1557            return Collections.unmodifiableList(this.funInfoList);
1558        }
1559    }
1560
1561    public RolapStar getStar(final String JavaDoc factTableName) {
1562      return getRolapStarRegistry().getStar(factTableName);
1563    }
1564
1565    public Collection<RolapStar> getStars() {
1566      return getRolapStarRegistry().getStars();
1567    }
1568
1569    /**
1570     * @deprecated Use {@link mondrian.olap.CacheControl#flush(mondrian.olap.CacheControl.CellRegion)}
1571     */

1572    public void flushRolapStarCaches(boolean forced) {
1573        for (RolapStar star : getStars()) {
1574            // this will only flush the star's aggregate cache if
1575
// 1) DisableCaching is true or 2) the star's cube has
1576
// cacheAggregations set to false in the schema.
1577
star.clearCachedAggregations(forced);
1578        }
1579    }
1580    /**
1581     * Check if there are modifications in the aggregations cache
1582     */

1583    public void checkAggregateModifications() {
1584        for (RolapStar star : getStars()) {
1585            star.checkAggregateModifications();
1586        }
1587    }
1588    /**
1589     * Push all modifications of the aggregations to global cache,
1590     * so other queries can start using the new cache
1591     */

1592    public void pushAggregateModificationsToGlobalCache() {
1593        for (RolapStar star : getStars()) {
1594            star.pushAggregateModificationsToGlobalCache();
1595        }
1596    }
1597
1598    /**
1599     * @deprecated Use {@link mondrian.olap.CacheControl#flush(mondrian.olap.CacheControl.CellRegion)}
1600     */

1601    public static void flushAllRolapStarCachedAggregations() {
1602        for (Iterator<RolapSchema> itSchemas = RolapSchema.getRolapSchemas();
1603                itSchemas.hasNext(); ) {
1604
1605            RolapSchema schema = itSchemas.next();
1606            schema.flushRolapStarCaches(true);
1607        }
1608    }
1609
1610    final RolapNativeRegistry nativeRegistry = new RolapNativeRegistry();
1611
1612    RolapNativeRegistry getNativeRegistry() {
1613        return nativeRegistry;
1614    }
1615
1616    /** Generator for {@link RolapDimension#globalOrdinal}. */
1617    private int nextDimensionOrdinal = 1; // 0 is reserved for [Measures]
1618

1619    public synchronized int getNextDimensionOrdinal() {
1620        return nextDimensionOrdinal++;
1621    }
1622
1623
1624    /**
1625     * @return Returns the dataSourceChangeListener.
1626     */

1627    public DataSourceChangeListener getDataSourceChangeListener() {
1628        return dataSourceChangeListener;
1629    }
1630
1631
1632    /**
1633     * @param dataSourceChangeListener The dataSourceChangeListener to set.
1634     */

1635    public void setDataSourceChangeListener(
1636            DataSourceChangeListener dataSourceChangeListener) {
1637        this.dataSourceChangeListener = dataSourceChangeListener;
1638    }
1639
1640}
1641
1642// End RolapSchema.java
1643
Popular Tags