/******************************************************************************* * Copyright (C) 2014, International Business Machines Corporation * All Rights Reserved *******************************************************************************/ package com.ibm.streamsx.messaging.mqtt; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Properties; import java.util.Set; import org.apache.log4j.Logger; import org.xml.sax.SAXException; import com.ibm.streams.operator.AbstractOperator; import com.ibm.streams.operator.OperatorContext.ContextCheck; import com.ibm.streams.operator.OutputTuple; import com.ibm.streams.operator.StreamSchema; import com.ibm.streams.operator.StreamingOutput; import com.ibm.streams.operator.Type.MetaType; import com.ibm.streams.operator.compile.OperatorContextChecker; import com.ibm.streams.operator.log4j.LogLevel; import com.ibm.streams.operator.log4j.LoggerNames; import com.ibm.streams.operator.log4j.TraceLevel; import com.ibm.streams.operator.metrics.Metric; import com.ibm.streams.operator.model.CustomMetric; import com.ibm.streams.operator.model.Parameter; import com.ibm.streams.operator.state.ConsistentRegionContext; import com.ibm.streamsx.messaging.common.PropertyProvider; import com.ibm.streamsx.messaging.mqtt.Messages; public abstract class AbstractMqttOperator extends AbstractOperator { public static final String PARAMNAME_KEY_STORE_PASSWORD = "keyStorePassword"; //$NON-NLS-1$ public static final String PARAMNAME_KEY_STORE = "keyStore"; //$NON-NLS-1$ public static final String PARAMNAME_TRUST_STORE_PASSWORD = "trustStorePassword"; //$NON-NLS-1$ public static final String PARAMNAME_TRUST_STORE = "trustStore"; //$NON-NLS-1$ public static final String PARAMNAME_CONNDOC = "connectionDocument"; //$NON-NLS-1$ public static final String PARAMNAME_CONNECTION = "connection"; //$NON-NLS-1$ public static final String PARAMNAME_SERVER_URI = "serverURI"; //$NON-NLS-1$ public static final String PARAMNAME_ERROR_OUT_ATTR_NAME = "errorOutAttrName"; //$NON-NLS-1$ public static final String PARAMNAME_CLIENT_ID = "clientID"; //$NON-NLS-1$ public static final String PARAMNAME_USER_ID = "userID"; //$NON-NLS-1$ public static final String PARAMNAME_PASSWORD = "password"; //$NON-NLS-1$ public static final String PARAMNAME_COMMAND_TIMEOUT = "commandTimeout"; //$NON-NLS-1$ public static final String PARAMNAME_KEEP_ALIVE = "keepAliveInterval"; //$NON-NLS-1$ public static final String PARAMNAME_DATA_ATTRIBUTE_NAME = "dataAttributeName"; //$NON-NLS-1$ public static final String PARAMNAME_SSL_PROTOCOL = "sslProtocol"; //$NON-NLS-1$ public static final String PARAMNAME_APP_CONFIG_NAME = "appConfigName"; //$NON-NLS-1$ public static final String PARAMNAME_USER_PROP_NAME = "userPropName"; //$NON-NLS-1$ public static final String PARAMNAME_PASSWORD_PROP_NAME = "passwordPropName"; //$NON-NLS-1$ static Logger TRACE = Logger.getLogger(AbstractMqttOperator.class); private static final Logger LOG = Logger.getLogger(LoggerNames.LOG_FACILITY + "." + AbstractMqttOperator.class.getName()); //$NON-NLS-1$ private String serverUri; private String connectionDocument; private String connection; private String trustStore; private String trustStorePassword; private String keyStore; private String keyStorePassword; private String sslProtocol = IMqttConstants.DEFAULT_SSL_PROTOCOL; private String clientID; private String userID; private String password; private String dataAttributeName; private long commandTimeout = IMqttConstants.UNINITIALIZED_COMMAND_TIMEOUT; private int keepAliveInterval = IMqttConstants.UNINITIALIZED_KEEP_ALIVE_INTERVAL; // application configuration name private String appConfigName; // user property name stored in application configuration private String userPropName; // password property name stored in application configuration private String passwordPropName; // Metrics keeping track of connection handling // nConnectionLost is number of lost connections to current MQTT server // isConnected indicates if operator currently connected to MQTT server, a value of 1 indicates it is connected and a value of 0 indicates it is not connected. Metric nConnectionLost; Metric isConnected; public AbstractMqttOperator() { super(); } @CustomMetric(kind = Metric.Kind.COUNTER, description="The number of lost connections to current MQTT server.") public void setnConnectionLost(Metric nConnectionLost) { this.nConnectionLost = nConnectionLost; } @CustomMetric(kind = Metric.Kind.GAUGE, description="Indicates if operator currently connected to MQTT server, a value of 1 indicates it is connected and a value of 0 indicates it is not connected.") public void setIsConnected(Metric isConnected) { this.isConnected = isConnected; } @ContextCheck(compile = true, runtime = false) public static void checkParams(OperatorContextChecker checker) { Set<String> parameterNames = checker.getOperatorContext() .getParameterNames(); if (!parameterNames.contains(PARAMNAME_SERVER_URI) && !parameterNames.contains(PARAMNAME_CONNECTION)) { checker.setInvalidContext( Messages.getString("SERVER_URI_OR_CONNECTION_MUST_BE_SET"), new Object[] {}); //$NON-NLS-1$ } checker.checkExcludedParameters(PARAMNAME_SERVER_URI, PARAMNAME_CONNECTION, PARAMNAME_CONNDOC); checker.checkExcludedParameters(PARAMNAME_CONNECTION, PARAMNAME_SERVER_URI); checker.checkDependentParameters(PARAMNAME_CONNDOC, PARAMNAME_CONNECTION); checker.checkDependentParameters(PARAMNAME_USER_ID, PARAMNAME_PASSWORD); checker.checkDependentParameters(PARAMNAME_PASSWORD, PARAMNAME_USER_ID); checker.checkDependentParameters(PARAMNAME_APP_CONFIG_NAME, PARAMNAME_USER_PROP_NAME, PARAMNAME_PASSWORD_PROP_NAME); checker.checkDependentParameters(PARAMNAME_USER_PROP_NAME, PARAMNAME_APP_CONFIG_NAME, PARAMNAME_PASSWORD_PROP_NAME); checker.checkDependentParameters(PARAMNAME_PASSWORD_PROP_NAME, PARAMNAME_APP_CONFIG_NAME, PARAMNAME_USER_PROP_NAME); } @ContextCheck(compile=false) public static void runtimeCheckParameterValue(OperatorContextChecker checker) { validateStringNotNullOrEmpty(checker, PARAMNAME_CLIENT_ID); validateStringNotNullOrEmpty(checker, PARAMNAME_USER_ID); validateStringNotNullOrEmpty(checker, PARAMNAME_PASSWORD); validateStringNotNullOrEmpty(checker, PARAMNAME_APP_CONFIG_NAME); validateStringNotNullOrEmpty(checker, PARAMNAME_USER_PROP_NAME); validateStringNotNullOrEmpty(checker, PARAMNAME_PASSWORD_PROP_NAME); // Verify existence of user credentials if they are specified via application configuration validateAppConfiguration(checker); validateNumber(checker, PARAMNAME_COMMAND_TIMEOUT, 0, Long.MAX_VALUE); validateNumber(checker, PARAMNAME_KEEP_ALIVE, 0, Integer.MAX_VALUE); } private static void validateAppConfiguration(OperatorContextChecker checker) { if ((checker.getOperatorContext().getParameterNames().contains(PARAMNAME_APP_CONFIG_NAME))) { String appConfigName = checker.getOperatorContext().getParameterValues(PARAMNAME_APP_CONFIG_NAME).get(0); String userPropName = checker.getOperatorContext().getParameterValues(PARAMNAME_USER_PROP_NAME).get(0); String passwordPropName = checker.getOperatorContext().getParameterValues(PARAMNAME_PASSWORD_PROP_NAME).get(0); PropertyProvider provider = new PropertyProvider(checker.getOperatorContext().getPE(), appConfigName); String userName = provider.getProperty(userPropName); String password = provider.getProperty(passwordPropName); if(userName == null || userName.trim().length() == 0) { checker.setInvalidContext(Messages.getString("PROPERTY_NOT_FOUND_IN_APP_CONFIG"), new Object[] {userPropName, appConfigName}); //$NON-NLS-1$ } if(password == null || password.trim().length() == 0) { checker.setInvalidContext(Messages.getString("PROPERTY_NOT_FOUND_IN_APP_CONFIG"), new Object[] {passwordPropName, appConfigName}); //$NON-NLS-1$ } } } public String getAppConfigName() { return appConfigName; } @Parameter(name = PARAMNAME_APP_CONFIG_NAME, description = SPLDocConstants.MQTT_PARAM_APP_CONFIG_NAME_DESC, optional = true) public void setAppConfigName(String appConfigName) { this.appConfigName = appConfigName; } public String getUserPropName() { return userPropName; } @Parameter(name = PARAMNAME_USER_PROP_NAME, description = SPLDocConstants.MQTT_PARAM_USER_PROP_NAME_DESC, optional = true) public void setUserPropName(String userPropName) { this.userPropName = userPropName; } public String getPasswordPropName() { return passwordPropName; } @Parameter(name = PARAMNAME_PASSWORD_PROP_NAME, description = SPLDocConstants.MQTT_PARAM_PASSWORD_PROP_NAME_DESC, optional = true) public void setPasswordPropName(String passwordPropName) { this.passwordPropName = passwordPropName; } @Parameter(name = PARAMNAME_SERVER_URI, description = SPLDocConstants.MQTTSRC_PARAM_SERVERIURI_DESC, optional = true) public void setServerUri(String serverUri) { this.serverUri = serverUri; } public String getServerUri() { return serverUri; } public String getConnection() { return connection; } @Parameter(name = PARAMNAME_CONNECTION, description = SPLDocConstants.PARAM_CONNECTION_DESC, optional = true) public void setConnection(String connection) { this.connection = connection; } public String getConnectionDocument() { return connectionDocument; } @Parameter(name = PARAMNAME_CONNDOC, description = SPLDocConstants.PARAM_CONNDOC_DESC, optional = true) public void setConnectionDocument(String connectionDocument) { this.connectionDocument = connectionDocument; } protected void initFromConnectionDocument() throws Exception { // serverUri and connection - at least once must exist // only read from connection document if connection is specified // only initialize from connection document if parameter is not // already initialized // if serverUri is null, read connection document if (getConnection() != null) { ConnectionDocumentHelper helper = new ConnectionDocumentHelper(); String connDoc = getConnectionDocument(); // if connection document is not specified, default to // ../etc/connections.xml if (connDoc == null) { File appDir = getOperatorContext().getPE() .getApplicationDirectory(); connDoc = appDir.getAbsolutePath() + "/etc/connections.xml"; //$NON-NLS-1$ } // convert from relative path to absolute path is necessary if (!connDoc.startsWith("/")) //$NON-NLS-1$ { File appDir = getOperatorContext().getPE() .getApplicationDirectory(); connDoc = appDir.getAbsolutePath() + "/" + connDoc; //$NON-NLS-1$ } try { helper.parseAndValidateConnectionDocument(connDoc); ConnectionSpecification connectionSpecification = helper .getConnectionSpecification(getConnection()); if (connectionSpecification != null) { setServerUri(connectionSpecification.getServerUri()); String trustStore = connectionSpecification.getTrustStore(); String trustStorePw = connectionSpecification .getTrustStorePassword(); String keyStore = connectionSpecification.getKeyStore(); String keyStorePw = connectionSpecification .getKeyStorePassword(); String userID = connectionSpecification.getUserID(); String password = connectionSpecification.getPassword(); int keepAliveInterval = connectionSpecification.getKeepAliveInterval(); long commandTimeout = connectionSpecification.getCommandTimeout(); if (getTrustStore() == null) setTrustStore(trustStore); if (getKeyStore() == null) setKeyStore(keyStore); if (getKeyStorePassword() == null) setKeyStorePassword(keyStorePw); if (getTrustStorePassword() == null) setTrustStorePassword(trustStorePw); if (getUserID() == null) setUserID(userID); if (getPassword() == null) setPassword(password); if(getKeepAliveInterval() == IMqttConstants.UNINITIALIZED_KEEP_ALIVE_INTERVAL && keepAliveInterval > IMqttConstants.UNINITIALIZED_KEEP_ALIVE_INTERVAL ) setKeepAliveInterval(keepAliveInterval); if(this.getCommandTimeout() == IMqttConstants.UNINITIALIZED_COMMAND_TIMEOUT && commandTimeout > IMqttConstants.UNINITIALIZED_COMMAND_TIMEOUT) this.setCommandTimeout(commandTimeout); } else { TRACE.log( TraceLevel.ERROR, Messages.getString("CANNOT_FIND_CONNECTION_IN_CONNECTION_DOC_WITH_NAME", getConnection())); //$NON-NLS-1$ LOG.log(LogLevel.ERROR, Messages.getString("CANNOT_FIND_CONNECTION_IN_CONNECTION_DOC_WITH_NAME", getConnection())); //$NON-NLS-1$ throw new RuntimeException( Messages.getString("CANNOT_FIND_CONNECTION_IN_CONNECTION_DOC")); //$NON-NLS-1$ } } catch (SAXException e) { TRACE.log(LogLevel.ERROR, Messages.getString("CONNECTION_DOC_MALFORMED")); //$NON-NLS-1$ throw e; } catch (IOException e) { TRACE.log(LogLevel.ERROR, Messages.getString("CONNECTION_DOC_MALFORMED")); //$NON-NLS-1$ throw e; } } } protected void setupSslProperties(MqttClientWrapper client) { String trustStore = getTrustStore(); String trustStorePw = getTrustStorePassword(); String keyStore = getKeyStore(); String keyStorePw = getKeyStorePassword(); if (trustStore != null || keyStore != null) { Properties sslProperties = new Properties(); if (trustStore != null) { sslProperties.setProperty(IMqttConstants.SSL_TRUST_STORE, trustStore); } if (keyStore != null) { sslProperties.setProperty(IMqttConstants.SSL_KEY_STORE, keyStore); } if (keyStorePw != null) { sslProperties.setProperty( IMqttConstants.SSL_KEY_STORE_PASSWORD, keyStorePw); } if (trustStorePw != null) { sslProperties.setProperty( IMqttConstants.SSK_TRUST_STORE_PASSWORD, trustStorePw); } sslProperties.setProperty(IMqttConstants.SSL_PROTOCOL, getSslProtocol()); client.setSslProperties(sslProperties); } } public String getTrustStore() { return toAbsolute(trustStore); } @Parameter(name = PARAMNAME_TRUST_STORE, optional = true, description = SPLDocConstants.PARAM_TRUSTORE_DESC) public void setTrustStore(String trustStore) { this.trustStore = trustStore; } public String getKeyStore() { return toAbsolute(keyStore); } @Parameter(name = PARAMNAME_KEY_STORE, optional = true, description = SPLDocConstants.PARAM_KEYSTORE_DESC) public void setKeyStore(String keyStore) { this.keyStore = keyStore; } public String getKeyStorePassword() { return keyStorePassword; } @Parameter(name = PARAMNAME_KEY_STORE_PASSWORD, optional = true, description = SPLDocConstants.PARAM_KEYSTORE_PW_DESC) public void setKeyStorePassword(String keyStorePassword) { this.keyStorePassword = keyStorePassword; } public String getTrustStorePassword() { return trustStorePassword; } @Parameter(name = PARAMNAME_TRUST_STORE_PASSWORD, optional = true, description = SPLDocConstants.PARAM_TRUSTORE_PW_DESC) public void setTrustStorePassword(String trustStorePassword) { this.trustStorePassword = trustStorePassword; } public String getSslProtocol() { return sslProtocol; } @Parameter(name = PARAMNAME_SSL_PROTOCOL, optional = true, description = SPLDocConstants.PARAM_SSL_PROTOCOL_DESC) public void setSslProtocol(String sslProtocol) { this.sslProtocol = sslProtocol; } public String getClientID() { return clientID; } @Parameter(name=PARAMNAME_CLIENT_ID, description = SPLDocConstants.MQTT_PARAM_CLIENT_ID_DESC, optional=true) public void setClientID(String clientID) { this.clientID = clientID; } public String getUserID() { return userID; } @Parameter(name=PARAMNAME_USER_ID, description = SPLDocConstants.MQTT_PARAM_USER_ID_DESC, optional=true) public void setUserID(String userID) { this.userID = userID; } public String getPassword() { return password; } @Parameter(name=PARAMNAME_PASSWORD, description = SPLDocConstants.MQTT_PARAM_PASSWORD_DESC, optional=true) public void setPassword(String password) { this.password = password; } public long getCommandTimeout() { return commandTimeout; } @Parameter(name=PARAMNAME_COMMAND_TIMEOUT, description = SPLDocConstants.MQTT_PARAM_COMMAND_TIMEOUT_DESC, optional=true) public void setCommandTimeout(long commandTimeout) { this.commandTimeout = commandTimeout; } public int getKeepAliveInterval() { return keepAliveInterval; } @Parameter(name=PARAMNAME_KEEP_ALIVE, description = SPLDocConstants.MQTT_PARAM_KEEP_ALIVE_INTERVAL_DESC, optional=true) public void setKeepAliveInterval(int keepAliveInterval) { this.keepAliveInterval = keepAliveInterval; } public String getDataAttributeName() { return dataAttributeName; } @Parameter(name=PARAMNAME_DATA_ATTRIBUTE_NAME, description = SPLDocConstants.MQTT_PARAM_DATA_ATTRIBUTE_DESC, optional=true) public void setDataAttributeName(String dataAttributeName) { this.dataAttributeName = dataAttributeName; } protected static void validateStringNotNullOrEmpty(OperatorContextChecker checker, String parameterName) { if ((checker.getOperatorContext().getParameterNames().contains(parameterName))) { String value = checker.getOperatorContext().getParameterValues(parameterName).get(0); if(value == null || value.trim().length() == 0) { checker.setInvalidContext(Messages.getString("PARAM_MUST_BE_NON_EMPTY_STRING"), new Object[] {parameterName}); //$NON-NLS-1$ } } } protected static void validateNumber(OperatorContextChecker checker, String parameterName, long min, long max) { try { List<String> paramValues = checker.getOperatorContext() .getParameterValues(parameterName); for (String strVal : paramValues) { Long longVal = Long.valueOf(strVal); if (longVal.longValue() > max || longVal.longValue() < min) { checker.setInvalidContext( Messages.getString("NOT_IN_RANGE"), //$NON-NLS-1$ new Object[] { parameterName, min, max }); } } } catch (NumberFormatException e) { checker.setInvalidContext( Messages.getString("NOT_A_NUMBER"), //$NON-NLS-1$ new Object[] { parameterName }); } } protected static void validateSchemaForErrorOutputPort( OperatorContextChecker checker, StreamingOutput<OutputTuple> errorPort) { if (errorPort != null) { Set<String> parameterNames = checker.getOperatorContext() .getParameterNames(); // if error port is present, check that it has the right schema // if there is not output parameter for error message, make sure // it's only // one attribute of ustring or rstring if (!parameterNames.contains(PARAMNAME_ERROR_OUT_ATTR_NAME)) { StreamSchema streamSchema = errorPort.getStreamSchema(); int attrCount = streamSchema.getAttributeCount(); if (attrCount > 1) { checker.setInvalidContext( Messages.getString("OUTPUT_PORT_EXPECTED_TO_HAVE_SINGLE_ATTRIB"), new Object[] {}); //$NON-NLS-1$ } checker.checkAttributeType(streamSchema.getAttribute(0), MetaType.RSTRING, MetaType.USTRING); } } } protected String toAbsolute(String path) { if (path == null) return null; File fPath = new File(path); if (!fPath.isAbsolute()) { File appDir = getOperatorContext().getPE().getApplicationDirectory(); fPath = new File(appDir, fPath.getPath()); } return fPath.getAbsolutePath(); } /** * @return error output port if present, null if not specified */ abstract protected StreamingOutput<OutputTuple> getErrorOutputPort(); protected void submitToErrorPort(String errorMsg, ConsistentRegionContext crContext) { StreamingOutput<OutputTuple> errorOutputPort = getErrorOutputPort(); if (errorOutputPort != null) { OutputTuple errorTuple = errorOutputPort.newTuple(); errorTuple.setString(0, errorMsg); try { if(crContext != null) { crContext.acquirePermit(); } errorOutputPort.submit(errorTuple); } catch (Exception e) { TRACE.log(TraceLevel.ERROR, Messages.getString("CANNOT_SUBMIT_ERROR_TUPLE"), e); //$NON-NLS-1$ } finally { if(crContext != null) { crContext.releasePermit(); } } } } }