Fault Handling and Signaling in Advanced BPEL

Exclusive offer: get 50% off this eBook here
WS-BPEL 2.0 for SOA Composite Applications with IBM WebSphere 7

WS-BPEL 2.0 for SOA Composite Applications with IBM WebSphere 7 — Save 50%

Define, model, implement, and monitor real-world BPEL 2.0 business processes with SOA-powered BPM for IBM WebSphere 7 with this book and eBook

£26.99    £13.50
by Marc Delisle | October 2010 | BPEL Enterprise Articles IBM SOA

In this article, by Matjaz B. Juric, author of WS-BPEL 2.0 for SOA Composite Applications with IBM WebSphere 7, we will learn about fault handling & signaling in BPEL

We will cover the following topics:

  • WSDL faults
  • Signaling faults
  • Handling faults

 

WS-BPEL 2.0 for SOA Composite Applications with IBM WebSphere 7

WS-BPEL 2.0 for SOA Composite Applications with IBM WebSphere 7

Define, model, implement, and monitor real-world BPEL 2.0 business processes with SOA-powered BPM

  • Develop BPEL and SOA composite solutions with IBM's WebSphere SOA platform
  • Automate business processes with WS-BPEL 2.0 and develop SOA composite applications efficiently
  • Detailed explanation of advanced topics, such as security, transactions, human workflow, dynamic processes, fault handling, and more—enabling you to work smarter

Appendix

        Read more about this book      

(For more resources on BPEL, see here.)

Introduction

Business processes specified using BPEL will interact with their partners through operation invocations of web services. Web services are based on loosely coupled Service Oriented Architecture (SOA) . The communication between web services is done over Internet connections that may or may not be highly reliable. Web services could also raise faults due to logical errors and execution errors arising from defects in the infrastructure. Therefore, BPEL business processes will need to handle faults appropriately. BPEL processes may also need to signal faults themselves. Fault handling and signaling is an important aspect of business processes designed using BPEL.

Faults in BPEL can arise in various situations:

  • When a BPEL process invokes a synchronous web service operation, the operation might return a WSDL fault message, which results in a BPEL fault.
  • A BPEL process can explicitly signal (throw) a fault.
  • A fault can be thrown automatically, for example, when a join failure has occurred.
  • The BPEL server might encounter error conditions in the runtime environment, network communications, or any other such area. BPEL defines several standard faults.

WSDL faults

WSDL faults occur due to synchronous operation invocations on partner web services. In WSDL, such faults are denoted with the &ltfault> element within the &ltoperation> declaration. In BPEL, WSDL faults are identified by the qualified name of the fault and the target namespace of the corresponding port type used in the operation declaration.

The TravelApproval operation used on the TravelApprovalPT port type with input and output messages is shown in the following WSDL excerpt:

<portType name="TravelApprovalPT">
<operation name="TravelApproval">
<input message="tns:TravelRequestMessage" />
<output message="aln:TravelResponseMessage" />
</operation>
</portType>

To add fault information to the operation, we first need to define a corresponding message. For simplicity, this message will be of the xs:string type:

<message name="TravelFaultMessage">
<part name="error" type="xs:string" />
</message>

Now we will add the fault declaration to the operation signature shown previously:

<portType name="TravelApprovalPT">
<operation name="TravelApproval">
<input message="tns:TravelRequestMessage" />
<output message="aln:TravelResponseMessage" />
<fault name="fault" message="tns:TravelFaultMessage" />
</operation>
</portType>

WSDL does not require that we use unique fault names within the namespace used to define the operation. This implies that faults that have the same name and are defined within the same namespace will be considered as the same fault in BPEL. Keep this in mind when designing services that can potentially become partners of BPEL business processes, because this can lead to conflicts in fault handling during execution.

Signaling faults

A business process may sometimes need to explicitly signal a fault. For such a situation, BPEL provides the &ltthrow> activity. It has the following syntax:

<throw faultName="name" />

BPEL does not require that we define fault names in advance, prior to their use in the &ltthrow> activity. This flexible approach can also be error-prone, because there is no compile-time checking of fault names. Therefore, a typo could result in a situation where a misspelled fault might not be handled by the designated fault handler. Faults can also have an associated variable that usually contains data related to the fault. If such a variable is associated with the fault, we need to specify it when throwing the fault. This is done by using the optional faultVariable attribute as shown here:

