[ 
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)

Reply via email to