001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/CacheControlImpl.java#3 $
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-release/3.0/src/main/mondrian/rolap/CacheControlImpl.java#3 $
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 Member[] measures =
134 cube.getSchemaReader(null).getLevelMembers(
135 measuresDimension.getHierarchy().getLevels()[0],
136 false);
137 return new MemberCellRegion(Arrays.asList(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 member : measuresList) {
396 RolapStoredMeasure measure = (RolapStoredMeasure) member;
397 final RolapStar.Measure starMeasure =
398 (RolapStar.Measure) measure.getStarMeasure();
399 if (!starList.contains(starMeasure.getStar())) {
400 starList.add(starMeasure.getStar());
401 }
402 }
403 return starList;
404 }
405
406 public void printCacheState(
407 PrintWriter pw,
408 CellRegion region)
409 {
410 List<RolapStar> starList = getStarList(region);
411 for (RolapStar star : starList) {
412 star.print(pw, "", false);
413 }
414 }
415
416 public MemberSet createMemberSet(Member member, boolean descendants)
417 {
418 return new SimpleMemberSet(
419 Collections.singletonList((RolapMember) member),
420 descendants);
421 }
422
423 public MemberSet createMemberSet(
424 boolean lowerInclusive,
425 Member lowerMember,
426 boolean upperInclusive,
427 Member upperMember,
428 boolean descendants)
429 {
430 // TODO check that upperMember & lowerMember are in same Level
431 if (lowerMember == null) {
432 lowerInclusive = false;
433 }
434 if (upperMember == null) {
435 upperInclusive = false;
436 }
437 return new RangeMemberSet(
438 stripMember((RolapMember) lowerMember), lowerInclusive,
439 stripMember((RolapMember) upperMember), upperInclusive,
440 descendants);
441 }
442
443 public MemberSet createUnionSet(MemberSet... args)
444 {
445 //noinspection unchecked
446 return new UnionMemberSet((List) Arrays.asList(args));
447 }
448
449 public MemberSet filter(Level level, MemberSet baseSet) {
450 if (level instanceof RolapCubeLevel) {
451 // be forgiving
452 level = ((RolapCubeLevel) level).getRolapLevel();
453 }
454 return ((MemberSetPlus) baseSet).filter((RolapLevel) level);
455 }
456
457 public void flush(MemberSet memberSet) {
458 // REVIEW How is flush(s) different to executing createDeleteCommand(s) ?
459 synchronized (MEMBER_CACHE_LOCK) {
460 final List<CellRegion> cellRegionList = new ArrayList<CellRegion>();
461 ((MemberSetPlus) memberSet).accept(
462 new MemberSetVisitorImpl() {
463 public void visit(RolapMember member) {
464 flushMember(member, cellRegionList);
465 }
466 }
467 );
468 // STUB: flush the set: another visitor
469
470 // finally, flush cells now invalid
471 flushRegionList(cellRegionList);
472 }
473 }
474
475 public void printCacheState(PrintWriter pw, MemberSet set)
476 {
477 synchronized (MEMBER_CACHE_LOCK) {
478 pw.println("need to implement printCacheState"); // TODO:
479 }
480 }
481
482 public MemberEditCommand createCompoundCommand(
483 List<MemberEditCommand> commandList)
484 {
485 //noinspection unchecked
486 return new CompoundCommand((List) commandList);
487 }
488
489 public MemberEditCommand createCompoundCommand(
490 MemberEditCommand... commands)
491 {
492 //noinspection unchecked
493 return new CompoundCommand((List) Arrays.asList(commands));
494 }
495
496 public MemberEditCommand createDeleteCommand(Member member) {
497 if (member == null) {
498 throw new IllegalArgumentException("cannot delete null member");
499 }
500 if (((RolapLevel) member.getLevel()).isParentChild()) {
501 throw new IllegalArgumentException(
502 "delete member not supported for parent-child hierarchy");
503 }
504 return createDeleteCommand(createMemberSet(member, false));
505 }
506
507 public MemberEditCommand createDeleteCommand(MemberSet s) {
508 return new DeleteMemberCommand((MemberSetPlus) s);
509 }
510
511 public MemberEditCommand createAddCommand(
512 Member member) throws IllegalArgumentException
513 {
514 if (member == null) {
515 throw new IllegalArgumentException("cannot add null member");
516 }
517 if (((RolapLevel) member.getLevel()).isParentChild()) {
518 throw new IllegalArgumentException(
519 "add member not supported for parent-child hierarchy");
520 }
521 return new AddMemberCommand((RolapMember) member);
522 }
523
524 public MemberEditCommand createMoveCommand(Member member, Member loc)
525 throws IllegalArgumentException
526 {
527 if (member == null) {
528 throw new IllegalArgumentException("cannot move null member");
529 }
530 if (((RolapLevel) member.getLevel()).isParentChild()) {
531 throw new IllegalArgumentException(
532 "move member not supported for parent-child hierarchy");
533 }
534 if (loc == null) {
535 throw new IllegalArgumentException("cannot move member to null location");
536 }
537 // TODO: check that MEMBER and LOC (its new parent) have appropriate Levels
538 return new MoveMemberCommand((RolapMember) member, (RolapMember) loc);
539 }
540
541 public MemberEditCommand createSetPropertyCommand(
542 Member member,
543 String name,
544 Object value)
545 throws IllegalArgumentException
546 {
547 if (member == null) {
548 throw new IllegalArgumentException("cannot set properties on null member");
549 }
550 if (((RolapLevel) member.getLevel()).isParentChild()) {
551 throw new IllegalArgumentException(
552 "set properties not supported for parent-child hierarchy");
553 }
554 // TODO: validate that prop NAME exists for Level of MEMBER
555 return new ChangeMemberPropsCommand(
556 new SimpleMemberSet(
557 Collections.singletonList((RolapMember) member),
558 false),
559 Collections.singletonMap(name, value));
560 }
561
562 public MemberEditCommand createSetPropertyCommand(
563 MemberSet members,
564 Map<String, Object> propertyValues)
565 throws IllegalArgumentException
566 {
567 // TODO: check that members all at same Level, and validate that props exist
568 validateSameLevel((MemberSetPlus) members);
569 return new ChangeMemberPropsCommand(
570 (MemberSetPlus) members,
571 propertyValues);
572 }
573
574 /**
575 * Validates that all members of a member set are the same level.
576 *
577 * @param memberSet Member set
578 * @throws IllegalArgumentException if members are from more than one level
579 */
580 private void validateSameLevel(MemberSetPlus memberSet)
581 throws IllegalArgumentException
582 {
583 memberSet.accept(
584 new MemberSetVisitor() {
585 final Set<RolapLevel> levelSet = new HashSet<RolapLevel>();
586
587 private void visitMember(
588 RolapMember member,
589 boolean descendants)
590 {
591 final String message =
592 "all members in set must belong to same level";
593 if (levelSet.add(member.getLevel())
594 && levelSet.size() > 1) {
595 throw new IllegalArgumentException(message);
596 }
597 if (descendants
598 && member.getLevel().getChildLevel() != null) {
599 throw new IllegalArgumentException(message);
600 }
601 }
602
603 public void visit(SimpleMemberSet simpleMemberSet) {
604 for (RolapMember member : simpleMemberSet.members) {
605 visitMember(member, simpleMemberSet.descendants);
606 }
607 }
608
609 public void visit(UnionMemberSet unionMemberSet) {
610 for (MemberSetPlus item : unionMemberSet.items) {
611 item.accept(this);
612 }
613 }
614
615 public void visit(RangeMemberSet rangeMemberSet) {
616 visitMember(
617 rangeMemberSet.lowerMember,
618 rangeMemberSet.descendants);
619 visitMember(
620 rangeMemberSet.upperMember,
621 rangeMemberSet.descendants);
622 }
623 }
624 );
625 }
626
627 public void execute(MemberEditCommand cmd) {
628 final BooleanProperty prop =
629 MondrianProperties.instance().EnableRolapCubeMemberCache;
630 if (prop.get()) {
631 throw new IllegalArgumentException(
632 "Member cache control operations are not allowed unless "
633 + "property " + prop.getPath() + " is false");
634 }
635 synchronized (MEMBER_CACHE_LOCK) {
636 final List<CellRegion> cellRegionList =
637 new ArrayList<CellRegion>();
638 ((MemberEditCommandPlus) cmd).execute(cellRegionList);
639 if (false) {
640 // TODO: Flushing regions currently fails with the error
641 // "Region of cells to be flushed must contain measures". This
642 // method should receive a cube (or cubes) whose measures to
643 // flush.
644 flushRegionList(cellRegionList);
645 }
646 }
647 }
648
649 private static MemberCache getMemberCache(RolapMember member) {
650 final MemberReader memberReader =
651 member.getHierarchy().getMemberReader();
652 SmartMemberReader smartMemberReader =
653 (SmartMemberReader) memberReader;
654 return smartMemberReader.getMemberCache();
655 }
656
657 // cell cache control implementation
658
659 /**
660 * Cell region formed by a list of members.
661 *
662 * @see MemberRangeCellRegion
663 */
664 static class MemberCellRegion implements CellRegionImpl {
665 private final List<Member> memberList;
666 private final Dimension dimension;
667 private final boolean descendants;
668
669 MemberCellRegion(List<Member> memberList, boolean descendants) {
670 assert memberList.size() > 0;
671 this.memberList = memberList;
672 this.dimension = (memberList.get(0)).getDimension();
673 this.descendants = descendants;
674 }
675
676 public List<Dimension> getDimensionality() {
677 return Collections.singletonList(dimension);
678 }
679
680 public String toString() {
681 return Util.commaList("Member", memberList);
682 }
683
684 public void accept(CellRegionVisitor visitor) {
685 visitor.visit(this);
686 }
687
688 public List<Member> getMemberList() {
689 return memberList;
690 }
691 }
692
693 /**
694 * Cell region formed a range of members between a lower and upper bound.
695 */
696 static class MemberRangeCellRegion implements CellRegionImpl {
697 private final RolapMember lowerMember;
698 private final boolean lowerInclusive;
699 private final RolapMember upperMember;
700 private final boolean upperInclusive;
701 private final boolean descendants;
702 private final RolapLevel level;
703
704 MemberRangeCellRegion(
705 RolapMember lowerMember,
706 boolean lowerInclusive,
707 RolapMember upperMember,
708 boolean upperInclusive,
709 boolean descendants)
710 {
711 assert lowerMember != null || upperMember != null;
712 assert lowerMember == null
713 || upperMember == null
714 || lowerMember.getLevel() == upperMember.getLevel();
715 assert !(lowerMember == null && lowerInclusive);
716 assert !(upperMember == null && upperInclusive);
717 this.lowerMember = lowerMember;
718 this.lowerInclusive = lowerInclusive;
719 this.upperMember = upperMember;
720 this.upperInclusive = upperInclusive;
721 this.descendants = descendants;
722 this.level = lowerMember == null ?
723 upperMember.getLevel() :
724 lowerMember.getLevel();
725 }
726
727 public List<Dimension> getDimensionality() {
728 return Collections.singletonList(level.getDimension());
729 }
730
731 public RolapLevel getLevel() {
732 return level;
733 }
734
735 public String toString() {
736 final StringBuilder sb = new StringBuilder("Range(");
737 if (lowerMember == null) {
738 sb.append("null");
739 } else {
740 sb.append(lowerMember);
741 if (lowerInclusive) {
742 sb.append(" inclusive");
743 } else {
744 sb.append(" exclusive");
745 }
746 }
747 sb.append(" to ");
748 if (upperMember == null) {
749 sb.append("null");
750 } else {
751 sb.append(upperMember);
752 if (upperInclusive) {
753 sb.append(" inclusive");
754 } else {
755 sb.append(" exclusive");
756 }
757 }
758 sb.append(")");
759 return sb.toString();
760 }
761
762 public void accept(CellRegionVisitor visitor) {
763 visitor.visit(this);
764 }
765
766 public boolean getLowerInclusive() {
767 return lowerInclusive;
768 }
769
770 public RolapMember getLowerBound() {
771 return lowerMember;
772 }
773
774 public boolean getUpperInclusive() {
775 return upperInclusive;
776 }
777
778 public RolapMember getUpperBound() {
779 return upperMember;
780 }
781 }
782
783 /**
784 * Cell region formed by a cartesian product of two or more CellRegions.
785 */
786 static class CrossjoinCellRegion implements CellRegionImpl {
787 final List<Dimension> dimensions;
788 private List<CellRegionImpl> components =
789 new ArrayList<CellRegionImpl>();
790
791 CrossjoinCellRegion(List<CellRegionImpl> regions) {
792 final List<Dimension> dimensionality = new ArrayList<Dimension>();
793 compute(regions, components, dimensionality);
794 dimensions = Collections.unmodifiableList(dimensionality);
795 }
796
797 private static void compute(
798 List<CellRegionImpl> regions,
799 List<CellRegionImpl> components,
800 List<Dimension> dimensionality)
801 {
802 final Set<Dimension> dimensionSet = new HashSet<Dimension>();
803 for (CellRegionImpl region : regions) {
804 addComponents(region, components);
805
806 final List<Dimension> regionDimensionality =
807 region.getDimensionality();
808 dimensionality.addAll(regionDimensionality);
809 dimensionSet.addAll(regionDimensionality);
810 assert dimensionSet.size() == dimensionality.size() :
811 "dimensions in common";
812 }
813 }
814
815 public void accept(CellRegionVisitor visitor) {
816 visitor.visit(this);
817 for (CellRegion component : components) {
818 CellRegionImpl cellRegion = (CellRegionImpl) component;
819 cellRegion.accept(visitor);
820 }
821 }
822
823 private static void addComponents(
824 CellRegionImpl region,
825 List<CellRegionImpl> list)
826 {
827 if (region instanceof CrossjoinCellRegion) {
828 CrossjoinCellRegion crossjoinRegion =
829 (CrossjoinCellRegion) region;
830 for (CellRegionImpl component : crossjoinRegion.components) {
831 list.add(component);
832 }
833 } else {
834 list.add(region);
835 }
836 }
837
838 public List<Dimension> getDimensionality() {
839 return dimensions;
840 }
841
842 public String toString() {
843 return Util.commaList("Crossjoin", components);
844 }
845
846 public List<CellRegion> getComponents() {
847 return Util.cast(components);
848 }
849 }
850
851 private static class UnionCellRegion implements CellRegionImpl {
852 private final List<CellRegionImpl> regions;
853
854 UnionCellRegion(List<CellRegionImpl> regions) {
855 this.regions = regions;
856 assert regions.size() >= 1;
857
858 // All regions must have same dimensionality.
859 for (int i = 1; i < regions.size(); i++) {
860 final CellRegion region0 = regions.get(0);
861 final CellRegion region = regions.get(i);
862 assert region0.getDimensionality().equals(
863 region.getDimensionality());
864 }
865 }
866
867 public List<Dimension> getDimensionality() {
868 return regions.get(0).getDimensionality();
869 }
870
871 public String toString() {
872 return Util.commaList("Union", regions);
873 }
874
875 public void accept(CellRegionVisitor visitor) {
876 visitor.visit(this);
877 for (CellRegionImpl cellRegion : regions) {
878 cellRegion.accept(visitor);
879 }
880 }
881 }
882
883 interface CellRegionImpl extends CellRegion {
884 void accept(CellRegionVisitor visitor);
885 }
886
887 /**
888 * Visitor which visits various sub-types of {@link CellRegion}.
889 */
890 interface CellRegionVisitor {
891 void visit(MemberCellRegion region);
892 void visit(MemberRangeCellRegion region);
893 void visit(UnionCellRegion region);
894 void visit(CrossjoinCellRegion region);
895 }
896
897 private static class FoundOne extends RuntimeException {
898 private final transient UnionCellRegion region;
899
900 public FoundOne(UnionCellRegion region) {
901 this.region = region;
902 }
903 }
904
905 /**
906 * Default implementation of {@link CellRegionVisitor}.
907 */
908 private static class CellRegionVisitorImpl implements CellRegionVisitor {
909 public void visit(MemberCellRegion region) {
910 // nothing
911 }
912
913 public void visit(MemberRangeCellRegion region) {
914 // nothing
915 }
916
917 public void visit(UnionCellRegion region) {
918 // nothing
919 }
920
921 public void visit(CrossjoinCellRegion region) {
922 // nothing
923 }
924 }
925
926
927 // ~ member cache control implementation ----------------------------------
928
929 /**
930 * Implementation-specific extensions to the
931 * {@link mondrian.olap.CacheControl.MemberEditCommand} interface.
932 */
933 interface MemberEditCommandPlus extends MemberEditCommand {
934 /**
935 * Executes this command, and gathers a list of cell regions affected
936 * in the {@code cellRegionList} parameter. The caller will flush the
937 * cell regions later.
938 *
939 * @param cellRegionList Populated with a list of cell regions which
940 * are invalidated by this action
941 */
942 void execute(final List<CellRegion> cellRegionList);
943 }
944
945 /**
946 * Implementation-specific extensions to the
947 * {@link mondrian.olap.CacheControl.MemberSet} interface.
948 */
949 interface MemberSetPlus extends MemberSet {
950 /**
951 * Accepts a visitor.
952 *
953 * @param visitor Visitor
954 */
955 void accept(MemberSetVisitor visitor);
956
957 /**
958 * Filters this member set, returning a member set containing all
959 * members at a given Level. When applicable, returns this member set
960 * unchanged.
961 *
962 * @param level Level
963 * @return Member set with members not at the given level removed
964 */
965 MemberSetPlus filter(RolapLevel level);
966 }
967
968 /**
969 * Visits the subclasses of {@link MemberSetPlus}.
970 */
971 interface MemberSetVisitor {
972 void visit(SimpleMemberSet s);
973 void visit(UnionMemberSet s);
974 void visit(RangeMemberSet s);
975 }
976
977 /**
978 * Default implementation of {@link MemberSetVisitor}.
979 *
980 * <p>The default implementation may not be efficient. For example, if
981 * flushing a range of members from the cache, you may not wish to fetch
982 * all of the members into the cache in order to flush them.
983 */
984 public static abstract class MemberSetVisitorImpl
985 implements MemberSetVisitor
986 {
987 public void visit(UnionMemberSet s) {
988 for (MemberSetPlus item : s.items) {
989 item.accept(this);
990 }
991 }
992
993 public void visit(RangeMemberSet s) {
994 final MemberReader memberReader =
995 s.level.getHierarchy().getMemberReader();
996 visitRange(
997 memberReader, s.level, s.lowerMember, s.upperMember,
998 s.descendants);
999 }
1000
1001 protected void visitRange(
1002 MemberReader memberReader,
1003 RolapLevel level,
1004 RolapMember lowerMember,
1005 RolapMember upperMember,
1006 boolean recurse)
1007 {
1008 final List<RolapMember> list = new ArrayList<RolapMember>();
1009 memberReader.getMemberRange(level, lowerMember, upperMember, list);
1010 for (RolapMember member : list) {
1011 visit(member);
1012 }
1013 if (recurse) {
1014 list.clear();
1015 memberReader.getMemberChildren(lowerMember, list);
1016 if (list.isEmpty()) {
1017 return;
1018 }
1019 RolapMember lowerChild = list.get(0);
1020 list.clear();
1021 memberReader.getMemberChildren(upperMember, list);
1022 if (list.isEmpty()) {
1023 return;
1024 }
1025 RolapMember upperChild = list.get(list.size() - 1);
1026 visitRange(
1027 memberReader, level, lowerChild, upperChild, recurse);
1028 }
1029 }
1030
1031 public void visit(SimpleMemberSet s) {
1032 for (RolapMember member : s.members) {
1033 visit(member);
1034 }
1035 }
1036
1037 /**
1038 * Visits a single member.
1039 *
1040 * @param member Member
1041 */
1042 public abstract void visit(RolapMember member);
1043 }
1044
1045 /**
1046 * Member set containing no members.
1047 */
1048 static class EmptyMemberSet implements MemberSetPlus {
1049 public static final EmptyMemberSet INSTANCE = new EmptyMemberSet();
1050
1051 private EmptyMemberSet() {
1052 // prevent instantiation except for singleton
1053 }
1054
1055 public void accept(MemberSetVisitor visitor) {
1056 // nothing
1057 }
1058
1059 public MemberSetPlus filter(RolapLevel level) {
1060 return this;
1061 }
1062
1063 public String toString() {
1064 return "Empty";
1065 }
1066 }
1067
1068 /**
1069 * Member set defined by a list of members from one hierarchy.
1070 */
1071 static class SimpleMemberSet implements MemberSetPlus {
1072 public final List<RolapMember> members;
1073 public final boolean descendants; // the set includes the descendants of all members
1074 public final RolapHierarchy hierarchy;
1075
1076 SimpleMemberSet(List<RolapMember> members, boolean descendants) {
1077 this.members = new ArrayList<RolapMember>(members);
1078 stripMemberList(this.members);
1079 this.descendants = descendants;
1080 this.hierarchy =
1081 members.isEmpty()
1082 ? null
1083 : members.get(0).getHierarchy();
1084 }
1085
1086 public String toString() {
1087 return Util.commaList("Member", members);
1088 }
1089
1090 public void accept(MemberSetVisitor visitor) {
1091 // Don't descend the subtrees here: may not want to load them into
1092 // cache.
1093 visitor.visit(this);
1094 }
1095
1096 public MemberSetPlus filter(RolapLevel level) {
1097 List<RolapMember> filteredMembers = new ArrayList<RolapMember>();
1098 for (RolapMember member : members) {
1099 if (member.getLevel().equals(level)) {
1100 filteredMembers.add(member);
1101 }
1102 }
1103 if (filteredMembers.isEmpty()) {
1104 return EmptyMemberSet.INSTANCE;
1105 } else if (filteredMembers.equals(members)) {
1106 return this;
1107 } else {
1108 return new SimpleMemberSet(filteredMembers, false);
1109 }
1110 }
1111 }
1112
1113 /**
1114 * Member set defined by the union of other member sets.
1115 */
1116 static class UnionMemberSet implements MemberSetPlus {
1117 private final List<MemberSetPlus> items;
1118
1119 UnionMemberSet(List<MemberSetPlus> items) {
1120 this.items = items;
1121 }
1122
1123 public String toString() {
1124 final StringBuilder sb = new StringBuilder("Union(");
1125 for (int i = 0; i < items.size(); i++) {
1126 if (i > 0) {
1127 sb.append(", ");
1128 }
1129 MemberSetPlus item = items.get(i);
1130 sb.append(item.toString());
1131 }
1132 sb.append(")");
1133 return sb.toString();
1134 }
1135
1136 public void accept(MemberSetVisitor visitor) {
1137 visitor.visit(this);
1138 }
1139
1140 public MemberSetPlus filter(RolapLevel level) {
1141 final List<MemberSetPlus> filteredItems =
1142 new ArrayList<MemberSetPlus>();
1143 for (MemberSetPlus item : items) {
1144 final MemberSetPlus filteredItem = item.filter(level);
1145 if (filteredItem == EmptyMemberSet.INSTANCE) {
1146 // skip it
1147 } else {
1148 assert !(filteredItem instanceof EmptyMemberSet);
1149 filteredItems.add(filteredItem);
1150 }
1151 }
1152 if (filteredItems.isEmpty()) {
1153 return EmptyMemberSet.INSTANCE;
1154 } else if (filteredItems.equals(items)) {
1155 return this;
1156 } else {
1157 return new UnionMemberSet(filteredItems);
1158 }
1159 }
1160 }
1161
1162 /**
1163 * Member set defined by a range of members between a lower and upper
1164 * bound.
1165 */
1166 static class RangeMemberSet implements MemberSetPlus {
1167 private final RolapMember lowerMember;
1168 private final boolean lowerInclusive;
1169 private final RolapMember upperMember;
1170 private final boolean upperInclusive;
1171 private final boolean descendants;
1172 private final RolapLevel level;
1173
1174 RangeMemberSet(
1175 RolapMember lowerMember,
1176 boolean lowerInclusive,
1177 RolapMember upperMember,
1178 boolean upperInclusive,
1179 boolean descendants)
1180 {
1181 assert lowerMember != null || upperMember != null;
1182 assert lowerMember == null
1183 || upperMember == null
1184 || lowerMember.getLevel() == upperMember.getLevel();
1185 assert !(lowerMember == null && lowerInclusive);
1186 assert !(upperMember == null && upperInclusive);
1187 assert !(lowerMember instanceof RolapCubeMember);
1188 assert !(upperMember instanceof RolapCubeMember);
1189 this.lowerMember = lowerMember;
1190 this.lowerInclusive = lowerInclusive;
1191 this.upperMember = upperMember;
1192 this.upperInclusive = upperInclusive;
1193 this.descendants = descendants;
1194 this.level = lowerMember == null ?
1195 upperMember.getLevel() :
1196 lowerMember.getLevel();
1197 }
1198
1199 public String toString() {
1200 final StringBuilder sb = new StringBuilder("Range(");
1201 if (lowerMember == null) {
1202 sb.append("null");
1203 } else {
1204 sb.append(lowerMember);
1205 if (lowerInclusive) {
1206 sb.append(" inclusive");
1207 } else {
1208 sb.append(" exclusive");
1209 }
1210 }
1211 sb.append(" to ");
1212 if (upperMember == null) {
1213 sb.append("null");
1214 } else {
1215 sb.append(upperMember);
1216 if (upperInclusive) {
1217 sb.append(" inclusive");
1218 } else {
1219 sb.append(" exclusive");
1220 }
1221 }
1222 sb.append(")");
1223 return sb.toString();
1224 }
1225
1226 public void accept(MemberSetVisitor visitor) {
1227 // Don't traverse the range here: may not want to load it into cache
1228 visitor.visit(this);
1229 }
1230
1231 public MemberSetPlus filter(RolapLevel level) {
1232 if (level == this.level) {
1233 return this;
1234 } else {
1235 return filter2(level, this.level, lowerMember, upperMember);
1236 }
1237 }
1238
1239 public MemberSetPlus filter2(
1240 RolapLevel seekLevel,
1241 RolapLevel level,
1242 RolapMember lower,
1243 RolapMember upper)
1244 {
1245 if (level == seekLevel) {
1246 return new RangeMemberSet(
1247 lower, lowerInclusive, upper, upperInclusive, false);
1248 } else if (descendants
1249 && level.getHierarchy() == seekLevel.getHierarchy()
1250 && level.getDepth() < seekLevel.getDepth())
1251 {
1252 final MemberReader memberReader =
1253 level.getHierarchy().getMemberReader();
1254 final List<RolapMember> list = new ArrayList<RolapMember>();
1255 memberReader.getMemberChildren(lower, list);
1256 if (list.isEmpty()) {
1257 return EmptyMemberSet.INSTANCE;
1258 }
1259 RolapMember lowerChild = list.get(0);
1260 list.clear();
1261 memberReader.getMemberChildren(upper, list);
1262 if (list.isEmpty()) {
1263 return EmptyMemberSet.INSTANCE;
1264 }
1265 RolapMember upperChild = list.get(list.size() - 1);
1266 return filter2(
1267 seekLevel, (RolapLevel) level.getChildLevel(),
1268 lowerChild, upperChild);
1269 } else {
1270 return EmptyMemberSet.INSTANCE;
1271 }
1272 }
1273 }
1274
1275 /**
1276 * Command consisting of a set of commands executed in sequence.
1277 */
1278 private static class CompoundCommand implements MemberEditCommandPlus {
1279 private final List<MemberEditCommandPlus> commandList;
1280
1281 CompoundCommand(List<MemberEditCommandPlus> commandList) {
1282 this.commandList = commandList;
1283 }
1284
1285 public String toString() {
1286 return Util.commaList("Compound", commandList);
1287 }
1288
1289 public void execute(final List<CellRegion> cellRegionList) {
1290 for (MemberEditCommandPlus command : commandList) {
1291 command.execute(cellRegionList);
1292 }
1293 }
1294 }
1295
1296 /**
1297 * Command that deletes a member and its descendants from the cache.
1298 */
1299 private class DeleteMemberCommand
1300 extends MemberSetVisitorImpl
1301 implements MemberEditCommandPlus
1302 {
1303 private final MemberSetPlus set;
1304 private List<CellRegion> cellRegionList;
1305
1306 DeleteMemberCommand(MemberSetPlus set) {
1307 this.set = set;
1308 }
1309
1310 public String toString() {
1311 return "DeleteMemberCommand(" + set + ")";
1312 }
1313
1314 public void execute(final List<CellRegion> cellRegionList) {
1315 // NOTE: use of member makes this class non-reentrant
1316 this.cellRegionList = cellRegionList;
1317 set.accept(this);
1318 this.cellRegionList = null;
1319 }
1320
1321 public void visit(RolapMember member) {
1322 deleteMember(member, member.getParentMember(), cellRegionList);
1323 }
1324 }
1325
1326 /**
1327 * Command that adds a new member to the cache.
1328 */
1329 private class AddMemberCommand implements MemberEditCommandPlus {
1330 private final RolapMember member;
1331
1332 public AddMemberCommand(RolapMember member) {
1333 assert member != null;
1334 this.member = stripMember(member);
1335 }
1336
1337 public String toString() {
1338 return "AddMemberCommand(" + member + ")";
1339 }
1340
1341 public void execute(List<CellRegion> cellRegionList) {
1342 addMember(member, member.getParentMember(), cellRegionList);
1343 }
1344 }
1345
1346 /**
1347 * Command that moves a member to a new parent.
1348 */
1349 private class MoveMemberCommand implements MemberEditCommandPlus {
1350 private final RolapMember member;
1351 private final RolapMember newParent;
1352
1353 MoveMemberCommand(RolapMember member, RolapMember newParent) {
1354 this.member = member;
1355 this.newParent = newParent;
1356 }
1357
1358 public String toString() {
1359 return "MoveMemberCommand(" + member + ", " + newParent + ")";
1360 }
1361
1362 public void execute(final List<CellRegion> cellRegionList) {
1363 deleteMember(member, member.getParentMember(), cellRegionList);
1364 member.setParentMember(newParent);
1365 addMember(member, member.getParentMember(), cellRegionList);
1366 }
1367 }
1368
1369 /**
1370 * Command that changes one or more properties of a member.
1371 */
1372 private class ChangeMemberPropsCommand
1373 extends MemberSetVisitorImpl
1374 implements MemberEditCommandPlus
1375 {
1376 final MemberSetPlus memberSet;
1377 final Map<String, Object> propertyValues;
1378
1379 ChangeMemberPropsCommand(
1380 MemberSetPlus memberSet,
1381 Map<String, Object> propertyValues)
1382 {
1383 this.memberSet = memberSet;
1384 this.propertyValues = propertyValues;
1385 }
1386
1387 public String toString() {
1388 return "CreateMemberPropsCommand(" + memberSet
1389 + ", " + propertyValues + ")";
1390 }
1391
1392 public void execute(List<CellRegion> cellRegionList) {
1393 // ignore cellRegionList - no changes to cell cache
1394 memberSet.accept(this);
1395 }
1396
1397 public void visit(RolapMember member) {
1398 // Change member's properties.
1399 member = stripMember(member);
1400 final MemberCache memberCache = getMemberCache(member);
1401 final Object cacheKey =
1402 memberCache.makeKey(
1403 member.getParentMember(),
1404 member.getKey());
1405 final RolapMember cacheMember = memberCache.getMember(cacheKey);
1406 if (cacheMember == null) {
1407 return;
1408 }
1409 for (Map.Entry<String, Object> entry : propertyValues.entrySet()) {
1410 cacheMember.setProperty(entry.getKey(), entry.getValue());
1411 }
1412 }
1413 }
1414
1415 private static RolapMember stripMember(RolapMember member) {
1416 if (member instanceof RolapCubeMember) {
1417 member = ((RolapCubeMember) member).rolapMember;
1418 }
1419 return member;
1420 }
1421
1422 private static void stripMemberList(List<RolapMember> members) {
1423 for (int i = 0; i < members.size(); i++) {
1424 RolapMember member = members.get(i);