WSDL Support in Allegro CL/SOAP (added 6/14/04)

A WSDL file defines a web service in a formal language that can be interpreted by a client computer to generate (automatically) the correct software interface to the web service. The syntax of WSDL is complex, obscure, and often non-intuitive; and a WSDL definition, to be useful, must be complete, accurate, and unambiguous. By generating the WSDL definition of a web service from the Lisp server definition, we relieve the programmer of a redundant, tedious, and error-prone task.

Using WSDL to Access a Web Service with the ACL SOAP Module

1. The Application

This application provides a Web Service that allows clients to access Lisp bignum arithmetic.

We define methods named calculate, decodeNum, and encodeNum:

Method calculate
    Input:    opname -> "+" "-" "*" "ash" "truncate" "ceiling" "factorial"
			     "rem" "gcd" "expt"
	      num1 -> a string of digits
	      num2 -> a string of digits
    Output:   calcResult -> a string of digits

Method decodeNum
    Input:     num -> a string of digits
	       base -> an integer 
    Output:    decResult -> an array of integers

    example:   num: "12345678901234567890"
	       base:    10000
	       output:  123  4567  8901  2345  6789

Method encodeNum
    Input:     bigits -> an array of integers
	       base -> an integer 
    Output:    encResult -> a string of digits

2. The Lisp Server

We define a Lisp SOAP server that implements the application. The Lisp code for the Web Service is in file bignum-server.cl.

To try the code examples, copy this file to the folder where Allegro CL will be started and compile and load the file with the :cload command. We assume this server is running in the examples below.

For simplicity, we define a very tolerant server that allows any namespace (or no namespace at all) on application tag names.

In a future Tech Corner entry we will delve into the namespace issue.

We start the server with the call (start-server). The default server advertises on host "localhost" at port 1776. Optional port and host arguments to start-server can be specified.

We can try the server in the same Lisp image with (try-server).

3. The WSDL Definition

A WSDL definition of the Web Service allows remote applications to access the service automatically. Since the server has already been started in the Lisp image, we create a WSDL definiton file with

  (encode-wsdl-file "bignum-server.wsdl" :servers *server*)

The result of the above call is a file bignum-server.wsdl. The file contains an XML description of the Web Service. We include the file sample-bignum-server.wsdl that shows the generated WSDL definition. (The file is for illustration only.)

4. A Generated Client

A Lisp application can generate a client interface to the Web Service, just like a Java or C++ application could access the Lisp Web Service with the help of the same WSDL definition. We include the file sample-bignum-client.cl that shows the generated Lisp client code.

The following commands will actually generate the client file (we use two images, which is easier; it can be done in one image):

  1. Start a second Allegro CL image
  2. Enter the following forms:
      (require :soap)
      (use-package :net.xmp.soap)
      (decode-wsdl-namespaces :file "bignum-server.wsdl")
      (setf *wsdl* (decode-wsdl-file "bignum-server.wsdl"))
      (make-client-interface *wsdl* 0 "bignum-client.cl")
    
  3. Exit Allegro CL

The result is a file bignum-client.cl that contains the Lisp code necessary to access the Web Service. A comment at the beginning of the file shows a summary description of the methods available in the service.

To access the service.

