Package mondrian.rolap

Source Code of mondrian.rolap.CacheControlImpl$MemberCellRegion

/*
// $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/CacheControlImpl.java#2 $
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// Copyright (C) 2006-2010 Julian Hyde and others
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
package mondrian.rolap;

import mondrian.olap.*;
import mondrian.resource.MondrianResource;
import mondrian.olap.CacheControl;

import javax.sql.DataSource;
import java.util.*;
import java.io.PrintWriter;

import org.eigenbase.util.property.BooleanProperty;

/**
* Implementation of {@link CacheControl} API.
*
* @author jhyde
* @version $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/CacheControlImpl.java#2 $
* @since Sep 27, 2006
*/
public class CacheControlImpl implements CacheControl {

    /**
     * Object to lock before making changes to the member cache.
     *
     * <p>The "member cache" is a figure of speech: each RolapHierarchy has its
     * own MemberCache object. But to provide transparently serialized access
     * to the "member cache" via the interface CacheControl, provide a common
     * lock here.
     *
     * <p>NOTE: static member is a little too wide a scope for this lock,
     * because in theory a JVM can contain multiple independent instances of
     * mondrian.
     */
    private static final Object MEMBER_CACHE_LOCK = new Object();

    // cell cache control
    public CellRegion createMemberRegion(Member member, boolean descendants) {
        if (member == null) {
            throw new NullPointerException();
        }
        final ArrayList<Member> list = new ArrayList<Member>();
        list.add(member);
        return new MemberCellRegion(list, descendants);
    }

    public CellRegion createMemberRegion(
        boolean lowerInclusive,
        Member lowerMember,
        boolean upperInclusive,
        Member upperMember,
        boolean descendants)
    {
        if (lowerMember == null) {
            lowerInclusive = false;
        }
        if (upperMember == null) {
            upperInclusive = false;
        }
        return new MemberRangeCellRegion(
            (RolapMember) lowerMember, lowerInclusive,
            (RolapMember) upperMember, upperInclusive,
            descendants);
    }

    public CellRegion createCrossjoinRegion(CellRegion... regions) {
        assert regions != null;
        assert regions.length >= 2;
        final HashSet<Dimension> set = new HashSet<Dimension>();
        final List<CellRegionImpl> list = new ArrayList<CellRegionImpl>();
        for (CellRegion region : regions) {
            int prevSize = set.size();
            List<Dimension> dimensionality = region.getDimensionality();
            set.addAll(dimensionality);
            if (set.size() < prevSize + dimensionality.size()) {
                throw MondrianResource.instance()
                    .CacheFlushCrossjoinDimensionsInCommon.ex(
                        getDimensionalityList(regions));
            }

            flattenCrossjoin((CellRegionImpl) region, list);
        }
        return new CrossjoinCellRegion(list);
    }

    // Returns e.g. "'[[Product]]', '[[Time], [Product]]'"
    private String getDimensionalityList(CellRegion[] regions) {
        StringBuilder buf = new StringBuilder();
        int k = 0;
        for (CellRegion region : regions) {
            if (k++ > 0) {
                buf.append(", ");
            }
            buf.append("'");
            buf.append(region.getDimensionality().toString());
            buf.append("'");
        }
        return buf.toString();
    }

    public CellRegion createUnionRegion(CellRegion... regions)
    {
        if (regions == null) {
            throw new NullPointerException();
        }
        if (regions.length < 2) {
            throw new IllegalArgumentException();
        }
        final List<CellRegionImpl> list = new ArrayList<CellRegionImpl>();
        for (CellRegion region : regions) {
            if (!region.getDimensionality().equals(
                regions[0].getDimensionality()))
            {
                throw MondrianResource.instance()
                    .CacheFlushUnionDimensionalityMismatch.ex(
                        regions[0].getDimensionality().toString(),
                        region.getDimensionality().toString());
            }
            list.add((CellRegionImpl) region);
        }
        return new UnionCellRegion(list);
    }

    public CellRegion createMeasuresRegion(Cube cube) {
        final Dimension measuresDimension = cube.getDimensions()[0];
        final List<Member> measures =
            cube.getSchemaReader(null).getLevelMembers(
                measuresDimension.getHierarchy().getLevels()[0],
                false);
        return new MemberCellRegion(measures, false);
    }

    public void flush(CellRegion region) {
        final List<Dimension> dimensionality = region.getDimensionality();
        boolean found = false;
        for (Dimension dimension : dimensionality) {
            if (dimension.isMeasures()) {
                found = true;
                break;
            }
        }
        if (!found) {
            throw MondrianResource.instance().CacheFlushRegionMustContainMembers
                .ex();
        }
        final UnionCellRegion union = normalize((CellRegionImpl) region);
        for (CellRegionImpl cellRegion : union.regions) {
            // Figure out the bits.
            flushNonUnion(cellRegion);
        }
    }

    /**
     * Flushes a list of cell regions.
     *
     * @param cellRegionList List of cell regions
     */
    protected void flushRegionList(List<CellRegion> cellRegionList) {
        final CellRegion cellRegion;
        switch (cellRegionList.size()) {
        case 0:
            return;
        case 1:
            cellRegion = cellRegionList.get(0);
            break;
        default:
            final CellRegion[] cellRegions =
                cellRegionList.toArray(new CellRegion[cellRegionList.size()]);
            cellRegion = createUnionRegion(cellRegions);
            break;
        }
        flush(cellRegion);
    }

