Contact Us 1-800-596-4880

Bookstore Example

The bookstore example demonstrates how to:

  • Run Mule ESB as a web application inside a servlet container (using the Jetty connector)

  • Use Mule with the Servlet transport

  • Use Mule with CXF web services

You can read more about the Jetty connector, Servlet transport, and CXF Module in the Mule User Guide.

Overview of the Bookstore Example

The bookstore example includes the following:

  • A bookstore catalog service with a public interface for browsing the catalog and an admin interface for adding books to the catalog

  • An order service that allows customers to order books

  • An email notification service that sends an email when an order has been placed.

  • A data warehouse service that tracks statistics on the books, such as the number of books sold, total revenue, and best sellers.

The example bundles two web apps:

  • The Admin webapp includes the Mule configuration file and the service definitions. This module is hosted behind the firewall.

  • The Bookstore webapp includes the JSPs for displaying the catalog to customers outside of the firewall.

In order to make the example easily deployable, both web apps are bundled together in a singe Mule application. In a real world scenario both web apps would be deployed separately using the following architecture:

bookstore example arch

Running the Example

The Bookstore example is configured to run in an embedded jetty servlet container right from the Mule server.

To run the Bookstore example, simply copy the mule-example-bookstore-3.1.0.zip application archive over from the example directory to the <MULE_HOME>/apps directory and start Mule.

To rebuild the example, you must use Maven or create a new project in the Mule IDE using the bookstore example as a template.

The applications will then be available at the following URLs:

Service Details

These services are defined in the bookstore-config.xml file in the examples\bookstore\src\main\resources directory. The file contains four flows:

<?xml version="1.0" encoding="UTF-8"?><mule xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:vm="http://www.mulesoft.org/schema/mule/vm" xmlns:cxf="http://www.mulesoft.org/schema/mule/cxf"    xmlns:smtp="http://www.mulesoft.org/schema/mule/smtp" xmlns:smtps="http://www.mulesoft.org/schema/mule/smtps"    xmlns:email="http://www.mulesoft.org/schema/mule/email"    xmlns:servlet="http://www.mulesoft.org/schema/mule/servlet"    xsi:schemaLocation="        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd        http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/3.1/mule.xsd        http://www.mulesoft.org/schema/mule/vm http://www.mulesoft.org/schema/mule/vm/3.1/mule-vm.xsd        http://www.mulesoft.org/schema/mule/cxf http://www.mulesoft.org/schema/mule/cxf/3.1/mule-cxf.xsd        http://www.mulesoft.org/schema/mule/smtp http://www.mulesoft.org/schema/mule/smtp/3.1/mule-smtp.xsd        http://www.mulesoft.org/schema/mule/smtps http://www.mulesoft.org/schema/mule/smtps/3.1/mule-smtps.xsd        http://www.mulesoft.org/schema/mule/email http://www.mulesoft.org/schema/mule/email/3.1/mule-email.xsd        http://www.mulesoft.org/schema/mule/servlet http://www.mulesoft.org/schema/mule/servlet/3.1/mule-servlet.xsd">    <context:property-placeholder location="email.properties" />    <!-- Configure some properties to work with GMail's SMTP -->    <smtp:gmail-connector name="emailConnector" />    <!-- Use this as a poor man's message queue, in the real world we would use JMS -->    <vm:connector name="vmQueues" />    <!-- This queue contains a feed of the latest statistics generated by         the Data Warehouse (it should really be a LIFO queue) -->    <vm:endpoint name="stats" path="statistics" exchange-pattern="one-way" />    <message-properties-transformer name="setHtmlContentType" overwrite="true">        <add-message-property key="Content-Type" value="text/html" />        <!-- Tomcat lowercases headers, need to drop this old one too -->        <delete-message-property key="content-type" />    </message-properties-transformer>    <flow name="CatalogService">        <composite-source>            <!-- Public interface -->            <inbound-endpoint address="http://0.0.0.0:8777/services/catalog" exchange-pattern="request-response">                <cxf:jaxws-service serviceClass="org.mule.example.bookstore.CatalogService" />            </inbound-endpoint>            <!-- Administration interface -->            <inbound-endpoint address="servlet://catalog" exchange-pattern="request-response">                <!-- Convert request parameters to Book object -->                <custom-transformer class="org.mule.example.bookstore.transformers.HttpRequestToBook" />                <response>                    <!-- Format response to be a nice HTML page -->                    <custom-transformer class="org.mule.example.bookstore.transformers.AddBookResponse" />                    <!-- Force text/html, otherwise it falls back to request                         props, which have form-encoded one -->                    <transformer ref="setHtmlContentType" />                </response>            </inbound-endpoint>        </composite-source>        <component>            <singleton-object class="org.mule.example.bookstore.CatalogServiceImpl" />        </component>    </flow>    <flow name="OrderService">        <!-- Public interface -->        <inbound-endpoint address="http://0.0.0.0:8777/services/order" exchange-pattern="request-response">            <cxf:jaxws-service serviceClass="org.mule.example.bookstore.OrderService" />        </inbound-endpoint>        <component>            <singleton-object class="org.mule.example.bookstore.OrderServiceImpl" />        </component>        <vm:outbound-endpoint path="emailNotification" exchange-pattern="one-way" />        <vm:outbound-endpoint path="dataWarehouse" exchange-pattern="one-way" />    </flow>    <flow name="EmailNotificationService">        <vm:inbound-endpoint path="emailNotification" exchange-pattern="one-way" />        <smtps:outbound-endpoint user="${user}" password="${password}" host="${host}" from="${from}" subject="Your order has been placed!">            <custom-transformer class="org.mule.example.bookstore.transformers.OrderToEmailTransformer" />            <email:string-to-email-transformer />        </smtps:outbound-endpoint>    </flow>    <flow name="DataWarehouse">        <vm:inbound-endpoint path="dataWarehouse" exchange-pattern="one-way" />        <component>            <singleton-object class="org.mule.example.bookstore.DataWarehouse" />        </component>        <outbound-endpoint ref="stats">            <transformer ref="setHtmlContentType" />        </outbound-endpoint>    </flow></mule>

