Spring-ws MessageDispatcherServlet e DataManager

Partendo dal problema risolto in Servlet registrata ma non raggiungibile, stavo provando un esempio leggermente più complesso nel quale la servlet che uso è di spring-ws (org.springframework.ws.transport.http.MessageDispatcherServlet):
quello che sto cercando di fare è affiancare al progetto di esempio sessionPlanner un servizio soap che prenoti una sessione.
ho aggiunto tutte le dipendenze, creato il file xsd, creato il file @Endpoint.

package com.company.sessionplanner.ws;

import com.company.sessionplanner.service.SessionService;
import com.company.sessionplanner.service.SpeakerService;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

@Endpoint
public class SessionPlannerEndpoint {
    private static final String NAMESPACE_URI = "http://company.com/sessionplanner/schemas";

    private XPathExpression<Element> topicExpression;
    private XPathExpression<Element> startDateExpression;
    private XPathExpression<Element> endDateExpression;
    private XPathExpression<Element> descriptionExpression;
    private XPathExpression<Element> firstNameExpression;
    private XPathExpression<Element> lastNameExpression;
    private XPathExpression<Element> emailExpression;

    private SessionService sessionService;
    private SpeakerService speakerService;

    @Autowired
    public SessionPlannerEndpoint(SessionService sessionService, SpeakerService speakerService) throws JDOMException {
        this.sessionService = sessionService;
        this.speakerService = speakerService;

        Namespace namespace = Namespace.getNamespace("sp", NAMESPACE_URI);
        XPathFactory xPathFactory = XPathFactory.instance();

        firstNameExpression = xPathFactory.compile("//sp:FirstName", Filters.element(), null, namespace);
        lastNameExpression = xPathFactory.compile("//sp:LastName", Filters.element(), null, namespace);
        emailExpression = xPathFactory.compile("//sp:Email", Filters.element(), null, namespace);

        topicExpression = xPathFactory.compile("//sp:Topic", Filters.element(), null, namespace);
        startDateExpression = xPathFactory.compile("//sp:StartDate", Filters.element(), null, namespace);
        endDateExpression = xPathFactory.compile("//sp:EndDate", Filters.element(), null, namespace);
        descriptionExpression = xPathFactory.compile("//sp:Description", Filters.element(), null, namespace);
    }


    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "SessionRequest")
    public void handleSessionRequest(@RequestPayload Element SessionRequest) throws Exception {
        String firstName = firstNameExpression.evaluateFirst(SessionRequest).getText();
        String lastName = lastNameExpression.evaluateFirst(SessionRequest).getText();
        String email = emailExpression.evaluateFirst(SessionRequest).getText();

        speakerService.ensureExistence(firstName, lastName, email); // il vero campo unique è email

        String topic = topicExpression.evaluateFirst(SessionRequest).getText();
        Date startDate = parseDate(startDateExpression, SessionRequest);
        Date endDate = parseDate(endDateExpression, SessionRequest);
        String description = descriptionExpression.evaluateFirst(SessionRequest).getText();

        sessionService.bookSession(startDate, endDate, email);
    }


    private Date parseDate(XPathExpression<Element> expression, Element element) throws ParseException {
        Element result = expression.evaluateFirst(element);
        if (result != null) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");
            return dateFormat.parse(result.getText());
        } else {
            throw new IllegalArgumentException("Could not evaluate [" + expression + "] on [" + element + "]");
        }
    }

    public SessionService getSessionService() {
        return sessionService;
    }

    public void setSessionService(SessionService sessionService) {
        this.sessionService = sessionService;
    }

    public SpeakerService getSpeakerService() {
        return speakerService;
    }

    public void setSpeakerService(SpeakerService speakerService) {
        this.speakerService = speakerService;
    }

}

In questo file ho cercato di utilizzare com.company.sessionplanner.service.SessionService già usato nell’esempio quickstart (ho aggiunto un metodo), creando analogamente anche com.company.sessionplanner.service.SpeakerService e i relativi bean con i quali interagire tramite DataManager con il livello database.

