001/*
002// $Id: QueryDimension.java 482 2012-01-05 23:27:27Z jhyde $
003//
004// Licensed to Julian Hyde under one or more contributor license
005// agreements. See the NOTICE file distributed with this work for
006// additional information regarding copyright ownership.
007//
008// Julian Hyde licenses this file to you under the Apache License,
009// Version 2.0 (the "License"); you may not use this file except in
010// compliance with the License. You may obtain a copy of the License at:
011//
012// http://www.apache.org/licenses/LICENSE-2.0
013//
014// Unless required by applicable law or agreed to in writing, software
015// distributed under the License is distributed on an "AS IS" BASIS,
016// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017// See the License for the specific language governing permissions and
018// limitations under the License.
019*/
020package org.olap4j.query;
021
022import org.olap4j.OlapException;
023import org.olap4j.impl.IdentifierParser;
024import org.olap4j.mdx.IdentifierSegment;
025import org.olap4j.metadata.*;
026
027import java.util.*;
028
029/**
030 * Usage of a dimension for an OLAP query.
031 *
032 * <p>It references an {@link org.olap4j.metadata.Dimension} and allows the
033 * query creator to manage the member selections for the dimension.
034 * The state of a QueryDimension does not affect the
035 * Dimension object in any way so a single Dimension object
036 * can be referenced by many QueryDimension objects.
037 *
038 * @author jdixon, jhyde, Luc Boudreau
039 * @version $Id: QueryDimension.java 482 2012-01-05 23:27:27Z jhyde $
040 * @since May 29, 2007
041 */
042public class QueryDimension extends QueryNodeImpl {
043    protected QueryAxis axis;
044    protected final List<Selection> inclusions = new SelectionList();
045    protected final List<Selection> exclusions = new SelectionList();
046    private final Query query;
047    protected Dimension dimension;
048    private SortOrder sortOrder = null;
049    private HierarchizeMode hierarchizeMode = null;
050    private boolean hierarchyConsistent = false;
051
052    public QueryDimension(Query query, Dimension dimension) {
053        super();
054        this.query = query;
055        this.dimension = dimension;
056    }
057
058    public Query getQuery() {
059        return query;
060    }
061
062    public void setAxis(QueryAxis axis) {
063        this.axis = axis;
064    }
065
066    public QueryAxis getAxis() {
067        return axis;
068    }
069
070    public String getName() {
071        return dimension.getName();
072    }
073
074    /**
075     * Selects members and includes them in the query.
076     *
077     * <p>This method selects and includes a single member with the
078     * {@link Selection.Operator#MEMBER} operator.
079     *
080     * @param nameParts Name of the member to select and include.
081     * @throws OlapException If no member corresponding to the supplied
082     * name parts could be resolved in the cube.
083     */
084    public Selection include(
085        List<IdentifierSegment> nameParts)
086        throws OlapException
087    {
088        return this.include(Selection.Operator.MEMBER, nameParts);
089    }
090
091    public Selection createSelection(
092        List<IdentifierSegment> nameParts)
093        throws OlapException
094    {
095        return this.createSelection(Selection.Operator.MEMBER, nameParts);
096    }
097
098    /**
099     * Selects members and includes them in the query.
100     *
101     * <p>This method selects and includes a member along with its
102     * relatives, depending on the supplied {@link Selection.Operator}
103     * operator.
104     *
105     * @param operator Selection operator that defines what relatives of the
106     * supplied member name to include along.
107     * @param nameParts Name of the root member to select and include.
108     * @throws OlapException If no member corresponding to the supplied
109     * name parts could be resolved in the cube.
110     */
111    public Selection include(
112        Selection.Operator operator,
113        List<IdentifierSegment> nameParts) throws OlapException
114    {
115        Member member = this.getQuery().getCube().lookupMember(nameParts);
116        if (member == null) {
117            throw new OlapException(
118                "Unable to find a member with name " + nameParts);
119        }
120        return this.include(
121            operator,
122            member);
123    }
124
125    public Selection createSelection(
126        Selection.Operator operator,
127        List<IdentifierSegment> nameParts) throws OlapException
128    {
129        Member member = this.getQuery().getCube().lookupMember(nameParts);
130        if (member == null) {
131            throw new OlapException(
132                "Unable to find a member with name " + nameParts);
133        }
134        return this.createSelection(
135            operator,
136            member);
137    }
138
139    /**
140     * Selects members and includes them in the query.
141     * <p>This method selects and includes a single member with the
142     * {@link Selection.Operator#MEMBER} selection operator.
143     * @param member The member to select and include in the query.
144     */
145    public Selection include(Member member) {
146        return include(Selection.Operator.MEMBER, member);
147    }
148
149    public Selection createSelection(Member member) {
150        return createSelection(Selection.Operator.MEMBER, member);
151    }
152
153    /**
154     * Selects a level and includes it in the query.
155     * <p>This method selects and includes a all members of the given
156     * query using the {@link Selection.Operator#MEMBERS} selection operator.
157     * @param level The level to select and include in the query.
158     */
159    public Selection include(Level level) {
160        if (level.getDimension().equals(this.dimension)) {
161            Selection selection =
162                    query.getSelectionFactory().createLevelSelection(level);
163            this.include(selection);
164            return selection;
165        }
166        return null;
167    }
168    /**
169     * Selects members and includes them in the query.
170     * <p>This method selects and includes a member along with it's
171     * relatives, depending on the supplied {@link Selection.Operator}
172     * operator.
173     * @param operator Selection operator that defines what relatives of the
174     * supplied member name to include along.
175     * @param member Root member to select and include.
176     */
177    public Selection createSelection(
178        Selection.Operator operator,
179        Member member)
180    {
181        if (member.getDimension().equals(this.dimension)) {
182            Selection selection =
183                query.getSelectionFactory().createMemberSelection(
184                    member, operator);
185            return selection;
186        }
187        return null;
188    }
189
190    /**
191     * Selects level and includes all members in the query.
192     * <p>This method selects and includes all members of a
193     * given Level, using the MEMBERS operator {@link Selection.Operator}
194     * @param level Root level to select and include.
195     */
196    public Selection createSelection(Level level)
197    {
198        if (level.getDimension().equals(this.dimension)) {
199            Selection selection =
200                    query.getSelectionFactory().createLevelSelection(level);
201            return selection;
202        }
203        return null;
204    }
205
206    /**
207     * Selects members and includes them in the query.
208     * <p>This method selects and includes a member along with it's
209     * relatives, depending on the supplied {@link Selection.Operator}
210     * operator.
211     * @param operator Selection operator that defines what relatives of the
212     * supplied member name to include along.
213     * @param member Root member to select and include.
214     */
215    public Selection include(
216        Selection.Operator operator,
217        Member member)
218    {
219        if (member.getDimension().equals(this.dimension)) {
220            Selection selection =
221                query.getSelectionFactory().createMemberSelection(
222                    member, operator);
223            this.include(selection);
224            return selection;
225        }
226        return null;
227    }
228
229    /**
230     * Includes a selection of members in the query.
231     * @param selection The selection of members to include.
232     */
233    private void include(Selection selection) {
234        this.getInclusions().add(selection);
235        Integer index = Integer.valueOf(
236            this.getInclusions().indexOf(selection));
237        this.notifyAdd(selection, index);
238    }
239
240    /**
241     * Clears the current member inclusions from this query dimension.
242     */
243    public void clearInclusions() {
244        Map<Integer, QueryNode> removed = new HashMap<Integer, QueryNode>();
245        for (Selection node : this.inclusions) {
246            removed.put(
247                Integer.valueOf(this.inclusions.indexOf(node)),
248                node);
249            ((QueryNodeImpl) node).clearListeners();
250        }
251        this.inclusions.clear();
252        this.notifyRemove(removed);
253    }
254
255    /**
256     * Selects members and excludes them from the query.
257     *
258     * <p>This method selects and excludes a single member with the
259     * {@link Selection.Operator#MEMBER} operator.
260     *
261     * @param nameParts Name of the member to select and exclude.
262     * @throws OlapException If no member corresponding to the supplied
263     * name parts could be resolved in the cube.
264     */
265    public void exclude(
266        List<IdentifierSegment> nameParts)
267        throws OlapException
268    {
269        this.exclude(Selection.Operator.MEMBER, nameParts);
270    }
271
272    /**
273     * Selects members and excludes them from the query.
274     *
275     * <p>This method selects and excludes a member along with its
276     * relatives, depending on the supplied {@link Selection.Operator}
277     * operator.
278     *
279     * @param operator Selection operator that defines what relatives of the
280     * supplied member name to exclude along.
281     * @param nameParts Name of the root member to select and exclude.
282     * @throws OlapException If no member corresponding to the supplied
283     * name parts could be resolved in the cube.
284     */
285    public void exclude(
286        Selection.Operator operator,
287        List<IdentifierSegment> nameParts) throws OlapException
288    {
289        Member rootMember = this.getQuery().getCube().lookupMember(nameParts);
290        if (rootMember == null) {
291            throw new OlapException(
292                "Unable to find a member with name " + nameParts);
293        }
294        this.exclude(
295            operator,
296            rootMember);
297    }
298
299    /**
300     * Selects level members and excludes them from the query.
301     * <p>This method selects and excludes members of a level with the
302     * {@link Selection.Operator#MEMBERS} selection operator.
303     * @param level The level to select and exclude from the query.
304     */
305    public void exclude(Level level) {
306        if (level.getDimension().equals(this.dimension)) {
307            Selection selection =
308                    query.getSelectionFactory().createLevelSelection(level);
309            this.exclude(selection);
310        }
311    }
312
313    /**
314     * Selects members and excludes them from the query.
315     * <p>This method selects and excludes a single member with the
316     * {@link Selection.Operator#MEMBER} selection operator.
317     * @param member The member to select and exclude from the query.
318     */
319    public void exclude(Member member) {
320        exclude(Selection.Operator.MEMBER, member);
321    }
322
323    /**
324     * Selects members and excludes them from the query.
325     * <p>This method selects and excludes a member along with it's
326     * relatives, depending on the supplied {@link Selection.Operator}
327     * operator.
328     * @param operator Selection operator that defines what relatives of the
329     * supplied member name to exclude along.
330     * @param member Root member to select and exclude.
331     */
332    public void exclude(
333        Selection.Operator operator,
334        Member member)
335    {
336        if (member.getDimension().equals(this.dimension)) {
337            Selection selection =
338                query.getSelectionFactory().createMemberSelection(
339                    member, operator);
340            this.exclude(selection);
341        }
342    }
343
344    /**
345     * Excludes a selection of members from the query.
346     * @param selection The selection of members to exclude.
347     */
348    private void exclude(Selection selection) {
349        this.getExclusions().add(selection);
350        Integer index = Integer.valueOf(
351            this.getExclusions().indexOf(selection));
352        this.notifyAdd(selection, index);
353    }
354
355    /**
356     * Clears the current member inclusions from this query dimension.
357     */
358    public void clearExclusions() {
359        Map<Integer, QueryNode> removed = new HashMap<Integer, QueryNode>();
360        for (Selection node : this.exclusions) {
361            removed.put(
362                Integer.valueOf(this.exclusions.indexOf(node)),
363                node);
364            ((QueryNodeImpl) node).clearListeners();
365        }
366        this.exclusions.clear();
367        this.notifyRemove(removed);
368    }
369
370    /**
371     * Resolves a selection of members into an actual list
372     * of the root member and it's relatives selected by the Selection object.
373     * @param selection The selection of members to resolve.
374     * @return A list of the actual members selected by the selection object.
375     * @throws OlapException If resolving the selections triggers an exception
376     * while looking up members in the underlying cube.
377     */
378    public List<Member> resolve(Selection selection) throws OlapException
379    {
380        assert selection != null;
381        final Member.TreeOp op;
382        Member.TreeOp secondOp = null;
383        switch (selection.getOperator()) {
384        case CHILDREN:
385            op = Member.TreeOp.CHILDREN;
386            break;
387        case SIBLINGS:
388            op = Member.TreeOp.SIBLINGS;
389            break;
390        case INCLUDE_CHILDREN:
391            op = Member.TreeOp.SELF;
392            secondOp = Member.TreeOp.CHILDREN;
393            break;
394        case MEMBER:
395            op = Member.TreeOp.SELF;
396            break;
397        default:
398            throw new OlapException(
399                "Operation not supported: " + selection.getOperator());
400        }
401        Set<Member.TreeOp> set = new TreeSet<Member.TreeOp>();
402        set.add(op);
403        if (secondOp != null) {
404            set.add(secondOp);
405        }
406        try {
407            return
408                query.getCube().lookupMembers(
409                    set,
410                    IdentifierParser.parseIdentifier(
411                        selection.getUniqueName()));
412        } catch (Exception e) {
413            throw new OlapException(
414                "Error while resolving selection " + selection.toString(),
415                e);
416        }
417    }
418
419    /**
420     * Returns a list of the inclusions within this dimension.
421     *
422     * <p>Be aware that modifications to this list might
423     * have unpredictable consequences.</p>
424     *
425     * @return list of inclusions
426     */
427    public List<Selection> getInclusions() {
428        return inclusions;
429    }
430
431    /**
432     * Returns a list of the exclusions within this dimension.
433     *
434     * <p>Be aware that modifications to this list might
435     * have unpredictable consequences.</p>
436     *
437     * @return list of exclusions
438     */
439    public List<Selection> getExclusions() {
440        return exclusions;
441    }
442
443    /**
444     * Returns the underlying dimension object onto which
445     * this query dimension is based.
446     * <p>Returns a mutable object so operations on it have
447     * unpredictable consequences.
448     * @return The underlying dimension representation.
449     */
450    public Dimension getDimension() {
451        return dimension;
452    }
453
454    /**
455     * Forces a change onto which dimension is the current
456     * base of this QueryDimension object.
457     * <p>Forcing a change in the duimension assignment has
458     * unpredictable consequences.
459     * @param dimension The new dimension to assign to this
460     * query dimension.
461     */
462    public void setDimension(Dimension dimension) {
463        this.dimension = dimension;
464    }
465
466    /**
467     * Sorts the dimension members by name in the
468     * order supplied as a parameter.
469     * @param order The {@link SortOrder} to use.
470     */
471    public void sort(SortOrder order) {
472        this.sortOrder = order;
473    }
474
475    /**
476     * Returns the current order in which the
477     * dimension members are sorted.
478     * @return A value of {@link SortOrder}
479     */
480    public SortOrder getSortOrder() {
481        return this.sortOrder;
482    }
483
484    /**
485     * Clears the current sorting settings.
486     */
487    public void clearSort() {
488        this.sortOrder = null;
489    }
490
491    /**
492     * Returns the current mode of hierarchization, or null
493     * if no hierarchization is currently performed.
494     *
495     * <p>This capability is only available when a single dimension is
496     * selected on an axis
497     *
498     * @return Either a hierarchization mode value or null
499     *     if no hierarchization is currently performed.
500     */
501    public HierarchizeMode getHierarchizeMode() {
502        return hierarchizeMode;
503    }
504
505    /**
506     * Triggers the hierarchization of the included members within this
507     * QueryDimension.
508     *
509     * <p>The dimension inclusions will be wrapped in an MDX Hierarchize
510     * function call.
511     *
512     * <p>This capability is only available when a single dimension is
513     * selected on an axis.
514     *
515     * @param hierarchizeMode If parents should be included before or after
516     * their children. (Equivalent to the POST/PRE MDX literal for the
517     * Hierarchize() function)
518     * inside the Hierarchize() MDX function call.
519     */
520    public void setHierarchizeMode(HierarchizeMode hierarchizeMode) {
521        this.hierarchizeMode = hierarchizeMode;
522    }
523
524    /**
525     * Tells the QueryDimension not to hierarchize its included
526     * selections.
527     *
528     * <p>This capability is only available when a single dimension is
529     * selected on an axis.
530     */
531    public void clearHierarchizeMode() {
532        this.hierarchizeMode = null;
533    }
534
535    /**
536     * Tells the QueryDimension not to keep a consistent hierarchy
537     * within the inclusions when the mdx is generated.
538     * Only members whose Ancestors are included will be included.
539     *
540     * <p>It uses the MDX function FILTER() in combination with
541     * ANCESTOR() to produce a set like:<br /><br />
542     * {[Time].[1997]}, <br />
543     * Filter({{[Time].[Quarter].Members}},
544     * (Ancestor([Time].CurrentMember, [Time].[Year]) IN {[Time].[1997]}))
545     */
546    public void setHierarchyConsistent(boolean consistent) {
547        this.hierarchyConsistent = consistent;
548    }
549
550    /**
551     * Tells the QueryDimension not to keep a consistent hierarchy
552     */
553    public boolean isHierarchyConsistent() {
554        return this.hierarchyConsistent;
555    }
556
557    private class SelectionList extends AbstractList<Selection> {
558        private final List<Selection> list = new ArrayList<Selection>();
559
560        public Selection get(int index) {
561            return list.get(index);
562        }
563
564        public int size() {
565            return list.size();
566        }
567
568        public Selection set(int index, Selection selection) {
569            return list.set(index, selection);
570        }
571
572        public void add(int index, Selection selection) {
573            if (this.contains(selection)) {
574                throw new IllegalStateException(
575                    "dimension already contains selection");
576            }
577            list.add(index, selection);
578        }
579
580        public Selection remove(int index) {
581            return list.remove(index);
582        }
583    }
584
585    /**
586     * Defines in which way the hierarchize operation
587     * should be performed.
588     */
589    public static enum HierarchizeMode {
590        /**
591         * Parents are placed before children.
592         */
593        PRE,
594        /**
595         * Parents are placed after children
596         */
597        POST
598    }
599
600    void tearDown() {
601        for (Selection node : this.inclusions) {
602            ((QueryNodeImpl)node).clearListeners();
603        }
604        for (Selection node : this.exclusions) {
605            ((QueryNodeImpl)node).clearListeners();
606        }
607        this.inclusions.clear();
608        this.exclusions.clear();
609    }
610}
611
612// End QueryDimension.java
613
614
615
616
617
618
619
620