001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapSchemaReader.java#54 $ 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) 2003-2008 Julian Hyde 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 // 010 // jhyde, Feb 24, 2003 011 */ 012 package mondrian.rolap; 013 014 import java.util.ArrayList; 015 import java.util.Arrays; 016 import java.util.Collections; 017 import java.util.HashMap; 018 import java.util.List; 019 import java.util.Map; 020 021 import javax.sql.DataSource; 022 023 import mondrian.olap.*; 024 import mondrian.olap.type.StringType; 025 import mondrian.rolap.sql.TupleConstraint; 026 import mondrian.rolap.sql.MemberChildrenConstraint; 027 import mondrian.calc.Calc; 028 import mondrian.calc.ExpCompiler; 029 import mondrian.calc.DummyExp; 030 import mondrian.calc.impl.AbstractCalc; 031 import mondrian.calc.impl.GenericCalc; 032 033 import org.apache.log4j.Logger; 034 import org.eigenbase.util.property.Property; 035 036 /** 037 * A <code>RolapSchemaReader</code> allows you to read schema objects while 038 * observing the access-control profile specified by a given role. 039 * 040 * @author jhyde 041 * @since Feb 24, 2003 042 * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapSchemaReader.java#54 $ 043 */ 044 public abstract class RolapSchemaReader 045 implements SchemaReader, RolapNativeSet.SchemaReaderWithMemberReaderAvailable { 046 private final Role role; 047 private final Map<Hierarchy, MemberReader> hierarchyReaders = 048 new HashMap<Hierarchy, MemberReader>(); 049 private final RolapSchema schema; 050 private final SqlConstraintFactory sqlConstraintFactory = 051 SqlConstraintFactory.instance(); 052 private static final Logger LOGGER = 053 Logger.getLogger(RolapSchemaReader.class); 054 055 RolapSchemaReader(Role role, RolapSchema schema) { 056 assert role != null : "precondition: role != null"; 057 this.role = role; 058 this.schema = schema; 059 } 060 061 public Role getRole() { 062 return role; 063 } 064 065 public List<Member> getHierarchyRootMembers(Hierarchy hierarchy) { 066 final Role.HierarchyAccess hierarchyAccess = 067 role.getAccessDetails(hierarchy); 068 final Level[] levels = hierarchy.getLevels(); 069 final Level firstLevel; 070 if (hierarchyAccess == null) { 071 firstLevel = levels[0]; 072 } else { 073 firstLevel = levels[hierarchyAccess.getTopLevelDepth()]; 074 } 075 return getLevelMembers(firstLevel, true); 076 } 077 078 public synchronized MemberReader getMemberReader(Hierarchy hierarchy) { 079 MemberReader memberReader = hierarchyReaders.get(hierarchy); 080 if (memberReader == null) { 081 memberReader = ((RolapHierarchy) hierarchy).createMemberReader(role); 082 hierarchyReaders.put(hierarchy, memberReader); 083 } 084 return memberReader; 085 } 086 087 public Member substitute(Member member) { 088 final MemberReader memberReader = 089 getMemberReader(member.getHierarchy()); 090 return memberReader.substitute((RolapMember) member); 091 } 092 093 public void getMemberRange( 094 Level level, Member startMember, Member endMember, List<Member> list) 095 { 096 getMemberReader(level.getHierarchy()).getMemberRange( 097 (RolapLevel) level, (RolapMember) startMember, 098 (RolapMember) endMember, Util.<RolapMember>cast(list)); 099 } 100 101 public int compareMembersHierarchically(Member m1, Member m2) { 102 RolapMember member1 = (RolapMember) m1; 103 RolapMember member2 = (RolapMember) m2; 104 final RolapHierarchy hierarchy = member1.getHierarchy(); 105 Util.assertPrecondition(hierarchy == m2.getHierarchy()); 106 return getMemberReader(hierarchy).compare(member1, member2, true); 107 } 108 109 public Member getMemberParent(Member member) { 110 return getMemberReader(member.getHierarchy()).getMemberParent( 111 (RolapMember) member); 112 } 113 114 public int getMemberDepth(Member member) { 115 final Role.HierarchyAccess hierarchyAccess = role.getAccessDetails(member.getHierarchy()); 116 if (hierarchyAccess != null) { 117 final int memberDepth = member.getLevel().getDepth(); 118 final int topLevelDepth = hierarchyAccess.getTopLevelDepth(); 119 return memberDepth - topLevelDepth; 120 } else if (((RolapLevel) member.getLevel()).isParentChild()) { 121 // For members of parent-child hierarchy, members in the same level 122 // may have different depths. 123 int depth = 0; 124 for (Member m = member.getParentMember(); 125 m != null; 126 m = m.getParentMember()) 127 { 128 depth++; 129 } 130 return depth; 131 } else { 132 return member.getLevel().getDepth(); 133 } 134 } 135 136 137 public List<Member> getMemberChildren(Member member) { 138 return getMemberChildren(member, null); 139 } 140 141 public List<Member> getMemberChildren(Member member, Evaluator context) { 142 MemberChildrenConstraint constraint = 143 sqlConstraintFactory.getMemberChildrenConstraint(context); 144 List<RolapMember> memberList = 145 internalGetMemberChildren(member, constraint); 146 return Util.cast(memberList); 147 } 148 149 private List<RolapMember> internalGetMemberChildren( 150 Member member, MemberChildrenConstraint constraint) { 151 List<RolapMember> children = new ArrayList<RolapMember>(); 152 final Hierarchy hierarchy = member.getHierarchy(); 153 final MemberReader memberReader = getMemberReader(hierarchy); 154 memberReader.getMemberChildren( 155 (RolapMember) member, children, constraint); 156 return children; 157 } 158 159 /** 160 * check, whether members children are cached, and 161 * if yes - return children count 162 * if no - return -1 163 */ 164 public int getChildrenCountFromCache(Member member) { 165 final Hierarchy hierarchy = member.getHierarchy(); 166 final MemberReader memberReader = getMemberReader(hierarchy); 167 if (memberReader instanceof 168 RolapCubeHierarchy.RolapCubeHierarchyMemberReader) { 169 List list = 170 ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader)memberReader) 171 .getRolapCubeMemberCacheHelper() 172 .getChildrenFromCache((RolapMember)member, null); 173 if (list == null) { 174 return -1; 175 } 176 return list.size(); 177 } 178 179 if (memberReader instanceof SmartMemberReader) { 180 List list = ((SmartMemberReader)memberReader).getMemberCache() 181 .getChildrenFromCache((RolapMember)member, null); 182 if (list == null) { 183 return -1; 184 } 185 return list.size(); 186 } 187 if (!(memberReader instanceof MemberCache)) { 188 return -1; 189 } 190 List list = ((MemberCache)memberReader) 191 .getChildrenFromCache((RolapMember)member, null); 192 if (list == null) { 193 return -1; 194 } 195 return list.size(); 196 } 197 198 /** 199 * Returns number of members in a level, 200 * if the information can be retrieved from cache. 201 * Otherwise {@link Integer#MIN_VALUE}. 202 * 203 * @param level Level 204 * @return number of members in level 205 */ 206 private int getLevelCardinalityFromCache(Level level) { 207 final Hierarchy hierarchy = level.getHierarchy(); 208 final MemberReader memberReader = getMemberReader(hierarchy); 209 if (memberReader instanceof 210 RolapCubeHierarchy.RolapCubeHierarchyMemberReader) { 211 final MemberCacheHelper cache = 212 ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader) 213 memberReader).getRolapCubeMemberCacheHelper(); 214 if (cache == null) { 215 return Integer.MIN_VALUE; 216 } 217 final List<RolapMember> list = 218 cache.getLevelMembersFromCache( 219 (RolapLevel) level, null); 220 if (list == null) { 221 return Integer.MIN_VALUE; 222 } 223 return list.size(); 224 } 225 226 if (memberReader instanceof SmartMemberReader) { 227 List<RolapMember> list = 228 ((SmartMemberReader) memberReader) 229 .getMemberCache() 230 .getLevelMembersFromCache( 231 (RolapLevel) level, null); 232 if (list == null) { 233 return Integer.MIN_VALUE; 234 } 235 return list.size(); 236 } 237 238 if (memberReader instanceof MemberCache) { 239 List<RolapMember> list = 240 ((MemberCache) memberReader) 241 .getLevelMembersFromCache( 242 (RolapLevel) level, null); 243 if (list == null) { 244 return Integer.MIN_VALUE; 245 } 246 return list.size(); 247 } 248 249 return Integer.MIN_VALUE; 250 } 251 252 public int getLevelCardinality( 253 Level level, 254 boolean approximate, 255 boolean materialize) 256 { 257 if (!this.role.canAccess(level)) { 258 return 1; 259 } 260 261 int rowCount = Integer.MIN_VALUE; 262 if (approximate) { 263 // See if the schema has an approximation. 264 rowCount = level.getApproxRowCount(); 265 } 266 267 if (rowCount == Integer.MIN_VALUE) { 268 // See if the precise row count is available in cache. 269 rowCount = getLevelCardinalityFromCache(level); 270 } 271 272 if (rowCount == Integer.MIN_VALUE) { 273 if (materialize) { 274 // Either the approximate row count hasn't been set, 275 // or they want the precise row count. 276 final MemberReader memberReader = 277 getMemberReader(level.getHierarchy()); 278 rowCount = 279 memberReader.getLevelMemberCount((RolapLevel) level); 280 // Cache it for future. 281 ((RolapLevel) level).setApproxRowCount(rowCount); 282 } 283 } 284 return rowCount; 285 } 286 287 public List<Member> getMemberChildren(List<Member> members) { 288 return getMemberChildren(members, null); 289 } 290 291 public List<Member> getMemberChildren( 292 List<Member> members, 293 Evaluator context) 294 { 295 if (members.size() == 0) { 296 return Collections.emptyList(); 297 } else { 298 MemberChildrenConstraint constraint = 299 sqlConstraintFactory.getMemberChildrenConstraint(context); 300 final Hierarchy hierarchy = members.get(0).getHierarchy(); 301 final MemberReader memberReader = getMemberReader(hierarchy); 302 final List<RolapMember> rolapMemberList = Util.cast(members); 303 final List<RolapMember> children = new ArrayList<RolapMember>(); 304 memberReader.getMemberChildren( 305 rolapMemberList, 306 children, 307 constraint); 308 return Util.cast(children); 309 } 310 } 311 312 public abstract Cube getCube(); 313 314 public OlapElement getElementChild(OlapElement parent, Id.Segment name) { 315 return getElementChild(parent, name, MatchType.EXACT); 316 } 317 318 public OlapElement getElementChild( 319 OlapElement parent, Id.Segment name, MatchType matchType) 320 { 321 return parent.lookupChild(this, name, matchType); 322 } 323 324 public final Member getMemberByUniqueName( 325 List<Id.Segment> uniqueNameParts, 326 boolean failIfNotFound) 327 { 328 return getMemberByUniqueName( 329 uniqueNameParts, failIfNotFound, MatchType.EXACT); 330 } 331 332 public Member getMemberByUniqueName( 333 List<Id.Segment> uniqueNameParts, 334 boolean failIfNotFound, 335 MatchType matchType) 336 { 337 // In general, this schema reader doesn't have a cube, so we cannot 338 // start looking up members. 339 return null; 340 } 341 342 public OlapElement lookupCompound( 343 OlapElement parent, 344 List<Id.Segment> names, 345 boolean failIfNotFound, 346 int category) 347 { 348 return lookupCompound( 349 parent, names, failIfNotFound, category, MatchType.EXACT); 350 } 351 352 public OlapElement lookupCompound( 353 OlapElement parent, 354 List<Id.Segment> names, 355 boolean failIfNotFound, 356 int category, 357 MatchType matchType) 358 { 359 return Util.lookupCompound( 360 this, parent, names, failIfNotFound, category, matchType); 361 } 362 363 public Member lookupMemberChildByName(Member parent, Id.Segment childName) 364 { 365 return lookupMemberChildByName(parent, childName, MatchType.EXACT); 366 } 367 368 public Member lookupMemberChildByName( 369 Member parent, Id.Segment childName, MatchType matchType) 370 { 371 LOGGER.debug("looking for child \"" + childName + "\" of " + parent); 372 assert !(parent instanceof RolapHierarchy.LimitedRollupMember); 373 try { 374 MemberChildrenConstraint constraint; 375 if (matchType == MatchType.EXACT) { 376 constraint = sqlConstraintFactory.getChildByNameConstraint( 377 (RolapMember) parent, childName); 378 } else { 379 constraint = 380 sqlConstraintFactory.getMemberChildrenConstraint(null); 381 } 382 List<RolapMember> children = 383 internalGetMemberChildren(parent, constraint); 384 if (children.size() > 0) { 385 return 386 RolapUtil.findBestMemberMatch( 387 children, 388 (RolapMember) parent, 389 children.get(0).getLevel(), 390 childName, 391 matchType, 392 true); 393 } 394 } catch (NumberFormatException e) { 395 // this was thrown in SqlQuery#quote(boolean numeric, Object value). This happens when 396 // Mondrian searches for unqualified Olap Elements like [Month], because it tries to look up 397 // a member with that name in all dimensions. Then it generates for example 398 // "select .. from time where year = Month" which will result in a NFE because 399 // "Month" can not be parsed as a number. The real bug is probably, that Mondrian 400 // looks at members at all. 401 // 402 // @see RolapCube#lookupChild() 403 LOGGER.debug("NumberFormatException in lookupMemberChildByName for parent = \"" + parent + "\", childName=\"" + childName + "\", exception: " + e.getMessage()); 404 } 405 return null; 406 } 407 408 public Member getCalculatedMember(List<Id.Segment> nameParts) { 409 // There are no calculated members defined against a schema. 410 return null; 411 } 412 413 public NamedSet getNamedSet(List<Id.Segment> nameParts) { 414 if (nameParts.size() != 1) { 415 return null; 416 } 417 final String name = nameParts.get(0).name; 418 return schema.getNamedSet(name); 419 } 420 421 public Member getLeadMember(Member member, int n) { 422 final MemberReader memberReader = getMemberReader(member.getHierarchy()); 423 return memberReader.getLeadMember((RolapMember) member, n); 424 } 425 426 public List<Member> getLevelMembers( 427 Level level, 428 boolean includeCalculated) 429 { 430 List<Member> members = getLevelMembers(level, null); 431 if (!includeCalculated) { 432 members = SqlConstraintUtils.removeCalculatedMembers(members); 433 } 434 return members; 435 } 436 437 public List<Member> getLevelMembers(Level level, Evaluator context) { 438 boolean[] satisfied = {false}; 439 TupleConstraint constraint = 440 sqlConstraintFactory.getLevelMembersConstraint( 441 context, 442 new Level [] { level }, 443 satisfied); 444 final MemberReader memberReader = 445 getMemberReader(level.getHierarchy()); 446 List<RolapMember> membersInLevel = 447 memberReader.getMembersInLevel( 448 (RolapLevel) level, 0, Integer.MAX_VALUE, constraint); 449 if (!satisfied[0]) { 450 // Could not satisfy the constraint by generating SQL. Apply the 451 // non-empty constraint manually. 452 final Evaluator evaluator = context.push(); 453 List<RolapMember> allMembersInLevel = membersInLevel; 454 membersInLevel = new ArrayList<RolapMember>(); 455 for (RolapMember member : allMembersInLevel) { 456 evaluator.setContext(member); 457 if (evaluator.evaluateCurrent() != null) { 458 membersInLevel.add(member); 459 } 460 } 461 } 462 return Util.cast(membersInLevel); 463 } 464 465 public List<Level> getHierarchyLevels(Hierarchy hierarchy) { 466 assert hierarchy != null; 467 final Role.HierarchyAccess hierarchyAccess = 468 role.getAccessDetails(hierarchy); 469 final Level[] levels = hierarchy.getLevels(); 470 if (hierarchyAccess == null) { 471 return Arrays.asList(levels); 472 } 473 Level topLevel = levels[hierarchyAccess.getTopLevelDepth()]; 474 Level bottomLevel = levels[hierarchyAccess.getBottomLevelDepth()]; 475 List<Level> restrictedLevels = 476 Arrays.asList(levels).subList( 477 topLevel.getDepth(), bottomLevel.getDepth() + 1); 478 assert restrictedLevels.size() >= 1 : "postcondition"; 479 return restrictedLevels; 480 } 481 482 public Member getHierarchyDefaultMember(Hierarchy hierarchy) { 483 assert hierarchy != null; 484 // If the whole hierarchy is inaccessible, return the intrinsic default 485 // member. This is important to construct a evaluator. 486 if (role.getAccess(hierarchy) == Access.NONE) { 487 return hierarchy.getDefaultMember(); 488 } 489 return getMemberReader(hierarchy).getDefaultMember(); 490 } 491 492 public boolean isDrillable(Member member) { 493 final RolapLevel level = (RolapLevel) member.getLevel(); 494 if (level.getParentExp() != null) { 495 // This is a parent-child level, so its children, if any, come from 496 // the same level. 497 // 498 // todo: More efficient implementation 499 return getMemberChildren(member).size() > 0; 500 } else { 501 // This is a regular level. It has children iff there is a lower 502 // level. 503 final Level childLevel = level.getChildLevel(); 504 return (childLevel != null) && 505 (role.getAccess(childLevel) != Access.NONE); 506 } 507 } 508 509 public boolean isVisible(Member member) { 510 return !member.isHidden() && role.canAccess(member); 511 } 512 513 public Cube[] getCubes() { 514 List<RolapCube> cubes = schema.getCubeList(); 515 List<Cube> visibleCubes = new ArrayList<Cube>(cubes.size()); 516 517 for (Cube cube : cubes) { 518 if (role.canAccess(cube)) { 519 visibleCubes.add(cube); 520 } 521 } 522 523 return visibleCubes.toArray(new Cube[visibleCubes.size()]); 524 } 525 526 public List<Member> getCalculatedMembers(Hierarchy hierarchy) { 527 return Collections.emptyList(); 528 } 529 530 public List<Member> getCalculatedMembers(Level level) { 531 return Collections.emptyList(); 532 } 533 534 public List<Member> getCalculatedMembers() { 535 return Collections.emptyList(); 536 } 537 538 public NativeEvaluator getNativeSetEvaluator( 539 FunDef fun, Exp[] args, Evaluator evaluator, Calc calc) { 540 RolapEvaluator revaluator = (RolapEvaluator) 541 AbstractCalc.simplifyEvaluator(calc, evaluator); 542 return schema.getNativeRegistry().createEvaluator(revaluator, fun, args); 543 } 544 545 public Parameter getParameter(String name) { 546 // Scan through schema parameters. 547 for (RolapSchemaParameter parameter : schema.parameterList) { 548 if (Util.equalName(parameter.getName(), name)) { 549 return parameter; 550 } 551 } 552 553 // Scan through mondrian and system properties. 554 List<Property> propertyList = MondrianProperties.instance().getPropertyList(); 555 for (Property property : propertyList) { 556 if (property.getPath().equals(name)) { 557 return new SystemPropertyParameter(name, false); 558 } 559 } 560 if (System.getProperty(name) != null) { 561 return new SystemPropertyParameter(name, true); 562 } 563 564 return null; 565 } 566 567 public DataSource getDataSource() { 568 return schema.getInternalConnection().getDataSource(); 569 } 570 571 RolapSchema getSchema() { 572 return schema; 573 } 574 575 /** 576 * Implementation of {@link Parameter} which is sourced from system 577 * propertes (see {@link System#getProperties()} or mondrian properties 578 * (see {@link MondrianProperties}. 579 * 580 * <p>The name of the property is the same as the key into the 581 * {@link java.util.Properties} object; for example "java.version" or 582 * "mondrian.trace.level". 583 */ 584 private static class SystemPropertyParameter 585 extends ParameterImpl 586 { 587 /** 588 * true if source is a system property; 589 * false if source is a mondrian property. 590 */ 591 private final boolean system; 592 /** 593 * Definition of mondrian property, or null if system property. 594 */ 595 private final Property propertyDefinition; 596 597 public SystemPropertyParameter(String name, boolean system) { 598 super(name, 599 Literal.nullValue, 600 "System property '" + name + "'", 601 new StringType()); 602 this.system = system; 603 this.propertyDefinition = 604 system ? null : 605 MondrianProperties.instance().getPropertyDefinition(name); 606 } 607 608 public Scope getScope() { 609 return Scope.System; 610 } 611 612 public boolean isModifiable() { 613 return false; 614 } 615 616 public Calc compile(ExpCompiler compiler) { 617 return new GenericCalc(new DummyExp(getType())) { 618 public Calc[] getCalcs() { 619 return new Calc[0]; 620 } 621 622 public Object evaluate(Evaluator evaluator) { 623 if (system) { 624 final String name = SystemPropertyParameter.this.getName(); 625 return System.getProperty(name); 626 } else { 627 return propertyDefinition.stringValue(); 628 } 629 } 630 }; 631 } 632 } 633 } 634 635 // End RolapSchemaReader.java