/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.metadata.index;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.teiid.core.id.UUID;
import org.teiid.core.index.IEntryResult;
import org.teiid.core.util.Assertion;
import org.teiid.internal.core.index.EntryResult;
import org.teiid.internal.core.index.IIndexConstants;
import org.teiid.metadata.*;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.metadata.Column.SearchType;
import org.teiid.metadata.Datatype.Variety;
import org.teiid.metadata.KeyRecord.Type;
/**
* RuntimeAdapter
*/
public class RecordFactory {
/** Delimiter used to separate the URI string from the URI fragment */
public static final String URI_REFERENCE_DELIMITER = "#"; //$NON-NLS-1$
public static final int INDEX_RECORD_BLOCK_SIZE = IIndexConstants.BLOCK_SIZE - 32;
/**
* The version number associated with any index records prior to the point
* when version information was encoded in newly created records
*/
public static final int NONVERSIONED_RECORD_INDEX_VERSION = 0;
/**
* The version number that is associated with the change made to change the list
* delimiter. Added 07/22/2004.
* @since 4.1.1
*/
public static final int DELIMITER_INDEX_VERSION = 1;
/**
* The version number that is associated with the change made to add materialization
* property on tables. Added 08/18/2004.
* @since 4.2
*/
public static final int TABLE_MATERIALIZATION_INDEX_VERSION = 2;
/**
* The version number that is associated with the change made to add native type
* property on columns. Added 08/24/2004.
* @since 4.2
*/
public static final int COLUMN_NATIVE_TYPE_INDEX_VERSION = 3;
/**
* The version number that is associated with the change made to add an input parameter
* flag on columns. The flag is used to indicate if an element for a virtual table
* represents an input parameter. This change was made to support the Procedural-Relational
* Mapping project. Added 09/29/2004.
* @since 4.2
*/
public static final int COLUMN_INPUT_PARAMETER_FLAG_INDEX_VERSION = 4;
/**
* The version number that is associated with the change made to remove property value
* pairs from the annotation records any properties on annotations would now be indexed
* as part of the properties index. Added 12/14/2004.
* @since 4.2
*/
public static final int ANNOTATION_TAGS_INDEX_VERSION = 5;
/**
* The version number that is associated with the change made to add uuid for the
* transformation mapping root on the transformation records, uuids would now be indexed
* as part of the transformation index. Added 1/13/2005.
* @since 4.2
*/
public static final int TRANSFORMATION_UUID_INDEX_VERSION = 6;
/**
* The version number that is associated with the change made to add count of null and
* distinct values for columns on the column records 7/8/2005.
* @since 4.2
*/
public static final int COLUMN_NULL_DISTINCT_INDEX_VERSION = 7;
/**
* The version number that is associated with the change made to add
* primitive type ID on datatype records 02/28/2006.
* @since 5.0
*/
public static final int PRIMITIVE_TYPE_ID_INDEX_VERSION = 8;
/**
* The version number that is associated with the change made to add
* an update count to physical stored and XQuery procedures 04/29/2008.
* @since 5.0
*/
public static final int PROCEDURE_UPDATE_COUNT_VERSION = 9;
public static final int NONZERO_UNKNOWN_CARDINALITY = 10;
/**
* The version number that is encoded with all newly created index records
*/
public static final int CURRENT_INDEX_VERSION = PROCEDURE_UPDATE_COUNT_VERSION;
private int version = NONVERSIONED_RECORD_INDEX_VERSION;
protected String parentId;
/**
* Return a collection of {@link AbstractMetadataRecord}
* instances for the result obtained from executing <code>queryEntriesMatching</code>
* @param queryResult
* @param container Container reference to be set on the record
*/
public List<AbstractMetadataRecord> getMetadataRecord(final IEntryResult[] queryResult) {
final List records = new ArrayList(queryResult.length);
for (int i = 0; i < queryResult.length; i++) {
final AbstractMetadataRecord record = getMetadataRecord(queryResult[i].getWord());
if (record != null) {
records.add(record);
}
}
return records;
}
/**
* Return the {@link AbstractMetadataRecord}
* instances for specified IEntryResult.
* @param entryResult
*/
protected AbstractMetadataRecord getMetadataRecord(final char[] record) {
parentId = null;
if (record == null || record.length == 0) {
return null;
}
switch (record[0]) {
case MetadataConstants.RECORD_TYPE.MODEL: return createModelRecord(record);
case MetadataConstants.RECORD_TYPE.TABLE: return createTableRecord(record);
case MetadataConstants.RECORD_TYPE.JOIN_DESCRIPTOR: return null;
case MetadataConstants.RECORD_TYPE.CALLABLE: return createProcedureRecord(record);
case MetadataConstants.RECORD_TYPE.CALLABLE_PARAMETER: return createProcedureParameterRecord(record);
case MetadataConstants.RECORD_TYPE.COLUMN: return createColumnRecord(record);
case MetadataConstants.RECORD_TYPE.ACCESS_PATTERN: return createColumnSetRecord(record, new KeyRecord(KeyRecord.Type.AccessPattern));
case MetadataConstants.RECORD_TYPE.INDEX: return createColumnSetRecord(record, new KeyRecord(KeyRecord.Type.Index));
case MetadataConstants.RECORD_TYPE.RESULT_SET: return createColumnSetRecord(record, new ColumnSet());
case MetadataConstants.RECORD_TYPE.UNIQUE_KEY: return createColumnSetRecord(record, new KeyRecord(KeyRecord.Type.Unique));
case MetadataConstants.RECORD_TYPE.PRIMARY_KEY: return createColumnSetRecord(record, new KeyRecord(KeyRecord.Type.Primary));
case MetadataConstants.RECORD_TYPE.FOREIGN_KEY: return createForeignKeyRecord(record);
case MetadataConstants.RECORD_TYPE.DATATYPE: return createDatatypeRecord(record);
case MetadataConstants.RECORD_TYPE.SELECT_TRANSFORM:
case MetadataConstants.RECORD_TYPE.INSERT_TRANSFORM:
case MetadataConstants.RECORD_TYPE.UPDATE_TRANSFORM:
case MetadataConstants.RECORD_TYPE.DELETE_TRANSFORM:
case MetadataConstants.RECORD_TYPE.MAPPING_TRANSFORM:
case MetadataConstants.RECORD_TYPE.PROC_TRANSFORM: return createTransformationRecord(record);
default:
return null;
}
}
/**
* Append the specified IEntryResult[] to the IEntryResult
* to create a single result representing an index entry that
* was split across multiple index records.
* @param result
* @param continuationResults
* @param blockSize
*/
public static IEntryResult joinEntryResults(final IEntryResult result,
final IEntryResult[] continuationResults,
final int blockSize) {
// If the IEntryResult is not continued on another record, return the original
char[] baseResult = result.getWord();
if (baseResult.length < blockSize || baseResult[blockSize-1] != MetadataConstants.RECORD_TYPE.RECORD_CONTINUATION) {
return result;
}
// Extract the UUID string from the original result
String baseStr = new String(baseResult);
String objectID = extractUUIDString(result);
// Create and initialize a StringBuffer to store the concatenated result
StringBuffer sb = new StringBuffer();
sb.append(baseStr.substring(0,blockSize-1));
// Append the continuation results onto the original -
// assumes the IEntryResult[] are in ascending order of segment number
final IEntryResult[] sortedResults = sortContinuationResults(objectID,continuationResults);
for (int i = 0; i < sortedResults.length; i++) {
char[] continuation = sortedResults[i].getWord();
int segNumber = getContinuationSegmentNumber(objectID,sortedResults[i]);
int beginIndex = objectID.length() + Integer.toString(segNumber).length() + 5;
for (int j = beginIndex; j < continuation.length; j++) {
if (j < blockSize-1) {
sb.append(continuation[j]);
}
}
}
return new EntryResult(sb.toString().toCharArray(),result.getFileReferences());
}
private static IEntryResult[] sortContinuationResults(final String objectID, final IEntryResult[] continuationResults) {
// If the array length is less than 10, then we should be able to safely
// assume the IEntryResult[] are in ascending order of segment count
if (continuationResults.length < 10) {
return continuationResults;
}
// If the number of continuation records is 10 or greater then we need to sort them
// by segment count since continuation records 10-19 will appear before records 1-9
// in the array
final IEntryResult[] sortedResults = new IEntryResult[continuationResults.length];
for (int i = 0; i < continuationResults.length; i++) {
int segNumber = getContinuationSegmentNumber(objectID,continuationResults[i]);
sortedResults[segNumber-1] = continuationResults[i];
}
return sortedResults;
}
public static int getContinuationSegmentNumber(final String objectID, final IEntryResult continuationResult) {
// The header portion of the continuation record is of the form:
// RECORD_CONTINUATION|objectID|segmentCount|
char[] record = continuationResult.getWord();
int segNumber = -1;
int index = objectID.length() + 4;
if (record[index+1] == IndexConstants.RECORD_STRING.RECORD_DELIMITER) {
// segment count < 10
segNumber = Character.getNumericValue(record[index]);
} else if (record[index+2] == IndexConstants.RECORD_STRING.RECORD_DELIMITER) {
// 9 < segment count < 100
char[] temp = new char[] {record[index], record[index+1]};
String segCount = new String(temp);
segNumber = Integer.parseInt(segCount);
} else if (record[index+3] == IndexConstants.RECORD_STRING.RECORD_DELIMITER) {
// 99 < segment count < 1000
char[] temp = new char[] {record[index], record[index+1], record[index+2]};
String segCount = new String(temp);
segNumber = Integer.parseInt(segCount);
}
return segNumber;
}
/**
* Extract the UUID string from the IEntryResult
* @param result
*/
public static String extractUUIDString(final IEntryResult result) {
char[] word = result.getWord();
String baseStr = new String(word);
int beginIndex = baseStr.indexOf(UUID.PROTOCOL);
int endIndex = word.length;
Assertion.assertTrue(beginIndex != -1);
for (int i = beginIndex; i < word.length; i++) {
if (word[i] == IndexConstants.RECORD_STRING.RECORD_DELIMITER) {
endIndex = i;
break;
}
}
Assertion.assertTrue(beginIndex < endIndex);
return new String(baseStr.substring(beginIndex,endIndex));
}
// ==================================================================================
// P R O T E C T E D M E T H O D S
// ==================================================================================
/**
* Create a ModelRecord instance from the specified index record
*/
public Schema createModelRecord(final char[] record) {
final List<String> tokens = getStrings(record, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
final Schema model = new Schema();
// The tokens are the standard header values
int tokenIndex = 0;
setRecordHeaderValues(model, tokens.get(tokenIndex++), tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++));
// The next token is the max set size
tokenIndex++;
// The next token is the model type
model.setPhysical(Integer.parseInt(tokens.get(tokenIndex++)) == 0);
// The next token is the primary metamodel Uri
model.setPrimaryMetamodelUri(getObjectValue(tokens.get(tokenIndex++)));
// The next token are the supports flags
tokens.get(tokenIndex++);
// The next tokens are footer values - the footer will contain the version number for the index record
setRecordFooterValues(model, tokens, tokenIndex);
return model;
}
/**
* Create a TransformationRecord instance from the specified index record
*/
public TransformationRecordImpl createTransformationRecord(final char[] record) {
final List<String> tokens = getStrings(record, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
final TransformationRecordImpl transform = new TransformationRecordImpl();
// Extract the index version information from the record
int indexVersion = getIndexVersion(record);
// The tokens are the standard header values
int tokenIndex = 2;
// The next token is the UUID of the transformed object
transform.setUUID(getObjectValue(tokens.get(tokenIndex++)));
// The next token is the UUID of the transformation object
if(includeTransformationUUID(indexVersion)) {
tokenIndex++;
//transform.setUUID(getObjectValue((tokens.get(tokenIndex++))));
}
// The next token is the transformation definition
transform.setTransformation(getObjectValue(tokens.get(tokenIndex++)));
// The next token are the list of bindings
List bindings = getStrings(tokens.get(tokenIndex++), getListDelimiter(indexVersion));
transform.setBindings(bindings);
// The next token are the list of schemaPaths
List schemaPaths = getStrings(tokens.get(tokenIndex++), getListDelimiter(indexVersion));
transform.setSchemaPaths(schemaPaths);
// The next tokens are footer values
setRecordFooterValues(transform, tokens, tokenIndex);
return transform;
}
protected static short getKeyTypeForRecordType(final char recordType) {
switch (recordType) {
case MetadataConstants.RECORD_TYPE.UNIQUE_KEY: return MetadataConstants.KEY_TYPES.UNIQUE_KEY;
case MetadataConstants.RECORD_TYPE.INDEX: return MetadataConstants.KEY_TYPES.INDEX;
case MetadataConstants.RECORD_TYPE.ACCESS_PATTERN: return MetadataConstants.KEY_TYPES.ACCESS_PATTERN;
case MetadataConstants.RECORD_TYPE.PRIMARY_KEY: return MetadataConstants.KEY_TYPES.PRIMARY_KEY;
case MetadataConstants.RECORD_TYPE.FOREIGN_KEY: return MetadataConstants.KEY_TYPES.FOREIGN_KEY;
case MetadataConstants.RECORD_TYPE.RESULT_SET : return -1;
default:
throw new IllegalArgumentException("Invalid record type, for key" + recordType); //$NON-NLS-1$
}
}
/**
* Create a TableRecord instance from the specified index record
*/
public Table createTableRecord(final char[] record) {
final List<String> tokens = getStrings(record, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
final Table table = new Table();
// Extract the index version information from the record
int indexVersion = getIndexVersion(record);
// The tokens are the standard header values
int tokenIndex = 0;
setRecordHeaderValues(table, tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++));
// The next token is the cardinality
int cardinality = Integer.parseInt(tokens.get(tokenIndex++));
if (indexVersion < NONZERO_UNKNOWN_CARDINALITY && cardinality == 0) {
cardinality = -1;
}
table.setCardinality(cardinality);
// The next token is the tableType
table.setTableType(Table.Type.values()[Integer.parseInt(tokens.get(tokenIndex++))]);
// The next token are the supports flags
char[] supportFlags = (tokens.get(tokenIndex++)).toCharArray();
table.setVirtual(getBooleanValue(supportFlags[0]));
table.setSystem(getBooleanValue(supportFlags[1]));
table.setSupportsUpdate(getBooleanValue(supportFlags[2]));
if(includeMaterializationFlag(indexVersion)) {
table.setMaterialized(getBooleanValue(supportFlags[3]));
}
// The next token are the UUIDs for the column references (no longer stored on the record)
tokenIndex++;
// The next token is the UUID of the primary key
String id = getObjectValue(tokens.get(tokenIndex++));
if (id != null) {
KeyRecord pk = new KeyRecord(KeyRecord.Type.Primary);
pk.setUUID(id);
table.setPrimaryKey(pk);
}
List<String> indexes = getStrings(tokens.get(++tokenIndex), getListDelimiter(indexVersion));
if (!indexes.isEmpty()) {
table.setIndexes(new ArrayList<KeyRecord>(indexes.size()));
for (String string : indexes) {
KeyRecord index = new KeyRecord(Type.Index);
index.setUUID(string);
table.getIndexes().add(index);
}
}
tokenIndex+=3; //skip reading uuids for associated records
if(includeMaterializationFlag(indexVersion)) {
// The next token are the UUIDs for the materialized table ID
Table matTable = new Table();
matTable.setUUID(tokens.get(tokenIndex++));
table.setMaterializedTable(matTable);
// The next token are the UUID for the materialized stage table ID
matTable = new Table();
matTable.setUUID(tokens.get(tokenIndex++));
table.setMaterializedStageTable(matTable);
}
// The next tokens are footer values
setRecordFooterValues(table, tokens, tokenIndex);
return table;
}
private static List<Column> createColumns(List<String> uuids) {
List<Column> columns = new ArrayList<Column>(uuids.size());
for (String uuid : uuids) {
Column column = new Column();
column.setUUID(uuid);
columns.add(column);
}
return columns;
}
/**
* Create a ColumnRecord instance from the specified index record
*/
public Column createColumnRecord(final char[] record) {
final List<String> tokens = getStrings(record, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
final Column column = new Column();
// Extract the index version information from the record
int indexVersion = getIndexVersion(record);
// The tokens are the standard header values
int tokenIndex = 0;
setRecordHeaderValues(column, tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++));
// The next token are the supports flags
char[] supportFlags = (tokens.get(tokenIndex++)).toCharArray();
column.setSelectable(getBooleanValue(supportFlags[0]));
column.setUpdatable(getBooleanValue(supportFlags[1]));
column.setAutoIncremented(getBooleanValue(supportFlags[2]));
column.setCaseSensitive(getBooleanValue(supportFlags[3]));
column.setSigned(getBooleanValue(supportFlags[4]));
column.setCurrency(getBooleanValue(supportFlags[5]));
column.setFixedLength(getBooleanValue(supportFlags[6]));
// The next token is the search type
column.setNullType(NullType.values()[Integer.parseInt(tokens.get(tokenIndex++))]);
// The next token is the search type
column.setSearchType(SearchType.values()[3 - Integer.parseInt(tokens.get(tokenIndex++))]);
// The next token is the length
column.setLength( Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the scale
column.setScale( Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the precision
column.setPrecision( Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the precision
column.setPosition( Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the charOctetLength
column.setCharOctetLength( Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the radix
column.setRadix( Integer.parseInt(tokens.get(tokenIndex++)) );
if (includeColumnNullDistinctValues(indexVersion)) {
// The next token is the distinct value
column.setDistinctValues(Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the null value
column.setNullValues(Integer.parseInt(tokens.get(tokenIndex++)) );
}
// The next token is the min value
column.setMinimumValue( getObjectValue(tokens.get(tokenIndex++)) );
// The next token is the max value
column.setMaximumValue( getObjectValue(tokens.get(tokenIndex++)) );
// The next token is the format value
column.setFormat( getObjectValue(tokens.get(tokenIndex++)) );
// The next token is the runtime type
column.setRuntimeType( getObjectValue(tokens.get(tokenIndex++)) );
if(includeColumnNativeType(indexVersion)) {
// The next token is the native type
column.setNativeType( getObjectValue(tokens.get(tokenIndex++)) );
}
// The next token is the datatype ObjectID
column.setDatatypeUUID( getObjectValue(tokens.get(tokenIndex++)) );
// The next token is the default value
column.setDefaultValue( getObjectValue(tokens.get(tokenIndex++)) );
// The next tokens are footer values
setRecordFooterValues(column, tokens, tokenIndex);
return column;
}
/**
* Create a ColumnSetRecord instance from the specified index record
*/
public ColumnSet createColumnSetRecord(final char[] record, ColumnSet columnSet) {
final List<String> tokens = getStrings(record, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
// Extract the index version information from the record
int indexVersion = getIndexVersion(record);
// The tokens are the standard header values
int tokenIndex = 0;
setRecordHeaderValues(columnSet, tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++));
// The next token are the UUIDs for the column references
List<String> uuids = getStrings(tokens.get(tokenIndex++), getListDelimiter(indexVersion));
columnSet.setColumns(createColumns(uuids));
if (record[0] == MetadataConstants.RECORD_TYPE.UNIQUE_KEY || record[0] == MetadataConstants.RECORD_TYPE.PRIMARY_KEY) {
//read the values from the index to update the tokenindex, but we don't actually use them.
tokenIndex++;
}
// The next tokens are footer values
setRecordFooterValues(columnSet, tokens, tokenIndex);
return columnSet;
}
/**
* Create a ForeignKeyRecord instance from the specified index record
*/
public ForeignKey createForeignKeyRecord(final char[] record) {
final List<String> tokens = getStrings(record, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
final ForeignKey fkRecord = new ForeignKey();
// Extract the index version information from the record
int indexVersion = getIndexVersion(record);
// The tokens are the standard header values
int tokenIndex = 0;
setRecordHeaderValues(fkRecord, tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++));
// The next token are the UUIDs for the column references
List<String> uuids = getStrings(tokens.get(tokenIndex++), getListDelimiter(indexVersion));
fkRecord.setColumns(createColumns(uuids));
// The next token is the UUID of the unique key
fkRecord.setUniqueKeyID(getObjectValue(tokens.get(tokenIndex++)));
// The next tokens are footer values
setRecordFooterValues(fkRecord, tokens, tokenIndex);
return fkRecord;
}
/**
* Create a DatatypeRecord instance from the specified index record
*/
public Datatype createDatatypeRecord(final char[] record) {
final List<String> tokens = getStrings(record, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
final Datatype dt = new Datatype();
// Extract the index version information from the record
int indexVersion = getIndexVersion(record);
// The tokens are the standard header values
int tokenIndex = 0;
// Set the record type
tokenIndex++;
// Set the datatype and basetype identifiers
tokenIndex++;
String basetypeID = getObjectValue(tokens.get(tokenIndex++));
if ( basetypeID != null ) {
final int i = basetypeID.lastIndexOf(URI_REFERENCE_DELIMITER);
if ( i != -1 && basetypeID.length() > (i+1)) {
basetypeID = basetypeID.substring(i+1);
}
}
dt.setBasetypeName(basetypeID);
// Set the fullName/objectID/nameInSource
String fullName = tokens.get(tokenIndex++);
int indx = fullName.lastIndexOf(URI_REFERENCE_DELIMITER);
if (indx > -1) {
fullName = new String(fullName.substring(indx+1));
} else {
indx = fullName.lastIndexOf(AbstractMetadataRecord.NAME_DELIM_CHAR);
if (indx > -1) {
fullName = new String(fullName.substring(indx+1));
}
}
dt.setName(fullName);
dt.setUUID(getObjectValue(tokens.get(tokenIndex++)));
dt.setNameInSource(getObjectValue(tokens.get(tokenIndex++)));
// Set the variety type and its properties
dt.setVarietyType(Variety.values()[Short.parseShort(tokens.get(tokenIndex++))]);
tokenIndex++;
// Set the runtime and java class names
dt.setRuntimeTypeName(getObjectValue(tokens.get(tokenIndex++)));
dt.setJavaClassName(getObjectValue(tokens.get(tokenIndex++)));
// Set the datatype type
dt.setType(Datatype.Type.values()[Short.parseShort(tokens.get(tokenIndex++))]);
// Set the search type
dt.setSearchType(SearchType.values()[3 - Integer.parseInt(tokens.get(tokenIndex++))]);
// Set the null type
dt.setNullType(NullType.values()[Integer.parseInt(tokens.get(tokenIndex++))]);
// Set the boolean flags
char[] booleanValues = (tokens.get(tokenIndex++)).toCharArray();
dt.setSigned(getBooleanValue(booleanValues[0]));
dt.setAutoIncrement(getBooleanValue(booleanValues[1]));
dt.setCaseSensitive(getBooleanValue(booleanValues[2]));
// Append the length
dt.setLength( Integer.parseInt(tokens.get(tokenIndex++)) );
// Append the precision length
dt.setPrecision( Integer.parseInt(tokens.get(tokenIndex++)) );
// Append the scale
dt.setScale( Integer.parseInt(tokens.get(tokenIndex++)) );
// Append the radix
dt.setRadix( Integer.parseInt(tokens.get(tokenIndex++)) );
// Set the primitive type identifier
if (includePrimitiveTypeIdValue(indexVersion)) {
// The next token is the primitive type identifier
tokenIndex++;
}
// The next tokens are footer values
setRecordFooterValues(dt, tokens, tokenIndex);
return dt;
}
/**
* Create a ProcedureRecord instance from the specified index record
*/
public Procedure createProcedureRecord(final char[] record) {
final List<String> tokens = getStrings(record, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
final Procedure procRd = new Procedure();
// Extract the index version information from the record
int indexVersion = getIndexVersion(record);
// The tokens are the standard header values
int tokenIndex = 0;
// Set the record type
setRecordHeaderValues(procRd, tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++));
// Set the boolean flags
char[] booleanValues = (tokens.get(tokenIndex++)).toCharArray();
// flag indicating if the procedure is a function
procRd.setFunction(getBooleanValue(booleanValues[0]));
// flag indicating if the procedure is virtual
procRd.setVirtual(getBooleanValue(booleanValues[1]));
// The next token are the UUIDs for the param references
List<String> uuids = getStrings(tokens.get(tokenIndex++), getListDelimiter(indexVersion));
List<ProcedureParameter> columns = new ArrayList<ProcedureParameter>(uuids.size());
for (String uuid : uuids) {
ProcedureParameter column = new ProcedureParameter();
column.setUUID(uuid);
columns.add(column);
}
procRd.setParameters(columns);
// The next token is the UUID of the resultSet object
String rsId = getObjectValue(tokens.get(tokenIndex++));
if (rsId != null) {
ColumnSet cs = new ColumnSet();
cs.setUUID(rsId);
procRd.setResultSet(cs);
}
if (includeProcedureUpdateCount(indexVersion)) {
procRd.setUpdateCount(Integer.parseInt(tokens.get(tokenIndex++)) - 1);
}
// The next tokens are footer values
setRecordFooterValues(procRd, tokens, tokenIndex);
return procRd;
}
/**
* Create a ProcedureParameterRecord instance from the specified index record
* header|defaultValue|dataType|length|radix|scale|nullType|precision|paramType|footer|
*/
public ProcedureParameter createProcedureParameterRecord(final char[] record) {
final String str = new String(record);
final List<String> tokens = getStrings(str, IndexConstants.RECORD_STRING.RECORD_DELIMITER);
final ProcedureParameter paramRd = new ProcedureParameter();
// The tokens are the standard header values
int tokenIndex = 0;
// Set the record type
setRecordHeaderValues(paramRd, tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++),
tokens.get(tokenIndex++), tokens.get(tokenIndex++));
// The next token is the default value of the parameter
paramRd.setDefaultValue(getObjectValue(tokens.get(tokenIndex++)) );
// The next token is the runtime type
paramRd.setRuntimeType(getObjectValue(tokens.get(tokenIndex++)) );
// The next token is the uuid
paramRd.setDatatypeUUID(getObjectValue(tokens.get(tokenIndex++)) );
// The next token is the length
paramRd.setLength(Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the radix
paramRd.setRadix(Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the scale
paramRd.setScale(Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the null type
paramRd.setNullType(NullType.values()[Integer.parseInt(tokens.get(tokenIndex++))]);
// The next token is the precision
paramRd.setPrecision(Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is the position
paramRd.setPosition(Integer.parseInt(tokens.get(tokenIndex++)) );
// The next token is parameter type
ProcedureParameter.Type type = null;
switch (Short.parseShort(tokens.get(tokenIndex++))) {
case MetadataConstants.PARAMETER_TYPES.IN_PARM:
type = ProcedureParameter.Type.In;
break;
case MetadataConstants.PARAMETER_TYPES.INOUT_PARM:
type = ProcedureParameter.Type.InOut;
break;
case MetadataConstants.PARAMETER_TYPES.OUT_PARM:
type = ProcedureParameter.Type.Out;
break;
case MetadataConstants.PARAMETER_TYPES.RETURN_VALUE:
type = ProcedureParameter.Type.ReturnValue;
break;
default:
throw new IllegalArgumentException("Invalid parameter type, please ensure all parameter types are valid in Designer."); //$NON-NLS-1$
}
paramRd.setType(type);
// The next token is flag for parameter optional prop
char[] flags = (tokens.get(tokenIndex++)).toCharArray();
paramRd.setOptional(getBooleanValue(flags[0]));
// The next tokens are footer values
setRecordFooterValues(paramRd, tokens, tokenIndex);
return paramRd;
}
/**
* Search for and return the version number associated with this record.
* If no version information is found encoded in the record then the
* version number of NONVERSIONED_RECORD_INDEX_VERSION will be returned.
* @param record
* @since 4.2
*/
int getIndexVersion(final char[] record) {
if (version == NONVERSIONED_RECORD_INDEX_VERSION) {
int endIndex = record.length;
int beginIndex = (endIndex - 6 > 0 ? endIndex - 6 : 1);
for (int i = beginIndex; i < endIndex; i++) {
if (record[i] == IndexConstants.RECORD_STRING.INDEX_VERSION_MARKER) {
char versionPart1 = record[i+1];
char versionPart2 = record[i+2];
if (Character.isDigit(versionPart1) && Character.isDigit(versionPart2)){
version = Character.digit(versionPart1, 10) * 10 + Character.digit(versionPart2, 10);
}
}
}
}
return version;
}
public String getObjectValue(final String str) {
if (str != null && str.length() == 1 && str.charAt(0) == IndexConstants.RECORD_STRING.SPACE) {
return null;
}
return str;
}
public boolean getBooleanValue(final char b) {
if (b == IndexConstants.RECORD_STRING.TRUE) {
return true;
}
return false;
}
public static List<String> getStrings(final String record, final char listDelimiter) {
return getStrings(record.toCharArray(), listDelimiter);
}
public static List<String> getStrings(final char[] record, final char listDelimiter) {
if (record == null || record.length == 0) {
return Collections.emptyList();
}
if (record.length == 1 && record[0] == IndexConstants.RECORD_STRING.SPACE) {
return Collections.emptyList();
}
List<String> result = new ArrayList<String>();
int start = 0;
for (int i = 0; i < record.length; i++) {
if (record[i] == listDelimiter) {
if (i != start) {
result.add(new String(record, start, i - start));
}
start = i+1;
}
}
if (start < record.length) {
result.add(new String(record, start, record.length - start));
}
return result;
}
public char getListDelimiter(final int indexVersionNumber) {
if (indexVersionNumber < DELIMITER_INDEX_VERSION) {
return IndexConstants.RECORD_STRING.LIST_DELIMITER_OLD;
}
return IndexConstants.RECORD_STRING.LIST_DELIMITER;
}
public boolean includeMaterializationFlag(final int indexVersionNumber) {
if (indexVersionNumber < TABLE_MATERIALIZATION_INDEX_VERSION) {
return false;
}
return true;
}
public boolean includeMaterializedTables(final int indexVersionNumber) {
if (indexVersionNumber < TABLE_MATERIALIZATION_INDEX_VERSION) {
return false;
}
return true;
}
public boolean includeColumnNativeType(final int indexVersionNumber) {
if (indexVersionNumber < COLUMN_NATIVE_TYPE_INDEX_VERSION) {
return false;
}
return true;
}
public boolean includeColumnNullDistinctValues(final int indexVersionNumber) {
if (indexVersionNumber < COLUMN_NULL_DISTINCT_INDEX_VERSION) {
return false;
}
return true;
}
public boolean includePrimitiveTypeIdValue(final int indexVersionNumber) {
if (indexVersionNumber < PRIMITIVE_TYPE_ID_INDEX_VERSION) {
return false;
}
return true;
}
public boolean includeInputParameterFlag(final int indexVersionNumber) {
if (indexVersionNumber < COLUMN_INPUT_PARAMETER_FLAG_INDEX_VERSION) {
return false;
}
return true;
}
public boolean includeAnnotationProperties(final int indexVersionNumber) {
if (indexVersionNumber < ANNOTATION_TAGS_INDEX_VERSION) {
return true;
}
return false;
}
public boolean includeTransformationUUID(final int indexVersionNumber) {
if (indexVersionNumber < TRANSFORMATION_UUID_INDEX_VERSION) {
return false;
}
return true;
}
private static boolean includeProcedureUpdateCount(final int indexVersionNumber) {
return (indexVersionNumber >= PROCEDURE_UPDATE_COUNT_VERSION);
}
// ==================================================================================
// P R I V A T E M E T H O D S
// ==================================================================================
/**
* Set the "header" values on the specified MetadataRecord.
* All index file record headers are of the form:
* recordType|upperFullName|objectID|fullName|nameInSource|parentObjectID
* The order of the fields in the index file header must also
* be the order of the arguments in method signature.
*/
private void setRecordHeaderValues(final AbstractMetadataRecord record, final String recordType,
final String upperName, final String objectID, String fullName,
final String nameInSource,
final String parentObjectID) {
record.setUUID(getObjectValue(objectID));
String parentName = fullName;
if (fullName != null) {
String name = fullName;
if (record instanceof ProcedureParameter || record instanceof KeyRecord) { //take only the last part
name = getShortName(fullName);
} else { //remove model name
int index = fullName.indexOf(IndexConstants.NAME_DELIM_CHAR);
if (index > 0) {
name = new String(fullName.substring(index + 1));
parentName = new String(fullName.substring(0, index));
}
}
record.setName(name);
}
if (parentName != null) {
if (record instanceof Table) {
Schema s = new Schema();
s.setName(parentName);
((Table)record).setParent(s);
} else if (record instanceof Procedure) {
Schema s = new Schema();
s.setName(parentName);
((Procedure)record).setParent(s);
}
}
parentId = getObjectValue(parentObjectID);
record.setNameInSource(getObjectValue(nameInSource));
}
static String getShortName(String fullName) {
int index = fullName.lastIndexOf(IndexConstants.NAME_DELIM_CHAR);
if (index > 0) {
fullName = new String(fullName.substring(index + 1));
}
return fullName;
}
/**
* Set the "footer" values on the specified MetadataRecord.
* All index file record footers are of the form:
* modelPath|name|indexVersion
* The order of the fields in the index file header must also
* be the order of the arguments in method signature.
*/
private void setRecordFooterValues(final AbstractMetadataRecord record, final List<String> tokens, int tokenIndex) {
if (record instanceof TransformationRecordImpl) {
((TransformationRecordImpl)record).setResourcePath(getOptionalToken(tokens, tokenIndex));
}
tokenIndex++;
if (record.getName() == null) {
record.setName(getOptionalToken(tokens, tokenIndex++));
}
//placeholder for index version
getOptionalToken(tokens, tokenIndex++);
}
public String getOptionalToken( final List<String> tokens, int tokenIndex) {
if(tokens.size() > tokenIndex) {
return tokens.get(tokenIndex);
}
return null;
}
}