| Back | Next | Contents | Cams Programmer's Guide |
This chapter describes the Cams Services API, which enables you to create and deploy Cams services for use from custom access control rules, session event handlers, login modules, callback handlers, and other services. Cams services are useful for implementing centralized business functionality that can be reused from any of these components.
Cams services are not intended to replace application server components like servlets, Enterprise Java Beans, or CORBA objects. They are very light weight and are designed to support caching and performance enhancements to security-related business logic. In fact, Cam services can easily make use of servlets and EJBs by hosting client code that accesses them. Each loaded Cams service has a single instance, so they must be written thread safe and they should be designed to be as fast and efficient as possible. We'll provide some tips on how to accomplish that in Programming with Cams Services.
Cams ships with two standard services available from every security domain:
You might find it useful to create a custom service that can send an e-mail or pager message to an administrator on certain security events, or a service that computes the number of days left in a user's web site subscription. These services are general enough that they may be useful from access control rules, session event handlers, login modules, etc. Making them Cams services enables you to leverage and reuse them.
Each Cams security domain has a service manager, which may manage zero or more Cams services. A service instance is available only to components within the same security domain, so a service foo hosted within the system security domain is not accessible from components in security domain mycompany.com. Figure 1 shows the relationship of high-level Cams components to Cams services.
Figure 1 - Cams services are managed and accessible only within the scope of a security domain
Cams services are registered within the service-manager element of security-domain.xml. When the enclosing security domain is loaded, each service registered with a service manager is loaded. A service is configured via it's initialize method and is given a ServiceConfig Object, which provides access to:
Services must indirectly implement the Cams service interface defined by class: com.cafesoft.core.service.Service. If a service implements interface: com.cafesoft.core.service.LifecycleService, then that service is started and stopped when the security domain is started and stopped. That gives services a way to gracefully setup and cleanup application state before and after service request can be made. The Services you write should define an interface that extends one of these two types, adding whatever business-level methods are appropriate for the service.
A service is uniquely-identified by it's String identifier, which must be unique within it's security domain. It can be looked up by this identifier or by it's type, which is defined by the Java interface class that it implements. So, if a service is implemented by class: com.mycompany.services.MyServiceImpl, which implements the Java interface: com.mycompany.services.MyService then it's service type is: com.mycompany.services.MyService.class. You'll see how the service type is used in the examples that follow.
This section contains information about the important classes and interfaces in the Cams Service API. You can also reference the Cams javadocs on com.cafesoft.core.service.
Figure 2 summarizes the relationships of various Cams Service API classes.

