/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package mondrian.spi;
import mondrian.olap.Util;
import mondrian.rolap.BitKey;
import mondrian.util.ByteString;
import java.io.Serializable;
import java.util.*;
/**
* SegmentHeaders are the key objects used to retrieve the segments
* from the segment cache.
*
* <p>The segment header objects are immutable and fully serializable.
*
* <p>The headers have each an ID which is a SHA-256 checksum of the
* following properties, concatenated. See
* {@link SegmentHeader#getUniqueID()}
* <ul>
* <li>Schema Name</li>
* <li>Cube Name</li>
* <li>Measure Name</li>
* <li>For each column:</li>
* <ul>
* <li>Column table name</li>
* <li>Column physical name</li>
* <li>For each predicate value:</li>
* <ul>
* <li>The equivalent of
* <code>String.valueof([value object])</code></li>
* </ul>
* </ul>
* </ul>
*
* @author LBoudreau
*/
public class SegmentHeader implements Serializable {
private static final long serialVersionUID = 8696439182886512850L;
private final int arity;
private final List<SegmentColumn> constrainedColumns;
private final List<SegmentColumn> excludedRegions;
public final List<String> compoundPredicates;
public final String measureName;
public final String cubeName;
public final String schemaName;
public final String rolapStarFactTableName;
public final BitKey constrainedColsBitKey;
private final int hashCode;
private ByteString uniqueID;
private String description;
public final ByteString schemaChecksum;
/**
* Creates a segment header.
*
* @param schemaName The name of the schema which this
* header belongs to.
* @param schemaChecksum Schema checksum
* @param cubeName The name of the cube this segment belongs to.
* @param measureName The name of the measure which defines
* this header.
* @param constrainedColumns An array of constrained columns
* objects which define the predicated of this segment header.
* @param compoundPredicates Compound predicates (Must not be null, but
* typically empty.)
* @param rolapStarFactTableName Star fact table name
* @param constrainedColsBitKey Constrained columns bit key
* @param excludedRegions Excluded regions. (Must not be null, but typically
*/
public SegmentHeader(
String schemaName,
ByteString schemaChecksum,
String cubeName,
String measureName,
List<SegmentColumn> constrainedColumns,
List<String> compoundPredicates,
String rolapStarFactTableName,
BitKey constrainedColsBitKey,
List<SegmentColumn> excludedRegions)
{
this.constrainedColumns = constrainedColumns;
this.excludedRegions = excludedRegions;
this.schemaName = schemaName;
this.schemaChecksum = schemaChecksum;
assert schemaChecksum != null;
this.cubeName = cubeName;
this.measureName = measureName;
this.compoundPredicates = compoundPredicates;
this.rolapStarFactTableName = rolapStarFactTableName;
this.constrainedColsBitKey = constrainedColsBitKey;
this.arity = constrainedColumns.size();
// Hash code might be used extensively. Better compute
// it up front.
this.hashCode = computeHashCode();
}
private int computeHashCode() {
int hash = 42;
hash = Util.hash(hash, schemaName);
hash = Util.hash(hash, schemaChecksum);
hash = Util.hash(hash, cubeName);
hash = Util.hash(hash, measureName);
for (SegmentColumn col : this.constrainedColumns) {
hash = Util.hash(hash, col.columnExpression);
if (col.values != null) {
hash = Util.hashArray(hash, col.values.toArray());
}
}
for (SegmentColumn col : this.excludedRegions) {
hash = Util.hash(hash, col.columnExpression);
if (col.values != null) {
hash = Util.hashArray(hash, col.values.toArray());
}
}
hash = Util.hash(hash, compoundPredicates);
return hash;
}
public int hashCode() {
return hashCode;
}
public boolean equals(Object obj) {
if (!(obj instanceof SegmentHeader)) {
return false;
}
final SegmentHeader that = (SegmentHeader) obj;
return getUniqueID().equals(that.getUniqueID())
&& excludedRegions.equals(that.excludedRegions);
}
/**
* Creates a clone of this header by replacing some of the
* constrained columns in the process.
* @param overrideValues A list of constrained columns to either
* replace or add to the original header.
* @return A clone of the header with the columns replaced.
*/
public SegmentHeader clone(SegmentColumn[] overrideValues) {
Map<String, SegmentColumn> colsToAdd =
new HashMap<String, SegmentColumn>();
for (SegmentColumn cc : this.constrainedColumns) {
colsToAdd.put(cc.columnExpression, cc);
}
for (SegmentColumn override : overrideValues) {
colsToAdd.put(override.columnExpression, override);
}
return
new SegmentHeader(
schemaName,
schemaChecksum,
cubeName,
measureName,
new ArrayList<SegmentColumn>(colsToAdd.values()),
Collections.<String>emptyList(),
rolapStarFactTableName,
constrainedColsBitKey,
Collections.<SegmentColumn>emptyList());
}
/**
* Checks if this header can be constrained by a given region.
*
* <p>It will return false if the region covers one of the axis in
* its entirety.
*
* <p>It will return false if the region sits outside of the bounds
* of this header. This means that when performing a flush operation,
* the header must be scrapped altogether.
*/
public boolean canConstrain(SegmentColumn[] region) {
boolean atLeastOnePresent = false;
for (SegmentColumn ccToFlush : region) {
SegmentColumn ccActual =
getConstrainedColumn(ccToFlush.columnExpression);
if (ccActual != null) {
final SegmentColumn ccActualExcl =
getExcludedRegion(ccToFlush.columnExpression);
if (ccToFlush.values == null
|| (ccActualExcl != null
&& ccActualExcl.values != null
&& ccActualExcl.merge(ccToFlush).values == null))
{
// This means that the whole axis is excluded.
// Better destroy that segment.
return false;
}
if (ccActual.values != null
&& ccActual.values.equals(ccToFlush.values))
{
// This means that the whole axis is excluded.
// Better destroy that segment.
return false;
}
// We know there is at least one column on which
// we can constrain.
atLeastOnePresent = true;
}
}
return atLeastOnePresent;
}
/**
* Applies a set of exclusions to this segment header and returns
* a new segment header representing the original one to which a
* region has been excluded.
*
* @param region Region
* @return Header with constraint applied
*/
public SegmentHeader constrain(SegmentColumn[] region) {
final Map<String, SegmentColumn> newRegions =
new HashMap<String, SegmentColumn>();
for (SegmentColumn excludedRegion : excludedRegions) {
newRegions.put(
excludedRegion.columnExpression,
excludedRegion);
}
for (SegmentColumn col : region) {
if (getConstrainedColumn(col.columnExpression) == null) {
continue;
}
if (newRegions.containsKey(col.columnExpression)) {
newRegions.put(
col.columnExpression,
newRegions.get(col.columnExpression)
.merge(col));
} else {
newRegions.put(
col.columnExpression,
col);
}
}
assert newRegions.size() > 0;
return
new SegmentHeader(
schemaName,
schemaChecksum,
cubeName,
measureName,
constrainedColumns,
compoundPredicates,
rolapStarFactTableName,
constrainedColsBitKey,
new ArrayList<SegmentColumn>(newRegions.values()));
}
public String toString() {
return this.getDescription();
}
/**
* Returns the arity of this SegmentHeader.
* @return The arity as an integer number.
*/
public int getArity() {
return arity;
}
public List<SegmentColumn> getExcludedRegions() {
return excludedRegions;
}
/**
* Returns a list of constrained columns which define this segment
* header. The caller should consider this list immutable.
*
* @return List of ConstrainedColumns
*/
public List<SegmentColumn> getConstrainedColumns() {
return constrainedColumns;
}
/**
* Returns the constrained column object, if any, corresponding
* to a column name and a table name.
* @param columnExpression The column name we want.
* @return A Constrained column, or null.
*/
public SegmentColumn getConstrainedColumn(
String columnExpression)
{
for (SegmentColumn c : constrainedColumns) {
if (c.columnExpression.equals(columnExpression)) {
return c;
}
}
return null;
}
public SegmentColumn getExcludedRegion(
String columnExpression)
{
for (SegmentColumn c : excludedRegions) {
if (c.columnExpression.equals(columnExpression)) {
return c;
}
}
return null;
}
public BitKey getConstrainedColumnsBitKey() {
return this.constrainedColsBitKey.copy();
}
/**
* Returns a unique identifier for this header. The identifier
* can be used for storage and will be the same across segments
* which have the same schema name, cube name, measure name,
* and for each constrained column, the same column name, table name,
* and predicate values.
* @return A unique identification string.
*/
public ByteString getUniqueID() {
if (this.uniqueID == null) {
StringBuilder hashSB = new StringBuilder();
hashSB.append(this.schemaName);
hashSB.append(this.schemaChecksum);
hashSB.append(this.cubeName);
hashSB.append(this.measureName);
for (SegmentColumn c : constrainedColumns) {
hashSB.append(c.columnExpression);
if (c.values != null) {
for (Object value : c.values) {
hashSB.append(String.valueOf(value));
}
}
}
for (SegmentColumn c : excludedRegions) {
hashSB.append(c.columnExpression);
if (c.values != null) {
for (Object value : c.values) {
hashSB.append(String.valueOf(value));
}
}
}
for (String c : compoundPredicates) {
hashSB.append(c);
}
this.uniqueID =
new ByteString(Util.digestSha256(hashSB.toString()));
}
return uniqueID;
}
/**
* Returns a human readable description of this
* segment header.
* @return A string describing the header.
*/
public String getDescription() {
if (this.description == null) {
StringBuilder descriptionSB = new StringBuilder();
descriptionSB.append("*Segment Header\n");
descriptionSB.append("Schema:[");
descriptionSB.append(this.schemaName);
descriptionSB.append("]\nChecksum:[");
descriptionSB.append(this.schemaChecksum);
descriptionSB.append("]\nCube:[");
descriptionSB.append(this.cubeName);
descriptionSB.append("]\nMeasure:[");
descriptionSB.append(this.measureName);
descriptionSB.append("]\n");
descriptionSB.append("Axes:[");
for (SegmentColumn c : constrainedColumns) {
descriptionSB.append("\n {");
descriptionSB.append(c.columnExpression);
descriptionSB.append("=(");
if (c.values == null) {
descriptionSB.append("* ");
} else {
for (Object value : c.values) {
descriptionSB.append("'");
descriptionSB.append(value);
descriptionSB.append("',");
}
}
descriptionSB.deleteCharAt(descriptionSB.length() - 1);
descriptionSB.append(")}");
}
descriptionSB.append("]\n");
descriptionSB.append("Excluded Regions:[");
for (SegmentColumn c : excludedRegions) {
descriptionSB.append("\n {");
descriptionSB.append(c.columnExpression);
descriptionSB.append("=(");
if (c.values == null) {
descriptionSB.append("* ");
} else {
for (Object value : c.values) {
descriptionSB.append("'");
descriptionSB.append(value);
descriptionSB.append("',");
}
}
descriptionSB.deleteCharAt(descriptionSB.length() - 1);
descriptionSB.append(")}");
}
descriptionSB.append("]\n");
descriptionSB.append("Compound Predicates:[");
for (String c : compoundPredicates) {
descriptionSB.append("\n\t{");
descriptionSB.append(c);
}
descriptionSB
.append("]\n")
.append("ID:[")
.append(getUniqueID())
.append("]\n");
this.description = descriptionSB.toString();
}
return description;
}
}
// End SegmentHeader.java