001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2006-2013 Pentaho and others 008// All Rights Reserved. 009*/ 010package mondrian.rolap; 011 012import mondrian.olap.*; 013import mondrian.resource.MondrianResource; 014import mondrian.rolap.agg.SegmentCacheManager; 015import mondrian.rolap.sql.MemberChildrenConstraint; 016import mondrian.server.Execution; 017import mondrian.server.Locus; 018import mondrian.spi.SegmentColumn; 019import mondrian.util.ArraySortedSet; 020 021import org.eigenbase.util.property.BooleanProperty; 022 023import java.io.PrintWriter; 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.UndeclaredThrowableException; 026import java.util.*; 027import java.util.Map.Entry; 028import java.util.concurrent.Callable; 029import javax.sql.DataSource; 030 031/** 032 * Implementation of {@link CacheControl} API. 033 * 034 * @author jhyde 035 * @since Sep 27, 2006 036 */ 037public class CacheControlImpl implements CacheControl { 038 private final RolapConnection connection; 039 040 /** 041 * Object to lock before making changes to the member cache. 042 * 043 * <p>The "member cache" is a figure of speech: each RolapHierarchy has its 044 * own MemberCache object. But to provide transparently serialized access 045 * to the "member cache" via the interface CacheControl, provide a common 046 * lock here. 047 * 048 * <p>NOTE: static member is a little too wide a scope for this lock, 049 * because in theory a JVM can contain multiple independent instances of 050 * mondrian. 051 */ 052 private static final Object MEMBER_CACHE_LOCK = new Object(); 053 054 /** 055 * Creates a CacheControlImpl. 056 * 057 * @param connection Connection 058 */ 059 public CacheControlImpl(RolapConnection connection) { 060 super(); 061 this.connection = connection; 062 } 063 064 // cell cache control 065 public CellRegion createMemberRegion(Member member, boolean descendants) { 066 if (member == null) { 067 throw new NullPointerException(); 068 } 069 final ArrayList<Member> list = new ArrayList<Member>(); 070 list.add(member); 071 return new MemberCellRegion(list, descendants); 072 } 073 074 public CellRegion createMemberRegion( 075 boolean lowerInclusive, 076 Member lowerMember, 077 boolean upperInclusive, 078 Member upperMember, 079 boolean descendants) 080 { 081 if (lowerMember == null) { 082 lowerInclusive = false; 083 } 084 if (upperMember == null) { 085 upperInclusive = false; 086 } 087 return new MemberRangeCellRegion( 088 (RolapMember) lowerMember, lowerInclusive, 089 (RolapMember) upperMember, upperInclusive, 090 descendants); 091 } 092 093 public CellRegion createCrossjoinRegion(CellRegion... regions) { 094 assert regions != null; 095 assert regions.length >= 2; 096 final HashSet<Dimension> set = new HashSet<Dimension>(); 097 final List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 098 for (CellRegion region : regions) { 099 int prevSize = set.size(); 100 List<Dimension> dimensionality = region.getDimensionality(); 101 set.addAll(dimensionality); 102 if (set.size() < prevSize + dimensionality.size()) { 103 throw MondrianResource.instance() 104 .CacheFlushCrossjoinDimensionsInCommon.ex( 105 getDimensionalityList(regions)); 106 } 107 108 flattenCrossjoin((CellRegionImpl) region, list); 109 } 110 return new CrossjoinCellRegion(list); 111 } 112 113 // Returns e.g. "'[[Product]]', '[[Time], [Product]]'" 114 private String getDimensionalityList(CellRegion[] regions) { 115 StringBuilder buf = new StringBuilder(); 116 int k = 0; 117 for (CellRegion region : regions) { 118 if (k++ > 0) { 119 buf.append(", "); 120 } 121 buf.append("'"); 122 buf.append(region.getDimensionality().toString()); 123 buf.append("'"); 124 } 125 return buf.toString(); 126 } 127 128 public CellRegion createUnionRegion(CellRegion... regions) 129 { 130 if (regions == null) { 131 throw new NullPointerException(); 132 } 133 if (regions.length < 2) { 134 throw new IllegalArgumentException(); 135 } 136 final List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 137 for (CellRegion region : regions) { 138 if (!region.getDimensionality().equals( 139 regions[0].getDimensionality())) 140 { 141 throw MondrianResource.instance() 142 .CacheFlushUnionDimensionalityMismatch.ex( 143 regions[0].getDimensionality().toString(), 144 region.getDimensionality().toString()); 145 } 146 list.add((CellRegionImpl) region); 147 } 148 return new UnionCellRegion(list); 149 } 150 151 public CellRegion createMeasuresRegion(Cube cube) { 152 Dimension measuresDimension = null; 153 for (Dimension dim : cube.getDimensions()) { 154 if (dim.isMeasures()) { 155 measuresDimension = dim; 156 break; 157 } 158 } 159 if (measuresDimension == null) { 160 throw new MondrianException( 161 "No measures dimension found for cube " 162 + cube.getName()); 163 } 164 final List<Member> measures = 165 cube.getSchemaReader(null).withLocus().getLevelMembers( 166 measuresDimension.getHierarchy().getLevels()[0], 167 false); 168 if (measures.size() == 0) { 169 return new EmptyCellRegion(); 170 } 171 return new MemberCellRegion(measures, false); 172 } 173 174 public void flush(final CellRegion region) { 175 Locus.execute( 176 connection, 177 "Flush", 178 new Locus.Action<Void>() { 179 public Void execute() { 180 flushInternal(region); 181 return null; 182 } 183 }); 184 } 185 186 private void flushInternal(CellRegion region) { 187 if (region instanceof EmptyCellRegion) { 188 return; 189 } 190 final List<Dimension> dimensionality = region.getDimensionality(); 191 boolean found = false; 192 for (Dimension dimension : dimensionality) { 193 if (dimension.isMeasures()) { 194 found = true; 195 break; 196 } 197 } 198 if (!found) { 199 throw MondrianResource.instance().CacheFlushRegionMustContainMembers 200 .ex(); 201 } 202 final UnionCellRegion union = normalize((CellRegionImpl) region); 203 for (CellRegionImpl cellRegion : union.regions) { 204 // Figure out the bits. 205 flushNonUnion(cellRegion); 206 } 207 } 208 209 /** 210 * Flushes a list of cell regions. 211 * 212 * @param cellRegionList List of cell regions 213 */ 214 protected void flushRegionList(List<CellRegion> cellRegionList) { 215 final CellRegion cellRegion; 216 switch (cellRegionList.size()) { 217 case 0: 218 return; 219 case 1: 220 cellRegion = cellRegionList.get(0); 221 break; 222 default: 223 final CellRegion[] cellRegions = 224 cellRegionList.toArray(new CellRegion[cellRegionList.size()]); 225 cellRegion = createUnionRegion(cellRegions); 226 break; 227 } 228 if (!containsMeasures(cellRegion)) { 229 for (RolapCube cube : connection.getSchema().getCubeList()) { 230 flush( 231 createCrossjoinRegion( 232 createMeasuresRegion(cube), 233 cellRegion)); 234 } 235 } else { 236 flush(cellRegion); 237 } 238 } 239 240 private boolean containsMeasures(CellRegion cellRegion) { 241 final List<Dimension> dimensionList = cellRegion.getDimensionality(); 242 for (Dimension dimension : dimensionList) { 243 if (dimension.isMeasures()) { 244 return true; 245 } 246 } 247 return false; 248 } 249 250 public void trace(String message) { 251 // ignore message 252 } 253 254 public boolean isTraceEnabled() { 255 return false; 256 } 257 258 public void flushSchemaCache() { 259 RolapSchemaPool.instance().clear(); 260 // In some cases, the request might originate from a reference 261 // to the schema which isn't in the pool anymore. We must also call 262 // the cleanup procedure on the current connection. 263 if (connection != null 264 && connection.getSchema() != null) 265 { 266 connection.getSchema().finalCleanUp(); 267 } 268 } 269 270 // todo: document 271 public void flushSchema( 272 String catalogUrl, 273 String connectionKey, 274 String jdbcUser, 275 String dataSourceStr) 276 { 277 RolapSchemaPool.instance().remove( 278 catalogUrl, 279 connectionKey, 280 jdbcUser, 281 dataSourceStr); 282 } 283 284 // todo: document 285 public void flushSchema( 286 String catalogUrl, 287 DataSource dataSource) 288 { 289 RolapSchemaPool.instance().remove( 290 catalogUrl, 291 dataSource); 292 } 293 294 /** 295 * Flushes the given RolapSchema instance from the pool 296 * 297 * @param schema RolapSchema 298 */ 299 public void flushSchema(Schema schema) { 300 if (RolapSchema.class.isInstance(schema)) { 301 RolapSchemaPool.instance().remove((RolapSchema)schema); 302 } else { 303 throw new UnsupportedOperationException( 304 schema.getClass().getName() + " cannot be flushed"); 305 } 306 } 307 308 protected void flushNonUnion(CellRegion region) { 309 throw new UnsupportedOperationException(); 310 } 311 312 /** 313 * Normalizes a CellRegion into a union of crossjoins of member regions. 314 * 315 * @param region Region 316 * @return normalized region 317 */ 318 UnionCellRegion normalize(CellRegionImpl region) { 319 // Search for Union within a Crossjoin. 320 // Crossjoin(a1, a2, Union(r1, r2, r3), a4) 321 // becomes 322 // Union( 323 // Crossjoin(a1, a2, r1, a4), 324 // Crossjoin(a1, a2, r2, a4), 325 // Crossjoin(a1, a2, r3, a4)) 326 327 // First, decompose into a flat list of non-union regions. 328 List<CellRegionImpl> nonUnionList = new LinkedList<CellRegionImpl>(); 329 flattenUnion(region, nonUnionList); 330 331 for (int i = 0; i < nonUnionList.size(); i++) { 332 while (true) { 333 CellRegionImpl nonUnionRegion = nonUnionList.get(i); 334 UnionCellRegion firstUnion = findFirstUnion(nonUnionRegion); 335 if (firstUnion == null) { 336 break; 337 } 338 List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 339 for (CellRegionImpl unionComponent : firstUnion.regions) { 340 // For each unionComponent in (r1, r2, r3), 341 // create Crossjoin(a1, a2, r1, a4). 342 CellRegionImpl cj = 343 copyReplacing( 344 nonUnionRegion, 345 firstUnion, 346 unionComponent); 347 list.add(cj); 348 } 349 // Replace one element which contained a union with several 350 // which contain one fewer union. (Double-linked list helps 351 // here.) 352 nonUnionList.remove(i); 353 nonUnionList.addAll(i, list); 354 } 355 } 356 return new UnionCellRegion(nonUnionList); 357 } 358 359 private CellRegionImpl copyReplacing( 360 CellRegionImpl region, 361 CellRegionImpl seek, 362 CellRegionImpl replacement) 363 { 364 if (region == seek) { 365 return replacement; 366 } 367 if (region instanceof UnionCellRegion) { 368 final UnionCellRegion union = (UnionCellRegion) region; 369 List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 370 for (CellRegionImpl child : union.regions) { 371 list.add(copyReplacing(child, seek, replacement)); 372 } 373 return new UnionCellRegion(list); 374 } 375 if (region instanceof CrossjoinCellRegion) { 376 final CrossjoinCellRegion crossjoin = (CrossjoinCellRegion) region; 377 List<CellRegionImpl> list = new ArrayList<CellRegionImpl>(); 378 for (CellRegionImpl child : crossjoin.components) { 379 list.add(copyReplacing(child, seek, replacement)); 380 } 381 return new CrossjoinCellRegion(list); 382 } 383 // This region is atomic, and since regions are immutable we don't need 384 // to clone. 385 return region; 386 } 387 388 /** 389 * Flatten a region into a list of regions none of which are unions. 390 * 391 * @param region Cell region 392 * @param list Target list 393 */ 394 private void flattenUnion( 395 CellRegionImpl region, 396 List<CellRegionImpl> list) 397 { 398 if (region instanceof UnionCellRegion) { 399 UnionCellRegion union = (UnionCellRegion) region; 400 for (CellRegionImpl region1 : union.regions) { 401 flattenUnion(region1, list); 402 } 403 } else { 404 list.add(region); 405 } 406 } 407 408 /** 409 * Flattens a region into a list of regions none of which are unions. 410 * 411 * @param region Cell region 412 * @param list Target list 413 */ 414 private void flattenCrossjoin( 415 CellRegionImpl region, 416 List<CellRegionImpl> list) 417 { 418 if (region instanceof CrossjoinCellRegion) { 419 CrossjoinCellRegion crossjoin = (CrossjoinCellRegion) region; 420 for (CellRegionImpl component : crossjoin.components) { 421 flattenCrossjoin(component, list); 422 } 423 } else { 424 list.add(region); 425 } 426 } 427 428 private UnionCellRegion findFirstUnion(CellRegion region) { 429 final CellRegionVisitor visitor = 430 new CellRegionVisitorImpl() { 431 public void visit(UnionCellRegion region) { 432 throw new FoundOne(region); 433 } 434 }; 435 try { 436 ((CellRegionImpl) region).accept(visitor); 437 return null; 438 } catch (FoundOne foundOne) { 439 return foundOne.region; 440 } 441 } 442 443 /** 444 * Returns a list of members of the Measures dimension which are mentioned 445 * somewhere in a region specification. 446 * 447 * @param region Cell region 448 * @return List of members mentioned in cell region specification 449 */ 450 public static List<Member> findMeasures(CellRegion region) { 451 final List<Member> list = new ArrayList<Member>(); 452 final CellRegionVisitor visitor = 453 new CellRegionVisitorImpl() { 454 public void visit(MemberCellRegion region) { 455 if (region.dimension.isMeasures()) { 456 list.addAll(region.memberList); 457 } 458 } 459 460 public void visit(MemberRangeCellRegion region) { 461 if (region.level.getDimension().isMeasures()) { 462 // FIXME: don't allow range on measures dimension 463 assert false : "ranges on measures dimension"; 464 } 465 } 466 }; 467 ((CellRegionImpl) region).accept(visitor); 468 return list; 469 } 470 471 public static SegmentColumn[] findAxisValues(CellRegion region) { 472 final List<SegmentColumn> list = 473 new ArrayList<SegmentColumn>(); 474 final CellRegionVisitor visitor = 475 new CellRegionVisitorImpl() { 476 public void visit(MemberCellRegion region) { 477 if (region.dimension.isMeasures()) { 478 return; 479 } 480 final Map<String, Set<Comparable>> levels = 481 new HashMap<String, Set<Comparable>>(); 482 for (Member member : region.memberList) { 483 while (true) { 484 if (member == null || member.isAll()) { 485 break; 486 } 487 final String ccName = 488 ((RolapLevel) member.getLevel()).getKeyExp() 489 .getGenericExpression(); 490 if (!levels.containsKey(ccName)) { 491 levels.put( 492 ccName, new HashSet<Comparable>()); 493 } 494 levels.get(ccName).add( 495 (Comparable)((RolapMember)member).getKey()); 496 member = member.getParentMember(); 497 } 498 } 499 for (Entry<String, Set<Comparable>> entry 500 : levels.entrySet()) 501 { 502 // Now sort and convert to an ArraySortedSet. 503 final Comparable[] keys = 504 entry.getValue().toArray( 505 new Comparable[entry.getValue().size()]); 506 if (keys.length == 1 && keys[0].equals(true)) { 507 list.add( 508 new SegmentColumn( 509 entry.getKey(), 510 -1, 511 null)); 512 } else { 513 Arrays.sort( 514 keys, 515 Util.SqlNullSafeComparator.instance); 516 //noinspection unchecked 517 list.add( 518 new SegmentColumn( 519 entry.getKey(), 520 -1, 521 new ArraySortedSet(keys))); 522 } 523 } 524 } 525 526 public void visit(MemberRangeCellRegion region) { 527 // We translate all ranges into wildcards. 528 // FIXME Optimize this by resolving the list of members 529 // into an actual list of values for ConstrainedColumn 530 list.add( 531 new SegmentColumn( 532 region.level.getKeyExp().getGenericExpression(), 533 -1, 534 null)); 535 } 536 }; 537 ((CellRegionImpl) region).accept(visitor); 538 return list.toArray(new SegmentColumn[list.size()]); 539 } 540 541 public static List<RolapStar> getStarList(CellRegion region) { 542 // Figure out which measure (therefore star) it belongs to. 543 List<RolapStar> starList = new ArrayList<RolapStar>(); 544 final List<Member> measuresList = findMeasures(region); 545 for (Member measure : measuresList) { 546 if (measure instanceof RolapStoredMeasure) { 547 RolapStoredMeasure storedMeasure = (RolapStoredMeasure) measure; 548 final RolapStar.Measure starMeasure = 549 (RolapStar.Measure) storedMeasure.getStarMeasure(); 550 if (!starList.contains(starMeasure.getStar())) { 551 starList.add(starMeasure.getStar()); 552 } 553 } 554 } 555 return starList; 556 } 557 558 public void printCacheState( 559 final PrintWriter pw, 560 final CellRegion region) 561 { 562 final List<RolapStar> starList = getStarList(region); 563 for (RolapStar star : starList) { 564 star.print(pw, "", false); 565 } 566 final SegmentCacheManager manager = 567 MondrianServer.forConnection(connection) 568 .getAggregationManager().cacheMgr; 569 Locus.execute( 570 connection, 571 "CacheControlImpl.printCacheState", 572 new Locus.Action<Void>() { 573 public Void execute() { 574 manager.printCacheState(region, pw, Locus.peek()); 575 return null; 576 } 577 }); 578 } 579 580 public MemberSet createMemberSet(Member member, boolean descendants) 581 { 582 return new SimpleMemberSet( 583 Collections.singletonList((RolapMember) member), 584 descendants); 585 } 586 587 public MemberSet createMemberSet( 588 boolean lowerInclusive, 589 Member lowerMember, 590 boolean upperInclusive, 591 Member upperMember, 592 boolean descendants) 593 { 594 if (upperMember != null && lowerMember != null) { 595 assert upperMember.getLevel().equals(lowerMember.getLevel()); 596 } 597 if (lowerMember == null) { 598 lowerInclusive = false; 599 } 600 if (upperMember == null) { 601 upperInclusive = false; 602 } 603 return new RangeMemberSet( 604 stripMember((RolapMember) lowerMember), lowerInclusive, 605 stripMember((RolapMember) upperMember), upperInclusive, 606 descendants); 607 } 608 609 public MemberSet createUnionSet(MemberSet... args) 610 { 611 //noinspection unchecked 612 return new UnionMemberSet((List) Arrays.asList(args)); 613 } 614 615 public MemberSet filter(Level level, MemberSet baseSet) { 616 if (level instanceof RolapCubeLevel) { 617 // be forgiving 618 level = ((RolapCubeLevel) level).getRolapLevel(); 619 } 620 return ((MemberSetPlus) baseSet).filter((RolapLevel) level); 621 } 622 623 public void flush(MemberSet memberSet) { 624 // REVIEW How is flush(s) different to executing createDeleteCommand(s)? 625 synchronized (MEMBER_CACHE_LOCK) { 626 final List<CellRegion> cellRegionList = new ArrayList<CellRegion>(); 627 ((MemberSetPlus) memberSet).accept( 628 new MemberSetVisitorImpl() { 629 public void visit(RolapMember member) { 630 flushMember(member, cellRegionList); 631 } 632 } 633 ); 634 // STUB: flush the set: another visitor 635 636 // finally, flush cells now invalid 637 flushRegionList(cellRegionList); 638 } 639 } 640 641 public void printCacheState(PrintWriter pw, MemberSet set) 642 { 643 synchronized (MEMBER_CACHE_LOCK) { 644 pw.println("need to implement printCacheState"); // TODO: 645 } 646 } 647 648 public MemberEditCommand createCompoundCommand( 649 List<MemberEditCommand> commandList) 650 { 651 //noinspection unchecked 652 return new CompoundCommand((List) commandList); 653 } 654 655 public MemberEditCommand createCompoundCommand( 656 MemberEditCommand... commands) 657 { 658 //noinspection unchecked 659 return new CompoundCommand((List) Arrays.asList(commands)); 660 } 661 662 public MemberEditCommand createDeleteCommand(Member member) { 663 if (member == null) { 664 throw new IllegalArgumentException("cannot delete null member"); 665 } 666 if (((RolapLevel) member.getLevel()).isParentChild()) { 667 throw new IllegalArgumentException( 668 "delete member not supported for parent-child hierarchy"); 669 } 670 return createDeleteCommand(createMemberSet(member, false)); 671 } 672 673 public MemberEditCommand createDeleteCommand(MemberSet s) { 674 return new DeleteMemberCommand((MemberSetPlus) s); 675 } 676 677 public MemberEditCommand createAddCommand( 678 Member member) throws IllegalArgumentException 679 { 680 if (member == null) { 681 throw new IllegalArgumentException("cannot add null member"); 682 } 683 if (((RolapLevel) member.getLevel()).isParentChild()) { 684 throw new IllegalArgumentException( 685 "add member not supported for parent-child hierarchy"); 686 } 687 return new AddMemberCommand((RolapMember) member); 688 } 689 690 public MemberEditCommand createMoveCommand(Member member, Member loc) 691 throws IllegalArgumentException 692 { 693 if (member == null) { 694 throw new IllegalArgumentException("cannot move null member"); 695 } 696 if (((RolapLevel) member.getLevel()).isParentChild()) { 697 throw new IllegalArgumentException( 698 "move member not supported for parent-child hierarchy"); 699 } 700 if (loc == null) { 701 throw new IllegalArgumentException( 702 "cannot move member to null location"); 703 } 704 // TODO: check that MEMBER and LOC (its new parent) have appropriate 705 // Levels 706 return new MoveMemberCommand((RolapMember) member, (RolapMember) loc); 707 } 708 709 public MemberEditCommand createSetPropertyCommand( 710 Member member, 711 String name, 712 Object value) 713 throws IllegalArgumentException 714 { 715 if (member == null) { 716 throw new IllegalArgumentException( 717 "cannot set properties on null member"); 718 } 719 if (((RolapLevel) member.getLevel()).isParentChild()) { 720 throw new IllegalArgumentException( 721 "set properties not supported for parent-child hierarchy"); 722 } 723 // TODO: validate that prop NAME exists for Level of MEMBER 724 return new ChangeMemberPropsCommand( 725 new SimpleMemberSet( 726 Collections.singletonList((RolapMember) member), 727 false), 728 Collections.singletonMap(name, value)); 729 } 730 731 public MemberEditCommand createSetPropertyCommand( 732 MemberSet members, 733 Map<String, Object> propertyValues) 734 throws IllegalArgumentException 735 { 736 // TODO: check that members all at same Level, and validate that props 737 // exist 738 validateSameLevel((MemberSetPlus) members); 739 return new ChangeMemberPropsCommand( 740 (MemberSetPlus) members, 741 propertyValues); 742 } 743 744 /** 745 * Validates that all members of a member set are the same level. 746 * 747 * @param memberSet Member set 748 * @throws IllegalArgumentException if members are from more than one level 749 */ 750 private void validateSameLevel(MemberSetPlus memberSet) 751 throws IllegalArgumentException 752 { 753 memberSet.accept( 754 new MemberSetVisitor() { 755 final Set<RolapLevel> levelSet = new HashSet<RolapLevel>(); 756 757 private void visitMember( 758 RolapMember member, 759 boolean descendants) 760 { 761 final String message = 762 "all members in set must belong to same level"; 763 if (levelSet.add(member.getLevel()) 764 && levelSet.size() > 1) 765 { 766 throw new IllegalArgumentException(message); 767 } 768 if (descendants 769 && member.getLevel().getChildLevel() != null) 770 { 771 throw new IllegalArgumentException(message); 772 } 773 } 774 775 public void visit(SimpleMemberSet simpleMemberSet) { 776 for (RolapMember member : simpleMemberSet.members) { 777 visitMember(member, simpleMemberSet.descendants); 778 } 779 } 780 781 public void visit(UnionMemberSet unionMemberSet) { 782 for (MemberSetPlus item : unionMemberSet.items) { 783 item.accept(this); 784 } 785 } 786 787 public void visit(RangeMemberSet rangeMemberSet) { 788 visitMember( 789 rangeMemberSet.lowerMember, 790 rangeMemberSet.descendants); 791 visitMember( 792 rangeMemberSet.upperMember, 793 rangeMemberSet.descendants); 794 } 795 } 796 ); 797 } 798 799 public void execute(MemberEditCommand cmd) { 800 final BooleanProperty prop = 801 MondrianProperties.instance().EnableRolapCubeMemberCache; 802 if (prop.get()) { 803 throw new IllegalArgumentException( 804 "Member cache control operations are not allowed unless " 805 + "property " + prop.getPath() + " is false"); 806 } 807 synchronized (MEMBER_CACHE_LOCK) { 808 // Make sure that a Locus is in the Execution stack, 809 // since some operations might require DB access. 810 Execution execution; 811 try { 812 execution = 813 Locus.peek().execution; 814 } catch (EmptyStackException e) { 815 if (connection == null) { 816 throw new IllegalArgumentException("Connection required"); 817 } 818 execution = new Execution(connection.getInternalStatement(), 0); 819 } 820 final Locus locus = new Locus( 821 execution, 822 "CacheControlImpl.execute", 823 "when modifying the member cache."); 824 Locus.push(locus); 825 try { 826 // Execute the command 827 final List<CellRegion> cellRegionList = 828 new ArrayList<CellRegion>(); 829 ((MemberEditCommandPlus) cmd).execute(cellRegionList); 830 831 // Flush the cells touched by the regions 832 for (CellRegion memberRegion : cellRegionList) { 833 // Iterate over the cubes, create a cross region with 834 // its measures, and flush the data cells. 835 // It is possible that some regions don't intersect 836 // with a cube. We will intercept the exceptions and 837 // skip to the next cube if necessary. 838 final List<Dimension> dimensions = 839 memberRegion.getDimensionality(); 840 if (dimensions.size() > 0) { 841 for (Cube cube 842 : dimensions.get(0) .getSchema().getCubes()) 843 { 844 try { 845 final List<CellRegionImpl> crossList = 846 new ArrayList<CellRegionImpl>(); 847 crossList.add( 848 (CellRegionImpl) 849 createMeasuresRegion(cube)); 850 crossList.add((CellRegionImpl) memberRegion); 851 final CellRegion crossRegion = 852 new CrossjoinCellRegion(crossList); 853 flush(crossRegion); 854 } catch (UndeclaredThrowableException e) { 855 if (e.getCause() 856 instanceof InvocationTargetException) 857 { 858 final InvocationTargetException ite = 859 (InvocationTargetException)e.getCause(); 860 if (ite.getTargetException() 861 instanceof MondrianException) 862 { 863 final MondrianException me = 864 (MondrianException) 865 ite.getTargetException(); 866 if (me.getMessage() 867 .matches( 868 "^Mondrian Error:Member " 869 + "'\\[.*\\]' not found$")) 870 { 871 continue; 872 } 873 } 874 } 875 throw new MondrianException(e); 876 } catch (MondrianException e) { 877 if (e.getMessage() 878 .matches( 879 "^Mondrian Error:Member " 880 + "'\\[.*\\]' not found$")) 881 { 882 continue; 883 } 884 throw e; 885 } 886 } 887 } 888 } 889 // Apply it all. 890 ((MemberEditCommandPlus) cmd).commit(); 891 } finally { 892 Locus.pop(locus); 893 } 894 } 895 } 896 897 private static MemberCache getMemberCache(RolapMember member) { 898 final MemberReader memberReader = 899 member.getHierarchy().getMemberReader(); 900 SmartMemberReader smartMemberReader = 901 (SmartMemberReader) memberReader; 902 return smartMemberReader.getMemberCache(); 903 } 904 905 // cell cache control implementation 906 907 /** 908 * Cell region formed by a list of members. 909 * 910 * @see MemberRangeCellRegion 911 */ 912 static class MemberCellRegion implements CellRegionImpl { 913 private final List<Member> memberList; 914 private final Dimension dimension; 915 916 MemberCellRegion(List<Member> memberList, boolean descendants) { 917 assert memberList.size() > 0; 918 this.memberList = memberList; 919 this.dimension = (memberList.get(0)).getDimension(); 920 Util.discard(descendants); 921 } 922 923 public List<Dimension> getDimensionality() { 924 return Collections.singletonList(dimension); 925 } 926 927 public String toString() { 928 return Util.commaList("Member", memberList); 929 } 930 931 public void accept(CellRegionVisitor visitor) { 932 visitor.visit(this); 933 } 934 935 public List<Member> getMemberList() { 936 return memberList; 937 } 938 } 939 940 /** 941 * An empty cell region. 942 */ 943 static class EmptyCellRegion implements CellRegionImpl { 944 public void accept(CellRegionVisitor visitor) { 945 visitor.visit(this); 946 } 947 public List<Dimension> getDimensionality() { 948 return Collections.emptyList(); 949 } 950 } 951 952 /** 953 * Cell region formed a range of members between a lower and upper bound. 954 */ 955 static class MemberRangeCellRegion implements CellRegionImpl { 956 private final RolapMember lowerMember; 957 private final boolean lowerInclusive; 958 private final RolapMember upperMember; 959 private final boolean upperInclusive; 960 private final boolean descendants; 961 private final RolapLevel level; 962 963 MemberRangeCellRegion( 964 RolapMember lowerMember, 965 boolean lowerInclusive, 966 RolapMember upperMember, 967 boolean upperInclusive, 968 boolean descendants) 969 { 970 assert lowerMember != null || upperMember != null; 971 assert lowerMember == null 972 || upperMember == null 973 || lowerMember.getLevel() == upperMember.getLevel(); 974 assert !(lowerMember == null && lowerInclusive); 975 assert !(upperMember == null && upperInclusive); 976 this.lowerMember = lowerMember; 977 this.lowerInclusive = lowerInclusive; 978 this.upperMember = upperMember; 979 this.upperInclusive = upperInclusive; 980 this.descendants = descendants; 981 this.level = 982 lowerMember == null 983 ? upperMember.getLevel() 984 : lowerMember.getLevel(); 985 } 986 987 public List<Dimension> getDimensionality() { 988 return Collections.singletonList(level.getDimension()); 989 } 990 991 public RolapLevel getLevel() { 992 return level; 993 } 994 995 public String toString() { 996 final StringBuilder sb = new StringBuilder("Range("); 997 if (lowerMember == null) { 998 sb.append("null"); 999 } else { 1000 sb.append(lowerMember); 1001 if (lowerInclusive) { 1002 sb.append(" inclusive"); 1003 } else { 1004 sb.append(" exclusive"); 1005 } 1006 } 1007 sb.append(" to "); 1008 if (upperMember == null) { 1009 sb.append("null"); 1010 } else { 1011 sb.append(upperMember); 1012 if (upperInclusive) { 1013 sb.append(" inclusive"); 1014 } else { 1015 sb.append(" exclusive"); 1016 } 1017 } 1018 sb.append(")"); 1019 return sb.toString(); 1020 } 1021 1022 public void accept(CellRegionVisitor visitor) { 1023 visitor.visit(this); 1024 } 1025 1026 public boolean getLowerInclusive() { 1027 return lowerInclusive; 1028 } 1029 1030 public RolapMember getLowerBound() { 1031 return lowerMember; 1032 } 1033 1034 public boolean getUpperInclusive() { 1035 return upperInclusive; 1036 } 1037 1038 public RolapMember getUpperBound() { 1039 return upperMember; 1040 } 1041 } 1042 1043 /** 1044 * Cell region formed by a cartesian product of two or more CellRegions. 1045 */ 1046 static class CrossjoinCellRegion implements CellRegionImpl { 1047 final List<Dimension> dimensions; 1048 private List<CellRegionImpl> components = 1049 new ArrayList<CellRegionImpl>(); 1050 1051 CrossjoinCellRegion(List<CellRegionImpl> regions) { 1052 final List<Dimension> dimensionality = new ArrayList<Dimension>(); 1053 compute(regions, components, dimensionality); 1054 dimensions = Collections.unmodifiableList(dimensionality); 1055 } 1056 1057 private static void compute( 1058 List<CellRegionImpl> regions, 1059 List<CellRegionImpl> components, 1060 List<Dimension> dimensionality) 1061 { 1062 final Set<Dimension> dimensionSet = new HashSet<Dimension>(); 1063 for (CellRegionImpl region : regions) { 1064 addComponents(region, components); 1065 1066 final List<Dimension> regionDimensionality = 1067 region.getDimensionality(); 1068 dimensionality.addAll(regionDimensionality); 1069 dimensionSet.addAll(regionDimensionality); 1070 assert dimensionSet.size() == dimensionality.size() 1071 : "dimensions in common"; 1072 } 1073 } 1074 1075 public void accept(CellRegionVisitor visitor) { 1076 visitor.visit(this); 1077 for (CellRegion component : components) { 1078 CellRegionImpl cellRegion = (CellRegionImpl) component; 1079 cellRegion.accept(visitor); 1080 } 1081 } 1082 1083 private static void addComponents( 1084 CellRegionImpl region, 1085 List<CellRegionImpl> list) 1086 { 1087 if (region instanceof CrossjoinCellRegion) { 1088 CrossjoinCellRegion crossjoinRegion = 1089 (CrossjoinCellRegion) region; 1090 for (CellRegionImpl component : crossjoinRegion.components) { 1091 list.add(component); 1092 } 1093 } else { 1094 list.add(region); 1095 } 1096 } 1097 1098 public List<Dimension> getDimensionality() { 1099 return dimensions; 1100 } 1101 1102 public String toString() { 1103 return Util.commaList("Crossjoin", components); 1104 } 1105 1106 public List<CellRegion> getComponents() { 1107 return Util.cast(components); 1108 } 1109 } 1110 1111 private static class UnionCellRegion implements CellRegionImpl { 1112 private final List<CellRegionImpl> regions; 1113 1114 UnionCellRegion(List<CellRegionImpl> regions) { 1115 this.regions = regions; 1116 assert regions.size() >= 1; 1117 1118 // All regions must have same dimensionality. 1119 for (int i = 1; i < regions.size(); i++) { 1120 final CellRegion region0 = regions.get(0); 1121 final CellRegion region = regions.get(i); 1122 assert region0.getDimensionality().equals( 1123 region.getDimensionality()); 1124 } 1125 } 1126 1127 public List<Dimension> getDimensionality() { 1128 return regions.get(0).getDimensionality(); 1129 } 1130 1131 public String toString() { 1132 return Util.commaList("Union", regions); 1133 } 1134 1135 public void accept(CellRegionVisitor visitor) { 1136 visitor.visit(this); 1137 for (CellRegionImpl cellRegion : regions) { 1138 cellRegion.accept(visitor); 1139 } 1140 } 1141 } 1142 1143 interface CellRegionImpl extends CellRegion { 1144 void accept(CellRegionVisitor visitor); 1145 } 1146 1147 /** 1148 * Visitor that visits various sub-types of 1149 * {@link mondrian.olap.CacheControl.CellRegion}. 1150 */ 1151 interface CellRegionVisitor { 1152 void visit(MemberCellRegion region); 1153 void visit(MemberRangeCellRegion region); 1154 void visit(UnionCellRegion region); 1155 void visit(CrossjoinCellRegion region); 1156 void visit(EmptyCellRegion region); 1157 } 1158 1159 private static class FoundOne extends RuntimeException { 1160 private final transient UnionCellRegion region; 1161 1162 public FoundOne(UnionCellRegion region) { 1163 this.region = region; 1164 } 1165 } 1166 1167 /** 1168 * Default implementation of {@link CellRegionVisitor}. 1169 */ 1170 private static class CellRegionVisitorImpl implements CellRegionVisitor { 1171 public void visit(MemberCellRegion region) { 1172 // nothing 1173 } 1174 1175 public void visit(MemberRangeCellRegion region) { 1176 // nothing 1177 } 1178 1179 public void visit(UnionCellRegion region) { 1180 // nothing 1181 } 1182 1183 public void visit(CrossjoinCellRegion region) { 1184 // nothing 1185 } 1186 1187 public void visit(EmptyCellRegion region) { 1188 // nothing 1189 } 1190 } 1191 1192 1193 // ~ member cache control implementation ---------------------------------- 1194 1195 /** 1196 * Implementation-specific extensions to the 1197 * {@link mondrian.olap.CacheControl.MemberEditCommand} interface. 1198 */ 1199 interface MemberEditCommandPlus extends MemberEditCommand { 1200 /** 1201 * Executes this command, and gathers a list of cell regions affected 1202 * in the {@code cellRegionList} parameter. The caller will flush the 1203 * cell regions later. 1204 * 1205 * @param cellRegionList Populated with a list of cell regions which 1206 * are invalidated by this action 1207 */ 1208 void execute(final List<CellRegion> cellRegionList); 1209 1210 void commit(); 1211 } 1212 1213 /** 1214 * Implementation-specific extensions to the 1215 * {@link mondrian.olap.CacheControl.MemberSet} interface. 1216 */ 1217 interface MemberSetPlus extends MemberSet { 1218 /** 1219 * Accepts a visitor. 1220 * 1221 * @param visitor Visitor 1222 */ 1223 void accept(MemberSetVisitor visitor); 1224 1225 /** 1226 * Filters this member set, returning a member set containing all 1227 * members at a given Level. When applicable, returns this member set 1228 * unchanged. 1229 * 1230 * @param level Level 1231 * @return Member set with members not at the given level removed 1232 */ 1233 MemberSetPlus filter(RolapLevel level); 1234 } 1235 1236 /** 1237 * Visits the subclasses of {@link MemberSetPlus}. 1238 */ 1239 interface MemberSetVisitor { 1240 void visit(SimpleMemberSet s); 1241 void visit(UnionMemberSet s); 1242 void visit(RangeMemberSet s); 1243 } 1244 1245 /** 1246 * Default implementation of {@link MemberSetVisitor}. 1247 * 1248 * <p>The default implementation may not be efficient. For example, if 1249 * flushing a range of members from the cache, you may not wish to fetch 1250 * all of the members into the cache in order to flush them. 1251 */ 1252 public static abstract class MemberSetVisitorImpl 1253 implements MemberSetVisitor 1254 { 1255 public void visit(UnionMemberSet s) { 1256 for (MemberSetPlus item : s.items) { 1257 item.accept(this); 1258 } 1259 } 1260 1261 public void visit(RangeMemberSet s) { 1262 final MemberReader memberReader = 1263 s.level.getHierarchy().getMemberReader(); 1264 visitRange( 1265 memberReader, s.level, s.lowerMember, s.upperMember, 1266 s.descendants); 1267 } 1268 1269 protected void visitRange( 1270 MemberReader memberReader, 1271 RolapLevel level, 1272 RolapMember lowerMember, 1273 RolapMember upperMember, 1274 boolean recurse) 1275 { 1276 final List<RolapMember> list = new ArrayList<RolapMember>(); 1277 memberReader.getMemberRange(level, lowerMember, upperMember, list); 1278 for (RolapMember member : list) { 1279 visit(member); 1280 } 1281 if (recurse) { 1282 list.clear(); 1283 memberReader.getMemberChildren(lowerMember, list); 1284 if (list.isEmpty()) { 1285 return; 1286 } 1287 RolapMember lowerChild = list.get(0); 1288 list.clear(); 1289 memberReader.getMemberChildren(upperMember, list); 1290 if (list.isEmpty()) { 1291 return; 1292 } 1293 RolapMember upperChild = list.get(list.size() - 1); 1294 visitRange( 1295 memberReader, level, lowerChild, upperChild, recurse); 1296 } 1297 } 1298 1299 public void visit(SimpleMemberSet s) { 1300 for (RolapMember member : s.members) { 1301 visit(member); 1302 } 1303 } 1304 1305 /** 1306 * Visits a single member. 1307 * 1308 * @param member Member 1309 */ 1310 public abstract void visit(RolapMember member); 1311 } 1312 1313 /** 1314 * Member set containing no members. 1315 */ 1316 static class EmptyMemberSet implements MemberSetPlus { 1317 public static final EmptyMemberSet INSTANCE = new EmptyMemberSet(); 1318 1319 private EmptyMemberSet() { 1320 // prevent instantiation except for singleton 1321 } 1322 1323 public void accept(MemberSetVisitor visitor) { 1324 // nothing 1325 } 1326 1327 public MemberSetPlus filter(RolapLevel level) { 1328 return this; 1329 } 1330 1331 public String toString() { 1332 return "Empty"; 1333 } 1334 } 1335 1336 /** 1337 * Member set defined by a list of members from one hierarchy. 1338 */ 1339 static class SimpleMemberSet implements MemberSetPlus { 1340 public final List<RolapMember> members; 1341 // the set includes the descendants of all members 1342 public final boolean descendants; 1343 public final RolapHierarchy hierarchy; 1344 1345 SimpleMemberSet(List<RolapMember> members, boolean descendants) { 1346 this.members = new ArrayList<RolapMember>(members); 1347 stripMemberList(this.members); 1348 this.descendants = descendants; 1349 this.hierarchy = 1350 members.isEmpty() 1351 ? null 1352 : members.get(0).getHierarchy(); 1353 } 1354 1355 public String toString() { 1356 return Util.commaList("Member", members); 1357 } 1358 1359 public void accept(MemberSetVisitor visitor) { 1360 // Don't descend the subtrees here: may not want to load them into 1361 // cache. 1362 visitor.visit(this); 1363 } 1364 1365 public MemberSetPlus filter(RolapLevel level) { 1366 List<RolapMember> filteredMembers = new ArrayList<RolapMember>(); 1367 for (RolapMember member : members) { 1368 if (member.getLevel().equals(level)) { 1369 filteredMembers.add(member); 1370 } 1371 } 1372 if (filteredMembers.isEmpty()) { 1373 return EmptyMemberSet.INSTANCE; 1374 } else if (filteredMembers.equals(members)) { 1375 return this; 1376 } else { 1377 return new SimpleMemberSet(filteredMembers, false); 1378 } 1379 } 1380 } 1381 1382 /** 1383 * Member set defined by the union of other member sets. 1384 */ 1385 static class UnionMemberSet implements MemberSetPlus { 1386 private final List<MemberSetPlus> items; 1387 1388 UnionMemberSet(List<MemberSetPlus> items) { 1389 this.items = items; 1390 } 1391 1392 public String toString() { 1393 final StringBuilder sb = new StringBuilder("Union("); 1394 for (int i = 0; i < items.size(); i++) { 1395 if (i > 0) { 1396 sb.append(", "); 1397 } 1398 MemberSetPlus item = items.get(i); 1399 sb.append(item.toString()); 1400 } 1401 sb.append(")"); 1402 return sb.toString(); 1403 } 1404 1405 public void accept(MemberSetVisitor visitor) { 1406 visitor.visit(this); 1407 } 1408 1409 public MemberSetPlus filter(RolapLevel level) { 1410 final List<MemberSetPlus> filteredItems = 1411 new ArrayList<MemberSetPlus>(); 1412 for (MemberSetPlus item : items) { 1413 final MemberSetPlus filteredItem = item.filter(level); 1414 if (filteredItem == EmptyMemberSet.INSTANCE) { 1415 // skip it 1416 } else { 1417 assert !(filteredItem instanceof EmptyMemberSet); 1418 filteredItems.add(filteredItem); 1419 } 1420 } 1421 if (filteredItems.isEmpty()) { 1422 return EmptyMemberSet.INSTANCE; 1423 } else if (filteredItems.equals(items)) { 1424 return this; 1425 } else { 1426 return new UnionMemberSet(filteredItems); 1427 } 1428 } 1429 } 1430 1431 /** 1432 * Member set defined by a range of members between a lower and upper 1433 * bound. 1434 */ 1435 static class RangeMemberSet implements MemberSetPlus { 1436 private final RolapMember lowerMember; 1437 private final boolean lowerInclusive; 1438 private final RolapMember upperMember; 1439 private final boolean upperInclusive; 1440 private final boolean descendants; 1441 private final RolapLevel level; 1442 1443 RangeMemberSet( 1444 RolapMember lowerMember, 1445 boolean lowerInclusive, 1446 RolapMember upperMember, 1447 boolean upperInclusive, 1448 boolean descendants) 1449 { 1450 assert lowerMember != null || upperMember != null; 1451 assert lowerMember == null 1452 || upperMember == null 1453 || lowerMember.getLevel() == upperMember.getLevel(); 1454 assert !(lowerMember == null && lowerInclusive); 1455 assert !(upperMember == null && upperInclusive); 1456 assert !(lowerMember instanceof RolapCubeMember); 1457 assert !(upperMember instanceof RolapCubeMember); 1458 this.lowerMember = lowerMember; 1459 this.lowerInclusive = lowerInclusive; 1460 this.upperMember = upperMember; 1461 this.upperInclusive = upperInclusive; 1462 this.descendants = descendants; 1463 this.level = 1464 lowerMember == null 1465 ? upperMember.getLevel() 1466 : lowerMember.getLevel(); 1467 } 1468 1469 public String toString() { 1470 final StringBuilder sb = new StringBuilder("Range("); 1471 if (lowerMember == null) { 1472 sb.append("null"); 1473 } else { 1474 sb.append(lowerMember); 1475 if (lowerInclusive) { 1476 sb.append(" inclusive"); 1477 } else { 1478 sb.append(" exclusive"); 1479 } 1480 } 1481 sb.append(" to "); 1482 if (upperMember == null) { 1483 sb.append("null"); 1484 } else { 1485 sb.append(upperMember); 1486 if (upperInclusive) { 1487 sb.append(" inclusive"); 1488 } else { 1489 sb.append(" exclusive"); 1490 } 1491 } 1492 sb.append(")"); 1493 return sb.toString(); 1494 } 1495 1496 public void accept(MemberSetVisitor visitor) { 1497 // Don't traverse the range here: may not want to load it into cache 1498 visitor.visit(this); 1499 } 1500 1501 public MemberSetPlus filter(RolapLevel level) { 1502 if (level == this.level) { 1503 return this; 1504 } else { 1505 return filter2(level, this.level, lowerMember, upperMember); 1506 } 1507 } 1508 1509 public MemberSetPlus filter2( 1510 RolapLevel seekLevel, 1511 RolapLevel level, 1512 RolapMember lower, 1513 RolapMember upper) 1514 { 1515 if (level == seekLevel) { 1516 return new RangeMemberSet( 1517 lower, lowerInclusive, upper, upperInclusive, false); 1518 } else if (descendants 1519 && level.getHierarchy() == seekLevel.getHierarchy() 1520 && level.getDepth() < seekLevel.getDepth()) 1521 { 1522 final MemberReader memberReader = 1523 level.getHierarchy().getMemberReader(); 1524 final List<RolapMember> list = new ArrayList<RolapMember>(); 1525 memberReader.getMemberChildren(lower, list); 1526 if (list.isEmpty()) { 1527 return EmptyMemberSet.INSTANCE; 1528 } 1529 RolapMember lowerChild = list.get(0); 1530 list.clear(); 1531 memberReader.getMemberChildren(upper, list); 1532 if (list.isEmpty()) { 1533 return EmptyMemberSet.INSTANCE; 1534 } 1535 RolapMember upperChild = list.get(list.size() - 1); 1536 return filter2( 1537 seekLevel, (RolapLevel) level.getChildLevel(), 1538 lowerChild, upperChild); 1539 } else { 1540 return EmptyMemberSet.INSTANCE; 1541 } 1542 } 1543 } 1544 1545 /** 1546 * Command consisting of a set of commands executed in sequence. 1547 */ 1548 private static class CompoundCommand implements MemberEditCommandPlus { 1549 private final List<MemberEditCommandPlus> commandList; 1550 1551 CompoundCommand(List<MemberEditCommandPlus> commandList) { 1552 this.commandList = commandList; 1553 } 1554 1555 public String toString() { 1556 return Util.commaList("Compound", commandList); 1557 } 1558 1559 public void execute(final List<CellRegion> cellRegionList) { 1560 for (MemberEditCommandPlus command : commandList) { 1561 command.execute(cellRegionList); 1562 } 1563 } 1564 1565 public void commit() { 1566 for (MemberEditCommandPlus command : commandList) { 1567 command.commit(); 1568 } 1569 } 1570 } 1571 1572 /** 1573 * Command that deletes a member and its descendants from the cache. 1574 */ 1575 private class DeleteMemberCommand 1576 extends MemberSetVisitorImpl 1577 implements MemberEditCommandPlus 1578 { 1579 private final MemberSetPlus set; 1580 private List<CellRegion> cellRegionList; 1581 private Callable<Boolean> callable; 1582 1583 DeleteMemberCommand(MemberSetPlus set) { 1584 this.set = set; 1585 } 1586 1587 public String toString() { 1588 return "DeleteMemberCommand(" + set + ")"; 1589 } 1590 1591 public void execute(final List<CellRegion> cellRegionList) { 1592 // NOTE: use of cellRegionList makes this class non-reentrant 1593 this.cellRegionList = cellRegionList; 1594 set.accept(this); 1595 this.cellRegionList = null; 1596 } 1597 1598 public void visit(RolapMember member) { 1599 this.callable = 1600 deleteMember(member, member.getParentMember(), cellRegionList); 1601 } 1602 1603 public void commit() { 1604 try { 1605 callable.call(); 1606 } catch (Exception e) { 1607 throw new MondrianException(e); 1608 } 1609 } 1610 } 1611 1612 /** 1613 * Command that adds a new member to the cache. 1614 */ 1615 private class AddMemberCommand implements MemberEditCommandPlus { 1616 private final RolapMember member; 1617 private Callable<Boolean> callable; 1618 1619 public AddMemberCommand(RolapMember member) { 1620 assert member != null; 1621 this.member = stripMember(member); 1622 } 1623 1624 public String toString() { 1625 return "AddMemberCommand(" + member + ")"; 1626 } 1627 1628 public void execute(List<CellRegion> cellRegionList) { 1629 this.callable = 1630 addMember(member, member.getParentMember(), cellRegionList); 1631 } 1632 1633 public void commit() { 1634 try { 1635 callable.call(); 1636 } catch (Exception e) { 1637 throw new MondrianException(e); 1638 } 1639 } 1640 } 1641 1642 /** 1643 * Command that moves a member to a new parent. 1644 */ 1645 private class MoveMemberCommand implements MemberEditCommandPlus { 1646 private final RolapMember member; 1647 private final RolapMember newParent; 1648 private Callable<Boolean> callable1; 1649 private Callable<Boolean> callable2; 1650 1651 MoveMemberCommand(RolapMember member, RolapMember newParent) { 1652 this.member = member; 1653 this.newParent = newParent; 1654 } 1655 1656 public String toString() { 1657 return "MoveMemberCommand(" + member + ", " + newParent + ")"; 1658 } 1659 1660 public void execute(final List<CellRegion> cellRegionList) { 1661 this.callable1 = 1662 deleteMember(member, member.getParentMember(), cellRegionList); 1663 this.callable2 = 1664 addMember(member, newParent, cellRegionList); 1665 } 1666 1667 public void commit() { 1668 try { 1669 ((RolapMemberBase) member).setParentMember(newParent); 1670 callable1.call(); 1671 ((RolapMemberBase) member).setUniqueName(member.getKey()); 1672 callable2.call(); 1673 } catch (Exception e) { 1674 throw new MondrianException(e); 1675 } 1676 } 1677 } 1678 1679 /** 1680 * Command that changes one or more properties of a member. 1681 */ 1682 private class ChangeMemberPropsCommand 1683 extends MemberSetVisitorImpl 1684 implements MemberEditCommandPlus 1685 { 1686 final MemberSetPlus memberSet; 1687 final Map<String, Object> propertyValues; 1688 final List<RolapMember> members = 1689 new ArrayList<RolapMember>(); 1690 1691 ChangeMemberPropsCommand( 1692 MemberSetPlus memberSet, 1693 Map<String, Object> propertyValues) 1694 { 1695 this.memberSet = memberSet; 1696 this.propertyValues = propertyValues; 1697 } 1698 1699 public String toString() { 1700 return "CreateMemberPropsCommand(" + memberSet 1701 + ", " + propertyValues + ")"; 1702 } 1703 1704 public void execute(List<CellRegion> cellRegionList) { 1705 // ignore cellRegionList - no changes to cell cache 1706 memberSet.accept(this); 1707 } 1708 1709 public void visit(RolapMember member) { 1710 members.add(member); 1711 } 1712 1713 public void commit() { 1714 for (RolapMember member : members) { 1715 // Change member's properties. 1716 member = stripMember(member); 1717 final MemberCache memberCache = getMemberCache(member); 1718 final Object cacheKey = 1719 memberCache.makeKey( 1720 member.getParentMember(), 1721 member.getKey()); 1722 final RolapMember cacheMember = memberCache.getMember(cacheKey); 1723 if (cacheMember == null) { 1724 return; 1725 } 1726 for (Map.Entry<String, Object> entry 1727 : propertyValues.entrySet()) 1728 { 1729 cacheMember.setProperty(entry.getKey(), entry.getValue()); 1730 } 1731 } 1732 } 1733 } 1734 1735 private static RolapMember stripMember(RolapMember member) { 1736 if (member instanceof RolapCubeMember) { 1737 member = ((RolapCubeMember) member).member; 1738 } 1739 return member; 1740 } 1741 1742 private static void stripMemberList(List<RolapMember> members) { 1743 for (int i = 0; i < members.size(); i++) { 1744 RolapMember member = members.get(i); 1745 if (member instanceof RolapCubeMember) { 1746 members.set(i, ((RolapCubeMember) member).member); 1747 } 1748 } 1749 } 1750 1751 private Callable<Boolean> deleteMember( 1752 final RolapMember member, 1753 final RolapMember previousParent, 1754 List<CellRegion> cellRegionList) 1755 { 1756 // Cells for member and its ancestors are now invalid. 1757 // It's sufficient to flush the member. 1758 cellRegionList.add(createMemberRegion(member, true)); 1759 1760 return new Callable<Boolean>() { 1761 public Boolean call() throws Exception { 1762 final MemberCache memberCache = getMemberCache(member); 1763 final MemberChildrenConstraint memberConstraint = 1764 new ChildByNameConstraint( 1765 new Id.NameSegment(member.getName())); 1766 1767 // Remove the member from its parent's lists. First try the 1768 // unconstrained cache. 1769 final List<RolapMember> childrenList = 1770 memberCache.getChildrenFromCache( 1771 previousParent, 1772 DefaultMemberChildrenConstraint.instance()); 1773 if (childrenList != null) { 1774 // A list existed before. Let's splice it. 1775 childrenList.remove(member); 1776 memberCache.putChildren( 1777 previousParent, 1778 DefaultMemberChildrenConstraint.instance(), 1779 childrenList); 1780 } 1781 1782 // Now make sure there is no constrained cache entry 1783 // for this member's parent. 1784 memberCache.putChildren( 1785 previousParent, 1786 memberConstraint, 1787 null); 1788 1789 // Let's update the level members cache. 1790 final List<RolapMember> levelMembers = 1791 memberCache 1792 .getLevelMembersFromCache( 1793 member.getLevel(), 1794 DefaultTupleConstraint.instance()); 1795 if (levelMembers != null) { 1796 levelMembers.remove(member); 1797 memberCache.putChildren( 1798 member.getLevel(), 1799 DefaultTupleConstraint.instance(), 1800 childrenList); 1801 } 1802 1803 // Remove the member itself. The MemberCacheHelper takes care of 1804 // removing the member's children as well. 1805 final Object key = 1806 memberCache.makeKey(previousParent, member.getKey()); 1807 memberCache.removeMember(key); 1808 1809 return true; 1810 } 1811 }; 1812 } 1813 1814 /** 1815 * Adds a member to cache. 1816 * 1817 * @param member Member 1818 * @param parent Member's parent (generally equals member.getParentMember) 1819 * @param cellRegionList List of cell regions to be flushed 1820 * 1821 * @return Callable that yields true when the member has been added to the 1822 * cache 1823 */ 1824 private Callable<Boolean> addMember( 1825 final RolapMember member, 1826 final RolapMember parent, 1827 List<CellRegion> cellRegionList) 1828 { 1829 // Cells for all of member's ancestors are now invalid. It's sufficient 1830 // to flush its parent. 1831 cellRegionList.add(createMemberRegion(parent, false)); 1832 1833 return new Callable<Boolean>() { 1834 public Boolean call() throws Exception { 1835 final MemberCache memberCache = getMemberCache(member); 1836 final MemberChildrenConstraint memberConstraint = 1837 new ChildByNameConstraint( 1838 new Id.NameSegment(member.getName())); 1839 1840 // Check if there is already a list in cache 1841 // constrained by a wildcard. 1842 List<RolapMember> childrenList = 1843 memberCache.getChildrenFromCache( 1844 parent, 1845 DefaultMemberChildrenConstraint.instance()); 1846 if (childrenList == null) { 1847 // There was no cached list. We can ignore. 1848 } else { 1849 // A list existed before. We can save a SQL query. 1850 // Might be immutable. Let's append to it. 1851 if (childrenList.isEmpty()) { 1852 childrenList = new ArrayList<RolapMember>(); 1853 } 1854 childrenList.add(member); 1855 memberCache.putChildren( 1856 parent, 1857 memberConstraint, 1858 childrenList); 1859 } 1860 1861 final List<RolapMember> levelMembers = 1862 memberCache 1863 .getLevelMembersFromCache( 1864 member.getLevel(), 1865 DefaultTupleConstraint.instance()); 1866 if (levelMembers != null) { 1867 // There was already a cached list. 1868 // Let's append to it. 1869 levelMembers.add(member); 1870 memberCache.putChildren( 1871 member.getLevel(), 1872 DefaultTupleConstraint.instance(), 1873 levelMembers); 1874 } 1875 1876 // Now add the member itself into cache 1877 final Object memberKey = 1878 memberCache.makeKey( 1879 member.getParentMember(), 1880 member.getKey()); 1881 memberCache.putMember(memberKey, member); 1882 1883 return true; 1884 } 1885 }; 1886 } 1887 1888 /** 1889 * Removes a member from cache. 1890 * 1891 * @param member Member 1892 * @param cellRegionList Populated with cell region to be flushed 1893 */ 1894 private void flushMember( 1895 RolapMember member, 1896 List<CellRegion> cellRegionList) 1897 { 1898 final MemberCache memberCache = getMemberCache(member); 1899 final Object key = 1900 memberCache.makeKey(member.getParentMember(), member.getKey()); 1901 memberCache.removeMember(key); 1902 cellRegionList.add(createMemberRegion(member, false)); 1903 } 1904} 1905 1906// End CacheControlImpl.java