    public void trace(String message) {
        // ignore message
    }

    public void flushSchemaCache() {
        RolapSchema.Pool.instance().clear();
    }

    // todo: document
    public void flushSchema(
        String catalogUrl,
        String connectionKey,
        String jdbcUser,
        String dataSourceStr)
    {
        RolapSchema.Pool.instance().remove(
            catalogUrl,
            connectionKey,
            jdbcUser,
            dataSourceStr);
    }

    // todo: document
    public void flushSchema(
        String catalogUrl,
        DataSource dataSource)
    {
        RolapSchema.Pool.instance().remove(
            catalogUrl,
            dataSource);
    }

    /**
     * Flushes the given RolapSchema instance from the pool
     *
     * @param schema RolapSchema
     */
    public void flushSchema(Schema schema) {
        if (RolapSchema.class.isInstance(schema)) {
            RolapSchema.Pool.instance().remove((RolapSchema)schema);
        } else {
            throw new UnsupportedOperationException(
                schema.getClass().getName() + " cannot be flushed");
        }
    }

    protected void flushNonUnion(CellRegion region) {
        throw new UnsupportedOperationException();
    }

    /**
     * Normalizes a CellRegion into a union of crossjoins of member regions.
     *
     * @param region Region
     * @return normalized region
     */
    UnionCellRegion normalize(CellRegionImpl region) {
        // Search for Union within a Crossjoin.
        //   Crossjoin(a1, a2, Union(r1, r2, r3), a4)
        // becomes
        //   Union(
        //     Crossjoin(a1, a2, r1, a4),
        //     Crossjoin(a1, a2, r2, a4),
        //     Crossjoin(a1, a2, r3, a4))

        // First, decompose into a flat list of non-union regions.
        List<CellRegionImpl> nonUnionList = new LinkedList<CellRegionImpl>();
        flattenUnion(region, nonUnionList);

        for (int i = 0; i < nonUnionList.size(); i++) {
            while (true) {
                CellRegionImpl nonUnionRegion = nonUnionList.get(i);
                UnionCellRegion firstUnion = findFirstUnion(nonUnionRegion);
                if (firstUnion == null) {
                    break;
                }
                List<CellRegionImpl> list = new ArrayList<CellRegionImpl>();
                for (CellRegionImpl unionComponent : firstUnion.regions) {
                    // For each unionComponent in (r1, r2, r3),
                    // create Crossjoin(a1, a2, r1, a4).
                    CellRegionImpl cj =
                        copyReplacing(
                            nonUnionRegion,
                            firstUnion,
                            unionComponent);
                    list.add(cj);
                }
                // Replace one element which contained a union with several
                // which contain one fewer union. (Double-linked list helps
                // here.)
                nonUnionList.remove(i);
                nonUnionList.addAll(i, list);
            }
        }
        return new UnionCellRegion(nonUnionList);
    }

    private CellRegionImpl copyReplacing(
        CellRegionImpl region,
        CellRegionImpl seek,
        CellRegionImpl replacement)
    {
        if (region == seek) {
            return replacement;
        }
        if (region instanceof UnionCellRegion) {
            final UnionCellRegion union = (UnionCellRegion) region;
            List<CellRegionImpl> list = new ArrayList<CellRegionImpl>();
            for (CellRegionImpl child : union.regions) {
                list.add(copyReplacing(child, seek, replacement));
            }
            return new UnionCellRegion(list);
        }
        if (region instanceof CrossjoinCellRegion) {
            final CrossjoinCellRegion crossjoin = (CrossjoinCellRegion) region;
            List<CellRegionImpl> list = new ArrayList<CellRegionImpl>();
            for (CellRegionImpl child : crossjoin.components) {
                list.add(copyReplacing(child, seek, replacement));
            }
            return new CrossjoinCellRegion(list);
        }
        // This region is atomic, and since regions are immutable we don't need
        // to clone.
        return region;
    }

    /**
     * Flatten a region into a list of regions none of which are unions.
     *
     * @param region Cell region
     * @param list Target list
     */
    private void flattenUnion(
        CellRegionImpl region,
        List<CellRegionImpl> list)
    {
        if (region instanceof UnionCellRegion) {
            UnionCellRegion union = (UnionCellRegion) region;
            for (CellRegionImpl region1 : union.regions) {
                flattenUnion(region1, list);
            }
        } else {
            list.add(region);
        }
    }

    /**
     * Flattens a region into a list of regions none of which are unions.
     *
     * @param region Cell region
     * @param list Target list
     */
    private void flattenCrossjoin(
        CellRegionImpl region,
        List<CellRegionImpl> list)
    {
        if (region instanceof CrossjoinCellRegion) {
            CrossjoinCellRegion crossjoin = (CrossjoinCellRegion) region;
            for (CellRegionImpl component : crossjoin.components) {
                flattenCrossjoin(component, list);
            }
        } else {
            list.add(region);
        }
    }

    private UnionCellRegion findFirstUnion(CellRegion region) {
        final CellRegionVisitor visitor =
            new CellRegionVisitorImpl() {
                public void visit(UnionCellRegion region) {
                    throw new FoundOne(region);
                }
            };
        try {
            ((CellRegionImpl) region).accept(visitor);
            return null;
        } catch (FoundOne foundOne) {
            return foundOne.region;
        }
    }

