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