/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 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.trans.steps.propertyoutput; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Map; import org.apache.commons.vfs2.FileObject; import org.pentaho.di.core.CheckResult; import org.pentaho.di.core.CheckResultInterface; import org.pentaho.di.core.Const; import org.pentaho.di.core.util.Utils; import org.pentaho.di.core.database.DatabaseMeta; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.exception.KettleXMLException; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.row.ValueMetaInterface; import org.pentaho.di.core.variables.VariableSpace; import org.pentaho.di.core.vfs.KettleVFS; import org.pentaho.di.core.xml.XMLHandler; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.repository.ObjectId; import org.pentaho.di.repository.Repository; import org.pentaho.di.resource.ResourceDefinition; import org.pentaho.di.resource.ResourceNamingInterface; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.BaseStepMeta; 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 org.pentaho.metastore.api.IMetaStore; import org.w3c.dom.Node; /** * Output rows to Properties file and create a file. * * @author Samatar * @since 13-Apr-2008 */ public class PropertyOutputMeta extends BaseStepMeta implements StepMetaInterface { private static Class<?> PKG = PropertyOutputMeta.class; // for i18n purposes, needed by Translator2!! private String keyfield; private String valuefield; private boolean addToResult; /** The base name of the output file */ private String fileName; /* Specification if file name is in field */ private boolean fileNameInField; private String fileNameField; /** The file extention in case of a generated filename */ private String extension; /** Flag: add the stepnr in the filename */ private boolean stepNrInFilename; /** Flag: add the partition number in the filename */ private boolean partNrInFilename; /** Flag: add the date in the filename */ private boolean dateInFilename; /** Flag: add the time in the filename */ private boolean timeInFilename; /** Flag: create parent folder if needed */ private boolean createparentfolder; /** Comment to add in file */ private String comment; /** Flag append in file **/ private boolean append; @Override public void loadXML( Node stepnode, List<DatabaseMeta> databases, IMetaStore metaStore ) throws KettleXMLException { readData( stepnode ); } @Override public Object clone() { PropertyOutputMeta retval = (PropertyOutputMeta) super.clone(); return retval; } /** * @return Returns the extension. */ public String getExtension() { return extension; } /** * @param extension * The extension to set. */ public void setExtension( String extension ) { this.extension = extension; } /** * @return Returns the fileName. */ public String getFileName() { return fileName; } /** * @return Is the file name coded in a field? */ public boolean isFileNameInField() { return fileNameInField; } /** * @param fileNameInField * Is the file name coded in a field? */ public void setFileNameInField( boolean fileNameInField ) { this.fileNameInField = fileNameInField; } /** * @return The field name that contains the output file name. */ public String getFileNameField() { return fileNameField; } /** * @param fileNameField * Name of the field that contains the file name */ public void setFileNameField( String fileNameField ) { this.fileNameField = fileNameField; } /** * @return Returns the stepNrInFilename. */ public boolean isStepNrInFilename() { return stepNrInFilename; } /** * @param stepNrInFilename * The stepNrInFilename to set. */ public void setStepNrInFilename( boolean stepNrInFilename ) { this.stepNrInFilename = stepNrInFilename; } /** * @return Returns the timeInFilename. */ public boolean isTimeInFilename() { return timeInFilename; } /** * @return Returns the dateInFilename. */ public boolean isDateInFilename() { return dateInFilename; } /** * @param dateInFilename * The dateInFilename to set. */ public void setDateInFilename( boolean dateInFilename ) { this.dateInFilename = dateInFilename; } /** * @param timeInFilename * The timeInFilename to set. */ public void setTimeInFilename( boolean timeInFilename ) { this.timeInFilename = timeInFilename; } /** * @param fileName * The fileName to set. */ public void setFileName( String fileName ) { this.fileName = fileName; } /** * @deprecated use {@link #isAddToResult()} * @return Returns the Add to result filesname flag. */ @Deprecated public boolean addToResult() { return isAddToResult(); } public boolean isAddToResult() { return addToResult; } /** * @param addToResult * The Add file to result to set. */ public void setAddToResult( boolean addToResult ) { this.addToResult = addToResult; } /** * @return Returns the create parent folder flag. */ public boolean isCreateParentFolder() { return createparentfolder; } /** * @param createparentfolder * The create parent folder flag to set. */ public void setCreateParentFolder( boolean createparentfolder ) { this.createparentfolder = createparentfolder; } /** * @return Returns the append flag. */ public boolean isAppend() { return append; } /** * @param append * The append to set. */ public void setAppend( boolean append ) { this.append = append; } public String getComment() { return comment; } public void setComment( String commentin ) { this.comment = commentin; } public String[] getFiles( VariableSpace space ) { int copies = 1; int parts = 1; if ( stepNrInFilename ) { copies = 3; } if ( partNrInFilename ) { parts = 3; } int nr = copies * parts; if ( nr > 1 ) { nr++; } String[] retval = new String[nr]; int i = 0; for ( int copy = 0; copy < copies; copy++ ) { for ( int part = 0; part < parts; part++ ) { retval[i] = buildFilename( space, copy ); i++; } } if ( i < nr ) { retval[i] = "..."; } return retval; } public String buildFilename( VariableSpace space, int stepnr ) { SimpleDateFormat daf = new SimpleDateFormat(); // Replace possible environment variables... String retval = space.environmentSubstitute( fileName ); Date now = new Date(); if ( dateInFilename ) { daf.applyPattern( "yyyMMdd" ); String d = daf.format( now ); retval += "_" + d; } if ( timeInFilename ) { daf.applyPattern( "HHmmss" ); String t = daf.format( now ); retval += "_" + t; } if ( stepNrInFilename ) { retval += "_" + stepnr; } if ( extension != null && extension.length() != 0 ) { retval += "." + extension; } return retval; } private void readData( Node stepnode ) throws KettleXMLException { try { keyfield = XMLHandler.getTagValue( stepnode, "keyfield" ); valuefield = XMLHandler.getTagValue( stepnode, "valuefield" ); comment = XMLHandler.getTagValue( stepnode, "comment" ); fileName = XMLHandler.getTagValue( stepnode, "file", "name" ); createparentfolder = "Y".equalsIgnoreCase( XMLHandler.getTagValue( stepnode, "file", "create_parent_folder" ) ); extension = XMLHandler.getTagValue( stepnode, "file", "extention" ); stepNrInFilename = "Y".equalsIgnoreCase( XMLHandler.getTagValue( stepnode, "file", "split" ) ); partNrInFilename = "Y".equalsIgnoreCase( XMLHandler.getTagValue( stepnode, "file", "haspartno" ) ); dateInFilename = "Y".equalsIgnoreCase( XMLHandler.getTagValue( stepnode, "file", "add_date" ) ); timeInFilename = "Y".equalsIgnoreCase( XMLHandler.getTagValue( stepnode, "file", "add_time" ) ); addToResult = "Y".equalsIgnoreCase( XMLHandler.getTagValue( stepnode, "file", "AddToResult" ) ); append = "Y".equalsIgnoreCase( XMLHandler.getTagValue( stepnode, "file", "append" ) ); fileName = XMLHandler.getTagValue( stepnode, "file", "name" ); fileNameInField = "Y".equalsIgnoreCase( XMLHandler.getTagValue( stepnode, "fileNameInField" ) ); fileNameField = XMLHandler.getTagValue( stepnode, "fileNameField" ); } catch ( Exception e ) { throw new KettleXMLException( "Unable to load step info from XML", e ); } } @Override public void setDefault() { append = false; createparentfolder = false; // Items ... keyfield = null; valuefield = null; comment = null; } @Override public String getXML() { StringBuilder retval = new StringBuilder(); // Items ... retval.append( " " + XMLHandler.addTagValue( "keyfield", keyfield ) ); retval.append( " " + XMLHandler.addTagValue( "valuefield", valuefield ) ); retval.append( " " + XMLHandler.addTagValue( "comment", comment ) ); retval.append( " " + XMLHandler.addTagValue( "fileNameInField", fileNameInField ) ); retval.append( " " + XMLHandler.addTagValue( "fileNameField", fileNameField ) ); retval.append( " <file>" + Const.CR ); retval.append( " " + XMLHandler.addTagValue( "name", fileName ) ); retval.append( " " + XMLHandler.addTagValue( "extention", extension ) ); retval.append( " " + XMLHandler.addTagValue( "split", stepNrInFilename ) ); retval.append( " " + XMLHandler.addTagValue( "haspartno", partNrInFilename ) ); retval.append( " " + XMLHandler.addTagValue( "add_date", dateInFilename ) ); retval.append( " " + XMLHandler.addTagValue( "add_time", timeInFilename ) ); retval.append( " " + XMLHandler.addTagValue( "create_parent_folder", createparentfolder ) ); retval.append( " " + XMLHandler.addTagValue( "addtoresult", addToResult ) ); retval.append( " " + XMLHandler.addTagValue( "append", append ) ); retval.append( " </file>" + Const.CR ); return retval.toString(); } @Override public void readRep( Repository rep, IMetaStore metaStore, ObjectId id_step, List<DatabaseMeta> databases ) throws KettleException { try { keyfield = rep.getStepAttributeString( id_step, "keyfield" ); valuefield = rep.getStepAttributeString( id_step, "valuefield" ); comment = rep.getStepAttributeString( id_step, "comment" ); fileName = rep.getStepAttributeString( id_step, "file_name" ); extension = rep.getStepAttributeString( id_step, "file_extention" ); stepNrInFilename = rep.getStepAttributeBoolean( id_step, "file_add_stepnr" ); partNrInFilename = rep.getStepAttributeBoolean( id_step, "file_add_partnr" ); dateInFilename = rep.getStepAttributeBoolean( id_step, "file_add_date" ); timeInFilename = rep.getStepAttributeBoolean( id_step, "file_add_time" ); createparentfolder = rep.getStepAttributeBoolean( id_step, "create_parent_folder" ); addToResult = rep.getStepAttributeBoolean( id_step, "addtoresult" ); append = rep.getStepAttributeBoolean( id_step, "append" ); fileNameInField = rep.getStepAttributeBoolean( id_step, "fileNameInField" ); fileNameField = rep.getStepAttributeString( id_step, "fileNameField" ); } catch ( Exception e ) { throw new KettleException( "Unexpected error reading step information from the repository", e ); } } @Override public void saveRep( Repository rep, IMetaStore metaStore, ObjectId id_transformation, ObjectId id_step ) throws KettleException { try { rep.saveStepAttribute( id_transformation, id_step, "keyfield", keyfield ); rep.saveStepAttribute( id_transformation, id_step, "valuefield", valuefield ); rep.saveStepAttribute( id_transformation, id_step, "comment", comment ); rep.saveStepAttribute( id_transformation, id_step, "file_name", fileName ); rep.saveStepAttribute( id_transformation, id_step, "file_extention", extension ); rep.saveStepAttribute( id_transformation, id_step, "file_add_stepnr", stepNrInFilename ); rep.saveStepAttribute( id_transformation, id_step, "file_add_partnr", partNrInFilename ); rep.saveStepAttribute( id_transformation, id_step, "file_add_date", dateInFilename ); rep.saveStepAttribute( id_transformation, id_step, "file_add_time", timeInFilename ); rep.saveStepAttribute( id_transformation, id_step, "create_parent_folder", createparentfolder ); rep.saveStepAttribute( id_transformation, id_step, "addtoresult", addToResult ); rep.saveStepAttribute( id_transformation, id_step, "append", append ); rep.saveStepAttribute( id_transformation, id_step, "fileNameInField", fileNameInField ); rep.saveStepAttribute( id_transformation, id_step, "fileNameField", fileNameField ); } catch ( Exception e ) { throw new KettleException( "Unable to save step information to the repository for id_step=" + id_step, e ); } } @Override public void check( List<CheckResultInterface> remarks, TransMeta transMeta, StepMeta stepMeta, RowMetaInterface prev, String[] input, String[] output, RowMetaInterface info, VariableSpace space, Repository repository, IMetaStore metaStore ) { CheckResult cr; // Now see what we can find as previous step... if ( prev != null && prev.size() > 0 ) { cr = new CheckResult( CheckResult.TYPE_RESULT_OK, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.FieldsReceived", "" + prev.size() ), stepMeta ); remarks.add( cr ); } else { cr = new CheckResult( CheckResult.TYPE_RESULT_ERROR, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.NoFields" ), stepMeta ); remarks.add( cr ); } // See if we have input streams leading to this step! if ( input.length > 0 ) { cr = new CheckResult( CheckResult.TYPE_RESULT_OK, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.ExpectedInputOk" ), stepMeta ); remarks.add( cr ); } else { cr = new CheckResult( CheckResult.TYPE_RESULT_ERROR, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.ExpectedInputError" ), stepMeta ); remarks.add( cr ); } // Check if filename is given if ( !Utils.isEmpty( fileName ) ) { cr = new CheckResult( CheckResult.TYPE_RESULT_OK, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.FilenameOk" ), stepMeta ); remarks.add( cr ); } else { cr = new CheckResult( CheckResult.TYPE_RESULT_ERROR, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.FilenameError" ), stepMeta ); remarks.add( cr ); } // Check for Key field ValueMetaInterface v = prev.searchValueMeta( keyfield ); if ( v == null ) { cr = new CheckResult( CheckResultInterface.TYPE_RESULT_ERROR, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.KeyFieldMissing" ), stepMeta ); remarks.add( cr ); } else { cr = new CheckResult( CheckResultInterface.TYPE_RESULT_OK, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.KeyFieldOk" ), stepMeta ); remarks.add( cr ); } // Check for Value field v = prev.searchValueMeta( valuefield ); if ( v == null ) { cr = new CheckResult( CheckResultInterface.TYPE_RESULT_ERROR, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.ValueFieldMissing" ), stepMeta ); remarks.add( cr ); } else { cr = new CheckResult( CheckResultInterface.TYPE_RESULT_OK, BaseMessages.getString( PKG, "PropertyOutputMeta.CheckResult.ValueFieldOk" ), stepMeta ); remarks.add( cr ); } } @Override public StepDataInterface getStepData() { return new PropertyOutputData(); } /** * @return the keyfield */ public String getKeyField() { return keyfield; } /** * @return the valuefield */ public String getValueField() { return valuefield; } /** * @param KeyField * the keyfield to set */ public void setKeyField( String KeyField ) { this.keyfield = KeyField; } /** * @param valuefield * the valuefield to set */ public void setValueField( String valuefield ) { this.valuefield = valuefield; } @Override public StepInterface getStep( StepMeta stepMeta, StepDataInterface stepDataInterface, int cnr, TransMeta tr, Trans trans ) { return new PropertyOutput( stepMeta, stepDataInterface, cnr, tr, trans ); } @Override public boolean supportsErrorHandling() { return true; } /** * Since the exported transformation that runs this will reside in a ZIP file, we can't reference files relatively. So * what this does is turn the name of files into absolute paths OR it simply includes the resource in the ZIP file. * For now, we'll simply turn it into an absolute path and pray that the file is on a shared drive or something like * that. * * @param space * the variable space to use * @param definitions * @param resourceNamingInterface * @param repository * The repository to optionally load other resources from (to be converted to XML) * @param metaStore * the metaStore in which non-kettle metadata could reside. * * @return the filename of the exported resource */ @Override public String exportResources( VariableSpace space, Map<String, ResourceDefinition> definitions, ResourceNamingInterface resourceNamingInterface, Repository repository, IMetaStore metaStore ) throws KettleException { try { // The object that we're modifying here is a copy of the original! // So let's change the filename from relative to absolute by grabbing the file object... // // From : ${Internal.Transformation.Filename.Directory}/../foo/bar.data // To : /home/matt/test/files/foo/bar.data // // In case the name of the file comes from previous steps, forget about this! if ( !fileNameInField ) { FileObject fileObject = KettleVFS.getFileObject( space.environmentSubstitute( fileName ), space ); // If the file doesn't exist, forget about this effort too! // if ( fileObject.exists() ) { // Convert to an absolute path... // fileName = resourceNamingInterface.nameResource( fileObject, space, true ); return fileName; } } return null; } catch ( Exception e ) { throw new KettleException( e ); } } }