    /**
     * Returns a list of members of the Measures dimension which are mentioned
     * somewhere in a region specification.
     *
     * @param region Cell region
     * @return List of members mentioned in cell region specification
     */
    static List<Member> findMeasures(CellRegion region) {
        final List<Member> list = new ArrayList<Member>();
        final CellRegionVisitor visitor =
            new CellRegionVisitorImpl() {
                public void visit(MemberCellRegion region) {
                    if (region.dimension.isMeasures()) {
                        list.addAll(region.memberList);
                    }
                }

                public void visit(MemberRangeCellRegion region) {
                    if (region.level.getDimension().isMeasures()) {
                        // FIXME: don't allow range on measures dimension
                        assert false : "ranges on measures dimension";
                    }
                }
            };
        ((CellRegionImpl) region).accept(visitor);
        return list;
    }

    static List<RolapStar> getStarList(CellRegion region) {
        // Figure out which measure (therefore star) it belongs to.
        List<RolapStar> starList = new ArrayList<RolapStar>();
        final List<Member> measuresList = findMeasures(region);
        for (Member measure : measuresList) {
            if (measure instanceof RolapStoredMeasure) {
                RolapStoredMeasure storedMeasure = (RolapStoredMeasure) measure;
                final RolapStar.Measure starMeasure =
                    (RolapStar.Measure) storedMeasure.getStarMeasure();
                if (!starList.contains(starMeasure.getStar())) {
                    starList.add(starMeasure.getStar());
                }
            }
        }
        return starList;
    }

    public void printCacheState(
        PrintWriter pw,
        CellRegion region)
    {
        List<RolapStar> starList = getStarList(region);
        for (RolapStar star : starList) {
            star.print(pw, "", false);
        }
    }

    public MemberSet createMemberSet(Member member, boolean descendants)
    {
        return new SimpleMemberSet(
            Collections.singletonList((RolapMember) member),
            descendants);
    }

    public MemberSet createMemberSet(
        boolean lowerInclusive,
        Member lowerMember,
        boolean upperInclusive,
        Member upperMember,
        boolean descendants)
    {
        // TODO check that upperMember & lowerMember are in same Level
        if (lowerMember == null) {
            lowerInclusive = false;
        }
        if (upperMember == null) {
            upperInclusive = false;
        }
        return new RangeMemberSet(
            stripMember((RolapMember) lowerMember), lowerInclusive,
            stripMember((RolapMember) upperMember), upperInclusive,
            descendants);
    }

    public MemberSet createUnionSet(MemberSet... args)
    {
        //noinspection unchecked
        return new UnionMemberSet((List) Arrays.asList(args));
    }

    public MemberSet filter(Level level, MemberSet baseSet) {
        if (level instanceof RolapCubeLevel) {
            // be forgiving
            level = ((RolapCubeLevel) level).getRolapLevel();
        }
        return ((MemberSetPlus) baseSet).filter((RolapLevel) level);
    }

    public void flush(MemberSet memberSet) {
        // REVIEW How is flush(s) different to executing createDeleteCommand(s)?
        synchronized (MEMBER_CACHE_LOCK) {
            final List<CellRegion> cellRegionList = new ArrayList<CellRegion>();
            ((MemberSetPlus) memberSet).accept(
                new MemberSetVisitorImpl() {
                    public void visit(RolapMember member) {
                        flushMember(member, cellRegionList);
                    }
                }
           );
            // STUB: flush the set: another visitor

            // finally, flush cells now invalid
            flushRegionList(cellRegionList);
        }
    }

    public void printCacheState(PrintWriter pw, MemberSet set)
    {
        synchronized (MEMBER_CACHE_LOCK) {
            pw.println("need to implement printCacheState"); // TODO:
        }
    }

    public MemberEditCommand createCompoundCommand(
        List<MemberEditCommand> commandList)
    {
        //noinspection unchecked
        return new CompoundCommand((List) commandList);
    }

    public MemberEditCommand createCompoundCommand(
        MemberEditCommand... commands)
    {
        //noinspection unchecked
        return new CompoundCommand((List) Arrays.asList(commands));
    }

    public MemberEditCommand createDeleteCommand(Member member) {
        if (member == null) {
            throw new IllegalArgumentException("cannot delete null member");
        }
        if (((RolapLevel) member.getLevel()).isParentChild()) {
            throw new IllegalArgumentException(
                "delete member not supported for parent-child hierarchy");
        }
        return createDeleteCommand(createMemberSet(member, false));
    }

    public MemberEditCommand createDeleteCommand(MemberSet s) {
        return new DeleteMemberCommand((MemberSetPlus) s);
    }

    public MemberEditCommand createAddCommand(
        Member member) throws IllegalArgumentException
    {
        if (member == null) {
            throw new IllegalArgumentException("cannot add null member");
        }
        if (((RolapLevel) member.getLevel()).isParentChild()) {
            throw new IllegalArgumentException(
                "add member not supported for parent-child hierarchy");
        }
        return new AddMemberCommand((RolapMember) member);
    }