<throw faultName="name" faultVariable="variable-name" />

The following example shows the most straightforward use of the &ltthrow> activity; where a WrongEmployeeName fault is thrown, no variable is needed. Remember that fault names are not declared in advance:

<throw faultName="WrongEmployeeName" />

The faults raised with the &ltthrow> activity have to be handled in the BPEL process. Faults that are not handled will not be automatically propagated to the client as is the case in modern programming languages (Java for example). Rather, the BPEL process will terminate abnormally. Sometimes, however, we may want to signal faults to clients.

Signaling faults to clients in synchronous replies

A BPEL process offers operations to its clients through the &ltreceive> activity. If the process wants to provide a synchronous request/response operation, it sends a &ltreply> activity in response to the initial &ltreceive>. Remember that the type of the operation is defined in the WSDL document of the BPEL process. A synchronous request/response operation is defined as an operation that has an input and an output message, and an optional fault message.

If such an operation has the fault part specified, we can use the &ltreply> activity to return a fault instead of the output message. The syntax of the &ltreply> activity in this case is:

<reply partnerLink="partner-link-name"
portType="port-type-name"
operation="operation-name"
variable="variable-name" <!-- optional -->
faultName="fault-name" >
</reply>

When we specify a fault name to be returned through the &ltreply> activity, the variable name is optional. If we specify a variable name, then the variable has to be of the fault message type as defined in WSDL.

Example

Let's modify the BPEL process definition in the synchronous travel example and signal the fault (TravelFaultMessage) to the client by using the &ltreply> activity.

First, we need to declare an additional variable that will hold the fault description to return to the client. The variable is of the TravelFaultMessage type:

<variables>
<!-- fault to the BPEL client -->
<variable name="TravelFault" messageType="trv:TravelFaultMessage"/>
</variables>
...

Then we return the fault to the BPEL process client. We will need to check if something went wrong in the travel process. For the purpose of this example, we will check whether the selected flight ticket has been approved. This information is stored in the confirmationData part of the TravelResponse variable in the Approved element. Note that this is an oversimplification, but it demonstrates how to return faults. We can use an &ltif> activity to determine whether the ticket is approved; then we construct the fault variable and use the &ltreply> activity to return it to the client. This is shown in the following code:

<!-- Check if the ticket is approved -->
<if>
<condition>
$TravelResponse.confirmationData/aln:Approved='true'
</condition>
<!-- Send a response to the client -->
<reply partnerLink="client"
portType="trv:TravelApprovalPT"
operation="TravelApproval"
variable="TravelResponse"/>
<else>
<sequence>
<!-- Create the TravelFault variable with fault description -->
<assign>
<copy>
<from>string('Ticket not approved')</from>
<to variable="TravelFault" part="error" />
</copy>
</assign>
<!-- Send a fault to the client -->
<reply partnerLink="client"
portType="trv:TravelApprovalPT"
operation="TravelApproval"
variable="TravelFault"
faultName="fault" />
</sequence>
</else>
</if>

If the ticket is not approved, the following fault is signaled to the client:

<TravelFault>
<part name="error">
<error xmlns="http://packtpub.com/bpel/travel/">
Ticket not approved
</error>
</part>
</TravelFault>

We have seen that signaling faults in synchronous replies is easy. Let us now discuss signaling faults in asynchronous scenarios.

Signaling faults to clients in asynchronous scenarios

If an asynchronous BPEL process needs to notify the client about a fault, it cannot use the &ltreply> activity. Remember that in asynchronous scenarios the client does not wait for the reply; rather, the process uses a callback. To return a fault in callback scenarios, we usually define additional callback operations on the same port type. Through these callback operations, we can signal that an exceptional situation has prevented normal completion of the process.

To demonstrate how faults can be propagated to the client using a callback operation, we will use the asynchronous travel process example. First, we need to modify the travel BPEL process WSDL and introduce another operation called ClientCallbackFault. This operation consists of an input message called tns:TravelFaultMessage. The message is of the string type. The declaration of the operation and the message is shown in the following code excerpt:

