franz inc logo  
  download techcorner franz inc franz inc store search franz inc          

products
services
support
  Latest Info
  Tech Corner
  Patches
     Info
  Documentation
  FAQs
  White Papers
  Tutorials
  Examples
  Archives
about
success
resources

RSS Feeds

AllegroServe at opensource.franz.com

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.

Return to Tech Corner page.


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)))

 

© 2008 Franz Inc - Privacy Statement
[ Consulting Services | Packages/Pricing | Allegro NFS | Certification Program ]