[
https://issues.apache.org/jira/browse/FELIX-6861?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul Rütter resolved FELIX-6861.
--------------------------------
Fix Version/s: framework-8.0.0
Resolution: Fixed
> 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
> Priority: Major
> Fix For: framework-8.0.0
>
>
> {{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)