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