Сервер на основе WSDL Generi c CXF, использующий привязку данных JAXB - PullRequest
0 голосов
/ 11 апреля 2020

Я ищу решение для интеграции CXF в приложение в качестве поставщика для реализации Web-сервисов. Приложение должно иметь возможность реализовывать веб-сервис динамически c (это означает, что классы SEI недоступны) на основе предоставленного файла WSDL. Поскольку приложение управляет запросами http и отображениями URL-адресов через собственный сервлет, использование стандартного сервлета CXF для публикации конечных точек sh не представляется возможным. Также я хочу использовать привязку данных JAXB. В идеале CXF должен позвонить моему Object invoke(String oper, Object... args) для реальной обработки веб-сервиса. В целом, он должен выглядеть как динамический c клиент, но реализованный для серверной части.

Мне удалось заставить код почти работать, но столкнулся с парой вещей, которые я не понимаю - об этом позже.

Сначала я прочитал WSDL в строку и создал ее определение. Определение передается в wsdlManager для доступа через уникальную ссылку. Затем я создаю клиент JaxWS Dynami c и получаю привязку данных JAXB, сгенерированную для него CXF.

        WSDLManager wsdlManager = serviceBus.getExtension(WSDLManager.class);
        WSDLFactory wsdlFactory = wsdlManager.getWSDLFactory();

        // Reader
        WSDLReader reader = wsdlFactory.newWSDLReader();
        reader.setFeature("javax.wsdl.verbose", true);
        reader.setFeature("javax.wsdl.importDocuments", true);

        Definition def = reader.readWSDL(null, TypeCast.getInputSource(wsdl)); // wsdl is a String containing wsdl definition

        // Precache definition using listenerRef as unique identifier
        wsdlManager.addDefinition(listenerRef, def);

        String[] compileOptions = null;

        // Create JAXWS dynamic client using precached address
        Client client = createClient(listenerRef, simpleDataBinding, allowElementReferences, compileOptions);

        if (Logger.isDebugEnabled()) 
           Logger.debug("WebServiceProcessor.initServiceListener: service client is created succefully: " + listenerName);

        EndpointInfo cei = client.getEndpoint().getEndpointInfo();

        // Use JAXB generated databinding
        DataBinding db = client.getEndpoint().getService().getDataBinding();

        if (Logger.isDebugEnabled()) 
           Logger.debug("WebServiceProcessor.initServiceListener: databinding: " + db);

Создание клиента Dynami c просто

   public Client createClient(String serviceDescription, boolean simpleDataBinding, boolean allowElementReferences, String[] schemaOptions) {
       ClassLoader old = Thread.currentThread().getContextClassLoader();

       try {
          ParentClassLoader dynaLoader = new ParentClassLoader();

          JaxWsDynamicClientFactory dynamicClientFactory = JaxWsDynamicClientFactory.newInstance(serviceBus);
          dynamicClientFactory.setSimpleBindingEnabled(simpleDataBinding);
          dynamicClientFactory.setAllowElementReferences(allowElementReferences);

          if (schemaOptions != null) {
             dynamicClientFactory.setSchemaCompilerOptions(schemaOptions);
          }

          return dynamicClientFactory.createClient(serviceDescription, dynaLoader);

       } catch (Throwable ex) {
          Logger.error("WebServiceProcessor.createClient: exception is caught: " + ex, ex);
          return null;

       } finally {
          Thread.currentThread().setContextClassLoader(old);
       }
   }

