2015-12-30 35 views
5

Devo preparare un servizio web per accettare una struttura wsdl già definita. Ho seguito il tutorial found here, con il codice sorgente per il progetto di test downloadable here.wsdl non valido generato da spring-ws quando l'elemento richiesta non termina con 'Richiesta'

Per xsd come questo: operazione

<xs:element name="getCountryRequest"> 
    <xs:complexType> 
     <xs:sequence> 
      <xs:element name="name" type="xs:string"/> 
     </xs:sequence> 
    </xs:complexType> 
</xs:element> 

WSDL per richiesta restituiti dalla applicazione è OK, appare così:

<wsdl:binding name="CountriesPortSoap11" type="tns:CountriesPort"> 
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> 
    <wsdl:operation name="getCountry"> 
     <soap:operation soapAction=""/> 
     <wsdl:input name="getCountryRequest"> 
      <soap:body use="literal"/> 
     </wsdl:input> 
     <wsdl:output name="getCountryResponse"> 
      <soap:body use="literal"/> 
     </wsdl:output> 
    </wsdl:operation> 
</wsdl:binding> 

Ma quando cambio la XSD (no 'Request' in nome di elemento):

<xs:element name="getCountry"> 
    <xs:complexType> 
     <xs:sequence> 
      <xs:element name="name" type="xs:string"/> 
     </xs:sequence> 
    </xs:complexType> 
</xs:element> 

il WSDL non è valido, e non ha specificato <input>:

<wsdl:binding name="CountriesPortSoap11" type="tns:CountriesPort"> 
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> 
    <wsdl:operation name="getCountry"> 
     <soap:operation soapAction=""/> 
     <wsdl:output name="getCountryResponse"> 
      <soap:body use="literal"/> 
     </wsdl:output> 
    </wsdl:operation> 
</wsdl:binding> 

Come posso risolvere il problema? Come posso fare in modo che un elemento Request -less venga visualizzato correttamente come input di soap in wsdl?

+0

Per tutti voi che stanno avendo problemi simili: utilizzare org.apache.cxf e generare l'endpoint da WSDL con wsdl2java. È più semplice e la lib in realtà consente sia soap 1.1 che 1.2 sullo stesso endpoint. – Dariusz

risposta

11

In base a official Spring WS documentation, il suffisso Richiesta/Risposta è quello predefinito utilizzato per determinare automaticamente la richiesta/risposta e come tale generare il WSDL previsto.

DefaultWsdl11Definition che crea un WSDL da uno schema XSD. Questa definizione esegue iterazioni su tutti gli elementi elemento presenti nello schema e crea un messaggio per tutti gli elementi. Successivamente, crea un'operazione WSDL per tutti i messaggi che terminano con la richiesta definita o il suffisso della risposta. Il suffisso di richiesta predefinito è Request; il suffisso di risposta predefinito è Response, sebbene possano essere modificati impostando rispettivamente le proprietà requestSuffix e responseSuffix.

È possibile quindi, nel codice di esempio citato, cambiare il suffisso nella classe di configurazione WebServiceConfig, defaultWsdl11Definition metodo, aggiungendo il metodo seguente invocazione:

wsdl11Definition.setRequestSuffix("your-new-prefix-here"); 

È possibile, ad esempio, impostarlo Req invece di Request, la generazione genererà automaticamente una nuova classe GetCountryReq, il codice di ApplicationTests e CountryEndpoint dovrà quindi essere adattato manualmente, rimuovendo gli errori di compilazione (come farebbero ancora riferimento alloesistente in precedenzaclasse), ma assicurandosi anche di modificare l'attributo localPart = "getCountryReq" dell'annotazione @PayloadRoot nella classe CountryEndPoint.

Ho provato e build andato bene e WSDL è stato aggiornato di conseguenza.

Si tratta di modificare il suffisso predefinito con un altro suffisso. Ma che ne dici di cambiarlo in un suffisso vuoto?

wsdl11Definition.setRequestSuffix(""); 

eccezione: suffisso non deve essere vuota. La primavera non lo supporta Secondo questo thread:

In sostanza, il problema è questo:
Dobbiamo distinguere quali elementi dello schema sono i messaggi WSDL, e quali no.
Di tutti i messaggi wsdl, dobbiamo capire quali sono i messaggi di input (richiesta).
Di tutti i messaggi wsdl, dobbiamo capire quali sono i messaggi di uscita (risposta).

