/*
# Licensed Materials - Property of IBM
# Copyright IBM Corp. 2015
*/
package com.ibm.streamsx.topology.internal.core;
import static com.ibm.streamsx.topology.builder.BVirtualMarker.ISOLATE;
import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT;
import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT_EXPLICIT_COLOCATE_ID;
import static com.ibm.streamsx.topology.generator.operator.OpProperties.PLACEMENT_RESOURCE_TAGS;
import static com.ibm.streamsx.topology.internal.gson.GsonUtilities.jstring;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import com.google.gson.JsonObject;
import com.ibm.json.java.JSONArray;
import com.ibm.json.java.JSONObject;
import com.ibm.streamsx.topology.Topology;
import com.ibm.streamsx.topology.TopologyElement;
import com.ibm.streamsx.topology.builder.BOperatorInvocation;
import com.ibm.streamsx.topology.builder.JOperator.JOperatorConfig;
import com.ibm.streamsx.topology.context.Placeable;
import com.ibm.streamsx.topology.generator.spl.GraphUtilities;
import com.ibm.streamsx.topology.internal.json4j.JSON4JUtilities;
/**
* Manages fusing of Placeables.
*/
class PlacementInfo {
private static final Map<Topology, WeakReference<PlacementInfo>> placements = new WeakHashMap<>();
private int nextFuseId;
private final Map<Placeable<?>, String> fusingIds = new HashMap<>();
private final Map<Placeable<?>, Set<String>> resourceTags = new HashMap<>();
static PlacementInfo getPlacementInfo(TopologyElement te) {
PlacementInfo pi;
synchronized(placements) {
WeakReference<PlacementInfo> wr = placements.get(te.topology());
if (wr == null) {
wr = new WeakReference<>(new PlacementInfo());
placements.put(te.topology(), wr);
}
pi = wr.get();
}
return pi;
}
/**
* Fuse a number of placeables.
* If fusing occurs then the fusing id
* is set as "explicitColocate" in the "placement" JSON object in
* the operator's config.
* @throws IllegalArgumentException if Placeables are from different
* topologies or if Placeable.isPlaceable()==false.
*/
synchronized boolean colocate(Placeable<?> first, Placeable<?> ... toFuse) {
Set<Placeable<?>> elements = new HashSet<>();
elements.add(first);
elements.addAll(Arrays.asList(toFuse));
// check high level constraints
for (Placeable<?> element : elements) {
if (!element.isPlaceable())
throw new IllegalArgumentException("Placeable.isPlaceable()==false");
if (!first.topology().equals(element.topology()) )
throw new IllegalArgumentException("Different topologies: "+ first.topology().getName() + " and " + element.topology().getName());
}
if (elements.size() < 2)
return false;
disallowColocateInLowLatency(elements);
disallowColocateIsolatedOpWithParent(first, toFuse);
String fusingId = null;
for (Placeable<?> element : elements) {
fusingId = fusingIds.get(element);
if (fusingId != null) {
break;
}
}
if (fusingId == null) {
fusingId = "__jaa_colocate" + nextFuseId++;
}
Set<String> fusedResourceTags = new HashSet<>();
for (Placeable<?> element : elements) {
fusingIds.put(element, fusingId);
Set<String> elementResourceTags = resourceTags.get(element);
if (elementResourceTags != null) {
fusedResourceTags.addAll(elementResourceTags);
}
resourceTags.put(element, fusedResourceTags);
}
// And finally update all the JSON info
for (Placeable<?> element : elements) {
updatePlacementJSON(element);
}
return true;
}
/** throw if s1.isolate().filter().colocate(s1) */
private void disallowColocateIsolatedOpWithParent(Placeable<?> first, Placeable<?> ... toFuse) {
JSONObject graph = first.builder().complete();
JSONObject colocateOp = first.operator().complete();
Set<JsonObject> parents = GraphUtilities.getUpstream(JSON4JUtilities.gson(colocateOp), JSON4JUtilities.gson(graph));
if (!parents.isEmpty()) {
JsonObject isolate = parents.iterator().next();
String kind = jstring(isolate, "kind");
if (!ISOLATE.kind().equals(kind))
return;
parents = GraphUtilities.getUpstream(isolate, JSON4JUtilities.gson(graph));
if (parents.isEmpty())
return;
JsonObject isolateParentOp = parents.iterator().next();
for (Placeable<?> placeable : toFuse) {
JSONObject tgtOp = placeable.operator().complete();
if (tgtOp.get("name").equals(jstring(isolateParentOp, "name")))
throw new IllegalStateException("Illegal to colocate an isolated stream with its parent.");
}
}
}
// A short term concession to the fact that colocate()
// and low latency regions aren't playing well together
// i.e., the low latency guarantee is being violated.
// So disallow that configuration for now.
private void disallowColocateInLowLatency(Set<Placeable<?>> elements) {
for (Placeable<?> element : elements) {
BOperatorInvocation op = element.operator();
if (element.builder().isInLowLatencyRegion(op))
throw new IllegalStateException("colocate() is not allowed in a low latency region");
}
}
synchronized Set<String> getResourceTags(Placeable<?> element) {
Set<String> elementResourceTags = resourceTags.get(element);
if (elementResourceTags == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(elementResourceTags);
}
synchronized void addResourceTags(Placeable<?> element, String ... tags) {
Set<String> elementResourceTags = resourceTags.get(element);
if (elementResourceTags == null) {
elementResourceTags = new HashSet<>();
resourceTags.put(element, elementResourceTags);
}
for (String tag : tags) {
if (!tag.isEmpty())
elementResourceTags.add(tag);
}
updatePlacementJSON(element);
}
/**
* Update an element's placement configuration.
*/
private void updatePlacementJSON(Placeable<?> element) {
JSONObject placement = JOperatorConfig.createJSONItem(element.operator().json(), PLACEMENT);
placement.put(PLACEMENT_EXPLICIT_COLOCATE_ID, fusingIds.get(element));
Set<String> elementResourceTags = resourceTags.get(element);
if (elementResourceTags != null && !elementResourceTags.isEmpty()) {
JSONArray listOfTags = new JSONArray();
listOfTags.addAll(elementResourceTags);
placement.put(PLACEMENT_RESOURCE_TAGS, listOfTags);
}
}
}