Далее Для создания серверных вещей объявлены некоторые вспомогательные классы

   protected class MyWSDLServiceFactory extends WSDLServiceFactory {

       public MyWSDLServiceFactory(Bus b, Definition d) {
          super(b, d);
       }

       @Override
       public Service create() {
          Service svc = super.create();

          // Post init
          initializeDefaultInterceptors();
          initializeDataBindings(); 

          return svc;
       }
   }

   public class MyInvoker extends AbstractInvoker {

     protected final Object implementor = new Object();

     public MyInvoker() {
     }

     @Override
     public Object getServiceObject(Exchange context) {
        return implementor;
     }

     protected void throwable() throws Exception {

     }

     @Override
     public Object invoke(Exchange exchange, Object o) {

        List<Object> params = null;
        if (o instanceof List) {
            params = CastUtils.cast((List<?>)o);
        } else if (o != null) {
            params = new MessageContentsList(o);
        }

        if (Logger.isTraceEnabled()) {
           for (Object arg : params)
              Logger.trace("MyInvoker.invoke: arg: " + arg);
        }

        // Method holding declararions of throwable exceptions 
        Method m = null;
        try {
           m = MsyInvoker.class.getMethod("throwable");
        } catch (NoSuchMethodException ex) {
           // Strange
        }

        return invoke(exchange, null, m, params);
     }

     @Override
     protected Object performInvocation(Exchange exchange, Object serviceObject, Method m, Object[] paramArray) throws Exception {
        Message inMessage = exchange.getInMessage();
        BindingOperationInfo bop = exchange.getBindingOperationInfo();

        String oper = bop.getName().getLocalPart();

        // Process request
        return processWebListenerRequest(oper, paramArray);
     }
   }

   protected class MyDestinationFactory implements DestinationFactory {

     protected final Set<String> prefixes = Collections.unmodifiableSet(new HashSet<String> (Arrays.asList("http://", "https://")));

     @Override
     public Destination getDestination(EndpointInfo ei, Bus bus) throws IOException {
         return new MyDestination(ei, ei.getAddress());
     }

     @Override
     public Set<String> getUriPrefixes() {
         return prefixes;
     }

     @Override
     public List<String> getTransportIds() {
         return null;
     }
   }

   protected class MyDestination extends ServletDestination {

     public MyDestination(EndpointInfo ei, String path) throws IOException {
        super(serviceBus, null, ei, path, false);

        // Disable async support
        isServlet3 = false;
     }

     @Override
     protected void setupMessage(final Message inMessage, final ServletConfig config, final ServletContext context, final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
        super.setupMessage(inMessage, config, context, req, resp);
     }

     @Override
     protected String getBasePath(String contextPath) throws IOException {
         if (endpointInfo.getAddress() == null) {
             return "";
         }
         return endpointInfo.getAddress();
     }
   }

Затем мы готовы создать сервер:

        MyWSDLServiceFactory sf = new MyWSDLServiceFactory(serviceBus, def);
        sf.setAllowElementRefs(allowElementReferences);
        sf.setDataBinding(db);

        Service svc = sf.create();

        // Clear cached definition
        wsdlManager.removeDefinition(def);

        svc.setInvoker(new MyInvoker());

        // Create endpoints

        for (ServiceInfo inf : svc.getServiceInfos()) {
           for (EndpointInfo ei : inf.getEndpoints()) {
              if (ei.getName().equals(cei.getName())) {

                 if (Logger.isDebugEnabled()) 
                    Logger.debug("WebServiceProcessor.initServiceListener: endpoint: " + ei.getName());

                 String addr = "/" + listenerRef;

                 try {
                     ei.setAddress(addr);
                     JaxWsEndpointImpl ep = new JaxWsEndpointImpl(serviceBus, svc, ei);

                     svc.getEndpoints().put(ei.getName(), ep);

                     ep.addHandlerInterceptors();
                     ep.getInInterceptors().add(new SoapUtil.SoapInLogger());

                     BindingFactoryManager bfm = serviceBus.getExtension(BindingFactoryManager.class);

                     // tried this but no effect
                     // ei.getBinding().setProperty("soap.force.doclit.bare", Boolean.TRUE);

                     String bindingId = ei.getBinding().getBindingId();

                     if (Logger.isDebugEnabled()) 
                        Logger.debug("WebServiceProcessor.initServiceListener: binding id: " + bindingId);

                     BindingFactory bindingFactory = bfm.getBindingFactory(bindingId);

                     Server server = new ServerImpl(serviceBus, ep, new MyDestinationFactory(), bindingFactory);

                     if (Logger.isDebugEnabled()) 
                        Logger.debug("WebServiceProcessor.initServiceListener: starting server: " + ei.getName());

                     server.start();

                     if (Logger.isDebugEnabled()) 
                        Logger.debug("WebServiceProcessor.initServiceListener: server is started: " + server.isStarted());

                     // Set reference
                     listeners.put(listenerRef, server); // Our map to keep web server listeners
                 } catch (EndpointException e) {
                     throw new ServiceConstructionException(e);
                 }
              }
           }
        }

