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