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