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