    public MemberEditCommand createMoveCommand(Member member, Member loc)
        throws IllegalArgumentException
    {
        if (member == null) {
            throw new IllegalArgumentException("cannot move null member");
        }
        if (((RolapLevel) member.getLevel()).isParentChild()) {
            throw new IllegalArgumentException(
                "move member not supported for parent-child hierarchy");
        }
        if (loc == null) {
            throw new IllegalArgumentException(
                "cannot move member to null location");
        }
        // TODO: check that MEMBER and LOC (its new parent) have appropriate
        // Levels
        return new MoveMemberCommand((RolapMember) member, (RolapMember) loc);
    }

    public MemberEditCommand createSetPropertyCommand(
        Member member,
        String name,
        Object value)
        throws IllegalArgumentException
    {
        if (member == null) {
            throw new IllegalArgumentException(
                "cannot set properties on null member");
        }
        if (((RolapLevel) member.getLevel()).isParentChild()) {
            throw new IllegalArgumentException(
                "set properties not supported for parent-child hierarchy");
        }
        // TODO: validate that prop NAME exists for Level of MEMBER
        return new ChangeMemberPropsCommand(
            new SimpleMemberSet(
                Collections.singletonList((RolapMember) member),
                false),
            Collections.singletonMap(name, value));
    }

    public MemberEditCommand createSetPropertyCommand(
        MemberSet members,
        Map<String, Object> propertyValues)
        throws IllegalArgumentException
    {
        // TODO: check that members all at same Level, and validate that props
        // exist
        validateSameLevel((MemberSetPlus) members);
        return new ChangeMemberPropsCommand(
            (MemberSetPlus) members,
            propertyValues);
    }

    /**
     * Validates that all members of a member set are the same level.
     *
     * @param memberSet Member set
     * @throws IllegalArgumentException if members are from more than one level
     */
    private void validateSameLevel(MemberSetPlus memberSet)
        throws IllegalArgumentException
    {
        memberSet.accept(
            new MemberSetVisitor() {
                final Set<RolapLevel> levelSet = new HashSet<RolapLevel>();

                private void visitMember(
                    RolapMember member,
                    boolean descendants)
                {
                    final String message =
                        "all members in set must belong to same level";
                    if (levelSet.add(member.getLevel())
                        && levelSet.size() > 1)
                    {
                        throw new IllegalArgumentException(message);
                    }
                    if (descendants
                        && member.getLevel().getChildLevel() != null)
                    {
                        throw new IllegalArgumentException(message);
                    }
                }

                public void visit(SimpleMemberSet simpleMemberSet) {
                    for (RolapMember member : simpleMemberSet.members) {
                        visitMember(member, simpleMemberSet.descendants);
                    }
                }

                public void visit(UnionMemberSet unionMemberSet) {
                    for (MemberSetPlus item : unionMemberSet.items) {
                        item.accept(this);
                    }
                }

                public void visit(RangeMemberSet rangeMemberSet) {
                    visitMember(
                        rangeMemberSet.lowerMember,
                        rangeMemberSet.descendants);
                    visitMember(
                        rangeMemberSet.upperMember,
                        rangeMemberSet.descendants);
                }
            }
       );
    }

    public void execute(MemberEditCommand cmd) {
        final BooleanProperty prop =
            MondrianProperties.instance().EnableRolapCubeMemberCache;
        if (prop.get()) {
            throw new IllegalArgumentException(
                "Member cache control operations are not allowed unless "
                + "property " + prop.getPath() + " is false");
        }
        synchronized (MEMBER_CACHE_LOCK) {
            final List<CellRegion> cellRegionList =
                new ArrayList<CellRegion>();
            ((MemberEditCommandPlus) cmd).execute(cellRegionList);
            if (false) {
                // TODO: Flushing regions currently fails with the error
                // "Region of cells to be flushed must contain measures". This
                // method should receive a cube (or cubes) whose measures to
                // flush.
                flushRegionList(cellRegionList);
            }
        }
    }

    private static MemberCache getMemberCache(RolapMember member) {
        final MemberReader memberReader =
            member.getHierarchy().getMemberReader();
        SmartMemberReader smartMemberReader =
            (SmartMemberReader) memberReader;
        return smartMemberReader.getMemberCache();
    }

    // cell cache control implementation

    /**
     * Cell region formed by a list of members.
     *
     * @see MemberRangeCellRegion
     */
    static class MemberCellRegion implements CellRegionImpl {
        private final List<Member> memberList;
        private final Dimension dimension;

        MemberCellRegion(List<Member> memberList, boolean descendants) {
            assert memberList.size() > 0;
            this.memberList = memberList;
            this.dimension = (memberList.get(0)).getDimension();
            Util.discard(descendants);
        }

        public List<Dimension> getDimensionality() {
            return Collections.singletonList(dimension);
        }

        public String toString() {
            return Util.commaList("Member", memberList);
        }

        public void accept(CellRegionVisitor visitor) {
            visitor.visit(this);
        }

        public List<Member> getMemberList() {
            return memberList;
        }
    }

    /**
     * Cell region formed a range of members between a lower and upper bound.
     */
    static class MemberRangeCellRegion implements CellRegionImpl {
        private final RolapMember lowerMember;
        private final boolean lowerInclusive;
        private final RolapMember upperMember;
        private final boolean upperInclusive;
        private final boolean descendants;
        private final RolapLevel level;

