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