/* # Licensed Materials - Property of IBM # Copyright IBM Corp. 2015 */ package com.ibm.streamsx.topology.internal.context; import static com.ibm.streamsx.topology.context.ContextProperties.SUBMISSION_PARAMS; import static com.ibm.streamsx.topology.context.ContextProperties.TRACING_LEVEL; import static com.ibm.streamsx.topology.context.JobProperties.CONFIG; import static com.ibm.streamsx.topology.context.JobProperties.DATA_DIRECTORY; import static com.ibm.streamsx.topology.context.JobProperties.GROUP; import static com.ibm.streamsx.topology.context.JobProperties.NAME; import static com.ibm.streamsx.topology.context.JobProperties.OVERRIDE_RESOURCE_LOAD_PROTECTION; import static com.ibm.streamsx.topology.context.JobProperties.PRELOAD_APPLICATION_BUNDLES; import static com.ibm.streamsx.topology.internal.context.remote.DeployKeys.DEPLOY; import static com.ibm.streamsx.topology.internal.json4j.JSON4JUtilities.gson; import java.io.File; import java.math.BigInteger; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Future; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.ibm.json.java.JSONObject; import com.ibm.streamsx.topology.Topology; import com.ibm.streamsx.topology.internal.context.JSONStreamsContext.AppEntity; import com.ibm.streamsx.topology.internal.context.remote.RemoteContexts; import com.ibm.streamsx.topology.internal.json4j.JSON4JUtilities; import com.ibm.streamsx.topology.internal.streams.JobConfigOverlay; import com.ibm.streamsx.topology.jobconfig.JobConfig; abstract class JSONStreamsContext<T> extends StreamsContextImpl<T> { static class AppEntity { final Topology app; final Map<String,Object> config; JsonObject submission; AppEntity(Topology app, Map<String,Object> config) throws Exception { this.app = app; this.config = config; } AppEntity(JsonObject submission) { this.app = null; this.config = null; this.submission = submission; } } /** * Force a copy of the config to avoid modifying the passed in config. */ @Override public final Future<T> submit(Topology app, Map<String, Object> config) throws Exception { return _submit(new AppEntity(app, new HashMap<>(config))); } Future<T> _submit(AppEntity entity) throws Exception { preSubmit(entity); if (entity.submission == null) createSubmission(entity); return postSubmit(entity, action(entity)); } /** * Pre-submit hook when submitting a Topology. */ void preSubmit(AppEntity entity) { } /** * Post-submit hook when submitting a Topology. */ Future<T> postSubmit(AppEntity entity, Future<T> future) throws Exception{ RemoteContexts.writeResultsToFile(entity.submission); return future; } @Override public final Future<T> submit(JSONObject submission) throws Exception { return _submit(new AppEntity(JSON4JUtilities.gson(submission))); } /** * Workhorse for handling a JSON graph. Sub-classes use Gson * to allow sharing code between remote (non-install) contexts * and product install contexts. */ abstract Future<T> action(AppEntity entity) throws Exception; /** * Create JSON form of the submission from a topology and config. * @throws Exception */ private void createSubmission(AppEntity entity) throws Exception { assert entity.submission == null; JsonObject submission = new JsonObject(); entity.app.finalizeGraph(getType()); JsonObject deploy = new JsonObject(); addConfigToDeploy(deploy, entity.config); submission.add(DEPLOY,deploy); submission.add(SUBMISSION_GRAPH, gson(entity.app.builder().complete())); entity.submission = submission; } @SuppressWarnings("unchecked") private static JsonElement convertConfigValue(Object value) { if (value instanceof Boolean) return new JsonPrimitive((Boolean) value); else if (value instanceof Number) return new JsonPrimitive((Number) value); else if (value instanceof String) { return new JsonPrimitive((String) value); } else if (value instanceof JSONObject) { return gson((JSONObject) value); } else if (value instanceof Collection) { JsonArray array = new JsonArray(); for (Object e : (Collection<Object>) value) { array.add(convertConfigValue(e)); } return array; } else if (value instanceof File) { return new JsonPrimitive(((File) value).getAbsolutePath()); } throw new IllegalArgumentException(value.getClass().getName()); } /** * Config keys that are skipped from being added generically in * the deploy JSON. */ private static final Set<String> CONFIG_SKIP_KEYS = new HashSet<>(); static { // Keys handled by Job Config overlays // ContextProperties Collections.addAll(CONFIG_SKIP_KEYS, TRACING_LEVEL, SUBMISSION_PARAMS); // JobProperties Collections.addAll(CONFIG_SKIP_KEYS, CONFIG, NAME, GROUP, DATA_DIRECTORY, OVERRIDE_RESOURCE_LOAD_PROTECTION, PRELOAD_APPLICATION_BUNDLES); } /** * Convert the config information into the JSON deploy. */ private static void addConfigToDeploy(JsonObject deploy, Map<String,Object> config) { // For job configuration information we convert to a job // config overlay JobConfig jc = JobConfig.fromProperties(config); JobConfigOverlay jco = new JobConfigOverlay(jc); jco.fullOverlayAsJSON(deploy); for (String key : config.keySet()) { if (CONFIG_SKIP_KEYS.contains(key)) continue; try { deploy.add(key, convertConfigValue(config.get(key))); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Unknown type for config:" + key + " - " + e.getMessage()); } } } }