Axis2/C Developer Guide

Please send your feedback to developer mailing list: axis-c-dev@ws.apache.org (Please remember to prefix the subject with [Axis2]). To subscribe to developer mailing lists see here.

Content

This guide walks you through the following topics with the aim of helping you get familiar with the Axis2/C project and its development norms quickly.

Programming Model

Axis2/C programming model uses a pseudo object oriented model to achieve data hiding. The following example illustrates how this is done. Each struct corresponds to the "class" concept in object oriented programming. Hence, each struct has its own header file, that exposes the API for that struct and a source file, that contains the functional implementation.

Operations associated with a struct are bundled into an operations struct ('ops' struct), and this ops struct is exposed in the header file. The real implementation struct ('impl' struct) with the data fields is hidden from the user, and lives in the source file. To ease the user from the complex syntax required to access the functions in a given ops struct, macros are provided to access them. Since the data is hidden, it is the usual practice to provide *getter* and *setter* methods for the data fields of the struct, in addition to the other processing functionality. The actual functional implementations in the source file are mapped to the ops struct functions declared in the header file by means of function pointer assignments.

Here is a sample header file associated with the "foo" struct. (Imagine a class called "foo" in the proper object oriented programming model.)

/* axis2_ foo.h */
 
typedef struct axis2_foo_ops axis2_foo_ops_t;
typedef struct axis2_foo axis2_foo_t;

struct axis2_foo_ops
{
    void (AXIS2_CALL *bar)(axis2_foo_t *foo, const axis2_env_t *env, void *data);
    axis2_status_t (AXIS2_CALL *free)(axis2_foo_t *foo, const axis2_env_t *env);
};

struct axis2_foo
{
    axis2_foo_ops_t *ops;
}


axis2_foo_t * axis2_foo_create(const axis2_env_t *env);

/* Macros are provided to access functions defined in the ops structure */

#define AXIS2_FOO_BAR(foo, env, data)\ 
            ((foo)->ops->bar(foo, env, data))
#define AXIS2_FOO_FREE(foo, env)\
            ((foo)->ops->free(foo, env))

The implementation file for "foo" struct is shown below.

#include <axis2_foo.h>

typedef struct axis2_foo_impl axis2_foo_impl_t;

struct axis2_foo_impl
{
    axis2_foo_t foo;
    my_type my_data; /*private data*/
};


/* Function Headers */

void AXIS2_CALL axis2_foo_bar(axis2_foo_t *foo, const axis2_env_t *env, void *data);
axis2_status_t AXIS2_CALL axis2_foo_free(axis2_foo_t *foo, const axis2_env_t *env);

/* Function Implementations */
axis2_foo_t * AXIS2_CALL axis2_foo_create(const axis2_env_t *env)
{
    axis2_foo_impl_t * foo_impl = NULL;
    /* create axis2_foo_impl_t structure and initialize private data */

    /* create ops structure of foo */

    /* bind ops to functions */
    foo_impl->foo.ops->bar = axis2_foo_bar;
    foo_impl->foo.ops->free = axis2_foo_free;

    return &(foo_impl->foo);
}

void AXIS2_CALL axis2_foo_bar(axis2_foo_t *foo, const axis2_env_t *env, void *data)
{
    /* do something */
}

axis2_status_t AXIS2_CALL axis2_foo_free(axis2_foo_t *foo, const axis2_env_t *env)
{
    /* do the dirty work of cleaning the allocated memory */
}

If you take a closer look at the sample above, you will see that almost all functions take a double pointer to the axis2_env struct. To learn more about the Axis2/C environment, please have a look at the architecture notes.

Memory Management

When writing services as well as client programs, you have to ensure that you do proper memory management. You need to be careful to deallocate the memory you have allocated in order to avoid memory leaks (and to avoid those nasty segmentation faults ;-) ). Remebering where you allocate memory is a good practice, which will help you to properly free such allocated memory.

Memory Management Guidelines for Developing Services

To understand how the allocated memory is reclaimed, it is worth looking at the service skeleton interface which is defined in : axis2_svc_skeleton.h

AXIS2_DECLARE_DATA struct axis2_svc_skeleton_ops
{
    int (AXIS2_CALL * free)(axis2_svc_skeleton_t *svc_skeli, const axis2_env_t *env);
    ...
};

AXIS2_DECLARE_DATA struct axis2_svc_skeleton
{
    axis2_svc_skeleton_ops_t *ops;
    axis2_array_list_t *func_array;
};

The service skeleton implementation should implement the free function and attach that to the ops struct of axis2_svc_skeleton_t struct. This free function is called each time after a web services request is served.

Let's try to understand this through an example.