Admin Webapp

When an administrator adds a book in the Admin application, the information in the web page is sent as an HTTP request using the POST method <form method="POST" action="../catalog"> over the Servlet transport to servlet://catalog. Mule then transforms the HTTP request to a Book object, which is passed via the addBook method to the Catalog service.

The Admin webapp web.xml file, located in the examples\bookstore\src\main\app\bookstore-admin\WEB-INF directory under your Mule directory, provides the Mule configuration as a context parameter so that Mule can be started within the webapp:

<web-app>    <display-name>Bookstore Administration</display-name>    <description>Administration console for the Mule-powered On-line Bookstore</description>    <!-- The Mule configuration is provided as a context parameter -->    <context-param>        <param-name>org.mule.config</param-name>        <param-value>bookstore-config.xml</param-value>    </context-param>    <!-- This listener will start up Mule inside the webapp -->    <listener>        <listener-class>org.mule.config.builders.MuleXmlBuilderContextListener</listener-class>    </listener>...

The JSP used to construct the web page, admin.jsp, is located in the examples\bookstore\src\main\app\bookstore-admin directory.

Bookstore Webapp

When a customer searches the catalog through the bookstore web application, the getBooks() method is called on the Catalog service. The request is sent over the CXF transport cxf:http://localhost:8777/services/catalog, which marshalls the request between Java and WSDL/XML. The Catalog service replies with the Book object, which is sent back as an XML response marshalled by CXF, and the result is displayed in the HTML page.

The Bookstore webapp web.xml file, located in the examples\bookstore\src\main\app\bookstore\WEB-INF directory under your Mule directory, loads the catalog JSP to display in the HTML page to customers:

<web-app>    <display-name>On-line Bookstore</display-name>    <description>Mule-powered On-line Bookstore</description>    <welcome-file-list>        <welcome-file>catalog.jsp</welcome-file>    </welcome-file-list></web-app>

The HTML page looks like this:

+ image::bookstore-example-html.png[]

Java Classes

The Java classes for the Admin webapp are located in examples\bookstore\src\main\java\org\mule\example\bookstore. The CatalogServiceImpl.java class defines the methods for adding and getting books. The OrderServiceImpl.java class defines the orderBook() method. The DataWarehouse.java class defines methods for updating statistics on the page, retrieving the best seller, and printing the statistics on the page.

The domain objects such as Book and Order are defined in Java classes in examples\bookstore\src\main\java\org\mule\example\bookstore.

In addition to defining the various methods, the Java files also contain annotations. @WebService lets CXF know that this is a JAX-WS service. For example, CatalogServiceImpl.java contains the following annotation:

@WebService(serviceName="CatalogService", endpointInterface="org.mule.example.bookstore.CatalogService")

The serviceName attribute specifies that the service name in the WSDL will be "CatalogService". The endpointInterface attribute will control what interface CXF uses to build your WSDL. If this is not specified, CXF will use your web service implementation class to generate the WSDL and therefore expects any @WebParam and @WebResult annotations to be located there.

The @WebResult and @WebParam annotations are used to define the parameter names in the WSDL (Java does not store parameter names in the class files, so they must be supplied with annotations):

@WebResult(name="order")   Order orderBook(@WebParam(name="book") Book book,     @WebParam(name="quantity") int quantity,     @WebParam(name="address") String address,    @WebParam(name="email") String email);

Transformers

The examples\bookstore\src\main\java\org\mule\example\bookstore\transformers directory contains the transformers that do the following:

  • Adds a book to the response message and wraps it in an HTML body using the HTML template in examples\bookstore\src\main\java\org\mule\example\bookstore\web.

  • Composes an e-mail notification message to be sent based on the Book Order. The <string-to-email-transformer> from the Email transport is then used to convert the text to an email message.

  • Transforms a Map of HttpRequest parameters into a Book object.