<message name="TravelFaultMessage">
<part name="error" type="xs:string" />
</message>
<portType name="ClientCallbackPT">
<operation name="ClientCallback">
<input message="aln:TravelResponseMessage" />
</operation>
<operation name="ClientCallbackFault">
<input message="tns:TravelFaultMessage" />
</operation>
</portType>

We can use the &ltif> activity to determine whether the ticket has been approved. If the ticket is not approved, however, we &ltinvoke> the ClientCallbackFault operation instead of using the &ltreply> activity to signal the fault to the client. This is shown in the following code excerpt:

<!-- Check if the ticket is approved -->
<if>
<condition>
$TravelResponse.confirmationData/aln:Approved='true'
</condition>
<!-- Make a callback to the client -->
<invoke partnerLink="client"
portType="trv:ClientCallbackPT"
operation="ClientCallback"
inputVariable="TravelResponse" />
<else>
<sequence>
<!-- Create the TravelFault variable with fault description -->
<assign>
<copy>
<from>string('Ticket not approved')</from>
<to variable="TravelFault" part="error" />
</copy>
</assign>
<!-- Send a fault to the client -->
<invoke partnerLink="client"
portType="trv:ClientCallbackPT"
operation="ClientCallbackFault"
inputVariable="TravelFault" />
</sequence>
</else>
</if>

In the next section, we will look at how to handle faults thrown in BPEL processes.

Handling faults

Now that we are familiar with how faults are signaled, let us consider how the business process handles faults. When a fault occurs within a business process (this can be a WSDL fault, a fault thrown by the BPEL process, or any other type of fault), it means that the process may not complete successfully. The process can complete successfully only if the fault is handled within a scope.

Business processes handle faults through fault handlers.

A business process can handle a fault through one or more fault handlers. Within a fault handler, the business process defines custom activities that are used to recover from the fault and recover the partial (unsuccessful) work of the activity in which the fault has occurred.

The fault handlers are specified before the first activity of the BPEL process, after the partner links and variables. The overall structure is shown in the following code excerpt:

<process ...>
<partnerLinks>
...
</partnerLinks>
<variables>
...
</variables>
<faultHandlers>
<catch ... >
<!-- Perform an activity -->
</catch>
<catch ... >
<!-- Perform an activity -->
</catch>
...
<catchAll>
<!-- catchAll is optional -->
<!-- Perform an activity -->
</catchAll>
</faultHandlers>
<sequence>
</sequence>
</process>

We can see that within the fault handlers, we specify several &ltcatch> activities where we indicate the fault that we would like to catch and handle. Within a fault handler, we have to specify at least one &ltcatch> or a &ltcatchAll> activity. Of course, the &ltcatchAll> activity can be specified only once within a fault handler.

Usually we will specify several &ltcatch> activities to handle specific faults and use the &ltcatchAll> to handle all other faults. The &ltcatch> activity has two attributes, of which we have to specify at least one:

  • faultName: Specifies the name of the fault to be handled.
  • faultVariable: Specifies the variable type used for fault data. Additionally, we can specify one of the following attributes (both are optional, but we may specify one, not both):
    • faultMessageType: Specifies the WSDL message type of the fault to be handled.
    • faultElement: Specifies the XML element type of the fault to be handled.

The flexibility of &ltcatch> activities is high and several variations are permissible. Listed ahead are the most common:

<faultHandlers>
<catch faultName="trv:TicketNotApproved" >
<!-- First fault handler -->
<!-- Perform an activity -->
</catch>
<catch faultName="trv:TicketNotApproved"
faultVariable="TravelFault" >
<!-- Second fault handler -->
<!-- Perform an activity -->
</catch>
<catch faultVariable="TravelFault" >
<!-- Third fault handler -->
<!-- Perform an activity -->
</catch>
<catchAll>
<!-- Perform an activity -->
</catchAll>
</faultHandlers>

We can see that fault handlers in BPEL are very similar to try/catch clauses found in modern programming languages.

WS-BPEL 2.0 for SOA Composite Applications with IBM WebSphere 7 Define, model, implement, and monitor real-world BPEL 2.0 business processes with SOA-powered BPM for IBM WebSphere 7 with this book and eBook
Published: October 2010
eBook Price: £26.99
Book Price: £43.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on BPEL, see here.)

