1 /*
   2  * Copyright (c) 1996, 2005, 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 
  26 package sun.rmi.transport;
  27 
  28 import java.io.DataInputStream;
  29 import java.io.DataOutputStream;
  30 import java.io.IOException;
  31 import java.io.ObjectInput;
  32 import java.io.ObjectOutput;
  33 import java.io.StreamCorruptedException;
  34 import java.rmi.RemoteException;
  35 import java.rmi.MarshalException;
  36 import java.rmi.UnmarshalException;
  37 import java.rmi.server.ObjID;
  38 import java.rmi.server.RemoteCall;
  39 import sun.rmi.runtime.Log;
  40 import sun.rmi.server.UnicastRef;
  41 import sun.rmi.transport.tcp.TCPEndpoint;
  42 
  43 /**
  44  * Stream-based implementation of the RemoteCall interface.
  45  *
  46  * @author Ann Wollrath
  47  */
  48 public class StreamRemoteCall implements RemoteCall {
  49     private ConnectionInputStream in = null;
  50     private ConnectionOutputStream out = null;
  51     private Connection conn;
  52     private boolean resultStarted = false;
  53     private Exception serverException = null;
  54 
  55     public StreamRemoteCall(Connection c) {
  56         conn = c;
  57     }
  58 
  59     public StreamRemoteCall(Connection c, ObjID id, int op, long hash)
  60         throws RemoteException
  61     {
  62         try {
  63             conn = c;
  64             Transport.transportLog.log(Log.VERBOSE,
  65                 "write remote call header...");
  66 
  67             // write out remote call header info...
  68             // call header, part 1 (read by Transport)
  69             conn.getOutputStream().write(TransportConstants.Call);
  70             getOutputStream();           // creates a MarshalOutputStream
  71             id.write(out);               // object id (target of call)
  72             // call header, part 2 (read by Dispatcher)
  73             out.writeInt(op);            // method number (operation index)
  74             out.writeLong(hash);         // stub/skeleton hash
  75         } catch (IOException e) {
  76             throw new MarshalException("Error marshaling call header", e);
  77         }
  78     }
  79 
  80     /**
  81      * Return the connection associated with this call.
  82      */
  83     public Connection getConnection() {
  84         return conn;
  85     }
  86 
  87     /**
  88      * Return the output stream the stub/skeleton should put arguments/results
  89      * into.
  90      */
  91     public ObjectOutput getOutputStream() throws IOException {
  92         return getOutputStream(false);
  93     }
  94 
  95     private ObjectOutput getOutputStream(boolean resultStream)
  96         throws IOException
  97     {
  98         if (out == null) {
  99             Transport.transportLog.log(Log.VERBOSE, "getting output stream");
 100 
 101             out = new ConnectionOutputStream(conn, resultStream);
 102         }
 103         return out;
 104     }
 105 
 106     /**
 107      * Release the outputStream  Currently, will not complain if the
 108      * output stream is released more than once.
 109      */
 110     public void releaseOutputStream() throws IOException {
 111         try {
 112             if (out != null) {
 113                 try {
 114                     out.flush();
 115                 } finally {
 116                     out.done();         // always start DGC ack timer
 117                 }
 118             }
 119             conn.releaseOutputStream();
 120         } finally {
 121             out = null;
 122         }
 123     }
 124 
 125     /**
 126      * Get the InputStream the stub/skeleton should get results/arguments
 127      * from.
 128      */
 129     public ObjectInput getInputStream() throws IOException {
 130         if (in == null) {
 131             Transport.transportLog.log(Log.VERBOSE, "getting input stream");
 132 
 133             in = new ConnectionInputStream(conn.getInputStream());
 134         }
 135         return in;
 136     }
 137 
 138     /**
 139      * Release the input stream, this would allow some transports to release
 140      * the channel early.
 141      */
 142     public void releaseInputStream() throws IOException {
 143         /* WARNING: Currently, the UnicastRef.java invoke methods rely
 144          * upon this method not throwing an IOException.
 145          */
 146 
 147         try {
 148             if (in != null) {
 149                 // execute MarshalInputStream "done" callbacks
 150                 try {
 151                     in.done();
 152                 } catch (RuntimeException e) {
 153                 }
 154 
 155                 // add saved references to DGC table
 156                 in.registerRefs();
 157 
 158                 /* WARNING: The connection being passed to done may have
 159                  * already been freed.
 160                  */
 161                 in.done(conn);
 162             }
 163             conn.releaseInputStream();
 164         } finally {
 165             in = null;
 166         }
 167     }
 168 
 169     /**
 170      * Returns an output stream (may put out header information
 171      * relating to the success of the call).
 172      * @param success If true, indicates normal return, else indicates
 173      * exceptional return.
 174      * @exception StreamCorruptedException If result stream previously
 175      * acquired
 176      * @exception IOException For any other problem with I/O.
 177      */
 178     public ObjectOutput getResultStream(boolean success) throws IOException {
 179         /* make sure result code only marshaled once. */
 180         if (resultStarted)
 181             throw new StreamCorruptedException("result already in progress");
 182         else
 183             resultStarted = true;
 184 
 185         // write out return header
 186         // return header, part 1 (read by Transport)
 187         DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
 188         wr.writeByte(TransportConstants.Return);// transport op
 189         getOutputStream(true);  // creates a MarshalOutputStream
 190         // return header, part 2 (read by client-side RemoteCall)
 191         if (success)            //
 192             out.writeByte(TransportConstants.NormalReturn);
 193         else
 194             out.writeByte(TransportConstants.ExceptionalReturn);
 195         out.writeID();          // write id for gcAck
 196         return out;
 197     }
 198 
 199     /**
 200      * Do whatever it takes to execute the call.
 201      * (Exception thrown before fallthrough can occur)
 202      */
 203     @SuppressWarnings("fallthrough")
 204     public void executeCall() throws Exception {
 205         byte returnType;
 206 
 207         // read result header
 208         DGCAckHandler ackHandler = null;
 209         try {
 210             if (out != null) {
 211                 ackHandler = out.getDGCAckHandler();
 212             }
 213             releaseOutputStream();
 214             DataInputStream rd = new DataInputStream(conn.getInputStream());
 215             byte op = rd.readByte();
 216             if (op != TransportConstants.Return) {
 217                 if (Transport.transportLog.isLoggable(Log.BRIEF)) {
 218                     Transport.transportLog.log(Log.BRIEF,
 219                         "transport return code invalid: " + op);
 220                 }
 221                 throw new UnmarshalException("Transport return code invalid");
 222             }
 223             getInputStream();
 224             returnType = in.readByte();
 225             in.readID();        // id for DGC acknowledgement
 226         } catch (UnmarshalException e) {
 227             throw e;
 228         } catch (IOException e) {
 229             throw new UnmarshalException("Error unmarshaling return header",
 230                                          e);
 231         } finally {
 232             if (ackHandler != null) {
 233                 ackHandler.release();
 234             }
 235         }
 236 
 237         // read return value
 238         switch (returnType) {
 239         case TransportConstants.NormalReturn:
 240             break;
 241 
 242         case TransportConstants.ExceptionalReturn:
 243             Object ex;
 244             try {
 245                 ex = in.readObject();
 246             } catch (Exception e) {
 247                 throw new UnmarshalException("Error unmarshaling return", e);
 248             }
 249 
 250             // An exception should have been received,
 251             // if so throw it, else flag error
 252             if (ex instanceof Exception) {
 253                 exceptionReceivedFromServer((Exception) ex);
 254             } else {
 255                 throw new UnmarshalException("Return type not Exception");
 256             }
 257         default:
 258             if (Transport.transportLog.isLoggable(Log.BRIEF)) {
 259                 Transport.transportLog.log(Log.BRIEF,
 260                     "return code invalid: " + returnType);
 261             }
 262             throw new UnmarshalException("Return code invalid");
 263         }
 264     }
 265 
 266     /**
 267      * Routine that causes the stack traces of remote exceptions to be
 268      * filled in with the current stack trace on the client.  Detail
 269      * exceptions are filled in iteratively.
 270      */
 271     protected void exceptionReceivedFromServer(Exception ex) throws Exception {
 272         serverException = ex;
 273 
 274         StackTraceElement[] serverTrace = ex.getStackTrace();
 275         StackTraceElement[] clientTrace = (new Throwable()).getStackTrace();
 276         StackTraceElement[] combinedTrace =
 277             new StackTraceElement[serverTrace.length + clientTrace.length];
 278         System.arraycopy(serverTrace, 0, combinedTrace, 0,
 279                          serverTrace.length);
 280         System.arraycopy(clientTrace, 0, combinedTrace, serverTrace.length,
 281                          clientTrace.length);
 282         ex.setStackTrace(combinedTrace);
 283 
 284         /*
 285          * Log the details of a server exception thrown as a result of a
 286          * remote method invocation.
 287          */
 288         if (UnicastRef.clientCallLog.isLoggable(Log.BRIEF)) {
 289             /* log call exception returned from server before it is rethrown */
 290             TCPEndpoint ep = (TCPEndpoint) conn.getChannel().getEndpoint();
 291             UnicastRef.clientCallLog.log(Log.BRIEF, "outbound call " +
 292                 "received exception: [" + ep.getHost() + ":" +
 293                 ep.getPort() + "] exception: ", ex);
 294         }
 295 
 296         throw ex;
 297     }
 298 
 299     /*
 300      * method to retrieve possible server side exceptions (which will
 301      * be throw from exceptionReceivedFromServer(...) )
 302      */
 303     public Exception getServerException() {
 304         return serverException;
 305     }
 306 
 307     public void done() throws IOException {
 308         /* WARNING: Currently, the UnicastRef.java invoke methods rely
 309          * upon this method not throwing an IOException.
 310          */
 311 
 312         releaseInputStream();
 313     }
 314 }