/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.pentaho.di.core.row.value;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.SocketTimeoutException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.database.DatabaseInterface;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleDatabaseException;
import org.pentaho.di.core.exception.KettleEOFException;
import org.pentaho.di.core.exception.KettleFileException;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.row.value.timestamp.SimpleTimestampFormat;
public class ValueMetaTimestamp extends ValueMetaDate {
public ValueMetaTimestamp() {
this( null );
}
public ValueMetaTimestamp( String name ) {
super( name, ValueMetaInterface.TYPE_TIMESTAMP );
}
@Override
public boolean isDate() {
return true;
}
@Override
public Date getDate( Object object ) throws KettleValueException {
Timestamp timestamp = getTimestamp( object );
if ( timestamp == null ) {
return null;
}
return timestamp;
}
@Override
public Long getInteger( Object object ) throws KettleValueException {
Timestamp timestamp = getTimestamp( object );
if ( timestamp == null ) {
return null;
}
long ms = timestamp.getTime();
return ms;
}
@Override
public Double getNumber( Object object ) throws KettleValueException {
Timestamp timestamp = getTimestamp( object );
if ( timestamp == null ) {
return null;
}
long ms = timestamp.getTime();
return Long.valueOf( ms ).doubleValue();
}
@Override
public BigDecimal getBigNumber( Object object ) throws KettleValueException {
Timestamp timestamp = getTimestamp( object );
if ( timestamp == null ) {
return null;
}
BigDecimal nanos =
BigDecimal.valueOf( timestamp.getTime() ).multiply( BigDecimal.valueOf( 1000000000L ) ).add(
BigDecimal.valueOf( timestamp.getNanos() ) );
return nanos;
}
@Override
public Boolean getBoolean( Object object ) throws KettleValueException {
throw new KettleValueException( toStringMeta() + ": it's not possible to convert from Timestamp to Boolean" );
}
@Override
public String getString( Object object ) throws KettleValueException {
return convertTimestampToString( getTimestamp( object ) );
}
public Timestamp getTimestamp( Object object ) throws KettleValueException {
if ( object == null ) {
return null;
}
switch ( type ) {
case TYPE_TIMESTAMP:
switch ( storageType ) {
case STORAGE_TYPE_NORMAL:
return (Timestamp) object;
case STORAGE_TYPE_BINARY_STRING:
return (Timestamp) convertBinaryStringToNativeType( (byte[]) object );
case STORAGE_TYPE_INDEXED:
return (Timestamp) index[( (Integer) object ).intValue()];
default:
throw new KettleValueException( toString() + " : Unknown storage type " + storageType + " specified." );
}
case TYPE_STRING:
switch ( storageType ) {
case STORAGE_TYPE_NORMAL:
return convertStringToTimestamp( (String) object );
case STORAGE_TYPE_BINARY_STRING:
return convertStringToTimestamp( (String) convertBinaryStringToNativeType( (byte[]) object ) );
case STORAGE_TYPE_INDEXED:
return convertStringToTimestamp( (String) index[( (Integer) object ).intValue()] );
default:
throw new KettleValueException( toString() + " : Unknown storage type " + storageType + " specified." );
}
case TYPE_NUMBER:
switch ( storageType ) {
case STORAGE_TYPE_NORMAL:
return convertNumberToTimestamp( (Double) object );
case STORAGE_TYPE_BINARY_STRING:
return convertNumberToTimestamp( (Double) convertBinaryStringToNativeType( (byte[]) object ) );
case STORAGE_TYPE_INDEXED:
return convertNumberToTimestamp( (Double) index[( (Integer) object ).intValue()] );
default:
throw new KettleValueException( toString() + " : Unknown storage type " + storageType + " specified." );
}
case TYPE_INTEGER:
switch ( storageType ) {
case STORAGE_TYPE_NORMAL:
return convertIntegerToTimestamp( (Long) object );
case STORAGE_TYPE_BINARY_STRING:
return convertIntegerToTimestamp( (Long) convertBinaryStringToNativeType( (byte[]) object ) );
case STORAGE_TYPE_INDEXED:
return convertIntegerToTimestamp( (Long) index[( (Integer) object ).intValue()] );
default:
throw new KettleValueException( toString() + " : Unknown storage type " + storageType + " specified." );
}
case TYPE_BIGNUMBER:
switch ( storageType ) {
case STORAGE_TYPE_NORMAL:
return convertBigNumberToTimestamp( (BigDecimal) object );
case STORAGE_TYPE_BINARY_STRING:
return convertBigNumberToTimestamp( (BigDecimal) convertBinaryStringToNativeType( (byte[]) object ) );
case STORAGE_TYPE_INDEXED:
return convertBigNumberToTimestamp( (BigDecimal) index[( (Integer) object ).intValue()] );
default:
throw new KettleValueException( toString() + " : Unknown storage type " + storageType + " specified." );
}
case TYPE_BOOLEAN:
throw new KettleValueException( toString() + " : I don't know how to convert a boolean to a timestamp." );
case TYPE_BINARY:
throw new KettleValueException( toString() + " : I don't know how to convert a binary value to timestamp." );
case TYPE_SERIALIZABLE:
throw new KettleValueException( toString()
+ " : I don't know how to convert a serializable value to timestamp." );
default:
throw new KettleValueException( toString() + " : Unknown type " + type + " specified." );
}
}
public int compare( Object data1, Object data2 ) throws KettleValueException {
Timestamp timestamp1 = getTimestamp( data1 );
Timestamp timestamp2 = getTimestamp( data2 );
int cmp = 0;
if ( timestamp1 == null ) {
if ( timestamp2 == null ) {
cmp = 0;
} else {
cmp = -1;
}
} else if ( timestamp2 == null ) {
cmp = 1;
} else {
cmp = timestamp1.compareTo( timestamp2 );
}
if ( isSortedDescending() ) {
return -cmp;
} else {
return cmp;
}
}
protected Timestamp convertBigNumberToTimestamp( BigDecimal bd ) {
if ( bd == null ) {
return null;
}
return convertIntegerToTimestamp( bd.longValue() );
}
protected Timestamp convertNumberToTimestamp( Double d ) {
if ( d == null ) {
return null;
}
long nanos = d.longValue();
return convertIntegerToTimestamp( nanos );
}
protected Timestamp convertIntegerToTimestamp( Long nanos ) {
if ( nanos == null ) {
return null;
}
long msSinceEpoch = nanos / 1000000;
int leftNanos = (int) ( nanos - ( msSinceEpoch * 1000000 ) );
Timestamp timestamp = new Timestamp( msSinceEpoch );
timestamp.setNanos( leftNanos );
return timestamp;
}
protected synchronized Timestamp convertStringToTimestamp( String string ) throws KettleValueException {
// See if trimming needs to be performed before conversion
//
string = Const.trimToType( string, getTrimType() );
if ( Utils.isEmpty( string ) ) {
return null;
}
Timestamp returnValue;
try {
returnValue = Timestamp.valueOf( string );
} catch ( IllegalArgumentException e ) {
try {
returnValue = (Timestamp) getDateFormat().parse( string );
} catch ( ParseException ex ) {
throw new KettleValueException( toString() + " : couldn't convert string [" + string
+ "] to a timestamp, expecting format [yyyy-mm-dd hh:mm:ss.ffffff]", e );
}
}
return returnValue;
}
protected synchronized String convertTimestampToString( Timestamp timestamp ) throws KettleValueException {
if ( timestamp == null ) {
return null;
}
return getDateFormat().format( timestamp );
}
@Override
public Object convertDataFromString( String pol, ValueMetaInterface convertMeta, String nullIf, String ifNull,
int trim_type ) throws KettleValueException {
// null handling and conversion of value to null
//
String null_value = nullIf;
if ( null_value == null ) {
switch ( convertMeta.getType() ) {
case ValueMetaInterface.TYPE_BOOLEAN:
null_value = Const.NULL_BOOLEAN;
break;
case ValueMetaInterface.TYPE_STRING:
null_value = Const.NULL_STRING;
break;
case ValueMetaInterface.TYPE_BIGNUMBER:
null_value = Const.NULL_BIGNUMBER;
break;
case ValueMetaInterface.TYPE_NUMBER:
null_value = Const.NULL_NUMBER;
break;
case ValueMetaInterface.TYPE_INTEGER:
null_value = Const.NULL_INTEGER;
break;
case ValueMetaInterface.TYPE_DATE:
null_value = Const.NULL_DATE;
break;
case ValueMetaInterface.TYPE_BINARY:
null_value = Const.NULL_BINARY;
break;
default:
null_value = Const.NULL_NONE;
break;
}
}
// See if we need to convert a null value into a String
// For example, we might want to convert null into "Empty".
//
if ( !Utils.isEmpty( ifNull ) ) {
// Note that you can't pull the pad method up here as a nullComp variable
// because you could get an NPE since you haven't checked isEmpty(pol)
// yet!
if ( Utils.isEmpty( pol )
|| pol.equalsIgnoreCase( Const.rightPad( new StringBuilder( null_value ), pol.length() ) ) ) {
pol = ifNull;
}
}
// See if the polled value is empty
// In that case, we have a null value on our hands...
//
if ( Utils.isEmpty( pol ) ) {
return null;
} else {
// if the null_value is specified, we try to match with that.
//
if ( !Utils.isEmpty( null_value ) ) {
if ( null_value.length() <= pol.length() ) {
// If the polled value is equal to the spaces right-padded null_value,
// we have a match
//
if ( pol.equalsIgnoreCase( Const.rightPad( new StringBuilder( null_value ), pol.length() ) ) ) {
return null;
}
}
} else {
// Verify if there are only spaces in the polled value...
// We consider that empty as well...
//
if ( Const.onlySpaces( pol ) ) {
return null;
}
}
}
// Trimming
StringBuilder strpol;
switch ( trim_type ) {
case ValueMetaInterface.TRIM_TYPE_LEFT:
strpol = new StringBuilder( pol );
while ( strpol.length() > 0 && strpol.charAt( 0 ) == ' ' ) {
strpol.deleteCharAt( 0 );
}
pol = strpol.toString();
break;
case ValueMetaInterface.TRIM_TYPE_RIGHT:
strpol = new StringBuilder( pol );
while ( strpol.length() > 0 && strpol.charAt( strpol.length() - 1 ) == ' ' ) {
strpol.deleteCharAt( strpol.length() - 1 );
}
pol = strpol.toString();
break;
case ValueMetaInterface.TRIM_TYPE_BOTH:
strpol = new StringBuilder( pol );
while ( strpol.length() > 0 && strpol.charAt( 0 ) == ' ' ) {
strpol.deleteCharAt( 0 );
}
while ( strpol.length() > 0 && strpol.charAt( strpol.length() - 1 ) == ' ' ) {
strpol.deleteCharAt( strpol.length() - 1 );
}
pol = strpol.toString();
break;
default:
break;
}
// On with the regular program...
// Simply call the ValueMeta routines to do the conversion
// We need to do some effort here: copy all
//
return convertData( convertMeta, pol );
}
public Timestamp convertDateToTimestamp( Date date ) throws KettleValueException {
if ( date == null ) {
return null;
}
Timestamp result = null;
if ( date instanceof Timestamp ) {
result = (Timestamp) date;
} else {
result = new Timestamp( date.getTime() );
}
return result;
}
/**
* Convert the specified data to the data type specified in this object.
*
* @param meta2
* the metadata of the object to be converted
* @param data2
* the data of the object to be converted
* @return the object in the data type of this value metadata object
* @throws KettleValueException
* in case there is a data conversion error
*/
@Override
public Object convertData( ValueMetaInterface meta2, Object data2 ) throws KettleValueException {
switch ( meta2.getType() ) {
case TYPE_TIMESTAMP:
return ( (ValueMetaTimestamp) meta2 ).getTimestamp( data2 );
case TYPE_STRING:
return convertStringToTimestamp( meta2.getString( data2 ) );
case TYPE_INTEGER:
return convertIntegerToTimestamp( meta2.getInteger( data2 ) );
case TYPE_NUMBER:
return convertNumberToTimestamp( meta2.getNumber( data2 ) );
case TYPE_DATE:
return convertDateToTimestamp( meta2.getDate( data2 ) );
case TYPE_BIGNUMBER:
return convertBigNumberToTimestamp( meta2.getBigNumber( data2 ) );
default:
throw new KettleValueException( meta2.toStringMeta() + " : can't be converted to a timestamp" );
}
}
@Override
public Object cloneValueData( Object object ) throws KettleValueException {
Timestamp timestamp = getTimestamp( object );
if ( timestamp == null ) {
return null;
}
Timestamp clone = new Timestamp( timestamp.getTime() );
clone.setNanos( timestamp.getNanos() );
return clone;
}
@Override
public ValueMetaInterface getValueFromSQLType( DatabaseMeta databaseMeta, String name, ResultSetMetaData rm,
int index, boolean ignoreLength, boolean lazyConversion ) throws KettleDatabaseException {
try {
int type = rm.getColumnType( index );
if ( type == java.sql.Types.TIMESTAMP ) {
int length = rm.getScale( index );
ValueMetaInterface valueMeta;
if ( databaseMeta.supportsTimestampDataType() ) {
valueMeta = new ValueMetaTimestamp( name );
} else {
valueMeta = new ValueMetaDate( name );
}
valueMeta.setLength( length );
// Also get original column details, comment, etc.
//
getOriginalColumnMetadata( valueMeta, rm, index, ignoreLength );
return valueMeta;
}
return null;
} catch ( Exception e ) {
throw new KettleDatabaseException( "Error evaluating timestamp value metadata", e );
}
}
@Override
public Object getValueFromResultSet( DatabaseInterface databaseInterface, ResultSet resultSet, int index )
throws KettleDatabaseException {
try {
return resultSet.getTimestamp( index + 1 );
} catch ( Exception e ) {
throw new KettleDatabaseException(
toStringMeta() + " : Unable to get timestamp from resultset at index " + index, e );
}
}
@Override
public void setPreparedStatementValue( DatabaseMeta databaseMeta, PreparedStatement preparedStatement, int index,
Object data ) throws KettleDatabaseException {
try {
if ( data != null ) {
preparedStatement.setTimestamp( index, getTimestamp( data ) );
} else {
preparedStatement.setNull( index, java.sql.Types.TIMESTAMP );
}
} catch ( Exception e ) {
throw new KettleDatabaseException( toStringMeta() + " : Unable to set value on prepared statement on index "
+ index, e );
}
}
@Override
public Object convertDataUsingConversionMetaData( Object data2 ) throws KettleValueException {
if ( conversionMetadata == null ) {
throw new KettleValueException(
"API coding error: please specify the conversion metadata before attempting to convert value " + name );
}
return super.convertDataUsingConversionMetaData( data2 );
}
@Override
public byte[] getBinaryString( Object object ) throws KettleValueException {
if ( object == null ) {
return null;
}
if ( isStorageBinaryString() && identicalFormat ) {
return (byte[]) object; // shortcut it directly for better performance.
}
switch ( storageType ) {
case STORAGE_TYPE_NORMAL:
return convertStringToBinaryString( getString( object ) );
case STORAGE_TYPE_BINARY_STRING:
return convertStringToBinaryString( (String) convertBinaryStringToNativeType( (byte[]) object ) );
case STORAGE_TYPE_INDEXED:
return convertStringToBinaryString( getString( index[( (Integer) object ).intValue()] ) );
default:
throw new KettleValueException( toString() + " : Unknown storage type " + storageType + " specified." );
}
}
@Override
public void writeData( DataOutputStream outputStream, Object object ) throws KettleFileException {
try {
// Is the value NULL?
outputStream.writeBoolean( object == null );
if ( object != null ) {
switch ( storageType ) {
case STORAGE_TYPE_NORMAL:
// Handle Content -- only when not NULL
Timestamp timestamp = convertDateToTimestamp( (Date) object );
outputStream.writeLong( timestamp.getTime() );
outputStream.writeInt( timestamp.getNanos() );
break;
case STORAGE_TYPE_BINARY_STRING:
// Handle binary string content -- only when not NULL
// In this case, we opt not to convert anything at all for speed.
// That way, we can save on CPU power.
// Since the streams can be compressed, volume shouldn't be an issue
// at all.
//
writeBinaryString( outputStream, (byte[]) object );
break;
case STORAGE_TYPE_INDEXED:
writeInteger( outputStream, (Integer) object ); // just an index
break;
default:
throw new KettleFileException( toString() + " : Unknown storage type " + getStorageType() );
}
}
} catch ( ClassCastException e ) {
throw new RuntimeException( toString() + " : There was a data type error: the data type of "
+ object.getClass().getName() + " object [" + object + "] does not correspond to value meta ["
+ toStringMeta() + "]" );
} catch ( IOException e ) {
throw new KettleFileException( toString() + " : Unable to write value timestamp data to output stream", e );
} catch ( KettleValueException e ) {
throw new RuntimeException( toString() + " : There was a data type error: the data type of "
+ object.getClass().getName() + " object [" + object + "] does not correspond to value meta ["
+ toStringMeta() + "]" );
}
}
@Override
public Object readData( DataInputStream inputStream ) throws KettleFileException, KettleEOFException,
SocketTimeoutException {
try {
// Is the value NULL?
if ( inputStream.readBoolean() ) {
return null; // done
}
switch ( storageType ) {
case STORAGE_TYPE_NORMAL:
// Handle Content -- only when not NULL
long time = inputStream.readLong();
int nanos = inputStream.readInt();
Timestamp timestamp = new Timestamp( time );
timestamp.setNanos( nanos );
return timestamp;
case STORAGE_TYPE_BINARY_STRING:
return readBinaryString( inputStream );
case STORAGE_TYPE_INDEXED:
return readSmallInteger( inputStream ); // just an index: 4-bytes should be enough.
default:
throw new KettleFileException( toString() + " : Unknown storage type " + getStorageType() );
}
} catch ( EOFException e ) {
throw new KettleEOFException( e );
} catch ( SocketTimeoutException e ) {
throw e;
} catch ( IOException e ) {
throw new KettleFileException( toString() + " : Unable to read value timestamp data from input stream", e );
}
}
@Override
public synchronized SimpleDateFormat getDateFormat() {
return getDateFormat( getType() );
}
private synchronized SimpleDateFormat getDateFormat( int valueMetaType ) {
if ( conversionMetadata != null ) {
return new SimpleTimestampFormat( conversionMetadata.getDateFormat().toPattern() );
}
if ( dateFormat == null || dateFormatChanged ) {
// This may not become static as the class is not thread-safe!
dateFormat = new SimpleTimestampFormat( new SimpleDateFormat().toPattern() );
String mask = getMask( valueMetaType );
// Do we have a locale?
//
if ( dateFormatLocale == null || dateFormatLocale.equals( Locale.getDefault() ) ) {
dateFormat = new SimpleTimestampFormat( mask );
} else {
dateFormat = new SimpleTimestampFormat( mask, dateFormatLocale );
}
// Do we have a time zone?
//
if ( dateFormatTimeZone != null ) {
dateFormat.setTimeZone( dateFormatTimeZone );
}
// Set the conversion leniency as well
//
dateFormat.setLenient( dateFormatLenient );
dateFormatChanged = false;
}
return dateFormat;
}
@Override
public String getFormatMask() {
return getTimestampFormatMask();
}
@Override
public Class<?> getNativeDataTypeClass() throws KettleValueException {
return Timestamp.class;
}
}