Selection of a fault handler

Let us consider the fault handlers and discuss the scenarios for which the &ltcatch> activities will be selected:

  • The first &ltcatch> will be selected if the trv:TicketNotApproved fault has been thrown and the fault carries no fault data.
  • The second &ltcatch> will be selected if the trv:TicketNotApproved fault has been thrown and carries data of a type matching that of the TravelFault variable.
  • The third &ltcatch> will be selected if a fault has been thrown whose fault variable type matches the TravelFault variable type, and whose name is not trv:TicketNotApproved.
  • In all other cases the &ltcatchAll> will be selected.

We can see that the selection of the &ltcatch> activity within fault handlers is quite complicated. It may even happen that a fault matches several &ltcatch> activities. Therefore, BPEL specifies exact rules to select the fault handler that will process a fault:

  • For faults without associated fault data, the fault name will be matched. The &ltcatch> activity with a matching faultName will be selected, if present; otherwise, the default &ltcatchAll> handler will be used, if present.
  • For faults with associated fault data, a &ltcatch> activity specifying a matching faultName value and faultVariable value will be selected, if present. Otherwise, a &ltcatch> activity with no specified faultName and a matching faultVariable will be selected, if present. If not, the default &ltcatchAll> handler will be used, if present.

The &ltcatchAll> activity will execute only if no other &ltcatch> activity has been selected.

If no &ltcatch> is selected and &ltcatchAll> is not present, the fault will be rethrown to the immediately enclosing scope, if present. Otherwise, the process will terminate abnormally. This situation is similar to explicitly terminating the process using the &ltexit> activity.

Synchronous example

Let's go back to the synchronous BPEL travel process example to add a fault handlers section. We need to define a fault handler that will simply signal the fault to the client. In real-world scenarios, a fault handler can perform additional work to try to recover the work done by an activity or retry the activity itself.

To signal the fault to the client, we use the same TravelFaultMessage message that we defined in the previous section. Here is an excerpt from the WSDL:


<message name="TravelFaultMessage">
<part name="error" type="xs:string" />
</message>

<portType name="TravelApprovalPT">
<operation name="TravelApproval">
<input message="tns:TravelRequestMessage" />
<output message="aln:TravelResponseMessage" />
<fault name="fault" message="tns:TravelFaultMessage" />
</operation>
</portType>

We define a fault handler and add a &ltfaultHandlers> section immediately after the &ltvariables> definition and before the &ltsequence> activity, as shown ahead. The fault handler for the trv:TicketNotApproved fault is defined with the associated TravelFault variable. This handler will use the &ltreply> activity to signal the fault to the BPEL client. We will also provide a default &ltcatchAll> handler, which will first create a variable and then use the &ltreply> activity to signal the fault to the client:

...
<faultHandlers>
<catch faultName="trv:TicketNotApproved"
faultVariable="TravelFault">
<reply partnerLink="client"
portType="trv:TravelApprovalPT"
operation="TravelApproval"
variable="TravelFault"
faultName="fault" />
</catch>
<catchAll>
<sequence>
<!-- Create the TravelFault variable -->
<assign>
<copy>
<from>string('Other fault')</from>
<to variable="TravelFault" part="error" />
</copy>
</assign>
<reply partnerLink="client"
portType="trv:TravelApprovalPT"
operation="TravelApproval"
variable="TravelFault"
faultName="fault" />
</sequence>
</catchAll>
</faultHandlers>

We also have to modify the process itself. Instead of replying to the client (&ltreply>) in the &ltif> activity, if the ticket has not been approved, we will simply throw a fault, which will be caught by the corresponding fault handler. The fault handler will also catch other possible faults:

...
<!-- Check if the ticket is approved -->
<if>
<condition>
$TravelResponse.confirmationData/aln:Approved='true'
</condition>
<!-- Send a response to the client -->
<reply partnerLink="client"
portType="trv:TravelApprovalPT"
operation="TravelApproval"
variable="TravelResponse"/>
<else>
<sequence>
<!-- Create the TravelFault variable with fault description -->
<assign>
<copy>
<from>string('Ticket not approved')</from>
<to variable="TravelFault" part="error" />
</copy>
</assign>
<!-- Throw fault -->
<throw faultName="trv:TicketNotApproved"
faultVariable="TravelFault" />
</sequence>
</else>
</if>