Il problema è proprio che sembra che la servlet spring-ws MessageDispatcherServlet venga creata ma credo con un context diverso in quanto in fase di avvio (ho forzato l’init) mi compare questa eccezione:

13:40:16.501 WARN  o.s.w.c.s.XmlWebApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sessionplanner_SessionService': Unsatisfied dependency expressed through field 'dataManager'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.haulmont.cuba.core.global.DataManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.inject.Inject()}
13:40:16.511 ERROR o.s.w.t.h.MessageDispatcherServlet - Context initialization failed

Qui di seguito la WebInitializer:

package com.company.sessionplanner.ws;

import com.haulmont.cuba.core.sys.AbstractWebAppContextLoader;
import com.haulmont.cuba.core.sys.servlet.ServletRegistrationManager;
import com.haulmont.cuba.core.sys.servlet.events.ServletContextInitializedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import javax.inject.Inject;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

@Component
public class WebInitializer {
    @Inject
    private ServletRegistrationManager servletRegistrationManager;

    @EventListener
    public void initializeHttpServlet(ServletContextInitializedEvent e) {
        Servlet myServlet = servletRegistrationManager.createServlet( e.getApplicationContext(), "org.springframework.ws.transport.http.MessageDispatcherServlet");

        ServletContext context = e.getSource();
        context.setInitParameter("contextConfigLocation","WEB-INF/ws-mic-servlet.xml");

        try {
            myServlet.init(new AbstractWebAppContextLoader.CubaServletConfig("ws-mic", context));
        } catch (ServletException ex) {
            ex.printStackTrace();
        }

        //context.setInitParameter("transformWsdlLocations",true);

        context.addServlet("ws-mic", myServlet).addMapping("/soap/*");

    }
}

ho creato il file ws-mic-servlet.xml in WEB-INF:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:sws="http://www.springframework.org/schema/web-services"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.company.sessionplanner"/>

    <sws:annotation-driven/>

    <sws:dynamic-wsdl id="sessionplanner"
                      portTypeName="HumanResource"
                      locationUri="/soap/"
                      targetNamespace="http://mycompany.com/sessionplanner/definitions">
        <sws:xsd location="sessionplanner.xsd"/>
    </sws:dynamic-wsdl>

</beans>

Pensavo che con

 servletRegistrationManager.createServlet( e.getApplicationContext(), "org.springframework.ws.transport.http.MessageDispatcherServlet");

venisse “propagato” il context…
Non riesco a capire se sia un errore dovuto ad una configurazione sbagliata, oppure se manchi qualcosa, oppure se proprio non si possa fare

grazie

Ciao, domanda preliminare a bruciapelo: ma c’è un motivo ben preciso per usare web services SOAP/XML?

Te lo chiedo perché per fortuna sono passati anni dall’ultima volta che ho dovuto esporre un WS, ed ora me la cavo “veloce” con REST+JSON esposti da Spring MVC controllers (in passato usavo Spring Data REST in Spring boot, ma non ho mai provato in CUBA, e non ne ho mai avuto l’esigenza).

Se proprio devi esporre WS, allora si l’errore sembra proprio il context non configurato correttamente, quindi la DI non funziona nella tua servlet. Se non ricordo male mi era capitato registrando una servlet che non fosse una semplice HttpServlet, ma non ricordo nemmeno se avevo risolto…

Guarda questo esempio nei docs:
https://doc.cuba-platform.com/manual-7.2/servlet_registration_sample.html

Quello è il modello da seguire per servlet più complesse che devono ereditare il context di CUBA. Prova a rifattorizzare la tua servlet su quel modello, e dovrebbe andare tutto.

Ciao!

grazie Paolo per le risposte! purtroppo SOAP è mandatorio :unamused:
comunque proverò senz’altro l’esempio nei docs
grazie mille