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    */
020    package org.olap4j.query;
021    
022    import org.olap4j.OlapException;
023    import org.olap4j.impl.IdentifierParser;
024    import org.olap4j.mdx.IdentifierSegment;
025    import org.olap4j.metadata.*;
026    
027    import 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     */
042    public 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