001    /*
002    // $Id: QueryDimension.java 277 2009-08-18 18:50:30Z lucboudreau $
003    // This software is subject to the terms of the Eclipse Public License v1.0
004    // Agreement, available at the following URL:
005    // http://www.eclipse.org/legal/epl-v10.html.
006    // Copyright (C) 2007-2009 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package org.olap4j.query;
011    
012    import org.olap4j.OlapException;
013    import org.olap4j.impl.Olap4jUtil;
014    import org.olap4j.mdx.IdentifierNode;
015    import org.olap4j.mdx.IdentifierNode.Segment;
016    import org.olap4j.metadata.*;
017    
018    import java.util.HashMap;
019    import java.util.List;
020    import java.util.ArrayList;
021    import java.util.AbstractList;
022    import java.util.Map;
023    import java.util.Set;
024    import java.util.TreeSet;
025    
026    /**
027     * Usage of a dimension for an OLAP query.
028     *
029     * <p>It references an {@link org.olap4j.metadata.Dimension} and allows the
030     * query creator to manage the member selections for the dimension.
031     * The state of a QueryDimension does not affect the
032     * Dimension object in any way so a single Dimension object
033     * can be referenced by many QueryDimension objects.
034     *
035     * @author jdixon, jhyde, Luc Boudreau
036     * @version $Id: QueryDimension.java 277 2009-08-18 18:50:30Z lucboudreau $
037     * @since May 29, 2007
038     */
039    public 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    
048        public QueryDimension(Query query, Dimension dimension) {
049            super();
050            this.query = query;
051            this.dimension = dimension;
052        }
053    
054        public Query getQuery() {
055            return query;
056        }
057    
058        public void setAxis(QueryAxis axis) {
059            this.axis = axis;
060        }
061    
062        public QueryAxis getAxis() {
063            return axis;
064        }
065    
066        public String getName() {
067            return dimension.getName();
068        }
069    
070        @Deprecated
071        public void select(String... nameParts) throws OlapException {
072            this.include(nameParts);
073        }
074    
075        @Deprecated
076        public void select(
077            Selection.Operator operator,
078            String... nameParts) throws OlapException
079        {
080            this.include(operator, nameParts);
081        }
082    
083        @Deprecated
084        public void select(Member member) {
085            this.include(member);
086        }
087    
088        @Deprecated
089        public void select(
090                Selection.Operator operator,
091                Member member)
092        {
093            this.include(operator, member);
094        }
095    
096        /**
097         * Clears the current member inclusions from this query dimension.
098         * @deprecated This method is deprecated in favor of
099         * {@link QueryDimension#clearInclusions()}
100         */
101        @Deprecated
102        public void clearSelection() {
103            this.clearInclusions();
104        }
105    
106        /**
107         * Selects members and includes them in the query.
108         * <p>This method selects and includes a single member with the
109         * {@link Selection.Operator#MEMBER} operator.
110         * @param nameParts Name of the member to select and include.
111         * @throws OlapException If no member corresponding to the supplied
112         * name parts could be resolved in the cube.
113         */
114        public void include(String... nameParts) throws OlapException {
115            this.include(Selection.Operator.MEMBER, nameParts);
116        }
117    
118        /**
119         * Selects members and includes them in the query.
120         * <p>This method selects and includes a member along with it's
121         * relatives, depending on the supplied {@link Selection.Operator}
122         * operator.
123         * @param operator Selection operator that defines what relatives of the
124         * supplied member name to include along.
125         * @param nameParts Name of the root member to select and include.
126         * @throws OlapException If no member corresponding to the supplied
127         * name parts could be resolved in the cube.
128         */
129        public void include(
130            Selection.Operator operator,
131            String... nameParts) throws OlapException
132        {
133            Member member = this.getQuery().getCube().lookupMember(nameParts);
134            if (member == null) {
135                throw new OlapException(
136                    "Unable to find a member with name "
137                        + Olap4jUtil.stringArrayToString(nameParts));
138            } else {
139                this.include(
140                    operator,
141                    member);
142            }
143        }
144    
145        /**
146         * Selects members and includes them in the query.
147         * <p>This method selects and includes a single member with the
148         * {@link Selection.Operator#MEMBER} selection operator.
149         * @param member The member to select and include in the query.
150         */
151        public void include(Member member) {
152            include(Selection.Operator.MEMBER, member);
153        }
154    
155        /**
156         * Selects members and includes them in the query.
157         * <p>This method selects and includes a member along with it's
158         * relatives, depending on the supplied {@link Selection.Operator}
159         * operator.
160         * @param operator Selection operator that defines what relatives of the
161         * supplied member name to include along.
162         * @param member Root member to select and include.
163         */
164        public void include(
165                Selection.Operator operator,
166                Member member)
167        {
168            if (member.getDimension().equals(this.dimension)) {
169                Selection selection =
170                        query.getSelectionFactory().createMemberSelection(
171                                member, operator);
172                this.include(selection);
173            }
174        }
175    
176        /**
177         * Includes a selection of members in the query.
178         * @param selection The selection of members to include.
179         */
180        private void include(Selection selection) {
181            this.getInclusions().add(selection);
182            Integer index = Integer.valueOf(
183                    this.getInclusions().indexOf(selection));
184            this.notifyAdd(selection, index);
185        }
186    
187        /**
188         * Clears the current member inclusions from this query dimension.
189         */
190        public void clearInclusions() {
191            Map<Integer, QueryNode> removed = new HashMap<Integer, QueryNode>();
192            for (Selection node : this.inclusions) {
193                removed.put(
194                    Integer.valueOf(this.inclusions.indexOf(node)),
195                    node);
196                ((QueryNodeImpl) node).clearListeners();
197            }
198            this.inclusions.clear();
199            this.notifyRemove(removed);
200        }
201    
202        /**
203         * Selects members and excludes them from the query.
204         * <p>This method selects and excludes a single member with the
205         * {@link Selection.Operator#MEMBER} operator.
206         * @param nameParts Name of the member to select and exclude.
207         * @throws OlapException If no member corresponding to the supplied
208         * name parts could be resolved in the cube.
209         */
210        public void exclude(String... nameParts) throws OlapException {
211            this.exclude(Selection.Operator.MEMBER, nameParts);
212        }
213    
214        /**
215         * Selects members and excludes them from the query.
216         * <p>This method selects and excludes a member along with it's
217         * relatives, depending on the supplied {@link Selection.Operator}
218         * operator.
219         * @param operator Selection operator that defines what relatives of the
220         * supplied member name to exclude along.
221         * @param nameParts Name of the root member to select and exclude.
222         * @throws OlapException If no member corresponding to the supplied
223         * name parts could be resolved in the cube.
224         */
225        public void exclude(
226            Selection.Operator operator,
227            String... nameParts) throws OlapException
228        {
229            Member rootMember = this.getQuery().getCube().lookupMember(nameParts);
230            if (rootMember == null) {
231                throw new OlapException(
232                    "Unable to find a member with name "
233                        + Olap4jUtil.stringArrayToString(nameParts));
234            } else {
235                this.exclude(
236                    operator,
237                    rootMember);
238            }
239        }
240    
241        /**
242         * Selects members and excludes them from the query.
243         * <p>This method selects and excludes a single member with the
244         * {@link Selection.Operator#MEMBER} selection operator.
245         * @param member The member to select and exclude from the query.
246         */
247        public void exclude(Member member) {
248            exclude(Selection.Operator.MEMBER, member);
249        }
250    
251        /**
252         * Selects members and excludes them from the query.
253         * <p>This method selects and excludes a member along with it's
254         * relatives, depending on the supplied {@link Selection.Operator}
255         * operator.
256         * @param operator Selection operator that defines what relatives of the
257         * supplied member name to exclude along.
258         * @param member Root member to select and exclude.
259         */
260        public void exclude(
261                Selection.Operator operator,
262                Member member)
263        {
264            if (member.getDimension().equals(this.dimension)) {
265                Selection selection =
266                        query.getSelectionFactory().createMemberSelection(
267                                member, operator);
268                this.exclude(selection);
269            }
270        }
271    
272        /**
273         * Excludes a selection of members from the query.
274         * @param selection The selection of members to exclude.
275         */
276        private void exclude(Selection selection) {
277            this.getExclusions().add(selection);
278            Integer index = Integer.valueOf(
279                    this.getExclusions().indexOf(selection));
280            this.notifyAdd(selection, index);
281        }
282    
283        /**
284         * Clears the current member inclusions from this query dimension.
285         */
286        public void clearExclusions() {
287            Map<Integer, QueryNode> removed = new HashMap<Integer, QueryNode>();
288            for (Selection node : this.exclusions) {
289                removed.put(
290                    Integer.valueOf(this.exclusions.indexOf(node)),
291                    node);
292                ((QueryNodeImpl) node).clearListeners();
293            }
294            this.exclusions.clear();
295            this.notifyRemove(removed);
296        }
297    
298        public static String[] getNameParts(String sel) {
299            List<Segment> list = IdentifierNode.parseIdentifier(sel);
300            String nameParts[] = new String[list.size()];
301            for (int i = 0; i < list.size(); i++) {
302                nameParts[i] = list.get(i).getName();
303            }
304            return nameParts;
305        }
306    
307        /**
308         * Resolves a selection of members into an actual list
309         * of the root member and it's relatives selected by the Selection object.
310         * @param selection The selection of members to resolve.
311         * @return A list of the actual members selected by the selection object.
312         * @throws OlapException If resolving the selections triggers an exception
313         * while looking up members in the underlying cube.
314         */
315        public List<Member> resolve(Selection selection) throws OlapException
316        {
317            assert selection != null;
318            final Member.TreeOp op;
319            Member.TreeOp secondOp = null;
320            switch (selection.getOperator()) {
321            case CHILDREN:
322                op = Member.TreeOp.CHILDREN;
323                break;
324            case SIBLINGS:
325                op = Member.TreeOp.SIBLINGS;
326                break;
327            case INCLUDE_CHILDREN:
328                op = Member.TreeOp.SELF;
329                secondOp = Member.TreeOp.CHILDREN;
330                break;
331            case MEMBER:
332                op = Member.TreeOp.SELF;
333                break;
334            default:
335                throw new OlapException(
336                    "Operation not supported: " + selection.getOperator());
337            }
338            Set<Member.TreeOp> set = new TreeSet<Member.TreeOp>();
339            set.add(op);
340            if (secondOp != null) {
341                set.add(secondOp);
342            }
343            try {
344                return
345                    query.getCube().lookupMembers(
346                        set,
347                        getNameParts(selection.getName()));
348            } catch (Exception e) {
349                throw new OlapException(
350                    "Error while resolving selection " + selection.toString(),
351                    e);
352            }
353        }
354    
355        /**
356         * Returns a list of the inclusions within this dimension.
357         * <p>Be aware that modifications to this list might
358         * have unpredictable consequences.</p>
359         * @deprecated Use {@link QueryDimension#getInclusions()}
360         * @return list of inclusions
361         */
362        @Deprecated
363        public List<Selection> getSelections() {
364            return this.getInclusions();
365        }
366    
367        /**
368         * Returns a list of the inclusions within this dimension.
369         *
370         * <p>Be aware that modifications to this list might
371         * have unpredictable consequences.</p>
372         *
373         * @return list of inclusions
374         */
375        public List<Selection> getInclusions() {
376            return inclusions;
377        }
378    
379        /**
380         * Returns a list of the exclusions within this dimension.
381         *
382         * <p>Be aware that modifications to this list might
383         * have unpredictable consequences.</p>
384         *
385         * @return list of exclusions
386         */
387        public List<Selection> getExclusions() {
388            return exclusions;
389        }
390    
391        /**
392         * Returns the underlying dimension object onto which
393         * this query dimension is based.
394         * <p>Returns a mutable object so operations on it have
395         * unpredictable consequences.
396         * @return The underlying dimension representation.
397         */
398        public Dimension getDimension() {
399            return dimension;
400        }
401    
402        /**
403         * Forces a change onto which dimension is the current
404         * base of this QueryDimension object.
405         * <p>Forcing a change in the duimension assignment has
406         * unpredictable consequences.
407         * @param dimension The new dimension to assign to this
408         * query dimension.
409         */
410        public void setDimension(Dimension dimension) {
411            this.dimension = dimension;
412        }
413    
414        /**
415         * Sorts the dimension members by name in the
416         * order supplied as a parameter.
417         * @param order The {@link SortOrder} to use.
418         */
419        public void sort(SortOrder order) {
420            this.sortOrder = order;
421        }
422    
423        /**
424         * Returns the current order in which the
425         * dimension members are sorted.
426         * @return A value of {@link SortOrder}
427         */
428        public SortOrder getSortOrder() {
429            return this.sortOrder;
430        }
431    
432        /**
433         * Clears the current sorting settings.
434         */
435        public void clearSort() {
436            this.sortOrder = null;
437        }
438    
439        /**
440         * Returns the current mode of hierarchyzation, or null
441         * if no hierarchyzation is currently performed.
442         * @return Either a hierarchyzation mode value or null
443         * if no hierarchyzation is currently performed.
444         */
445        public HierarchizeMode getHierarchizeMode() {
446            return hierarchizeMode;
447        }
448    
449        /**
450         * Triggers the hierarchization of the included members within this
451         * QueryDimension.
452         * <p>The dimension inclusions will be wrapped in an MDX Hierarchize
453         * function call.
454         * @param hierarchizeMode If parents should be included before or after
455         * their children. (Equivalent to the POST/PRE MDX literal for the
456         * Hierarchize() function)
457         * inside the Hierarchize() MDX function call.
458         */
459        public void setHierarchizeMode(HierarchizeMode hierarchizeMode) {
460            this.hierarchizeMode = hierarchizeMode;
461        }
462    
463        /**
464         * Tells the QueryDimension not to hierarchyze it's included selections.
465         */
466        public void clearHierarchizeMode() {
467            this.hierarchizeMode = null;
468        }
469    
470        private class SelectionList extends AbstractList<Selection> {
471            private final List<Selection> list = new ArrayList<Selection>();
472    
473            public Selection get(int index) {
474                return list.get(index);
475            }
476    
477            public int size() {
478                return list.size();
479            }
480    
481            public Selection set(int index, Selection selection) {
482                return list.set(index, selection);
483            }
484    
485            public void add(int index, Selection selection) {
486                if (this.contains(selection)) {
487                    throw new IllegalStateException(
488                        "dimension already contains selection");
489                }
490                list.add(index, selection);
491            }
492    
493            public Selection remove(int index) {
494                return list.remove(index);
495            }
496        }
497    
498        /**
499         * Defines in which way the hierarchize operation
500         * should be performed.
501         */
502        public static enum HierarchizeMode {
503            /**
504             * Parents are placed before children.
505             */
506            PRE,
507            /**
508             * Parents are placed after children
509             */
510            POST
511        }
512    
513        void tearDown() {
514            for (Selection node : this.inclusions) {
515                ((QueryNodeImpl)node).clearListeners();
516            }
517            for (Selection node : this.exclusions) {
518                ((QueryNodeImpl)node).clearListeners();
519            }
520            this.inclusions.clear();
521            this.exclusions.clear();
522        }
523    }
524    
525    // End QueryDimension.java