The DefaultWsdl11Definition lo identifica con i suffissi. O, in particolare, delega a SuffixBasedMessagesProvider e SuffixBasedPortTypesProvider di farlo.
Quindi, se si dispone di un altro modo preferibile per determinare ciò che rende un messaggio di input/output, è necessario scrivere il proprio messageprovider e/o porttypesprovider.

In poche parole: non esiste un modo generico per Spring-WS per determinare cosa costituisce una richiesta e una risposta, piuttosto che utilizzare i suffissi.

Nota: il manifesto di questo messaggio è stato Arjen Poutsma, autore della classe DefaultWsdl11Definition (secondo javadoc), il componente che gestisce la mappatura automatica in base a queste convenzioni suffisso.

Ma lui lascia una porta aperta: scrivendo il proprio SuffixBasedMessagesProvider e SuffixBasedPortTypesProvider. Tuttavia, ha anche lasciato tutto come privato nel DefaultWsdl11Definition (dove questi provider sono istanziati), quindi è anche necessario scrivere (copiare) il proprio mappatore di definizione WSDL11.

Qui è il processo che ho seguito poi:

  • Crea il tuo CustomSuffixBasedMessagesProvider, l'override del metodo setRequestSuffix e rimuovendo il controllo sul suffisso vuoto, nel metodo isMessageElement si avrebbe bisogno di gestire la nuova mappatura
  • Crea il tuo CustomSuffixBasedPortTypesProvider, sovrascrivendo il metodo setRequestSuffix e rimuovendo il controllo sul suffisso vuoto, nei metodi getOperationName e isInputMessage che ti serviranno per gestire la nuova mappatura
  • Creare la propria CustomWsdl11Definition come copia della DefaultWsdl11Definition esistente e creare un'istanza dei propri provider creati sopra
  • Modificare la classe WebServiceConfig, defaultWsdl11Definition, per utilizzare la propria CustomWsdl11Definition per applicare l'intera personalizzazione.

Tuttavia, il suffisso vuoto viene fornito con un po 'di difficoltà, dal momento che andrebbe bene per qualsiasi elemento (ovvero, ogni elemento ha un suffisso vuoto). Questo è il motivo per cui ho menzionato la gestione di isMessageElement, isInputMessage e getOperationName: su WSDL in crescita, si può facilmente finire con l'harcoding del mapping (per la richiesta GetCountry, GetCountryResponse è la risposta) o il passaggio di una proprietà/mappa (come suggerito nello thread di cui sopra) , ma ripeti di nuovo la maggior parte del tuo XSD nella tua classe di configurazione WebServiceConfig, rendendo difficile la manutenzione, la risoluzione dei problemi e la condivisione.

Quindi, vorrei davvero suggerire di non intraprendere questo viaggio e attenersi al suffisso predefinito (se possibile) o crearne uno nuovo, ma evitare il suffisso vuoto (non sono ammessi dalla libreria dopo tutto).

Ma da quando ho preso il viaggio, ecco la soluzione di lavoro:

Il MySuffixBasedMessagesProvider classe personalizzata (importazioni rimossi per brevità):

package hello; 

public class MySuffixBasedMessagesProvider extends SuffixBasedMessagesProvider { 

    protected String requestSuffix = DEFAULT_REQUEST_SUFFIX; 

    public String getRequestSuffix() { 
     return this.requestSuffix; 
    } 

    public void setRequestSuffix(String requestSuffix) { 
     this.requestSuffix = requestSuffix; 
    } 

    @Override 
    protected boolean isMessageElement(Element element) { 
     if (isMessageElement0(element)) { 
      String elementName = getElementName(element); 
      Assert.hasText(elementName, "Element has no name"); 
      return elementName.endsWith(getResponseSuffix()) 
        || (getRequestSuffix().isEmpty() ? true : elementName.endsWith(getRequestSuffix())) 
        || elementName.endsWith(getFaultSuffix()); 
     } 
     return false; 
    } 

    protected boolean isMessageElement0(Element element) { 
     return "element".equals(element.getLocalName()) 
       && "http://www.w3.org/2001/XMLSchema".equals(element.getNamespaceURI()); 
    } 
} 

Il MySuffixBasedPortTypesProvider classe personalizzata (importazioni rimosso per brevità):

package hello; 

