/*!
* Copyright 2010 - 2016 Pentaho Corporation. All rights reserved.
*
* 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.repository.pur;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.pentaho.di.base.AbstractMeta;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.ProgressMonitorListener;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.BaseLogTable;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.logging.LogTableInterface;
import org.pentaho.di.core.vfs.KettleVFS;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.imp.ImportRules;
import org.pentaho.di.imp.rule.ImportValidationFeedback;
import org.pentaho.di.job.JobMeta;
import org.pentaho.di.repository.IRepositoryExporter;
import org.pentaho.di.repository.RepositoryDirectoryInterface;
import org.pentaho.di.repository.RepositoryObjectType;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.RepositoryFileTree;
/**
* Optimized exporter designed to keep the server calls to a minimum.
*
* @author jganoff
*/
public class PurRepositoryExporter implements IRepositoryExporter, java.io.Serializable {
private static final long serialVersionUID = -8972308694755905930L; /* EESOURCE: UPDATE SERIALVERUID */
private static Class<?> PKG = PurRepositoryExporter.class;
/**
* Amount of repository files and content to load from the repository at once.
*/
private static final int DEFAULT_BATCH_SIZE = 50;
private static final String REPOSITORY_BATCH_SIZE_PROPERTY = "KettleRepositoryExportBatchSize"; //$NON-NLS-1$
private int batchSize;
private PurRepository repository;
private LogChannelInterface log;
private ImportRules importRules;
public PurRepositoryExporter( PurRepository repository ) {
this.repository = repository;
this.importRules = new ImportRules();
this.log = repository.getLog();
}
public synchronized void exportAllObjects( ProgressMonitorListener monitor, String xmlFilename,
RepositoryDirectoryInterface root, String exportType ) throws KettleException {
initBatchSize();
OutputStream os = null;
OutputStreamWriter writer = null;
try {
os = new BufferedOutputStream( KettleVFS.getOutputStream( xmlFilename, false ) );
writer = new OutputStreamWriter( os, Const.XML_ENCODING );
if ( monitor != null ) {
monitor.beginTask( "Exporting the repository to XML...", 3 ); //$NON-NLS-1$
}
root = root == null ? repository.findDirectory( "/" ) : root; //$NON-NLS-1$
String path = root.getPath();
RepositoryFileTree repoTree = repository.loadRepositoryFileTree( path );
writer.write( XMLHandler.getXMLHeader() );
writer.write( "<repository>" + Const.CR + Const.CR ); //$NON-NLS-1$
if ( monitor != null ) {
monitor.worked( 1 );
}
if ( exportType.equals( "all" ) || exportType.equals( "trans" ) ) { //$NON-NLS-1$ //$NON-NLS-2$
// Dump the transformations...
writer.write( "<transformations>" + Const.CR ); //$NON-NLS-1$
// exportAllTransformations(monitor, repoTree, repoDirTree, writer);
export( monitor, repoTree, writer, new TransformationBatchExporter() );
writer.write( "</transformations>" + Const.CR ); //$NON-NLS-1$
}
if ( exportType.equals( "all" ) || exportType.equals( "jobs" ) ) { //$NON-NLS-1$ //$NON-NLS-2$
// Now dump the jobs...
writer.write( "<jobs>" + Const.CR ); //$NON-NLS-1$
export( monitor, repoTree, writer, new JobBatchExporter() );
writer.write( "</jobs>" + Const.CR ); //$NON-NLS-1$
}
writer.write( "</repository>" + Const.CR + Const.CR ); //$NON-NLS-1$
if ( monitor != null ) {
monitor.worked( 1 );
monitor.subTask( "Saving XML to file [" + xmlFilename + "]" ); //$NON-NLS-1$ //$NON-NLS-2$
monitor.worked( 1 );
}
} catch ( IOException e ) {
log.logError( BaseMessages.getString( PKG, "PurRepositoryExporter.ERROR_CREATE_FILE", xmlFilename ), e ); //$NON-NLS-1$
} finally {
try {
if ( writer != null ) {
writer.close();
}
} catch ( Exception e ) {
log.logError( BaseMessages.getString( PKG, "PurRepositoryExporter.ERROR_CLOSE_FILE", xmlFilename ), e ); //$NON-NLS-1$
}
}
if ( monitor != null ) {
monitor.done();
}
}
/**
* Set the batch size for fetching objects from the repository.
*/
private void initBatchSize() {
batchSize = DEFAULT_BATCH_SIZE;
String batchProp = Const.getEnvironmentVariable( REPOSITORY_BATCH_SIZE_PROPERTY, null );
boolean err = false;
if ( !Utils.isEmpty( batchProp ) ) {
try {
batchSize = Integer.parseInt( batchProp );
if ( batchSize < 1 ) {
err = true;
}
} catch ( Exception ex ) {
err = true;
}
}
if ( err ) {
batchSize = DEFAULT_BATCH_SIZE;
log.logError( BaseMessages.getString( PKG, "PurRepositoryExporter.ERROR_INVALID_BATCH_SIZE",
REPOSITORY_BATCH_SIZE_PROPERTY, batchProp, batchSize ), err ); //$NON-NLS-1$
}
log.logDetailed( BaseMessages.getString( PKG, "PurRepositoryExporter.DETAILED_USED_BATCH_SIZE", batchSize ) ); //$NON-NLS-1$
}
/**
* Controls what objects are exported in bulk from the repository and how to export them.
*/
private interface RepositoryFileBatchExporter {
/**
* Friendly name for the types this exporter exports. (Used for logging)
*/
String getFriendlyTypeName();
/**
* Can this file be exported by this batch exporter?
*
* @param file
* File in question
* @return true if this batch exporter can handle this type
* @throws KettleException
* error determining object type of {@code file}
*/
boolean canExport( final RepositoryFile file ) throws KettleException;
/**
* Export the files.
*
* @param monitor
* Progress should be provided using this monitor.
* @param files
* Repository files to export
* @param writer
* Writer to serialize files to
* @throws KettleException
* error exporting files
*/
void export( final ProgressMonitorListener monitor, final List<RepositoryFile> files,
final OutputStreamWriter writer ) throws KettleException;
}
/**
* Transformation exporter
*/
private class TransformationBatchExporter implements RepositoryFileBatchExporter {
public String getFriendlyTypeName() {
return "transformations"; //$NON-NLS-1$
}
public boolean canExport( RepositoryFile file ) throws KettleException {
return RepositoryObjectType.TRANSFORMATION.equals( repository.getObjectType( file.getName() ) );
}
public void export( ProgressMonitorListener monitor, List<RepositoryFile> files, OutputStreamWriter writer )
throws KettleException {
List<TransMeta> transformations = repository.loadTransformations( monitor, log, files, true );
Iterator<TransMeta> transMetasIter = transformations.iterator();
Iterator<RepositoryFile> filesIter = files.iterator();
while ( ( monitor == null || !monitor.isCanceled() ) && transMetasIter.hasNext() ) {
TransMeta trans = transMetasIter.next();
setGlobalVariablesOfLogTablesNull( trans.getLogTables() );
RepositoryFile file = filesIter.next();
try {
// Validate against the import rules first!
if ( toExport( trans ) ) {
writer.write( trans.getXML() + Const.CR );
}
} catch ( Exception ex ) {
// if exception while writing one item is occurred logging it and continue looping
log.logError( BaseMessages.getString( PKG, "PurRepositoryExporter.ERROR_SAVE_TRANSFORMATION",
trans.getName(), file.getPath() ), ex ); //$NON-NLS-1$
}
}
}
}
private boolean toExport( AbstractMeta meta ) {
boolean shouldExport = true;
List<ImportValidationFeedback> feedback = importRules.verifyRules( meta );
List<ImportValidationFeedback> errors = ImportValidationFeedback.getErrors( feedback );
if ( !errors.isEmpty() ) {
shouldExport = false;
log.logError( BaseMessages.getString( PKG, "PurRepositoryExporter.ERROR_EXPORT_ITEM", meta.getName() ) ); //$NON-NLS-1$
for ( ImportValidationFeedback error : errors ) {
log.logError( BaseMessages.getString( PKG, "PurRepositoryExporter.ERROR_EXPORT_ITEM_RULE", error.toString() ) ); //$NON-NLS-1$
}
}
return shouldExport;
}
protected void setGlobalVariablesOfLogTablesNull( List<LogTableInterface> logTables ) {
for ( LogTableInterface logTable : logTables ) {
if ( logTable instanceof BaseLogTable ) {
( (BaseLogTable) logTable ).setAllGlobalParametersToNull();
}
}
}
private class JobBatchExporter implements RepositoryFileBatchExporter {
public String getFriendlyTypeName() {
return "jobs"; //$NON-NLS-1$
}
public boolean canExport( RepositoryFile file ) throws KettleException {
return RepositoryObjectType.JOB.equals( repository.getObjectType( file.getName() ) );
}
public void export( ProgressMonitorListener monitor, List<RepositoryFile> files, OutputStreamWriter writer )
throws KettleException {
List<JobMeta> jobs = repository.loadJobs( monitor, log, files, true );
Iterator<JobMeta> jobsMeta = jobs.iterator();
Iterator<RepositoryFile> filesIter = files.iterator();
while ( ( monitor == null || !monitor.isCanceled() ) && jobsMeta.hasNext() ) {
JobMeta meta = jobsMeta.next();
setGlobalVariablesOfLogTablesNull( meta.getLogTables() );
RepositoryFile file = filesIter.next();
try {
// Validate against the import rules first!
if ( toExport( meta ) ) {
writer.write( meta.getXML() + Const.CR );
}
} catch ( Exception ex ) {
// if exception while writing one item is occurred logging it and continue looping
log.logError( BaseMessages.getString( PKG, "PurRepositoryExporter.ERROR_SAVE_JOB", meta.getName(), file
.getPath() ), ex ); //$NON-NLS-1$
}
}
}
}
/**
* Export objects by directory, breadth first.
*
* @param monitor
* Feedback handler
* @param root
* Root of repository to export from
* @param writer
* Output stream the exporter uses to serialize repository objects
* @param exporter
* Processes groups of nodes per directory
* @throws KettleException
* error performing export
*/
private void export( final ProgressMonitorListener monitor, final RepositoryFileTree root,
final OutputStreamWriter writer, final RepositoryFileBatchExporter exporter ) throws KettleException {
List<RepositoryFileTree> subdirectories = new ArrayList<RepositoryFileTree>();
// Assume the repository objects are loaded. If they're null then there are no repository objects in this directory
if ( root.getChildren() != null && !root.getChildren().isEmpty() ) {
Iterator<RepositoryFileTree> repObjIter = root.getChildren().iterator();
List<RepositoryFile> files = new ArrayList<RepositoryFile>();
// Walk the tree collecting subdirectories and objects to export
while ( ( monitor == null || !monitor.isCanceled() ) && repObjIter.hasNext() ) {
RepositoryFileTree repObj = repObjIter.next();
if ( repObj.getFile().isFolder() ) {
// This is a directory, cache it so we can export it after the current folder's objects
subdirectories.add( repObj );
continue;
} else if ( !exporter.canExport( repObj.getFile() ) ) {
// Cannot export this type
continue;
}
files.add( repObj.getFile() );
}
if ( !files.isEmpty() ) {
log.logBasic( BaseMessages.getString( PKG, "PurRepositoryExporter.BASIC_EXPORT_FROM", files.size(), exporter
.getFriendlyTypeName(), root.getFile().getPath() ) ); //$NON-NLS-1$
// Only fetch batchSize transformations at a time
for ( int i = 0; ( monitor == null || !monitor.isCanceled() ) && i < files.size(); i += batchSize ) {
int start = i;
int end = Math.min( i + batchSize, files.size() );
List<RepositoryFile> group = files.subList( start, end );
if ( monitor != null ) {
monitor.subTask( "Loading " + group.size() + " " + exporter.getFriendlyTypeName() + " from "
+ root.getFile().getPath() ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
try {
exporter.export( monitor, group, writer );
} catch ( KettleException ex ) {
log.logError( BaseMessages.getString( PKG, "PurRepositoryExporter.ERROR_EXPORT", exporter
.getFriendlyTypeName(), root.getFile().getPath() ), ex ); //$NON-NLS-1$
}
}
}
// Export subdirectories
Iterator<RepositoryFileTree> subdirIter = subdirectories.iterator();
while ( ( monitor == null || !monitor.isCanceled() ) && subdirIter.hasNext() ) {
export( monitor, subdirIter.next(), writer, exporter );
}
}
}
public void setImportRulesToValidate( ImportRules importRules ) {
this.importRules = importRules;
}
public ImportRules getImportRules() {
return importRules;
}
}