I have a running spring-ws project that can unmarshal requests using Jax2b, but when unmarshalling of integers/booleans fail I get an error message with little detail and often without the name of the invalid element. E.g.:
org.springframework.oxm.UnmarshallingFailureException: JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; cvc-datatype-valid.1.2.1: '' is not a valid value for 'integer'.]
This also becomes the content of the SOAPFault response from my webservice.
I am trying to change the message to include the element name. I'm using a ValidationEventHandler to change the message by throwing a RuntimeException from the event handler, but it only works i some cases.
ValidationEventHandler:
#Component
public class ValidationEventHandlerImpl implements ValidationEventHandler {
#Override
public boolean handleEvent(ValidationEvent event) {
String message = event.getMessage();
String linkedMessage = "";
if(event.getLinkedException() != null)
linkedMessage = event.getLinkedException().toString();
boolean ignoreValidationEvent = true;
if(message.contains("NumberFormatException") ||
message.contains("is not a valid value") ||
linkedMessage.contains("NumberFormatException") ||
linkedMessage.contains("is not a valid value")){
ignoreValidationEvent = false;
}
if(ignoreValidationEvent){
return true;
}else{
String nodeName = "";
if(event.getLocator() != null && event.getLocator().getNode() != null)
nodeName = event.getLocator().getNode().getNodeName();
//This is the important line
throw new RuntimeException("Error parsing '" + nodeName + "': " + event.getMessage());
}
}
}
It successfully changes:
JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException: Not a number: 32g321
- with linked exception:
[java.lang.NumberFormatException: Not a number: 32g321]
to: RuntimeException message: "java.lang.RuntimeException: Error parsing 'MyNodeName': Not a number: 32g321"
(Event Severity: ERROR)
But it does not work when I want it to change:
JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; cvc-datatype-valid.1.2.1: '' is not a valid value for 'integer'.]
to: RuntimeException message: "java.lang.RuntimeException: Error parsing 'MyNodeName': '' is not a valid value for 'integer'".
The RuntimeException is ignored and the SAXParseException is thrown instead and added to the SOAPFault reponse.
(Event Severity: FATAL_ERROR)
Spring configuration for Jaxb2Marshalling:
<bean id="jaxb2MarshallerContact" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.model.oxm.ContactRequest</value>
<value>com.model.oxm.ContactResponse</value>
</list>
</property>
<property name="marshallerProperties">
<map>
<entry>
<key>
<util:constant static-field="javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT" />
</key>
<value type="boolean">true</value>
</entry>
<entry>
<key>
<util:constant static-field="javax.xml.bind.Marshaller.JAXB_FRAGMENT" />
</key>
<value type="boolean">true</value>
</entry>
</map>
</property>
<property name="schema" ref="ContactServiceSchema" />
<property name="validationEventHandler" ref="validationEventHandlerImpl" />
</bean>
<bean id="ContactServiceSchema" class="org.springframework.core.io.ClassPathResource">
<constructor-arg value="WEB-INF/schemas/ContactService.xsd" />
</bean>
Endpoint:
#Endpoint
public class ContactEndpoint {
private Logger logger = Logger.getLogger(ContactEndpoint.class);
#Autowired
private ContactService contactService;
private static final String NAMESPACE_URI = "http://mydomain/schemas";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "ContactRequest")
#ResponsePayload
public ContactResponse handleContactRequest(#RequestPayload ContactRequest contactRequest) throws Exception {
...
How can I return a custom message instead of the SAXParseException message?
Is there a better way of implementing this, e.g. using ValidationErrorHandler?
Thanks!
I finally found a way around this issue. Instead of throwing a new RuntimeException from the ValidationEventHandler, I added it as a suppressed exception on the events linked exception:
event.getLinkedException().addSuppressed(new RuntimeException(errorMessage));
and in the Endpoint I changed the RequestPayload the soapenvelope instead. The marshallingService wraps the jax2bmarshaller:
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "ContactRequest")
#ResponsePayload
public ContactResponse handleContactRequest(#RequestPayload SoapEnvelope soapEnvelope) throws Exception {
ContactRequest contactRequest = marshallingService.unmarshalContact(soapEnvelope.getBody().getPayloadSource());
the marshallingService catch the exception, extract my suppressed exception message and throws that instead:
((UnmarshalException) xmlMappingException.getCause()).getLinkedException().getSuppressed()[0].getLocalizedMessage();
It is not an elegant solution but the endpoint produces much better error messages than before.
Have you tried using the PayloadValidatingInterceptor? That will validate the message before demarshalling as far as I know. Perhaps that'll give you some more insight in what is going wrong.
You can simply set it up in your application config:
private ClassPathResource yourXsdFile = new ClassPathResource("xsd.xsd");
#Bean
public PayloadValidatingInterceptor validatingInterceptor() {
PayloadValidatingInterceptor interceptor = new PayloadValidatingInterceptor();
interceptor.setSchema(yourXsdFile);
interceptor.setAddValidationErrorDetail(true);
return interceptor;
}
#Bean
public PayloadRootAnnotationMethodEndpointMapping endpointMapping() {
PayloadRootAnnotationMethodEndpointMapping mapping = new PayloadRootAnnotationMethodEndpointMapping();
mapping.setInterceptors(new EndpointInterceptor[]{
validatingInterceptor()
});
return mapping;
}
For more info check out the api docs.
Related
I am consuming WCF Data Service as Following:
DataMan.ContextWrapper context = new DataMan.ContextWrapper(new Uri("http://localhost:2060/PCM/DataMan.svc/rest/"));
DataMan.Report newReport = DataMan.Report.CreateReport("123123123123", DateTime.Now, "999.199905171156550187000.25");
newReport.Title = "tt";
newReport.StudyAcDate = Convert.ToDateTime("2016-05-04 12:09:00");
newReport.Body = "asdasd";
newReport.Auther = "ali.h";
newReport.ApproverComment = "cm";
newReport.Approver = "admin";
context.AddToReports(newReport);
DataServiceResponse response = context.SaveChanges();
but after calling SaveChange() I have got the following error:
The server encountered an error processing the request. The exception message is 'Incoming message for operation 'ProcessRequestForMessage' (contract 'IRequestHandler' with namespace 'http://tempuri.org/') contains an unrecognized http body format value 'Xml'. The expected body format value is 'Raw'. This can be because a WebContentTypeMapper has not been configured on the binding. See the documentation of WebContentTypeMapper for more details.'. See server logs for more details.
and my WCF Data Service is as following:
public class ContextWrapper : DataAccessDbContext
{
public ContextWrapper() : base("connection string")
{
}
}
[JSONPSupportBehavior]
public class DataMan : EntityFrameworkDataService<ContextWrapper>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetEntitySetAccessRule("Studies", EntitySetRights.None);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.UseVerboseErrors = true; // TODO - Remove for production?
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
protected override void HandleException(HandleExceptionArgs args)
{
base.HandleException(args);
}
}
I also implemented and configured WebContentTypeMapper to bypass mentioned Error as following:
public class ContentTypeMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Raw;
}
}
Custom binding:
<binding name="XmlMapper">
<webMessageEncoding webContentTypeMapperType="MARCO.SOA.PCMServiceLib.ContentTypeMapper,MARCO.SOA.PCMServiceLib.Core"/>
<httpTransport manualAddressing="true"/>
</binding>
</customBinding>
Service endpoint:
<service behaviorConfiguration="Commom2.Behavior" name="MARCO.SOA.PCMServiceLib.DataMan">
<endpoint address="rest" behaviorConfiguration="Rest.Behavior" binding="webHttpBinding"
bindingConfiguration="XmlMapper" contract="System.Data.Services.IRequestHandler">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:2060/PCM/DataMan.svc"/>
</baseAddresses>
</host>
</service>
but it still get exception, I think something went wrong with my configuration.
Any help would be truly appreciated.
Thanks in advance.
okay, after much trouble I finally solved the problem,
so we need to initiate factory property for serviceActivation
So my relative address was:
<serviceHostingEnvironment>
<serviceActivations>
.
.
.
<add relativeAddress="DataMan.svc" service="MARCO.SOA.PCMServiceLib.DataMan"/>
.
.
.
</serviceActivations>
</serviceHostingEnvironment>
and I have changed it to
<serviceHostingEnvironment>
<serviceActivations>
.
.
.
<add relativeAddress="DataMan.svc" service="MARCO.SOA.PCMServiceLib.DataMan" factory="System.Data.Services.DataServiceHostFactory, Microsoft.Data.Services, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
.
.
.
</serviceActivations>
</serviceHostingEnvironment>
now everything is now working nice.
more info about DataServiceHostFactory
Note:
By this we don't need to override GetMessageFormatForContentType of WebContentTypeMapper and force it to return WebContentFormat.Raw or another content format and don't need any customBinding in config file.
Thanks to all.
I've tried a 1000 different options an permutations.
Why can't compile my Cat mapping?
Exception thrown: 'NHibernate.MappingException' in NHibernate.dll
Additional information: Could not compile the mapping document: (string)
Configuration cfg = new Configuration();
cfg.Configure();
cfg.AddXmlString(
#"<?xml version=""1.0"" encoding=""utf - 8"" ?>"
+ #"<hibernate-mapping xmlns=""urn:nhibernate-mapping-2.2"" assembly=""QuickStart"" namespace=""QuickStart.Cat"">"
+ #"<class name=""Cat"" table=""Cat"">"
+ #" <id name=""Id"" ><column name=""CatId"" /><generator class=""uuid.hex"" /></id>"
//+ #" <id name=""Id"" ><column name=""CatId"" sql-type=""char(32)"" not-null=""true"" /><generator class=""uuid.hex"" /></id>"
//+ #" <property name=""Name"" ><column name=""Name"" length=""16"" not-null=""true"" /></property>"
//+ #" <property name=""Sex"" />"
//+ #" <property name=""Weight"" />"
+ #"</class>"
+ #"</hibernate-mapping>"
);
--- from Web.Config
NHibernate.Connection.DriverConnectionProvider
NHibernate.Driver.SqlClientDriver
NHibernate.Dialect.MsSql2012Dialect
Server=localhost;initial catalog=PlaygroundDB;Integrated Security=True
encoding=""utf - 8"" ?> should be encoding=""utf-8""?>
But I never add xml mapping programmaticaly one by one. And even less as raw string (which is quite prone to errors). Instead, I add ClassName.hbm.xml files in project, setting their Build action property to Embedded resource, and I call .AddAssembly(MyProjectAssembly) before building the configuration.
If your mappings are embedded in the same assembly defining your session factory, the code is as simple as:
public static class NHibernateSessionFactory
{
private static readonly ISessionFactory _sessionFactory;
static NHibernateSessionFactory()
{
var configuration = new Configuration();
configuration.Configure();
configuration.AddAssembly(typeof(NHibernateSessionFactory).Assembly);
_sessionFactory = configuration.BuildSessionFactory();
}
public static ISessionFactory Instance { get { return _sessionFactory; } }
}
Minimal configuration file (hibernate.cfg.xml) I am using:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory name="ProjectName">
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string_name">ProjectConnectionStringName</property>
<property name="dialect">NHibernate.Dialect.MsSql2012Dialect</property>
<property name="adonet.batch_size">100</property>
<property name="prepare_sql">true</property>
</session-factory>
</hibernate-configuration>
ProjectConnectionStringName refers to a connection string defined in .Net standard configuration connectionStrings node.
I have searched for a good example and cannopt find one. I want to take the username and password from the SOAP header, and set the spring security context after I authenticate using our exisiting service methods. I have implemented the Wss4jSecurityInterceptor and it validates the header element. WHat I need to do in the callback, or some other mechanism, is create an uthetication context so I can access it later in our endpoint.
However, I dont think that the callback is the correct place to do it, as I keep getting password supplied no password errors. I am new to spring security and integration.
Config:
<bean id="SOAPSecurityInterceptor" class="com.ps.snt.ws.interceptor.SOAPSecurityInterceptor">
<property name="validationActions" value="UsernameToken"/>
<property name="validationCallbackHandler" ref="callbackHandler"/>
</bean>
<bean id="callbackHandler" class="com.ps.snt.ws.interceptor.SOAPSecurityValidationCallbackHandler">
</bean>
callback:
public class SOAPSecurityValidationCallbackHandler extends SimplePasswordValidationCallbackHandler {
#Override
protected void handleUsernameToken(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException {
System.out.println("In security callback " + callback.getPassword());
boolean valid = true;
String token = callback.getIdentifier();
String password = callback.getPassword();
Integer zoneID = null;
String username = null;
StringBuffer errorMessages = new StringBuffer();
if(StringUtils.isEmpty(token)) {
errorMessages.append("Username token cannot be empty");
valid = false;
} else {
Pattern pattern = Pattern.compile("^[\\w]+\\d\\d\\d\\d\\d");
Matcher matcher = pattern.matcher(token);
if(!matcher.matches()) {
valid = false;
errorMessages.append("Username token must be in the format 'user#zone'.");
}
else {
String[] parts = token.split("#");
username = parts[0];
zoneID = Integer.parseInt(parts[1]);
}
}
if(StringUtils.isEmpty(password)) {
errorMessages.append("Password cannot be empty.");
valid = false;
}
if(valid && username != null && zoneID != null) {
LoginService loginService = new LoginService();
LoginContextDO loginContextDO = loginService.getAuthenticatedLoginContext(username, password, zoneID);
AbstractAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password);
authentication.setDetails(loginContextDO);
authentication.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
System.out.println("Authetnication failed!");
}
}
}
My requirements are simple:
- validate the SOAP header (works)
- retrieve the username and password
- call our legacy service to create our login context
- set the spring security context (with logincontext as details) so I can use later in an endpoint
What mechanism can I use to validate the soap header and set a security context from that header?
SpringSecurityPasswordValidationCallbackHandler is for you. From Spring WS docs:
The SpringSecurityPasswordValidationCallbackHandler validates plain text and digest passwords using a Spring Security UserDetailService to operate. It uses this service to retrieve the (digest of ) the password of the user specified in the token. The (digest of) the password contained in this details object is then compared with the digest in the message. If they are equal, the user has successfully authenticated, and a UsernamePasswordAuthenticationToken is stored in theSecurityContextHolder. You can set the service using the userDetailsService. Additionally, you can set a userCache property, to cache loaded user details.
<beans>
<bean class="org.springframework.ws.soap.security.wss4j.callback.SpringDigestPasswordValidationCallbackHandler">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
<bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
...
</beans>
I'm working on a large Grails 1.3.7 project and I want to access the flow state name from a filter for logging purposes. I've been googling a lot and the closest answer I could find was: Grails WebFlow State Name, but it only works from within the flow itself.
Is there any way to obtain the state name of the flow that is being executed in current session from outside the flow (the filter)?
Thanks in advance,
Guillermo
After a lot of googling and debugging I managed to produce the following code. It works in a simple application. I'll integrate it with the main application when I come back from holidays and then I'll update this question.
package org.glalejos
import org.springframework.context.ApplicationContext
import org.springframework.webflow.context.ExternalContext
import org.springframework.webflow.context.ExternalContextHolder
import org.springframework.webflow.context.servlet.ServletExternalContext
import org.springframework.webflow.execution.FlowExecution
import org.springframework.webflow.execution.FlowExecutionKey
import org.springframework.webflow.execution.repository.FlowExecutionRepository
class LoggingFilters {
def grailsApplication
String getFlowStateName(def grailsApplication, def servletContext, def request, def response) {
String stateName
if (grailsApplication && servletContext && request && request.queryString && response) {
try {
String strKey = null
String[] keys = request.queryString.split("&")
keys.each{ if (it.startsWith("execution=")) strKey = it.substring(10)}
if (strKey != null) {
ApplicationContext ctx = grailsApplication.mainContext
FlowExecutionRepository fer = ctx.getBean("flowExecutionRepository")
FlowExecutionKey fek = fer.parseFlowExecutionKey(strKey)
ExternalContext previousContext = ExternalContextHolder.getExternalContext()
try {
// You have to set an external context before invoking "fer.getFlowExecution()" or it'll throw a NPE
ExternalContextHolder.setExternalContext(new ServletExternalContext(servletContext, request, response));
FlowExecution fe = fer.getFlowExecution(fek)
stateName = fe.getActiveSession().getState().getId()
} finally {
ExternalContextHolder.setExternalContext(previousContext);
}
} else {
stateName = null
}
} catch(Exception e) {
stateName = null
}
} else {
stateName = null
}
return stateName
}
def filters = {
logData(controller:"*", action:"*") {
before = {
println("Incoming request. Current flow state name is: ${getFlowStateName(grailsApplication, servletContext, request, response)}")
}
after = {
println("Dispatched request. Current flow state name is: ${getFlowStateName(grailsApplication, servletContext, request, response)}")
}
}
}
}
EDIT: The code above is OK to determine the name of the current flow state at a given point in time, but it won't update the Mapped Diagnostic Context of the logging framework as the flow execution evolves. For this purpose it is necessary to implement a org.springframework.webflow.execution.FlowExecutionListener and register it in conf/spring/resources.groovy:
beans = {
myLoggingFlowExecutionListener(org.example.MyLoggingFlowExecutionListener)
}
You have to register this listener bean and the hibernateConversationListener bean in a executionListenerLoader bean, but, for some reason, Spring DSL doesn't work in this case (see EDIT2 below). So here is the resources.xml you can place in the same folder as resources.groovy in order to properly declare your resources:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="executionListenerLoader" class="org.springframework.webflow.execution.factory.StaticFlowExecutionListenerLoader">
<constructor-arg>
<list>
<ref bean="hibernateConversationListener" />
<ref bean="myLoggingFlowExecutionListener" />
</list>
</constructor-arg>
</bean>
</beans>
Each FlowExecutionListener method receives a lot of context information that can be used for logging purposes (I'm ommiting the implementation of this class for clarity).
EDIT2: Failing to add the hibernateConversationListener bean to the executionListenerLoader results in Hibernate exceptions when manipulating domain objects during the lifecycle of the flow. However, Spring DSL doesn't work in this specific case, so I had to declare the required beans using XML format. See http://grails.1312388.n4.nabble.com/Registering-custom-flow-execution-listener-td2279764.html. I've updated the code above to the final, working, version.
I have used Spring OXM and JiBX in my application.
below is my Spring Config file
<context:component-scan base-package="com.controller"/>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
<oxm:jibx-marshaller target-class="com.request.RequestClass" id="rqMarshaller"/>
<oxm:jibx-marshaller target-class="com.response.ResponseClass" id="rsMarshaller"/>
<bean id="xmlViewer" class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg ref="rsMarshaller" />
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
below is controller class
#Controller
public class MyController {
#Autowired
private JibxMarshaller rqMarshaller;
#RequestMapping(value = "/myrequest", method = RequestMethod.POST)
public ModelAndView searchFlights(#RequestBody String request) {
System.out.println("Inside");
System.out.println("request = "+request);
Source source = new StreamSource(new StringReader(request));
RequestClass rq = null;
try {
rq = (RequestClass) rqMarshaller.unmarshal(source);
} catch (XmlMappingException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
ResponseClass e = new ResponseClass();
e.setVersion("2.0");
Orig ond = new Orig();
ond.setCode("AIT");
e.getOrig().add(ond);
return new ModelAndView("xmlViewer","object",e);
}
}
When i send XML request it marshaled successfully but for response i got following error message.
org.jibx.runtime.JiBXException: No marshaller defined for class com.response.ResponseClass
I have already defined marshaller for ResponseClassin spring config file.
please help. Thanks.
Finally i figured out the solution !!!
Need to specify the bindingName attribute while registering the JiBxMarshaller.
<oxm:jibx-marshaller target-class="com.request.RequestClass" id="rqMarshaller" bindingName="rqBinding"/>
<oxm:jibx-marshaller target-class="com.response.ResponseClass" id="rsMarshaller" bindingName="rsBinding/>
and specify same name in respective binding/mapping file of JiBX.
That's it !