/* # Licensed Materials - Property of IBM # Copyright IBM Corp. 2015 */ package com.ibm.streamsx.topology.spl; import static com.ibm.streamsx.topology.generator.operator.OpProperties.LANGUAGE; import static com.ibm.streamsx.topology.generator.operator.OpProperties.LANGUAGE_JAVA; import static com.ibm.streamsx.topology.generator.operator.OpProperties.MODEL; import static com.ibm.streamsx.topology.generator.operator.OpProperties.MODEL_SPL; import static com.ibm.streamsx.topology.internal.core.InternalProperties.TOOLKITS; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.xml.bind.JAXBException; import com.ibm.json.java.JSONArray; import com.ibm.json.java.JSONObject; import com.ibm.streams.operator.StreamSchema; import com.ibm.streams.operator.Type.MetaType; import com.ibm.streamsx.topology.TSink; import com.ibm.streamsx.topology.Topology; import com.ibm.streamsx.topology.TopologyElement; import com.ibm.streamsx.topology.builder.BInputPort; import com.ibm.streamsx.topology.builder.BOperatorInvocation; import com.ibm.streamsx.topology.builder.BOutputPort; import com.ibm.streamsx.topology.function.Supplier; import com.ibm.streamsx.topology.internal.context.remote.TkInfo; import com.ibm.streamsx.topology.internal.core.SourceInfo; import com.ibm.streamsx.topology.internal.core.SubmissionParameter; import com.ibm.streamsx.topology.internal.core.TSinkImpl; import com.ibm.streamsx.topology.internal.toolkit.info.IdentityType; import com.ibm.streamsx.topology.internal.toolkit.info.ToolkitInfoModelType; /** * Integration between Java topologies and SPL operator invocations. If the SPL * operator to be invoked is an SPL Java primitive operator then the methods of * {@link JavaPrimitive} must be used. * <BR> * An operator's kind is its namespace followed by the * operator name separated with '{@code ::}'. For example * the {@code FileSource} operator from the SPL Standard toolkit must be specified * as {@code spl.adapter::FileSource}. * <p> * When necessary use {@code createValue(T, MetaType)} * to create parameter values for SPL types. * For example: * <pre>{@code * Map<String,Object> params = ... * params.put("aInt32", 13); * params.put("aUInt32", SPL.createValue(13, MetaType.UINT32)); * params.put("aRString", "some string value"); * params.put("aUString", SPL.createValue("some ustring value", MetaType.USTRING)); * ... = SPLPrimitive.invokeOperator(..., params); * }</pre> * <p> * In addition to the usual Java types used for operator parameter values, * a {@code Supplier<T>} parameter value may be specified. * Submission time parameters are passed in this manner. * See {@link #createSubmissionParameter(Topology, String, Object, boolean)}. * For example: * <pre>{@code * Map<String,Object> params = ... * params.put("aInt32", topology.createSubmissionParameter("int32Param", 13); * params.put("aUInt32", SPL.createSubmissionParameter(topology, "uint32Param", SPL.createValue(13, MetaType.UINT32), true); * params.put("aRString", topology.createSubmissionParameter("rstrParam", "some string value"); * params.put("aUString", SPL.createSubmissionParameter(topology, "ustrParam", SPL.createValue("some ustring value", MetaType.USTRING), true); * params.put("aUInt8", SPL.createSubmissionParameter(topology, "uint8Param", SPL.createValue((byte)13, MetaType.UINT8), true); * ... = SPLPrimitive.invokeOperator(..., params); * }</pre> */ public class SPL { /** * Create a SPL value wrapper object for the * specified SPL {@code MetaType}. * <p> * Use of this is required to construct a SPL operator parameter value * whose SPL type is not implied from simple Java type. e.g., * a {@code String} value is interpreted as a SPL {@code rstring}, * and {@code Byte,Short,Integer,Long} are interpreted as SPL signed integers. * @param value the value to wrap * @param metaType the SPL meta type * @return the wrapper object * @throws IllegalArgumentException if value is null or its class is * not appropriate for {@code metaType} */ public static <T> Object createValue(T value, MetaType metaType) { return new SPLValue<T>(value, metaType).toJSON(); } private static SPLValue<?> createSPLValue(Object paramValue) { if (paramValue instanceof JSONObject) { SPLValue<?> splValue = SPLValue.fromJSON((JSONObject) paramValue); return splValue; } throw new IllegalArgumentException("param is not from createValue()"); } /** * Create a submission parameter with or without a default value. * <p> * Use of this is required to construct a submission parameter for * a SPL operator parameter whose SPL type requires the use of * {@code createValue(Object, MetaType)}. * <p> * See {@link Topology#createSubmissionParameter(String, Class)} for * general information about submission parameters. * * @param top the topology * @param name the submission parameter name * @param paramValue a value from {@code createValue()} * @param withDefault true to create a submission parameter with * a default value, false to create one without a default value * When false, the paramValue's wrapped value's value in ignored. * @return the {@code Supplier<T>} for the submission parameter * @throws IllegalArgumentException if {@code paramValue} is not a * value from {@code createValue()}. */ public static <T> Supplier<T> createSubmissionParameter(Topology top, String name, Object paramValue, boolean withDefault) { SPLValue<?> splValue = createSPLValue(paramValue); SubmissionParameter<T> sp = new SubmissionParameter<T>(top, name, splValue.toJSON(), withDefault); top.builder().createSubmissionParameter(name, sp.toJSON()); return sp; } /** * Create an SPLStream from the invocation of an SPL operator * that consumes a stream. * * @param kind * SPL kind of the operator to be invoked. * @param input * Stream that will be connected to the only input port of the * operator * @param outputSchema * SPL schema of the operator's only output port. * @param params * Parameters for the SPL operator, ignored if it is null. * @return SPLStream the represents the output of the operator. */ public static SPLStream invokeOperator(String kind, SPLInput input, StreamSchema outputSchema, Map<String, ? extends Object> params) { return invokeOperator(opNameFromKind(kind), kind, input, outputSchema, params); } /** * Create an SPLStream from the invocation of an SPL operator * that consumes a stream and produces a stream. * * @param name Name for the operator invocation. * @param kind * SPL kind of the operator to be invoked. * @param input * Stream that will be connected to the only input port of the * operator * @param outputSchema * SPL schema of the operator's only output port. * @param params * Parameters for the SPL operator, ignored if it is null. * @return SPLStream the represents the output of the operator. */ public static SPLStream invokeOperator(String name, String kind, SPLInput input, StreamSchema outputSchema, Map<String, ? extends Object> params) { BOperatorInvocation op = input.builder().addSPLOperator(name, kind, params); SourceInfo.setSourceInfo(op, SPL.class); SPL.connectInputToOperator(input, op); BOutputPort stream = op.addOutput(outputSchema); return new SPLStreamImpl(input, stream); } /** * Invoke an SPL operator with an arbitrary number * of input and output ports. * <BR> * Each input stream or window in {@code inputs} results in * a input port for the operator with the input port index * matching the position of the input in {@code inputs}. * If {@code inputs} is {@code null} or empty then the operator will not * have any input ports. * <BR> * Each SPL schema in {@code outputSchemas} an output port * for the operator with the output port index * matching the position of the schema in {@code outputSchemas}. * If {@code outputSchemas} is {@code null} or empty then the operator will not * have any output ports. * * @param te Reference to Topology the operator will be in. * @param name Name for the operator invocation. * @param kind * SPL kind of the operator to be invoked. * @param inputs Input streams to be connected to the operator. May be {@code null} if no input streams are required. * @param outputSchemas Schemas of the output streams. May be {@code null} if no output streams are required. * @param params * Parameters for the SPL Java Primitive operator, ignored if {@code null}. * @return List of {@code SPLStream} instances that represent the outputs of the operator. */ public static List<SPLStream> invokeOperator( TopologyElement te, String name, String kind, List<? extends SPLInput> inputs, List<StreamSchema> outputSchemas, Map<String, ? extends Object> params) { BOperatorInvocation op = te.builder().addSPLOperator(name, kind, params); SourceInfo.setSourceInfo(op, SPL.class); if (inputs != null && !inputs.isEmpty()) { for (SPLInput input : inputs) SPL.connectInputToOperator(input, op); } if (outputSchemas == null || outputSchemas.isEmpty()) return Collections.emptyList(); List<SPLStream> streams = new ArrayList<>(outputSchemas.size()); for (StreamSchema outputSchema : outputSchemas) streams.add(new SPLStreamImpl(te, op.addOutput(outputSchema))); return streams; } /** * Connect an input to an operator invocation, including making the input * windowed if it is an instance of SPLWindow. */ static BInputPort connectInputToOperator(SPLInput input, BOperatorInvocation op) { BInputPort inputPort = input.getStream().connectTo(op, false, null); if (input instanceof SPLWindow) { ((SPLWindowImpl) input).windowInput(inputPort); } return inputPort; } /** * Invocation an SPL operator that consumes a Stream. * * @param kind * SPL kind of the operator to be invoked. * @param input * Stream that will be connected to the only input port of the * operator * @param params * Parameters for the SPL operator, ignored if it is null. * @return the sink element */ public static TSink invokeSink(String kind, SPLInput input, Map<String, ? extends Object> params) { return invokeSink(opNameFromKind(kind), kind, input, params); } /** * Invocation an SPL operator that consumes a Stream. * * @param name * Name of the operator * @param kind * SPL kind of the operator to be invoked. * @param input * Stream that will be connected to the only input port of the * operator * @param params * Parameters for the SPL operator, ignored if it is null. * @return the sink element */ public static TSink invokeSink(String name, String kind, SPLInput input, Map<String, ? extends Object> params) { BOperatorInvocation op = input.builder().addSPLOperator(name, kind, params); SourceInfo.setSourceInfo(op, SPL.class); SPL.connectInputToOperator(input, op); return new TSinkImpl(input.topology(), op); } private static String opNameFromKind(String kind) { String opName; if (kind.contains("::")) opName = kind.substring(kind.lastIndexOf(':') + 1); else opName = kind; return opName; } /** * Invocation an SPL source operator to produce a Stream. * * @param te * Reference to Topology the operator will be in. * @param kind * SPL kind of the operator to be invoked. * @param params * Parameters for the SPL operator. * @param schema * Schema of the output port. * @return SPLStream the represents the output of the operator. */ public static SPLStream invokeSource(TopologyElement te, String kind, Map<String, Object> params, StreamSchema schema) { BOperatorInvocation splSource = te.builder().addSPLOperator( opNameFromKind(kind), kind, params); SourceInfo.setSourceInfo(splSource, SPL.class); BOutputPort stream = splSource.addOutput(schema); return new SPLStreamImpl(te, stream); } /** * Add a dependency on an SPL toolkit. * @param te Element within the topology. * @param toolkitRoot Root directory of the toolkit. * @throws IOException {@code toolkitRoot} is not a valid path. */ public static void addToolkit(TopologyElement te, File toolkitRoot) throws IOException { JSONObject tkinfo = newToolkitDepInfo(te); tkinfo.put("root", toolkitRoot.getCanonicalPath()); ToolkitInfoModelType infoModel; try { infoModel = TkInfo.getToolkitInfo(toolkitRoot); } catch (JAXBException e) { throw new IOException(e); } if (infoModel != null) { IdentityType tkIdentity = infoModel.getIdentity(); tkinfo.put("name", tkIdentity.getName()); tkinfo.put("version", tkIdentity.getVersion()); } } private static JSONObject newToolkitDepInfo(TopologyElement te) { JSONArray tks = (JSONArray) te.topology().getConfig().get(TOOLKITS); if (tks == null) { te.topology().getConfig().put(TOOLKITS, tks = new JSONArray()); } JSONObject tkinfo = new JSONObject(); tks.add(tkinfo); return tkinfo; } /** * Add a logical dependency on an SPL toolkit. * @param te Element within the topology. * @param name Name of the toolkit. * @param version Version dependency string. */ public static void addToolkitDependency(TopologyElement te, String name, String version) { JSONObject tkinfo = newToolkitDepInfo(te); tkinfo.put("name", name); tkinfo.put("version", version); } /** * Internal method. * <BR> * Not intended to be called by applications, may be removed at any time. * <br> * This is in lieu of a "kind" based JavaPrimitive.invoke*() methods. * @param op the operator invocation * @param kind SPL kind of the operator to be invoked. * @param className the Java primitive operator's class name. */ public static void tagOpAsJavaPrimitive(BOperatorInvocation op, String kind, String className) { op.json().put(MODEL, MODEL_SPL); op.json().put(LANGUAGE, LANGUAGE_JAVA); op.json().put("kind", kind); op.json().put("kind.javaclass", className); } }