Faults that are not handled by the BPEL process result in abnormal termination of the process and are not propagated to the client. In other words, unhandled faults do not cross service boundaries unless explicitly specified using a &ltreply> activity as we did in our example. This differentiates BPEL from Java and other languages, where unhandled exceptions are propagated to the client.

Asynchronous example

In asynchronous BPEL processes, faults are handled in the same way as in synchronous processes by using &ltfaultHandlers>. We need to define a fault handler that, in our example, will simply forward the fault to the client. We cannot, however, use the &ltreply> activity to signal the fault to the client. Instead, we need to define an additional callback operation and use the &ltinvoke> activity, as we did in our previous example. In this example, we will use the same fault callback operation as in the previous asynchronous example:

<message name="TravelFaultMessage">
<part name="error" type="xs:string" />
</message>
<portType name="ClientCallbackPT">
<operation name="ClientCallback">
<input message="aln:TravelResponseMessage" />
</operation>
<operation name="ClientCallbackFault">
<input message="tns:TravelFaultMessage" />
</operation>
</portType>

Now we will define the &ltfaultHandlers> section. The difference compared to the synchronous example will be that we will use the &ltinvoke> activity to invoke the newly defined operation instead of the &ltreply> activity to propagate the fault to the client:

<faultHandlers>
<catch faultName="trv:TicketNotApproved"
faultVariable="TravelFault">
<!-- Make a callback to the client -->
<invoke partnerLink="client"
portType="trv:ClientCallbackPT"
operation="ClientCallbackFault"
inputVariable="TravelFault" />
</catch>
<catchAll>
<sequence>
<!-- Create the TravelFault variable -->
<assign>
<copy>
<from>string('Other fault')</from>
<to variable="TravelFault" part="error" />
</copy>
</assign>
<invoke partnerLink="client"
portType="trv:ClientCallbackPT"
operation="ClientCallbackFault"
inputVariable="TravelFault" />
</sequence>
</catchAll>
</faultHandlers>

Another important question related to fault handling is how the BPEL process can be notified of faults that occurred in asynchronously invoked partner web service operations. A typical example is the invocation of the American and Delta Airlines web services in our example. To invoke the operation, we used the &ltinvoke> activity and then a &ltreceive> activity to wait for the callback.

BPEL provides a way to wait for more than just one message (operation call) using the &ltpick> activity. By using &ltpick> instead of &ltreceive>, our BPEL process can wait for several incoming messages. One of these can be a message for regular callback and others can be messages that signal fault conditions. With &ltpick>, we can even specify a time-out for receiving a callback.

Propagating faults

In fault handlers we might want to propagate a fault that we have caught to the higher-level fault handler. For example, a fault handler of a nested scope catches a fault. However, it is a type of fault that it will not handle. Rather, it will propagate it to the fault handler of the higher-level scope.

To achieve this, we can use the &ltrethrow> activity. &ltrethrow> is used to rethrow the fault caught by the fault handler. &ltrethrow> can be used only within a fault handler (&ltcatch> and &ltcatchAll>).

The syntax is simple:

<rethrow />

WS-BPEL 2.0 for SOA Composite Applications with IBM WebSphere 7 Define, model, implement, and monitor real-world BPEL 2.0 business processes with SOA-powered BPM for IBM WebSphere 7 with this book and eBook
Published: October 2010
eBook Price: £26.99
Book Price: £43.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on BPEL, see here.)

Default fault handler

If a &ltcatchAll> fault handler for any fault is not defined for any given &ltscope>, the BPEL engine implicitly creates a default fault handler. The default fault handler will compensate all inner scopes and rethrow the fault to the upper scope.

The default implicit fault handler looks like this:

<catchAll>
<sequence>
<compensate />
<rethrow />
</sequence>
</catchAll>

Inline fault handling

The loosely coupled model of web services and the use of Internet connections for accessing them make the invocation of operations on web services particularly error prone. Numerous situations can prevent a BPEL process from successfully invoking a partner web service operation, such as broken connections, unavailability of web services, changes in the WSDL, and so on. Such faults can be handled in the general &ltfaultHandlers> sections. However, a more efficient way is to handle faults related to the &ltinvoke> activity directly and not rely on the general fault handlers. The &ltinvoke> activity provides a shortcut to achieve this, that is, inline fault handlers.

