/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://opensso.dev.java.net/public/CDDLv1.0.html or
* opensso/legal/CDDLv1.0.txt
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at opensso/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* $Id: SOAPClient.java,v 1.18 2009/11/19 18:17:28 bhavnab Exp $
*
*/
/**
* Portions Copyrighted 2012-2013 ForgeRock Inc
*/
package com.sun.identity.shared.jaxrpc;
import com.sun.identity.common.HttpURLConnectionManager;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.datastruct.OrderedSet;
import com.sun.identity.shared.encode.Base64;
import com.sun.identity.shared.xml.XMLUtils;
import java.lang.reflect.Constructor;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
/**
* The class SOAPClient
provides methods for SOAP and JAXRPC
* client to send and receive messages. The method call(..)
will
* be used by SOAP client to send SOAP messages, and JAXRPC clients will use
* encodeMessage
and send
to send JAXRPC requests.
* The method encodeMessage(String functionName,
* Object[] args)
,
* encodes the JAXRPC data in SOAP, which can then be sent using the send(
* String message, String cookies)
.
*
* The SOAPClient
can be initialized either with known SOAP
* endpoint URLs or it will find an active server using Naming service. In the
* case of JAXRPC, the SOAP response is decoded and returns a java
* Object
; else an exception is thrown.
*/
public class SOAPClient {
// Debug file
static final Debug debug = Debug.getInstance("amJAXRPC");
// Instance variables
String serviceName;
// Variables for direct URLs
String urls[];
/**
* Constructor for applications that would like to dynamically set the SOAP
* endponts using
*
setUrls(String[] urls)
before
* invoking either send()
or
* call()
.
*/
public SOAPClient() throws IOException {
// do nothing
}
/**
* Constructor for services that use JAXRPC as their communication protocol.
* The URL end points for these services will be obtained from Naming
* service for jaxrpc service, and the service name will appended to it as
* the JAXRPC interface name.
*/
public SOAPClient(String serviceName) {
this.serviceName = serviceName;
}
/**
* Constructor for applications that have the list of end point URLs. The
* SOAPClient
will iterate through the URLs in case of server
* failure.
*/
public SOAPClient(String urls[]) {
this.urls = urls;
}
/**
* Performs a raw SOAP call with "message" as the SOAP data
* and response is returned as InputStream
*/
public InputStream call(String message, String lbcookie, String cookies)
throws Exception {
if (lbcookie != null) {
if((cookies == null) || (cookies.length() == 0)) {
cookies = lbcookie;
} else {
cookies = cookies + ";" + lbcookie;
}
}
return (call(message, cookies).getResponse());
}
/**
* Performs a raw SOAP call with "message" as the SOAP data and response is
* returned as SOAPResponseObject
*/
private SOAPResponseObject call(String message, String cookies)
throws Exception {
if (debug.messageEnabled()) {
debug.message("SOAP Client: Message being sent:" + message);
}
// Setup the connection, support for failover
String url = null;
InputStream in_buf = null;
boolean done = false;
boolean isException = false;
int urlIndex = 0;
while (!done) {
// Check for a valid URL, if not find one
if (url == null) {
// Check if URLs are provided at the time of
// constructor, else get it from JAXRPCHelper
if (urls != null) {
if (urlIndex >= urls.length) {
// All the URLs have been checked
// throw RemoteException
if (debug.warningEnabled()) {
debug.warning("SOAPClient: No vaild server found");
}
throw (new RemoteException("no-server-found"));
}
url = urls[urlIndex++];
} else {
// This function throws RemoteException
// if no servers are found
boolean validServerFound = false;
try {
if ((url = JAXRPCHelper.getValidURL(serviceName))
!= null) {
validServerFound = true;
setURL(url);
}
} catch (RemoteException re) {
// This exception is thrown only when there
// are no available server
}
if (!validServerFound) {
// It is possible the WebtopNaming has not recognized
// server is down and removed from the list.
// Retry 3 times
if (++urlIndex > 3) {
debug.error("SOAPClient::call() no valid servers");
throw (new RemoteException("no-server-found"));
}
// Sleep for 1 second, and try again
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
// Ignore the exception and continue
}
continue;
}
}
}
URL endpoint = new URL(url);
HttpURLConnection connection =
HttpURLConnectionManager.getConnection(endpoint);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"text/xml; charset=\"utf-8\"");
connection.setRequestProperty("SOAPAction", "\"\"");
if (cookies != null) {
connection.setRequestProperty("Cookie", cookies);
}
String userInfo = endpoint.getUserInfo();
if (userInfo != null) {
connection.setRequestProperty("Authorization", "Basic "
+ Base64.encode(userInfo.getBytes("UTF-8")));
}
// Output
byte[] data = message.getBytes("UTF-8");
int requestLength = data.length;
connection.setRequestProperty("Content-Length", Integer
.toString(requestLength));
OutputStream out = null;
try {
out = connection.getOutputStream();
} catch (ConnectException ce) {
// Debug the exception
if (debug.warningEnabled()) {
debug.warning("SOAP Client: Connection Exception: " + url,
ce);
}
// Server may be down, try the next server
JAXRPCHelper.serverFailed(url);
url = null;
continue;
}
// Write out the message
out.write(data);
out.flush();
// Get the response
try {
in_buf = connection.getInputStream();
} catch (IOException ioe) {
// Could be receiving SOAP fault
// Debug the exception
if (debug.messageEnabled()) {
debug.message("SOAP Client: READ Exception", ioe);
}
in_buf = connection.getErrorStream();
isException = true; // Used by send(...)
} finally {
done = true;
}
}
// Debug the input/output messages
if (debug.messageEnabled()) {
StringBuffer inbuf = new StringBuffer();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(
in_buf, "UTF-8"));
while ((line = reader.readLine()) != null) {
inbuf.append(line).append("\n");
}
String data = new String(inbuf);
debug.message("SOAP Client: Input: " + message + "\nOutput: "
+ data);
in_buf = new ByteArrayInputStream(data.getBytes("UTF-8"));
}
return (new SOAPResponseObject(in_buf, isException));
}
/**
* Performs a JAXRPC method call. The parameter
* functionName
* is the JAXRPC function to be called with parameters params
.
* Returns an object on success, else throws an Exception
*
.
*/
public Object send(String functionName, Object params[],
String lbcookie, String cookies) throws Exception {
return (send(encodeMessage(functionName, params), lbcookie, cookies));
}
/**
* Performs a JAXRPC method call. The parameter
* functionName
* is the JAXRPC function to be called with parameter param
.
* Returns an object on success, else throws an Exception
*
.
*/
public Object send(String functionName, Object param,
String lbcookie, String cookies) throws Exception {
return (send(encodeMessage(functionName, param), lbcookie, cookies));
}
public Object send(String message, String lbcookie,
String cookies) throws Exception {
/*** TODO
* If token is null try to user APPSSOToken
*/
if(lbcookie != null) {
if((cookies == null) || (cookies.length() == 0)) {
cookies = lbcookie;
} else {
cookies = cookies + ";" + lbcookie;
}
}
return (send(message, cookies));
}
/**
* Performs a JAXRPC method call. The parameter
* message
* contains SOAP encoded function call obtained from
* encodeMessage
. Returns an object on success, else throws
* an Exception
*
.
*/
private Object send(String message, String cookies)
throws Exception {
// Send the SOAP request and get the response
SOAPResponseObject response = call(message, cookies);
InputStream in_buf = response.getResponse();
// Decode the output. Parse the document using SAX
SOAPContentHandler handler = new SOAPContentHandler(
response.isException());
try {
SAXParser saxParser;
if (debug.warningEnabled()) {
saxParser = XMLUtils.getSafeSAXParser(true);
} else {
saxParser = XMLUtils.getSafeSAXParser(false);
}
XMLReader parser = saxParser.getXMLReader();
parser.setContentHandler(handler);
parser.setErrorHandler(new SOAPErrorHandler());
parser.parse(new InputSource(in_buf));
} catch (ParserConfigurationException pce) {
if (debug.warningEnabled()) {
debug.warning("SOAPClient:send parser config exception", pce);
}
} catch (SAXException saxe) {
if (debug.warningEnabled()) {
debug.warning("SOAPClient:send SAX exception", saxe);
}
}
// Check for exceptions
if (handler.isException()) {
throw (handler.getException());
}
return (handler.getObject());
}
public void setURL(String url) {
urls = new String[1];
urls[0] = url;
}
void setURLs(String[] urls) {
this.urls = urls;
}
String encodeString(String str) {
return (encodeString("String_1", str));
}
String encodeInt(String name, Integer i) {
StringBuffer sb = new StringBuffer(100);
sb.append("<").append(name);
sb.append(" xsi:type=\"xsd:int\">");
sb.append(i).append("").append(name).append(">");
return (sb.toString());
}
String encodeInt(Integer i) {
return (encodeInt("int_1", i));
}
String encodeBoolean(String name, Boolean b) {
StringBuffer sb = new StringBuffer(100);
sb.append("<").append(name);
sb.append(" xsi:type=\"xsd:boolean\">");
sb.append(b).append("").append(name).append(">");
return (sb.toString());
}
String encodeBoolean(Boolean b) {
return (encodeBoolean("boolean_1", b));
}
String encodeString(String name, String str) {
StringBuffer sb = new StringBuffer(100);
sb.append("<").append(name);
sb.append(" xsi:type=\"xsd:string\">");
// Make string to be XML compliant
String data = escapeSpecialCharacters(str);
sb.append(data).append("").append(name).append(">");
return (sb.toString());
}
String encodeSet(Set set) {
return (encodeSet("Set_1", set));
}
String encodeSet(String name, Set set) {
StringBuffer sb = new StringBuffer(200);
sb.append("<").append(name);
sb.append(" xsi:type=\"ns1:hashSet\" enc:arrayType=\"xsd:anyType[");
sb.append(set.size());
sb.append("]\">");
for (Iterator items = set.iterator(); items.hasNext();) {
sb.append(encodeString("item", items.next().toString()));
}
sb.append("").append(name).append(">");
return (sb.toString());
}
String encodeList(List list) {
return (encodeList("List_1", list));
}
String encodeList(String name, List list) {
StringBuffer sb = new StringBuffer(200);
sb.append("<").append(name);
sb.append(" xsi:type=\"ns1:linkedList\" enc:arrayType=\"xsd:anyType[");
sb.append(list.size());
sb.append("]\">");
for (Iterator items = list.iterator(); items.hasNext();) {
sb.append(encodeString("item", items.next().toString()));
}
sb.append("").append(name).append(">");
return (sb.toString());
}
public String encodeMap(Map map) {
return (encodeMap("Map_1", map));
}
String encodeByteArrayArray(String name, byte[][] data) {
if (data == null || data.length == 0) {
throw new IllegalArgumentException("The provided byte array is null or empty");
}
Setfunction
that takes the parameter param
* as the only argument.
*/
public String encodeMessage(String function, Object param) {
Object params[] = null;
if (param != null) {
params = new Object[1];
params[0] = param;
}
return (encodeMessage(function, params));
}
/**
* Returns a SOAP request compliant with JAXRPC for the provide function
* name function
that takes the parameters
* params
as its arguments.
*/
public String encodeMessage(String function, Object[] params) {
int index = 1;
StringBuffer sb = new StringBuffer(1000);
sb.append(ENVELOPE).append(HEADSTART).append(HEADEND).append(ENV_BODY);
sb.append("