Do a HTTP POST instead of a GET - java

The WebService I'm trying to use is SOAP and receives only the HTTP method POST on its requests, but for some reason, I'm not being able to make that happen and it's sending only GET.
I've been loosely following this guide: https://spring.io/guides/gs/consuming-web-service/
I've thought that it wouldn’t be the case of the wrong method, but I've tried via Postman with the logged envelope and it worked fine, also this:
debugger image with GET
This is how I've done it:
package com.example.ws;
import com.demo.partner.wsdl.ObjectFactory;
import com.demo.partner.wsdl.StoreInformation;
import com.example.util.PartnerWSHttpHeaderCallBack;
import com.example.util.SoapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
public class StoreInformationClient extends WebServiceGatewaySupport {
private static final Logger log = LoggerFactory.getLogger(StoreInformationClient.class);
#Autowired
private ObjectFactory objectFactory;
public StoreInformation getStoreInformation() {
StoreInformation storeInformation = objectFactory.createStoreInformation();
storeInformation.setStoreID("99633");
return (StoreInformation) getWebServiceTemplate()
.marshalSendAndReceive("https://aurl.com/api/ws.wsdl", SoapUtils.buildEnvelope(storeInformation, StoreInformation.class),
new PartnerWSHttpHeaderCallBack());
}
}
package com.example.configuration;
import com.demo.partner.wsdl.ObjectFactory;
import com.example.ws.StoreInformationClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
#Configuration
public class AppConfiguration {
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.demo.partner.wsdl");
return marshaller;
}
#Bean
public StoreInformationClient countryClient(Jaxb2Marshaller marshaller) {
StoreInformationClient client = new StoreInformationClient();
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
#Bean
public ObjectFactory objectFactory() {
return new ObjectFactory();
}
}
package com.example.util;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.transport.WebServiceConnection;
import org.springframework.ws.transport.context.TransportContext;
import org.springframework.ws.transport.context.TransportContextHolder;
import org.springframework.ws.transport.http.HttpUrlConnection;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class PartnerWSHttpHeaderCallBack implements WebServiceMessageCallback {
#Override
public void doWithMessage(WebServiceMessage webServiceMessage) {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Basic definitelyABase64value");
headers.put("SOAPAction", "http://www.partner.com/action/GetStoreInformation");
headers.put("Content-Type", "text/xml");
addRequestHeader(headers);
}
private void addRequestHeader(Map<String, String> headers) {
TransportContext context = TransportContextHolder.getTransportContext();
WebServiceConnection connection = context.getConnection();
if (connection instanceof HttpUrlConnection) {
HttpUrlConnection conn = (HttpUrlConnection) connection;
for (Map.Entry<String, String> entry : headers.entrySet()) {
String k = entry.getKey();
String v = entry.getValue();
try {
conn.addRequestHeader(k, v);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

Had a look at both 6 api and 8 api docs for spring and find that way is almost impossible (if not it is impossible) because as you are finding there does not appear to be a method in that hierarchy set of classes and interfaces (IN YOUR FORM OF CODE) a sensible coherent way of converting the http connection to "POST" , it will only send "GET".
I do understand that the HttpUrlConnection is not java.net and is an org.spring. but the spring connections must be obtained through extensions and the instance context of the connected objects.
the spring framework documentation seems erroneous also because it shows no-where org.springframework.ws.transport.TransportConstants and org.springframework.ws.transport.http.HttpTransportConstants are implemented.
Niether taking a look searching the web.
All i can suggest "trying" is this however "Spring.org project" needs to clearly document how to set the constants for those interfaces AND if anything actually implements them !
/*
org.springframework.ws.transport.TransportConstants
import org.springframework.ws.transport.http.HttpTransportConstants
*/
HttpTransportConstants httCxt = ((HttpTransportConstants)TransportContextHolder.getTransportContext());
// perhaps httCxt."METHOD_POST";
httCxt.METHOD_POST;
// no really, there should be an implementing class in the .transport packages that uses and sets this however i could not locate one throughout the ws classes and interfaces.
// all interface variables are static and final (because interfaces are abstract they can be cast together)
TransportContext context = (TransportContext)httCxt;
Here is an article that seems to have some way alike
https://pretagteam.com/question/soap-request-to-webservice-with-java
however i think they use a java.net HttpURLConnection
There is a similar HttpURLConnection object in spring
*** The spring docs also do not show full hierarchy on identical classes and interfaces**
org.springframework.ws.transport.http
Class HttpUrlConnection IMPLEMENTS org.springframework.ws.transport
Interface WebServiceConnection
The following worked many years back for applets and since finding ambiguous docs naming it appears this would work (or be immensely close)
To make a org.springframework.ws.transport.http.HttpUrlConnection
make a protected method
protected org.springframework.ws.transport.http.HttpUrlConnection makeConnHttp(String uuu){
java.net.URL urli = new java.net.URL(uuu);
Java.net.UrlConnection urlcon = new Java.net.UrlConnection(urli);
Java.net.HttpUrlConnection netUrl = (Java.net.HttpUrlConnection)urlcon;
netUrl.setRequestMethod("POST");
org.springframework.ws.transport.http.HttpUrlConnection wsUrlConn = new org.springframework.ws.transport.http.HttpUrlConnection(netUrl);
return (org.springframework.ws.transport.http.HttpUrlConnection)wsUrlConn;
}
public org.springframework.ws.transport.http.HttpUrlConnection getWSconnectionHttp(String uuu){
return (org.springframework.ws.transport.http.HttpUrlConnection)makeConnHttp(uuu);
}
// spring 2.1.4 HttpUrlConnection is also a WebServiceConnection
org.springframework.ws.transport.WebServiceConnection wdConnsvc = (org.springframework.ws.transport.WebServiceConnection)getWSconnectionHttp(uuu);
It is not known to me whether the api process calls open()/close() on UrLConnection or that has to be done manually.
Where your code goes wrong
Notice how your code gets a TransportContext and it gets a WebServiceConnection.
That produced object instance is a SEPERATE object instance not related to the later constructed spring framework HttpUrlConnection.
You could open the connection but it is not the WebServiceConnection object constructed with the transport context.
The problem is the org spring HttpUrlConnection IS( a WebServiceConnection) also by polymorphism , so you do not need to obtain a WebServiceConnection because you would have that from creating a org.spring HttpUrlConnection.
However an org spring HttpUrlConnection must be fed an implementing coded java.net.HttpUrlConnection for the actual networking, the org spring HttpUrlConnection is more of a joiner wrapper for obtaining and managing data bound to the java.net.HttpUrlConnection and the containers registrations of the service endpoints.
Your code would require joining the two parts by replacing any instance of org.springframework....HttpUrlConnection with an/or-the externally created HttpUrlConnection using lava.lang.reflect.
However, the best method for binding objectes in that problem is extend from the class org.springframework.ws.transport.http.HttpUrlConnection to prevent binding and access problems.

Related

Migrate from HystrixCommand to Resilience4j

Resilience4j version: 1.7.0
Java version: 1.8
I have challenge in implementing TimeLimiter feature of Resilience4j. I am able to get the Circuit Breaker (CB) work.
We have 2 services Lets say serviceA and serviceB. We have used Command design pattern which encapsulates logic to communicate with ServiceB. RabbitMQ is used to establish inter microservice communication. We had implemented Hystrix CB by making all our Command classes extend HystrixCommand. When we decided to move to Resilience4j main challenge was to retain the existing design pattern than configuring Resilence4J CB.
We have Synchronous communication at present between ServiceA and ServiceB. Though we use RabbitMQ to communicate which is Async communication, with the help of Spring wrapper method RabbitTemplate.convertSendAndReceive() we are able to achieve Sync mode of communication with RabbitMQ.
When I removed HystrixCommand reference which was the Base class for all my Command classes, naturally there was a need to implement a custom Base Command class which will be implemented using Resilience4J Decorators.
I managed introduce a Resilience4JCommand abstract class which will implement a execute() and execute run() from all my command classes. Also defined a abstract run() which all my existing Command classes will override and implement business logic.
I understood from many of the discussion that our method which needs to implement CB pattern needs to return of type CompletableFuture and also understood from many places that fallback method also must have same return type. My Base Command Class Resilience4JCommand looks something like below
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import com.ge.hc.XYZ.exception.ResourceNotFoundException;
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
import io.github.resilience4j.bulkhead.annotation.Bulkhead.Type;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
#Component
public abstract class Resilience4JCommand<R> {
/** The class logger. */
protected static final Logger LOGGER = LoggerFactory.getLogger(Resilience4JCommand.class);
public R execute() {
R result = null;
try {
result = executeWithCircuitBreaker().get();
} catch (Exception e) {
System.out.println("Inside Catch block of executeAsync ...........**************\n\n ");
e.printStackTrace();
throw new RuntimeException(e);
}
return result;
}
#Bulkhead(name = "XYZStreamingServer3", fallbackMethod = "getFallback", type = Bulkhead.Type.THREADPOOL)
#TimeLimiter(name = "XYZStreamingServer2", fallbackMethod = "getFallback")
#CircuitBreaker(name = "XYZStreamingServer1", fallbackMethod = "getFallback")
public CompletableFuture<R> executeWithCircuitBreaker() {
return CompletableFuture.supplyAsync(new Supplier<R>() {
#Override
public R get() {
return run();
}
});
}
protected abstract R run();
public CompletableFuture<R> getFallback(Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
if (e != null) {
e.printStackTrace(pw);
}
String reason = sw.toString();
LOGGER.error("Calling XYZ-hystrix fallback method for command: {}; fallback reason: {}",
this.getClass().getSimpleName(), (reason.isEmpty() ? "unknown" : reason));
throw new ResourceNotFoundException("Circuit Breaker ");
}
}
But nothing works with above setup. I am able to achieve CB alone work without the need of writing new method executeWithCircuitBreaker() which returns CompletableFuture. I can make CB work just with below execute()
Bulkhead AND TimeLimiter do not work with return type other than CompletableFuture
#CircuitBreaker(name = SCHEME_NAME, fallbackMethod = "getFallback")
public R execute() {
return run();
}
I have spent more than a week in setting up this .. Helpful if someone can point me what I am missing 😢
My application.properties looks something like belwo
management.health.circuitbreakers.enabled=true
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=always
resilience4j.circuitbreaker.instances.XYZStreamingServer1.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.XYZStreamingServer1.eventConsumerBufferSize=10
resilience4j.circuitbreaker.instances.XYZStreamingServer1.failureRateThreshold=50
resilience4j.circuitbreaker.instances.XYZStreamingServer1.minimumNumberOfCalls=5
resilience4j.circuitbreaker.instances.XYZStreamingServer1.automaticTransitionFromOpenToHalfOpenEnabled=true
resilience4j.circuitbreaker.instances.XYZStreamingServer1.waitDurationInOpenState=5s
resilience4j.circuitbreaker.instances.XYZStreamingServer1.permittedNumberOfCallsInHalfOpenState=3
resilience4j.circuitbreaker.instances.XYZStreamingServer1.slidingWindowSize=10
resilience4j.circuitbreaker.instances.XYZStreamingServer1.slidingWindowType=COUNT_BASED
resilience4j.timelimiter.instances.XYZStreamingServer2.timeoutDuration=5s
resilience4j.timelimiter.instances.XYZStreamingServer2.cancelRunningFuture=true
resilience4j.thread-pool-bulkhead.instances.XYZStreamingServer3.maxThreadPoolSize=10
resilience4j.thread-pool-bulkhead.instances.XYZStreamingServer3.coreThreadPoolSize=5
resilience4j.thread-pool-bulkhead.instances.XYZStreamingServer3.queueCapacity=5

unable to use Stripe API from a Java Jersey Jackson REST API due to snake case to camel case conversion

On the server side, I develop a REST API with Java and Jersey / Jackson, and this API makes calls to the Stripe API.
The Stripe API returns all objects with properties names in snake case, such as client_secret for class PaymentIntent.
The JSON returned by my REST API using Jersey automatically converts these properties to camel case, with names such as clientSecret.
On the client side, I use the Stripe JS library, which also expects properties names in snake case, and therefore I get errors when I try to read properties of objects returned by my REST API.
I have seen many posts about configuring Jersey to use snake case instead of camel case, but I have not been able to apply what I found to my use case, which is using camel case for my own classes, and snake case for Stripe classes I have no control on.
Here is my current code on the server site:
package com.knowledgeplaces.metalmsapi.resources;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.knowledgeplaces.metalmsapi.records.PaymentIntentArgsRec;
import com.knowledgeplaces.metalmsapi.records.PaymentIntentResponseRec;
import com.knowledgeplaces.metalmsapi.utils.MetaLmsConstants;
import com.stripe.Stripe;
import com.stripe.model.PaymentIntent;
import com.stripe.exception.StripeException;
import com.stripe.net.ApiResource;
#Path("/eShops/{eShopId}/stripePaymentIntent")
public class StripePaymentIntentRest {
// request one Payment Intent
#POST
#Produces({ MediaType.APPLICATION_JSON })
public PaymentIntentResponseRec createService(
#Context HttpServletRequest req,
#PathParam("eShopId") Integer eShopId,
PaymentIntentArgsRec paymentIntentArgs) {
PaymentIntent paymentIntent;
PaymentIntentResponseRec paymentIntentResponse;
// get Stripe API secret key
if (paymentIntentArgs.stripeTestMode()) {
Stripe.apiKey = "***********************";
} else {
Stripe.apiKey = "***********************";
}
// create Payment Intent
List<Object> paymentMethodTypes = new ArrayList<>();
paymentMethodTypes.add("card");
Map<String, Object> params = new HashMap<>();
params.put("amount", paymentIntentArgs.amount());
params.put("currency", paymentIntentArgs.currency());
params.put(
"payment_method_types",
paymentMethodTypes);
try {
// paymentIntent = PaymentIntent.create(params);
paymentIntent = ApiResource.GSON.fromJson(PaymentIntent.create(params).toJson(), PaymentIntent.class);
paymentIntentResponse = new PaymentIntentResponseRec(null, paymentIntent);
} catch (StripeException ex) {
paymentIntentResponse = new PaymentIntentResponseRec(MetaLmsConstants.StripeApiError, null);
}
return paymentIntentResponse;
}
}
With this code, I get a Payment Intent object with properties in camel case.
If I uncomment the line
paymentIntent = PaymentIntent.create(params);
And comment the line
paymentIntent = ApiResource.GSON.fromJson(PaymentIntent.create(params).toJson(), PaymentIntent.class);
Then I get the following error:
No serializer found for class com.stripe.net.StripeResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.knowledgeplaces.metalmsapi.records.PaymentIntentResponseRec["paymentIntent"]->com.stripe.model.PaymentIntent["lastResponse"])
Please advise on how to get rid off this error.
I have reviewed my code and here is a working solution:
try {
paymentIntent = PaymentIntent.create(params);
String paymentIntentJson = paymentIntent.toJson();
stringResponse = new StringResponseRec(null, paymentIntentJson);
} catch (StripeException ex) {
stringResponse = new StringResponseRec(ex.getMessage(), null);
}
return stringResponse;
The problem was related to snake case support in Jersey Jackson or records in Java 16, I don't know.
So, instead of loading a record of type PaymentIntentResponseRec which has a Stripe PaymentIntent object as a property, I load a record of type StringResponseRec which has a property of type string, and I load that string from the paymentIntent.toJson() provided by the Stripe API.
On the client side, my Angular app which uses the Stripe JS library gets my PaymentIntent object fine.
It works, but if you think there is a more elegant solution, feel free to comment.

Quarkus Mutiny Web Client Decode JSON safely

I've been getting into Quarkus and trying to utilize the Mutiny Vertx WebClient. My code works but I prefer not to have to rely on unsafe/unchecked assignments which is how I've currently written the code with bodyAsJson method on HttpResponse. Is there a better way, or more standard way to decode JSON from the Mutiny Vertx client? I realize I could just call bodyAsJsonObject and return that, but I need to do processing on the data the comes back from API calls so I need to decode it to a class representing the data shape/structure.
package com.something.app.language;
import com.something.app.model.Language;
import io.micrometer.core.annotation.Timed;
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.core.Vertx;
import io.vertx.mutiny.ext.web.client.WebClient;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.List;
#ApplicationScoped
public class LanguageService {
#Inject
Vertx vertx;
private WebClient client;
#PostConstruct
void init() {
this.client = WebClient.create(vertx);
}
#Timed
public Uni<List<Language>> getLanguages() {
return this.client
.get(80, "somehost.com", "/languages")
.timeout(1000)
.send()
.onItem()
.transform(resp -> {
if (resp.statusCode() == 200) {
return resp.bodyAsJson(List.class);
} else {
throw new RuntimeException("");
}
});
}
}
There are several ways. First, Vert.x uses Jackson under the hood, so everything that can be done with Jackson is possible.
You can use resp.bodyAsJson(MyStructure.class), which would create an instance of MyStructure.
If you have a JSON Array, you can map every element to the object class.
Finally, you can implement your own body codec (See https://vertx.io/docs/apidocs/io/vertx/ext/web/codec/BodyCodec.html).

Adding REST route to an existing Jetty endpoint in Camel at runtime

I have been inventing a way how to work around the problem of adding consumers to a jetty endpoint (it does not allow multiple consumers). The way we do it in our company is to build our own router and a broadcasting endpoint which consumes from jetty and routes requests to underlying "subscriptions". Only one of them will eventually process the request. It kind of works but it's not completely ok, since recently when updating to latest Camel we have found our custom built component to leak memory and in general I consider using built-in functionality over custom hacks.
I started investigating the Camel REST API and found it very nice and pretty much replacing our home-grown component apart from one thing - you cannot re-configure it at runtime - you have to stop the context basically for this to work. Below I include my unit test with a happy path and the path that fails. Frankly I think is a bug, but if there is a legitimate way to achieve what I want, I'd like to hear sound advice:
package com.anydoby.camel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
/**
* Test tries to add/remove routes at runtime.
*/
public class RoutesTest {
private DefaultCamelContext ctx;
#Before
public void pre() throws Exception {
ctx = new DefaultCamelContext();
new RouteBuilder(ctx) {
#Override
public void configure() throws Exception {
restConfiguration("jetty").host("localhost").port(8080);
rest("/")
.get("/issues/{isin}").route().id("issues")
.process(e -> e.getOut().setBody("Here's your issue " + e.getIn().getHeader("isin"))).endRest()
.get("/listings").route().id("listings").process(e -> e.getOut().setBody("some listings"));
}
}.addRoutesToCamelContext(ctx);
ctx.start();
}
#Test
public void test() throws IOException {
{
InputStream stream = new URL("http://localhost:8080/issues/35").openStream();
assertEquals("Here's your issue 35", IOUtils.toString(stream));
}
{
InputStream stream = new URL("http://localhost:8080/listings").openStream();
assertEquals("some listings", IOUtils.toString(stream));
}
}
#Test
public void disableRoute() throws Exception {
ctx.stopRoute("issues");
ctx.removeRoute("issues");
try (InputStream stream = new URL("http://localhost:8080/issues/35").openStream()) {
fail();
} catch (Exception e) {
}
new RouteBuilder(ctx) {
#Override
public void configure() throws Exception {
rest().get("/issues/{isin}/{sedol}").route().id("issues")
.process(e -> e.getOut()
.setBody("Here's your issue " + e.getIn().getHeader("isin") + ":" + e.getIn().getHeader("sedol")))
.endRest();
}
}.addRoutesToCamelContext(ctx);
{
InputStream stream = new URL("http://localhost:8080/issues/35/65").openStream();
assertEquals("Here's your issue 35:65", IOUtils.toString(stream));
}
}
}
The disableRoute() test fails since I cannot add another consumer to an existing endpoint.
So my question is - "is there a way to add a new URL mapping to a restful camel-jetty endpoint"? If you do it during first configuration it works fine, but when later you want to reconfigure one of the routes the error is:
org.apache.camel.FailedToStartRouteException: Failed to start route because of Multiple consumers for the same endpoint is not allowed: jetty:http://localhost:8080/issues/%7Bisin%7D/%7Bsedol%7D?httpMethodRestrict=GET

PowerMock java.lang.ClassCastException: sun.net.www.protocol.https.HttpsURLConnectionImpl cannot be cast to javax.net.ssl.HttpsURLConnection

I created a mock HttpsURLConnection based on an StackExchange answer:
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
...
#RunWith(PowerMockRunner.class)
public class DialogTest {
public void mockHttpsUrlConnectionExample() throws Exception
{
URL mockUrl = PowerMockito.mock(URL.class);
PowerMockito.whenNew(URL.class).withAnyArguments().thenReturn(mockUrl);
HttpsURLConnection mockUrlConnection = PowerMockito.mock(HttpsURLConnection.class);
PowerMockito.when(mockUrl.openConnection()).thenReturn(mockUrlConnection);
PowerMockito.when(mockUrlConnection.getResponseCode()).thenReturn(200);
// Create and call my objects ...
}
}
However, when I use it, I'm seeing a cast exception:
java.lang.ClassCastException: sun.net.www.protocol.https.HttpsURLConnectionImpl cannot be cast to javax.net.ssl.HttpsURLConnection
The problem lies in this code:
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
...
private Boolean sendRequest(String endpoint, JSONObject requestData, Boolean throwOnAuthException) throws JSONException, IOException {
this.responseData = null;
try {
String serviceURI = getServiceURI();
String dialogUri = String.format("%s%s", serviceURI, endpoint);
URL url = new URL(dialogUri);
// Exception source is this cast
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
However, when I look at the source code, I see that sun.net.www.protocol.https.HttpsURLConnectionImpl implements javax.net.ssl.HttpsURLConnection
Any suggestions on how to remedy this problem?
The issue a conflict between the regular class loader and PowerMock's
The drawback to PowerMock is the use of a custom class loader. This class loader can modify type signatures in a way that is incompatible with the default class loader.
In certain circumstances, instantiation by reflection will cause the default class loader to be used to load a type. That class loader will not know that a type has already be loaded by PowerMock, because of the use of different signatures. The result can be casting errors for
objects that should implement the cast type.
To avoid this problem, first stop PowerMock from loading javax.net.ssl.HttpsURLConnection
To prevent the cast exception, use ensure javax.net.ssl.HttpsURLConnection is only loaded by one class loader. Since I cannot stop the regular class loader from being used, the best approach is to stop the PowerMock loader from acting using the #PowerMockIgnore annotation. E.g.
#PowerMockIgnore({"javax.net.ssl.*"})
#PrepareForTest(android.util.Log.class)
public class DialogTest {
...
The side effect is that PowerMock is no longer able to provide it's version of HttpsURLConnection
Next, expose HttpsURLConnection construction, and substitute a mock object
Introduce a factory for HttpsURLConnection. E.g.
public class HttpsUrlConnectionProvider {
public HttpsURLConnection getHttpsURLConnection(String dialogUri) throws IOException {
URL url = new URL(dialogUri);
return (HttpsURLConnection) url.openConnection();
}
}
Create a mock of the HttpsURLConnection object used for HTTP request E.g.
final HttpsURLConnection mockUrlConnection = PowerMockito.mock(HttpsURLConnection.class);
PowerMockito.when(mockUrlConnection, "getResponseCode").thenReturn(200);
PowerMockito.when(mockUrlConnection, "getOutputStream").thenReturn(outputStream);
// Replace the HttpsURLConnection factory with one that returns our mock HttpsURLConnection
HttpsUrlConnectionProvider mockConnFactory = new HttpsUrlConnectionProvider() {
public HttpsURLConnection getHttpsURLConnection(String dialogUri) throws
IOException {
return mockUrlConnection;
}
};
dialog.setHttpsUrlConnectionProvider(mockConnFactory);

Categories

Resources