The Bean Component is a pluggable container in SwitchYard which allows Java classes (or beans) to provide and consume services. This means that you can implement a service by simply annotating a Java class. It also means you can consume a service by injecting a reference to that service directly into your Java class. Rather than writing our own POJO container to provide this capability, we have implemented the bean component as a Weld extension. No need to learn a new programming model - bean services are standard CDI beans with a few extra annotations. This also opens up the possibilities of how SwitchYard is used; you can now expose existing CDI-based beans in your application as services to the outside world or consume services within your bean.
Providing a Service
Providing a service with the Bean component is as simple as adding an @Service annotation to your bean.
@Service(SimpleService.class)
public class SimpleServiceBean implements SimpleService {
public String sayHello(String message) {
System.out.println("*** Hello message received: " + message);
return "Hi there!!";
}
}
public interface SimpleService {
String sayHello(String message);
}
The SimpleService interface represents the Service Interface, defining the service operations that are exposed by SwitchYard.
The only other thing you need is a META-INF/beans.xml file in your deployed application. When the application is deployed, the Weld runtime scans the application for beans and enlists the SwitchYardCDIServiceDiscovery CDI extension in the CDI lifecycle. Our extension will pick up @Service beans and make them available to the application deployer (will depend on the container). At this point, the service can be invoked from other services within SwitchYard or bound to a wire protocol via SwitchYard gateways.
Service Operations
All SwitchYard Services, no matter what the implementation type, are composed of one or more Service Operations. As you might imagine, in the case of a Bean Service, the Service Operations are the set of Java methods exposed by the Service Interface.
There are a few restrictions when it comes to defining Bean Service Operations:
-
Declares a maximum of one Input type i.e. the Java method signature must have a maximum of one Java parameter.
-
Declares a maximum of one Output type. This is obviously enforced by the Java language since you can only define one return type on a Java method.
-
Declares a maximum of one Fault (Exception) type.
Service Operation Types
All Service Operations on a SwitchYard Service can define an
Input
,
Output
and
Fault
message. These messages have a
type
associated with them, which is defined as a QName. This type is used by the data transformation layer, when trying to work out which transformers to apply to a Message payload.
For bean Services, the default type QName for Input (input param), Output (return value) and Fault (Exception) are derived from the Java class name in each case (param, return, throws). For some types however (e.g. org.w3c.dom.Element), the Java type name alone does not tell you the real type of the data being held by that Java Object instance. For this reason, Bean Service Operations (methods) can be annotated with the @OperationTypes annotation e.g.
public interface OrderService {
@OperationTypes(
in = "{http://acme.com/orders}createOrder",
out = "{http://acme.com/orders}createOrderResult",
fault = "java:com.acme.exceptions.OrderManagementException"
)
Element createOrder(Element order) throws OrderCreateFailureException;
}
It's also possible to set the default data type for a Java type using the @DefaultType Type level annotation. This is useful for setting the type for a hierarchy of Java types. Note in the example code above how we changed the type for OrderCreateFailureException (to "java:com.acme.exceptions.OrderManagementException") by defining a fault type on the @OperationTypes. It's type would otherwise default to "java:com.acme.exceptions.OrderCreateFailureException". We could also do this by annotating the base OrderManagementException class with the @DefaultType annotation. This would set the default type for the OrderManagementException class and all its sub-classes, including OrderCreateFailureException, which would mean not having to defining a fault type on the @OperationTypes wherever one of these exceptions is used on a Bean Service Operation.
SwitchYard Context Injection
Sometimes you need access to the SwitchYard Exchange
Context
instance associated with a given Bean Service Operation invocation. To access this, simply add a
Context
property to your bean and annotate it with the CDI
@Inject
annotation.
@Service(SimpleService.class)
public class SimpleServiceBean implements SimpleService {
@Inject
private Context context;
public String sayHello(String message) {
System.out.println("*** Funky Context Property Value: " + context.getPropertyValue("funkyContextProperty"));
return "Hi there!!";
}
}
Note that you can only make calls on the Context instance within the scope of one of the Service Operation methods. Invoking it outside this scope will result in an UnsupportedOperationException being thrown.
Consuming a Service
Consuming a SwitchYard service from within a CDI bean is done via @Reference annotations.
@Service(ConsumerService.class)
public class ConsumerServiceBean implements ConsumerService {
@Inject
@Reference
private SimpleService service;
public void consumeSomeService(String consumerName) {
service.sayHello("Hello " + consumerName);
}
}
public interface ConsumerService {
void consumeSomeService(String consumerName);
}
Note that the contract for the service is all that's exposed to the bean consumer. The reference could point to a service that is hosted outside of SwitchYard and exposed over JMS, SOAP, FTP, etc. The SwitchYard runtime handles the resolution of the service reference to a concrete service, allowing your service logic to remain blissfully ignorant. Invocations made through this reference are routed through the SwitchYard exchange mechanism.
The @Reference annotation can accept a service name in cases where the default name resolution (interface Class simple name e.g. "OrderService") are not appropriate.
@Reference("urn:myservices:purchasing:OrderService")
private OrderService orders;
This can be useful when the default name resolution is not appropriate. Keep in mind that the name used by the reference is not required to match the target service, but it resolve to a service through some mechanism. For example, the deployed application could contain wiring configuration which maps the service name used by the bean reference to a different service name in the runtime.
Bean Services In a Java EE Web Application Container
As of SwitchYard 0.2.0, applications can be deployed in a JEE Web Application Container such as Tomcat or Jetty i.e. you don't need to use the SwitchYard Application Servers (AS6 or AS7).
Two distinct tasks need to be performed in order to get your CDI based SwitchYard application working in your Web Application Container:
-
Configure the SwitchYard WebApplicationDeployer Servlet Listener in your Web Application.
-
Configure Weld into your Servlet Container.
See the "webapp-deploy" quickstart (in the demos folder) as an example of how to create a CDI Bean based SwitchYard application that can be deployed on Tomcat.
JavaServer Faces
The JavaServer Faces (JSF) technology provides a server-side component framework that is designed to simplify the development of user interfaces (UIs) for Java EE applications. JSF has very tight integration with CDI to provide the Object Model behind the JSF user interface components.
The fact that SwitchYard Bean Services are based on CDI means it's possible to have a really nice integration between SwitchYard Bean Services and a JSF based user interface. This section is not an CDI or JSF reference, but does provide some tips/guidelines on using these technologies within the context of SwitchYard. This list of tips will grow and be refined as more use cases are tested and verified.
JavaServer Faces - General Guidelines
The following guidelines/tips should apply to any container when building a JSF user interface on top of a SwitchYard Service.
Indirect Service Invocation
As with any JSF based user interface, the JSF pages will contain EL tokens referencing scoped CDI beans (by name ala the
@Named
bean annotation). It's possible to annotate your SwitchYard Service CDI beans with the
@Named
annotation and inject them directly into your application's JSF components, but that would result in your JSF pages making direct invocations on the Service implementation (i.e. the Service implementation bean would be injected directly into the JSF components). What you really want to do is to invoke your SwitchYard Services through the SwitchYard
Exchange
mechanism. This reduces the coupling between your JSF components and your SwitchYard Service implementations.
Currently, the only way to invoke a SwitchYard CDI Bean Service through the SwitchYard Exchange mechanism is to invoke them through a @Reference injected Service reference (other options may be available in future). This provides a client side proxy bean that handles all the SwitchYard Exchange invocation magic for all the operations exposed by the Service in question. The catch here is that these proxy beans are not available (through CDI) to the JSF components, so you need a bog standard named (@Named) CDI bean containing an injected @Reference sitting between the JSF components and the SwitchYard CDI Bean Services. This is not really a problem though, because your JSF components will likely have a more natural interaction with a business/model type value-object bean (getters/setters) than they will with a Service interface type bean (operations). So, a layer of indirection will probably make sense anyway.
So if you consider an example of an OrderService (Service implementation not shown):
public interface OrderService {
OrderAck submitOrder(Order order);
}
public class Order implements Serializable {
private String orderId;
private String itemId;
private int quantity = 1;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
Your JSF components will probably have a more natural interaction with the Order bean than with the OrderService e.g.:
<div id="content">
<h1>New Order</h1>
<div style="color: red">
<h:messages id="messages" globalOnly="false" />
</div>
<h:form id="newOrder">
<div>
Order ID:
<h:inputText id="orderID" value="#{order.orderId}" required="true"/>
<br/>
Item ID:
<h:inputText id="itemID" value="#{order.itemId}" required="true"/>
<br/>
Quantity:
<h:inputText id="quantity" value="#{order.quantity}" required="true"/>
<p/>
<h:commandButton id="createOrder" value="Create" action="#{order.create}"/>
</div>
</h:form>
</div>
However, in order to make this work, we need to make a few tweaks to the Order bean:
-
Annotating it with @Named and @RequestScoped.
-
Adding a @Reference to the OrderService.
-
Implementing the create method that invokes the OrderService reference (Exchange proxy).
@Named
@RequestScoped
public class Order implements Serializable {
@Inject
@Reference
private OrderService orderService;
private String orderId;
private String itemId;
private int quantity = 1;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getItemId() {
return itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public void create() {
OrderAck serviceAck = orderService.submitOrder(this);
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(serviceAck.toString()));
}
}
To reiterate, because of the @Reference annotation, the orderService property instance is not a reference to the actual Service implementation. Instead, it is a SwitchYard Exchange proxy to that Service implementation.
Also note that by using @Reference injected Service references, your backend Service implementation can be a non CDI Bean Service implementation e.g. a Camel Routing Services. This mechanism opens up all sorts of integration possibilities!
See the "orders" quickstart (in the demos folder) as an example of how to provide a JSF user interface on top of a SwitchYard CDI Bean Service deployed on SwitchYard AS7.