Inline fault handlers can catch WSDL faults for synchronous operations and also other faults related to the runtime environment, communications, and so on.

The syntax for inline fault handlers in the &ltinvoke> activity is similar to the syntax of the &ltfaultHandlers> section. As shown in the following code excerpt, we can specify zero or more &ltcatch> activities and we can also specify a &ltcatchAll> handler. The only difference is that in inline &ltcatch> activities, we have to specify a fault name. Optionally, we may specify the fault variable:

<invoke ... >
<catch faultName="fault-name" >
<!-- Perform an activity -->
</catch>
...
<catch faultName="fault-name" faultVariable="fault-variable">
<!-- Perform an activity -->
</catch>
...
<catch faultName="fault-name"
faultVariable="fault-variable"
faultMessageType="WSDL-message"
<!-- Optional one or the other -->
faultElement="XML-element" >
<!-- Perform an activity -->
</catch>
...
<catchAll>
<!-- Perform an activity -->
</catchAll>
</invoke>

The following code excerpt shows an inline fault handler for invoking the Employee Travel Status web service from our BPEL travel process example. Please notice that this also requires modifying the Employee Travel Status WSDL and declaring an additional fault message for the operation. Because this code is similar to what we did in previous examples, it is not repeated here again. The following code excerpt demonstrates inline fault handling. The rules, as to which catch activity will be selected, are the same as for standalone fault handlers and have been discussed in the previous section:

<invoke partnerLink="employeeTravelStatus"
portType="emp:EmployeeTravelStatusPT"
operation="EmployeeTravelStatus"
input Variable="EmployeeTravelStatusRequest"
outputVariable="EmployeeTravelStatusResponse" >
<catch faultName="emp:WrongEmployeeName" >
<!-- Perform an activity -->
</catch>
<catch faultName="emp:TravelNotAllowed" faultVariable="FaultDesc" >
<!-- Perform an activity -->
</catch>
<catchAll>
<!-- Perform an activity -->
</catchAll>
</invoke>

 

This brings us to the thought that it would be useful if we could specify more than one &ltfaultHandlers> section in a BPEL process. It would be great if we could specify different fault handlers sections for different parts of the process, particularly for complex processes. This is possible if we use scopes, described in the next section. We will see that inline fault handling of the &ltinvoke> activity is equal to enclosing the &ltinvoke> activity in a local scope.

Summary

In the above article we have covered:

  • WSDL faults
  • Signaling faults
  • Handling faults

Further resources on this subject:


About the Author :


Marc Delisle

Marc Delisle was awarded "MySQL Community Member of the year 2009" because of his involvement with phpMyAdmin. He started to contribute to the project in December 1998, when he made the multi-language version. He is still involved with phpMyAdmin as a developer and project administrator.

Marc is a system administrator at Cegep de Sherbrooke, Québec, Canada. He has been teaching networking, security, and web application development. In one of his classes, he was pleased to meet a phpMyAdmin user from Argentina. Marc lives in Sherbrooke with his wife and they enjoy spending time with their four children.

Books From Packt


WS-BPEL 2.0 for SOA Composite Applications with Oracle SOA Suite 11g
WS-BPEL 2.0 for SOA Composite Applications with Oracle SOA Suite 11g

BPEL PM and OSB operational management with Oracle Enterprise Manager 10g Grid Control
BPEL PM and OSB operational management with Oracle Enterprise Manager 10g Grid Control

Oracle SOA Suite 11g R1 Developer's Guide
Oracle SOA Suite 11g R1 Developer's Guide

Service Oriented Architecture: An Integration Blueprint
Service Oriented Architecture: An Integration Blueprint

IBM Lotus Notes 8.5 User Guide
IBM Lotus Notes 8.5 User Guide

IBM Lotus Notes and Domino 8.5.1
IBM Lotus Notes and Domino 8.5.1

SOA and WS-BPEL
SOA and WS-BPEL

IBM InfoSphere Replication Server and Data Event Publisher
IBM InfoSphere Replication Server and Data Event Publisher


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software