Hi,
<P>
I have Dyn Pro application deployed on NW app server. The application uses webservices on PI and the whole process working until a month back. Recently the application stopped working and throwing following error. Also I use following code for Authentication.
<P>
<P>
I am not sure where the issue is? I looked at forums before posting and could not find anything. I wonder if any one ran into the issue and how is it fixed? I appreciate your help.
<P>
<P>
CODE
<P>
<P>
if ( port instanceof Stub ) {
final Stub stub = (Stub)port; stub._setProperty(Stub.USERNAME_PROPERTY, «abcd»);
stub._setProperty(Stub.PASSWORD_PROPERTY, «123»);
} else if (port instanceof DInterfaceInvoker) {
final DInterfaceInvoker invoker = (DInterfaceInvoker)port;
invoker.setProperty(Stub.USERNAME_PROPERTY, «abcd»);
invoker.setProperty(Stub.PASSWORD_PROPERTY, «123»);
}
<P>
}
<P>
Exception is:
<P>
<P>
java.lang.RuntimeException: com.sap.tc.webdynpro.model.webservice.api.WDWSModelExecuteException: Exception on execution of web service with WSDL URL ‘C:
cliqbook
wsdl
MI_Project_Search_Request_Response1.wsdl’ with operation ‘MI_Project_Search_Request_Response’ in interface ‘MI_Project_Search_Request_Response’ at com.app.freeman.search.cliqbook.CliqBookSearch.executeRequest_MI_Project_Search_Request_Response(CliqBookSearch.java:294) at com.app.freeman.search.cliqbook.wdp.InternalCliqBookSearch.executeRequest_MI_Project_Search_Request_Response(InternalCliqBookSearch.java:387) at com.app.freeman.search.cliqbook.CliqBookSearchView.onActionSearch(CliqBookSearchView.java:160) at com.app.freeman.search.cliqbook.wdp.InternalCliqBookSearchView.wdInvokeEventHandler(InternalCliqBookSearchView.java:380) at com.sap.tc.webdynpro.progmodel.generation.DelegatingView.invokeEventHandler(DelegatingView.java:87) at com.sap.tc.webdynpro.progmodel.controller.Action.fire(Action.java:67) at com.sap.tc.webdynpro.clientserver.window.WindowPhaseModel.doHandleActionEvent(WindowPhaseModel.java:420) at com.sap.tc.webdynpro.clientserver.window.WindowPhaseModel.processRequest(WindowPhaseModel.java:132) at com.sap.tc.webdynpro.clientserver.window.WebDynproWindow.processRequest(WebDynproWindow.java:335) at com.sap.tc.webdynpro.clientserver.cal.AbstractClient.executeTasks(AbstractClient.java:143) at com.sap.tc.webdynpro.clientserver.session.ApplicationSession.doProcessing(ApplicationSession.java:321) at com.sap.tc.webdynpro.clientserver.session.ClientSession.doApplicationProcessingStandalone(ClientSession.java:713) at com.sap.tc.webdynpro.clientserver.session.ClientSession.doApplicationProcessing(ClientSession.java:666) at com.sap.tc.webdynpro.clientserver.session.ClientSession.doProcessing(ClientSession.java:250) at com.sap.tc.webdynpro.clientserver.session.RequestManager.doProcessing(RequestManager.java:149) at com.sap.tc.webdynpro.serverimpl.defaultimpl.DispatcherServlet.doContent(DispatcherServlet.java:62) at com.sap.tc.webdynpro.serverimpl.defaultimpl.DispatcherServlet.doPost(DispatcherServlet.java:53) at javax.servlet.http.HttpServlet.service(HttpServlet.java:760) at javax.servlet.http.HttpServlet.service(HttpServlet.java:853) at com.sap.engine.services.servlets_jsp.server.HttpHandlerImpl.runServlet(HttpHandlerImpl.java:401) at com.sap.engine.services.servlets_jsp.server.HttpHandlerImpl.handleRequest(HttpHandlerImpl.java:266) at com.sap.engine.services.httpserver.server.RequestAnalizer.startServlet(RequestAnalizer.java:386) at com.sap.engine.services.httpserver.server.RequestAnalizer.startServlet(RequestAnalizer.java:364) at com.sap.engine.services.httpserver.server.RequestAnalizer.invokeWebContainer(RequestAnalizer.java:1039) at com.sap.engine.services.httpserver.server.RequestAnalizer.handle(RequestAnalizer.java:265) at com.sap.engine.services.httpserver.server.Client.handle(Client.java:95) at com.sap.engine.services.httpserver.server.Processor.request(Processor.java:175) at com.sap.engine.core.service630.context.cluster.session.ApplicationSessionMessageListener.process(ApplicationSessionMessageListener.java:33) at com.sap.engine.core.cluster.impl6.session.MessageRunner.run(MessageRunner.java:41) at com.sap.engine.core.thread.impl3.ActionObject.run(ActionObject.java:37) at java.security.AccessController.doPrivileged(Native Method) at com.sap.engine.core.thread.impl3.SingleThread.execute(SingleThread.java:104) at com.sap.engine.core.thread.impl3.SingleThread.run(SingleThread.java:176) Caused by: com.sap.tc.webdynpro.model.webservice.api.WDWSModelExecuteException: Exception on execution of web service with WSDL URL ‘C:
cliqbook
wsdl
MI_Project_Search_Request_Response1.wsdl’ with operation ‘MI_Project_Search_Request_Response’ in interface ‘MI_Project_Search_Request_Response’ … 33 more Caused by: java.lang.reflect.InvocationTargetException: Server Error at com.sap.engine.services.webservices.jaxrpc.wsdl2java.soapbinding.MimeHttpBinding.processDocumentFault(MimeHttpBinding.java:927) at com.sap.engine.services.webservices.jaxrpc.wsdl2java.soapbinding.MimeHttpBinding.call(MimeHttpBinding.java:1440) at MI_ProjectSearchRequestResponseBindingStub._invoke(MI_ProjectSearchRequestResponseBindingStub.java:99) at com.sap.engine.services.webservices.espbase.client.dynamic.impl.DInterfaceInvokerImpl.invokeOperation(DInterfaceInvokerImpl.java:63) at com.sap.tc.webdynpro.model.webservice.model.WSGenericModelClassExecutable.execute(WSGenericModelClassExecutable.java:68) at com.sap.tc.webdynpro.model.webservice.gci.WSTypedModelClassExecutable.execute(WSTypedModelClassExecutable.java:46) at com.app.freeman.search.cliqbook.CliqBookSearch.executeRequest_MI_Project_Search_Request_Response(CliqBookSearch.java:248) … 32 more Caused by: javax.xml.rpc.soap.SOAPFaultException: Server Error at com.sap.engine.services.webservices.jaxrpc.wsdl2java.soapbinding.MimeHttpBinding.buildFaultException(MimeHttpBinding.java:737) at com.sap.engine.services.webservices.jaxrpc.wsdl2java.soapbinding.MimeHttpBinding.processDocumentFault(MimeHttpBinding.java:860) …
<P>
Thank you,
Balaji
bearchik |
|
Статус: Новичок Группы: Участники
|
Собственно есть DSS поднятый полностью на сайте с гост алгоритмами. Пытаюсь подключится используя примеры — вываливается exception: Цитата: Exception in thread «main» javax.xml.ws.soap.SOAPFaultException: ID4184: The EncryptingCredentials provided in the SecurityTokenDescriptor must not be null if Scope.TokenEncryptionRequired is set to true. This can be caused by the Scope specifying an unsupported encryption key type, or by the incoming RequestSecurityToken message containing an unsupported EncryptionAlgorithm. Override CreateSecurityTokenDescriptor if you need to support algorithms other than AES 256. Гугл, яндекс, дакдакго ничего не дал по этому поводу, куда копать уже не знаю. Код:
Полный стек ошибки: Цитата: Exception in thread «main» javax.xml.ws.soap.SOAPFaultException: ID4184: The EncryptingCredentials provided in the SecurityTokenDescriptor must not be null if Scope.TokenEncryptionRequired is set to true. This can be caused by the Scope specifying an unsupported encryption key type, or by the incoming RequestSecurityToken message containing an unsupported EncryptionAlgorithm. Override CreateSecurityTokenDescriptor if you need to support algorithms other than AES 256. |
|
|
khomenko |
|
Статус: Активный участник Группы: Администраторы, Участники Поблагодарили: 15 раз в 14 постах |
На данный момент DSS предоставляет более простой для интеграции интерфейс REST. Указанная ошибка приходит с Центра Идентификации DSS. Необходимо проверить его настройки: |
|
|
bearchik |
|
Статус: Новичок Группы: Участники
|
Спасибо за ответ. Сертификат шифрования был задан. ID Свойства Статус 3 Отображаемое имя проверяющей стороны: SignServer Активна Сертификат шифрования маркера безопасности: Параметр Значение Субъект CN=dss.testserver.test Издатель CN=dss.testserver.test Отпечаток 8AB8F02F4B602BD6151CE980BE8382605F17C1B5 Срок действия 12.07.2018 |
|
|
marrow |
|
Статус: Активный участник Группы: Участники Сказал «Спасибо»: 4 раз |
День добрый. |
|
|
khomenko |
|
Статус: Активный участник Группы: Администраторы, Участники Поблагодарили: 15 раз в 14 постах |
Добрый день, Client_ID задаёт администратор. См. рук-во администратора, командлеты *-DssClient |
|
|
marrow |
|
Статус: Активный участник Группы: Участники Сказал «Спасибо»: 4 раз |
Есть ли возможность узнать Client_ID от вашего тестового DSS (dss.cryptopro.ru) и сделать запрос? |
|
|
marrow |
|
Статус: Активный участник Группы: Участники Сказал «Спасибо»: 4 раз |
Добрый день. Хочу получить маркер доступа через интерфейс REST. Выполняю запрос: grant_type=password&client_id=test-oauth-client&scope=dss&username=user700&password=12345&resource=test Приходит ответ с ошибкой — {«error»:»invalid_client»} Client_ID в DSS прописан правильный: ClientId : test-oauth-client В журнале DSS (в STS’e) ошибка — Instance Unique Identifier: 1/STS] Source: ClientValidator Message: Client with id user700 is not registered.. На самом деле Пользователь зарегистрирован в DSS’e. |
|
|
marrow |
|
Статус: Активный участник Группы: Участники Сказал «Спасибо»: 4 раз |
AllowedFlow пробовал как и ResourceOwner и ClientCredentials — без успешно. Возможно ли у вас попросить пример Вашего Get-DSSClient (с dss.cryptopro.ru). И пример запроса обращающий по логину и паролю. Выполнял пример с документации — без успешно. Так же не очень понятно в Руководство администратора Вы пишите, что параметр ClientCredentials не доступен, а в Руководство разработчика есть описание и примеры запроса и ответа — «Авторизации с использование учётных данных пользователя» Отредактировано пользователем 27 сентября 2017 г. 14:23:16(UTC) |
|
|
Пользователи, просматривающие эту тему |
Guest |
Быстрый переход
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.
Exception handling is an important aspect in any software application development. An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. When the exception occurs in a Web Service, it will be propagated to the client as a SOAP fault. This article will help you understand different types of exceptions that could be thrown inside the Web Service and how user defined exceptions can improve error handling in your Web Service.
Different Types of Exceptions
Exceptions could be thrown inside a Web Service for various reasons. The possible types of exceptions that could be thrown are RuntimeException, RemoteException, SOAPFaultException and User Defined Exception.
The Web Service developer might try throwing a RuntimeException such as NullPointerException or ArrayIndexOutOfBoundsException inside the Web Service. But, throwing RuntimeException inside the Web Service is considered to be a bad exercise because RuntimeException will always be converted into RemoteException at the client side. While the client is waiting to catch the RuntimeException after invoking a Web Service, he will get only the RemoteException instead of RuntimeException. Eventually, he cannot perform proper error handling for RuntimeException.
The problem with throwing RemoteException is that the different client side libraries will interpret this exception in different ways. It is not portable.
The appropriate exception that the Web Service application could throw is SOAPFaultException. The SOAPFaultException contains four parts: faultcode, faultstring, actor, and detail. This exception can give you the complete information about the error condition. The faultcode might tell you whether the error has happened because of the Server, Client, or something else. For example, when a client doesn’t provide security information such as username and password in the HTTP/SOAP header but the service mandates them, the pre-processing logic in the service implementation might obviously throw an authentication exception. This kind of error is considered to be a Client error. The faultstring contains the corresponding description of the error condition that has happened. This string message is human readable and the developer can easily debug the problem with the help of it. The detail element contains the actual exception message and its complete stack trace. Actually, the detail element is an instance of the javax.xml.soap.Detail class and can be created by using the javax.xml.soap.SOAPFactory.createDetail() API.
User-Defined Exceptions
Although SOAPFaultException gives necessary information that is needed to debug the problem, the Web Service client might want to do something more with a fault message that he had received. The one thing that he might want to do is to have a configurable and dynamic text description of the error that occurred. The other thing could be to have the internationalization support for the error messages. By providing text description in languages other than English, he could try addressing needs of all set of people. The list may grow more and more.
To achieve the previously stated goals, a user-defined exception with some specific format could be thrown in the service. Based on the exception that the client has received, he can decide what to do with fault message. The user-defined exception thrown inside the Web Service is directly mapped into wsdl:fault element in the Web Services Description Language (WSDL) of the Web Service. You could see the fault element as one of the child elements of operation element of the portType. The other two child elements of operation are input and output. The fault element, an optional field in the operation element, defines the abstract format of the error message that might be thrown by the Web Service. The wsdl:message element pertaining to WSDL:fault can contain only one message part, and the message part could be either a complexType or simple XMLType.
I propose here how a user-defined exception, MyCustomException, could be defined to better handle error messages. The MyCustomException contains an appropriate fault message that is as defined in the WSDL definition. The fault message contains the following three parts:
- Error Code, containing a five-letter identifier plus a three-digit identifier
For example, GENEX001 could mean a generic error, whereas AUTEX001 could mean an authentication error.
- Text describing the fault, including substitution variables (mentioned as {0}, {1}, and so forth)
For example, the corresponding text description for Error Code GENEX001 will be something like “Number that you have entered is {0}.”
- A list of values corresponding to the substitution variables defined in the above text.
For example, for the above case, the variable list will contain only one value (say 1) because the text description above contains only one substitution variable.
After processing the text using relevant logic, the final text description would be “Number that you have entered is 1.”
With the above approach:
- Faults can be identified, using the error code, without the need for applications or people to parse or understand the fault text (which may be in a unknown language).
- Text description can be defined in many languages, and corresponding variables can be placed accordingly in a language-independent manner.
- Internationalization support can be offered by the Services by providing alternate language forms of fault messages automatically by replacing the text description portion only.
Sample: Service, Exception Class, WSDL, and Client
In this section, you will see a sample for service, exception, WSDL, and client. These samples will use Weblogic implementation.
Service Implementation
This service takes two inputs: int number and String message. If the number is 1, SOAPFaultException will be thrown. If the number is 2, MyCustomException will be thrown; otherwise, the method will return successfully.
package WS.exception; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFactory; import javax.xml.soap.Detail; public class ExceptionService{ public String echo(int number, String message) throws MyCustomException{ System.out.println("Number: "+number+", Message: " + message); switch(number){ case 2: throw new MyCustomException( "GENEX001", "Number that you have entered is {0}", new String[]{String.valueOf(number)} ); case 1: Detail detail = null; try{ detail = SOAPFactory.newInstance().createDetail(); //add error message here detail.addTextNode( "Choice 1 means SOAPFaultException"); }catch(SOAPException ex){ throw new MyCustomException( "SEREX001", "Error while creating detail element", new String(){}); } throw new javax.xml.rpc.soap.SOAPFaultException( new javax.xml.namespace.QName("env.Server"), "Number that you have entered is "+number, "NO ACTOR", detail); } return "SUCCESS"; } }
MyCustomException class
The pseudo implementation for the custom exception is given below:
package WS.exception; public class MyCustomException extends Exception{ private String errorCode; private String errorDescription; private String[] variables; public MyCustomException(String errorCode, String errorDescription, String[] variables){ this.errorCode = errorCode; this.errorDescription = errorDescription; this.variables = variables; } //all setter and getter methods need to be implemented here }
WSDL Fault
The description of wsdl:fault is given below:
<xsd:schema xmlns_xsd="http://www.w3.org/2001/XMLSchema" xmlns_stns="java:WS.exception" elementFormDefault="qualified" attributeFormDefault="qualified" targetNamespace="java:WS.exception"> <xsd:import namespace="java:language_builtins.lang"> </xsd:import> <xsd:element type="stns:MyCustomException" name="MyCustomException"> </xsd:element> <xsd:complexType name="MyCustomException"> <xsd:sequence> <xsd:element type="xsd:string" name="errorCode" minOccurs="1" nillable="true" maxOccurs="1"> </xsd:element> <xsd:element type="xsd:string" name="errorDescription" minOccurs="1" nillable="true" maxOccurs="1"> </xsd:element> <xsd:element xmlns_tp="java:language_builtins.lang" type="tp:ArrayOfString" name="variables" minOccurs="1" nillable="true" maxOccurs="1"> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:schema> <message name="MyCustomException"> <part xmlns_partns="java:WS.exception" type="partns:MyCustomException" name="MyCustomException"> </part> </message> <portType name="ESPort"> <operation name="echo"> <input message="tns:echo"> </input> <output message="tns:echoResponse"> </output> <fault name="MyCustomException" message="tns:MyCustomException"> </fault> </operation> </portType>
Client Implementation
Web Services can be invoked using two approaches:
- Using stubs
- Using Dynamic Invocation Interface (DII)
Following is the client implementation using DII:
import java.util.*; import javax.xml.namespace.QName; import javax.xml.rpc.*; import javax.xml.rpc.soap.SOAPFaultException; public class ExceptionService{ public static void main(String[] args) throws Exception{ System.setProperty( ServiceFactory.SERVICEFACTORY_PROPERTY, "weblogic.webservice.core.rpc.ServiceFactoryImpl"); ServiceFactory factory = ServiceFactory.newInstance(); Service service = factory.createService(new QName("")); Call call = service.createCall(); call.addParameter("intVal", new QName("http://www.w3.org/2001/XMLSchema", "int"), ParameterMode.IN); call.addParameter("string", new QName("http://www.w3.org/2001/XMLSchema", "string"), ParameterMode.IN); call.setOperationName(new QName("http://tempuri.org", "echo")); call.setReturnType( new QName("http://www.w3.org/2001/XMLSchema", "string")); call.setTargetEndpointAddress("http://localhost/exception/ES"); Object[] argArray = {new Integer(args[0]), "how are you"}; Object resultObj = null; try{ resultObj = call.invoke(argArray); }catch(SOAPFaultException fault){ fault.printStackTrace(); resultObj = "ERROR Caught"; System.out.println("Fault Detail : "+ fault.getDetail()); System.out.println("Fault Actor : "+ fault.getFaultActor()); System.out.println("Fault String: "+ fault.getFaultString()); } System.out.println("Result: "+resultObj); } }
When you generate stubs using the Weblogic clientgen utility, you would be able to catch MyCustomeException itself on the client side. The sample code is given below:
package WS.exception; import java.text.MessageFormat; public class Client { public static void main(String[] args ) throws Exception{ System.setProperty( "javax.xml.rpc.ServiceFactory", "weblogic.webservice.core.rpc.ServiceFactoryImpl"); ES_Impl ws = new ES_Impl(args[0]); ESPort port = ws.getESPort(); String returnVal = null; try{ returnVal = port.echo(Integer.parseInt(args[1]), "A for Apple"); }catch(MyCustomException ex){ System.out.println("MyCustomException occurred:"); //ex.printStackTrace(); String errorCode = ex.getErrorCode(); String errorDescription = ex.getErrorDescription(); String[] variables = ex.getVariables(); String resolvedErrorMsg = MessageFormat.format( errorDescription, variables); if(errorCode.startsWith("AUT")){ //Authentication error: do something }else if(errorCode.startsWith("MSG")){ //SOAP Message error: do something }else{ //General error: do something } }catch(Exception ex){ System.out.println("One of the other Exceptions occurred:"); ex.printStackTrace(); } System.out.println(returnVal); } }
In the program above, after catching MyCustomException, you can get the error code, text, and variables. Based on the error, you can segregate the type of error that had happened. If you want, you can use the text description that comes with the excerption. Otherwise, you can fetch the text description which in other language from a database, or a disk file that is associated with the error code. You can store the error code and description as a name-value pair in a disk file or a database. This will really help you manage internationalization of the error message. Also, MessageFormat class will help you get the dynamic error message.
SOAP Response 1 (when a SOAPFaultException is thrown)
Following is the SOAP response that I got while the service had thrown SOAPFaultException.
<env:Envelope xmlns_env="http://schemas.xmlsoap.org/soap/envelope/" xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns_soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns_xsd="http://www.w3.org/2001/XMLSchema"> <env:Body> <env:Fault xmlns_fault=""> <faultcode>fault:env.Server</faultcode> <faultstring>Number that you have entered is 1</faultstring> <faultactor>NO ACTOR</faultactor> <detail>Choice 1 means SOAPFaultException</detail> </env:Fault> </env:Body></env:Envelope>
SOAP Response 2 (when MyCustomException is thrown)
Following is the SOAP response that I got while the service had thrown MyCustomException.
<env:Envelope xmlns_env="http://schemas.xmlsoap.org/soap/envelope/" xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns_soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns_xsd="http://www.w3.org/2001/XMLSchema"> <env:Body> <env:Fault> <faultcode>env:Server</faultcode> <faultstring>Service specific exception: WS.exception.MyCustomException</faultstring> <detail> <MyCustomException xmlns_n1="java:WS.exception" xsi_type="n1:MyCustomException"> <errorCode xsi_type="xsd:string">GENEX001</errorCode> <errorDescription xsi_type="xsd:string"> Number that you have entered is {0} </errorDescription> <variables soapenc_arrayType="xsd:string[1]"> <string xsi_type="xsd:string">2</string> </variables> </MyCustomException> </detail> </env:Fault> </env:Body></env:Envelope>
Conclusion
This article has discussed various exceptions in Web Service and how a user-defined exception could be used for effective error handling in Web Services.
About the Author
Ayyappan Gandhirajan holds a Bachelor’s degree in Electronics & Communication Engineering from MK University, India and is pursuing a Master’s in Software Systems from BITS, Pilani. He has been working as an Associate System Analyst for Hewlett-Packard, Bangalore. He has more than six years of software experience involving Web services, WS Security, and J2EE technologies. He is currently involved in Web services orchestration and access controllers. He can be reached at [email protected] or [email protected].
Using SOAP Faults and Exceptions in Java JAX-WS Web Services
Pick up a copy of Java SOA Cookbook by Eben Hewitt for more stuff like this.
This article shows you how to avoid this error: javax.xml.ws.soap.SOAPFaultException: java.lang.NoSuchMethodException
This article shows you how to create proper throws clauses in your Java web services, and how to write JUnit 4.4 unit tests to handle the exceptions that you get, and mapping semantics with Java exceptions and SOAP Faults. It also shows you how to use the @WebFault annotation.
If you see a stack trace like this in your Java web service client, the answer is below:
javax.xml.ws.soap.SOAPFaultException: java.lang.NoSuchMethodException: com.example.MyExceptionBean.setMessage(java.lang.String)
at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:171)
at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:94)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:240)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:210)
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:103)
at $Proxy33.verify(Unknown Source)
I.
Let’s start with a regular Java class with an operation that throws an exception, like this:
public class CheckVerify {
public MessageResponseType verify(CheckType check)
throws MyException { … }
}
Because annotations are so easy in JAX-WS, I would expect that I could turn such a class into a Web Service using annotations, have the deployment tool run wsgen and create the web service artifacts (WSDL and XML Schema) for me, which it would. I would expect that Java would handle the exception for me like it does everything else: it turns the regular method into an ‘operation’ in the WSDL—it should also turn the ‘throws MyException’ into a ‘<soap:fault name=»MyException» use=»literal»/>’ in the WSDL too. It does.
So what’s the problem?
The problem is that while this web service will deploy, this doesn’t actually work for a generated Java client of that web service. You need to know a little more, and make use of an additional annotation and something called a fault bean. This article shows you how.
II.
When implementing a web service using Java and JAX-WS, you might want to code in a natural style that allows you to throw an exception from your service operation, and have the runtime translate that into a SOAP fault that is received on the client.
You can code exceptions in JAX-WS, but the documentation on the web for this is practically non-existent, which is strange because it’s such a common thing for Java programmers to want to do. Sometimes you have no choice but to deal with a checked exception that an underlying library throws (IOException is common). We need to know how to deal with this.
Say we have the following web service—the annotated version of the class we started with above. It’s called CheckVerify and it defines a single operation that makes sure that a check writing customer is not known to the bad check writer database:
@WebService(
serviceName=»CheckVerifyService»,
portName=»CheckVerify»,
targetNamespace=»http://www.example.com«)
public class CheckVerify {
@WebMethod(operationName=»verify»)
public @WebResult(name=»checkVerifyResponse»,
partName=»checkVerifyResponse»,
targetNamespace=»http://www.example.com«)
MessageResponseType
verify(
@WebParam(name=»check»,
partName=»check»,
targetNamespace=»http://www.example.com«)
CheckType check)
throws CheckVerifyFault {
//…
}
}
This operation throws a CheckVerifyFault. Say that our implementation of the verify method actually calls a second, remote web service that uses HTTPS to validate the check, or goes to a database using JDBC. There might be checked Java exceptions that we want to translate into a SOAP Fault to use to return to the client.
Here’s the portType snippet of the WSDL that results from these annotations:
<portType name=»CheckVerify»>
<operation name=»verify»>
<input message=»tns:verify» />
<output message=»tns:verifyResponse» />
<fault message=»tns:MyException» name=»MyException» />
</operation>
</portType>
While the above code will deploy to a container such as WebLogic, it won’t have the desired result on the client.
First of all, JAX-WS will create an exception class for us, which is nice. But the exception class will be called MyException_Exception on the client side. Doing what we want is a more complicated and obscure part of the JAX-WS model, and using exceptions is not quite as easy as other things in JAX-WS.
If we run wsimport before our automated test, then compile the generated code and run the test, we’ll see what effect this code has on the client view. And we’ll have a good basis for undersanding the client side of this.
First we know that we want to change the name of the generated exception to something more friendly. So let’s use bindings file to customize the generated code. You can read more about this in Java SOA Cookbook, but if you’re using Maven to run the build that executes your wsimport, you can put a file called bindings.xml in the src/jaxws folder. Give that file the following contents:
<?xml version=»1.0″ encoding=»UTF-8″?>
<bindings
xmlns=»http://java.sun.com/xml/ns/jaxws«
xmlns:wsdl=»http://schemas.xmlsoap.org/wsdl/«>
<bindings node=»wsdl:definitions/wsdl:portType[@name=’CheckVerify’]/wsdl:operation[@name=’verify’]/wsdl:fault[@name=’MyException’]»>
<class name=»MyFault»>
<javadoc>Exception generated during any operation with the service.</javadoc>
</class>
</bindings>
</bindings>
This uses XPath to find the node in the generated WSDL that matches the fault with the name of «MyException» and indicates that the generated class should be named MyFault.
JAX-WS will create two classes now: MyFault, which is the JAXB customization re-name of the MyException class, and a class called MyException. That class will be a bean that holds a message.
The generated MyFault class on the client looks like this:
@WebFault(name = «MyException», targetNamespace = «http://www.example.com«)
public class MyFault extends Exception {
/**
* Java type that goes as soapenv:Fault detail element.
*/
private MyException faultInfo;
public MyFault(String message, MyException faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
public MyFault(String message, MyException faultInfo, Throwable cause) {
super(message, cause);
this.faultInfo = faultInfo;
}
public MyException getFaultInfo() {
return faultInfo;
}
}
The class is annotated with @WebFault, but indicates in its ‘name’ attribute that it is called MyException, which is the bean class (shown below) that we were renaming in our JAXB XML customization file.
So this is where things start getting weird. Notice that a MyFault constructor takes a MyException type. There is not a no-arg constructor in MyFault. The faultInfo (of type MyException) is expected to contain the SOAP Fault detail information. That is, there is a class (here, MyException, that holds the data element value of the <detail> element child of the <soap:fault> in the underlying SOAP envelope—the exception class is not doing that work for us.
So the first thing to understand is that MyException is not an exception type at all, but a regular bean that acts as a holder for the SOAP Fault information, to ensure it complies with that contract (because a SOAP Fault is not the same as a Java checked exception; checked exceptions don’t exist in many languages). Put another way, even if you coded your exception to include a message String along with a getter and setter for it that could hold this info, JAX-WS doesn’t know about it, so you need to define it explicitly.
So here is that generated ‘exception’ class, which is really not an exception at all, and rather a bean that your MyFault class (which actually IS the client-side exception class) accepts.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = «MyException», propOrder = {
«message»
})
public class MyException {
protected String message;
public String getMessage() {
return message;
}
public void setMessage(String value) {
this.message = value;
}
} //comments deleted
Because the WSDL is valid and JAX-WS generated these classes for you, you might think that everything would work at this point. But it won’t. Let’s write the naive unit test we expect would work around this code on the client.
public class CheckVerifyServiceTest {
public static final Logger LOGGER = Logger.getLogger(CheckVerifyServiceTest.class);
private static final String WSDL_URL_KEY = «checkVerify.wsdl.url»;
private static final QName QNAME = new QName(
«http://www.example.com«, «CheckVerifyService»);
private static CheckVerify checkVerify;
private static final String TODAY;
@BeforeClass
public static void setup() throws Exception {
LOGGER.debug(«Attempting to initiate service…»);
String wsdl = System.getProperty(WSDL_URL_KEY);
LOGGER.debug(«Using WSDL: » + wsdl);
final URL wsdlUrl = new URL(wsdl);
final CheckVerifyService service = new CheckVerifyService(wsdlUrl, QNAME);
checkVerify = service.getCheckVerify();
if (checkVerify != null) {
LOGGER.debug(«Found service stub.»);
} else {
fail();
}
}
@Test
public void testMe() {
LOGGER.debug(«Executing…»);
CheckType check = new CheckType();
//….
MessageResponseType response = null;
try {
response = checkVerify.verify(check);
LOGGER.debug(«Completed Exception response.»);
} catch (MyFault ex) {
LOGGER.error(«Error class: » + ex.getClass().getName());
LOGGER.error(«Caught error. Message: » + ex.getMessage());
assertTrue(true);
}
}
}
In our @BeforeClass annotated method we use the generated service stub to get the port type for the service, and use that in our tests.
In the @Test method we invoke the verify method that throws the MyException exception (which we renamed to MyFault using JAXB customizations). So we put a try/catch around it and we should be able to expect that if we do something to make the method throw a fault/exception, that it would be caught here and our assertion would pass and everything would be fine, right? Wrong. The above code doesn’t work.
NOTE: The proper way to positively test that your method throws an exception when it is supposed to in JUnit is with the @Test(expected=MyFault.class) annoation attribute. We want to look at the details here, so this is for illustration.
Here’s what happens when you run the above test is that the test compiles but results in an ERROR (not a regular test failure):
testMe(com.dte.soa.checks.ws.CheckVerifyServiceTest) Time elapsed: 0.094 sec <<< ERROR!
javax.xml.ws.soap.SOAPFaultException: java.lang.NoSuchMethodException: com.dte.soa.checks.ws.jaxws.MyExceptionBean.setMessage(java.lang.String)
at com.sun.xml.internal.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:171)
at com.sun.xml.internal.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:94)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:240)
at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:210)
at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:103)
at $Proxy33.verify(Unknown Source)
The problematic thing here is this line:
javax.xml.ws.soap.SOAPFaultException: java.lang.NoSuchMethodException: com.example.MyExceptionBean.setMessage(java.lang.String)
The problem appears to be that there is no method called setMessage in the MyExceptionBean class. This seems like a bug in JAX-WS, because we didn’t write any class called MyExceptionBean, and it’s not listed in our classes under the Generated Sources, so how could we possibly fix this?
The answer is to do this on the service side:
1. Write a class that extends Exception.
2. Annotate it with @WebFault and point its faultBean attribute to the name of a Java class that has a no-arg constructor, a message String field, and a getter and setter for it. This will be your carrier for that soap:fault detail element.
3. Redeploy your service and regenerate your client. Now the client view will be exactly how you want it. JAX-WS will take any message you supply to your exception type on the server side and put it in the SOAP Fault detail in the SOAP envelope. It will unpack that string message into the fault bean and make it available as the message within the exception you’re catching.
Below are examples of using the @WebFault annotation to deal with SOAP faults and exceptions on the web service side.
We still write the method on the service exactly as before—there’s no @WebFault annotation on the method—it’s a class-level annotation.
@WebMethod(operationName=»verify»)
public @WebResult(name=»checkVerifyResponse»,
partName=»checkVerifyResponse»,
targetNamespace=»http://www.example.com«)
MessageResponseType
verify(
@WebParam(name=»check»,
partName=»check»,
targetNamespace=»http://www.example.com«)
CheckType check)
throws CheckVerifyFault {
Now we’ll write that CheckVerifyFault class:
@WebFault(name=»CheckVerifyFault»,
targetNamespace=»http://www.example.com«)
public class CheckVerifyFault extends Exception {
/**
* Java type that goes as soapenv:Fault detail element.
*/
private CheckFaultBean faultInfo;
public CheckVerifyFault(String message, CheckFaultBean faultInfo) {
super(message);
this.faultInfo = faultInfo;
}
public CheckVerifyFault(String message, CheckFaultBean faultInfo, Throwable cause) {
super(message, cause);
this.faultInfo = faultInfo;
}
public CheckFaultBean getFaultInfo() {
return faultInfo;
}
}
This class extends Exception, and indicates that it takes a CheckFaultBean. So in Java web services we don’t have the luxury of just using any checked exception that we have lying around; we’ll have to annotate it, add the constructors that take our Bean info object, and then create that Bean info class. Here’s what that looks like:
/**
* The Java type that goes as soapenv:Fault detail element.
* Used in web services exceptions, fault beans just hold the details
* of the SOAP fault. This one is used by the {@link CheckVerifyFault).
*
* @author eben hewitt
*/
public class CheckFaultBean {
private String message;
public CheckFaultBean() { }
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Now you’ve got something that matches the SOAP semantics better, and we can recompile and redeploy the web service, then regenerate our client artifacts. Because I’ve renamed the exception class here, I’ll need to update my bindings.xml file too, of course.
We also need to use this bean info in our service method. So instead of just throwing exception, we populate the fault bean with the data for the SOAP Fault Detail, and pass that into our exception; JAX-WS will handle the rest.
Here is our update service operation:
public MessageResponseType verify(CheckType check)
throws CheckVerifyFault {
LOGGER.debug(«Executing verify method in Check web service.»);
if (check == null) {
final String msg = «The Check cannot be null.»;
LOGGER.debug(msg);
CheckFaultBean faultBean = new CheckFaultBean();
faultBean.setMessage(msg);
throw new CheckVerifyFault(«This is the Basic Message.», faultBean);
}
//do work…
}
Notice that we create a bean instance, put our message for the client in that, then construct the exception and throw it. We can include two messages: the one associated with the exception string, and the more detailed message that is the string value of the FaultInfo object.
Here is the updated test case. It passes a null parameter to the verify method. That’s just the condition the verify method is looking for to throw an exception:
@Test
public void testException() {
LOGGER.debug(«Executing…»);
CheckType check = new CheckType();
MessageResponseType response = null;
try {
response = checkVerify.verify(null);
LOGGER.debug(«Completed Exception response.»);
} catch (CheckVerifyFault ex) {
LOGGER.error(«Error class: » + ex.getClass().getName());
LOGGER.error(«Caught error. Message: » + ex.getMessage());
LOGGER.error(«Detailed Message: » + ex.getFaultInfo().getMessage());
assertTrue(true);
}
}
Running this test gives us just the result that we want:
7/1/09-17:15 ERROR com.example.CheckVerifyServiceTest.testException — Error class: com.discounttire.checkverify.service.CheckVerifyFault
7/1/09-17:15 ERROR com.example.CheckVerifyServiceTest.testException — Caught error. Message: This is the Basic Message.
7/1/09-17:15 ERROR com.example.CheckVerifyServiceTest.testException — Detailed Message: The Check cannot be null.
…
BUILD SUCCESSFUL
————————————————————————
Total time: 10 seconds
Finished at: Wed Jul 01 17:15:54 MST 2009
Final Memory: 25M/153M
Now you can see that:
1. The test actually allows a totally natural client feel for using Java exceptions and SOAP faults.
2. The string that we populated the exception with on the service-side is readily visible to the client.
3. We get both the regular string message in the exception, and the detail in the fault info bean.
To read more about Java web services and SOAP, pick up a copy of my new book, Java SOA Cookbook from Amazon.
The comments to this entry are closed.