Skip to content

Commit

Permalink
Add a J4pResponseExtractor for hooking into Java client deserialization.
Browse files Browse the repository at this point in the history
Fixes #173
  • Loading branch information
rhuss committed Dec 23, 2014
1 parent 3b434f4 commit cc81040
Show file tree
Hide file tree
Showing 11 changed files with 339 additions and 103 deletions.
151 changes: 94 additions & 57 deletions client/java/src/main/java/org/jolokia/client/J4pClient.java
Expand Up @@ -43,6 +43,9 @@ public class J4pClient extends J4pClientBuilderFactory {
// Creating and parsing HTTP-Requests and Responses
private J4pRequestHandler requestHandler;

// Extractor used for creating J4pResponses
private J4pResponseExtractor responseExtractor;

/**
* Construct a new client for a given server url
*
Expand Down Expand Up @@ -71,7 +74,22 @@ public J4pClient(String pJ4pServerUrl, HttpClient pHttpClient) {
* @param pTargetConfig optional target
*/
public J4pClient(String pJ4pServerUrl, HttpClient pHttpClient,J4pTargetConfig pTargetConfig) {
this(pJ4pServerUrl,pHttpClient,pTargetConfig, ValidatingResponseExtractor.DEFAULT);
}


/**
* Constructor using a given Agent URL, HttpClient and a proxy target config. If the HttpClient is null,
* a default client is used. If no target config is given, a plain request is performed
*
* @param pJ4pServerUrl the agent URL for how to contact the server.
* @param pHttpClient HTTP client to use for the connecting to the agent
* @param pTargetConfig optional target
* @param pExtractor response extractor to use
*/
public J4pClient(String pJ4pServerUrl, HttpClient pHttpClient,J4pTargetConfig pTargetConfig,J4pResponseExtractor pExtractor) {
requestHandler = new J4pRequestHandler(pJ4pServerUrl,pTargetConfig);
responseExtractor = pExtractor;
// Using the default as defined in the client builder
if (pHttpClient != null) {
httpClient = pHttpClient;
Expand All @@ -81,21 +99,22 @@ public J4pClient(String pJ4pServerUrl, HttpClient pHttpClient,J4pTargetConfig pT
}
}

// =============================================================================================

/**
* Execute a single J4pRequest returning a single response.
* The HTTP Method used is determined automatically.
*
* @param pRequest request to execute
* @param <R> response type
* @param <T> request type
* @param <RESP> response type
* @param <REQ> request type
* @return the response as returned by the server
*/
public <R extends J4pResponse<T>,T extends J4pRequest> R execute(T pRequest)
public <RESP extends J4pResponse<REQ>, REQ extends J4pRequest> RESP execute(REQ pRequest)
throws J4pException {
// type spec is required to keep OpenJDK 1.6 happy (other JVM dont have a problem
// with infering the type is missing here)
return this.<R,T>execute(pRequest,null,null);
return this.<RESP, REQ>execute(pRequest,null,null);
}

/**
Expand All @@ -104,16 +123,16 @@ public <R extends J4pResponse<T>,T extends J4pRequest> R execute(T pRequest)
*
* @param pRequest request to execute
* @param pProcessingOptions optional map of processing options
* @param <R> response type
* @param <T> request type
* @param <RESP> response type
* @param <REQ> request type
* @return the response as returned by the server
* @throws java.io.IOException when the execution fails
* @throws org.json.simple.parser.ParseException if parsing of the JSON answer fails
*/
public <R extends J4pResponse<T>,T extends J4pRequest> R execute(T pRequest,
public <RESP extends J4pResponse<REQ>, REQ extends J4pRequest> RESP execute(REQ pRequest,
Map<J4pQueryParameter,String> pProcessingOptions)
throws J4pException {
return this.<R,T>execute(pRequest,null,pProcessingOptions);
return this.<RESP, REQ>execute(pRequest,null,pProcessingOptions);
}

/**
Expand All @@ -122,43 +141,58 @@ public <R extends J4pResponse<T>,T extends J4pRequest> R execute(T pRequest,
* @param pRequest request to execute
* @param pMethod method to use which should be either "GET" or "POST"
*
* @param <R> response type
* @param <T> request type
* @param <RESP> response type
* @param <REQ> request type
* @return response object
* @throws J4pException if something's wrong (e.g. connection failed or read timeout)
*/
public <R extends J4pResponse<T>,T extends J4pRequest> R execute(T pRequest,String pMethod) throws J4pException {
return this.<R,T>execute(pRequest, pMethod, null);
public <RESP extends J4pResponse<REQ>, REQ extends J4pRequest> RESP execute(REQ pRequest,String pMethod) throws J4pException {
return this.<RESP, REQ>execute(pRequest, pMethod, null);
}

/**
* Execute a single J4pRequest which returns a single response.
*
* @param pRequest request to execute
* @param pMethod method to use which should be either "GET" or "POST"
* @param pProcessingOptions optional map of processiong options
* @param pProcessingOptions optional map of processing options
*
* @param <R> response type
* @param <T> request type
* @param <RESP> response type
* @param <REQ> request type
* @return response object
* @throws J4pException if something's wrong (e.g. connection failed or read timeout)
*/
public <R extends J4pResponse<T>,T extends J4pRequest> R execute(T pRequest,String pMethod,
public <RESP extends J4pResponse<REQ>, REQ extends J4pRequest> RESP execute(REQ pRequest,String pMethod,
Map<J4pQueryParameter,String> pProcessingOptions)
throws J4pException {
return this.<RESP, REQ>execute(pRequest,pMethod,pProcessingOptions,responseExtractor);
}

/**
* Execute a single J4pRequest which returns a single response.
*
* @param pRequest request to execute
* @param pMethod method to use which should be either "GET" or "POST"
* @param pProcessingOptions optional map of processing options
* @param pExtractor extractor for actually creating the response
*
* @param <RESP> response type
* @param <REQ> request type
* @return response object
* @throws J4pException if something's wrong (e.g. connection failed or read timeout)
*/
public <RESP extends J4pResponse<REQ>, REQ extends J4pRequest> RESP execute(REQ pRequest,String pMethod,
Map<J4pQueryParameter,String> pProcessingOptions,
J4pResponseExtractor pExtractor)
throws J4pException {

try {
HttpResponse response = httpClient.execute(requestHandler.getHttpRequest(pRequest,pMethod,pProcessingOptions));
JSONAware jsonResponse = extractJsonResponse(pRequest,response);
if (! (jsonResponse instanceof JSONObject)) {
throw new J4pException("Invalid JSON answer for a single request (expected a map but got a " + jsonResponse.getClass() + ")");
}
JSONObject jsonResponseObject = (JSONObject) jsonResponse;
J4pRemoteException exp = validate(pRequest,jsonResponseObject);
if (exp == null) {
return requestHandler.<R,T>extractResponse(pRequest, jsonResponseObject);
} else {
throw exp;
}
return pExtractor.extract(pRequest, (JSONObject) jsonResponse);
}
catch (IOException e) {
throw mapException(e);
Expand All @@ -172,14 +206,14 @@ public <R extends J4pResponse<T>,T extends J4pRequest> R execute(T pRequest,Stri
* dispatched on the agent side. The results are given back in the same order as the arguments provided.
*
* @param pRequests requests to execute
* @param <R> response type
* @param <T> request type
* @param <RESP> response type
* @param <REQ> request type
* @return list of responses, one response for each request
* @throws J4pException when an communication error occurs
*/
public <R extends J4pResponse<T>,T extends J4pRequest> List<R> execute(List<T> pRequests)
public <RESP extends J4pResponse<REQ>, REQ extends J4pRequest> List<RESP> execute(List<REQ> pRequests)
throws J4pException {
return this.<R,T>execute(pRequests, null);
return this.<RESP, REQ>execute(pRequests, null);
}

/**
Expand All @@ -188,20 +222,39 @@ public <R extends J4pResponse<T>,T extends J4pRequest> List<R> execute(List<T> p
*
* @param pRequests requests to execute
* @param pProcessingOptions processing options to use
* @param <R> response type
* @param <T> request type
* @param <RESP> response type
* @param <REQ> request type
* @return list of responses, one response for each request
* @throws J4pException when an communication error occurs
*/
public <R extends J4pResponse<T>,T extends J4pRequest> List<R> execute(List<T> pRequests,Map<J4pQueryParameter,String> pProcessingOptions)
public <RESP extends J4pResponse<REQ>, REQ extends J4pRequest> List<RESP> execute(List<REQ> pRequests,Map<J4pQueryParameter,String> pProcessingOptions)
throws J4pException {
return execute(pRequests,pProcessingOptions,responseExtractor);
}

/**
* Execute multiple requests at once. All given request will result in a single HTTP request where it gets
* dispatched on the agent side. The results are given back in the same order as the arguments provided.
*
* @param pRequests requests to execute
* @param pProcessingOptions processing options to use
* @param pResponseExtractor use this for custom extraction handling
* @param <RESP> response type
* @param <REQ> request type
* @return list of responses, one response for each request
* @throws J4pException when an communication error occurs
*/
public <RESP extends J4pResponse<REQ>, REQ extends J4pRequest> List<RESP> execute(List<REQ> pRequests,
Map<J4pQueryParameter,String> pProcessingOptions,
J4pResponseExtractor pResponseExtractor)
throws J4pException {
try {
HttpResponse response = httpClient.execute(requestHandler.getHttpRequest(pRequests,pProcessingOptions));
JSONAware jsonResponse = extractJsonResponse(null, response);

verifyBulkJsonResponse(jsonResponse);

return this.<R,T>extractResponses(jsonResponse, pRequests);
return this.<RESP, REQ>extractResponses(jsonResponse, pRequests, pResponseExtractor);
} catch (IOException e) {
throw mapException(e);
} catch (URISyntaxException e) {
Expand All @@ -212,7 +265,7 @@ public <R extends J4pResponse<T>,T extends J4pRequest> List<R> execute(List<T> p
// =====================================================================================================

@SuppressWarnings("PMD.PreserveStackTrace")
private <T extends J4pRequest> JSONAware extractJsonResponse(T pRequest, HttpResponse pResponse) throws J4pException {
private <REQ extends J4pRequest> JSONAware extractJsonResponse(REQ pRequest, HttpResponse pResponse) throws J4pException {
try {
return requestHandler.extractJsonResponse(pResponse);
} catch (IOException e) {
Expand All @@ -228,9 +281,10 @@ private <T extends J4pRequest> JSONAware extractJsonResponse(T pRequest, HttpRes
}
}


// Extract J4pResponses from a returned bulk JSON answer
private <R extends J4pResponse<T>, T extends J4pRequest> List<R> extractResponses(JSONAware pJsonResponse, List<T> pRequests) throws J4pException {
private <R extends J4pResponse<T>, T extends J4pRequest> List<R> extractResponses(JSONAware pJsonResponse,
List<T> pRequests,
J4pResponseExtractor pResponseExtractor) throws J4pException {
JSONArray responseArray = (JSONArray) pJsonResponse;
List<R> ret = new ArrayList<R>(responseArray.size());
J4pRemoteException remoteExceptions[] = new J4pRemoteException[responseArray.size()];
Expand All @@ -241,14 +295,12 @@ private <R extends J4pResponse<T>, T extends J4pRequest> List<R> extractResponse
if (!(jsonResp instanceof JSONObject)) {
throw new J4pException("Response for request Nr. " + i + " is invalid (expected a map but got " + jsonResp.getClass() + ")");
}
JSONObject jsonRespObject = (JSONObject) jsonResp;
J4pRemoteException exp = validate(request,jsonRespObject);
if (exp != null) {
try {
ret.add(i,pResponseExtractor.<R,T>extract(request, (JSONObject) jsonResp));
} catch (J4pRemoteException exp) {
remoteExceptions[i] = exp;
exceptionFound = true;
ret.add(i,null);
} else {
ret.add(i,requestHandler.<R,T>extractResponse(request, (JSONObject) jsonResp));
}
}
if (exceptionFound) {
Expand Down Expand Up @@ -293,30 +345,15 @@ private void verifyBulkJsonResponse(JSONAware pJsonResponse) throws J4pException
if (!(pJsonResponse instanceof JSONArray)) {
if (pJsonResponse instanceof JSONObject) {
JSONObject errorObject = (JSONObject) pJsonResponse;
J4pRemoteException exp = validate(null,errorObject);
if (exp != null) {
throw exp;

if (!errorObject.containsKey("status") || (Long) errorObject.get("status") != 200) {
throw new J4pRemoteException(null, errorObject);
}
}
throw new J4pException("Invalid JSON answer for a bulk request (expected an array but got a " + pJsonResponse.getClass() + ")");
}
}

// Validate a result object and create a remote exception in case of an error
private <T extends J4pRequest> J4pRemoteException validate(T pRequest, JSONObject pJsonRespObject) {
Long status = (Long) pJsonRespObject.get("status");
if (status == null) {
return new J4pRemoteException(pRequest,"Invalid response received: " + pJsonRespObject.toJSONString(), null, 500,null, null);
} else if (status != 200) {
return new J4pRemoteException(pRequest,(String) pJsonRespObject.get("error"), (String) pJsonRespObject.get("error_type"),
status.intValue(),(String) pJsonRespObject.get("stacktrace"),
(JSONObject) pJsonRespObject.get("error_value"));
} else {
return null;
}
}


/**
* Execute multiple requests at once. All given request will result in a single HTTP request where it gets
* dispatched on the agent side. The results are given back in the same order as the arguments provided.
Expand Down
55 changes: 38 additions & 17 deletions client/java/src/main/java/org/jolokia/client/J4pClientBuilder.java
Expand Up @@ -42,7 +42,7 @@
import org.apache.http.impl.io.DefaultHttpResponseParserFactory;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.VersionInfo;
import org.jolokia.client.request.J4pTargetConfig;
import org.jolokia.client.request.*;

/**
* A builder for a {@link org.jolokia.client.J4pClient}.
Expand Down Expand Up @@ -91,6 +91,9 @@ public class J4pClientBuilder {
// HTTP proxy settings
private Proxy httpProxy;

// Extractor used creating responses
private J4pResponseExtractor responseExtractor;

/**
* Package access constructor, use static method on J4pClient for creating
* the builder.
Expand All @@ -109,6 +112,7 @@ public J4pClientBuilder() {
password(null);
cookieStore(new BasicCookieStore());
authenticator(new BasicAuthenticator());
responseExtractor(ValidatingResponseExtractor.DEFAULT);
}

/**
Expand Down Expand Up @@ -362,20 +366,16 @@ public final J4pClientBuilder useProxyFromEnvironment(){
}

/**
* Parse proxy specification and return a proxy object representing the proxy configuration.
* @param spec specification of for a proxy
* @return proxy object or null if none is set
* Set the response extractor to use for handling single responses. By default the JSON answer from
* the agent is parsed and only considered as successful if the status code returned is 200. In all other
* cases an exception is thrown. An alternative extractor e.g. could silently ignored non existent MBeans (which
* might be considered optional.
*
* @param pResponseExtractor response extractor to use.
*/
static Proxy parseProxySettings(String spec) {

try {
if (spec == null || spec.length() == 0) {
return null;
}
return new Proxy(spec);
} catch (URISyntaxException e) {
return null;
}
public final J4pClientBuilder responseExtractor(J4pResponseExtractor pResponseExtractor) {
this.responseExtractor = pResponseExtractor;
return this;
}

// =====================================================================================
Expand All @@ -387,9 +387,8 @@ static Proxy parseProxySettings(String spec) {
*/
public J4pClient build() {
return new J4pClient(url,createHttpClient(),
targetUrl != null ?
new J4pTargetConfig(targetUrl,targetUser,targetPassword) :
null);
targetUrl != null ? new J4pTargetConfig(targetUrl,targetUser,targetPassword) : null,
responseExtractor);
}

public HttpClient createHttpClient() {
Expand All @@ -411,6 +410,27 @@ public HttpClient createHttpClient() {
return builder.build();
}



/**
* Parse proxy specification and return a proxy object representing the proxy configuration.
* @param spec specification of for a proxy
* @return proxy object or null if none is set
*/
static Proxy parseProxySettings(String spec) {

try {
if (spec == null || spec.length() == 0) {
return null;
}
return new Proxy(spec);
} catch (URISyntaxException e) {
return null;
}
}

// ==========================================================================================

private void setupProxyIfNeeded(HttpClientBuilder builder) {
if (httpProxy != null) {
builder.setProxy(new HttpHost(httpProxy.getHost(),httpProxy.getPort()));
Expand Down Expand Up @@ -496,6 +516,7 @@ private HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> getConnect
new DefaultHttpResponseParserFactory());
}


/**
* Internal representation of proxy server. Package protected so that it can be accessed by tests.
*/
Expand Down

0 comments on commit cc81040

Please sign in to comment.