/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2013 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.www;
import org.pentaho.di.cluster.SlaveServer;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.database.Database;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.encryption.Encr;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleXMLException;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.logging.LoggingObjectInterface;
import org.pentaho.di.core.logging.LoggingObjectType;
import org.pentaho.di.core.logging.SimpleLoggingObject;
import org.pentaho.di.core.plugins.PluginRegistry;
import org.pentaho.di.core.plugins.RepositoryPluginType;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.metastore.MetaStoreConst;
import org.pentaho.di.repository.RepositoriesMeta;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.repository.RepositoryMeta;
import org.pentaho.metastore.api.exceptions.MetaStoreException;
import org.pentaho.metastore.stores.delegate.DelegatingMetaStore;
import org.pentaho.metastore.stores.memory.MemoryMetaStore;
import org.pentaho.metastore.stores.xml.XmlMetaStore;
import org.w3c.dom.Node;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class SlaveServerConfig {
public static final String XML_TAG = "slave_config";
public static final String XML_TAG_MASTERS = "masters";
public static final String XML_TAG_REPOSITORY = "repository";
public static final String XML_TAG_SEQUENCES = "sequences";
public static final String XML_TAG_AUTOSEQUENCE = "autosequence";
public static final String XML_TAG_AUTO_CREATE = "autocreate";
public static final String XML_TAG_JETTY_OPTIONS = "jetty_options";
public static final String XML_TAG_ACCEPTORS = "acceptors";
public static final String XML_TAG_ACCEPT_QUEUE_SIZE = "acceptQueueSize";
public static final String XML_TAG_LOW_RES_MAX_IDLE_TIME = "lowResourcesMaxIdleTime";
private List<SlaveServer> masters;
private SlaveServer slaveServer;
private boolean reportingToMasters;
private boolean joining;
private int maxLogLines;
private int maxLogTimeoutMinutes;
private int objectTimeoutMinutes;
private String filename;
private List<DatabaseMeta> databases;
private List<SlaveSequence> slaveSequences;
private SlaveSequence autoSequence;
private boolean automaticCreationAllowed;
private Repository repository;
private RepositoryMeta repositoryMeta;
private String repositoryId;
private String repositoryUsername;
private String repositoryPassword;
private DelegatingMetaStore metaStore;
private String passwordFile;
public SlaveServerConfig() {
masters = new ArrayList<SlaveServer>();
databases = new ArrayList<DatabaseMeta>();
slaveSequences = new ArrayList<SlaveSequence>();
automaticCreationAllowed = false;
metaStore = new DelegatingMetaStore();
// Add the local Pentaho MetaStore to the delegation.
// This sets it as the active one.
//
try {
XmlMetaStore localStore = new XmlMetaStore( MetaStoreConst.getDefaultPentahoMetaStoreLocation() );
metaStore.addMetaStore( localStore );
metaStore.setActiveMetaStoreName( localStore.getName() );
} catch ( MetaStoreException e ) {
LogChannel.GENERAL.logError( "Unable to open local Pentaho meta store from [" + MetaStoreConst.getDefaultPentahoMetaStoreLocation() + "]", e );
// now replace this with an in memory metastore.
//
try {
MemoryMetaStore memoryStore = new MemoryMetaStore();
memoryStore.setName( "Memory metastore" );
metaStore.addMetaStore( memoryStore );
metaStore.setActiveMetaStoreName( memoryStore.getName() );
} catch ( MetaStoreException e2 ) {
throw new RuntimeException( "Unable to add a default memory metastore to the delegating store", e );
}
}
passwordFile = null; // force lookup by server in ~/.kettle or local folder
}
public SlaveServerConfig( SlaveServer slaveServer ) {
this();
this.slaveServer = slaveServer;
}
public SlaveServerConfig( List<SlaveServer> masters, boolean reportingToMasters, SlaveServer slaveServer ) {
this.masters = masters;
this.reportingToMasters = reportingToMasters;
this.slaveServer = slaveServer;
}
public String getXML() {
StringBuilder xml = new StringBuilder();
xml.append( XMLHandler.openTag( XML_TAG ) );
for ( SlaveServer slaveServer : masters ) {
xml.append( slaveServer.getXML() );
}
XMLHandler.addTagValue( "report_to_masters", reportingToMasters );
if ( slaveServer != null ) {
xml.append( slaveServer.getXML() );
}
XMLHandler.addTagValue( "joining", joining );
XMLHandler.addTagValue( "max_log_lines", maxLogLines );
XMLHandler.addTagValue( "max_log_timeout_minutes", maxLogTimeoutMinutes );
XMLHandler.addTagValue( "object_timeout_minutes", objectTimeoutMinutes );
xml.append( XMLHandler.openTag( XML_TAG_SEQUENCES ) );
for ( SlaveSequence slaveSequence : slaveSequences ) {
xml.append( XMLHandler.openTag( SlaveSequence.XML_TAG ) );
xml.append( slaveSequence.getXML() );
xml.append( XMLHandler.closeTag( SlaveSequence.XML_TAG ) );
}
xml.append( XMLHandler.closeTag( XML_TAG_SEQUENCES ) );
if ( autoSequence != null ) {
xml.append( XMLHandler.openTag( XML_TAG_AUTOSEQUENCE ) );
xml.append( autoSequence.getXML() );
xml.append( XMLHandler.addTagValue( XML_TAG_AUTO_CREATE, automaticCreationAllowed ) );
xml.append( XMLHandler.closeTag( XML_TAG_AUTOSEQUENCE ) );
}
if ( repositoryMeta != null ) {
xml.append( XMLHandler.openTag( XML_TAG_REPOSITORY ) );
xml.append( " " ).append( XMLHandler.addTagValue( "id", repositoryMeta.getId() ) );
xml.append( " " ).append( XMLHandler.addTagValue( "username", repositoryUsername ) );
xml.append( " " ).append(
XMLHandler.addTagValue( "password", Encr.encryptPasswordIfNotUsingVariables( repositoryPassword ) ) );
xml.append( XMLHandler.closeTag( XML_TAG_REPOSITORY ) );
}
xml.append( XMLHandler.closeTag( XML_TAG ) );
return xml.toString();
}
public SlaveServerConfig( LogChannelInterface log, Node node ) throws KettleXMLException {
this();
Node slaveNode = XMLHandler.getSubNode( node, SlaveServer.XML_TAG );
if ( slaveNode != null ) {
slaveServer = new SlaveServer( slaveNode );
checkNetworkInterfaceSetting( log, slaveNode, slaveServer );
}
Node mastersNode = XMLHandler.getSubNode( node, XML_TAG_MASTERS );
int nrMasters = XMLHandler.countNodes( mastersNode, SlaveServer.XML_TAG );
for ( int i = 0; i < nrMasters; i++ ) {
Node masterSlaveNode = XMLHandler.getSubNodeByNr( mastersNode, SlaveServer.XML_TAG, i );
SlaveServer masterSlaveServer = new SlaveServer( masterSlaveNode );
checkNetworkInterfaceSetting( log, masterSlaveNode, masterSlaveServer );
masterSlaveServer.setSslMode( slaveServer.isSslMode() );
masters.add( masterSlaveServer );
}
reportingToMasters = "Y".equalsIgnoreCase( XMLHandler.getTagValue( node, "report_to_masters" ) );
joining = "Y".equalsIgnoreCase( XMLHandler.getTagValue( node, "joining" ) );
maxLogLines = Const.toInt( XMLHandler.getTagValue( node, "max_log_lines" ), 0 );
maxLogTimeoutMinutes = Const.toInt( XMLHandler.getTagValue( node, "max_log_timeout_minutes" ), 0 );
objectTimeoutMinutes = Const.toInt( XMLHandler.getTagValue( node, "object_timeout_minutes" ), 0 );
// Read sequence information
//
List<Node> dbNodes = XMLHandler.getNodes( node, DatabaseMeta.XML_TAG );
for ( Node dbNode : dbNodes ) {
databases.add( new DatabaseMeta( dbNode ) );
}
Node sequencesNode = XMLHandler.getSubNode( node, "sequences" );
List<Node> seqNodes = XMLHandler.getNodes( sequencesNode, SlaveSequence.XML_TAG );
for ( Node seqNode : seqNodes ) {
slaveSequences.add( new SlaveSequence( seqNode, databases ) );
}
Node autoSequenceNode = XMLHandler.getSubNode( node, XML_TAG_AUTOSEQUENCE );
if ( autoSequenceNode != null ) {
autoSequence = new SlaveSequence( autoSequenceNode, databases );
automaticCreationAllowed =
"Y".equalsIgnoreCase( XMLHandler.getTagValue( autoSequenceNode, XML_TAG_AUTO_CREATE ) );
}
// Set Jetty Options
setUpJettyOptions( node );
Node repositoryNode = XMLHandler.getSubNode( node, XML_TAG_REPOSITORY );
repositoryId = XMLHandler.getTagValue( repositoryNode, "name" );
repositoryUsername = XMLHandler.getTagValue( repositoryNode, "username" );
repositoryPassword = XMLHandler.getTagValue( repositoryNode, "password" );
}
/** Set up jetty options to the system properties
* @param node
*/
protected void setUpJettyOptions( Node node ) {
Map<String, String> jettyOptions = parseJettyOptions( node );
if ( jettyOptions != null && jettyOptions.size() > 0 ) {
for ( Entry<String, String> jettyOption : jettyOptions.entrySet() ) {
System.setProperty( jettyOption.getKey(), jettyOption.getValue() );
}
}
}
/**
* Read and parse jetty options
*
* @param node
* that contains jetty options nodes
* @return map of not empty jetty options
*/
protected Map<String, String> parseJettyOptions( Node node ) {
Map<String, String> jettyOptions = null;
Node jettyOptionsNode = XMLHandler.getSubNode( node, XML_TAG_JETTY_OPTIONS );
if ( jettyOptionsNode != null ) {
jettyOptions = new HashMap<String, String>();
if ( XMLHandler.getTagValue( jettyOptionsNode, XML_TAG_ACCEPTORS ) != null ) {
jettyOptions.put( Const.KETTLE_CARTE_JETTY_ACCEPTORS, XMLHandler.getTagValue( jettyOptionsNode, XML_TAG_ACCEPTORS ) );
}
if ( XMLHandler.getTagValue( jettyOptionsNode, XML_TAG_ACCEPT_QUEUE_SIZE ) != null ) {
jettyOptions.put( Const.KETTLE_CARTE_JETTY_ACCEPT_QUEUE_SIZE, XMLHandler.getTagValue( jettyOptionsNode,
XML_TAG_ACCEPT_QUEUE_SIZE ) );
}
if ( XMLHandler.getTagValue( jettyOptionsNode, XML_TAG_LOW_RES_MAX_IDLE_TIME ) != null ) {
jettyOptions.put( Const.KETTLE_CARTE_JETTY_RES_MAX_IDLE_TIME, XMLHandler.getTagValue( jettyOptionsNode,
XML_TAG_LOW_RES_MAX_IDLE_TIME ) );
}
}
return jettyOptions;
}
private void openRepository( String repositoryId ) throws KettleException {
try {
RepositoriesMeta repositoriesMeta = new RepositoriesMeta();
repositoriesMeta.readData();
repositoryMeta = repositoriesMeta.findRepository( repositoryId );
if ( repositoryMeta == null ) {
throw new KettleException( "Unable to find repository: " + repositoryId );
}
PluginRegistry registry = PluginRegistry.getInstance();
repository = registry.loadClass( RepositoryPluginType.class, repositoryMeta, Repository.class );
repository.init( repositoryMeta );
repository.connect( repositoryUsername, repositoryPassword );
// Add the repository MetaStore to the delegation as well.
// Set this one as active with the highest priority
//
if ( repository.getMetaStore() != null ) {
metaStore.addMetaStore( 0, repository.getMetaStore() );
metaStore.setActiveMetaStoreName( repository.getMetaStore().getName() );
}
LogChannel.GENERAL.logBasic( "Connected to repository '" + repository.getName() + "'" );
} catch ( Exception e ) {
throw new KettleException( "Unable to open repository connection", e );
}
}
public void readAutoSequences() throws KettleException {
if ( autoSequence == null ) {
return;
}
Database database = null;
try {
DatabaseMeta databaseMeta = autoSequence.getDatabaseMeta();
LoggingObjectInterface loggingInterface =
new SimpleLoggingObject( "auto-sequence", LoggingObjectType.GENERAL, null );
database = new Database( loggingInterface, databaseMeta );
database.connect();
String schemaTable =
databaseMeta.getQuotedSchemaTableCombination( autoSequence.getSchemaName(), autoSequence.getTableName() );
String seqField = databaseMeta.quoteField( autoSequence.getSequenceNameField() );
String valueField = databaseMeta.quoteField( autoSequence.getValueField() );
String sql = "SELECT " + seqField + ", " + valueField + " FROM " + schemaTable;
List<Object[]> rows = database.getRows( sql, 0 );
RowMetaInterface rowMeta = database.getReturnRowMeta();
for ( Object[] row : rows ) {
// Automatically create a new sequence for each sequence found...
//
String sequenceName = rowMeta.getString( row, seqField, null );
if ( !Utils.isEmpty( sequenceName ) ) {
Long value = rowMeta.getInteger( row, valueField, null );
if ( value != null ) {
SlaveSequence slaveSequence =
new SlaveSequence( sequenceName, value, databaseMeta, autoSequence.getSchemaName(), autoSequence
.getTableName(), autoSequence.getSequenceNameField(), autoSequence.getValueField() );
slaveSequences.add( slaveSequence );
LogChannel.GENERAL.logBasic( "Automatically created slave sequence '"
+ slaveSequence.getName() + "' with start value " + slaveSequence.getStartValue() );
}
}
}
} catch ( Exception e ) {
throw new KettleException( "Unable to automatically configure slave sequences", e );
} finally {
if ( database != null ) {
database.disconnect();
}
}
}
private void checkNetworkInterfaceSetting( LogChannelInterface log, Node slaveNode, SlaveServer slaveServer ) {
// See if we need to grab the network interface to use and then override the host name
//
String networkInterfaceName = XMLHandler.getTagValue( slaveNode, "network_interface" );
if ( !Utils.isEmpty( networkInterfaceName ) ) {
// OK, so let's try to get the IP address for this network interface...
//
try {
String newHostname = Const.getIPAddress( networkInterfaceName );
if ( newHostname != null ) {
slaveServer.setHostname( newHostname );
// Also change the name of the slave...
//
slaveServer.setName( slaveServer.getName() + "-" + newHostname );
log.logBasic( "Hostname for slave server ["
+ slaveServer.getName() + "] is set to [" + newHostname + "], information derived from network "
+ networkInterfaceName );
}
} catch ( SocketException e ) {
log.logError( "Unable to get the IP address for network interface "
+ networkInterfaceName + " for slave server [" + slaveServer.getName() + "]", e );
}
}
}
public SlaveServerConfig( String hostname, int port, boolean joining ) {
this();
this.joining = joining;
this.slaveServer = new SlaveServer( hostname + ":" + port, hostname, "" + port, null, null );
}
/**
* @return the list of masters to report back to if the report to masters flag is enabled.
*/
public List<SlaveServer> getMasters() {
return masters;
}
/**
* @param masters
* the list of masters to set. It is the list of masters to report back to if the report to masters flag is
* enabled.
*/
public void setMasters( List<SlaveServer> masters ) {
this.masters = masters;
}
/**
* @return the slave server.<br>
* The user name and password defined in here are used to contact this slave by the masters.
*/
public SlaveServer getSlaveServer() {
return slaveServer;
}
/**
* @param slaveServer
* the slave server details to set.<br>
* The user name and password defined in here are used to contact this slave by the masters.
*/
public void setSlaveServer( SlaveServer slaveServer ) {
this.slaveServer = slaveServer;
}
/**
* @return true if this slave reports to the masters
*/
public boolean isReportingToMasters() {
return reportingToMasters;
}
/**
* @param reportingToMaster
* set to true if this slave should report to the masters
*/
public void setReportingToMasters( boolean reportingToMaster ) {
this.reportingToMasters = reportingToMaster;
}
/**
* @return true if the webserver needs to join with the webserver threads (wait/block until finished)
*/
public boolean isJoining() {
return joining;
}
/**
* @param joining
* Set to true if the webserver needs to join with the webserver threads (wait/block until finished)
*/
public void setJoining( boolean joining ) {
this.joining = joining;
}
/**
* @return the maxLogLines
*/
public int getMaxLogLines() {
return maxLogLines;
}
/**
* @param maxLogLines
* the maxLogLines to set
*/
public void setMaxLogLines( int maxLogLines ) {
this.maxLogLines = maxLogLines;
}
/**
* @return the maxLogTimeoutMinutes
*/
public int getMaxLogTimeoutMinutes() {
return maxLogTimeoutMinutes;
}
/**
* @param maxLogTimeoutMinutes
* the maxLogTimeoutMinutes to set
*/
public void setMaxLogTimeoutMinutes( int maxLogTimeoutMinutes ) {
this.maxLogTimeoutMinutes = maxLogTimeoutMinutes;
}
/**
* @return the objectTimeoutMinutes
*/
public int getObjectTimeoutMinutes() {
return objectTimeoutMinutes;
}
/**
* @param objectTimeoutMinutes
* the objectTimeoutMinutes to set
*/
public void setObjectTimeoutMinutes( int objectTimeoutMinutes ) {
this.objectTimeoutMinutes = objectTimeoutMinutes;
}
/**
* @return the filename
*/
public String getFilename() {
return filename;
}
/**
* @param filename
* the filename to set
*/
public void setFilename( String filename ) {
this.filename = filename;
}
/**
* @return the databases
*/
public List<DatabaseMeta> getDatabases() {
return databases;
}
/**
* @param databases
* the databases to set
*/
public void setDatabases( List<DatabaseMeta> databases ) {
this.databases = databases;
}
/**
* @return the slaveSequences
*/
public List<SlaveSequence> getSlaveSequences() {
return slaveSequences;
}
/**
* @param slaveSequences
* the slaveSequences to set
*/
public void setSlaveSequences( List<SlaveSequence> slaveSequences ) {
this.slaveSequences = slaveSequences;
}
/**
* @return the autoSequence
*/
public SlaveSequence getAutoSequence() {
return autoSequence;
}
/**
* @param autoSequence
* the autoSequence to set
*/
public void setAutoSequence( SlaveSequence autoSequence ) {
this.autoSequence = autoSequence;
}
/**
* @return the automaticCreationAllowed
*/
public boolean isAutomaticCreationAllowed() {
return automaticCreationAllowed;
}
/**
* @param automaticCreationAllowed
* the automaticCreationAllowed to set
*/
public void setAutomaticCreationAllowed( boolean automaticCreationAllowed ) {
this.automaticCreationAllowed = automaticCreationAllowed;
}
/**
* @return the repository, loaded lazily
*/
public Repository getRepository() throws KettleException {
if ( !Utils.isEmpty( repositoryId ) && repository == null ) {
openRepository( repositoryId );
}
return repository;
}
/**
* @param repository
* the repository to set
*/
public void setRepository( Repository repository ) {
this.repository = repository;
}
/**
* @return the repositoryUsername
*/
public String getRepositoryUsername() {
return repositoryUsername;
}
/**
* @param repositoryUsername
* the repositoryUsername to set
*/
public void setRepositoryUsername( String repositoryUsername ) {
this.repositoryUsername = repositoryUsername;
}
/**
* @return the repositoryPassword
*/
public String getRepositoryPassword() {
return repositoryPassword;
}
/**
* @param repositoryPassword
* the repositoryPassword to set
*/
public void setRepositoryPassword( String repositoryPassword ) {
this.repositoryPassword = repositoryPassword;
}
public DelegatingMetaStore getMetaStore() {
return metaStore;
}
public void setMetaStore( DelegatingMetaStore metaStore ) {
this.metaStore = metaStore;
}
public String getPasswordFile() {
return passwordFile;
}
public void setPasswordFile( String passwordFile ) {
this.passwordFile = passwordFile;
}
public String getRepositoryId() {
return repositoryId;
}
public void setRepositoryId( String repositoryId ) {
this.repositoryId = repositoryId;
}
}