        MemberRangeCellRegion(
            RolapMember lowerMember,
            boolean lowerInclusive,
            RolapMember upperMember,
            boolean upperInclusive,
            boolean descendants)
        {
            assert lowerMember != null || upperMember != null;
            assert lowerMember == null
                || upperMember == null
                || lowerMember.getLevel() == upperMember.getLevel();
            assert !(lowerMember == null && lowerInclusive);
            assert !(upperMember == null && upperInclusive);
            this.lowerMember = lowerMember;
            this.lowerInclusive = lowerInclusive;
            this.upperMember = upperMember;
            this.upperInclusive = upperInclusive;
            this.descendants = descendants;
            this.level =
                lowerMember == null
                ? upperMember.getLevel()
                : lowerMember.getLevel();
        }

        public List<Dimension> getDimensionality() {
            return Collections.singletonList(level.getDimension());
        }

        public RolapLevel getLevel() {
            return level;
        }

        public String toString() {
            final StringBuilder sb = new StringBuilder("Range(");
            if (lowerMember == null) {
                sb.append("null");
            } else {
                sb.append(lowerMember);
                if (lowerInclusive) {
                    sb.append(" inclusive");
                } else {
                    sb.append(" exclusive");
                }
            }
            sb.append(" to ");
            if (upperMember == null) {
                sb.append("null");
            } else {
                sb.append(upperMember);
                if (upperInclusive) {
                    sb.append(" inclusive");
                } else {
                    sb.append(" exclusive");
                }
            }
            sb.append(")");
            return sb.toString();
        }

        public void accept(CellRegionVisitor visitor) {
            visitor.visit(this);
        }

        public boolean getLowerInclusive() {
            return lowerInclusive;
        }

        public RolapMember getLowerBound() {
            return lowerMember;
        }

        public boolean getUpperInclusive() {
            return upperInclusive;
        }

        public RolapMember getUpperBound() {
            return upperMember;
        }
    }

    /**
     * Cell region formed by a cartesian product of two or more CellRegions.
     */
    static class CrossjoinCellRegion implements CellRegionImpl {
        final List<Dimension> dimensions;
        private List<CellRegionImpl> components =
            new ArrayList<CellRegionImpl>();

        CrossjoinCellRegion(List<CellRegionImpl> regions) {
            final List<Dimension> dimensionality = new ArrayList<Dimension>();
            compute(regions, components, dimensionality);
            dimensions = Collections.unmodifiableList(dimensionality);
        }

        private static void compute(
            List<CellRegionImpl> regions,
            List<CellRegionImpl> components,
            List<Dimension> dimensionality)
        {
            final Set<Dimension> dimensionSet = new HashSet<Dimension>();
            for (CellRegionImpl region : regions) {
                addComponents(region, components);

                final List<Dimension> regionDimensionality =
                    region.getDimensionality();
                dimensionality.addAll(regionDimensionality);
                dimensionSet.addAll(regionDimensionality);
                assert dimensionSet.size() == dimensionality.size()
                    : "dimensions in common";
            }
        }

        public void accept(CellRegionVisitor visitor) {
            visitor.visit(this);
            for (CellRegion component : components) {
                CellRegionImpl cellRegion = (CellRegionImpl) component;
                cellRegion.accept(visitor);
            }
        }

        private static void addComponents(
            CellRegionImpl region,
            List<CellRegionImpl> list)
        {
            if (region instanceof CrossjoinCellRegion) {
                CrossjoinCellRegion crossjoinRegion =
                    (CrossjoinCellRegion) region;
                for (CellRegionImpl component : crossjoinRegion.components) {
                    list.add(component);
                }
            } else {
                list.add(region);
            }
        }

        public List<Dimension> getDimensionality() {
            return dimensions;
        }

        public String toString() {
            return Util.commaList("Crossjoin", components);
        }

        public List<CellRegion> getComponents() {
            return Util.cast(components);
        }
    }

    private static class UnionCellRegion implements CellRegionImpl {
        private final List<CellRegionImpl> regions;

        UnionCellRegion(List<CellRegionImpl> regions) {
            this.regions = regions;
            assert regions.size() >= 1;

            // All regions must have same dimensionality.
            for (int i = 1; i < regions.size(); i++) {
                final CellRegion region0 = regions.get(0);
                final CellRegion region = regions.get(i);
                assert region0.getDimensionality().equals(
                    region.getDimensionality());
            }
        }

        public List<Dimension> getDimensionality() {
            return regions.get(0).getDimensionality();
        }

        public String toString() {
            return Util.commaList("Union", regions);
        }

        public void accept(CellRegionVisitor visitor) {
            visitor.visit(this);
            for (CellRegionImpl cellRegion : regions) {
                cellRegion.accept(visitor);
            }
        }
    }

    interface CellRegionImpl extends CellRegion {
        void accept(CellRegionVisitor visitor);
    }

    /**
     * Visitor which visits various sub-types of {@link CellRegion}.
     */
    interface CellRegionVisitor {
        void visit(MemberCellRegion region);
        void visit(MemberRangeCellRegion region);
        void visit(UnionCellRegion region);
        void visit(CrossjoinCellRegion region);
    }

    private static class FoundOne extends RuntimeException {
        private final transient UnionCellRegion region;

        public FoundOne(UnionCellRegion region) {
            this.region = region;
        }
    }

    /**
     * Default implementation of {@link CellRegionVisitor}.
     */
    private static class CellRegionVisitorImpl implements CellRegionVisitor {
        public void visit(MemberCellRegion region) {
            // nothing
        }