public class MySuffixBasedPortTypesProvider extends SuffixBasedPortTypesProvider { 

    private String requestSuffix = DEFAULT_REQUEST_SUFFIX; 

    public String getRequestSuffix() { 
     return requestSuffix; 
    } 

    public void setRequestSuffix(String requestSuffix) { 
     this.requestSuffix = requestSuffix; 
    } 

    @Override 
    protected String getOperationName(Message message) { 
     String messageName = getMessageName(message); 
     String result = null; 
     if (messageName != null) { 
      if (messageName.endsWith(getResponseSuffix())) { 
       result = messageName.substring(0, messageName.length() - getResponseSuffix().length()); 
      } else if (messageName.endsWith(getFaultSuffix())) { 
       result = messageName.substring(0, messageName.length() - getFaultSuffix().length()); 
      } else if (messageName.endsWith(getRequestSuffix())) { 
       result = messageName.substring(0, messageName.length() - getRequestSuffix().length()); 
      } 
     } 
     return result; 
    } 

    @Override 
    protected boolean isInputMessage(Message message) { 
     String messageName = getMessageName(message); 

     return messageName != null && !messageName.endsWith(getResponseSuffix()); 
    } 

    private String getMessageName(Message message) { 
     return message.getQName().getLocalPart(); 
    } 

} 

Il MyWsdl11Definition classe personalizzata (essenzialmente una copia di default, basta istanziare classi sopra, importazioni rimossa per brevità):

package hello; 

public class MyWsdl11Definition implements Wsdl11Definition, InitializingBean { 

    private final InliningXsdSchemaTypesProvider typesProvider = new InliningXsdSchemaTypesProvider(); 

    private final SuffixBasedMessagesProvider messagesProvider = new MySuffixBasedMessagesProvider(); 

    private final SuffixBasedPortTypesProvider portTypesProvider = new MySuffixBasedPortTypesProvider(); 

    private final SoapProvider soapProvider = new SoapProvider(); 

    private final ProviderBasedWsdl4jDefinition delegate = new ProviderBasedWsdl4jDefinition(); 

    private String serviceName; 

    public MyWsdl11Definition() { 
     delegate.setTypesProvider(typesProvider); 
     delegate.setMessagesProvider(messagesProvider); 
     delegate.setPortTypesProvider(portTypesProvider); 
     delegate.setBindingsProvider(soapProvider); 
     delegate.setServicesProvider(soapProvider); 
    } 

    public void setTargetNamespace(String targetNamespace) { 
     delegate.setTargetNamespace(targetNamespace); 
    } 

    public void setSchema(XsdSchema schema) { 
     typesProvider.setSchema(schema); 
    } 

    public void setSchemaCollection(XsdSchemaCollection schemaCollection) { 
     typesProvider.setSchemaCollection(schemaCollection); 
    } 

    public void setPortTypeName(String portTypeName) { 
     portTypesProvider.setPortTypeName(portTypeName); 
    } 

    public void setRequestSuffix(String requestSuffix) { 
     portTypesProvider.setRequestSuffix(requestSuffix); 
     messagesProvider.setRequestSuffix(requestSuffix); 
    } 

    public void setResponseSuffix(String responseSuffix) { 
     portTypesProvider.setResponseSuffix(responseSuffix); 
     messagesProvider.setResponseSuffix(responseSuffix); 
    } 

    public void setFaultSuffix(String faultSuffix) { 
     portTypesProvider.setFaultSuffix(faultSuffix); 
     messagesProvider.setFaultSuffix(faultSuffix); 
    } 

    public void setCreateSoap11Binding(boolean createSoap11Binding) { 
     soapProvider.setCreateSoap11Binding(createSoap11Binding); 
    } 

    public void setCreateSoap12Binding(boolean createSoap12Binding) { 
     soapProvider.setCreateSoap12Binding(createSoap12Binding); 
    } 

    public void setSoapActions(Properties soapActions) { 
     soapProvider.setSoapActions(soapActions); 
    } 

    public void setTransportUri(String transportUri) { 
     soapProvider.setTransportUri(transportUri); 
    } 

    public void setLocationUri(String locationUri) { 
     soapProvider.setLocationUri(locationUri); 
    } 

    public void setServiceName(String serviceName) { 
     soapProvider.setServiceName(serviceName); 
     this.serviceName = serviceName; 
    } 

