/*! ******************************************************************************
*
* 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.cluster;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.changed.ChangedFlag;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.value.ValueMetaString;
import org.pentaho.di.core.variables.VariableSpace;
import org.pentaho.di.core.variables.Variables;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.core.xml.XMLInterface;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.repository.ObjectId;
import org.pentaho.di.repository.ObjectRevision;
import org.pentaho.di.repository.RepositoryDirectory;
import org.pentaho.di.repository.RepositoryDirectoryInterface;
import org.pentaho.di.repository.RepositoryElementInterface;
import org.pentaho.di.repository.RepositoryObjectType;
import org.pentaho.di.shared.SharedObjectInterface;
import org.pentaho.di.www.SlaveServerDetection;
import org.w3c.dom.Node;
/**
* A cluster schema combines a list of slave servers so that they can be set altogether. It (can) also contain a number
* of performance tuning options regarding this cluster. For example options regarding communications with the master
* node of the nodes themselves come to mind.
*
* @author Matt
* @since 17-nov-2006
*/
public class ClusterSchema extends ChangedFlag implements Cloneable, SharedObjectInterface, VariableSpace,
RepositoryElementInterface, XMLInterface {
private static Class<?> PKG = ClusterSchema.class; // for i18n purposes, needed by Translator2!!
public static final String XML_TAG = "clusterschema";
public static final RepositoryObjectType REPOSITORY_ELEMENT_TYPE = RepositoryObjectType.CLUSTER_SCHEMA;
/** the name of the cluster schema */
private String name;
/** The list of slave servers we can address */
private List<SlaveServer> slaveServers;
/** The data socket port where we start numbering. The upper limit is the number of remote socket connections. */
private String basePort;
private boolean shared;
/** Size of the buffer for the created socket reader/writers */
private String socketsBufferSize;
/** Flush outputstreams every X rows */
private String socketsFlushInterval;
/** flag to compress data over the sockets or not */
private boolean socketsCompressed;
/**
* Flag to indicate that this cluster schema is dynamic.<br>
* This means that the slave server configuration is taken from one of the defined master servers.<br>
*/
private boolean dynamic;
private VariableSpace variables = new Variables();
private ObjectId id;
private ObjectRevision objectRevision;
private Date changedDate;
public ClusterSchema() {
id = null;
slaveServers = new ArrayList<SlaveServer>();
socketsBufferSize = "2000";
socketsFlushInterval = "5000";
socketsCompressed = true;
basePort = "40000";
dynamic = false;
this.changedDate = new Date();
}
/**
* @param name
* @param slaveServers
*/
public ClusterSchema( String name, List<SlaveServer> slaveServers ) {
this.name = name;
this.slaveServers = slaveServers;
this.changedDate = new Date();
}
public ClusterSchema clone() {
ClusterSchema clusterSchema = new ClusterSchema();
clusterSchema.replaceMeta( this );
return clusterSchema;
}
public void replaceMeta( ClusterSchema clusterSchema ) {
this.name = clusterSchema.name;
this.basePort = clusterSchema.basePort;
this.socketsBufferSize = clusterSchema.socketsBufferSize;
this.socketsCompressed = clusterSchema.socketsCompressed;
this.socketsFlushInterval = clusterSchema.socketsFlushInterval;
this.dynamic = clusterSchema.dynamic;
this.slaveServers.clear();
this.slaveServers.addAll( clusterSchema.slaveServers ); // no clone() of the slave server please!
this.shared = clusterSchema.shared;
this.id = clusterSchema.id;
this.setChanged( true );
}
public String toString() {
return name;
}
public boolean equals( Object obj ) {
if ( obj == null ) {
return false;
}
return name.equals( ( (ClusterSchema) obj ).name );
}
public int hashCode() {
return name.hashCode();
}
public String getXML() {
StringBuilder xml = new StringBuilder( 500 );
xml.append( " " ).append( XMLHandler.openTag( XML_TAG ) ).append( Const.CR );
xml.append( " " ).append( XMLHandler.addTagValue( "name", name ) );
xml.append( " " ).append( XMLHandler.addTagValue( "base_port", basePort ) );
xml.append( " " ).append( XMLHandler.addTagValue( "sockets_buffer_size", socketsBufferSize ) );
xml.append( " " ).append( XMLHandler.addTagValue( "sockets_flush_interval", socketsFlushInterval ) );
xml.append( " " ).append( XMLHandler.addTagValue( "sockets_compressed", socketsCompressed ) );
xml.append( " " ).append( XMLHandler.addTagValue( "dynamic", dynamic ) );
xml.append( " " ).append( XMLHandler.openTag( "slaveservers" ) ).append( Const.CR );
for ( int i = 0; i < slaveServers.size(); i++ ) {
SlaveServer slaveServer = slaveServers.get( i );
xml.append( " " ).append( XMLHandler.addTagValue( "name", slaveServer.getName() ) );
}
xml.append( " " ).append( XMLHandler.closeTag( "slaveservers" ) ).append( Const.CR );
xml.append( " " ).append( XMLHandler.closeTag( XML_TAG ) ).append( Const.CR );
return xml.toString();
}
public ClusterSchema( Node clusterSchemaNode, List<SlaveServer> referenceSlaveServers ) {
this();
name = XMLHandler.getTagValue( clusterSchemaNode, "name" );
basePort = XMLHandler.getTagValue( clusterSchemaNode, "base_port" );
socketsBufferSize = XMLHandler.getTagValue( clusterSchemaNode, "sockets_buffer_size" );
socketsFlushInterval = XMLHandler.getTagValue( clusterSchemaNode, "sockets_flush_interval" );
socketsCompressed = "Y".equalsIgnoreCase( XMLHandler.getTagValue( clusterSchemaNode, "sockets_compressed" ) );
dynamic = "Y".equalsIgnoreCase( XMLHandler.getTagValue( clusterSchemaNode, "dynamic" ) );
Node slavesNode = XMLHandler.getSubNode( clusterSchemaNode, "slaveservers" );
int nrSlaves = XMLHandler.countNodes( slavesNode, "name" );
for ( int i = 0; i < nrSlaves; i++ ) {
Node serverNode = XMLHandler.getSubNodeByNr( slavesNode, "name", i );
String serverName = XMLHandler.getNodeValue( serverNode );
SlaveServer slaveServer = SlaveServer.findSlaveServer( referenceSlaveServers, serverName );
if ( slaveServer != null ) {
slaveServers.add( slaveServer );
}
}
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name
* the name to set
*/
public void setName( String name ) {
this.name = name;
}
/**
* @return the internal (static) list of slave servers
*/
public List<SlaveServer> getSlaveServers() {
return slaveServers;
}
/**
* @param slaveServers
* the slaveServers to set
*/
public void setSlaveServers( List<SlaveServer> slaveServers ) {
this.slaveServers = slaveServers;
}
/**
* @return The slave server strings from this cluster schema
*/
public String[] getSlaveServerStrings() {
String[] strings = new String[slaveServers.size()];
for ( int i = 0; i < strings.length; i++ ) {
strings[i] = ( slaveServers.get( i ) ).toString();
}
return strings;
}
/**
* @return the shared
*/
public boolean isShared() {
return shared;
}
/**
* @param shared
* the shared to set
*/
public void setShared( boolean shared ) {
this.shared = shared;
}
/**
* @return the basePort
*/
public String getBasePort() {
return basePort;
}
/**
* @param basePort
* the basePort to set
*/
public void setBasePort( String basePort ) {
this.basePort = basePort;
}
public SlaveServer findMaster() throws KettleException {
for ( int i = 0; i < slaveServers.size(); i++ ) {
SlaveServer slaveServer = slaveServers.get( i );
if ( slaveServer.isMaster() ) {
return slaveServer;
}
}
if ( slaveServers.size() > 0 ) {
throw new KettleException( BaseMessages.getString( PKG, "ClusterSchema.NoMasterServerDefined", name ) );
}
throw new KettleException( BaseMessages.getString( PKG, "ClusterSchema.NoSlaveServerDefined", name ) );
}
/**
* @return The number of slave servers, excluding the master server
*/
public int findNrSlaves() {
int nr = 0;
for ( int i = 0; i < slaveServers.size(); i++ ) {
SlaveServer slaveServer = slaveServers.get( i );
if ( !slaveServer.isMaster() ) {
nr++;
}
}
return nr;
}
/**
* @return the socketFlushInterval
*/
public String getSocketsFlushInterval() {
return socketsFlushInterval;
}
/**
* @param socketFlushInterval
* the socketFlushInterval to set
*/
public void setSocketsFlushInterval( String socketFlushInterval ) {
this.socketsFlushInterval = socketFlushInterval;
}
/**
* @return the socketsBufferSize
*/
public String getSocketsBufferSize() {
return socketsBufferSize;
}
/**
* @param socketsBufferSize
* the socketsBufferSize to set
*/
public void setSocketsBufferSize( String socketsBufferSize ) {
this.socketsBufferSize = socketsBufferSize;
}
/**
* @return the socketsCompressed
*/
public boolean isSocketsCompressed() {
return socketsCompressed;
}
/**
* @param socketsCompressed
* the socketsCompressed to set
*/
public void setSocketsCompressed( boolean socketsCompressed ) {
this.socketsCompressed = socketsCompressed;
}
public SlaveServer findSlaveServer( String slaveServerName ) {
for ( int i = 0; i < slaveServers.size(); i++ ) {
SlaveServer slaveServer = slaveServers.get( i );
if ( slaveServer.getName().equalsIgnoreCase( slaveServerName ) ) {
return slaveServer;
}
}
return null;
}
public ObjectId getObjectId() {
return id;
}
public void setObjectId( ObjectId id ) {
this.id = id;
}
public void copyVariablesFrom( VariableSpace space ) {
variables.copyVariablesFrom( space );
}
public String environmentSubstitute( String aString ) {
return variables.environmentSubstitute( aString );
}
public String[] environmentSubstitute( String[] aString ) {
return variables.environmentSubstitute( aString );
}
public String fieldSubstitute( String aString, RowMetaInterface rowMeta, Object[] rowData ) throws KettleValueException {
return variables.fieldSubstitute( aString, rowMeta, rowData );
}
public VariableSpace getParentVariableSpace() {
return variables.getParentVariableSpace();
}
public void setParentVariableSpace( VariableSpace parent ) {
variables.setParentVariableSpace( parent );
}
public String getVariable( String variableName, String defaultValue ) {
return variables.getVariable( variableName, defaultValue );
}
public String getVariable( String variableName ) {
return variables.getVariable( variableName );
}
public boolean getBooleanValueOfVariable( String variableName, boolean defaultValue ) {
if ( !Utils.isEmpty( variableName ) ) {
String value = environmentSubstitute( variableName );
if ( !Utils.isEmpty( value ) ) {
return ValueMetaString.convertStringToBoolean( value );
}
}
return defaultValue;
}
public void initializeVariablesFrom( VariableSpace parent ) {
variables.initializeVariablesFrom( parent );
}
public String[] listVariables() {
return variables.listVariables();
}
public void setVariable( String variableName, String variableValue ) {
variables.setVariable( variableName, variableValue );
}
public void shareVariablesWith( VariableSpace space ) {
variables = space;
}
public void injectVariables( Map<String, String> prop ) {
variables.injectVariables( prop );
}
/**
* @return the dynamic
*/
public boolean isDynamic() {
return dynamic;
}
/**
* @param dynamic
* the dynamic to set
*/
public void setDynamic( boolean dynamic ) {
this.dynamic = dynamic;
}
/**
* @return A list of dynamic slave servers, retrieved from the first master server that was available.
* @throws KettleException
* when none of the masters can be contacted.
*/
public List<SlaveServer> getSlaveServersFromMasterOrLocal() throws KettleException {
if ( isDynamic() ) {
// Find a master that is available
//
List<SlaveServer> dynamicSlaves = null;
Exception exception = null;
for ( int i = 0; i < slaveServers.size(); i++ ) {
SlaveServer slave = slaveServers.get( i );
if ( slave.isMaster() && dynamicSlaves == null ) {
try {
List<SlaveServerDetection> detections = slave.getSlaveServerDetections();
dynamicSlaves = new ArrayList<SlaveServer>();
for ( SlaveServerDetection detection : detections ) {
if ( detection.isActive() ) {
dynamicSlaves.add( detection.getSlaveServer() );
}
}
} catch ( Exception e ) {
exception = e; // Remember the last exception
}
}
}
if ( dynamicSlaves == null && exception != null ) {
throw new KettleException( exception );
}
return dynamicSlaves;
} else {
return slaveServers;
}
}
/**
* Not supported for Partition schema, return the root.
*/
public RepositoryDirectoryInterface getRepositoryDirectory() {
return new RepositoryDirectory();
}
public void setRepositoryDirectory( RepositoryDirectoryInterface repositoryDirectory ) {
}
public RepositoryObjectType getRepositoryElementType() {
return REPOSITORY_ELEMENT_TYPE;
}
public ObjectRevision getObjectRevision() {
return objectRevision;
}
public void setObjectRevision( ObjectRevision objectRevision ) {
this.objectRevision = objectRevision;
}
public String getDescription() {
// NOT USED
return null;
}
public void setDescription( String description ) {
// NOT USED
}
/**
* @return the changedDate
*/
public Date getChangedDate() {
return changedDate;
}
/**
* @param changedDate
* the changedDate to set
*/
public void setChangedDate( Date changedDate ) {
this.changedDate = changedDate;
}
}