Example: Service skeleton implementation for math service.

The following code shows the implementation of math_free function which is to be attached to the ops of axis2_svc_skeleton_t struct. Usually the memory allocated for the operations struct, the function array and the service skeleton struct are cleaned inside this. You may clean additional memory here if you have allocated any specific memory blocks in math_create function or elsewhere, depending on your implementation specifics.

int AXIS2_CALL math_free(axis2_svc_skeleton_t *svc_skeleton, const axis2_env_t *env)
{
    if(svc_skeleton->ops)
    {
        AXIS2_FREE(env->allocator, svc_skeleton->ops);
        svc_skeleton->ops = NULL;
    }
    if(svc_skeleton->func_array)
    {
        AXIS2_ARRAY_LIST_FREE(svc_skeleton->func_array, env);
        svc_skeleton->func_array = NULL;
    }
    if(svc_skeleton)
    {
        AXIS2_FREE(env->allocator, svc_skeleton);
        svc_skeleton = NULL;
    }
    return AXIS2_SUCCESS;
}

In the axis2_math_create function, you assign the function pointer of math_free function to the free function of the operations struct. As you can see, now the SOAP engine has a function reference to perform garbage collection after each service request.

AXIS2_EXTERN axis2_svc_skeleton_t * AXIS2_CALL axis2_math_create(const axis2_env_t *env)
{
    axis2_svc_skeleton_t *svc_skeleton = NULL;
    svc_skeleton = AXIS2_MALLOC(env->allocator, sizeof(axis2_svc_skeleton_t));

    svc_skeleton->ops = AXIS2_MALLOC(env->allocator, sizeof(axis2_svc_skeleton_ops_t));

    svc_skeleton->ops->free = math_free;
    ...
    return svc_skeleton;
}


SOAP engine frees the memory allocated for the axiom_node_t struct that is used to form the response SOAP message. However, it is the responsibility of the service developer to clean any additional memory allocated.

Memory Management Guidelines for Developing Clients

Most of the memory allocated in the client side (including SOAP request and reply messges), should be cleaned in the client side itself. Memory allocated for properties are taken care of by the SOAP engine. SOAP engine reclaims the memory allocated for properties based on the scope (Request, Session or Application) that you set for each property.

Coding Conventions

Coding conventions used with the Axis2 project is listed in this document.

Unit Tests

CuTest library is used to write unit tests in Axis2/C.

You need to follow two steps to write a unit test for a module.

1. Add the unit test to the module's test suite.

2. Add the module's test suite to the main unit test suite.

Step1: Adding a unit test to a module's unit test suite

This section illustrates how to add a unit test case to the util module. Let's take, for example, the unit tests for axis2 stream. There are two files named util_stream_test.c/.h placed into modules/util/test folder. Here's a sample code written to test the operation axis2_stream_ops_read.

#include "util_stream_test.h"

void Testaxis2_stream_ops_read(CuTest *tc)
{
    char actual[10];
    axis2_allocator_t *allocator = axis2_allocator_init(NULL);
    axis2_env_t *env = axis2_environment_create(allocator, NULL, NULL, NULL, NULL);
    axis2_stream_read(env->stream, actual, 10);
    char *expected = strdup("aaaaaaaaa");
    CuAssertStrEquals(tc, expected, actual);
}


The prototype of the function should be added to the header file. The test suite is defined in util_test.c. You need to add the above defined test case to the test suite as shown below.

#include "util_test.h"
#include <axis2_allocator.h>
#include <axis2_environment.h>

CuSuite* axis2_utilGetSuite()
{
    CuSuite* suite = CuSuiteNew();

    SUITE_ADD_TEST(suite, Testaxis2_stream_ops_read);
    SUITE_ADD_TEST(suite, Testaxis2_log_ops_write);
    SUITE_ADD_TEST(suite, Testaxis2_hash_ops_get);

    return suite;
}


Step2: Adding the Module test suite to the Main Unit Test Suite

Now, you need to add the util module test suite to the main test suite.

#include <CuTest.h>
#include "../../util/test/util_test.h"
#include "../../common/test/common_test.h"

void RunAllTests(void)
{
    CuString *output = CuStringNew();
    CuSuite* suite = CuSuiteNew();

    CuSuiteAddSuite(suite, axis2_utilGetSuite());
    CuSuiteAddSuite(suite, axis2_commonGetSuite());
    CuSuiteRun(suite);
    CuSuiteSummary(suite, output);
    CuSuiteDetails(suite, output);
    printf("%s\n", output->buffer);
}

int main(void)
{   
    RunAllTests();
    return 0;
}


You can either run only the unit tests written for the util module or all the tests.