Вызов сервера выглядит как

     String address = "/" + listenerRef;
     Server server = listeners.get(listenerRef); // Find our server listener in a map

     if (server != null) {
        Endpoint ep = server.getEndpoint();
        EndpointInfo ei = ep.getEndpointInfo();

        if (Logger.isDebugEnabled())
          Logger.debug("WebServiceProcessor.invoke: endpoint: " + listenerName);

        try {
           AbstractHTTPDestination dest = (AbstractHTTPDestination) server.getDestination();
           AsyncContext asyncCtx = requestContext.getAsyncContext();

           HttpServletRequest req = (HttpServletRequest) asyncCtx.getRequest();
           HttpServletResponse resp = (HttpServletResponse) asyncCtx.getResponse();
           ServletContext sctx = req.getServletContext();
           ServletConfig scfg = null; 

           if (Logger.isDebugEnabled())
             Logger.debug("WebServiceProcessor.invoke: destination resolved successfully: " + listenerName);

           // Trigger CXF processing
           dest.invoke(scfg, sctx, req, resp);

           if (Logger.isDebugEnabled())
             Logger.debug("WebServiceProcessor.invoke: endpoint processed successfully: " + listenerName);

        } catch (Exception ex) {
           Logger.error("WebServiceProcessor.invoke: exception is caught: " + ex, ex);
        }
     }

Как я уже упоминал, решение почти работает, я попытался протестировать его с CXF 3.3 и одним WSDL, который я взял в качестве примера http://www.dneonline.com/calculator.asmx?WSDL. Мне удалось позвонить в сервис с SoapUI и получить ответ. Но теперь странная часть. Когда я вызываю веб-сервис со стандартным запросом

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
   <soap:Header/>
   <soap:Body>
   <Add xmlns="http://tempuri.org/">
       <intA>1</intA><intB>2</intB> 
   </Add>
   </soap:Body>
</soap:Envelope>

, он завершается с Unmarshalling Error

org.apache.cxf.interceptor.Fault: Unmarshalling Error: unexpected element (uri:"http://tempuri.org/", local:"intA"). Expected elements are <{http://tempuri.org/}Add>,<{http://tempuri.org/}AddResponse>,<{http://tempuri.org/}Divide>,<{http://tempuri.org/}DivideResponse>,<{http://tempuri.org/}Multiply>,<{http://tempuri.org/}MultiplyResponse>,<{http://tempuri.org/}Subtract>,<{http://tempuri.org/}SubtractResponse> 
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:932) ~[cxf-rt-databinding-jaxb-3.3.6.jar:3.3.6]
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:738) ~[cxf-rt-databinding-jaxb-3.3.6.jar:3.3.6]
    at org.apache.cxf.jaxb.io.DataReaderImpl.read(DataReaderImpl.java:170) ~[cxf-rt-databinding-jaxb-3.3.6.jar:3.3.6]
    at org.apache.cxf.wsdl.interceptors.DocLiteralInInterceptor.getPara(DocLiteralInInterceptor.java:325) ~[cxf-rt-wsdl-3.3.6.jar:3.3.6]
    at org.apache.cxf.wsdl.interceptors.DocLiteralInInterceptor.handleMessage(DocLiteralInInterceptor.java:127) ~[cxf-rt-wsdl-3.3.6.jar:3.3.6]

Но он успешно проходит проверку с

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
   <soap:Header/>
   <soap:Body>
   <Add xmlns="http://tempuri.org/">
       <Add>
       <intA>1</intA><intB>2</intB> 
       </Add>
   </Add>
   </soap:Body>
</soap:Envelope>

Но в этом случае параметры, которые передаются MyInvoker, представляют собой массив из двух нулевых элементов. Тем не менее он генерирует правильно отформатированный (за исключением того, что вычисленное значение является неправильным, поскольку входные параметры равны нулю). 1036 *

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
   <soap:Header/>
   <soap:Body>
   <Add xmlns="http://tempuri.org/">
       <intA>1</intA><intB>2</intB> 
   </Add>
   </soap:Body>
</soap:Envelope>

Я проверил вызов клиента CXF dynamici c с проверенным WSDL, из которого я заимствовал привязку данных JAXB, и он генерирует точно такой же запрос при вызове этой службы, но, похоже, не может разобрать это по какой-то причине.

Другой вопрос, который, я думаю, относится к первому, заключается в том, почему неупорядоченные параметры являются нулевыми в случае второго запроса? Любое предложение, где искать дальше?

Заранее спасибо

1 Ответ

0 голосов
/ 16 апреля 2020

Вот ответ на мой вопрос. Все становится правильно, если повторно использовать экземпляр службы CXF из динамического клиента c с добавлением некоторых перехватчиков серверной части:

        Service svc = client.getEndpoint().getService();

        // Server part interceptors
        svc.getInInterceptors().add(new ServiceInvokerInterceptor());
        svc.getInInterceptors().add(new OutgoingChainInterceptor());
        svc.getInInterceptors().add(new OneWayProcessorInterceptor());

...