/*
* This software 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 3 of the License, or
* (at your option) any later version.
*
* This software 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 software. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2011 De Bortoli Wines Pty Limited (Australia)
*/
package org.pentaho.di.trans.steps.openerp.objectoutput;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.xmlrpc.XmlRpcException;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.openerp.core.OpenERPHelper;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStep;
import org.pentaho.di.trans.step.StepDataInterface;
import org.pentaho.di.trans.step.StepInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.StepMetaInterface;
import com.debortoliwines.openerp.api.FieldCollection;
import com.debortoliwines.openerp.api.FilterCollection;
import com.debortoliwines.openerp.api.ObjectAdapter;
import com.debortoliwines.openerp.api.OpeneERPApiException;
import com.debortoliwines.openerp.api.Row;
import com.debortoliwines.openerp.api.RowCollection;
public class OpenERPObjectOutput extends BaseStep implements StepInterface {
private OpenERPObjectOutputMeta meta;
private OpenERPObjectOutputData data;
ObjectAdapter openerERPAdapter;
private int idIndex = -1;
private int[] index;
private FilterCollection readSourceFilter;
private ArrayList<String> readFieldList = new ArrayList<String>();
private int[] readRowIndex = new int[0];
private HashMap<String, Object> filterRowCache = new HashMap<String, Object>();
private final String SEPARATOR = "A|a";
private FieldCollection rowFields;
public OpenERPObjectOutput( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta,
Trans trans ) {
super( stepMeta, stepDataInterface, copyNr, transMeta, trans );
}
public boolean processRow( final StepMetaInterface smi, final StepDataInterface sdi ) throws KettleException {
Object[] inputRow = getRow(); // this also waits for a previous step to be finished.
if ( inputRow == null ) {
try {
// Commit the last batch
CommitBatch();
} catch ( Exception e ) {
throw new KettleException( "Failed to commit batch: ", e );
}
// no more input to be expected...
this.logDebug( "No More Rows." );
setOutputDone();
return false;
}
if ( first ) {
try {
// Prepare output meta
data.outputRowMeta = getInputRowMeta().clone();
meta.getFields( data.outputRowMeta, getStepname(), null, null, this, repository, metaStore );
prepareFieldList();
/* If the ID isn't used as the filter, prepare the filter */
if ( idIndex == -1 && meta.getKeyLookups().size() > 0 ) {
prepareReadParameters();
prepareCache();
}
rowFields = openerERPAdapter.getFields( meta.getModelFields() );
} catch ( Exception e ) {
throw new KettleException( "Failed to initialize step ", e );
}
first = false;
data.updateBatchRows.clear();
}
// Prepare output row
Object[] outputRow = new Object[data.outputRowMeta.size()];
for ( int i = 0; i < getInputRowMeta().size(); i++ ) {
outputRow[i] = inputRow[i]; // Don't convert to normal storage. Send it through as is.
}
String row = "";
try {
Row updateRow = openerERPAdapter.getNewRow( rowFields );
// If ID field was mapped in the filter, use it. Otherwise try and find it from cache.
if ( idIndex >= 0 ) {
updateRow.put( "id", this.getInputValue( inputRow, idIndex ) );
} else {
String combinedKey = "";
for ( int i : readRowIndex ) {
combinedKey += SEPARATOR + ( inputRow[i] == null ? "" : this.getInputValue( inputRow, i ) );
}
if ( filterRowCache.containsKey( combinedKey ) ) {
updateRow.put( "id", filterRowCache.get( combinedKey ) );
} else {
updateRow.put( "id", 0 );
}
}
for ( int i = 0; i < meta.getModelFields().length; i++ ) {
updateRow.put( meta.getModelFields()[i], this.getInputValue( inputRow, this.index[i] ) );
}
// If the import function does not return the ID field once complete then
// we have to call the create function for each row, to return the ID
if ( data.helper.getImportReturnIDS() == false && meta.getOutputIDField() && updateRow.getID() == 0 ) {
openerERPAdapter.createObject( updateRow );
outputRow[data.outputRowMeta.indexOfValue( meta.getOutputIDFieldName() )] =
Long.parseLong( updateRow.get( "id" ).toString() );
incrementLinesOutput();
putRow( data.outputRowMeta, outputRow );
} else {
// Get the ID of an existing row
if ( data.helper.getImportReturnIDS() == false && meta.getOutputIDField() ) {
outputRow[data.outputRowMeta.indexOfValue( meta.getOutputIDFieldName() )] =
Long.parseLong( updateRow.get( "id" ).toString() );
}
data.updateBatchRows.add( updateRow );
data.outputBatchRows.add( outputRow );
if ( data.updateBatchRows.size() == meta.getCommitBatchSize() ) {
CommitBatch();
}
}
} catch ( Exception e ) {
throw new KettleException( "Failed to commit batch: " + row, e );
}
return true;
}
// Returns the normal storage (Native java) representation of the object (vs binary string etc).
private Object getInputValue( Object[] inputRow, int index ) throws KettleValueException {
return this.getInputRowMeta().getValueMeta( index ).convertToNormalStorageType( inputRow[index] );
}
private void CommitBatch() throws Exception {
// In the process of stopping, return
if ( isStopped() || data.updateBatchRows.size() == 0 ) {
return;
}
try {
openerERPAdapter.importData( data.updateBatchRows );
int idFieldIndex = -1;
if ( meta.getOutputIDField() ) {
idFieldIndex = data.outputRowMeta.indexOfValue( meta.getOutputIDFieldName() );
}
// The input and output rows are in the same order
for ( int i = 0; i < data.updateBatchRows.size(); i++ ) {
incrementLinesOutput();
Object[] outputrow = data.outputBatchRows.get( i );
if ( data.helper.getImportReturnIDS() && idFieldIndex >= 0 ) {
outputrow[idFieldIndex] = Long.parseLong( data.updateBatchRows.get( i ).get( "id" ).toString() );
}
putRow( data.outputRowMeta, outputrow );
}
} finally {
data.updateBatchRows.clear();
data.outputBatchRows.clear();
}
}
private void prepareCache() throws XmlRpcException, OpeneERPApiException {
filterRowCache.clear();
RowCollection rows =
data.helper.getModelData( meta.getModelName(), readSourceFilter, readFieldList
.toArray( new String[readFieldList.size()] ) );
for ( Row row : rows ) {
String combinedKey = "";
for ( String fieldname : readFieldList ) {
Object value = row.get( fieldname );
// For id fields
if ( value instanceof Object[] ) {
value = ( (Object[]) value )[0];
}
combinedKey += SEPARATOR + ( value == null ? "" : value );
}
filterRowCache.put( combinedKey, row.get( "id" ) );
}
}
private void prepareReadParameters() throws Exception {
ArrayList<Integer> readIdx = new ArrayList<Integer>();
// Building search filter with constant values
readSourceFilter = new FilterCollection();
for ( int i = 0; i < meta.getKeyLookups().size(); i++ ) {
String modelField = meta.getKeyLookups().get( i )[0];
String comparison = meta.getKeyLookups().get( i )[1];
String streamField = meta.getKeyLookups().get( i )[2];
// Only pass through filters for constant values that aren't linked to stream fileds
if ( streamField != null && streamField.length() > 0 && getInputRowMeta().indexOfValue( streamField ) >= 0 ) {
readFieldList.add( modelField );
readIdx.add( getInputRowMeta().indexOfValue( streamField ) );
continue;
}
readSourceFilter.add( modelField, comparison, streamField );
this.logBasic( "Setting filter: [" + modelField + "," + comparison + "," + streamField + "]" );
}
readRowIndex = new int[readIdx.size()];
for ( int i = 0; i < readRowIndex.length; i++ ) {
readRowIndex[i] = readIdx.get( i );
}
}
private void prepareFieldList() throws Exception {
// If the ID field is the only filter, include it in the field
if ( meta.getKeyLookups().size() == 1 && meta.getKeyLookups().get( 0 )[0] != null
&& meta.getKeyLookups().get( 0 )[1] != null && meta.getKeyLookups().get( 0 )[2] != null
&& meta.getKeyLookups().get( 0 )[0].equals( "id" ) && meta.getKeyLookups().get( 0 )[1].equals( "=" ) ) {
idIndex = getInputRowMeta().indexOfValue( meta.getKeyLookups().get( 0 )[2] );
}
index = new int[meta.getModelFields().length];
for ( int i = 0; i < meta.getModelFields().length; i++ ) {
index[i] = getInputRowMeta().indexOfValue( meta.getStreamFields()[i] );
if ( index[i] < 0 ) {
throw new KettleException( "Stream field not found", new Exception( "Could not find stream field: "
+ meta.getStreamFields()[i] ) );
}
}
}
public boolean init( StepMetaInterface smi, StepDataInterface sdi ) {
meta = (OpenERPObjectOutputMeta) smi;
data = (OpenERPObjectOutputData) sdi;
if ( super.init( smi, sdi ) ) {
try {
this.logDebug( "Initializing OpenERP Session" );
data.helper = new OpenERPHelper( meta.getDatabaseMeta() );
data.helper.StartSession();
openerERPAdapter = data.helper.getAdapter( meta.getModelName() );
return true;
} catch ( Exception e ) {
logError( "An error occurred, processing will be stopped: " + e.getMessage() );
setErrors( 1 );
stopAll();
}
}
return false;
}
}