001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2011-2012 Pentaho and others 008// All Rights Reserved. 009*/ 010package mondrian.spi; 011 012import mondrian.olap.Util; 013import mondrian.rolap.BitKey; 014import mondrian.util.ByteString; 015 016import java.io.Serializable; 017import java.util.*; 018 019/** 020 * SegmentHeaders are the key objects used to retrieve the segments 021 * from the segment cache. 022 * 023 * <p>The segment header objects are immutable and fully serializable. 024 * 025 * <p>The headers have each an ID which is a SHA-256 checksum of the 026 * following properties, concatenated. See 027 * {@link SegmentHeader#getUniqueID()} 028 * <ul> 029 * <li>Schema Name</li> 030 * <li>Cube Name</li> 031 * <li>Measure Name</li> 032 * <li>For each column:</li> 033 * <ul> 034 * <li>Column table name</li> 035 * <li>Column physical name</li> 036 * <li>For each predicate value:</li> 037 * <ul> 038 * <li>The equivalent of 039 * <code>String.valueof([value object])</code></li> 040 * </ul> 041 * </ul> 042 * </ul> 043 * 044 * @author LBoudreau 045 */ 046public class SegmentHeader implements Serializable { 047 private static final long serialVersionUID = 8696439182886512850L; 048 private final int arity; 049 private final List<SegmentColumn> constrainedColumns; 050 private final List<SegmentColumn> excludedRegions; 051 public final List<String> compoundPredicates; 052 public final String measureName; 053 public final String cubeName; 054 public final String schemaName; 055 public final String rolapStarFactTableName; 056 public final BitKey constrainedColsBitKey; 057 private final int hashCode; 058 private ByteString uniqueID; 059 private String description; 060 public final ByteString schemaChecksum; 061 062 /** 063 * Creates a segment header. 064 * 065 * @param schemaName The name of the schema which this 066 * header belongs to. 067 * @param schemaChecksum Schema checksum 068 * @param cubeName The name of the cube this segment belongs to. 069 * @param measureName The name of the measure which defines 070 * this header. 071 * @param constrainedColumns An array of constrained columns 072 * objects which define the predicated of this segment header. 073 * @param compoundPredicates Compound predicates (Must not be null, but 074 * typically empty.) 075 * @param rolapStarFactTableName Star fact table name 076 * @param constrainedColsBitKey Constrained columns bit key 077 * @param excludedRegions Excluded regions. (Must not be null, but typically 078 */ 079 public SegmentHeader( 080 String schemaName, 081 ByteString schemaChecksum, 082 String cubeName, 083 String measureName, 084 List<SegmentColumn> constrainedColumns, 085 List<String> compoundPredicates, 086 String rolapStarFactTableName, 087 BitKey constrainedColsBitKey, 088 List<SegmentColumn> excludedRegions) 089 { 090 this.constrainedColumns = constrainedColumns; 091 this.excludedRegions = excludedRegions; 092 this.schemaName = schemaName; 093 this.schemaChecksum = schemaChecksum; 094 assert schemaChecksum != null; 095 this.cubeName = cubeName; 096 this.measureName = measureName; 097 this.compoundPredicates = compoundPredicates; 098 this.rolapStarFactTableName = rolapStarFactTableName; 099 this.constrainedColsBitKey = constrainedColsBitKey; 100 this.arity = constrainedColumns.size(); 101 // Hash code might be used extensively. Better compute 102 // it up front. 103 this.hashCode = computeHashCode(); 104 } 105 106 private int computeHashCode() { 107 int hash = 42; 108 hash = Util.hash(hash, schemaName); 109 hash = Util.hash(hash, schemaChecksum); 110 hash = Util.hash(hash, cubeName); 111 hash = Util.hash(hash, measureName); 112 for (SegmentColumn col : this.constrainedColumns) { 113 hash = Util.hash(hash, col.columnExpression); 114 if (col.values != null) { 115 hash = Util.hashArray(hash, col.values.toArray()); 116 } 117 } 118 for (SegmentColumn col : this.excludedRegions) { 119 hash = Util.hash(hash, col.columnExpression); 120 if (col.values != null) { 121 hash = Util.hashArray(hash, col.values.toArray()); 122 } 123 } 124 hash = Util.hash(hash, compoundPredicates); 125 return hash; 126 } 127 128 public int hashCode() { 129 return hashCode; 130 } 131 132 public boolean equals(Object obj) { 133 if (!(obj instanceof SegmentHeader)) { 134 return false; 135 } 136 final SegmentHeader that = (SegmentHeader) obj; 137 return getUniqueID().equals(that.getUniqueID()) 138 && excludedRegions.equals(that.excludedRegions); 139 } 140 141 /** 142 * Creates a clone of this header by replacing some of the 143 * constrained columns in the process. 144 * @param overrideValues A list of constrained columns to either 145 * replace or add to the original header. 146 * @return A clone of the header with the columns replaced. 147 */ 148 public SegmentHeader clone(SegmentColumn[] overrideValues) { 149 Map<String, SegmentColumn> colsToAdd = 150 new HashMap<String, SegmentColumn>(); 151 for (SegmentColumn cc : this.constrainedColumns) { 152 colsToAdd.put(cc.columnExpression, cc); 153 } 154 for (SegmentColumn override : overrideValues) { 155 colsToAdd.put(override.columnExpression, override); 156 } 157 return 158 new SegmentHeader( 159 schemaName, 160 schemaChecksum, 161 cubeName, 162 measureName, 163 new ArrayList<SegmentColumn>(colsToAdd.values()), 164 Collections.<String>emptyList(), 165 rolapStarFactTableName, 166 constrainedColsBitKey, 167 Collections.<SegmentColumn>emptyList()); 168 } 169 170 /** 171 * Checks if this header can be constrained by a given region. 172 * 173 * <p>It will return false if the region covers one of the axis in 174 * its entirety. 175 * 176 * <p>It will return false if the region sits outside of the bounds 177 * of this header. This means that when performing a flush operation, 178 * the header must be scrapped altogether. 179 */ 180 public boolean canConstrain(SegmentColumn[] region) { 181 boolean atLeastOnePresent = false; 182 for (SegmentColumn ccToFlush : region) { 183 SegmentColumn ccActual = 184 getConstrainedColumn(ccToFlush.columnExpression); 185 if (ccActual != null) { 186 final SegmentColumn ccActualExcl = 187 getExcludedRegion(ccToFlush.columnExpression); 188 if (ccToFlush.values == null 189 || (ccActualExcl != null 190 && ccActualExcl.values != null 191 && ccActualExcl.merge(ccToFlush).values == null)) 192 { 193 // This means that the whole axis is excluded. 194 // Better destroy that segment. 195 return false; 196 } 197 if (ccActual.values != null 198 && ccActual.values.equals(ccToFlush.values)) 199 { 200 // This means that the whole axis is excluded. 201 // Better destroy that segment. 202 return false; 203 } 204 // We know there is at least one column on which 205 // we can constrain. 206 atLeastOnePresent = true; 207 } 208 } 209 return atLeastOnePresent; 210 } 211 212 /** 213 * Applies a set of exclusions to this segment header and returns 214 * a new segment header representing the original one to which a 215 * region has been excluded. 216 * 217 * @param region Region 218 * @return Header with constraint applied 219 */ 220 public SegmentHeader constrain(SegmentColumn[] region) { 221 final Map<String, SegmentColumn> newRegions = 222 new HashMap<String, SegmentColumn>(); 223 for (SegmentColumn excludedRegion : excludedRegions) { 224 newRegions.put( 225 excludedRegion.columnExpression, 226 excludedRegion); 227 } 228 for (SegmentColumn col : region) { 229 if (getConstrainedColumn(col.columnExpression) == null) { 230 continue; 231 } 232 if (newRegions.containsKey(col.columnExpression)) { 233 newRegions.put( 234 col.columnExpression, 235 newRegions.get(col.columnExpression) 236 .merge(col)); 237 } else { 238 newRegions.put( 239 col.columnExpression, 240 col); 241 } 242 } 243 assert newRegions.size() > 0; 244 return 245 new SegmentHeader( 246 schemaName, 247 schemaChecksum, 248 cubeName, 249 measureName, 250 constrainedColumns, 251 compoundPredicates, 252 rolapStarFactTableName, 253 constrainedColsBitKey, 254 new ArrayList<SegmentColumn>(newRegions.values())); 255 } 256 257 public String toString() { 258 return this.getDescription(); 259 } 260 261 /** 262 * Returns the arity of this SegmentHeader. 263 * @return The arity as an integer number. 264 */ 265 public int getArity() { 266 return arity; 267 } 268 269 public List<SegmentColumn> getExcludedRegions() { 270 return excludedRegions; 271 } 272 273 /** 274 * Returns a list of constrained columns which define this segment 275 * header. The caller should consider this list immutable. 276 * 277 * @return List of ConstrainedColumns 278 */ 279 public List<SegmentColumn> getConstrainedColumns() { 280 return constrainedColumns; 281 } 282 283 /** 284 * Returns the constrained column object, if any, corresponding 285 * to a column name and a table name. 286 * @param columnExpression The column name we want. 287 * @return A Constrained column, or null. 288 */ 289 public SegmentColumn getConstrainedColumn( 290 String columnExpression) 291 { 292 for (SegmentColumn c : constrainedColumns) { 293 if (c.columnExpression.equals(columnExpression)) { 294 return c; 295 } 296 } 297 return null; 298 } 299 300 public SegmentColumn getExcludedRegion( 301 String columnExpression) 302 { 303 for (SegmentColumn c : excludedRegions) { 304 if (c.columnExpression.equals(columnExpression)) { 305 return c; 306 } 307 } 308 return null; 309 } 310 311 public BitKey getConstrainedColumnsBitKey() { 312 return this.constrainedColsBitKey.copy(); 313 } 314 315 /** 316 * Returns a unique identifier for this header. The identifier 317 * can be used for storage and will be the same across segments 318 * which have the same schema name, cube name, measure name, 319 * and for each constrained column, the same column name, table name, 320 * and predicate values. 321 * @return A unique identification string. 322 */ 323 public ByteString getUniqueID() { 324 if (this.uniqueID == null) { 325 StringBuilder hashSB = new StringBuilder(); 326 hashSB.append(this.schemaName); 327 hashSB.append(this.schemaChecksum); 328 hashSB.append(this.cubeName); 329 hashSB.append(this.measureName); 330 for (SegmentColumn c : constrainedColumns) { 331 hashSB.append(c.columnExpression); 332 if (c.values != null) { 333 for (Object value : c.values) { 334 hashSB.append(String.valueOf(value)); 335 } 336 } 337 } 338 for (SegmentColumn c : excludedRegions) { 339 hashSB.append(c.columnExpression); 340 if (c.values != null) { 341 for (Object value : c.values) { 342 hashSB.append(String.valueOf(value)); 343 } 344 } 345 } 346 for (String c : compoundPredicates) { 347 hashSB.append(c); 348 } 349 this.uniqueID = 350 new ByteString(Util.digestSha256(hashSB.toString())); 351 } 352 return uniqueID; 353 } 354 355 /** 356 * Returns a human readable description of this 357 * segment header. 358 * @return A string describing the header. 359 */ 360 public String getDescription() { 361 if (this.description == null) { 362 StringBuilder descriptionSB = new StringBuilder(); 363 descriptionSB.append("*Segment Header\n"); 364 descriptionSB.append("Schema:["); 365 descriptionSB.append(this.schemaName); 366 descriptionSB.append("]\nChecksum:["); 367 descriptionSB.append(this.schemaChecksum); 368 descriptionSB.append("]\nCube:["); 369 descriptionSB.append(this.cubeName); 370 descriptionSB.append("]\nMeasure:["); 371 descriptionSB.append(this.measureName); 372 descriptionSB.append("]\n"); 373 descriptionSB.append("Axes:["); 374 for (SegmentColumn c : constrainedColumns) { 375 descriptionSB.append("\n {"); 376 descriptionSB.append(c.columnExpression); 377 descriptionSB.append("=("); 378 if (c.values == null) { 379 descriptionSB.append("* "); 380 } else { 381 for (Object value : c.values) { 382 descriptionSB.append("'"); 383 descriptionSB.append(value); 384 descriptionSB.append("',"); 385 } 386 } 387 descriptionSB.deleteCharAt(descriptionSB.length() - 1); 388 descriptionSB.append(")}"); 389 } 390 descriptionSB.append("]\n"); 391 descriptionSB.append("Excluded Regions:["); 392 for (SegmentColumn c : excludedRegions) { 393 descriptionSB.append("\n {"); 394 descriptionSB.append(c.columnExpression); 395 descriptionSB.append("=("); 396 if (c.values == null) { 397 descriptionSB.append("* "); 398 } else { 399 for (Object value : c.values) { 400 descriptionSB.append("'"); 401 descriptionSB.append(value); 402 descriptionSB.append("',"); 403 } 404 } 405 descriptionSB.deleteCharAt(descriptionSB.length() - 1); 406 descriptionSB.append(")}"); 407 } 408 descriptionSB.append("]\n"); 409 descriptionSB.append("Compound Predicates:["); 410 for (String c : compoundPredicates) { 411 descriptionSB.append("\n\t{"); 412 descriptionSB.append(c); 413 } 414 descriptionSB 415 .append("]\n") 416 .append("ID:[") 417 .append(getUniqueID()) 418 .append("]\n"); 419 this.description = descriptionSB.toString(); 420 } 421 return description; 422 } 423} 424 425// End SegmentHeader.java