        public void visit(MemberRangeCellRegion region) {
            // nothing
        }

        public void visit(UnionCellRegion region) {
            // nothing
        }

        public void visit(CrossjoinCellRegion region) {
            // nothing
        }
    }


    // ~ member cache control implementation ----------------------------------

    /**
     * Implementation-specific extensions to the
     * {@link mondrian.olap.CacheControl.MemberEditCommand} interface.
     */
    interface MemberEditCommandPlus extends MemberEditCommand {
        /**
         * Executes this command, and gathers a list of cell regions affected
         * in the {@code cellRegionList} parameter. The caller will flush the
         * cell regions later.
         *
         * @param cellRegionList Populated with a list of cell regions which
         * are invalidated by this action
         */
        void execute(final List<CellRegion> cellRegionList);
    }

    /**
     * Implementation-specific extensions to the
     * {@link mondrian.olap.CacheControl.MemberSet} interface.
     */
    interface MemberSetPlus extends MemberSet {
        /**
         * Accepts a visitor.
         *
         * @param visitor Visitor
         */
        void accept(MemberSetVisitor visitor);

        /**
         * Filters this member set, returning a member set containing all
         * members at a given Level. When applicable, returns this member set
         * unchanged.
         *
         * @param level Level
         * @return Member set with members not at the given level removed
         */
        MemberSetPlus filter(RolapLevel level);
    }

    /**
     * Visits the subclasses of {@link MemberSetPlus}.
     */
    interface MemberSetVisitor {
        void visit(SimpleMemberSet s);
        void visit(UnionMemberSet s);
        void visit(RangeMemberSet s);
    }

    /**
     * Default implementation of {@link MemberSetVisitor}.
     *
     * <p>The default implementation may not be efficient. For example, if
     * flushing a range of members from the cache, you may not wish to fetch
     * all of the members into the cache in order to flush them.
     */
    public static abstract class MemberSetVisitorImpl
        implements MemberSetVisitor
    {
        public void visit(UnionMemberSet s) {
            for (MemberSetPlus item : s.items) {
                item.accept(this);
            }
        }

        public void visit(RangeMemberSet s) {
            final MemberReader memberReader =
                s.level.getHierarchy().getMemberReader();
            visitRange(
                memberReader, s.level, s.lowerMember, s.upperMember,
                s.descendants);
        }

        protected void visitRange(
            MemberReader memberReader,
            RolapLevel level,
            RolapMember lowerMember,
            RolapMember upperMember,
            boolean recurse)
        {
            final List<RolapMember> list = new ArrayList<RolapMember>();
            memberReader.getMemberRange(level, lowerMember, upperMember, list);
            for (RolapMember member : list) {
                visit(member);
            }
            if (recurse) {
                list.clear();
                memberReader.getMemberChildren(lowerMember, list);
                if (list.isEmpty()) {
                    return;
                }
                RolapMember lowerChild = list.get(0);
                list.clear();
                memberReader.getMemberChildren(upperMember, list);
                if (list.isEmpty()) {
                    return;
                }
                RolapMember upperChild = list.get(list.size() - 1);
                visitRange(
                    memberReader, level, lowerChild, upperChild, recurse);
            }
        }

        public void visit(SimpleMemberSet s) {
            for (RolapMember member : s.members) {
                visit(member);
            }
        }

        /**
         * Visits a single member.
         *
         * @param member Member
         */
        public abstract void visit(RolapMember member);
    }

    /**
     * Member set containing no members.
     */
    static class EmptyMemberSet implements MemberSetPlus {
        public static final EmptyMemberSet INSTANCE = new EmptyMemberSet();

        private EmptyMemberSet() {
            // prevent instantiation except for singleton
        }

        public void accept(MemberSetVisitor visitor) {
            // nothing
        }

        public MemberSetPlus filter(RolapLevel level) {
            return this;
        }

        public String toString() {
            return "Empty";
        }
    }

    /**
     * Member set defined by a list of members from one hierarchy.
     */
    static class SimpleMemberSet implements MemberSetPlus {
        public final List<RolapMember> members;
        // the set includes the descendants of all members
        public final boolean descendants;
        public final RolapHierarchy hierarchy;

        SimpleMemberSet(List<RolapMember> members, boolean descendants) {
            this.members = new ArrayList<RolapMember>(members);
            stripMemberList(this.members);
            this.descendants = descendants;
            this.hierarchy =
                members.isEmpty()
                    ? null
                    : members.get(0).getHierarchy();
        }

        public String toString() {
            return Util.commaList("Member", members);
        }

        public void accept(MemberSetVisitor visitor) {
            // Don't descend the subtrees here: may not want to load them into
            // cache.
            visitor.visit(this);
        }

        public MemberSetPlus filter(RolapLevel level) {
            List<RolapMember> filteredMembers = new ArrayList<RolapMember>();
            for (RolapMember member : members) {
                if (member.getLevel().equals(level)) {
                    filteredMembers.add(member);
                }
            }
            if (filteredMembers.isEmpty()) {
                return EmptyMemberSet.INSTANCE;
            } else if (filteredMembers.equals(members)) {
                return this;
            } else {
                return new SimpleMemberSet(filteredMembers, false);
            }
        }
    }

    /**
     * Member set defined by the union of other member sets.
     */
    static class UnionMemberSet implements MemberSetPlus {
        private final List<MemberSetPlus> items;

