John Irish created FELIX-6861:
---------------------------------
Summary: URLHandlersStreamHandlerProxy.openConnection(URL) wraps
handler IOException in IllegalStateException
Key: FELIX-6861
URL: https://issues.apache.org/jira/browse/FELIX-6861
Project: Felix
Issue Type: Bug
Components: Framework
Affects Versions: framework-7.0.5
Reporter: John Irish
{{org.apache.felix.framework.URLHandlersStreamHandlerProxy.openConnection(URL)}}
does not preserve an {{IOException}} thrown by a stream handler when the
handler is invoked through reflection. This issue completely breaks normal
error handling for `URL.openConnection(URL)`.
The reflective invocation:
{{return (URLConnection) OPEN_CONNECTION.invoke(svc, new Object[]\{url});}}
wraps an exception thrown by the handler in {{{}InvocationTargetException{}}}.
Consequently, the existing {{catch (IOException ex)}} does not handle the
underlying {{{}IOException{}}}. Instead, the generic {{catch (Exception ex)}}
wraps the {{InvocationTargetException}} in an {{{}IllegalStateException{}}}.
This produces the following exception chain:
{{IllegalStateException}}
{{ caused by InvocationTargetException}}
{{ caused by IOException}}
In contrast, the direct {{URLStreamHandlerService}} invocation propagates the
original {{{}IOException{}}}. Exception behavior therefore differs depending on
which invocation path is used.
h3. Expected behavior
If the stream handler throws an {{{}IOException{}}}, including a subclass such
as {{{}FileNotFoundException{}}}, {{openConnection(URL)}} should propagate that
original exception.
h3. Actual behavior
On the reflective invocation path, the original {{IOException}} is wrapped in
{{InvocationTargetException}} and then in {{{}IllegalStateException{}}}.
h3. Impact
Callers handling connection failures with {{catch (IOException ex)}} do not
catch these failures. They also cannot handle specific {{IOException}}
subclasses as expected.
h3. Reproduction / regression test
# Arrange for {{getStreamHandlerService()}} to return a handler that uses the
reflective invocation path, rather than the direct {{URLStreamHandlerService}}
branch.
# Have that handler’s {{openConnection(URL)}} throw a known {{IOException}}
instance.
# Invoke the proxy’s {{{}openConnection(URL){}}}.
# Verify that the same {{IOException}} instance is propagated.
Currently, this path throws {{IllegalStateException}} instead.
Additional tests should cover an {{IOException}} subclass and confirm that
non-IO invocation failures retain the existing behavior.
h3. Suggested fix
Catch {{InvocationTargetException}} before the generic exception handler and
rethrow its cause when that cause is an {{{}IOException{}}}.
The extension-handler branch should also be reviewed: its inner generic catch
can intercept reflective invocation failures, and {{new
IOException(ex.getMessage())}} discards the original cause.
----
h2. Potential method improvement
The following changes preserve the original {{IOException}} on both reflective
paths while retaining the existing wrapping policy for other exceptions.
*Replace the inner catch sequence in the extension-handler branch with:*
{{}}
{code:java}
catch (IOException ex)
{
throw ex;
}
catch (InvocationTargetException ex)
{
Throwable cause = ex.getCause();
if (cause instanceof IOException)
{
throw (IOException) cause;
}
// Keep the existing IOException wrapping policy, but retain the cause.
throw new IOException(ex.getMessage(), ex);
}
catch (Exception ex)
{
throw new IOException(ex.getMessage(), ex);
}{code}
*Replace the outer catch sequence with:*
{code:java}
catch (IOException ex)
{
throw ex;
}
catch (InvocationTargetException ex)
{
Throwable cause = ex.getCause();
if (cause instanceof IOException)
{
throw (IOException) cause;
}
// Preserve existing behavior for non-IO invocation failures.
throw new IllegalStateException(
"Stream handler unavailable due to: " + ex.getMessage(), ex);
}
catch (Exception ex)
{
throw new IllegalStateException(
"Stream handler unavailable due to: " + ex.getMessage(), ex);
} {code}
The *outer change is the minimal fix* for the reported
{{OPEN_CONNECTION.invoke(...)}} problem. The inner change additionally
preserves causes in the extension-handler path and handles
{{InvocationTargetException}} if it is propagated by
{{{}m_action.invoke(...){}}}.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)