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