1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.jshell.execution;
  26 
  27 import java.io.IOException;
  28 import java.io.ObjectInput;
  29 import java.io.ObjectOutput;
  30 import jdk.jshell.JShellException;
  31 import jdk.jshell.spi.ExecutionControl;
  32 import static jdk.jshell.execution.RemoteCodes.*;
  33 
  34 /**
  35  * An implementation of the {@link jdk.jshell.spi.ExecutionControl}
  36  * execution engine SPI which streams requests to a remote agent where
  37  * execution takes place.
  38  *
  39  * @author Robert Field
  40  */
  41 public class StreamingExecutionControl implements ExecutionControl {
  42 
  43     private final ObjectOutput out;
  44     private final ObjectInput in;
  45     
  46     /**
  47      * Creates an instance.
  48      * 
  49      * @param out the output for commands
  50      * @param in the input for command responses
  51      */
  52     public StreamingExecutionControl(ObjectOutput out, ObjectInput in) {
  53         this.out = out;
  54         this.in = in;
  55     }
  56 
  57     @Override
  58     public void load(ClassBytecodes[] cbcs)
  59             throws ClassInstallException, NotImplementedException, EngineTerminationException {
  60         try {
  61             // Send a load command to the remote agent.
  62             writeCommand(CMD_LOAD);
  63             out.writeObject(cbcs);
  64             out.flush();
  65             // Retrieve and report results from the remote agent.
  66             readAndReportClassInstallResult();
  67         } catch (IOException ex) {
  68             throw new EngineTerminationException("Exception writing remote load: " + ex);
  69         }
  70     }
  71 
  72     @Override
  73     public void redefine(ClassBytecodes[] cbcs)
  74             throws ClassInstallException, NotImplementedException, EngineTerminationException {
  75         try {
  76             // Send a load command to the remote agent.
  77             writeCommand(CMD_REDEFINE);
  78             out.writeObject(cbcs);
  79             out.flush();
  80             // Retrieve and report results from the remote agent.
  81             readAndReportClassInstallResult();
  82         } catch (IOException ex) {
  83             throw new EngineTerminationException("Exception writing remote redefine: " + ex);
  84         }
  85     }
  86 
  87     @Override
  88     public String invoke(String classname, String methodname)
  89             throws RunException, EngineTerminationException, InternalException {
  90         try {
  91             // Send the invoke command to the remote agent.
  92             writeCommand(CMD_INVOKE);
  93             out.writeUTF(classname);
  94             out.writeUTF(methodname);
  95             out.flush();
  96             // Retrieve and report results from the remote agent.
  97             readAndReportExecutionResult();
  98             String result = in.readUTF();
  99             return result;
 100         } catch (IOException ex) {
 101             throw new EngineTerminationException("Exception writing remote invoke: " + ex);
 102         }
 103     }
 104 
 105     @Override
 106     public String varValue(String classname, String varname)
 107             throws RunException, EngineTerminationException, InternalException {
 108         try {
 109             // Send the variable-value command to the remote agent.
 110             writeCommand(CMD_VAR_VALUE);
 111             out.writeUTF(classname);
 112             out.writeUTF(varname);
 113             out.flush();
 114             // Retrieve and report results from the remote agent.
 115             readAndReportExecutionResult();
 116             String result = in.readUTF();
 117             return result;
 118         } catch (IOException ex) {
 119             throw new EngineTerminationException("Exception writing remote varValue: " + ex);
 120         }
 121     }
 122 
 123 
 124     @Override
 125     public void addToClasspath(String path)
 126             throws EngineTerminationException, InternalException {
 127         try {
 128             // Send the classpath addition command to the remote agent.
 129             writeCommand(CMD_ADD_CLASSPATH);
 130             out.writeUTF(path);
 131             out.flush();
 132             // Retrieve and report results from the remote agent.
 133             readAndReportClassSimpleResult();
 134         } catch (IOException ex) {
 135             throw new EngineTerminationException("Exception writing remote add to classpath: " + ex);
 136         }
 137     }
 138 
 139     @Override
 140     public void setClasspath(String path)
 141             throws EngineTerminationException, InternalException {
 142         try {
 143             // Send the classpath addition command to the remote agent.
 144             writeCommand(CMD_SET_CLASSPATH);
 145             out.writeUTF(path);
 146             out.flush();
 147             // Retrieve and report results from the remote agent.
 148             readAndReportClassSimpleResult();
 149         } catch (IOException ex) {
 150             throw new EngineTerminationException("Exception writing remote set classpath: " + ex);
 151         }
 152     }
 153 
 154     @Override
 155     public void stop()
 156             throws EngineTerminationException, InternalException {
 157         try {
 158             // Send the variable-value command to the remote agent.
 159             writeCommand(CMD_STOP);
 160             out.flush();
 161         } catch (IOException ex) {
 162             throw new EngineTerminationException("Exception writing remote stop: " + ex);
 163         }
 164     }
 165 
 166     @Override
 167     public Object extensionCommand(String command, Object arg)
 168             throws RunException, EngineTerminationException, InternalException {
 169         try {
 170             writeCommand(command);
 171             out.writeObject(arg);
 172             out.flush();
 173             // Retrieve and report results from the remote agent.
 174             readAndReportExecutionResult();
 175             Object result = in.readObject();
 176             return result;
 177         } catch (IOException | ClassNotFoundException ex) {
 178             throw new EngineTerminationException("Exception transmitting remote extensionCommand: "
 179                     + command + " -- " + ex);
 180         }
 181     }
 182 
 183     /**
 184      * Closes the execution engine. Send an exit command to the remote agent.
 185      */
 186     @Override
 187     public void close() {
 188         try {
 189             writeCommand(CMD_CLOSE);
 190             out.flush();
 191         } catch (IOException ex) {
 192             // ignore;
 193         }
 194     }
 195     
 196     private void writeCommand(String cmd) throws IOException {
 197         out.writeInt(COMMAND_PREFIX);
 198         out.writeUTF(cmd);
 199     }
 200 
 201     /**
 202      * Reports results from a remote agent command that does not expect
 203      * exceptions.
 204      */
 205     private void readAndReportClassSimpleResult() throws EngineTerminationException, InternalException {
 206         try {
 207             int status = in.readInt();
 208             switch (status) {
 209                 case RESULT_SUCCESS:
 210                     return;
 211                 case RESULT_NOT_IMPLEMENTED: {
 212                     String message = in.readUTF();
 213                     throw new NotImplementedException(message);
 214                 }
 215                 case RESULT_INTERNAL_PROBLEM: {
 216                     String message = in.readUTF();
 217                     throw new InternalException(message);
 218                 }
 219                 case RESULT_TERMINATED: {
 220                     String message = in.readUTF();
 221                     throw new EngineTerminationException(message);
 222                 }
 223                 default: {
 224                     throw new EngineTerminationException("Bad remote result code: " + status);
 225                 }
 226             }
 227         } catch (IOException ex) {
 228             throw new EngineTerminationException(ex.toString());
 229         }
 230     }
 231 
 232     /**
 233      * Reports results from a remote agent command that does not expect
 234      * exceptions.
 235      */
 236     private void readAndReportClassInstallResult() throws ClassInstallException,
 237             NotImplementedException, EngineTerminationException {
 238         try {
 239             int status = in.readInt();
 240             switch (status) {
 241                 case RESULT_SUCCESS:
 242                     return;
 243                 case RESULT_NOT_IMPLEMENTED: {
 244                     String message = in.readUTF();
 245                     throw new NotImplementedException(message);
 246                 }
 247                 case RESULT_CLASS_INSTALL_EXCEPTION: {
 248                     String message = in.readUTF();
 249                     boolean[] loaded = (boolean[]) in.readObject();
 250                     throw new ClassInstallException(message, loaded);
 251                 }
 252                 case RESULT_TERMINATED: {
 253                     String message = in.readUTF();
 254                     throw new EngineTerminationException(message);
 255                 }
 256                 default: {
 257                     throw new EngineTerminationException("Bad remote result code: " + status);
 258                 }
 259             }
 260         } catch (IOException | ClassNotFoundException ex) {
 261             throw new EngineTerminationException(ex.toString());
 262         }
 263     }
 264 
 265     /**
 266      * Reports results from a remote agent command that expects runtime
 267      * exceptions.
 268      *
 269      * @return true if successful
 270      * @throws IOException if the connection has dropped
 271      * @throws JShellException {@link jdk.jshell.EvalException}, if a user
 272      * exception was encountered on invoke;
 273      * {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved
 274      * reference was encountered
 275      * @throws java.lang.ClassNotFoundException
 276      */
 277     private void readAndReportExecutionResult() throws RunException,
 278             EngineTerminationException, InternalException {
 279         try {
 280             int status = in.readInt();
 281             switch (status) {
 282                 case RESULT_SUCCESS:
 283                     return;
 284                 case RESULT_NOT_IMPLEMENTED: {
 285                     String message = in.readUTF();
 286                     throw new NotImplementedException(message);
 287                 }
 288                 case RESULT_USER_EXCEPTION: {
 289                     // A user exception was encountered.
 290                     String message = in.readUTF();
 291                     String exceptionClassName = in.readUTF();
 292                     StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
 293                     throw new UserException(message, exceptionClassName, elems);
 294                 }
 295                 case RESULT_CORRALLED: {
 296                     // An unresolved reference was encountered.
 297                     int id = in.readInt();
 298                     StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
 299                     ResolutionException re = new ResolutionException(id, elems);
 300                     throw re;
 301                 }
 302                 case RESULT_STOPPED: {
 303                     // Execution was aborted by the stop()
 304                     throw new StoppedException();
 305                 }
 306                 case RESULT_INTERNAL_PROBLEM: {
 307                     // An internal error has occurred.
 308                     String message = in.readUTF();
 309                     throw new InternalException(message);
 310                 }
 311                 case RESULT_TERMINATED: {
 312                     String message = in.readUTF();
 313                     throw new EngineTerminationException(message);
 314                 }
 315                 default: {
 316                     throw new EngineTerminationException("Bad remote result code: " + status);
 317                 }
 318             }
 319         } catch (IOException | ClassNotFoundException ex) {
 320             throw new EngineTerminationException(ex.toString());
 321         }
 322     }
 323 
 324 }