I am implementing invoking of a soap web service in java
I have a wsdl file that I have imported using the embedded wsdl2java in axis2
then I was able to call the web service and it is working fine
util the size of the request exceed a 30 mega in size
the output request consist of the following parameters:
<param1>some value</param1>
<array>
recored1
recored2
.
.
.
</array>
I was able to send 500000 recodes but when exceed this number I stuck in getting "java heap space error"
question is is there any way to send the soap request in small chunks ...and does importing the WSDL using xmlbeans affect the performance
for rest APIs I was able to add transfer encoding for chunk and provide the body of it in small chunks but for soap services I found no solutions, please if there is any example of that I will be grateful.. thanks in advance
Streaming one transaction require all pipeline involved steps are in a streaming fashion. If one of them collapse the stream (i.e. myStream.collect(toList())) then, the streaming process will be broken and, at that point, all the input stream will be into memory (maybe filtered, reducted, ...).
In general, to process a XML input in a streaming fashion (under axis2) you could use Axiom, a simple example to process in streaming is (from here):
public void processFragments(InputStream in) throws XMLStreamException {
// Create an XMLStreamReader without building the object model
XMLStreamReader reader =
OMXMLBuilderFactory.createOMBuilder(in).getDocument().getXMLStreamReader(false);
while (reader.hasNext()) {
if (reader.getEventType() == XMLStreamReader.START_ELEMENT &&
reader.getName().equals(new QName("tag"))) {
// A matching START_ELEMENT event was found. Build a corresponding OMElement.
OMElement element =
OMXMLBuilderFactory.createStAXOMBuilder(reader).getDocumentElement();
// Make sure that all events belonging to the element are consumed so
// that the XMLStreamReader points to a well defined location (namely the
// event immediately following the END_ELEMENT event).
element.build();
// Now process the element.
processFragment(element);
} else {
reader.next();
}
}
}
Although you can open a HTTP stream and use this snippet, unfortunately translate it to your autogenerated client is not easy.
Create an AXIOM client is much less comfortable since you must to deal with AXIOM objects, from the documentation:
private static EndpointReference targetEPR =
new EndpointReference("http://localhost:8080/axis2/services/StockQuoteService");
public static OMElement getPricePayload(String symbol) {
OMFactory fac = OMAbstractFactory.getOMFactory();
OMNamespace omNs = fac.createOMNamespace("http://axiom.service.quickstart.samples/xsd", "tns");
OMElement method = fac.createOMElement("getPrice", omNs);
OMElement value = fac.createOMElement("symbol", omNs);
value.addChild(fac.createOMText(value, symbol));
method.addChild(value);
return method;
}
public static OMElement updatePayload(String symbol, double price) {
OMFactory fac = OMAbstractFactory.getOMFactory();
OMNamespace omNs = fac.createOMNamespace("http://axiom.service.quickstart.samples/xsd", "tns");
OMElement method = fac.createOMElement("update", omNs);
OMElement value1 = fac.createOMElement("symbol", omNs);
value1.addChild(fac.createOMText(value1, symbol));
method.addChild(value1);
OMElement value2 = fac.createOMElement("price", omNs);
value2.addChild(fac.createOMText(value2,
Double.toString(price)));
method.addChild(value2);
return method;
}
public static void main(String[] args) {
try {
OMElement getPricePayload = getPricePayload("WSO");
OMElement updatePayload = updatePayload("WSO", 123.42);
Options options = new Options();
options.setTo(targetEPR);
options.setTransportInProtocol(Constants.TRANSPORT_HTTP);
ServiceClient sender = new ServiceClient();
sender.setOptions(options);
sender.fireAndForget(updatePayload);
System.err.println("price updated");
OMElement result = sender.sendReceive(getPricePayload);
String response = result.getFirstElement().getText();
System.err.println("Current price of WSO: " + response);
} catch (Exception e) {
e.printStackTrace();
}
}
then you can digest the response in a streaming fashion way:
result.getFirstElement().getText()
if your WSDL definition is complex may be suitable write an AXIOM client only for big transactions but if you can split the call will be a much better approach.
Related
I'm new to Java and to backend development, and I really could use some help.
I am currently using Vert.x to develop a server that takes in a Json request that tells this server which file to analyze, and the server analyzes the file and gives a response in a Json format.
I have created an ImageRecognition class where there is a method called "getNum" which gets a json as an input and outputs a json containing the result.
But I am currently having trouble getting the Json file from the request.
public void start(Promise<Void> startPromise) throws Exception {
JsonObject reqJo = new JsonObject();
Router router = Router.router(vertx);
router.get("/getCall").handler(req ->{
JsonObject subJson = req.getBodyAsJson();
reqJo.put("name", subJson.getValue("name"));
req.end(reqJo.encodePrettily());
});
router.post("/getCall").produces("*/json").handler(plateReq ->{
plateReq.response().putHeader("content-tpye", "application/json");
JsonObject num = imageRecogService.getNum(reqJo);
plateReq.end(num.encodePrettily());
});
vertx.createHttpServer().requestHandler(router).listen(8080)
.onSuccess(ok -> {
log.info("http server running on port 8080");
startPromise.complete();
})
.onFailure(startPromise::fail);
}
}
Any feedback or solution to the code would be deeply appreciated!!
Thank you in advance!!
You have several errors in your code:
1:
JsonObject reqJo = new JsonObject();
Router router = Router.router(vertx);
router.get("/getCall").handler(req ->{
reqJo.put("name", subJson.getValue("name"));
});
You are modifying the reqJo object in handlers. I am not sure if this is thread safe, but a more common practice is to allocate the JsonObject object inside of request handlers and pass them to consequent handlers using RoutingContext.data().
2:
Your two handlers are not on the same method (the first one is GET, while the second is POST). I assume you want them both be POST.
3:
In order to extract multipart body data, you need to use POST, not GET.
4:
You need to append a BodyHandler before any of your handlers that reads the request body. For example:
// Important!
router.post("/getCall").handler(BodyHandler.create());
// I changed to posts
router.post("/getCall").handler(req ->{
JsonObject subJson = req.getBodyAsJson();
reqJo.put("name", subJson.getValue("name"));
req.end(reqJo.encodePrettily());
});
Otherwise, getBodyAsJson() will return null.
According to the Document of RoutingContext#getBodyAsJson, "the context must have first been routed to a BodyHandler for this to be populated."
Read more: BodyHandler.
Before writing something like "why don't you use Java HTTP client such as apache, etc", I need you to know that the reason is SSL. I wish I could, they are very convenient, but I can't.
None of the available HTTP clients support GOST cipher suite, and I get handshake exception all the time. The ones which do support the suite, doesn't support SNI (they are also proprietary) - I'm returned with a wrong cert and get handshake exception over and over again.
The only solution was to configure openssl (with gost engine) and curl and finally execute the command with Java.
Having said that, I wrote a simple snippet for executing a command and getting input stream response:
public static InputStream executeCurlCommand(String finalCurlCommand) throws IOException
{
return Runtime.getRuntime().exec(finalCurlCommand).getInputStream();
}
Additionally, I can convert the returned IS to a string like that:
public static String convertResponseToString(InputStream isToConvertToString) throws IOException
{
StringWriter writer = new StringWriter();
IOUtils.copy(isToConvertToString, writer, "UTF-8");
return writer.toString();
}
However, I can't see a pattern according to which I could get a good response or a desired response header:
Here's what I mean
After executing a command (with -i flag), there might be lots and lots of information like in the screen below:
At first, I thought that I could just split it with '\n', but the thing is that a required response's header or a response itself may not satisfy the criteria (prettified JSON or long redirect URL break the rule).
Also, the static line GOST engine already loaded is a bit annoying (but I hope that I'll be able to get rid of it and nothing unrelated info like that will emerge)
I do believe that there's a pattern which I can use.
For now I can only do that:
public static String getLocationRedirectHeaderValue(String curlResponse)
{
String locationHeaderValue = curlResponse.substring(curlResponse.indexOf("Location: "));
locationHeaderValue = locationHeaderValue.substring(0, locationHeaderValue.indexOf("\n")).replace("Location: ", "");
return locationHeaderValue;
}
Which is not nice, obviosuly
Thanks in advance.
Instead of reading the whole result as a single string you might want to consider reading it line by line using a scanner.
Then keep a few status variables around. The main task would be to separate header from body. In the body you might have a payload you want to treat differently (e.g. use GSON to make a JSON object).
The nice thing: Header and Body are separated by an empty line. So your code would be along these lines:
boolean inHeader = true;
StringBuilder b = new StringBuilder;
String lastLine = "";
// Technically you would need Multimap
Map<String,String> headers = new HashMap<>();
Scanner scanner = new Scanner(yourInputStream);
while scanner.hasNextLine() {
String line = scanner.nextLine();
if (line.length() == 0) {
inHeader = false;
} else {
if (inHeader) {
// if line starts with space it is
// continuation of previous header
treatHeader(line, lastLine);
} else {
b.append(line);
b.appen(System.lineSeparator());
}
}
}
String body = b.toString();
Updates:
For now using a Map. Class that wants to send something to other instance sends the object, the routing string.
Use an object stream, use Java serializable to write the object to servlet.
Write String first and then the object.
Receiving servlet wraps input stream around a ObjectInputStream. Reads string first and then the Object. Routing string decides were it goes.
A more generic way might have been to send a class name and its declared method or a Spring bean name, but this was enough for us.
Original question
Know the basic way but want details of steps. Also know I can use Jaxb or RMI or EJB ... but would like to do this using pure serialization to a bytearray and then encode that send it from servlet 1 in jvm 1 to servlet 2 in jvm 2 (two app server instances in same LAN, same java versions and jars set up in both J2EE apps)
Basic steps are (Approcah 1) :-
serialize any Serializable object to a byte array and make a string. Exact code see below
Base64 output of 1. Is it required to base 64 or can skip step 2?
use java.util.URLEncode.encode to encode the string
use apache http components or URL class to send from servlet 1 to 2 after naming params
on Servlet 2 J2EE framework would have already URLDecoced it, now just do reverse steps and cast to object according to param name.
Since both are our apps we would know the param name to type / class mapping. Basically looking for the fastest & most convenient way of sending objects between JVMs.
Example :
POJO class to send
package tst.ser;
import java.io.Serializable;
public class Bean1 implements Serializable {
/**
* make it 2 if add something without default handling
*/
private static final long serialVersionUID = 1L;
private String s;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
* Utility *
package tst.ser;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URLEncoder;
public class SerUtl {
public static String serialize(Object o) {
String s = null;
ObjectOutputStream os = null;
try {
os = new ObjectOutputStream(new ByteArrayOutputStream());
os.writeObject(o);
s = BAse64.encode(os.toByeArray());
//s = URLEncoder.encode(s, "UTF-8");//keep this for sending part
} catch (Exception e) {
// TODO: logger
e.printStackTrace();
return null;
} finally {
// close OS but is in RAM
try {
os.close();// not required in RAM
} catch (Exception e2) {// TODO: handle exception logger
}
os = null;
}
return s;
}
public static Object deserialize(String s) {
Object o = null;
ObjectInputStream is = null;
try {
// do base 64 decode if done in serialize
is = new ObjectInputStream(new ByteArrayInputStream(
Base64.decode(s)));
o = is.readObject();
} catch (Exception e) {
// TODO: logger
e.printStackTrace();
return null;
} finally {
// close OS but is in RAM
try {
is.close();// not required in RAM
} catch (Exception e2) {// TODO: handle exception logger
}
is = null;
}
return o;
}
}
**** sample sending servlet ***
Bean1 b = new Bean1(); b.setS("asdd");
String s = SerUtl.serialize(b);
//do UrlEncode.encode here if sending lib does not.
HttpParam p = new HttpParam ("bean1", s);
//http components send obj
**** sample receiving servlet ***
String s = request.getParameter("bean1");
Bean1 b1 = (Beean1)SerUtl.deserialize(s);
Serialize any Serializable object with to a byte array
Yes.
and make a string.
No.
Exact statements see below
os = new ObjectOutputStream(new ByteArrayOutputStream());
os.writeObject(o);
s = os.toString();
// s = Base64.encode(s);//Need this some base 64 impl like Apache ?
s = URLEncoder.encode(s, "UTF-8");
These statements don't even do what you have described, which is in any case incorrect. OutputStream.toString() doesn't turn any bytes into Strings, it just returns a unique object identifier.
Base64 output of 1.
The base64 output should use the byte array as the input, not a String. String is not a container for binary data. See below for corrected code.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
os = new ObjectOutputStream(baos);
os.writeObject(o);
os.close();
s = Base64.encode(baos.toByeArray()); // adjust to suit your API
s = URLEncoder.encode(s, "UTF-8");
This at least accomplishes your objective.
Is it required to base 64 or can skip step 2?
If you want a String you must encode it somehow.
Use java.util.URLEncode.encode to encode the string
This is only necessary if you're sending it as a GET or POST parameter.
Use apache http components or URL class to send from servlet 1 to 2 after naming params
Yes.
On Servlet 2 J2EE framework would have already URLDecoded it, now just do reverse steps and cast to object according to param name.
Yes, but remember to go directly from the base64-encoded string to the byte array, no intermediate String.
Basically looking for the fastest & most convenient way of sending objects between JVMs.
These objectives aren't necessarily reconcilable. The most convenient these days is probably XML or JSON but I doubt that these are faster than Serialization.
os = null;
Setting references that are about to fall out of scope to null is pointless.
HttpParam p = new HttpParam ("bean1", s);
It's possible that HttpParam does the URLEncoding for you. Check this.
You need not convert to string. You can post the binary data straight to the servlet, for example by creating an ObjectOutputStream on top of a HttpUrlConnection's outputstream. Set the request method to POST.
The servlet handling the post can deserialize from an ObjectStream created from the HttpServletRequest's ServletInputStream.
I'd recommend JAXB any time over binary serialization, though. The frameworks are not only great for interoperability, they also speed up development and create more robust solutions.
The advantages I see are way better tooling, type safety, and code generation, keeping your options open so you can call your code from another version or another language, and easier debugging. Don't underestimate the cost of hard to solve bugs caused by accidentally sending the wrong type or doubly escaped data to the servlet. I'd expect the performance benefits to be too small to compensate for this.
Found this Base64 impl that does a lot of the heavy lifting for me : http://iharder.net/base64
Has utility methods :
String encodeObject(java.io.Serializable serializableObject, int options )
Object decodeToObject(String encodedObject, int options, final ClassLoader loader )
Using :
try {
String dat = Base64.encodeObject(srlzblObj, options);
StringBuilder data = new StringBuilder().append("type=");
data.append(appObjTyp).append("&obj=").append(java.net.URLEncoder.encode(dat, "UTF-8"));
Use the type param to tell the receiving JVM what type of object I'm sending. Each servlet/ jsps at most receives 4 types, usually 1. Again since its our own app and classes that we are sending this is quick (as in time to send over the network) and simple.
On the other end unpack it by :
String objData = request.getParameter("obj");
Object obj = Base64.decodeToObject(objData, options, null);
Process it, encode the result, send result back:
reply = Base64.encodeObject(result, options);
out.print("rsp=" + reply);
Calling servlet / jsp gets the result:
if (reply != null && reply.length() > 4) {
String objDataFromServletParam = reply.substring(4);
Object obj = Base64.decodeToObject(objDataFromServletParam, options, null);
options can be 0 or Base64.GZIP
You can use JMS as well.
Apache Active-MQ is one good solution. You will not have to bother with all this conversion.
/**
* #param objectToQueue
* #throws JMSException
*/
public void sendMessage(Serializable objectToQueue) throws JMSException
{
ObjectMessage message = session.createObjectMessage();
message.setObject(objectToQueue);
producerForQueue.send(message);
}
/**
* #param objectToQueue
* #throws JMSException
*/
public Serializable receiveMessage() throws JMSException
{
Message message = consumerForQueue.receive(timeout);
if (message instanceof ObjectMessage)
{
ObjectMessage objMsg = (ObjectMessage) message;
Serializable sobject = objMsg.getObject();
return sobject;
}
return null;
}
My point is do not write custom code for Serialization, iff it can be avoided.
When you use AMQ, all you need to do is make your POJO serializable.
Active-MQ functions take care of serialization.
If you want fast response from AMQ, use vm-transport. It will minimize n/w overhead.
You will automatically get benefits of AMQ features.
I am suggesting this because
You have your own Applications running on network.
You need a mechanism to transfer objects.
You will need a way to monitor it as well.
If you go for custom solution, you might have to solve above things yourselves.
This may be one of the insane / stupid / dumb / lengthy questions as I am newbie to web services.
I want to write a web service which will return answer in XML format (I am using my service for YUI autocomplete). I am using Eclipse and Axis2 and following http://www.softwareagility.gr/index.php?q=node/21
I want response in following format
<codes>
<code value="Pegfilgrastim"/>
<code value="Peggs"/>
<code value="Peggy"/>
<code value="Peginterferon alfa-2 b"/>
<code value="Pegram"/>
</codes>
Number of code elements may vary depending on response.
Till now I tried following ways
1) Create XML using String buffer and return the string.(I am providing partial code to avoid confusion)
public String myService ()
{
// Some other stuff
StringBuffer outputXML = new StringBuffer();
outputXML.append("<?xml version='1.0' standalone='yes'?>");
outputXML.append("<codes>");
while(SOME_CONDITION)
{
// Some business logic
outputXML.append("<code value=\""+tempStr+"\">"+"</code>");
}
outputXML.append("</codes>");
return (outputXML.toString());
}
It gives following response with unwanted <ns:myServiceResponse> and <ns:return> element.
<ns:myServiceResponse>
<ns:return>
<?xml version='1.0' standalone='yes'?><codes><code value="Peg-shaped teeth"></code><code value="Pegaspargase"></code><code value="Pegfilgrastim"></code><code value="Peggs"></code><code value="Peggy"></code><code value="Peginterferon alfa-2 b"></code><code value="Pegram"></code></codes>
</ns:return>
</ns:findTermsResponse>
But it didnt work with YUI autocomplete (May be because it required response in format mentioned above)
2) Using DocumentBuilderFactory :
Like
public Element myService ()
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
Element codes = doc.createElement("codes");
while(SOME_CONDITION)
{
// Some business logic
Element code = doc.createElement("code");
code.setAttribute("value", tempStr);
codes.appendChild(code);
}
return(codes);
}
Got following error
org.apache.axis2.AxisFault: Mapping qname not fond for the package: com.sun.org.apache.xerces.internal.dom
3) Using servlet : I tried to get same response using simple servlet and it worked. Here is my servlet
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
StringBuffer outputXML = new StringBuffer();
response.setContentType("text/xml");
PrintWriter out = response.getWriter();
outputXML.append("<?xml version='1.0' standalone='yes'?>");
outputXML.append("<codes>");
while(SOME_CONDITION)
{
// Some business logic
outputXML.append("<code value=\"" + tempStr + "\">" + "</code>");
}
outputXML.append("</codes>");
out.println(outputXML.toString());
}
It gave response same as mentioned above and it worked with YUI autocomplete without any extra tag.
Please can you tell how can I get XML response without any unwanted elements ?
Thanks.
Axis2 is for delivering Objects back to the caller. Thats why it adds extra stuff to the response even it is a simple String object.
Using the second approach your service returns a complex Java object (Element instance) that is for describing an XML fragment. This way the caller has to be aware of this object to be able to deserialize it and restore the Java object that contains XML data.
The third approach is the simplest and best in your case regarding the return type: it doesn't return a serialized Java object, only the plain xml text. Of course you could use DocumentBuilder to prepare the XML, but in the end you have to make String of it by calling the appropriate getXml(), asXml() method (or kind of...)
Finally got it work though I am not able to remove unwanted element. (I don't bother till all things are in place). I used AXIOM to generate response.
public OMElement myService ()
{
OMFactory fac = OMAbstractFactory.getOMFactory();
OMNamespace omNs = fac.createOMNamespace("", "");
OMElement codes = fac.createOMElement("codes", omNs);
while(SOME_CONDITION)
{
OMElement code = fac.createOMElement("code", null, codes);
OMAttribute value = fac.createOMAttribute("value", null, tempStr);
code.addAttribute(value);
}
return(codes);
}
Links : 1) http://songcuulong.com/public/html/webservice/create_ws.html
2) http://sv.tomicom.ac.jp/~koba/axis2-1.3/docs/xdocs/1_3/rest-ws.html
I think you cannot return your custom xml with Axis. It will wrap it into its envelope anyways.
I have some Java code that takes an XML (SOAP) message and returns the deserialized object:
public static <T> T deserializeObject(String xml, Class<T> clazz) throws AxisFault, Exception {
assert xml != null : "xml != null";
assert clazz != null : "clazz != null";
T result = null;
try {
Message message = new Message(SOAP_START + xml + SOAP_END);
result = (T)message.getSOAPEnvelope().getFirstBody().getObjectValue(clazz);
} catch (Exception e) {
// most likely namespace error due to removed namespaces
Message message = new Message(SOAP_START_XSI + xml + SOAP_END);
result = (T)message.getSOAPEnvelope().getFirstBody().getObjectValue(clazz);
}
return result;
}
However this code only works with Axis 1.4 :-( Could someone Help me have that code work with Axis 2?
In fact, I might just need to know what to replace the import org.apache.axis.Message with?
Thanks in advance.
Every message within the Axis2 engine is wrapped inside a MessageContext object. When a SOAP message arrives into the system or is prepared to be sent out, we create an AXIOM object model of the SOAP message.
(Please read the AXIOM article series for more information on AXIOM). This AXIOM model is then included inside the message context object. Let's see how to access this SOAP message inside Axis2.
// if you are within a handler, reference to the message context
MessageContext messageContext;
object will be passed to you through Handler.invoke(MessageContext) method.
SOAPEnvelope soapEnvelope = messageContext.getEnvelope();
please see :
javax.xml.soap
Interface SOAPEnvelope
Processing Axis2 message