/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015
*/
package com.ibm.streamsx.topology.internal.core;
import static com.ibm.streamsx.topology.internal.functional.ops.FunctionFunctor.FUNCTIONAL_LOGIC_PARAM;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.ibm.streams.operator.Operator;
import com.ibm.streams.operator.StreamSchema;
import com.ibm.streams.operator.Tuple;
import com.ibm.streamsx.topology.TStream;
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.BOutput;
import com.ibm.streamsx.topology.builder.BOutputPort;
import com.ibm.streamsx.topology.internal.functional.ObjectUtils;
import com.ibm.streamsx.topology.internal.spljava.Schemas;
/**
* Maintains the core core for building a topology of Java streams.
*
*/
public class JavaFunctional {
/**
* Add an operator that executes a Java function (as an instance of a class)
* for its logic.
*/
public static BOperatorInvocation addFunctionalOperator(TopologyElement te,
Class<? extends Operator> opClass, Serializable logic) {
verifySerializable(logic);
String logicString = ObjectUtils.serializeLogic(logic);
BOperatorInvocation bop = te.builder().addOperator(opClass,
Collections.singletonMap(FUNCTIONAL_LOGIC_PARAM, logicString));
addDependency(te, bop, logic);
return bop;
}
public static BOperatorInvocation addFunctionalOperator(TopologyElement te,
String name, Class<? extends Operator> opClass, Serializable logic) {
return addFunctionalOperator(te, name, opClass, logic, null);
}
public static BOperatorInvocation addFunctionalOperator(TopologyElement te,
String name, Class<? extends Operator> opClass, Serializable logic,
Map<String,Object> params) {
if (params == null)
params = new HashMap<>();
verifySerializable(logic);
String logicString = ObjectUtils.serializeLogic(logic);
params.put(FUNCTIONAL_LOGIC_PARAM, logicString);
BOperatorInvocation bop = te.builder().addOperator(name, opClass,
params);
addDependency(te, bop, logic);
return bop;
}
private static final Set<Class<?>> VIEWABLE_TYPES = new HashSet<>();
static {
VIEWABLE_TYPES.add(Tuple.class);
VIEWABLE_TYPES.add(String.class);
}
/**
* Add an output port to an operator that uses a Java class as its object
* type.
*/
public static <T> TStream<T> addJavaOutput(TopologyElement te,
BOperatorInvocation bop, Type tupleType) {
StreamSchema mappingSchema = Schemas.getSPLMappingSchema(tupleType);
BOutputPort bstream = bop.addOutput(mappingSchema);
return getJavaTStream(te, bop, bstream, tupleType);
}
/**
* Create a Java stream on top of an output port.
* Multiple streams can be added to an output port.
*/
public static <T> TStream<T> getJavaTStream(TopologyElement te,
BOperatorInvocation bop, BOutputPort bstream, Type tupleType) {
if (tupleType != null)
bstream.json().put("type.native", tupleType.toString()); // Java 8 should use getTypeName
addDependency(te, bop, tupleType);
// If the stream is just a Java object as a blob
// then don't allow them to be viewed.
if (!VIEWABLE_TYPES.contains(tupleType)) {
bop.addConfig("streamViewability", false);
}
return new StreamImpl<T>(te, bstream, tupleType);
}
/**
* Connect a Java functional operator to output from an output creating an
* input port if required. Ensures the operator has its dependencies on the
* tuple type added.
*/
public static BInputPort connectTo(TopologyElement te, BOutput output,
Type tupleType, BOperatorInvocation receivingBop,
BInputPort input) {
addDependency(te, receivingBop, tupleType);
return receivingBop.inputFrom(output, input);
}
/**
* Add a third-party dependency to all eligible operators
*/
public static void addJarDependency(TopologyElement te,
String location) {
te.topology().getDependencyResolver().addJarDependency(location);
}
/**
* Add a third-party dependency to all eligible operators.
*/
public static void addClassDependency(TopologyElement te,
Class<?> clazz) {
te.topology().getDependencyResolver().addClassDependency(clazz);
}
/**
* Add a dependency for the operator to a Java tuple type.
*/
public static void addDependency(TopologyElement te,
BOperatorInvocation bop, Type tupleType) {
if (Tuple.class.equals(tupleType))
return;
if (tupleType instanceof Class)
te.topology().getDependencyResolver().addJarDependency(bop, (Class<?>) tupleType);
if (tupleType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) tupleType;
addDependency(te, bop, pt.getRawType());
for (Type typeArg : pt.getActualTypeArguments())
addDependency(te, bop, typeArg);
}
}
/**
* Add a dependency for the operator to its functional logic.
*/
private static void addDependency(TopologyElement te,
BOperatorInvocation bop, Object function) {
te.topology().getDependencyResolver().addJarDependency(bop, function);
}
/**
* Simple check of the fields in the serializable logic
* to ensure that all non-transient field are serializable.
* @param logic
*/
private static void verifySerializable(Serializable logic) {
final Class<?> logicClass = logic.getClass();
for (Field f : logicClass.getDeclaredFields()) {
final int modifiers = f.getModifiers();
if (Modifier.isStatic(modifiers))
continue;
if (Modifier.isTransient(modifiers))
continue;
// We can't check for regular fields as the
// declaration might be valid as Object, but only
// contain serializable objects at runtime.
if (!f.isSynthetic())
continue;
Class<?> fieldClass = f.getType();
if (fieldClass.isPrimitive())
continue;
if (!Serializable.class.isAssignableFrom(fieldClass)) {
throw new IllegalArgumentException(
"Functional logic argument " + logic + " contains a non-serializable field:"
+ f.getName() + " ,"
+ "ensure anonymous classes are declared in a static context.");
}
}
}
}