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)

Reply via email to