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