I have a small app which query our SharePoint server' Web Service interface for a list of all users for a group. I can see the raw HTTP response is coming back with all the users listed, but JAX-WS response object (as created under NetBeans 6.9) contains only a blank Group Name String value. There is no trace of all the user names from the HTTP response.
Anyone know why JAX-WS is not reading in the SOAP response correctly?
The WSDL is to long to post, but is widely accessable from various locations, including this site: http://www.hezser.de/_vti_bin/UserGroup.asmx?wsdl
Here's the start of raw HTTP response:
---[HTTP response - http://{server}/_vti_bin/usergroup.asmx - 200]---
null: HTTP/1.1 200 OK
Cache-control: private, max-age=0
Content-type: text/xml; charset=utf-8
Content-length: 136738
X-powered-by: ASP.NET
Server: Microsoft-IIS/6.0
Date: Wed, 22 Sep 2010 20:53:12 GMT
X-aspnet-version: 2.0.50727
Set-cookie: WSS_KeepSessionAuthenticated=80; path=开发者_StackOverflow/
Microsoftsharepointteamservices: 12.0.0.6303
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><GetUserCollectionFromGroupResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/directory/"><GetUserCollectionFromGroupResult><GetUserCollectionFromGroup><Users><User ID="201" Sid="S-1-5-21-1545385408-2720673749-3828181483-1245" ....
You'll need to edit the UserGroup.wsdl manually before generating the stubs. You need to add processContents='skip'
to the <s:any>
tag where the response is defined.
<s:element name="GetUserCollectionFromGroupResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetUserCollectionFromGroupResult">
<s:complexType mixed="true">
<s:sequence>
<!-- Added the "processContents" attribute below -->
<s:any processContents='skip' />
</s:sequence>
</s:complexType>
</s:element>
</s:sequence>
</s:complexType>
</s:element>
Then, when processing the response, JAXB will return the children as DOM Elements:
UserGroup service = new UserGroup();
UserGroupSoap port = service.getUserGroupSoap();
GetUserCollectionFromGroupResult usersCollection = port.getUserCollectionFromGroup(Settings.usersGroup);
List<Object> content = usersCollection.getContent();
org.w3c.dom.Element usersElement = (org.w3c.dom.Element) content.get(0);
Why this works
The problem is caused by a combination of conditions:
A. The response returned by the web service contains a <GetUserCollectionFromGroup>
tag:
<GetUserCollectionFromGroupResult>
<GetUserCollectionFromGroup>
<Users>
<User ID="4" Name="User1_Display_Name" />
<User ID="5" Name="User2_Display_Name" />
</Users>
</GetUserCollectionFromGroup>
</GetUserCollectionFromGroupResult>
B. The schema embedded in the WSDL defines the <GetUserCollectionFromGroup>
as containing one child, <groupName>
(this is the element used to make the request):
<s:element name="GetUserCollectionFromGroup">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="groupName" type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
C. JAXB honors the processContents
attribute of <xs:any>
(see Mapping of <xs:any />). When processContents='strict'
, JAXB tries to match (and marshall) the child elements based on the namespace they belong to.
D. The WSDL schema definition for the <GetUserCollectionFromGroupResult>
includes <xs:any>
:
<s:element name="GetUserCollectionFromGroupResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetUserCollectionFromGroupResult">
<s:complexType mixed="true">
<s:sequence>
<s:any/>
</s:sequence>
</s:complexType>
</s:element>
</s:sequence>
</s:complexType>
</s:element>
E. When processContents
is omitted, the default is strict
.
Thus, when JAX-WS/JAXB processes the results from the web service, it tries to marshall the children of <GetUserCollectionFromGroupResult>
using the schema. The children appear in the response as belonging to the same namespace as the request. When the <GetUserCollectionFromGroup>
element is processed, it is marshalled to an instance of the same class used in the request for the <GetUserCollectionFromGroup>
element. Thus, you are effectively blocked from getting at the <Users>
elements.
I've searched high and low, and the only solution(s) that I can find is to ether (a) edit the WSDL as described at the start of this answer, or (b) edit the generated stubs. Not ideal, but unavoidable in this case.
More information about the <xs:any>
schema element (and the processContents
attribute) can be found on MSDN here.
Your question is a bit hard to answer because we don't see the generated client you're using nor the debug/error messages on invocation, but I'll try. Your WSDL looks valid, and if your JAX-WS toolstack is capable of creating a client (with appropriate Java classes) and is able to invoke the endpoint correctly, you did well so far.
Looking at the HTTP response and your WSDL, your SOAP request was the GetUserCollectionFromGroup call. Looking at XML-Schema definition (in the WSDL) of GetUserCollectionFromGroupResponse I'm puzzled about one thing:
<s:element name="GetUserCollectionFromGroupResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="GetUserCollectionFromGroupResult">
<s:complexType mixed="true">
<s:sequence>
<s:any />
</s:sequence>
</s:complexType>
</s:element>
</s:sequence>
</s:complexType>
</s:element>
The WSDL basically says your call can return Any kind of XML. What you got back from the webservice is an XML piece like:
<users><user ID="201" Sid="" /></users>
Of course this does fit the description of Any kind of XML, but I think your generated client has trouble understanding it. Now, I don't have experience with the jax-ws toolstack of NetBeans 6.9, but you should configure the WSDL-to-client generation in such a way that it transforms this 'any' into a java XML-Element or java XML-Node object.
What does the generated code for this call look like? Does it really think you get a String user back?
Thanks for the response. You'r right I should have posted the generated code, but the invocation, and response was so simple I didn't think of it. Simplified the calls are
System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true");
GetUserCollectionFromGroupResult usersCollection = null;
Object o = null;
UserGroup service = new UserGroup();
UserGroupSoap port = service.getUserGroupSoap();
usersCollection = port.getUserCollectionFromGroup(Settings.usersGroup);
The returned usersCollection only contains one element, which is "groupName" with value "null".
Unfortunately Microsoft seem to love to use the ANY element in almost all their WSDL definitions. I have several working, including Authentication, Webs, Lists, Versions, but this one just won't go.
I think it's possible to overwrite the default receiver code, but earlier today I decided it probably would be easier to just write my own simple SOAP client, rather to try and figure out to fix the JAX-WS receiver. So even though it's probably not the most correct approach, it got the work done. Here's the code in all it horror.
I'm new to Java, so go easy on me ;-)
HashMap<String, String> users = null;
String SOAPUrl = Settings.userListWebServiceURL;
String SOAPAction = "http://schemas.microsoft.com/sharepoint/soap/directory/GetUserCollectionFromGroup";
// Create the connection.
URL url = new URL(SOAPUrl);
URLConnection connection = url.openConnection();
HttpURLConnection httpConn = (HttpURLConnection) connection;
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" ?><S:Envelope xmlns:S=\"http://schemas.xmlsoap.org/soap/envelope/\"><S:Body><GetUserCollectionFromGroup xmlns=\"http://schemas.microsoft.com/sharepoint/soap/directory/\"><groupName>");
sb.append(Settings.usersGroup);
sb.append("</groupName></GetUserCollectionFromGroup></S:Body></S:Envelope>");
byte[] b = sb.toString().getBytes("UTF-8");
// Set the appropriate HTTP parameters.
httpConn.setRequestProperty("Content-Length", String.valueOf( b.length ) );
httpConn.setRequestProperty("Content-Type","text/xml; charset=utf-8");
httpConn.setRequestProperty("SOAPAction",SOAPAction);
httpConn.setRequestMethod( "POST" );
httpConn.setDoOutput(true);
httpConn.setDoInput(true);
// Everything's set up; send the XML that was read in to b.
OutputStream out = httpConn.getOutputStream();
out.write( b );
out.flush();
out.close();
// Setup to receive the result and convert stream to DOM Document
DOMParser parser = new DOMParser();
InputStreamReader in = new InputStreamReader(httpConn.getInputStream());
InputSource source = new InputSource(in);
parser.parse(source);
org.w3c.dom.Document d = parser.getDocument();
in.close();
httpConn.disconnect();
// Read the DOM and contruct a Hashmap with username to e-mail mapping.
NodeList nl = d.getElementsByTagName("User");
users = new HashMap<String, String>();
for (int i = 0; i < nl.getLength(); i++) {
NamedNodeMap attr = nl.item(i).getAttributes();
users.put(attr.getNamedItem("LoginName").getNodeValue(), attr.getNamedItem("Email").getNodeValue());
}
精彩评论