001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/CacheControlImpl.java#11 $ 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) 2006-2008 Julian Hyde and others. 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.rolap; 011 012 import mondrian.olap.*; 013 import mondrian.resource.MondrianResource; 014 import mondrian.olap.CacheControl; 015 016 import javax.sql.DataSource; 017 import java.util.*; 018 import java.io.PrintWriter; 019 020 import org.eigenbase.util.property.BooleanProperty; 021 022 /** 023 * Implementation of {@link CacheControl} API. 024 * 025 * @author jhyde 026 * @version $Id: //open/mondrian/src/main/mondrian/rolap/CacheControlImpl.java#11 $ 027 * @since Sep 27, 2006 028 */ 029 public class CacheControlImpl implements CacheControl { 030 031 /** 032 * Object to lock before making changes to the member cache. 033 * 034 * <p>The "member cache" is a figure of speech: each RolapHierarchy has its 035 * own MemberCache object. But to provide transparently serialized access 036 * to the "member cache" via the interface CacheControl, provide a common 037 * lock here. 038 * 039 * <p>NOTE: static member is a little too wide a scope for this lock, 040 * because in theory a JVM can contain multiple independent instances of 041 * mondrian. 042 */ 043 private static final Object MEMBER_CACHE_LOCK = new Object(); 044 045 // cell cache control 046 public CellRegion createMemberRegion(Member member, boolean descendants) { 047 if (member == null) { 048 throw new NullPointerException(); 049 } 050 final ArrayList<Member> list = new ArrayList<Member>(); 051 list.add(member); 052 return new MemberCellRegion(list, descendants); 053 } 054 055 public CellRegion createMemberRegion( 056 boolean lowerInclusive, 057 Member lowerMember, 058 boolean upperInclusive, 059 Member upperMember, 060 boolean descendants) 061 { 062 if (lowerMember == null) { 063 lowerInclusive = false; 064 } 065 if (upperMember == null) { 066 upperInclusive = false; 067 } 068 return new MemberRangeCellRegion( 069 (RolapMember) lowerMember, lowerInclusive, 070 (RolapMember) upperMember, upperInclusive, 071 descendants); 072 } 073 074 public CellRegion createCrossjoinRegion(CellRegion... regions) { 075 assert regions != null; 076 assert regions.length >= 2; 077 final HashSet<Dimension> set = new HashSet<Dimension>(); 078 final List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 079 for (CellRegion region : regions) { 080 int prevSize = set.size(); 081 List<Dimension> dimensionality = region.getDimensionality(); 082 set.addAll(dimensionality); 083 if (set.size() < prevSize + dimensionality.size()) { 084 throw MondrianResource.instance(). 085 CacheFlushCrossjoinDimensionsInCommon.ex( 086 getDimensionalityList(regions)); 087 } 088 089 flattenCrossjoin((CellRegionImpl) region, list); 090 } 091 return new CrossjoinCellRegion(list); 092 } 093 094 // Returns e.g. "'[[Product]]', '[[Time], [Product]]'" 095 private String getDimensionalityList(CellRegion[] regions) { 096 StringBuilder buf = new StringBuilder(); 097 int k = 0; 098 for (CellRegion region : regions) { 099 if (k++ > 0) { 100 buf.append(", "); 101 } 102 buf.append("'"); 103 buf.append(region.getDimensionality().toString()); 104 buf.append("'"); 105 } 106 return buf.toString(); 107 } 108 109 public CellRegion createUnionRegion(CellRegion... regions) 110 { 111 if (regions == null) { 112 throw new NullPointerException(); 113 } 114 if (regions.length < 2) { 115 throw new IllegalArgumentException(); 116 } 117 final List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 118 for (CellRegion region : regions) { 119 if (!region.getDimensionality().equals( 120 regions[0].getDimensionality())) { 121 throw MondrianResource.instance(). 122 CacheFlushUnionDimensionalityMismatch.ex( 123 regions[0].getDimensionality().toString(), 124 region.getDimensionality().toString()); 125 } 126 list.add((CellRegionImpl) region); 127 } 128 return new UnionCellRegion(list); 129 } 130 131 public CellRegion createMeasuresRegion(Cube cube) { 132 final Dimension measuresDimension = cube.getDimensions()[0]; 133 final List<Member> measures = 134 cube.getSchemaReader(null).getLevelMembers( 135 measuresDimension.getHierarchy().getLevels()[0], 136 false); 137 return new MemberCellRegion(measures, false); 138 } 139 140 public void flush(CellRegion region) { 141 final List<Dimension> dimensionality = region.getDimensionality(); 142 boolean found = false; 143 for (Dimension dimension : dimensionality) { 144 if (dimension.isMeasures()) { 145 found = true; 146 break; 147 } 148 } 149 if (!found) { 150 throw MondrianResource.instance(). 151 CacheFlushRegionMustContainMembers.ex(); 152 } 153 final UnionCellRegion union = normalize((CellRegionImpl) region); 154 for (CellRegionImpl cellRegion : union.regions) { 155 // Figure out the bits. 156 flushNonUnion(cellRegion); 157 } 158 } 159 160 /** 161 * Flushes a list of cell regions. 162 * 163 * @param cellRegionList List of cell regions 164 */ 165 protected void flushRegionList(List<CellRegion> cellRegionList) { 166 final CellRegion cellRegion; 167 switch (cellRegionList.size()) { 168 case 0: 169 return; 170 case 1: 171 cellRegion = cellRegionList.get(0); 172 break; 173 default: 174 final CellRegion[] cellRegions = 175 cellRegionList.toArray(new CellRegion[cellRegionList.size()]); 176 cellRegion = createUnionRegion(cellRegions); 177 break; 178 } 179 flush(cellRegion); 180 } 181 182 public void trace(String message) { 183 // ignore message 184 } 185 186 public void flushSchemaCache() { 187 RolapSchema.Pool.instance().clear(); 188 } 189 190 // todo: document 191 public void flushSchema( 192 String catalogUrl, 193 String connectionKey, 194 String jdbcUser, 195 String dataSourceStr) 196 { 197 RolapSchema.Pool.instance().remove( 198 catalogUrl, 199 connectionKey, 200 jdbcUser, 201 dataSourceStr); 202 } 203 204 // todo: document 205 public void flushSchema( 206 String catalogUrl, 207 DataSource dataSource) 208 { 209 RolapSchema.Pool.instance().remove( 210 catalogUrl, 211 dataSource); 212 } 213 214 /** 215 * Flushes the given RolapSchema instance from the pool 216 * 217 * @param schema RolapSchema 218 */ 219 public void flushSchema(Schema schema) { 220 if (RolapSchema.class.isInstance(schema)) { 221 RolapSchema.Pool.instance().remove((RolapSchema)schema); 222 } else { 223 throw new UnsupportedOperationException(schema.getClass().getName()+ 224 " cannot be flushed"); 225 } 226 } 227 228 protected void flushNonUnion(CellRegion region) { 229 throw new UnsupportedOperationException(); 230 } 231 232 /** 233 * Normalizes a CellRegion into a union of crossjoins of member regions. 234 * 235 * @param region Region 236 * @return normalized region 237 */ 238 UnionCellRegion normalize(CellRegionImpl region) { 239 // Search for Union within a Crossjoin. 240 // Crossjoin(a1, a2, Union(r1, r2, r3), a4) 241 // becomes 242 // Union( 243 // Crossjoin(a1, a2, r1, a4), 244 // Crossjoin(a1, a2, r2, a4), 245 // Crossjoin(a1, a2, r3, a4)) 246 247 // First, decompose into a flat list of non-union regions. 248 List<CellRegionImpl> nonUnionList = new LinkedList<CellRegionImpl>(); 249 flattenUnion(region, nonUnionList); 250 251 for (int i = 0; i < nonUnionList.size(); i++) { 252 while (true) { 253 CellRegionImpl nonUnionRegion = nonUnionList.get(i); 254 UnionCellRegion firstUnion = findFirstUnion(nonUnionRegion); 255 if (firstUnion == null) { 256 break; 257 } 258 List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 259 for (CellRegionImpl unionComponent : firstUnion.regions) { 260 // For each unionComponent in (r1, r2, r3), 261 // create Crossjoin(a1, a2, r1, a4). 262 CellRegionImpl cj = 263 copyReplacing( 264 nonUnionRegion, 265 firstUnion, 266 unionComponent); 267 list.add(cj); 268 } 269 // Replace one element which contained a union with several 270 // which contain one fewer union. (Double-linked list helps 271 // here.) 272 nonUnionList.remove(i); 273 nonUnionList.addAll(i, list); 274 } 275 } 276 return new UnionCellRegion(nonUnionList); 277 } 278 279 private CellRegionImpl copyReplacing( 280 CellRegionImpl region, 281 CellRegionImpl seek, 282 CellRegionImpl replacement) 283 { 284 if (region == seek) { 285 return replacement; 286 } 287 if (region instanceof UnionCellRegion) { 288 final UnionCellRegion union = (UnionCellRegion) region; 289 List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 290 for (CellRegionImpl child : union.regions) { 291 list.add(copyReplacing(child, seek, replacement)); 292 } 293 return new UnionCellRegion(list); 294 } 295 if (region instanceof CrossjoinCellRegion) { 296 final CrossjoinCellRegion crossjoin = (CrossjoinCellRegion) region; 297 List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 298 for (CellRegionImpl child : crossjoin.components) { 299 list.add(copyReplacing(child, seek, replacement)); 300 } 301 return new CrossjoinCellRegion(list); 302 } 303 // This region is atomic, and since regions are immutable we don't need 304 // to clone. 305 return region; 306 } 307 308 /** 309 * Flatten a region into a list of regions none of which are unions. 310 * 311 * @param region Cell region 312 * @param list Target list 313 */ 314 private void flattenUnion( 315 CellRegionImpl region, 316 List<CellRegionImpl> list) 317 { 318 if (region instanceof UnionCellRegion) { 319 UnionCellRegion union = (UnionCellRegion) region; 320 for (CellRegionImpl region1 : union.regions) { 321 flattenUnion(region1, list); 322 } 323 } else { 324 list.add(region); 325 } 326 } 327 328 /** 329 * Flattens a region into a list of regions none of which are unions. 330 * 331 * @param region Cell region 332 * @param list Target list 333 */ 334 private void flattenCrossjoin( 335 CellRegionImpl region, 336 List<CellRegionImpl> list) 337 { 338 if (region instanceof CrossjoinCellRegion) { 339 CrossjoinCellRegion crossjoin = (CrossjoinCellRegion) region; 340 for (CellRegionImpl component : crossjoin.components) { 341 flattenCrossjoin(component, list); 342 } 343 } else { 344 list.add(region); 345 } 346 } 347 348 private UnionCellRegion findFirstUnion(CellRegion region) { 349 final CellRegionVisitor visitor = 350 new CellRegionVisitorImpl() { 351 public void visit(UnionCellRegion region) { 352 throw new FoundOne(region); 353 } 354 }; 355 try { 356 ((CellRegionImpl) region).accept(visitor); 357 return null; 358 } catch (FoundOne foundOne) { 359 return foundOne.region; 360 } 361 } 362 363 /** 364 * Returns a list of members of the Measures dimension which are mentioned 365 * somewhere in a region specification. 366 * 367 * @param region Cell region 368 * @return List of members mentioned in cell region specification 369 */ 370 static List<Member> findMeasures(CellRegion region) { 371 final List<Member> list = new ArrayList<Member>(); 372 final CellRegionVisitor visitor = 373 new CellRegionVisitorImpl() { 374 public void visit(MemberCellRegion region) { 375 if (region.dimension.isMeasures()) { 376 list.addAll(region.memberList); 377 } 378 } 379 380 public void visit(MemberRangeCellRegion region) { 381 if (region.level.getDimension().isMeasures()) { 382 // FIXME: don't allow range on measures dimension 383 assert false : "ranges on measures dimension"; 384 } 385 } 386 }; 387 ((CellRegionImpl) region).accept(visitor); 388 return list; 389 } 390 391 static List<RolapStar> getStarList(CellRegion region) { 392 // Figure out which measure (therefore star) it belongs to. 393 List<RolapStar> starList = new ArrayList<RolapStar>(); 394 final List<Member> measuresList = findMeasures(region); 395 for (Member measure : measuresList) { 396 if (measure instanceof RolapStoredMeasure) { 397 RolapStoredMeasure storedMeasure = (RolapStoredMeasure) measure; 398 final RolapStar.Measure starMeasure = 399 (RolapStar.Measure) storedMeasure.getStarMeasure(); 400 if (!starList.contains(starMeasure.getStar())) { 401 starList.add(starMeasure.getStar()); 402 } 403 } 404 } 405 return starList; 406 } 407 408 public void printCacheState( 409 PrintWriter pw, 410 CellRegion region) 411 { 412 List<RolapStar> starList = getStarList(region); 413 for (RolapStar star : starList) { 414 star.print(pw, "", false); 415 } 416 } 417 418 public MemberSet createMemberSet(Member member, boolean descendants) 419 { 420 return new SimpleMemberSet( 421 Collections.singletonList((RolapMember) member), 422 descendants); 423 } 424 425 public MemberSet createMemberSet( 426 boolean lowerInclusive, 427 Member lowerMember, 428 boolean upperInclusive, 429 Member upperMember, 430 boolean descendants) 431 { 432 // TODO check that upperMember & lowerMember are in same Level 433 if (lowerMember == null) { 434 lowerInclusive = false; 435 } 436 if (upperMember == null) { 437 upperInclusive = false; 438 } 439 return new RangeMemberSet( 440 stripMember((RolapMember) lowerMember), lowerInclusive, 441 stripMember((RolapMember) upperMember), upperInclusive, 442 descendants); 443 } 444 445 public MemberSet createUnionSet(MemberSet... args) 446 { 447 //noinspection unchecked 448 return new UnionMemberSet((List) Arrays.asList(args)); 449 } 450 451 public MemberSet filter(Level level, MemberSet baseSet) { 452 if (level instanceof RolapCubeLevel) { 453 // be forgiving 454 level = ((RolapCubeLevel) level).getRolapLevel(); 455 } 456 return ((MemberSetPlus) baseSet).filter((RolapLevel) level); 457 } 458 459 public void flush(MemberSet memberSet) { 460 // REVIEW How is flush(s) different to executing createDeleteCommand(s) ? 461 synchronized (MEMBER_CACHE_LOCK) { 462 final List<CellRegion> cellRegionList = new ArrayList<CellRegion>(); 463 ((MemberSetPlus) memberSet).accept( 464 new MemberSetVisitorImpl() { 465 public void visit(RolapMember member) { 466 flushMember(member, cellRegionList); 467 } 468 } 469 ); 470 // STUB: flush the set: another visitor 471 472 // finally, flush cells now invalid 473 flushRegionList(cellRegionList); 474 } 475 } 476 477 public void printCacheState(PrintWriter pw, MemberSet set) 478 { 479 synchronized (MEMBER_CACHE_LOCK) { 480 pw.println("need to implement printCacheState"); // TODO: 481 } 482 } 483 484 public MemberEditCommand createCompoundCommand( 485 List<MemberEditCommand> commandList) 486 { 487 //noinspection unchecked 488 return new CompoundCommand((List) commandList); 489 } 490 491 public MemberEditCommand createCompoundCommand( 492 MemberEditCommand... commands) 493 { 494 //noinspection unchecked 495 return new CompoundCommand((List) Arrays.asList(commands)); 496 } 497 498 public MemberEditCommand createDeleteCommand(Member member) { 499 if (member == null) { 500 throw new IllegalArgumentException("cannot delete null member"); 501 } 502 if (((RolapLevel) member.getLevel()).isParentChild()) { 503 throw new IllegalArgumentException( 504 "delete member not supported for parent-child hierarchy"); 505 } 506 return createDeleteCommand(createMemberSet(member, false)); 507 } 508 509 public MemberEditCommand createDeleteCommand(MemberSet s) { 510 return new DeleteMemberCommand((MemberSetPlus) s); 511 } 512 513 public MemberEditCommand createAddCommand( 514 Member member) throws IllegalArgumentException 515 { 516 if (member == null) { 517 throw new IllegalArgumentException("cannot add null member"); 518 } 519 if (((RolapLevel) member.getLevel()).isParentChild()) { 520 throw new IllegalArgumentException( 521 "add member not supported for parent-child hierarchy"); 522 } 523 return new AddMemberCommand((RolapMember) member); 524 } 525 526 public MemberEditCommand createMoveCommand(Member member, Member loc) 527 throws IllegalArgumentException 528 { 529 if (member == null) { 530 throw new IllegalArgumentException("cannot move null member"); 531 } 532 if (((RolapLevel) member.getLevel()).isParentChild()) { 533 throw new IllegalArgumentException( 534 "move member not supported for parent-child hierarchy"); 535 } 536 if (loc == null) { 537 throw new IllegalArgumentException("cannot move member to null location"); 538 } 539 // TODO: check that MEMBER and LOC (its new parent) have appropriate Levels 540 return new MoveMemberCommand((RolapMember) member, (RolapMember) loc); 541 } 542 543 public MemberEditCommand createSetPropertyCommand( 544 Member member, 545 String name, 546 Object value) 547 throws IllegalArgumentException 548 { 549 if (member == null) { 550 throw new IllegalArgumentException("cannot set properties on null member"); 551 } 552 if (((RolapLevel) member.getLevel()).isParentChild()) { 553 throw new IllegalArgumentException( 554 "set properties not supported for parent-child hierarchy"); 555 } 556 // TODO: validate that prop NAME exists for Level of MEMBER 557 return new ChangeMemberPropsCommand( 558 new SimpleMemberSet( 559 Collections.singletonList((RolapMember) member), 560 false), 561 Collections.singletonMap(name, value)); 562 } 563 564 public MemberEditCommand createSetPropertyCommand( 565 MemberSet members, 566 Map<String, Object> propertyValues) 567 throws IllegalArgumentException 568 { 569 // TODO: check that members all at same Level, and validate that props exist 570 validateSameLevel((MemberSetPlus) members); 571 return new ChangeMemberPropsCommand( 572 (MemberSetPlus) members, 573 propertyValues); 574 } 575 576 /** 577 * Validates that all members of a member set are the same level. 578 * 579 * @param memberSet Member set 580 * @throws IllegalArgumentException if members are from more than one level 581 */ 582 private void validateSameLevel(MemberSetPlus memberSet) 583 throws IllegalArgumentException 584 { 585 memberSet.accept( 586 new MemberSetVisitor() { 587 final Set<RolapLevel> levelSet = new HashSet<RolapLevel>(); 588 589 private void visitMember( 590 RolapMember member, 591 boolean descendants) 592 { 593 final String message = 594 "all members in set must belong to same level"; 595 if (levelSet.add(member.getLevel()) 596 && levelSet.size() > 1) { 597 throw new IllegalArgumentException(message); 598 } 599 if (descendants 600 && member.getLevel().getChildLevel() != null) { 601 throw new IllegalArgumentException(message); 602 } 603 } 604 605 public void visit(SimpleMemberSet simpleMemberSet) { 606 for (RolapMember member : simpleMemberSet.members) { 607 visitMember(member, simpleMemberSet.descendants); 608 } 609 } 610 611 public void visit(UnionMemberSet unionMemberSet) { 612 for (MemberSetPlus item : unionMemberSet.items) { 613 item.accept(this); 614 } 615 } 616 617 public void visit(RangeMemberSet rangeMemberSet) { 618 visitMember( 619 rangeMemberSet.lowerMember, 620 rangeMemberSet.descendants); 621 visitMember( 622 rangeMemberSet.upperMember, 623 rangeMemberSet.descendants); 624 } 625 } 626 ); 627 } 628 629 public void execute(MemberEditCommand cmd) { 630 final BooleanProperty prop = 631 MondrianProperties.instance().EnableRolapCubeMemberCache; 632 if (prop.get()) { 633 throw new IllegalArgumentException( 634 "Member cache control operations are not allowed unless " 635 + "property " + prop.getPath() + " is false"); 636 } 637 synchronized (MEMBER_CACHE_LOCK) { 638 final List<CellRegion> cellRegionList = 639 new ArrayList<CellRegion>(); 640 ((MemberEditCommandPlus) cmd).execute(cellRegionList); 641 if (false) { 642 // TODO: Flushing regions currently fails with the error 643 // "Region of cells to be flushed must contain measures". This 644 // method should receive a cube (or cubes) whose measures to 645 // flush. 646 flushRegionList(cellRegionList); 647 } 648 } 649 } 650 651 private static MemberCache getMemberCache(RolapMember member) { 652 final MemberReader memberReader = 653 member.getHierarchy().getMemberReader(); 654 SmartMemberReader smartMemberReader = 655 (SmartMemberReader) memberReader; 656 return smartMemberReader.getMemberCache(); 657 } 658 659 // cell cache control implementation 660 661 /** 662 * Cell region formed by a list of members. 663 * 664 * @see MemberRangeCellRegion 665 */ 666 static class MemberCellRegion implements CellRegionImpl { 667 private final List<Member> memberList; 668 private final Dimension dimension; 669 670 MemberCellRegion(List<Member> memberList, boolean descendants) { 671 assert memberList.size() > 0; 672 this.memberList = memberList; 673 this.dimension = (memberList.get(0)).getDimension(); 674 Util.discard(descendants); 675 } 676 677 public List<Dimension> getDimensionality() { 678 return Collections.singletonList(dimension); 679 } 680 681 public String toString() { 682 return Util.commaList("Member", memberList); 683 } 684 685 public void accept(CellRegionVisitor visitor) { 686 visitor.visit(this); 687 } 688 689 public List<Member> getMemberList() { 690 return memberList; 691 } 692 } 693 694 /** 695 * Cell region formed a range of members between a lower and upper bound. 696 */ 697 static class MemberRangeCellRegion implements CellRegionImpl { 698 private final RolapMember lowerMember; 699 private final boolean lowerInclusive; 700 private final RolapMember upperMember; 701 private final boolean upperInclusive; 702 private final boolean descendants; 703 private final RolapLevel level; 704 705 MemberRangeCellRegion( 706 RolapMember lowerMember, 707 boolean lowerInclusive, 708 RolapMember upperMember, 709 boolean upperInclusive, 710 boolean descendants) 711 { 712 assert lowerMember != null || upperMember != null; 713 assert lowerMember == null 714 || upperMember == null 715 || lowerMember.getLevel() == upperMember.getLevel(); 716 assert !(lowerMember == null && lowerInclusive); 717 assert !(upperMember == null && upperInclusive); 718 this.lowerMember = lowerMember; 719 this.lowerInclusive = lowerInclusive; 720 this.upperMember = upperMember; 721 this.upperInclusive = upperInclusive; 722 this.descendants = descendants; 723 this.level = lowerMember == null ? 724 upperMember.getLevel() : 725 lowerMember.getLevel(); 726 } 727 728 public List<Dimension> getDimensionality() { 729 return Collections.singletonList(level.getDimension()); 730 } 731 732 public RolapLevel getLevel() { 733 return level; 734 } 735 736 public String toString() { 737 final StringBuilder sb = new StringBuilder("Range("); 738 if (lowerMember == null) { 739 sb.append("null"); 740 } else { 741 sb.append(lowerMember); 742 if (lowerInclusive) { 743 sb.append(" inclusive"); 744 } else { 745 sb.append(" exclusive"); 746 } 747 } 748 sb.append(" to "); 749 if (upperMember == null) { 750 sb.append("null"); 751 } else { 752 sb.append(upperMember); 753 if (upperInclusive) { 754 sb.append(" inclusive"); 755 } else { 756 sb.append(" exclusive"); 757 } 758 } 759 sb.append(")"); 760 return sb.toString(); 761 } 762 763 public void accept(CellRegionVisitor visitor) { 764 visitor.visit(this); 765 } 766 767 public boolean getLowerInclusive() { 768 return lowerInclusive; 769 } 770 771 public RolapMember getLowerBound() { 772 return lowerMember; 773 } 774 775 public boolean getUpperInclusive() { 776 return upperInclusive; 777 } 778 779 public RolapMember getUpperBound() { 780 return upperMember; 781 } 782 } 783 784 /** 785 * Cell region formed by a cartesian product of two or more CellRegions. 786 */ 787 static class CrossjoinCellRegion implements CellRegionImpl { 788 final List<Dimension> dimensions; 789 private List<CellRegionImpl> components = 790 new ArrayList<CellRegionImpl>(); 791 792 CrossjoinCellRegion(List<CellRegionImpl> regions) { 793 final List<Dimension> dimensionality = new ArrayList<Dimension>(); 794 compute(regions, components, dimensionality); 795 dimensions = Collections.unmodifiableList(dimensionality); 796 } 797 798 private static void compute( 799 List<CellRegionImpl> regions, 800 List<CellRegionImpl> components, 801 List<Dimension> dimensionality) 802 { 803 final Set<Dimension> dimensionSet = new HashSet<Dimension>(); 804 for (CellRegionImpl region : regions) { 805 addComponents(region, components); 806 807 final List<Dimension> regionDimensionality = 808 region.getDimensionality(); 809 dimensionality.addAll(regionDimensionality); 810 dimensionSet.addAll(regionDimensionality); 811 assert dimensionSet.size() == dimensionality.size() : 812 "dimensions in common"; 813 } 814 } 815 816 public void accept(CellRegionVisitor visitor) { 817 visitor.visit(this); 818 for (CellRegion component : components) { 819 CellRegionImpl cellRegion = (CellRegionImpl) component; 820 cellRegion.accept(visitor); 821 } 822 } 823 824 private static void addComponents( 825 CellRegionImpl region, 826 List<CellRegionImpl> list) 827 { 828 if (region instanceof CrossjoinCellRegion) { 829 CrossjoinCellRegion crossjoinRegion = 830 (CrossjoinCellRegion) region; 831 for (CellRegionImpl component : crossjoinRegion.components) { 832 list.add(component); 833 } 834 } else { 835 list.add(region); 836 } 837 } 838 839 public List<Dimension> getDimensionality() { 840 return dimensions; 841 } 842 843 public String toString() { 844 return Util.commaList("Crossjoin", components); 845 } 846 847 public List<CellRegion> getComponents() { 848 return Util.cast(components); 849 } 850 } 851 852 private static class UnionCellRegion implements CellRegionImpl { 853 private final List<CellRegionImpl> regions; 854 855 UnionCellRegion(List<CellRegionImpl> regions) { 856 this.regions = regions; 857 assert regions.size() >= 1; 858 859 // All regions must have same dimensionality. 860 for (int i = 1; i < regions.size(); i++) { 861 final CellRegion region0 = regions.get(0); 862 final CellRegion region = regions.get(i); 863 assert region0.getDimensionality().equals( 864 region.getDimensionality()); 865 } 866 } 867 868 public List<Dimension> getDimensionality() { 869 return regions.get(0).getDimensionality(); 870 } 871 872 public String toString() { 873 return Util.commaList("Union", regions); 874 } 875 876 public void accept(CellRegionVisitor visitor) { 877 visitor.visit(this); 878 for (CellRegionImpl cellRegion : regions) { 879 cellRegion.accept(visitor); 880 } 881 } 882 } 883 884 interface CellRegionImpl extends CellRegion { 885 void accept(CellRegionVisitor visitor); 886 } 887 888 /** 889 * Visitor which visits various sub-types of {@link CellRegion}. 890 */ 891 interface CellRegionVisitor { 892 void visit(MemberCellRegion region); 893 void visit(MemberRangeCellRegion region); 894 void visit(UnionCellRegion region); 895 void visit(CrossjoinCellRegion region); 896 } 897 898 private static class FoundOne extends RuntimeException { 899 private final transient UnionCellRegion region; 900 901 public FoundOne(UnionCellRegion region) { 902 this.region = region; 903 } 904 } 905 906 /** 907 * Default implementation of {@link CellRegionVisitor}. 908 */ 909 private static class CellRegionVisitorImpl implements CellRegionVisitor { 910 public void visit(MemberCellRegion region) { 911 // nothing 912 } 913 914 public void visit(MemberRangeCellRegion region) { 915 // nothing 916 } 917 918 public void visit(UnionCellRegion region) { 919 // nothing 920 } 921 922 public void visit(CrossjoinCellRegion region) { 923 // nothing 924 } 925 } 926 927 928 // ~ member cache control implementation ---------------------------------- 929 930 /** 931 * Implementation-specific extensions to the 932 * {@link mondrian.olap.CacheControl.MemberEditCommand} interface. 933 */ 934 interface MemberEditCommandPlus extends MemberEditCommand { 935 /** 936 * Executes this command, and gathers a list of cell regions affected 937 * in the {@code cellRegionList} parameter. The caller will flush the 938 * cell regions later. 939 * 940 * @param cellRegionList Populated with a list of cell regions which 941 * are invalidated by this action 942 */ 943 void execute(final List<CellRegion> cellRegionList); 944 } 945 946 /** 947 * Implementation-specific extensions to the 948 * {@link mondrian.olap.CacheControl.MemberSet} interface. 949 */ 950 interface MemberSetPlus extends MemberSet { 951 /** 952 * Accepts a visitor. 953 * 954 * @param visitor Visitor 955 */ 956 void accept(MemberSetVisitor visitor); 957 958 /** 959 * Filters this member set, returning a member set containing all 960 * members at a given Level. When applicable, returns this member set 961 * unchanged. 962 * 963 * @param level Level 964 * @return Member set with members not at the given level removed 965 */ 966 MemberSetPlus filter(RolapLevel level); 967 } 968 969 /** 970 * Visits the subclasses of {@link MemberSetPlus}. 971 */ 972 interface MemberSetVisitor { 973 void visit(SimpleMemberSet s); 974 void visit(UnionMemberSet s); 975 void visit(RangeMemberSet s); 976 } 977 978 /** 979 * Default implementation of {@link MemberSetVisitor}. 980 * 981 * <p>The default implementation may not be efficient. For example, if 982 * flushing a range of members from the cache, you may not wish to fetch 983 * all of the members into the cache in order to flush them. 984 */ 985 public static abstract class MemberSetVisitorImpl 986 implements MemberSetVisitor 987 { 988 public void visit(UnionMemberSet s) { 989 for (MemberSetPlus item : s.items) { 990 item.accept(this); 991 } 992 } 993 994 public void visit(RangeMemberSet s) { 995 final MemberReader memberReader = 996 s.level.getHierarchy().getMemberReader(); 997 visitRange( 998 memberReader, s.level, s.lowerMember, s.upperMember, 999 s.descendants); 1000 } 1001 1002 protected void visitRange( 1003 MemberReader memberReader, 1004 RolapLevel level, 1005 RolapMember lowerMember, 1006 RolapMember upperMember, 1007 boolean recurse) 1008 { 1009 final List<RolapMember> list = new ArrayList<RolapMember>(); 1010 memberReader.getMemberRange(level, lowerMember, upperMember, list); 1011 for (RolapMember member : list) { 1012 visit(member); 1013 } 1014 if (recurse) { 1015 list.clear(); 1016 memberReader.getMemberChildren(lowerMember, list); 1017 if (list.isEmpty()) { 1018 return; 1019 } 1020 RolapMember lowerChild = list.get(0); 1021 list.clear(); 1022 memberReader.getMemberChildren(upperMember, list); 1023 if (list.isEmpty()) { 1024 return; 1025 } 1026 RolapMember upperChild = list.get(list.size() - 1); 1027 visitRange( 1028 memberReader, level, lowerChild, upperChild, recurse); 1029 } 1030 } 1031 1032 public void visit(SimpleMemberSet s) { 1033 for (RolapMember member : s.members) { 1034 visit(member); 1035 } 1036 } 1037 1038 /** 1039 * Visits a single member. 1040 * 1041 * @param member Member 1042 */ 1043 public abstract void visit(RolapMember member); 1044 } 1045 1046 /** 1047 * Member set containing no members. 1048 */ 1049 static class EmptyMemberSet implements MemberSetPlus { 1050 public static final EmptyMemberSet INSTANCE = new EmptyMemberSet(); 1051 1052 private EmptyMemberSet() { 1053 // prevent instantiation except for singleton 1054 } 1055 1056 public void accept(MemberSetVisitor visitor) { 1057 // nothing 1058 } 1059 1060 public MemberSetPlus filter(RolapLevel level) { 1061 return this; 1062 } 1063 1064 public String toString() { 1065 return "Empty"; 1066 } 1067 } 1068 1069 /** 1070 * Member set defined by a list of members from one hierarchy. 1071 */ 1072 static class SimpleMemberSet implements MemberSetPlus { 1073 public final List<RolapMember> members; 1074 public final boolean descendants; // the set includes the descendants of all members 1075 public final RolapHierarchy hierarchy; 1076 1077 SimpleMemberSet(List<RolapMember> members, boolean descendants) { 1078 this.members = new ArrayList<RolapMember>(members); 1079 stripMemberList(this.members); 1080 this.descendants = descendants; 1081 this.hierarchy = 1082 members.isEmpty() 1083 ? null 1084 : members.get(0).getHierarchy(); 1085 } 1086 1087 public String toString() { 1088 return Util.commaList("Member", members); 1089 } 1090 1091 public void accept(MemberSetVisitor visitor) { 1092 // Don't descend the subtrees here: may not want to load them into 1093 // cache. 1094 visitor.visit(this); 1095 } 1096 1097 public MemberSetPlus filter(RolapLevel level) { 1098 List<RolapMember> filteredMembers = new ArrayList<RolapMember>(); 1099 for (RolapMember member : members) { 1100 if (member.getLevel().equals(level)) { 1101 filteredMembers.add(member); 1102 } 1103 } 1104 if (filteredMembers.isEmpty()) { 1105 return EmptyMemberSet.INSTANCE; 1106 } else if (filteredMembers.equals(members)) { 1107 return this; 1108 } else { 1109 return new SimpleMemberSet(filteredMembers, false); 1110 } 1111 } 1112 } 1113 1114 /** 1115 * Member set defined by the union of other member sets. 1116 */ 1117 static class UnionMemberSet implements MemberSetPlus { 1118 private final List<MemberSetPlus> items; 1119 1120 UnionMemberSet(List<MemberSetPlus> items) { 1121 this.items = items; 1122 } 1123 1124 public String toString() { 1125 final StringBuilder sb = new StringBuilder("Union("); 1126 for (int i = 0; i < items.size(); i++) { 1127 if (i > 0) { 1128 sb.append(", "); 1129 } 1130 MemberSetPlus item = items.get(i); 1131 sb.append(item.toString()); 1132 } 1133 sb.append(")"); 1134 return sb.toString(); 1135 } 1136 1137 public void accept(MemberSetVisitor visitor) { 1138 visitor.visit(this); 1139 } 1140 1141 public MemberSetPlus filter(RolapLevel level) { 1142 final List<MemberSetPlus> filteredItems = 1143 new ArrayList<MemberSetPlus>(); 1144 for (MemberSetPlus item : items) { 1145 final MemberSetPlus filteredItem = item.filter(level); 1146 if (filteredItem == EmptyMemberSet.INSTANCE) { 1147 // skip it 1148 } else { 1149 assert !(filteredItem instanceof EmptyMemberSet); 1150 filteredItems.add(filteredItem); 1151 } 1152 } 1153 if (filteredItems.isEmpty()) { 1154 return EmptyMemberSet.INSTANCE; 1155 } else if (filteredItems.equals(items)) { 1156 return this; 1157 } else { 1158 return new UnionMemberSet(filteredItems); 1159 } 1160 } 1161 } 1162 1163 /** 1164 * Member set defined by a range of members between a lower and upper 1165 * bound. 1166 */ 1167 static class RangeMemberSet implements MemberSetPlus { 1168 private final RolapMember lowerMember; 1169 private final boolean lowerInclusive; 1170 private final RolapMember upperMember; 1171 private final boolean upperInclusive; 1172 private final boolean descendants; 1173 private final RolapLevel level; 1174 1175 RangeMemberSet( 1176 RolapMember lowerMember, 1177 boolean lowerInclusive, 1178 RolapMember upperMember, 1179 boolean upperInclusive, 1180 boolean descendants) 1181 { 1182 assert lowerMember != null || upperMember != null; 1183 assert lowerMember == null 1184 || upperMember == null 1185 || lowerMember.getLevel() == upperMember.getLevel(); 1186 assert !(lowerMember == null && lowerInclusive); 1187 assert !(upperMember == null && upperInclusive); 1188 assert !(lowerMember instanceof RolapCubeMember); 1189 assert !(upperMember instanceof RolapCubeMember); 1190 this.lowerMember = lowerMember; 1191 this.lowerInclusive = lowerInclusive; 1192 this.upperMember = upperMember; 1193 this.upperInclusive = upperInclusive; 1194 this.descendants = descendants; 1195 this.level = lowerMember == null ? 1196 upperMember.getLevel() : 1197 lowerMember.getLevel(); 1198 } 1199 1200 public String toString() { 1201 final StringBuilder sb = new StringBuilder("Range("); 1202 if (lowerMember == null) { 1203 sb.append("null"); 1204 } else { 1205 sb.append(lowerMember); 1206 if (lowerInclusive) { 1207 sb.append(" inclusive"); 1208 } else { 1209 sb.append(" exclusive"); 1210 } 1211 } 1212 sb.append(" to "); 1213 if (upperMember == null) { 1214 sb.append("null"); 1215 } else { 1216 sb.append(upperMember); 1217 if (upperInclusive) { 1218 sb.append(" inclusive"); 1219 } else { 1220 sb.append(" exclusive"); 1221 } 1222 } 1223 sb.append(")"); 1224 return sb.toString(); 1225 } 1226 1227 public void accept(MemberSetVisitor visitor) { 1228 // Don't traverse the range here: may not want to load it into cache 1229 visitor.visit(this); 1230 } 1231 1232 public MemberSetPlus filter(RolapLevel level) { 1233 if (level == this.level) { 1234 return this; 1235 } else { 1236 return filter2(level, this.level, lowerMember, upperMember); 1237 } 1238 } 1239 1240 public MemberSetPlus filter2( 1241 RolapLevel seekLevel, 1242 RolapLevel level, 1243 RolapMember lower, 1244 RolapMember upper) 1245 { 1246 if (level == seekLevel) { 1247 return new RangeMemberSet( 1248 lower, lowerInclusive, upper, upperInclusive, false); 1249 } else if (descendants 1250 && level.getHierarchy() == seekLevel.getHierarchy() 1251 && level.getDepth() < seekLevel.getDepth()) 1252 { 1253 final MemberReader memberReader = 1254 level.getHierarchy().getMemberReader(); 1255 final List<RolapMember> list = new ArrayList<RolapMember>(); 1256 memberReader.getMemberChildren(lower, list); 1257 if (list.isEmpty()) { 1258 return EmptyMemberSet.INSTANCE; 1259 } 1260 RolapMember lowerChild = list.get(0); 1261 list.clear(); 1262 memberReader.getMemberChildren(upper, list); 1263 if (list.isEmpty()) { 1264 return EmptyMemberSet.INSTANCE; 1265 } 1266 RolapMember upperChild = list.get(list.size() - 1); 1267 return filter2( 1268 seekLevel, (RolapLevel) level.getChildLevel(), 1269 lowerChild, upperChild); 1270 } else { 1271 return EmptyMemberSet.INSTANCE; 1272 } 1273 } 1274 } 1275 1276 /** 1277 * Command consisting of a set of commands executed in sequence. 1278 */ 1279 private static class CompoundCommand implements MemberEditCommandPlus { 1280 private final List<MemberEditCommandPlus> commandList; 1281 1282 CompoundCommand(List<MemberEditCommandPlus> commandList) { 1283 this.commandList = commandList; 1284 } 1285 1286 public String toString() { 1287 return Util.commaList("Compound", commandList); 1288 } 1289 1290 public void execute(final List<CellRegion> cellRegionList) { 1291 for (MemberEditCommandPlus command : commandList) { 1292 command.execute(cellRegionList); 1293 } 1294 } 1295 } 1296 1297 /** 1298 * Command that deletes a member and its descendants from the cache. 1299 */ 1300 private class DeleteMemberCommand 1301 extends MemberSetVisitorImpl 1302 implements MemberEditCommandPlus 1303 { 1304 private final MemberSetPlus set; 1305 private List<CellRegion> cellRegionList; 1306 1307 DeleteMemberCommand(MemberSetPlus set) { 1308 this.set = set; 1309 } 1310 1311 public String toString() { 1312 return "DeleteMemberCommand(" + set + ")"; 1313 } 1314 1315 public void execute(final List<CellRegion> cellRegionList) { 1316 // NOTE: use of member makes this class non-reentrant 1317 this.cellRegionList = cellRegionList; 1318 set.accept(this); 1319 this.cellRegionList = null; 1320 } 1321 1322 public void visit(RolapMember member) { 1323 deleteMember(member, member.getParentMember(), cellRegionList); 1324 } 1325 } 1326 1327 /** 1328 * Command that adds a new member to the cache. 1329 */ 1330 private class AddMemberCommand implements MemberEditCommandPlus { 1331 private final RolapMember member; 1332 1333 public AddMemberCommand(RolapMember member) { 1334 assert member != null; 1335 this.member = stripMember(member); 1336 } 1337 1338 public String toString() { 1339 return "AddMemberCommand(" + member + ")"; 1340 } 1341 1342 public void execute(List<CellRegion> cellRegionList) { 1343 addMember(member, member.getParentMember(), cellRegionList); 1344 } 1345 } 1346 1347 /** 1348 * Command that moves a member to a new parent. 1349 */ 1350 private class MoveMemberCommand implements MemberEditCommandPlus { 1351 private final RolapMember member; 1352 private final RolapMember newParent; 1353 1354 MoveMemberCommand(RolapMember member, RolapMember newParent) { 1355 this.member = member; 1356 this.newParent = newParent; 1357 } 1358 1359 public String toString() { 1360 return "MoveMemberCommand(" + member + ", " + newParent + ")"; 1361 } 1362 1363 public void execute(final List<CellRegion> cellRegionList) { 1364 deleteMember(member, member.getParentMember(), cellRegionList); 1365 member.setParentMember(newParent); 1366 addMember(member, member.getParentMember(), cellRegionList); 1367 } 1368 } 1369 1370 /** 1371 * Command that changes one or more properties of a member. 1372 */ 1373 private class ChangeMemberPropsCommand 1374 extends MemberSetVisitorImpl 1375 implements MemberEditCommandPlus 1376 { 1377 final MemberSetPlus memberSet; 1378 final Map<String, Object> propertyValues; 1379 1380 ChangeMemberPropsCommand( 1381 MemberSetPlus memberSet, 1382 Map<String, Object> propertyValues) 1383 { 1384 this.memberSet = memberSet; 1385 this.propertyValues = propertyValues; 1386 } 1387 1388 public String toString() { 1389 return "CreateMemberPropsCommand(" + memberSet 1390 + ", " + propertyValues + ")"; 1391 } 1392 1393 public void execute(List<CellRegion> cellRegionList) { 1394 // ignore cellRegionList - no changes to cell cache 1395 memberSet.accept(this); 1396 } 1397 1398 public void visit(RolapMember member) { 1399 // Change member's properties. 1400 member = stripMember(member); 1401 final MemberCache memberCache = getMemberCache(member); 1402 final Object cacheKey = 1403 memberCache.makeKey( 1404 member.getParentMember(), 1405 member.getKey()); 1406 final RolapMember cacheMember = memberCache.getMember(cacheKey); 1407 if (cacheMember == null) { 1408 return; 1409 } 1410 for (Map.Entry<String, Object> entry : propertyValues.entrySet()) { 1411 cacheMember.setProperty(entry.getKey(), entry.getValue()); 1412 } 1413 } 1414 } 1415 1416 private static RolapMember stripMember(RolapMember member) { 1417 if (member instanceof RolapCubeMember) { 1418 member = ((RolapCubeMember) member).rolapMember; 1419 } 1420 return member; 1421 } 1422 1423 private static void stripMemberList(List<RolapMember> members) { 1424 for (int i = 0; i < members.size(); i++) { 1425 RolapMember member = members.get(i); 1426 if (member instanceof RolapCubeMember) { 1427 members.set(i, ((RolapCubeMember) member).rolapMember); 1428 } 1429 } 1430 } 1431 1432 private void deleteMember( 1433 final RolapMember member, 1434 RolapMember previousParent, 1435 List<CellRegion> cellRegionList) 1436 { 1437 // Remove member from cache, 1438 // and that will remove the parent's children list. 1439 final MemberCache memberCache = getMemberCache(member); 1440 final Object key = 1441 memberCache.makeKey(previousParent, member.getKey()); 1442 memberCache.removeMember(key); 1443 1444 // Cells for member and its ancestors are now invalid. It's sufficient 1445 // to flush the member. 1446 cellRegionList.add(createMemberRegion(member, true)); 1447 } 1448 1449 /** 1450 * Adds a member to cache. 1451 * 1452 * @param member Member 1453 * @param parent Member's parent (generally equals member.getParentMember) 1454 * @param cellRegionList List of cell regions to be flushed 1455 */ 1456 private void addMember( 1457 final RolapMember member, 1458 final RolapMember parent, 1459 List<CellRegion> cellRegionList) 1460 { 1461 // Parent's children are now invalid. Remove parent from cache, 1462 // and that will remove the parent's children list. Children lists 1463 // of its existing children can remain in cache. 1464 final MemberCache memberCache = getMemberCache(member); 1465 final Object parentKey = 1466 memberCache.makeKey(parent.getParentMember(), parent.getKey()); 1467 memberCache.removeMember(parentKey); 1468 1469 // Cells for all of member's ancestors are now invalid. It's sufficient 1470 // to flush its parent. 1471 cellRegionList.add(createMemberRegion(parent, false)); 1472 } 1473 1474 /** 1475 * Removes a member from cache. 1476 * 1477 * @param member Member 1478 * @param cellRegionList Populated with cell region to be flushed 1479 */ 1480 private void flushMember( 1481 RolapMember member, 1482 List<CellRegion> cellRegionList) 1483 { 1484 // TODO 1485 1486 cellRegionList.add(createMemberRegion(member, false)); 1487 } 1488 } 1489 1490 // End CacheControlImpl.java