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      */
 202     public void executeCall() throws Exception {
 203         byte returnType;
 204 
 205         // read result header
 206         DGCAckHandler ackHandler = null;
 207         try {
 208             if (out != null) {
 209                 ackHandler = out.getDGCAckHandler();
 210             }
 211             releaseOutputStream();
 212             DataInputStream rd = new DataInputStream(conn.getInputStream());
 213             byte op = rd.readByte();
 214             if (op != TransportConstants.Return) {
 215                 if (Transport.transportLog.isLoggable(Log.BRIEF)) {
 216                     Transport.transportLog.log(Log.BRIEF,
 217                         "transport return code invalid: " + op);
 218                 }
 219                 throw new UnmarshalException("Transport return code invalid");
 220             }
 221             getInputStream();
 222             returnType = in.readByte();
 223             in.readID();        // id for DGC acknowledgement
 224         } catch (UnmarshalException e) {
 225             throw e;
 226         } catch (IOException e) {
 227             throw new UnmarshalException("Error unmarshaling return header",
 228                                          e);
 229         } finally {
 230             if (ackHandler != null) {
 231                 ackHandler.release();
 232             }
 233         }
 234 
 235         // read return value
 236         switch (returnType) {
 237         case TransportConstants.NormalReturn:
 238             break;
 239 
 240         case TransportConstants.ExceptionalReturn:
 241             Object ex;
 242             try {
 243                 ex = in.readObject();
 244             } catch (Exception e) {
 245                 throw new UnmarshalException("Error unmarshaling return", e);
 246             }
 247 
 248             // An exception should have been received,
 249             // if so throw it, else flag error
 250             if (ex instanceof Exception) {
 251                 exceptionReceivedFromServer((Exception) ex);
 252             } else {
 253                 throw new UnmarshalException("Return type not Exception");
 254             }
 255         default:
 256             if (Transport.transportLog.isLoggable(Log.BRIEF)) {
 257                 Transport.transportLog.log(Log.BRIEF,
 258                     "return code invalid: " + returnType);
 259             }
 260             throw new UnmarshalException("Return code invalid");
 261         }
 262     }
 263 
 264     /**
 265      * Routine that causes the stack traces of remote exceptions to be
 266      * filled in with the current stack trace on the client.  Detail
 267      * exceptions are filled in iteratively.
 268      */
 269     protected void exceptionReceivedFromServer(Exception ex) throws Exception {
 270         serverException = ex;
 271 
 272         StackTraceElement[] serverTrace = ex.getStackTrace();
 273         StackTraceElement[] clientTrace = (new Throwable()).getStackTrace();
 274         StackTraceElement[] combinedTrace =
 275             new StackTraceElement[serverTrace.length + clientTrace.length];
 276         System.arraycopy(serverTrace, 0, combinedTrace, 0,
 277                          serverTrace.length);
 278         System.arraycopy(clientTrace, 0, combinedTrace, serverTrace.length,
 279                          clientTrace.length);
 280         ex.setStackTrace(combinedTrace);
 281 
 282         /*
 283          * Log the details of a server exception thrown as a result of a
 284          * remote method invocation.
 285          */
 286         if (UnicastRef.clientCallLog.isLoggable(Log.BRIEF)) {
 287             /* log call exception returned from server before it is rethrown */
 288             TCPEndpoint ep = (TCPEndpoint) conn.getChannel().getEndpoint();
 289             UnicastRef.clientCallLog.log(Log.BRIEF, "outbound call " +
 290                 "received exception: [" + ep.getHost() + ":" +
 291                 ep.getPort() + "] exception: ", ex);
 292         }
 293 
 294         throw ex;
 295     }
 296 
 297     /*
 298      * method to retrieve possible server side exceptions (which will
 299      * be throw from exceptionReceivedFromServer(...) )
 300      */
 301     public Exception getServerException() {
 302         return serverException;
 303     }
 304 
 305     public void done() throws IOException {
 306         /* WARNING: Currently, the UnicastRef.java invoke methods rely
 307          * upon this method not throwing an IOException.
 308          */
 309 
 310         releaseInputStream();
 311     }
 312 }