We assume the server started in the example above is still running.

  1. Start a second Allegro CL image again
  2. Enter the following forms (the '...' suspension points indicates output and prompts not reproduced):
      (require :soap)
      :ld bignum-client
      ...
      (client-1 :|opname| "ash" :|num1| "1" :|num2| "500")
      ...
      (client-5 :|num| "1234567890" :|base| 100)
      ...
      (client-3 :|bigits| '(12 34 56 78 90) :|base| 100)
      ...  
    

See soap.htm for more details on Allegro CL/SOAP and WSDL generation.


The file bignum-server.cl. To download this file, click here.

;; copyright (c) 2002-2004 Franz Inc, Oakland, CA - All rights reserved.
;;
;; The software, data and information contained herein are proprietary
;; to, and comprise valuable trade secrets of, Franz, Inc.  They are
;; given in confidence by Franz, Inc. pursuant to a written license
;; agreement, and may be stored and used only in accordance with the terms
;; of such license.
;;
;; Restricted Rights Legend
;; ------------------------
;; Use, duplication, and disclosure of the software, data and information
;; contained herein by any agency, department or entity of the U.S.
;; Government are subject to restrictions of Restricted Rights for
;; Commercial Software developed at private expense as specified in
;; DOD FAR Supplement 52.227-7013 (c) (1) (ii), as applicable.
;;

;;  The file bignum-server.cl.  An example file associated with
;;  the tech corner entry 'The WSDL generation facility in Allegro 
;;  CL/SOAP API (added 6/14/04)'.

(in-package :user)

(eval-when (compile load eval)

  ;; This form is needed to make the SOAP module available
  (require :soap)

  ;; This form is to let us use unqualified names
  (use-package :net.xmp.soap)

  )

;; This form is to allow a short package prefix for Schema symbols
(defpackage :net.xmp.schema (:use) (:nicknames :xsd))

;; A Lisp function to translate
;;   a string to a function name symbol
;;   a second string to an arbitrarily large integer
;;   a third string to a second integer
;; The result is a string of digits representing the answer.
(defun calculate (op arg1 arg2)
  (let* ((opsym (read-from-string (format nil "~A" op)))
	 (num1 (parse-integer arg1))
	 (num2 (parse-integer arg2)))
    (case opsym
      ((+ - * ash truncate ceiling expt factorial gcd rem)
       (format nil "~A" (funcall opsym num1 num2)))
      (otherwise (error "Unknown operation"))
      )))

;; A Lisp function to translate a string of digits into a list
;;   of digits in some arbitrary base.
;; This function allows a foreign language caller to view the
;;   large integer as a vector of integers within the integer
;;   range of the foreign language.
(defun decode-num (string base)
  (let ((num (parse-integer string)) rem res)
    (loop
     (multiple-value-setq (num rem) (truncate num base))
     (push rem res)
     (when (zerop num) (return)))
    res))
	
;; A Lisp function to translate a sequence of digits into
;;   a string representing the actual number.
(defun encode-num (seq base &aux (res 0))
  (dotimes (i (length seq) (format nil "~A" res))
    (setf res (+ (* res base) (elt seq i)))))

(defun factorial (n dummy &aux (r 1))
  (declare (ignore dummy))
  (loop
   (when (< n 2) (return r))
   (setf r (* r n))
   (decf n)))


(define-soap-element nil "calculate"
  '(:complex
    (:seq 
     (:element "opname" xsd:|string|)
     (:element "num1"   xsd:|string|)
     (:element "num2"   xsd:|string|))
    :action "calculate"
    ))

(define-soap-element nil "calculateResponse"
  `(:complex
    (:seq
     (:element "calcResult" xsd:|string|))))


(define-soap-element nil "decodeNum"
  '(:complex
    (:seq
     (:element "num" xsd:|string|)
     (:element "base" xsd:|int|))
    :action "decodeNum"
    ))

(define-soap-type nil :|arrayOfBigits|
  '(:array xsd:|int|
	   :array-item (:element "item" :send-type t)
	   ))

(define-soap-element nil "decodeNumResponse"
  '(:complex
    (:seq
     (:element "decResult" :|arrayOfBigits|))))


(define-soap-element nil "encodeNum"
  `(:complex
    (:seq
     (:element "bigits" :|arrayOfBigits|)
     (:element "base"  xsd:|int|))
    :action "encodeNum"
    ))

