Skip to content

Commit 70f978f

Browse files
ngmrjoe-chacko
andcommitted
refactor(yoko-core): defer portable interceptor invocation until first read
- Changed DowncallStub.invoke() to return WrappedReplyInputStream instead of calling postUnmarshal() immediately - Created WrappedReplyInputStream wrapper that intercepts first read operation to trigger interceptors - Ensures interceptors are called AFTER stub unmarshals first value, making result available to interceptors - Properly reports unmarshalling exceptions via receive_exception interception point - Updated CmsfTest, RoflTest, and YasfTest to reflect new behavior where thread-local options are set during unmarshalling - Lines added: 192, lines removed: 24 Co-authored-by: Joe Chacko <chackoj@uk.ibm.com> Co-authored-by-AI: IBM Bob 1.0.4
1 parent e1884c6 commit 70f978f

5 files changed

Lines changed: 192 additions & 24 deletions

File tree

yoko-core/src/main/java/org/apache/yoko/orb/OB/DowncallStub.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ public void AMIRouterPostMarshal(GIOPOutgoingMessage outgoing, OutputStreamHolde
546546
//
547547
// Invoke a request from a portable stub
548548
//
549-
public YokoInputStream invoke(
549+
public org.omg.CORBA.portable.InputStream invoke(
550550
org.omg.CORBA.Object self,
551551
YokoOutputStream out)
552552
throws ApplicationException,
@@ -610,13 +610,11 @@ public YokoInputStream invoke(
610610
throw new ApplicationException(id, in);
611611
} else {
612612
//
613-
// We're using portable stubs, so we'll never
614-
// know the unmarshalled results. Therefore,
615-
// we might as well invoke the interceptors now.
613+
// Wrap the InputStream to trigger interceptors after the first read.
614+
// This ensures the result is available to interceptors and unmarshalling
615+
// exceptions are properly reported via receive_exception.
616616
//
617-
down.postUnmarshal();
618-
619-
return in;
617+
return new WrappedReplyInputStream(in, down);
620618
}
621619
} else {
622620
down.preUnmarshal();
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2026 IBM Corporation and others.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
package org.apache.yoko.orb.OB;
19+
20+
import org.apache.yoko.orb.CORBA.YokoInputStream;
21+
import org.omg.CORBA.Any;
22+
import org.omg.CORBA.Context;
23+
import org.omg.CORBA.INTERNAL;
24+
import org.omg.CORBA.ORB;
25+
import org.omg.CORBA.Principal;
26+
import org.omg.CORBA.SystemException;
27+
import org.omg.CORBA.TypeCode;
28+
import org.omg.CORBA.portable.BoxedValueHelper;
29+
import org.omg.CORBA_2_3.portable.InputStream;
30+
31+
import java.io.Serializable;
32+
import java.math.BigDecimal;
33+
import java.util.function.Supplier;
34+
35+
import static org.apache.yoko.util.Exceptions.as;
36+
37+
/**
38+
* Wrapper for InputStream that intercepts the first read operation to trigger
39+
* portable interceptor receive_reply/receive_exception interception points.
40+
* <p>
41+
* This ensures interceptors are called AFTER the stub has unmarshalled at least
42+
* the first value, making the result available to interceptors and ensuring
43+
* unmarshalling exceptions are properly reported via receive_exception.
44+
*/
45+
final class WrappedReplyInputStream extends InputStream {
46+
private final YokoInputStream yokoStream;
47+
private final Downcall downcall;
48+
private boolean readYet;
49+
50+
WrappedReplyInputStream(YokoInputStream yokoStream, Downcall downcall) {
51+
this.yokoStream = yokoStream;
52+
this.downcall = downcall;
53+
}
54+
55+
/**
56+
* Wraps a read operation with first-read interception logic.
57+
* Handles both value-returning and void operations uniformly.
58+
*/
59+
private <T> T wrapRead(Supplier<T> readOp) {
60+
try {
61+
T result = readOp.get();
62+
triggerInterceptorsOnFirstRead();
63+
return result;
64+
} catch (SystemException ex) {
65+
return handleFirstReadException(ex);
66+
}
67+
}
68+
69+
/**
70+
* Wraps a void read operation (array reads) with first-read interception logic.
71+
*/
72+
private void wrapVoidRead(Runnable readOp) {
73+
try {
74+
readOp.run();
75+
triggerInterceptorsOnFirstRead();
76+
} catch (SystemException ex) {
77+
handleFirstReadException(ex);
78+
}
79+
}
80+
81+
/**
82+
* Called before the first read operation returns to trigger interceptors.
83+
* Subsequent reads bypass this check.
84+
*/
85+
private void triggerInterceptorsOnFirstRead() throws SystemException {
86+
if (readYet) return;
87+
readYet = true;
88+
89+
try {
90+
downcall.postUnmarshal();
91+
} catch (LocationForward ex) {
92+
throw as(INTERNAL::new, ex, "Location forward during postUnmarshal");
93+
} catch (FailureException ex) {
94+
throw as(INTERNAL::new, ex, "Failure during postUnmarshal");
95+
}
96+
}
97+
98+
/**
99+
* Handles SystemException during first read by reporting to interceptors
100+
* via receive_exception before propagating the exception.
101+
*/
102+
private <T> T handleFirstReadException(SystemException ex) throws SystemException {
103+
if (readYet) throw ex;
104+
readYet = true;
105+
try {
106+
downcall.unmarshalEx(ex);
107+
downcall.postUnmarshal();
108+
throw ex;
109+
} catch (LocationForward e) {
110+
throw as(INTERNAL::new, e, "Location forward during exception handling");
111+
} catch (FailureException ignored) {
112+
// FailureException is expected when interceptors detect issues during exception handling
113+
// Let it propagate naturally - it will be converted to appropriate CORBA exception
114+
throw ex;
115+
}
116+
}
117+
118+
// Primitive read methods
119+
@Override public boolean read_boolean() { return wrapRead(yokoStream::read_boolean); }
120+
@Override public char read_char() { return wrapRead(yokoStream::read_char); }
121+
@Override public char read_wchar() { return wrapRead(yokoStream::read_wchar); }
122+
@Override public byte read_octet() { return wrapRead(yokoStream::read_octet); }
123+
@Override public short read_short() { return wrapRead(yokoStream::read_short); }
124+
@Override public short read_ushort() { return wrapRead(yokoStream::read_ushort); }
125+
@Override public int read_long() { return wrapRead(yokoStream::read_long); }
126+
@Override public int read_ulong() { return wrapRead(yokoStream::read_ulong); }
127+
@Override public long read_longlong() { return wrapRead(yokoStream::read_longlong); }
128+
@Override public long read_ulonglong() { return wrapRead(yokoStream::read_ulonglong); }
129+
@Override public float read_float() { return wrapRead(yokoStream::read_float); }
130+
@Override public double read_double() { return wrapRead(yokoStream::read_double); }
131+
@Override public String read_string() { return wrapRead(yokoStream::read_string); }
132+
@Override public String read_wstring() { return wrapRead(yokoStream::read_wstring); }
133+
134+
// Array read methods
135+
@Override public void read_boolean_array(boolean[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_boolean_array(v, o, l)); }
136+
@Override public void read_char_array(char[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_char_array(v, o, l)); }
137+
@Override public void read_wchar_array(char[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_wchar_array(v, o, l)); }
138+
@Override public void read_octet_array(byte[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_octet_array(v, o, l)); }
139+
@Override public void read_short_array(short[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_short_array(v, o, l)); }
140+
@Override public void read_ushort_array(short[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_ushort_array(v, o, l)); }
141+
@Override public void read_long_array(int[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_long_array(v, o, l)); }
142+
@Override public void read_ulong_array(int[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_ulong_array(v, o, l)); }
143+
@Override public void read_longlong_array(long[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_longlong_array(v, o, l)); }
144+
@Override public void read_ulonglong_array(long[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_ulonglong_array(v, o, l)); }
145+
@Override public void read_float_array(float[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_float_array(v, o, l)); }
146+
@Override public void read_double_array(double[] v, int o, int l) { wrapVoidRead(() -> yokoStream.read_double_array(v, o, l)); }
147+
148+
// Complex type read methods
149+
@Override public org.omg.CORBA.Object read_Object() { return wrapRead(yokoStream::read_Object); }
150+
@Override public TypeCode read_TypeCode() { return wrapRead(yokoStream::read_TypeCode); }
151+
@Override public Any read_any() { return wrapRead(yokoStream::read_any); }
152+
@Override @Deprecated public Principal read_Principal() { return wrapRead(yokoStream::read_Principal); }
153+
@Override public BigDecimal read_fixed() { return wrapRead(yokoStream::read_fixed); }
154+
@SuppressWarnings("rawtypes")
155+
@Override public org.omg.CORBA.Object read_Object(Class clz) { return wrapRead(() -> yokoStream.read_Object(clz)); }
156+
@Override public Context read_Context() { return wrapRead(yokoStream::read_Context); }
157+
158+
// CORBA 2.3 methods
159+
@Override public Serializable read_value() { return wrapRead(yokoStream::read_value); }
160+
@SuppressWarnings("rawtypes")
161+
@Override public Serializable read_value(Class clz) { return wrapRead(() -> yokoStream.read_value(clz)); }
162+
@Override public Serializable read_value(BoxedValueHelper factory) { return wrapRead(() -> yokoStream.read_value(factory)); }
163+
@Override public Serializable read_value(String rep_id) { return wrapRead(() -> yokoStream.read_value(rep_id)); }
164+
@Override public Serializable read_value(Serializable value) { return wrapRead(() -> yokoStream.read_value(value)); }
165+
@Override public Object read_abstract_interface() { return wrapRead(yokoStream::read_abstract_interface); }
166+
@SuppressWarnings("rawtypes")
167+
@Override public Object read_abstract_interface(Class clz) { return wrapRead(() -> yokoStream.read_abstract_interface(clz)); }
168+
169+
// java.io.InputStream method - wrapped for transparency
170+
@Override public int read() { return wrapRead(yokoStream::read); }
171+
172+
// Passthrough method (no interception needed - doesn't read data)
173+
@Override public ORB orb() { return yokoStream.orb(); }
174+
}

yoko-verify/src/test/java-testify/org/apache/yoko/util/cmsf/CmsfTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2025 IBM Corporation and others.
2+
* Copyright 2026 IBM Corporation and others.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,10 +37,9 @@ public static final class Message implements Serializable {
3737
private final String text;
3838
public Message(String text) { this.text = text; }
3939
private void readObject(ObjectInputStream in) throws Exception {
40-
// Cmsf is only set when writing out.
41-
// All options default when reading in,
42-
// so the thread should have Cmsf set to null.
43-
assertEquals(1, CmsfThreadLocal.get());
40+
// Since we are marshalling from another Yoko ORB,
41+
// Cmsf options should be set when reading in.
42+
assertEquals(2, CmsfThreadLocal.get());
4443
in.defaultReadObject();
4544
}
4645
private void writeObject(ObjectOutputStream out) throws Exception {

yoko-verify/src/test/java-testify/org/apache/yoko/util/rofl/RoflTest.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,14 @@ public static final class Message implements Serializable {
5555
private final String text;
5656
public Message(String text) { this.text = text; }
5757
private void readObject(ObjectInputStream in) throws Exception {
58-
// Yasf is only set when writing out.
59-
// All options default when reading in,
60-
// so the thread should have Yasf set to null.
61-
assertEquals(Rofl.NONE, RoflThreadLocal.get());
58+
// Since we are marshalling from another ORB purporting to be IBM,
59+
// Rofl options should be set when reading in.
60+
assertEquals(IBM, RoflThreadLocal.get().type());
6261
in.defaultReadObject();
6362
}
6463
private void writeObject(ObjectOutputStream out) throws Exception {
65-
// Since we are marshalling to another Yoko ORB,
66-
// Yasf options should be set when writing out.
64+
// Since we are marshalling to another ORB purporting to be IBM,
65+
// Rofl options should be set when writing out.
6766
assertEquals(IBM, RoflThreadLocal.get().type());
6867
out.defaultWriteObject();
6968
}

yoko-verify/src/test/java-testify/org/apache/yoko/util/yasf/YasfTest.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2025 IBM Corporation and others.
2+
* Copyright 2026 IBM Corporation and others.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@
3030

3131
import static org.junit.jupiter.api.Assertions.assertEquals;
3232
import static org.junit.jupiter.api.Assertions.assertNotNull;
33-
import static org.junit.jupiter.api.Assertions.assertNull;
3433

3534
@ConfigureServer
3635
public class YasfTest {
@@ -39,10 +38,9 @@ public static final class Message implements Serializable {
3938
private final String text;
4039
public Message(String text) { this.text = text; }
4140
private void readObject(ObjectInputStream in) throws Exception {
42-
// Yasf is only set when writing out.
43-
// All options default when reading in,
44-
// so the thread should have Yasf set to null.
45-
assertNull(YasfThreadLocal.get());
41+
// Since we are marshalling from another Yoko ORB,
42+
// Yasf options should be set when reading in.
43+
assertNotNull(YasfThreadLocal.get());
4644
in.defaultReadObject();
4745
}
4846
private void writeObject(ObjectOutputStream out) throws Exception {

0 commit comments

Comments
 (0)