How to register my custom MessageBodyReader in my CLIENT? - jersey

Maybe somebody can help me find out how to solve this.
I am using jersey-apache-client 1.17
I tried to use Jersey client to build a standalone application (no Servlet container or whatever, just the Java classes) which communicates with a RESTFUL API, and everything worked fine until I tried to handle the mediatype "text/csv; charset=utf-8" which is a CSV stream sent by the server.
The thing is that I can read this stream with the following code:
InputStreamReader reader = new InputStreamReader(itemExportBuilder
.get(ClientResponse.class).getEntityInputStream());
Csv csv = new Csv();
Input input = csv.createInput(reader);
try {
String[] readLine;
while ((readLine = input.readLine()) != null) {
LOG.debug("Reading CSV: {}", readLine);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
But I'd like to encapsulate it and put it into a MessageBodyReader. But after writing this code, I just can't make the client use the following class:
package client.response;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Provider
public class ItemExportMessageBodyReader implements MessageBodyReader<ItemExportResponse> {
private static final Logger LOG = LoggerFactory.getLogger(ItemExportMessageBodyReader.class);
private static final Integer SKU = 0;
private static final Integer BASE_SKU = 1;
public boolean isReadable(Class<?> paramClass, Type type, Annotation[] annotations,
MediaType mediaType) {
LOG.info("Cheking if content is readable or not");
return paramClass == ItemExportResponse.class && !mediaType.isWildcardType()
&& !mediaType.isWildcardSubtype()
&& mediaType.isCompatible(MediaType.valueOf("text/csv; charset=utf-8"));
}
public ItemExportResponse readFrom(Class<ItemExportResponse> paramClass, Type paramType,
Annotation[] paramArrayOfAnnotation, MediaType paramMediaType,
MultivaluedMap<String, String> paramMultivaluedMap, InputStream entityStream)
throws IOException, WebApplicationException {
InputStreamReader reader = new InputStreamReader(entityStream);
Csv csv = new Csv();
Input input = csv.createInput(reader);
List<Item> items = new ArrayList<Item>();
try {
String[] readLine;
while ((readLine = input.readLine()) != null) {
LOG.trace("Reading CSV: {}", readLine);
Item item = new Item();
item.setBaseSku(readLine[BASE_SKU]);
items.add(item);
}
} catch (IOException e) {
LOG.warn("Item export HTTP response handling failed", e);
} finally {
try {
input.close();
} catch (IOException e) {
LOG.warn("Could not close the HTTP response stream", e);
}
}
ItemExportResponse response = new ItemExportResponse();
response.setItems(items);
return response;
}
}
The following documentation says that the preferred way of making this work in a JAX-RS client to register the message body reader with the code below:
Using Entity Providers with JAX-RS Client API
Client client = ClientBuilder.newBuilder().register(MyBeanMessageBodyReader.class).build();
Response response = client.target("http://example/comm/resource").request(MediaType.APPLICATION_XML).get();
System.out.println(response.getStatus());
MyBean myBean = response.readEntity(MyBean.class);
System.out.println(myBean);
Now the thing is that I can't use the ClientBuilder. I have to extend from a specific class which constructs the client another way, and I have no access to change the construction.
So when I receive the response from the server, the client fails with the following Exception:
com.sun.jersey.api.client.ClientHandlerException: A message body reader for Java class client.response.ItemExportResponse, and Java type class client.response.ItemExportResponse, and MIME media type text/csv; charset=utf-8 was not found
Any other way to register my MessageBodyReader?

OK. If anybody would bump into my question I solved this mystery by upgrading from Jersey 1.17 to version 2.9. The documentation I linked above also covers this version not the old one, this is where the confusion stems from.
Jersey introduced backward INCOMPATIBLE changes starting from version 2, so I have no clue how to configure it in version 1.17.
In version 2 the proposed solution worked fine.

Related

Wiremock request templating in standalone mode: can I use a XML file as response template and inject value with XPATH?

I know that request template supports XPath, so that I can get value from request like {{xPath request.body '/outer/inner/text()'}}. I already have a XML file as response, and I want to inject this value I got from request, but keep the other parts of this response XML intact. For example, I want to inject it to XPATH /svc_result/slia/pos/msid.
And I need to use it in standalone mode.
I see another question(Wiremock Stand alone - How to manipulate response with request data) but that was with JSON, I have XML request/response.
How can it be done? Thanks.
For example, I have this definition of mapping:
{
"request": {
"method": "POST",
"bodyPatterns": [
{
"matchesXPath": {
"expression": "/svc_init/slir/msids/msid[#type='MSISDN']/text()",
"equalTo": "200853000105614"
}
},
{
"matchesXPath": "/svc_init/hdr/client[id and pwd]"
}
]
},
"response": {
"status": 200,
"bodyFileName": "slia.xml",
"headers": {
"Content-Type": "application/xml;charset=UTF-8"
}
}
}
And this request:
<?xml version="1.0"?>
<!DOCTYPE svc_init>
<svc_init ver="3.2.0">
<hdr ver="3.2.0">
<client>
<id>dummy</id>
<pwd>dummy</pwd>
</client>
</hdr>
<slir ver="3.2.0" res_type="SYNC">
<msids>
<msid type="MSISDN">200853000105614</msid>
</msids>
</slir>
</svc_init>
I expect this response, with xxxxxxxxxxx replaced with the <msid> in the request.
<?xml version="1.0" ?>
<!DOCTYPE svc_result SYSTEM "MLP_SVC_RESULT_320.DTD">
<svc_result ver="3.2.0">
<slia ver="3.0.0">
<pos>
<msid type="MSISDN" enc="ASC">xxxxxxxxxxx</msid>
<pd>
<time utc_off="+0800">20111122144915</time>
<shape>
<EllipticalArea srsName="www.epsg.org#4326">
<coord>
<X>00 01 01N</X>
<Y>016 31 53E</Y>
</coord>
<angle>0</angle>
<semiMajor>2091</semiMajor>
<semiMinor>2091</semiMinor>
<angularUnit>Degrees</angularUnit>
</EllipticalArea>
</shape>
<lev_conf>90</lev_conf>
</pd>
<gsm_net_param>
<cgi>
<mcc>100</mcc>
<mnc>01</mnc>
<lac>2222</lac>
<cellid>10002</cellid>
</cgi>
<neid>
<vmscid>
<vmscno>00004946000</vmscno>
</vmscid>
<vlrid>
<vlrno>99994946000</vlrno>
</vlrid>
</neid>
</gsm_net_param>
</pos>
</slia>
</svc_result>
My first thought was to use transformerParameters to change the response file by inserting the value from the body. Unfortunately, WireMock doesn't resolve the helpers before inserting them into the body response. So while we can reference that MSID value via an xpath helper like
{{xPath request.body '/svc_init/slir/msids/msid/text()'}}
if we try to insert that as a custom transformer parameter, it won't resolve. (I've written up an issue on the WireMock github about this.)
Unfortunately, I think this leaves us with having to write a custom extension that will take the request and find the value and then modify the response file. More information on creating a custom transformer extensions can be found here.
At last I created my own transformer:
package com.company.department.app.extensions;
import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.ResponseTransformer;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
public class NLGResponseTransformer extends ResponseTransformer {
private static final Logger LOG = LoggerFactory.getLogger(NLGResponseTransformer.class);
private static final String SLIA_FILE = "/stubs/__files/slia.xml";
private static final String REQ_IMSI_XPATH = "/svc_init/slir/msids/msid";
private static final String[] RES_IMSI_XPATHS = {
"/svc_result/slia/pos/msid",
"/svc_result/slia/company_mlp320_slia/company_netinfo/company_ms_netinfo/msid"
};
private static final String[] RES_TIME_XPATHS = {
// for slia.xml
"/svc_result/slia/company_mlp320_slia/company_netinfo/company_ms_netinfo/time",
// for slia_poserror.xml
"/svc_result/slia/pos/poserror/time"
};
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final String UTC_OFF = "utc_off";
private static final String TRANSFORM_FACTORY_ATTRIBUTE_INDENT_NUMBER = "indent-number";
protected static final String COMPANY_MLP_320_SLIA_EXTENSION_DTD = "company_mlp320_slia_extension.dtd";
protected static final String MLP_SVC_RESULT_320_DTD = "MLP_SVC_RESULT_320.DTD";
#Override
public String getName() {
return "inject-request-values";
}
#Override
public Response transform(Request request, Response response, FileSource fileSource, Parameters parameters) {
Document responseDocument = injectValuesFromRequest(request);
String transformedResponse = transformToString(responseDocument);
if (transformedResponse == null) {
return response;
}
return Response.Builder.like(response)
.but()
.body(transformedResponse)
.build();
}
private Document injectValuesFromRequest(Request request) {
// NOTE: according to quickscan:
// "time" element in the MLP is the time MME reports cell_id to GMLC (NLG), NOT the time when MME got the cell_id.
LocalDateTime now = LocalDateTime.now();
Document responseTemplate = readDocument(SLIA_FILE);
Document requestDocument = readDocumentFromBytes(request.getBody());
if (responseTemplate == null || requestDocument == null) {
return null;
}
try {
injectIMSI(responseTemplate, requestDocument);
injectTime(responseTemplate, now);
} catch (XPathExpressionException e) {
LOG.error("Cannot parse XPath expression {}. Cause: ", REQ_IMSI_XPATH, e);
}
return responseTemplate;
}
private Document readDocument(String inputStreamPath) {
try {
DocumentBuilder builder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
// ignore missing dtd
builder.setEntityResolver((publicId, systemId) -> {
if (systemId.contains(COMPANY_MLP_320_SLIA_EXTENSION_DTD) ||
systemId.contains(MLP_SVC_RESULT_320_DTD)) {
return new InputSource(new StringReader(""));
} else {
return null;
}
});
return builder.parse(this.getClass().getResourceAsStream(inputStreamPath));
} catch (Exception e) {
LOG.error("Cannot construct document from resource path. ", e);
return null;
}
}
private Document readDocumentFromBytes(byte[] array) {
try {
DocumentBuilder builder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
// ignore missing dtd
builder.setEntityResolver((publicId, systemId) -> {
if (systemId.contains(COMPANY_MLP_320_SLIA_EXTENSION_DTD) ||
systemId.contains(MLP_SVC_RESULT_320_DTD)) {
return new InputSource(new StringReader(""));
} else {
return null;
}
});
return builder.parse(new ByteArrayInputStream(array));
} catch (Exception e) {
LOG.error("Cannot construct document from byte array. ", e);
return null;
}
}
private XPath newXPath() {
return XPathFactory.newInstance().newXPath();
}
private void injectTime(Document responseTemplate, LocalDateTime now) throws XPathExpressionException {
for (String timeXPath: RES_TIME_XPATHS) {
Node timeTarget = (Node) (newXPath().evaluate(timeXPath, responseTemplate, XPathConstants.NODE));
if (timeTarget != null) {
// set offset in attribute
Node offset = timeTarget.getAttributes().getNamedItem(UTC_OFF);
offset.setNodeValue(getOffsetString());
// set value
timeTarget.setTextContent(TIME_FORMAT.format(now));
}
}
}
private void injectIMSI(Document responseTemplate, Document requestDocument) throws XPathExpressionException {
Node imsiSource = (Node) (newXPath().evaluate(REQ_IMSI_XPATH, requestDocument, XPathConstants.NODE));
String imsi = imsiSource.getTextContent();
for (String xpath : RES_IMSI_XPATHS) {
Node imsiTarget = (Node) (newXPath().evaluate(xpath, responseTemplate, XPathConstants.NODE));
if (imsiTarget != null) {
imsiTarget.setTextContent(imsi);
}
}
}
private String transformToString(Document document) {
if (document == null) {
return null;
}
document.setXmlStandalone(true); // make document to be standalone, so we can avoid outputing standalone="no" in first line
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans;
try {
trans = tf.newTransformer();
trans.setOutputProperty(OutputKeys.INDENT, "no"); // no extra indent; file already has intent of 4
// cannot find a workaround to inject dtd in doctype line. TODO
//trans.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "MLP_SVC_RESULT_320.DTD [<!ENTITY % extension SYSTEM \"company_mlp320_slia_extension.dtd\"> %extension;]");
StringWriter sw = new StringWriter();
trans.transform(new DOMSource(document), new StreamResult(sw));
// Spaces between tags are considered as text node, so when outputing we need to remove the extra empty lines
return sw.toString().replaceAll("\\n\\s*\\n", "\n");
} catch (TransformerException e) {
LOG.error("Cannot transform response document to String. ", e);
return null;
}
}
/**
* Compare system default timezone with UTC and get zone offset in form of (+/-)XXXX.
* Dependent on the machine default timezone/locale.
* #return
*/
private String getOffsetString() {
// getting offset in (+/-)XX:XX format, or "Z" if is UTC
String offset = ZonedDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).getOffset().toString();
if (offset.equals("Z")) {
return "+0000";
}
return offset.replace(":", "");
}
}
And use it like this:
mvn package it as a JAR(non-runnable), put it aside wiremock standalone jar, for example libs
Run this:
java -cp libs/* com.github.tomakehurst.wiremock.standalone.WireMockServerRunner --extensions com.company.department.app.extensions NLGResponseTransformer --https-port 8443 --verbose
Put the whole command on the same line.
Notice the app jar which contains this transformer and wiremock standalone jar should be among classpath. Also, other dependencies under libs are needed. (I use jib maven plugin which copies all dependencies under libs/; I also move app and wiremock jars to libs/, so I can put "-cp libs/*"). If that does not work, try to specify the location of these two jars in -cp. Be ware that Wiremock will runs OK even when the extension class is not found. So maybe add some loggings.
You can use --root-dir to point to stubs files root, for example --root-dir resources/stubs in my case. By default it points to .(where java runs).

Mockito Unit Test with HTTPUrlConnection

I wrote code in our Spring Boot 2 application to make a third-party API call with HTTPUrlConnection.
public String loginApi(LoginDTO loginDto)
{
String responseData = null;
HttpURLConnection conn = null;
try {
link = authBaseUrl + loginUrl;
url = new URL(link);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty(CONTENT_TYPE, MEDIA_TYPE);
String body = getAuth0LoginDto(loginDto);
// =====================
// For POST only - START
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(body.getBytes(StandardCharsets.UTF_8));
os.flush();
os.close();
// For POST only - END
// ====================
try (BufferedReader br = (conn.getResponseCode() >= 400
? new BufferedReader(new InputStreamReader(conn.getErrorStream()))
: new BufferedReader(new InputStreamReader(conn.getInputStream())))) {
StringBuilder everything = new StringBuilder();
String output = null;
while ((output = br.readLine()) != null) {
everything.append(output);
}
responseData = everything.toString();
}
} catch (JsonProcessingException e) {
throw new Auth0Exception("Could not create Auth0 Login Body", e);
} catch (IOException e) {
throw new Auth0Exception("Error with Login API", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return responseData;
}
Now, I am very much used to doing real integration testing, where I make a real call to the web-service and check the results.
I am now being asked to use strictly Mockito, not PowerMockito, not EasyMock, to create mocking tests, and I have never done that before. My knowledge of Mockito is weak also since I haven't used it in a very long time.
So, I know it has been asked before, and I have really searched on the internet, and I really haven't found a full piece of code as an example. I see code snippets which leaves me with pieces missing, and I am not knowledgeable enough to add those parts myself.
I know this code actual implementation works fine, and the integration test works fine also. But, what I have seen before is that some users are being told they need to change their client code in order to make the mockito tests work.
If I don't get the mocking tests working for HTTPUrlConnection, then I'll be forced to switch over to RestTemplate and Mocking since my co-worker insists we use RestTemplate anyway.
Thanks!
Since you have asked for a small example which does not make sense but should show the idea:
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.HttpURLConnection;
import java.net.URL;
public class App {
public int status(URL url) {
HttpURLConnection urlConnection = null;
try {
urlConnection = create(url);
return urlConnection.getResponseCode();
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
HttpURLConnection create(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}
I would implement this with a spy and as I recommended a mocked HttpURLConnection:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
#ExtendWith(MockitoExtension.class)
class AppTest {
#Spy
App app;
#Mock
HttpURLConnection connection;
#Test
void status() throws IOException {
int expected = 200;
doReturn(connection).when(app).create(any());
doReturn(expected).when(connection).getResponseCode();
URL url = new URL("http://www.google.ats");
int status = app.status(url);
Assertions.assertEquals(expected, status);
}
}

CXF InInterceptor not firing

I have created web service. It works fine. Now I'm trying to implement authentication to it. I'm using CXF interceptors for that purpose. For some reason interceptors won't fire. What am I missing? This is my first web service.
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.xml.ws.WebServiceContext;
import org.apache.cxf.interceptor.InInterceptors;
#WebService
#InInterceptors(interceptors = "ws.BasicAuthAuthorizationInterceptor")
public class Service {
#WebMethod
public void test(#WebParam(name = "value") Integer value) throws Exception {
System.out.println("Value = " + value);
}
}
-
package ws;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.cxf.binding.soap.interceptor.SoapHeaderInterceptor;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
public class BasicAuthAuthorizationInterceptor extends SoapHeaderInterceptor {
#Override
public void handleMessage(Message message) throws Fault {
System.out.println("**** GET THIS LINE TO CONSOLE TO SEE IF INTERCEPTOR IS FIRING!!!");
AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
// If the policy is not set, the user did not specify credentials.
// 401 is sent to the client to indicate that authentication is required.
if (policy == null) {
sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
return;
}
String username = policy.getUserName();
String password = policy.getPassword();
// CHECK USERNAME AND PASSWORD
if (!checkLogin(username, password)) {
System.out.println("handleMessage: Invalid username or password for user: "
+ policy.getUserName());
sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
}
}
private boolean checkLogin(String username, String password) {
if (username.equals("admin") && password.equals("admin")) {
return true;
}
return false;
}
private void sendErrorResponse(Message message, int responseCode) {
Message outMessage = getOutMessage(message);
outMessage.put(Message.RESPONSE_CODE, responseCode);
// Set the response headers
#SuppressWarnings("unchecked")
Map<String, List<String>> responseHeaders = (Map<String, List<String>>) message
.get(Message.PROTOCOL_HEADERS);
if (responseHeaders != null) {
responseHeaders.put("WWW-Authenticate", Arrays.asList(new String[] { "Basic realm=realm" }));
responseHeaders.put("Content-Length", Arrays.asList(new String[] { "0" }));
}
message.getInterceptorChain().abort();
try {
getConduit(message).prepare(outMessage);
close(outMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
private Message getOutMessage(Message inMessage) {
Exchange exchange = inMessage.getExchange();
Message outMessage = exchange.getOutMessage();
if (outMessage == null) {
Endpoint endpoint = exchange.get(Endpoint.class);
outMessage = endpoint.getBinding().createMessage();
exchange.setOutMessage(outMessage);
}
outMessage.putAll(inMessage);
return outMessage;
}
private Conduit getConduit(Message inMessage) throws IOException {
Exchange exchange = inMessage.getExchange();
EndpointReferenceType target = exchange.get(EndpointReferenceType.class);
Conduit conduit = exchange.getDestination().getBackChannel(inMessage, null, target);
exchange.setConduit(conduit);
return conduit;
}
private void close(Message outMessage) throws IOException {
OutputStream os = outMessage.getContent(OutputStream.class);
os.flush();
os.close();
}
}
I'm fighting with this for few days now. Don't know what to google any more. Help is appreciated.
I've found solution. I was missing the following line in MANIFEST.MF file in war project:
Dependencies: org.apache.cxf
Maven wasn't includint this line by himself so I had to find workaround. I found about that here. It says: When using annotations on your endpoints / handlers such as the Apache CXF ones (#InInterceptor, #GZIP, ...) remember to add the proper module dependency in your manifest. Otherwise your annotations are not picked up and added to the annotation index by JBoss Application Server 7, resulting in them being completely and silently ignored.
This is where I found out how to change MANIFEST.MF file.
In short, I added custom manifest file to my project and referenced it in pom.xml. Hope this helps someone.
The answer provided by Felix is accurate. I managed to solve the problem using his instructions. Just for completion here is the maven config that lets you use your own MANIFEST.MF file placed in the META-INF folder.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
and here is the relevant content of the content of the MANIFEST.MF file I was using.
Manifest-Version: 1.0
Description: yourdescription
Dependencies: org.apache.ws.security,org.apache.cxf

Exception in Flying Saucer when processing images for custom UserAgentCallback

I've setup a test environment for Flying Saucer R8, and is testing building PDF's from templates and data. I'm using a custom UserAgentCallback to read external js/css and images locally from filesystem. The problem occured when introducing the following custom UserAgentCallback:
package support;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.xhtmlrenderer.swing.NaiveUserAgent;
public class FileUserAgent extends NaiveUserAgent {
#Override
public String resolveURI(String uri) {
URL url = null;
String path = null;
try {
url = new URL(uri);
path = url.getPath();
} catch (MalformedURLException e) {
// only path present
path = uri;
}
if (path.charAt(0) == '/') {
path = path.substring(1, path.length());
}
return path;
}
#Override
protected InputStream resolveAndOpenStream(String filepath) {
InputStream is = null;
try {
File file = new File(filepath);
is = new FileInputStream(file);
} catch (Exception e) {
System.out.print("an error occured when reading resource: " + e.getMessage());
}
return is;
}
}
The UserAgentCallback is able to read CSS without any problem, but when reading images either from src or background-image property i get the followng exception:
java.lang.ClassCastException: org.xhtmlrenderer.swing.AWTFSImage$OldAWTFSImage cannot be cast to org.xhtmlrenderer.pdf.ITextFSImage
at org.xhtmlrenderer.pdf.ITextOutputDevice.drawImage(ITextOutputDevice.java:761)
at org.xhtmlrenderer.render.AbstractOutputDevice.paintTiles(AbstractOutputDevice.java:300)
at org.xhtmlrenderer.render.AbstractOutputDevice.paintBackground0(AbstractOutputDevice.java:245)
at org.xhtmlrenderer.render.AbstractOutputDevice.paintBackground(AbstractOutputDevice.java:191)
at org.xhtmlrenderer.pdf.ITextOutputDevice.paintBackground(ITextOutputDevice.java:187)
at org.xhtmlrenderer.render.Box.paintBackground(Box.java:436)
at org.xhtmlrenderer.layout.Layer.paintBackgroundsAndBorders(Layer.java:243)
at org.xhtmlrenderer.layout.Layer.paint(Layer.java:329)
at org.xhtmlrenderer.pdf.ITextRenderer.paintPage(ITextRenderer.java:384)
at org.xhtmlrenderer.pdf.ITextRenderer.writePDF(ITextRenderer.java:348)
at org.xhtmlrenderer.pdf.ITextRenderer.createPDF(ITextRenderer.java:315)
at org.xhtmlrenderer.pdf.ITextRenderer.createPDF(ITextRenderer.java:246)
from what i read in the flying-saucer forum you have to make sure to return the right ImageResource!
what helped for me was extending the class with ITextUserAgent instead of NaiveUserAgent. (ITextUserAgent extends NaiveUserAgent).

How to convert a string to an Apache HttpComponents HttpRequest

I have a String that contains an HTTP header. I want to turn this into an Apache HttpComponents HttpRequest object. Is there a way to do this without picking apart the string myself?
This tutorial: http://hc.apache.org/httpcomponents-core-dev/tutorial/html/fundamentals.html#d5e56 and the javadoc does not indicate as much.
A class to convert a string to apache request:
import org.apache.http.*;
import org.apache.http.impl.DefaultHttpRequestFactory;
import org.apache.http.impl.entity.EntityDeserializer;
import org.apache.http.impl.entity.LaxContentLengthStrategy;
import org.apache.http.impl.io.AbstractSessionInputBuffer;
import org.apache.http.impl.io.HttpRequestParser;
import org.apache.http.io.HttpMessageParser;
import org.apache.http.io.SessionInputBuffer;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicLineParser;
import org.apache.http.params.BasicHttpParams;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
*
*/
public class ApacheRequestFactory {
public static HttpRequest create(final String requestAsString) {
try {
SessionInputBuffer inputBuffer = new AbstractSessionInputBuffer() {
{
init(new ByteArrayInputStream(requestAsString.getBytes()), 10, new BasicHttpParams());
}
#Override
public boolean isDataAvailable(int timeout) throws IOException {
throw new RuntimeException("have to override but probably not even called");
}
};
HttpMessageParser parser = new HttpRequestParser(inputBuffer, new BasicLineParser(new ProtocolVersion("HTTP", 1, 1)), new DefaultHttpRequestFactory(), new BasicHttpParams());
HttpMessage message = parser.parse();
if (message instanceof BasicHttpEntityEnclosingRequest) {
BasicHttpEntityEnclosingRequest request = (BasicHttpEntityEnclosingRequest) message;
EntityDeserializer entityDeserializer = new EntityDeserializer(new LaxContentLengthStrategy());
HttpEntity entity = entityDeserializer.deserialize(inputBuffer, message);
request.setEntity(entity);
}
return (HttpRequest) message;
} catch (IOException e) {
throw new RuntimeException(e);
} catch (HttpException e) {
throw new RuntimeException(e);
}
}
}
and a test class showing how to use it:
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import static org.junit.Assert.*;
/**
*
*/
public class ApacheRequestFactoryTest {
#Test
public void testGet() {
String requestString = "GET /?one=aone&two=atwo HTTP/1.1\n" +
"Host: localhost:7788\n" +
"Connection: Keep-Alive\n" +
"User-Agent: Apache-HttpClient/4.0.1 (java 1.5)";
HttpRequest request = ApacheRequestFactory.create(requestString);
assertEquals("GET", request.getRequestLine().getMethod());
List<NameValuePair> pairs = URLEncodedUtils.parse(URI.create(request.getRequestLine().getUri()), "ISO-8859-1");
checkPairs(pairs);
}
#Test
public void testPost() throws IOException {
String requestString = "POST / HTTP/1.1\n" +
"Content-Length: 17\n" +
"Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1\n" +
"Host: localhost:7788\n" +
"Connection: Keep-Alive\n" +
"User-Agent: Apache-HttpClient/4.0.1 (java 1.5)\n" +
"\n" +
"one=aone&two=atwo";
HttpRequest request = ApacheRequestFactory.create(requestString);
assertEquals("POST", request.getRequestLine().getMethod());
List<NameValuePair> pairs = URLEncodedUtils.parse(((BasicHttpEntityEnclosingRequest)request).getEntity());
checkPairs(pairs);
}
private void checkPairs(List<NameValuePair> pairs) {
for (NameValuePair pair : pairs) {
if (pair.getName().equals("one")) assertEquals("aone", pair.getValue());
else if (pair.getName().equals("two")) assertEquals("atwo", pair.getValue());
else assertTrue("got more parameters than expected:"+pair.getName(), false);
}
}
}
And a small rant:
WHAT ARE THE APACHE HTTP TEAM THINKING ? The api is incredibly awkward to use. Developers around the world are wasting time writing wrapper and conversion classes for what should be run of the mill every day usage (like this example the simple act of converting a string to an apache http request, and the bizarre way you need to extract the form parameters (also having to do it in two different ways depending on what type of request was made)). The global time wasted because of this is huge. When you write an API from the bottom up, starting with the specs, you MUST then start a layer from the top down (top being an interface where you can get typical work done without having to understand or look at the way the code is implemented), making every day usage of the library CONVENIENT and intuitive. Apache http libraries are anything but. It's almost a miracle that its the standard library for this type of task.

Categories

Resources