Figure 2 - Cams Service API class relationships
com.cafesoft.core.service.Service com.cafesoft.core.service.LifecycleService
The com.cafesoft.core.service.Service interface must be implemented either directly or indirectly by any class that is to be registered as a Cams service. If your service would benefit by being started and stopped when the Cams Policy Server and it's security domains are started and stopped, then it can implement interface com.cafesoft.core.service.LifecycleService, which includes start() and stop() methods.
com.cafesoft.core.service.AbstractService com.cafesoft.core.service.AbstractLifecycleService
These class provide default implementations for very basic Service and LifecycleService methods. You'll write less code in your service implementations if you just extend one of these classes.
com.cafesoft.core.service.ServiceConfig com.cafesoft.core.service.ServiceContext com.cafesoft.core.service.ServiceFinder
When a Service implementation is initialized, it is given a class that implements com.cafesoft.core.service.ServiceConfig. Your Service implementation can configure itself with parameters provided through this interface. ServiceConfig also provides access to an implementation of com.cafesoft.core.service.ServiceContext. This class provides access to a ServiceFinder, which enables your services to lookup and use other registered services.
com.cafesoft.core.log.Logger
ServiceContext also provides access to the Logger used by the enclosing security domain. Your service can use this Logger to report DEBUG, INFO, WARNING, ERROR, and FATAL messages that get logged to the security domain's trace logger.
This section describes how to create your own Cams service using an example service that notifies an administrator via e-mail whenever a user with the manager role logs in. Because this service must be configured with a valid SMTP mail server, it is disabled by default. See Enabling the Cams Text Notifier Service Example to configure the example for your environment. The service will be hosted under the Cams system security domain and will be accessed and used from a session event handler created using the Cams Session Event Handler API.
The general steps for creating and deploying a Cams service include:
Example 1 summarizes the java code for TextNotifierService interface example. Click here to see the full source code for TextNotifierService.java. Some important points to note about this code include:
package examples.service;
import com.cafesoft.core.service.Service;
public interface TextNotifierService extends Service
{
/**
* Send a textual message.
*
* @param from the message sender.
* @param subject the subject of the message.
* @param body the body of the textual message.
*/
public void sendText(String from, String subject, String body);
} // End of interface: TextNotifierService
|
| Example 1 - TextNotifierService interface code |
Writing a Cams service implementation class can roughly be broken into the following steps:
If your service implements a LifecycleService because it can benefit by being started and stopped, then the following steps also apply:
Example 2 shows how you can declare your Cams service implementation class to take advantage of methods in the AbstractService class that ships with Cams. This abstract class contains ready-to-use code for the most general Cams service methods. You can override the methods in your implementation class that require customization. Click here to see the full source code for class: SmtpTextNotifierService.java.
Package examples.service;
import com.cafesoft.core.service.AbstractService;
import examples.service.TextNofifierService;
public class SmtpTextNotifierService
extends AbstractService
implements TextNotifierService
{
...
}
|
| Example 2 - Declaring the SmtpTextNotifierService class |
Example 3 shows how you can initialize your Cams service implementation. In this case, the code overrides the initialize() method from AbstractService to check for required configuration parameters. Some important points within this example include:
/**
* Override the initialize method to check for required config parameters.
*
* @param serviceConfig the object through which configuration parameters and
* other resources are available.
* @exception ServiceException if one or more configuration parameters
* are missing.
*/
Public void initialize(ServiceConfig serviceConfig) throws ServiceException
{
// Initialize state in the abstract base class
super.initialize(serviceConfig);
// Complain if required parameters are missing
this.smtpHost = serviceConfig.getInitParameter("smtp.host");
if ((smtpHost == null) || (smtpHost.trim().length() == 0))
throw new ServiceException("Required parameter: 'smtp.host' " +
"is missing or empty");
this.smtpTo = serviceConfig.getInitParameter("smtp.to");
if ((smtpTo == null) || (smtpTo.trim()Length() == 0))
throw new ServiceException("Required parameter: 'smtp.to' " +
"is missing or empty");
}
|
| Example 3 - Initializing the SmtpTextNotifierService |
Example 4 shows the code that implements the sendText() business method for SmtpTextNotifierService. Important issues to note include:
/**
* Send a textual message.
*
* @param from the message sender.
* @param subject the subject of the message.
* @param body the body of the textual message.
*/
Public void sendText(String from, String subject, String body)
{
// Sending an SMTP message is slow, so create a new Thread and
// send the message asynchronously.
SmtpClientRunnable r = new SmtpClientRunnable(from, subject, body);
Thread t = new Thread(r);
t.start();
}
//
// inner classes
//
/**
* SmtpClientRunnable implements a Runnable class
* for asynchronously sending a message to an SMTP server.
*/
private class SmtpClientRunnable implements Runnable
{
private String from;
private String subject;
private String body;
/**
* Create a new ManagerLoginNofifierRunnable.
*
* @param from the message sender.
* @param subject the subject of the message.
* @param body the body of the textual message.
*/
Public SmtpClientRunnable(String from, String subject, String body)
{
this.from = from;
this.subject = subject;
this.body = body;
}
//
// implementing Runnable
//
/**
* Send the text message via SMTP.
*/
Public void run()
{
try
{
if (debug)
logger.debug(this, "Connecting to SMTP server=" +
smtpHost +
", from=" + from +
", to=" + smtpTo +
", subject=" + subject);
// Setup the mail transport host
SmtpClient c = new SmtpClient(smtpHost, false);
c.from(from);
c.to(smtpTo);
// Send the message
PrintStream ps = c.startMessage();
ps.print("To: " + smtpTo + "\n");
ps.print("From: " + from + "\n");
ps.print("Subject: " + subject + "\n");
ps.print("X-Client: com.cafesoft.core.smtp.SmtpClient" + "\n");
ps.print("\n");
ps.print(body);
ps.print("\n");
// Close connection with the SMTP server
c.closeServer();
if (debug)
logger.debug(this, "Successfully sent text message");
}
catch (UnknownHostException e)
{
logger.error(this, "Unknown SMTP server host=" + smtpHost, e);
}
catch (UnknownUserException e)
{
logger.error(this, "Unknown message recipient=" + smtpTo, e);
}
catch (IOException e)
{
logger.error(this, "Error sending message", e);
}
}
} // End of class: SmtpClientRunnable
|
| Example 4 - Implementing the SmtpTextNotifierService business method |
The Cams service destroy() method is responsible for cleaning up any resources referenced by a Cams service before it is destroyed. It will be invoked by the Cams service manager when the enclosing security domain's service manager is destroyed. This occurs when the Cams Policy Server exits and when the enclosing security domain is destroyed. Example 5 shows the destroy method for SmtpTextNotifierService. Here are some important points to note within this example:
/**
* Destroy the service.
*/
Public void destroy()
{
// null local Object references.
this.smtpHost = null;
this.smtpTo = null;
// Invoke the super class destroy method.
super.destroy();
}
|
| Example 5 - Destroying the SmtpTextNotifierService |
In some cases, it may be useful to implement a Cams service as a LifecycleService. This interface includes start() and stop() methods that are invoked when the enclosing security domain's service manager is started and stopped. Implementing these method gives your service the opportunity to:
The SmtpTextNotifierService example is not a LifecycleService, so it does not implement start and stop methods, but you can see an example in StandardRdbmsService.java.
The following commands show how you can compile your Cams services classes and deploy them to a directory where the Cams policy server will automatically find and load them. The examples assume that environment variable CAMS_HOME is defined and points to the camsServer directory within the Cams distribution.
Unix: javac -classpath .;$CAMS_HOME/lib/cams.jar:$CAMS_HOME/lib/cscore.jar -d $CAMS_HOME/classes *.java
Windows: javac -classpath .;%CAMS_HOME%\lib\cams.jar;%CAMS_HOME%\lib\cscore.jar -d %CAMS_HOME%\classes *.java
You'll need to shutdown and restart the Cams Policy Server for changes to take effect. For production use, you may want to jar your components, drop them in directory $CAMS_HOME/lib, and modify script: runcams.sh or runcams.bat to add the jar file to the Cams Policy Server's classpath.
Example 6 shows how you might register and configure this service for use by other custom components (or other services) within a security domain. This service can be looked up by it's identifier: email-text-notifier-service and/or by it's type: com.myco.message.TextNotifier.
<service id="email-text-notifier-service" enabled="true"> <service-type>com.myco.message.TextNotifierService</service-type> <service-class>com.myco.message.SmtpTextNotifierService</service-class> <param-list> <param name="smtp.host" value="mycompany.com"> <param name="smtp.to" value="security-admin@mycompany.com"> </param-list> </service> |
| Example 6 - Registering the email-text-notifier-service within a security domain |
The Cams services deployed within the service manager of a security domain can be found and invoked by Cams LoginModules, Access Control Rules, Managed Session Event Handlers, and other Cams Services within that security domain. Cams services are looked up via a ServiceFinder object, which is made available to Cams components.
Cams supports JAAS-compatible LoginModules. For sake of performance and scalability, some of the LoginModules shipped with Cams (e.g. XmlLoginModule) also implement a Cams interface that enables them to make use of Cams services. The XmlLoginModule makes use of an UserRepository Cams service, which allows Cams to authenticate users using cams-users.xml as it's user repository. Example 7 shows the code that causes LoginModules to be given a ServiceFinder before any other LoginModule methods are invoked. The important points in this code include:
import javax.security.auth.spi.LoginModule;
import com.cafesoft.core.service.Service;
import com.cafesoft.core.service.ServiceClient;
import com.cafesoft.core.service.ServiceException;
import com.cafesoft.core.service.ServiceFinder;
public class XmlLoginModule implements LoginModule, ServiceClient
{
...
/** Used to lookup Cams services. */
private ServiceFinder serviceFinder;
...
//
// implementing ServiceClient
//
/**
* Set the ServiceFinder.
*
* @param serviceFinder the class used to find Cams Services
*/
public void setServiceFinder(ServiceFinder serviceFinder)
{
this.serviceFinder = serviceFinder;
}
...
}
|
| Example 7 - Implementing the ServiceClient interface in a Cams LoginModule |
Cams access control rules and managed session event handers are initialized with a com.cafesoft.cams.Config Object that references a com.cafesoft.cams.Context Object. A ServiceFinder is available from the Context Object as shown in Example 8. Of course, you don't really need to save a reference to the Context and ServiceFinder Objects because they are always available from the Config Object.
import com.cafesoft.core.Config;
import com.cafesoft.core.Context;
import com.cafesoft.core.ConfigException;
import com.cafesoft.core.service.Service;
import com.cafesoft.core.service.ServiceClient;
import com.cafesoft.core.service.ServiceException;
import com.cafesoft.core.service.ServiceFinder;
public void initialize(Config config) throws ConfigException
{
this.config = config;
this.context = config.getContext();
this.serviceFinder = context.getServiceFinder();
...
}
|
| Example 8 - Getting a ServiceFinder in AccessControlRule and ManagedSessioEventHandlers |
Cams services are initialized with a com.cafesoft.core.service.ServiceConfig Object that references a com.cafesoft.core.service.ServiceContext Object. A ServiceFinder is available from the ServiceContext Object as shown in Example 9.
import com.cafesoft.core.service.ServiceConfig;
import com.cafesoft.core.service.ServiceContext;
import com.cafesoft.core.service.Service;
import com.cafesoft.core.service.ServiceClient;
import com.cafesoft.core.service.ServiceException;
import com.cafesoft.core.service.ServiceFinder;
public void initialize(ServiceConfig serviceConfig) throws ServiceException
{
this.serviceConfig = serviceConfig;
this.serviceContext = serviceConfig.getServiceContext();
this.serviceFinder = serviceContext.getServiceFinder();
...
}
|
| Example 9 - Getting a ServiceFinder in a Cams Service |
Once you've got a ServiceFinder, finding and using a Cams service is easy. The ServiceFinder provides multiple ways to find a Service instance:
Example 10 shows how you can find and invoke all Service instances by a specific type. Important points to note include:
import com.cafesoft.core.service.Service;
import com.cafesoft.core.service.ServiceException;
import com.cafesoft.core.service.ServiceFinder;
import examples.session.TextNotifierService;
...
try
{
// Lookup the TextNotifierService instance(s) by type
Service[] serviceArray = serviceFinder.find(TextNotifierService.class);
// Send the message to every available TextNotifierService
String body = createMessageBody(event);
for (int i = 0; i < serviceArray.length; i++)
{
TextNotifierService tns = (TextNotifierService)serviceArray[i];
tns.sendText(fromAddress, msgSubject, body);
}
}
catch (ServiceException e)
{
logger.error(this, "Unable to find TextNotifierService");
}
...
|
| Example 10 - Finding and using all Cams Service by type |
Example 11 shows how you can find and invoke a specific Service instances by a specific type and identifier. Important points to note include:
import com.cafesoft.core.service.Service;
import com.cafesoft.core.service.ServiceException;
import com.cafesoft.core.service.ServiceFinder;
import examples.session.TextNotifierService;
...
try
{
// Lookup the TextNotifierService instance(s) by type and ID
TextNotifierService tns = (TextNotifierService)serviceFinder.find(
"email-text-notifier-service", TextNotifierService.class);
// Send the message to the TextNotifierService
String body = createMessageBody(event);
tns.sendText(fromAddress, msgSubject, body);
}
catch (ServiceException e)
{
logger.error(this, "Unable to find TextNotifierService " +
"with id=email-text-notifier-service");
}
...
|
| Example 11 - Finding a Cams Service instance by identifier and type |
Example 12 shows how you can find and invoke a specific Service instances by identifier only. Important points to note include:
import com.cafesoft.core.service.Service;
import com.cafesoft.core.service.ServiceException;
import com.cafesoft.core.service.ServiceFinder;
import examples.session.TextNotifierService;
...
try
{
// Lookup the TextNotifierService instance(s) by type and ID
Service service = serviceFinder.find("email-text-notifier-service");
// Make sure it implements TextNotifierService
if (!(service instanceof TextNotifierService))
{
logger.error(this, "The Service with id=email-text-notifier-service " +
"does not implement type: " + TextNotifierService.class.getName());
return;
}
// Send the message to the TextNotifierService
String body = createMessageBody(event);
((TextNotifierService)service).sendText(fromAddress, msgSubject, body);
}
catch (ServiceException e)
{
logger.error(this, "Unable to find TextNotifierService " +
"with id=email-text-notifier-service");
}
...
|
| Example 12 - Finding a Cams Service instance by identifier |
In order for the service to be loaded, you must restart the Cams policy server if it is currently running. You should use the shutdown.bat/shutdown.sh command in the bin directory of Cams to shutdown the server. Then, use runcams.bat/runcams.sh to start it.
Because the Cams Text Notifier Service example requires a valid SMTP mail server specific to your environment, it is disabled by default. To configure it for use, navigate to the examples security domain configuration files and open security-domain.xml in a text editor:
Linux/UNIX:
cd $CAMS_HOME/conf/domains/examples
vi security-domain.xmlWindows
CD %CAMS_HOME%\conf\domains\examples
notepad security-domain.xml
You'll need to edit the file in two distinct XML blocks as shown in Example 13 and described below.
<session-manager-service>
...
<!-- Example of a session-event-handler that sends an email notification
when user "manager" logs in. You must also enable and configure the
TextNotifierService for this example to work.
-->
<session-event-handler
className="examples.service.ManagerLoginNotifier">
|
| Example 13 - Enabling the Cams Text Notifier Service example in security-domain.xml |
First, you must uncomment the ManagerLoginNotifier session-event-handler found in the session-manager-service enclosing elements. To do this, simply move the end comment "-->" from its default position after the ManagerLoginNotifier session-event-handler element to preceding it as shown in red in Example 13. You may optionally change the email fromAddress and msgSubject if you like.
Second, navigate to the service-manager element and find the email-text-notifier-service. You must enable the service by setting the enabled flag to true as show in red in Example 13. You MUST also configure the stmp.host and stmp.to parameters with values specific to your environment. The smtp.host is the IP or DNS name of the SMTP mail server and the stmp.to is the email address to which you'll send the message.
In order for the service to be loaded, you must restart the Cams policy server if it is currently running. You should use the shutdown.bat/shutdown.sh command in the bin directory of Cams to shutdown the server. Then, use runcams.bat/runcams.sh to start it.
© 1996-2005 Cafésoft LLC. All rights reserved.