/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015
*/
package com.ibm.streamsx.topology.internal.functional.ops;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.ibm.json.java.JSONObject;
import com.ibm.streams.operator.OperatorContext;
import com.ibm.streams.operator.Type.MetaType;
import com.ibm.streamsx.topology.builder.GraphBuilder;
import com.ibm.streamsx.topology.context.ContextProperties;
import com.ibm.streamsx.topology.generator.functional.FunctionalOpProperties;
import com.ibm.streamsx.topology.generator.spl.SubmissionTimeValue;
import static com.ibm.streamsx.topology.builder.JParamTypes.TYPE_SUBMISSION_PARAMETER;
/**
* A manager for making a submission parameter Supplier usable
* from functional logic.
* <p>
* The manager maintains a collection of values for every submission parameter
* in the graph.
* <p>
* The submission parameter's {@code Supplier.get()} implementation learns
* the actual submission parameter value by calling
* {@link #getValue(String, MetaType)}.
* <p>
* The strategy for the manager learning the submission parameter values
* is as follows.
* <p>
* For DISTRIBUTED and STANDALONE (SPL code) submission parameter
* values are conveyed via a NAME_SUBMISSION_PARAMS functional operator
* parameter. The parameter's value consists of all the submission parameters
* created in the graph. All functional operators have the same
* value for this parameter in their OperatorContext. Functional operators
* call {@link #initialize(OperatorContext)}.
* <p>
* For EMBEDDED the functional operator's operator context does not have
* a NAME_SUBMISSION_PARAMS parameter. Instead the EMBEDDED
* {@code StreamsContext.submit()} calls
* {@link #initializeEmbedded(GraphBuilder, Map)}.
* The collection of all submission parameters, with optional default values,
* are learned from the graph and actual values are learned from the
* submit configuration's {@link ContextProperties#SUBMISSION_PARAMS} value.
*/
public class SubmissionParameterManager {
private static abstract class Factory {
abstract Object valueOf(String s);
}
private static Map<MetaType,Factory> factories = new HashMap<>();
static {
// TODO more
factories.put(MetaType.RSTRING, new Factory() {
Object valueOf(String s) { return s; } });
factories.put(MetaType.USTRING, new Factory() {
Object valueOf(String s) { return s; } });
factories.put(MetaType.INT8, new Factory() {
Object valueOf(String s) { return Byte.valueOf(s); } });
factories.put(MetaType.INT16, new Factory() {
Object valueOf(String s) { return Short.valueOf(s); } });
factories.put(MetaType.INT32, new Factory() {
Object valueOf(String s) { return Integer.valueOf(s); } });
factories.put(MetaType.INT64, new Factory() {
Object valueOf(String s) { return Long.valueOf(s); } });
factories.put(MetaType.UINT8, new Factory() {
Object valueOf(String s) { return Byte.valueOf(s); } });
factories.put(MetaType.UINT16, new Factory() {
Object valueOf(String s) { return Short.valueOf(s); } });
factories.put(MetaType.UINT32, new Factory() {
Object valueOf(String s) { return Integer.valueOf(s); } });
factories.put(MetaType.UINT64, new Factory() {
Object valueOf(String s) { return Long.valueOf(s); } });
factories.put(MetaType.FLOAT32, new Factory() {
Object valueOf(String s) { return Float.valueOf(s); } });
factories.put(MetaType.FLOAT64, new Factory() {
Object valueOf(String s) { return Double.valueOf(s); } });
}
private static final Map<String,String> UNINIT_MAP = Collections.emptyMap();
/** map of topology's <spOpParamName, strVal> */
private static volatile Map<String,String> params = UNINIT_MAP;
/** The name of the functional operator's actual SPL parameter
* for the submission parameters names */
public static final String NAME_SUBMISSION_PARAM_NAMES = FunctionalOpProperties.NAME_SUBMISSION_PARAM_NAMES;
/** The name of the functional operator's actual SPL parameter
* for the submission parameters values */
public static final String NAME_SUBMISSION_PARAM_VALUES = FunctionalOpProperties.NAME_SUBMISSION_PARAM_VALUES;
/**
* Initialize submission parameter value information
* from operator context information.
* @param context the operator context
*/
public synchronized static void initialize(OperatorContext context) {
// The TYPE_SPL_SUBMISSION_PARAMS parameter value is the same for
// all operator contexts.
if (params == UNINIT_MAP) {
List<String> names = context.getParameterValues(NAME_SUBMISSION_PARAM_NAMES);
if (names != null && !names.isEmpty()) {
List<String> values = context.getParameterValues(NAME_SUBMISSION_PARAM_VALUES);
Map<String,String> map = new HashMap<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
String value = values.get(i);
map.put(name, value);
}
params = map;
// System.out.println("SPM.initialize() " + params);
}
}
}
/**
* Initialize EMBEDDED submission parameter value information
* from topology's graph and StreamsContext.submit() config.
* @param builder the topology's builder
* @param config StreamsContext.submit() configuration
*/
public synchronized static void initializeEmbedded(GraphBuilder builder,
Map<String, Object> config) {
// N.B. in an embedded context, within a single JVM/classloader,
// multiple topologies can be executed serially as well as concurrently.
// TODO handle the concurrent case - e.g., with per-topology-submit
// managers.
// create map of all submission params used by the topology
// and the parameter's string value (initially null for no default)
Map<String,String> allsp = new HashMap<>(); // spName, spStrVal
JSONObject graph = builder.json();
JSONObject gparams = (JSONObject) graph.get("parameters");
if (gparams != null) {
for (Object o : gparams.keySet()) {
JSONObject param = (JSONObject) gparams.get((String)o);
if (TYPE_SUBMISSION_PARAMETER.equals(param.get("type"))) {
JSONObject spval = (JSONObject) param.get("value");
Object val = spval.get("defaultValue");
if (val != null)
val = val.toString();
allsp.put((String)spval.get("name"), (String)val);
}
}
}
if (allsp.isEmpty())
return;
// update values from config
@SuppressWarnings("unchecked")
Map<String,Object> spValues =
(Map<String, Object>) config.get(ContextProperties.SUBMISSION_PARAMS);
for (String spName : spValues.keySet()) {
if (allsp.containsKey(spName)) {
Object val = spValues.get(spName);
if (val != null)
val = val.toString();
allsp.put(spName, (String)val);
}
}
// failure if any are still undefined
for (String spName : allsp.keySet()) {
if (allsp.get(spName) == null)
throw new IllegalStateException("Submission parameter \""+spName+"\" requires a value but none has been supplied");
}
// good to go. initialize params
params = new HashMap<>();
for (Map.Entry<String, String> e : allsp.entrySet()) {
params.put(e.getKey(), e.getValue());
}
// System.out.println("SPM.initializeEmbedded() " + params);
}
/**
* Get the submission parameter's value.
* <p>
* The value will be null while composing the topology. It will be
* the actual submission time value when the topology is running.
* @param spName submission parameter name
* @param metaType parameter's metaType
* @return the parameter's value appropriately typed for metaType.
* may be null.
*/
public static Object getValue(String spName, MetaType metaType) {
String value = params.get(spName);
if (value == null) {
// System.out.println("SPM.getValue "+spName+" "+metaType+ " params " + params);
throw new IllegalArgumentException("Unexpected submission parameter name " + spName);
}
Factory factory = factories.get(metaType);
if (factory == null)
throw new IllegalArgumentException("Unhandled MetaType " + metaType);
return factory.valueOf(value);
}
}