/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.zeppelin.interpreter.remote; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.nio.ByteBuffer; import java.util.*; import org.apache.thrift.TException; import org.apache.thrift.server.TThreadPoolServer; import org.apache.thrift.transport.TServerSocket; import org.apache.thrift.transport.TTransportException; import org.apache.zeppelin.dep.DependencyResolver; import org.apache.zeppelin.display.*; import org.apache.zeppelin.helium.*; import org.apache.zeppelin.interpreter.*; import org.apache.zeppelin.interpreter.InterpreterHookRegistry.HookType; import org.apache.zeppelin.interpreter.InterpreterHookListener; import org.apache.zeppelin.interpreter.InterpreterResult.Code; import org.apache.zeppelin.interpreter.thrift.*; import org.apache.zeppelin.resource.*; import org.apache.zeppelin.scheduler.Job; import org.apache.zeppelin.scheduler.Job.Status; import org.apache.zeppelin.scheduler.JobListener; import org.apache.zeppelin.scheduler.JobProgressPoller; import org.apache.zeppelin.scheduler.Scheduler; import org.apache.zeppelin.user.AuthenticationInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; /** * Entry point for Interpreter process. * Accepting thrift connections from ZeppelinServer. */ public class RemoteInterpreterServer extends Thread implements RemoteInterpreterService.Iface, AngularObjectRegistryListener { Logger logger = LoggerFactory.getLogger(RemoteInterpreterServer.class); InterpreterGroup interpreterGroup; AngularObjectRegistry angularObjectRegistry; InterpreterHookRegistry hookRegistry; DistributedResourcePool resourcePool; private ApplicationLoader appLoader; Gson gson = new Gson(); RemoteInterpreterService.Processor<RemoteInterpreterServer> processor; private int port; private TThreadPoolServer server; RemoteInterpreterEventClient eventClient = new RemoteInterpreterEventClient(); private DependencyResolver depLoader; private final Map<String, RunningApplication> runningApplications = Collections.synchronizedMap(new HashMap<String, RunningApplication>()); private Map<String, Object> remoteWorksResponsePool; private ZeppelinRemoteWorksController remoteWorksController; public RemoteInterpreterServer(int port) throws TTransportException { this.port = port; processor = new RemoteInterpreterService.Processor<>(this); TServerSocket serverTransport = new TServerSocket(port); server = new TThreadPoolServer( new TThreadPoolServer.Args(serverTransport).processor(processor)); remoteWorksResponsePool = Collections.synchronizedMap(new HashMap<String, Object>()); remoteWorksController = new ZeppelinRemoteWorksController(this, remoteWorksResponsePool); } @Override public void run() { logger.info("Starting remote interpreter server on port {}", port); server.serve(); } @Override public void shutdown() throws TException { eventClient.waitForEventQueueBecomesEmpty(); if (interpreterGroup != null) { interpreterGroup.close(); } server.stop(); // server.stop() does not always finish server.serve() loop // sometimes server.serve() is hanging even after server.stop() call. // this case, need to force kill the process long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < 2000 && server.isServing()) { try { Thread.sleep(300); } catch (InterruptedException e) { logger.info("Exception in RemoteInterpreterServer while shutdown, Thread.sleep", e); } } if (server.isServing()) { System.exit(0); } } public int getPort() { return port; } public boolean isRunning() { if (server == null) { return false; } else { return server.isServing(); } } public static void main(String[] args) throws TTransportException, InterruptedException { int port = Constants.ZEPPELIN_INTERPRETER_DEFAUlT_PORT; if (args.length > 0) { port = Integer.parseInt(args[0]); } RemoteInterpreterServer remoteInterpreterServer = new RemoteInterpreterServer(port); remoteInterpreterServer.start(); remoteInterpreterServer.join(); System.exit(0); } @Override public void createInterpreter(String interpreterGroupId, String sessionKey, String className, Map<String, String> properties, String userName) throws TException { if (interpreterGroup == null) { interpreterGroup = new InterpreterGroup(interpreterGroupId); angularObjectRegistry = new AngularObjectRegistry(interpreterGroup.getId(), this); hookRegistry = new InterpreterHookRegistry(interpreterGroup.getId()); resourcePool = new DistributedResourcePool(interpreterGroup.getId(), eventClient); interpreterGroup.setInterpreterHookRegistry(hookRegistry); interpreterGroup.setAngularObjectRegistry(angularObjectRegistry); interpreterGroup.setResourcePool(resourcePool); String localRepoPath = properties.get("zeppelin.interpreter.localRepo"); if (properties.containsKey("zeppelin.interpreter.output.limit")) { InterpreterOutput.limit = Integer.parseInt( properties.get("zeppelin.interpreter.output.limit")); } depLoader = new DependencyResolver(localRepoPath); appLoader = new ApplicationLoader(resourcePool, depLoader); } try { Class<Interpreter> replClass = (Class<Interpreter>) Object.class.forName(className); Properties p = new Properties(); p.putAll(properties); setSystemProperty(p); Constructor<Interpreter> constructor = replClass.getConstructor(new Class[] {Properties.class}); Interpreter repl = constructor.newInstance(p); repl.setClassloaderUrls(new URL[]{}); synchronized (interpreterGroup) { List<Interpreter> interpreters = interpreterGroup.get(sessionKey); if (interpreters == null) { interpreters = new LinkedList<>(); interpreterGroup.put(sessionKey, interpreters); } interpreters.add(new LazyOpenInterpreter(repl)); } logger.info("Instantiate interpreter {}", className); repl.setInterpreterGroup(interpreterGroup); repl.setUserName(userName); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { logger.error(e.toString(), e); throw new TException(e); } } protected InterpreterGroup getInterpreterGroup() { return interpreterGroup; } protected ResourcePool getResourcePool() { return resourcePool; } protected RemoteInterpreterEventClient getEventClient() { return eventClient; } private void setSystemProperty(Properties properties) { for (Object key : properties.keySet()) { if (!RemoteInterpreterUtils.isEnvString((String) key)) { String value = properties.getProperty((String) key); if (value == null || value.isEmpty()) { System.clearProperty((String) key); } else { System.setProperty((String) key, properties.getProperty((String) key)); } } } } protected Interpreter getInterpreter(String sessionKey, String className) throws TException { if (interpreterGroup == null) { throw new TException( new InterpreterException("Interpreter instance " + className + " not created")); } synchronized (interpreterGroup) { List<Interpreter> interpreters = interpreterGroup.get(sessionKey); if (interpreters == null) { throw new TException( new InterpreterException("Interpreter " + className + " not initialized")); } for (Interpreter inp : interpreters) { if (inp.getClassName().equals(className)) { return inp; } } } throw new TException(new InterpreterException("Interpreter instance " + className + " not found")); } @Override public void open(String noteId, String className) throws TException { Interpreter intp = getInterpreter(noteId, className); intp.open(); } @Override public void close(String sessionKey, String className) throws TException { // unload all applications for (String appId : runningApplications.keySet()) { RunningApplication appInfo = runningApplications.get(appId); // see NoteInterpreterLoader.SHARED_SESSION if (appInfo.noteId.equals(sessionKey) || sessionKey.equals("shared_session")) { try { logger.info("Unload App {} ", appInfo.pkg.getName()); appInfo.app.unload(); // see ApplicationState.Status.UNLOADED eventClient.onAppStatusUpdate(appInfo.noteId, appInfo.paragraphId, appId, "UNLOADED"); } catch (ApplicationException e) { logger.error(e.getMessage(), e); } } } // close interpreters List<Interpreter> interpreters; synchronized (interpreterGroup) { interpreters = interpreterGroup.get(sessionKey); } if (interpreters != null) { Iterator<Interpreter> it = interpreters.iterator(); while (it.hasNext()) { Interpreter inp = it.next(); if (inp.getClassName().equals(className)) { inp.close(); it.remove(); break; } } } } @Override public RemoteInterpreterResult interpret(String noteId, String className, String st, RemoteInterpreterContext interpreterContext) throws TException { if (logger.isDebugEnabled()) { logger.debug("st:\n{}", st); } Interpreter intp = getInterpreter(noteId, className); InterpreterContext context = convert(interpreterContext); context.setClassName(intp.getClassName()); Scheduler scheduler = intp.getScheduler(); InterpretJobListener jobListener = new InterpretJobListener(); InterpretJob job = new InterpretJob( interpreterContext.getParagraphId(), "remoteInterpretJob_" + System.currentTimeMillis(), jobListener, JobProgressPoller.DEFAULT_INTERVAL_MSEC, intp, st, context); scheduler.submit(job); while (!job.isTerminated()) { synchronized (jobListener) { try { jobListener.wait(1000); } catch (InterruptedException e) { logger.info("Exception in RemoteInterpreterServer while interpret, jobListener.wait", e); } } } InterpreterResult result; if (job.getStatus() == Status.ERROR) { result = new InterpreterResult(Code.ERROR, Job.getStack(job.getException())); } else { result = (InterpreterResult) job.getReturn(); // in case of job abort in PENDING status, result can be null if (result == null) { result = new InterpreterResult(Code.KEEP_PREVIOUS_RESULT); } } return convert(result, context.getConfig(), context.getGui()); } @Override public void onReceivedZeppelinResource(String responseJson) throws TException { RemoteZeppelinServerResource response = gson.fromJson( responseJson, RemoteZeppelinServerResource.class); if (response == null) { throw new TException("Bad response for remote resource"); } try { if (response.getResourceType() == RemoteZeppelinServerResource.Type.PARAGRAPH_RUNNERS) { List<InterpreterContextRunner> intpContextRunners = new LinkedList<>(); List<Map<String, Object>> remoteRunnersMap = (List<Map<String, Object>>) response.getData(); String noteId = null; String paragraphId = null; for (Map<String, Object> runnerItem : remoteRunnersMap) { noteId = (String) runnerItem.get("noteId"); paragraphId = (String) runnerItem.get("paragraphId"); intpContextRunners.add( new ParagraphRunner(this, noteId, paragraphId) ); } synchronized (this.remoteWorksResponsePool) { this.remoteWorksResponsePool.put( response.getOwnerKey(), intpContextRunners); } } } catch (Exception e) { throw e; } } class InterpretJobListener implements JobListener { @Override public void onProgressUpdate(Job job, int progress) { } @Override public void beforeStatusChange(Job job, Status before, Status after) { } @Override public void afterStatusChange(Job job, Status before, Status after) { synchronized (this) { notifyAll(); } } } class InterpretJob extends Job { private Interpreter interpreter; private String script; private InterpreterContext context; private Map<String, Object> infos; private Object results; public InterpretJob( String jobId, String jobName, JobListener listener, long progressUpdateIntervalMsec, Interpreter interpreter, String script, InterpreterContext context) { super(jobId, jobName, listener, progressUpdateIntervalMsec); this.interpreter = interpreter; this.script = script; this.context = context; } @Override public Object getReturn() { return results; } @Override public int progress() { return 0; } @Override public Map<String, Object> info() { if (infos == null) { infos = new HashMap<>(); } return infos; } private void processInterpreterHooks(final String noteId) { InterpreterHookListener hookListener = new InterpreterHookListener() { @Override public void onPreExecute(String script) { String cmdDev = interpreter.getHook(noteId, HookType.PRE_EXEC_DEV); String cmdUser = interpreter.getHook(noteId, HookType.PRE_EXEC); // User defined hook should be executed before dev hook List<String> cmds = Arrays.asList(cmdDev, cmdUser); for (String cmd : cmds) { if (cmd != null) { script = cmd + '\n' + script; } } InterpretJob.this.script = script; } @Override public void onPostExecute(String script) { String cmdDev = interpreter.getHook(noteId, HookType.POST_EXEC_DEV); String cmdUser = interpreter.getHook(noteId, HookType.POST_EXEC); // User defined hook should be executed after dev hook List<String> cmds = Arrays.asList(cmdUser, cmdDev); for (String cmd : cmds) { if (cmd != null) { script += '\n' + cmd; } } InterpretJob.this.script = script; } }; hookListener.onPreExecute(script); hookListener.onPostExecute(script); } @Override protected Object jobRun() throws Throwable { try { InterpreterContext.set(context); InterpreterResult result = null; // Open the interpreter instance prior to calling interpret(). // This is necessary because the earliest we can register a hook // is from within the open() method. LazyOpenInterpreter lazy = (LazyOpenInterpreter) interpreter; if (!lazy.isOpen()) { lazy.open(); result = lazy.executePrecode(context); } if (result == null || result.code() == Code.SUCCESS) { // Add hooks to script from registry. // Global scope first, followed by notebook scope processInterpreterHooks(null); processInterpreterHooks(context.getNoteId()); result = interpreter.interpret(script, context); } // data from context.out is prepended to InterpreterResult if both defined context.out.flush(); List<InterpreterResultMessage> resultMessages = context.out.toInterpreterResultMessage(); resultMessages.addAll(result.message()); // put result into resource pool if (resultMessages.size() > 0) { int lastMessageIndex = resultMessages.size() - 1; if (resultMessages.get(lastMessageIndex).getType() == InterpreterResult.Type.TABLE) { context.getResourcePool().put( context.getNoteId(), context.getParagraphId(), WellKnownResourceName.ZeppelinTableResult.toString(), resultMessages.get(lastMessageIndex)); } } return new InterpreterResult(result.code(), resultMessages); } finally { InterpreterContext.remove(); } } @Override protected boolean jobAbort() { return false; } @Override public void setResult(Object results) { this.results = results; } } @Override public void cancel(String noteId, String className, RemoteInterpreterContext interpreterContext) throws TException { logger.info("cancel {} {}", className, interpreterContext.getParagraphId()); Interpreter intp = getInterpreter(noteId, className); String jobId = interpreterContext.getParagraphId(); Job job = intp.getScheduler().removeFromWaitingQueue(jobId); if (job != null) { job.setStatus(Status.ABORT); } else { intp.cancel(convert(interpreterContext, null)); } } @Override public int getProgress(String noteId, String className, RemoteInterpreterContext interpreterContext) throws TException { Interpreter intp = getInterpreter(noteId, className); return intp.getProgress(convert(interpreterContext, null)); } @Override public String getFormType(String noteId, String className) throws TException { Interpreter intp = getInterpreter(noteId, className); return intp.getFormType().toString(); } @Override public List<InterpreterCompletion> completion(String noteId, String className, String buf, int cursor, RemoteInterpreterContext remoteInterpreterContext) throws TException { Interpreter intp = getInterpreter(noteId, className); List completion = intp.completion(buf, cursor, convert(remoteInterpreterContext, null)); return completion; } private InterpreterContext convert(RemoteInterpreterContext ric) { return convert(ric, createInterpreterOutput(ric.getNoteId(), ric.getParagraphId())); } private InterpreterContext convert(RemoteInterpreterContext ric, InterpreterOutput output) { List<InterpreterContextRunner> contextRunners = new LinkedList<>(); List<InterpreterContextRunner> runners = gson.fromJson(ric.getRunners(), new TypeToken<List<RemoteInterpreterContextRunner>>() { }.getType()); for (InterpreterContextRunner r : runners) { contextRunners.add(new ParagraphRunner(this, r.getNoteId(), r.getParagraphId())); } return new InterpreterContext( ric.getNoteId(), ric.getParagraphId(), ric.getReplName(), ric.getParagraphTitle(), ric.getParagraphText(), gson.fromJson(ric.getAuthenticationInfo(), AuthenticationInfo.class), (Map<String, Object>) gson.fromJson(ric.getConfig(), new TypeToken<Map<String, Object>>() {}.getType()), GUI.fromJson(ric.getGui()), interpreterGroup.getAngularObjectRegistry(), interpreterGroup.getResourcePool(), contextRunners, output, remoteWorksController, eventClient); } protected InterpreterOutput createInterpreterOutput(final String noteId, final String paragraphId) { return new InterpreterOutput(new InterpreterOutputListener() { @Override public void onUpdateAll(InterpreterOutput out) { try { eventClient.onInterpreterOutputUpdateAll( noteId, paragraphId, out.toInterpreterResultMessage()); } catch (IOException e) { logger.error(e.getMessage(), e); } } @Override public void onAppend(int index, InterpreterResultMessageOutput out, byte[] line) { String output = new String(line); logger.debug("Output Append: {}", output); eventClient.onInterpreterOutputAppend( noteId, paragraphId, index, output); } @Override public void onUpdate(int index, InterpreterResultMessageOutput out) { String output; try { output = new String(out.toByteArray()); logger.debug("Output Update: {}", output); eventClient.onInterpreterOutputUpdate( noteId, paragraphId, index, out.getType(), output); } catch (IOException e) { logger.error(e.getMessage(), e); } } }); } static class ParagraphRunner extends InterpreterContextRunner { Logger logger = LoggerFactory.getLogger(ParagraphRunner.class); private transient RemoteInterpreterServer server; public ParagraphRunner(RemoteInterpreterServer server, String noteId, String paragraphId) { super(noteId, paragraphId); this.server = server; } @Override public void run() { server.eventClient.run(this); } } static class ZeppelinRemoteWorksController implements RemoteWorksController{ Logger logger = LoggerFactory.getLogger(ZeppelinRemoteWorksController.class); private final long DEFAULT_TIMEOUT_VALUE = 300000; private final Map<String, Object> remoteWorksResponsePool; private RemoteInterpreterServer server; public ZeppelinRemoteWorksController( RemoteInterpreterServer server, Map<String, Object> remoteWorksResponsePool) { this.remoteWorksResponsePool = remoteWorksResponsePool; this.server = server; } public String generateOwnerKey() { String hashKeyText = new String("ownerKey" + System.currentTimeMillis()); String hashKey = String.valueOf(hashKeyText.hashCode()); return hashKey; } public boolean waitForEvent(String eventOwnerKey) throws InterruptedException { return waitForEvent(eventOwnerKey, DEFAULT_TIMEOUT_VALUE); } public boolean waitForEvent(String eventOwnerKey, long timeout) throws InterruptedException { boolean wasGetData = false; long now = System.currentTimeMillis(); long endTime = System.currentTimeMillis() + timeout; while (endTime >= now) { synchronized (this.remoteWorksResponsePool) { wasGetData = this.remoteWorksResponsePool.containsKey(eventOwnerKey); } if (wasGetData == true) { break; } now = System.currentTimeMillis(); sleep(500); } return wasGetData; } @Override public List<InterpreterContextRunner> getRemoteContextRunner(String noteId) { return getRemoteContextRunner(noteId, null); } public List<InterpreterContextRunner> getRemoteContextRunner( String noteId, String paragraphID) { List<InterpreterContextRunner> runners = null; String ownerKey = generateOwnerKey(); ZeppelinServerResourceParagraphRunner resource = new ZeppelinServerResourceParagraphRunner(); resource.setNoteId(noteId); resource.setParagraphId(paragraphID); server.eventClient.getZeppelinServerNoteRunner(ownerKey, resource); try { this.waitForEvent(ownerKey); } catch (Exception e) { return new LinkedList<>(); } synchronized (this.remoteWorksResponsePool) { runners = (List<InterpreterContextRunner>) this.remoteWorksResponsePool.get(ownerKey); this.remoteWorksResponsePool.remove(ownerKey); } return runners; } } private RemoteInterpreterResult convert(InterpreterResult result, Map<String, Object> config, GUI gui) { List<RemoteInterpreterResultMessage> msg = new LinkedList<>(); for (InterpreterResultMessage m : result.message()) { msg.add(new RemoteInterpreterResultMessage( m.getType().name(), m.getData())); } return new RemoteInterpreterResult( result.code().name(), msg, gson.toJson(config), gui.toJson()); } @Override public String getStatus(String sessionKey, String jobId) throws TException { if (interpreterGroup == null) { return "Unknown"; } synchronized (interpreterGroup) { List<Interpreter> interpreters = interpreterGroup.get(sessionKey); if (interpreters == null) { return "Unknown"; } for (Interpreter intp : interpreters) { for (Job job : intp.getScheduler().getJobsRunning()) { if (jobId.equals(job.getId())) { return job.getStatus().name(); } } for (Job job : intp.getScheduler().getJobsWaiting()) { if (jobId.equals(job.getId())) { return job.getStatus().name(); } } } } return "Unknown"; } @Override public void onAdd(String interpreterGroupId, AngularObject object) { eventClient.angularObjectAdd(object); } @Override public void onUpdate(String interpreterGroupId, AngularObject object) { eventClient.angularObjectUpdate(object); } @Override public void onRemove(String interpreterGroupId, String name, String noteId, String paragraphId) { eventClient.angularObjectRemove(name, noteId, paragraphId); } /** * Poll event from RemoteInterpreterEventPoller * @return * @throws TException */ @Override public RemoteInterpreterEvent getEvent() throws TException { return eventClient.pollEvent(); } /** * called when object is updated in client (web) side. * @param name * @param noteId noteId where the update issues * @param paragraphId paragraphId where the update issues * @param object * @throws TException */ @Override public void angularObjectUpdate(String name, String noteId, String paragraphId, String object) throws TException { AngularObjectRegistry registry = interpreterGroup.getAngularObjectRegistry(); // first try local objects AngularObject ao = registry.get(name, noteId, paragraphId); if (ao == null) { logger.debug("Angular object {} not exists", name); return; } if (object == null) { ao.set(null, false); return; } Object oldObject = ao.get(); Object value = null; if (oldObject != null) { // first try with previous object's type try { value = gson.fromJson(object, oldObject.getClass()); ao.set(value, false); return; } catch (Exception e) { // it's not a previous object's type. proceed to treat as a generic type logger.debug(e.getMessage(), e); } } // Generic java object type for json. if (value == null) { try { value = gson.fromJson(object, new TypeToken<Map<String, Object>>() { }.getType()); } catch (Exception e) { // it's not a generic json object, too. okay, proceed to threat as a string type logger.debug(e.getMessage(), e); } } // try string object type at last if (value == null) { value = gson.fromJson(object, String.class); } ao.set(value, false); } /** * When zeppelinserver initiate angular object add. * Dont't need to emit event to zeppelin server */ @Override public void angularObjectAdd(String name, String noteId, String paragraphId, String object) throws TException { AngularObjectRegistry registry = interpreterGroup.getAngularObjectRegistry(); // first try local objects AngularObject ao = registry.get(name, noteId, paragraphId); if (ao != null) { angularObjectUpdate(name, noteId, paragraphId, object); return; } // Generic java object type for json. Object value = null; try { value = gson.fromJson(object, new TypeToken<Map<String, Object>>() { }.getType()); } catch (Exception e) { // it's okay. proceed to treat object as a string logger.debug(e.getMessage(), e); } // try string object type at last if (value == null) { value = gson.fromJson(object, String.class); } registry.add(name, value, noteId, paragraphId, false); } @Override public void angularObjectRemove(String name, String noteId, String paragraphId) throws TException { AngularObjectRegistry registry = interpreterGroup.getAngularObjectRegistry(); registry.remove(name, noteId, paragraphId, false); } @Override public void resourcePoolResponseGetAll(List<String> resources) throws TException { eventClient.putResponseGetAllResources(resources); } /** * Get payload of resource from remote * @param resourceId json serialized ResourceId * @param object java serialized of the object * @throws TException */ @Override public void resourceResponseGet(String resourceId, ByteBuffer object) throws TException { eventClient.putResponseGetResource(resourceId, object); } @Override public List<String> resourcePoolGetAll() throws TException { logger.debug("Request getAll from ZeppelinServer"); List<String> result = new LinkedList<>(); if (resourcePool == null) { return result; } ResourceSet resourceSet = resourcePool.getAll(false); Gson gson = new Gson(); for (Resource r : resourceSet) { result.add(gson.toJson(r)); } return result; } @Override public boolean resourceRemove(String noteId, String paragraphId, String resourceName) throws TException { Resource resource = resourcePool.remove(noteId, paragraphId, resourceName); return resource != null; } @Override public ByteBuffer resourceGet(String noteId, String paragraphId, String resourceName) throws TException { logger.debug("Request resourceGet {} from ZeppelinServer", resourceName); Resource resource = resourcePool.get(noteId, paragraphId, resourceName, false); if (resource == null || resource.get() == null || !resource.isSerializable()) { return ByteBuffer.allocate(0); } else { try { return Resource.serializeObject(resource.get()); } catch (IOException e) { logger.error(e.getMessage(), e); return ByteBuffer.allocate(0); } } } @Override public ByteBuffer resourceInvokeMethod( String noteId, String paragraphId, String resourceName, String invokeMessage) { InvokeResourceMethodEventMessage message = gson.fromJson(invokeMessage, InvokeResourceMethodEventMessage.class); Resource resource = resourcePool.get(noteId, paragraphId, resourceName, false); if (resource == null || resource.get() == null) { return ByteBuffer.allocate(0); } else { try { Object o = resource.get(); Method method = o.getClass().getMethod( message.methodName, message.getParamTypes()); Object ret = method.invoke(o, message.params); if (message.shouldPutResultIntoResourcePool()) { // if return resource name is specified, // then put result into resource pool // and return empty byte buffer resourcePool.put( noteId, paragraphId, message.returnResourceName, ret); return ByteBuffer.allocate(0); } else { // if return resource name is not specified, // then return serialized result ByteBuffer serialized = Resource.serializeObject(ret); if (serialized == null) { return ByteBuffer.allocate(0); } else { return serialized; } } } catch (Exception e) { logger.error(e.getMessage(), e); return ByteBuffer.allocate(0); } } } /** * Get payload of resource from remote * @param invokeResourceMethodEventMessage json serialized InvokeResourcemethodEventMessage * @param object java serialized of the object * @throws TException */ @Override public void resourceResponseInvokeMethod( String invokeResourceMethodEventMessage, ByteBuffer object) throws TException { InvokeResourceMethodEventMessage message = gson.fromJson(invokeResourceMethodEventMessage, InvokeResourceMethodEventMessage.class); if (message.shouldPutResultIntoResourcePool()) { Resource resource = resourcePool.get( message.resourceId.getNoteId(), message.resourceId.getParagraphId(), message.returnResourceName, true); eventClient.putResponseInvokeMethod(message, resource); } else { eventClient.putResponseInvokeMethod(message, object); } } @Override public void angularRegistryPush(String registryAsString) throws TException { try { Map<String, Map<String, AngularObject>> deserializedRegistry = gson .fromJson(registryAsString, new TypeToken<Map<String, Map<String, AngularObject>>>() { }.getType()); interpreterGroup.getAngularObjectRegistry().setRegistry(deserializedRegistry); } catch (Exception e) { logger.info("Exception in RemoteInterpreterServer while angularRegistryPush, nolock", e); } } protected InterpreterOutput createAppOutput(final String noteId, final String paragraphId, final String appId) { return new InterpreterOutput(new InterpreterOutputListener() { @Override public void onUpdateAll(InterpreterOutput out) { } @Override public void onAppend(int index, InterpreterResultMessageOutput out, byte[] line) { eventClient.onAppOutputAppend(noteId, paragraphId, index, appId, new String(line)); } @Override public void onUpdate(int index, InterpreterResultMessageOutput out) { try { eventClient.onAppOutputUpdate(noteId, paragraphId, index, appId, out.getType(), new String(out.toByteArray())); } catch (IOException e) { logger.error(e.getMessage(), e); } } }); } private ApplicationContext getApplicationContext( HeliumPackage packageInfo, String noteId, String paragraphId, String applicationInstanceId) { InterpreterOutput out = createAppOutput(noteId, paragraphId, applicationInstanceId); return new ApplicationContext( noteId, paragraphId, applicationInstanceId, new HeliumAppAngularObjectRegistry(angularObjectRegistry, noteId, applicationInstanceId), out); } @Override public RemoteApplicationResult loadApplication( String applicationInstanceId, String packageInfo, String noteId, String paragraphId) throws TException { if (runningApplications.containsKey(applicationInstanceId)) { logger.warn("Application instance {} is already running"); return new RemoteApplicationResult(true, ""); } HeliumPackage pkgInfo = gson.fromJson(packageInfo, HeliumPackage.class); ApplicationContext context = getApplicationContext( pkgInfo, noteId, paragraphId, applicationInstanceId); try { Application app = null; logger.info( "Loading application {}({}), artifact={}, className={} into note={}, paragraph={}", pkgInfo.getName(), applicationInstanceId, pkgInfo.getArtifact(), pkgInfo.getClassName(), noteId, paragraphId); app = appLoader.load(pkgInfo, context); runningApplications.put( applicationInstanceId, new RunningApplication(pkgInfo, app, noteId, paragraphId)); return new RemoteApplicationResult(true, ""); } catch (Exception e) { logger.error(e.getMessage(), e); return new RemoteApplicationResult(false, e.getMessage()); } } @Override public RemoteApplicationResult unloadApplication(String applicationInstanceId) throws TException { RunningApplication runningApplication = runningApplications.remove(applicationInstanceId); if (runningApplication != null) { try { logger.info("Unloading application {}", applicationInstanceId); runningApplication.app.unload(); } catch (ApplicationException e) { logger.error(e.getMessage(), e); return new RemoteApplicationResult(false, e.getMessage()); } } return new RemoteApplicationResult(true, ""); } @Override public RemoteApplicationResult runApplication(String applicationInstanceId) throws TException { logger.info("run application {}", applicationInstanceId); RunningApplication runningApp = runningApplications.get(applicationInstanceId); if (runningApp == null) { logger.error("Application instance {} not exists", applicationInstanceId); return new RemoteApplicationResult(false, "Application instance does not exists"); } else { ApplicationContext context = runningApp.app.context(); try { context.out.clear(); context.out.setType(InterpreterResult.Type.ANGULAR); ResourceSet resource = appLoader.findRequiredResourceSet( runningApp.pkg.getResources(), context.getNoteId(), context.getParagraphId()); for (Resource res : resource) { System.err.println("Resource " + res.get()); } runningApp.app.run(resource); context.out.flush(); InterpreterResultMessageOutput out = context.out.getOutputAt(0); eventClient.onAppOutputUpdate( context.getNoteId(), context.getParagraphId(), 0, applicationInstanceId, out.getType(), new String(out.toByteArray())); return new RemoteApplicationResult(true, ""); } catch (ApplicationException | IOException e) { return new RemoteApplicationResult(false, e.getMessage()); } } } private static class RunningApplication { public final Application app; public final HeliumPackage pkg; public final String noteId; public final String paragraphId; public RunningApplication(HeliumPackage pkg, Application app, String noteId, String paragraphId) { this.app = app; this.pkg = pkg; this.noteId = noteId; this.paragraphId = paragraphId; } }; }