    @Override 
    public void afterPropertiesSet() throws Exception { 
     if (!StringUtils.hasText(delegate.getTargetNamespace()) && typesProvider.getSchemaCollection() != null && 
       typesProvider.getSchemaCollection().getXsdSchemas().length > 0) { 
      XsdSchema schema = typesProvider.getSchemaCollection().getXsdSchemas()[0]; 
      setTargetNamespace(schema.getTargetNamespace()); 
     } 
     if (!StringUtils.hasText(serviceName) && StringUtils.hasText(portTypesProvider.getPortTypeName())) { 
      soapProvider.setServiceName(portTypesProvider.getPortTypeName() + "Service"); 
     } 
     delegate.afterPropertiesSet(); 
    } 

    @Override 
    public Source getSource() { 
     return delegate.getSource(); 
    } 

} 

Inoltre, il metodo della classe WebServiceConfigdefaultWsdl11Definition cambierebbero come segue (a utilizzare la personalizzazione sopra):

@Bean(name = "countries") 
public Wsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) { 
    MyWsdl11Definition wsdl11Definition = new MyWsdl11Definition(); 
    wsdl11Definition.setPortTypeName("CountriesPort"); 
    wsdl11Definition.setLocationUri("/ws"); 
    wsdl11Definition.setRequestSuffix(""); 
    wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service"); 
    wsdl11Definition.setSchema(countriesSchema); 
    return wsdl11Definition; 
} 

Nota la wsdl11Definition.setRequestSuffix(""); che definisce efficacemente il suffisso a vuoto.

Dopo questa personalizzazione, è possibile modificare XSD rimuovendo il suffisso Request, verrà generata la nuova classe GetCountry, sarà necessario rimuovere manualmente il GetCountryRequest esistente e correggere gli errori di compilazione come menzionato sopra (test ed endpoint class , non dimenticare di aggiornare anche l'annotazione @PayloadRoot).

Build funzionerebbe correttamente. L'esecuzione del Application principale, il WSDL generato sarebbe quindi contenere come richiesto:

<wsdl:binding name="CountriesPortSoap11" type="tns:CountriesPort"> 
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> 
<wsdl:operation name="getCountry"> 
    <soap:operation soapAction=""/> 
    <wsdl:input name="getCountry"> 
    <soap:body use="literal"/> 
    </wsdl:input> 
    <wsdl:output name="getCountryResponse"> 
    <soap:body use="literal"/> 
    </wsdl:output> 
</wsdl:operation> 
</wsdl:binding> 

Speranza che aiuta. Questo è un esempio reale di ciò che offre la convenzione sulla configurazione e cosa invece un piccolo cambiamento imprevisto in un framework significherebbe in termini di scrittura del codice e aggiunta di personalizzazione.

2

funzione di esposizione WSDL automatica Primavera-WS si basa su convenzioni descritte al http://docs.spring.io/spring-ws/site/reference/html/server.html#server-automatic-wsdl-exposure

Il tutorial si utilizza come punto di partenza sta usando le annotazioni, invece di spazio dei nomi, ma ci dovrebbe essere un modo per specificare requestSuffix e responseSuffix proprietà menzionato nella documentazione. Tuttavia temo che non puoi tenerli in bianco.

In alternativa è possibile esporre WSDL scritto manualmente. Suggerisco di farlo visto che hai dato wsdl dall'inizio.

0

C'è un modo più semplice. Al posto di utilizzare DefaultWsdl11Definition ProviderBasedWsdl4jDefinition e impostare tutti i provider di default, ma uno - al posto di SuffixBasedPortTypesProvider usare qualcosa di simile:

public class DelphiStylePortTypesProvider extends AbstractPortTypesProvider { 

    @Override 
    protected String getOperationName(Message msg) { 
     if (isInputMessage(msg)) { 
      return msg.getQName().getLocalPart(); 
     } 
     if (isOutputMessage(msg)) { 
      return msg.getQName().getLocalPart().replace("Response", ""); 
     } 
     return ""; 
    } 

    @Override 
    protected boolean isInputMessage(Message msg) { 
     return !msg.getQName().getLocalPart().endsWith("Response"); 
    } 

    @Override 
    protected boolean isOutputMessage(Message msg) { 
     return msg.getQName().getLocalPart().endsWith("Response"); 
    } 

    @Override 
    protected boolean isFaultMessage(Message msg) { 
     return false; 
    } 

}