        UnionMemberSet(List<MemberSetPlus> items) {
            this.items = items;
        }

        public String toString() {
            final StringBuilder sb = new StringBuilder("Union(");
            for (int i = 0; i < items.size(); i++) {
                if (i > 0) {
                    sb.append(", ");
                }
                MemberSetPlus item = items.get(i);
                sb.append(item.toString());
            }
            sb.append(")");
            return sb.toString();
        }

        public void accept(MemberSetVisitor visitor) {
            visitor.visit(this);
        }

        public MemberSetPlus filter(RolapLevel level) {
            final List<MemberSetPlus> filteredItems =
                new ArrayList<MemberSetPlus>();
            for (MemberSetPlus item : items) {
                final MemberSetPlus filteredItem = item.filter(level);
                if (filteredItem == EmptyMemberSet.INSTANCE) {
                    // skip it
                } else {
                    assert !(filteredItem instanceof EmptyMemberSet);
                    filteredItems.add(filteredItem);
                }
            }
            if (filteredItems.isEmpty()) {
                return EmptyMemberSet.INSTANCE;
            } else if (filteredItems.equals(items)) {
                return this;
            } else {
                return new UnionMemberSet(filteredItems);
            }
        }
    }

    /**
     * Member set defined by a range of members between a lower and upper
     * bound.
     */
    static class RangeMemberSet implements MemberSetPlus {
        private final RolapMember lowerMember;
        private final boolean lowerInclusive;
        private final RolapMember upperMember;
        private final boolean upperInclusive;
        private final boolean descendants;
        private final RolapLevel level;

        RangeMemberSet(
            RolapMember lowerMember,
            boolean lowerInclusive,
            RolapMember upperMember,
            boolean upperInclusive,
            boolean descendants)
        {
            assert lowerMember != null || upperMember != null;
            assert lowerMember == null
                || upperMember == null
                || lowerMember.getLevel() == upperMember.getLevel();
            assert !(lowerMember == null && lowerInclusive);
            assert !(upperMember == null && upperInclusive);
            assert !(lowerMember instanceof RolapCubeMember);
            assert !(upperMember instanceof RolapCubeMember);
            this.lowerMember = lowerMember;
            this.lowerInclusive = lowerInclusive;
            this.upperMember = upperMember;
            this.upperInclusive = upperInclusive;
            this.descendants = descendants;
            this.level =
                lowerMember == null
                ? upperMember.getLevel()
                : lowerMember.getLevel();
        }

        public String toString() {
            final StringBuilder sb = new StringBuilder("Range(");
            if (lowerMember == null) {
                sb.append("null");
            } else {
                sb.append(lowerMember);
                if (lowerInclusive) {
                    sb.append(" inclusive");
                } else {
                    sb.append(" exclusive");
                }
            }
            sb.append(" to ");
            if (upperMember == null) {
                sb.append("null");
            } else {
                sb.append(upperMember);
                if (upperInclusive) {
                    sb.append(" inclusive");
                } else {
                    sb.append(" exclusive");
                }
            }
            sb.append(")");
            return sb.toString();
        }

        public void accept(MemberSetVisitor visitor) {
            // Don't traverse the range here: may not want to load it into cache
            visitor.visit(this);
        }

        public MemberSetPlus filter(RolapLevel level) {
            if (level == this.level) {
                return this;
            } else {
                return filter2(level, this.level, lowerMember, upperMember);
            }
        }

        public MemberSetPlus filter2(
            RolapLevel seekLevel,
            RolapLevel level,
            RolapMember lower,
            RolapMember upper)
        {
            if (level == seekLevel) {
                return new RangeMemberSet(
                    lower, lowerInclusive, upper, upperInclusive, false);
            } else if (descendants
                && level.getHierarchy() == seekLevel.getHierarchy()
                && level.getDepth() < seekLevel.getDepth())
            {
                final MemberReader memberReader =
                    level.getHierarchy().getMemberReader();
                final List<RolapMember> list = new ArrayList<RolapMember>();
                memberReader.getMemberChildren(lower, list);
                if (list.isEmpty()) {
                    return EmptyMemberSet.INSTANCE;
                }
                RolapMember lowerChild = list.get(0);
                list.clear();
                memberReader.getMemberChildren(upper, list);
                if (list.isEmpty()) {
                    return EmptyMemberSet.INSTANCE;
                }
                RolapMember upperChild = list.get(list.size() - 1);
                return filter2(
                    seekLevel, (RolapLevel) level.getChildLevel(),
                    lowerChild, upperChild);
            } else {
                return EmptyMemberSet.INSTANCE;
            }
        }
    }

    /**
     * Command consisting of a set of commands executed in sequence.
     */
    private static class CompoundCommand implements MemberEditCommandPlus {
        private final List<MemberEditCommandPlus> commandList;

        CompoundCommand(List<MemberEditCommandPlus> commandList) {
            this.commandList = commandList;
        }

        public String toString() {
            return Util.commaList("Compound", commandList);
        }

        public void execute(final List<CellRegion> cellRegionList) {
            for (MemberEditCommandPlus command : commandList) {
                command.execute(cellRegionList);
            }
        }
    }