(define-soap-element nil "encodeNumResponse"
  `(:complex
    (:seq
     (:element "encResult" xsd:|string|))))



(defvar *server* nil)
(defun start-server (&optional (port 1776) (host "localhost"))
  (let ((server (soap-message-server :start (list :port port)
				     :lisp-package :keyword
				     :message-dns *wsdl-1.1-namespaces*
				     :url (format nil "http://~A:~A/SOAP" host port)
				     :service-name "BigNumService"
				     )))
    (soap-export-method
     server "calculate" '("opname" "num1" "num2")
     :lisp-name 'calculate-method
     :return "calculateResponse"
     :action "calculate"
     )
    (soap-export-method
     server "decodeNum" '("num" "base")
     :lisp-name 'decode-num-method
     :return "decodeNumResponse"
     :action "decodeNum"
     )
    (soap-export-method
     server "encodeNum" '("bigits" "base")
     :lisp-name 'encode-num-method
     :return "encodeNumResponse"
     :action "encodeNum"
     )
    (setf *server* server)))

(defun calculate-method (&key |opname| |num1| |num2|)  
  (list "calcResult" (calculate |opname| |num1| |num2|)))

(defun decode-num-method (&key |num| |base|)
  (list "decResult" (decode-num |num| |base|)))

(defun encode-num-method (&key |bigits| |base|)
  (list "encResult" (encode-num |bigits| |base|)))





(defun try-server (&optional (port 1776) (host "localhost"))
  (let ((client (soap-message-client 
		 :url (format nil "http://~A:~A/SOAP" host port)
		 )))
    (values
     (call-soap-method client "calculate" "opname" "factorial" "num1" "17" "num2" "1")
     client)))

The file sample-bignum-client.cl. A file similar to this one is generated in the above example.

(in-package #:common-lisp-user)

#|
Defined functions:

     client-5  Send client message decodeNum 
      Message "decodeNum" takes 2 arguments:
        The element "num" of type net.xmp.schema:string
        The element "base" of type net.xmp.schema:int
      and returns a result decodeNumResponse containing the element(s):
        The element "decResult" of type net.xmp.wsdl::arrayOfBigits
          The type net.xmp.wsdl::arrayOfBigits is array of net.xmp.schema:int

     client-3  Send client message encodeNum 
      Message "encodeNum" takes 2 arguments:
        The element "bigits" of type net.xmp.wsdl::arrayOfBigits
          The type net.xmp.wsdl::arrayOfBigits is array of net.xmp.schema:int
        The element "base" of type net.xmp.schema:int
      and returns a result encodeNumResponse containing the element(s):
        The element "encResult" of type net.xmp.schema:string

     client-1  Send client message calculate 
      Message "calculate" takes 3 arguments:
        The element "opname" of type net.xmp.schema:string
        The element "num1" of type net.xmp.schema:string
        The element "num2" of type net.xmp.schema:string
      and returns a result calculateResponse containing the element(s):
        The element "calcResult" of type net.xmp.schema:string

Defined SOAP elements:
     "decodeNumResponse"
     "decodeNum"
     "encodeNumResponse"
     "encodeNum"
     "calculateResponse"
     "calculate"

Defined SOAP types: net.xmp.wsdl::arrayOfBigits

Defined parameters: *application-namespaces*

Defined packages:
     :net.xmp.schema
     :net.xmp.schema-instance
     :net.xmp.soap.envelope
     :net.xmp.soap.encoding
     :net.xmp.wsdl.soap
     :net.xmp.wsdl
     #:common-lisp-user

Lisp package of generated file: #:common-lisp-user

|#


(defpackage #:common-lisp-user
            (:use #:net.xmp.soap #:common-lisp #:excl))

(defpackage :net.xmp.wsdl (:use))

(defpackage :net.xmp.wsdl.soap (:use))

(defpackage :net.xmp.soap.encoding (:use))

(defpackage :net.xmp.soap.envelope (:use))

(defpackage :net.xmp.schema-instance (:use))

(defpackage :net.xmp.schema (:use))

(defparameter *application-namespaces*
    (quote
     (nil (:net.xmp.wsdl "wsdl" "http://schemas.xmlsoap.org/wsdl/")
      (:net.xmp.wsdl.soap "soap"
       "http://schemas.xmlsoap.org/wsdl/soap/")
      (:net.xmp.soap.encoding "soapenc"
       "http://schemas.xmlsoap.org/soap/encoding/")
      (:net.xmp.soap.envelope "soapenv"
       "http://schemas.xmlsoap.org/soap/envelope/")
      (:net.xmp.schema-instance "xsi"
       "http://www.w3.org/2000/10/XMLSchema-instance")
      (:net.xmp.schema "xsd" "http://www.w3.org/2000/10/XMLSchema"))))

(define-soap-type nil 'net.xmp.wsdl::arrayOfBigits
                  '(:array net.xmp.schema:int))

(define-soap-element nil '"calculate"
                     '(:complex
                       (:seq1 (:element "opname" net.xmp.schema:string)
                        (:element "num1" net.xmp.schema:string)
                        (:element "num2" net.xmp.schema:string))
                       :namespaces
                       (nil (nil "tns" "ThisWebServiceNamespace"))
                       :encoding
                       "http://schemas.xmlsoap.org/soap/encoding/"
                       :action "calculate"))

(define-soap-element nil '"calculateResponse"
                     '(:complex
                       (:seq1
                        (:element "calcResult" net.xmp.schema:string))
                       :namespaces
                       (nil (nil "tns" "ThisWebServiceNamespace"))
                       :encoding
                       "http://schemas.xmlsoap.org/soap/encoding/"))


;; Send client message calculate 
;; calculate

(defun client-1 (&rest args &key opname num1 num2)
  (declare (ignore opname num1 num2))
  (let ((conn
         (soap-message-client :url "http://localhost:1776/SOAP"
                              :message-dns *application-namespaces*
                              :lisp-package :keyword)))
    (values (apply 'call-soap-method conn '"calculate" args) conn)))

(define-soap-element nil '"encodeNum"
                     '(:complex
                       (:seq1
                        (:element "bigits" net.xmp.wsdl::arrayOfBigits)
                        (:element "base" net.xmp.schema:int))
                       :namespaces
                       (nil (nil "tns" "ThisWebServiceNamespace"))
                       :encoding
                       "http://schemas.xmlsoap.org/soap/encoding/"
                       :action "encodeNum"))

(define-soap-element nil '"encodeNumResponse"
                     '(:complex
                       (:seq1
                        (:element "encResult" net.xmp.schema:string))
                       :namespaces
                       (nil (nil "tns" "ThisWebServiceNamespace"))
                       :encoding
                       "http://schemas.xmlsoap.org/soap/encoding/"))


;; Send client message encodeNum 
;; encodeNum

(defun client-3 (&rest args &key bigits base)
  (declare (ignore bigits base))
  (let ((conn
         (soap-message-client :url "http://localhost:1776/SOAP"
                              :message-dns *application-namespaces*
                              :lisp-package :keyword)))
    (values (apply 'call-soap-method conn '"encodeNum" args) conn)))

(define-soap-element nil '"decodeNum"
                     '(:complex
                       (:seq1 (:element "num" net.xmp.schema:string)
                        (:element "base" net.xmp.schema:int))
                       :namespaces
                       (nil (nil "tns" "ThisWebServiceNamespace"))
                       :encoding
                       "http://schemas.xmlsoap.org/soap/encoding/"
                       :action "decodeNum"))

(define-soap-element nil '"decodeNumResponse"
                     '(:complex
                       (:seq1
                        (:element "decResult"
                         net.xmp.wsdl::arrayOfBigits))
                       :namespaces
                       (nil (nil "tns" "ThisWebServiceNamespace"))
                       :encoding
                       "http://schemas.xmlsoap.org/soap/encoding/"))


;; Send client message decodeNum 
;; decodeNum

(defun client-5 (&rest args &key num base)
  (declare (ignore num base))
  (let ((conn
         (soap-message-client :url "http://localhost:1776/SOAP"
                              :message-dns *application-namespaces*
                              :lisp-package :keyword)))
    (values (apply 'call-soap-method conn '"decodeNum" args) conn)))

Copyright © 2023 Franz Inc., All Rights Reserved | Privacy Statement Twitter