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