    /**
     * Command that deletes a member and its descendants from the cache.
     */
    private class DeleteMemberCommand
        extends MemberSetVisitorImpl
        implements MemberEditCommandPlus
    {
        private final MemberSetPlus set;
        private List<CellRegion> cellRegionList;

        DeleteMemberCommand(MemberSetPlus set) {
            this.set = set;
        }

        public String toString() {
            return "DeleteMemberCommand(" + set + ")";
        }

        public void execute(final List<CellRegion> cellRegionList) {
            // NOTE: use of member makes this class non-reentrant
            this.cellRegionList = cellRegionList;
            set.accept(this);
            this.cellRegionList = null;
        }

        public void visit(RolapMember member) {
            deleteMember(member, member.getParentMember(), cellRegionList);
        }
    }

    /**
     * Command that adds a new member to the cache.
     */
    private class AddMemberCommand implements MemberEditCommandPlus {
        private final RolapMember member;

        public AddMemberCommand(RolapMember member) {
            assert member != null;
            this.member = stripMember(member);
        }

        public String toString() {
            return "AddMemberCommand(" + member + ")";
        }

        public void execute(List<CellRegion> cellRegionList) {
            addMember(member, member.getParentMember(), cellRegionList);
        }
    }

    /**
     * Command that moves a member to a new parent.
     */
    private class MoveMemberCommand implements MemberEditCommandPlus {
        private final RolapMember member;
        private final RolapMember newParent;

        MoveMemberCommand(RolapMember member, RolapMember newParent) {
            this.member = member;
            this.newParent = newParent;
        }

        public String toString() {
            return "MoveMemberCommand(" + member + ", " + newParent + ")";
        }

        public void execute(final List<CellRegion> cellRegionList) {
            deleteMember(member, member.getParentMember(), cellRegionList);
            ((RolapMemberBase) member).setParentMember(newParent);
            addMember(member, member.getParentMember(), cellRegionList);
        }
    }

    /**
     * Command that changes one or more properties of a member.
     */
    private class ChangeMemberPropsCommand
        extends MemberSetVisitorImpl
        implements MemberEditCommandPlus
    {
        final MemberSetPlus memberSet;
        final Map<String, Object> propertyValues;

        ChangeMemberPropsCommand(
            MemberSetPlus memberSet,
            Map<String, Object> propertyValues)
        {
            this.memberSet = memberSet;
            this.propertyValues = propertyValues;
        }

        public String toString() {
            return "CreateMemberPropsCommand(" + memberSet
                + ", " + propertyValues + ")";
        }

        public void execute(List<CellRegion> cellRegionList) {
            // ignore cellRegionList - no changes to cell cache
            memberSet.accept(this);
        }

        public void visit(RolapMember member) {
            // Change member's properties.
            member = stripMember(member);
            final MemberCache memberCache = getMemberCache(member);
            final Object cacheKey =
                memberCache.makeKey(
                    member.getParentMember(),
                    member.getKey());
            final RolapMember cacheMember = memberCache.getMember(cacheKey);
            if (cacheMember == null) {
                return;
            }
            for (Map.Entry<String, Object> entry : propertyValues.entrySet()) {
                cacheMember.setProperty(entry.getKey(), entry.getValue());
            }
        }
    }

    private static RolapMember stripMember(RolapMember member) {
        if (member instanceof RolapCubeMember) {
            member = ((RolapCubeMember) member).member;
        }
        return member;
    }

    private static void stripMemberList(List<RolapMember> members) {
        for (int i = 0; i < members.size(); i++) {
            RolapMember member = members.get(i);
            if (member instanceof RolapCubeMember) {
                members.set(i, ((RolapCubeMember) member).member);
            }
        }
    }

    private void deleteMember(
        final RolapMember member,
        RolapMember previousParent,
        List<CellRegion> cellRegionList)
    {
        // Remove member from cache,
        // and that will remove the parent's children list.
        final MemberCache memberCache = getMemberCache(member);
        final Object key =
            memberCache.makeKey(previousParent, member.getKey());
        memberCache.removeMember(key);

        // Cells for member and its ancestors are now invalid. It's sufficient
        // to flush the member.
        cellRegionList.add(createMemberRegion(member, true));
    }

    /**
     * Adds a member to cache.
     *
     * @param member Member
     * @param parent Member's parent (generally equals member.getParentMember)
     * @param cellRegionList List of cell regions to be flushed
     */
    private void addMember(
        final RolapMember member,
        final RolapMember parent,
        List<CellRegion> cellRegionList)
    {
        // Parent's children are now invalid. Remove parent from cache,
        // and that will remove the parent's children list. Children lists
        // of its existing children can remain in cache.
        final MemberCache memberCache = getMemberCache(member);
        final Object parentKey =
            memberCache.makeKey(parent.getParentMember(), parent.getKey());
        memberCache.removeMember(parentKey);

        // Cells for all of member's ancestors are now invalid. It's sufficient
        // to flush its parent.
        cellRegionList.add(createMemberRegion(parent, false));
    }

    /**
     * Removes a member from cache.
     *
     * @param member Member
     * @param cellRegionList Populated with cell region to be flushed
     */
    private void flushMember(
        RolapMember member,
        List<CellRegion> cellRegionList)
    {
        // TODO

        cellRegionList.add(createMemberRegion(member, false));
    }
}

// End CacheControlImpl.java
TOP

Related Classes of mondrian.rolap.CacheControlImpl$MemberCellRegion

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.