Skip Headers

Oracle® Application Server Syndication Services Developer’s and Administrator’s Guide
10g (9.0.4)
Part No. B10667-01
Go To Documentation Library
Home
Go To Product List
Solution Area
Go To Table Of Contents
Contents
Go To Index
Index

Previous Next

4 Content Connector Development

Content connectors (also known as Content Provider Adaptors or CPAdaptors) are software components used by Syndication Services to interact with external content repositories. Developers can create a content connector to expose resources of a repository as offers open for subscriptions.

Content connectors are developed using the Java programming language. To develop a content connector, you have to build a JavaBean, which implements the CPAdaptor interface in the oracle.syndicate.server.cp package. The following sections describe the code sequence of a sample content connector, which takes as input an RSS (Really Simple Syndication) feed (content source) and makes it a resource to which users can subscribe.

A set of demonstration files, including the full source file of this example, can be downloaded from the Oracle Technology Network (OTN) Web site http://otn.oracle.com/

A complete listing of the RSSCPAdaptor.java program, parts of which are described in this chapter, can be found in Section D.1. The API documentation that describes how to use the Syndication Services API for developing a content connector can be found on the Oracle Application Server 10g Documentation Library as Oracle Application Server Syndication Services API Reference (Javadoc) under Oracle Application Server Portal.

This chapter describes the following topics:

4.1 Developing a Content Connector (CPAdaptor)

From a Java programming perspective, content connectors are JavaBean classes that implement the CPAdaptor interface in the oracle.syndicate.server.cp package. Content connector responsibilities include the following:

The oracle.syndicate.server.cp.CPAdaptor interface defines the set of APIs to be implemented by content connectors. Content connectors should be designed as stateless java classes whose only state information is determined by the values of their properties, and such values are determined by the administrator during the process of a content provider registration. The state is also immutable (cannot be changed) by the connector because the properties values can be set or modified through the administrator interfaces, and must not be altered by the connector during its operations. In summary, CPAdaptor classes must be carefully designed for a multithreaded environment, for example, multiple content package-building requests can be sent to the content provider concurrently.

The APIs are described by using a sample content connector, which reads an RSS feed and creates a resource for it. For more information regarding RSS and its specification, please refer to http://backend.userland.com/rss

The sample connector will create a content package item for every item defined in the RSS feed.

The sections that follow describe each stage of the content connector development process:

4.1.1 Exposed Content Connector Properties

Content connectors can expose some of their settings as customizable properties. When building a generic content connector for a whole class of external content repository properties, exposing some of their settings can be used to customize a content provider instance to connect to one specific repository. In this respect, the content connector can be seen as a driver for a type of content management system, and its properties as the connection parameters to a specific repository instance.

The sample content connector described in Example 4-1 exposes only one property, which identifies the URL of the RSS feed to be exposed as a resource. The JavaBean BeanInfo classes can also be supplied to filter the set of properties exposed, and to add a property display name and description.

Example 4-1 Exposing a Content Connector Property

public class RSSCPAdaptor 
  implements CPAdaptor, OSSExceptionConstants
{
  private static final String LAST_BUILD_DATE_FORMAT = "EEE, dd MMM yyyy H:mm:ssz";

  private String     _rssurl;
  private CPContext _cpctx;

  public RSSCPAdaptor()
  {}

  /**
   * Returns the URL of the RSS feed.
   */
  public String getRSSURL()
  {
    return _rssurl;
  }

  /**
   * Sets the URL of the RSS feed.
   */
  public void setRSSURL(String rssurl)
  {
    _rssurl = rssurl;
  }

4.1.2 Initialized Content Connector

During the initialization of a content connector, Syndication Services builds an instance of the class implementing the CPAdaptor interface, in this example of the RSSCPAdaptor class, and sets its properties according to the values specified by the administrator during content provider registration.

After the instantiation and restoration of the bean state, the CPAdaptor.init method is called to supply an instance of the CPContext object to the content connector. The CPContext object provides the content connector instance with a set of information that will be useful throughout the duration of the content connector, (see Example 4-2). Such information includes the CPMessageFactory object, and which CPAdaptor it must use to build data structures that the content connector has to process and return. The content connector is expected to store a reference to the supplied CPContext object into one of its fields.

The ping method may be used by the Syndication Services administration code to verify if the content connector has been correctly configured and if it is ready for operation. This RSS content connector could, for example, verify that the RSS URL is active, and correctly return an RSS feed.

Example 4-2 Initializing the Content Connector

/**
   * Receives and stores the CPContext object from which 
   * you can access the CPMessageFactory object.
   */
  public void init(CPContext cpctx)
    throws CPException
  {
    _cpctx = cpctx;
  }

  public boolean ping()
    throws CPException
  {
    return true;
  }

4.1.3 Exposed List of Resources

Once a content connector has been installed in the system and one or more content providers have been registered using this content connector, Syndication Services administrators may decide to expose one or more of the content provider resources as offers. To do this, administrators will go through the creation offer wizard and select the content provider resource they want to expose as an offer.

Content connectors expose the list of resources they would like to make available for offer creation through the CPAdaptor.buildCatalog API. The content connector will use the CPMessageFactory object to build a list of CPOffer objects, set the offer metadata, and return an iterator over the list of available resources. Example 4-3 shows how a CPOffer is built from an RSS feed. Some of the feed metadata is used to provide defaults for the resource properties. These properties can be reviewed and edited by the administrator at offer creation time.

Example 4-3 Building a List of CPOffer Objects from an RSS Feed

  public Iterator buildCatalog()
    throws CPException
  {
    Document docrss = parseRSS();
    Element   elrss = docrss.getDocumentElement();
    NodeList  nlchs = elrss.getElementsByTagName("channel");
    
   // Loop over the channels defined in this RSS
   // and for each one create an offer.
    CPMessageFactory cpmf = _cpctx.getCPMessageFactory();
    ArrayList      alOffs = new ArrayList();
    for (int i=0; i<nlchs.getLength(); i++) {

      // For each channel, you will create 
      // a CPOffer object. CPOffer objects are shown to 
      // syndication administrators as content
      // provider resources from which you can build offers.
      Element  elCh = (Element) nlchs.item(i);
      CPOffer cpoff = cpmf.createCPOffer();
      
      // Each offer must have a unique ID for its content provider.
      // Set the mandatory ID attribute and any other 
      // optional attributes.
      String title = getChildElementValue(elCh, "title");
      if ((title == null) || (title.trim().length() == 0)) {
        title = "unknown";
      }

      // Set the offer properties.
      cpoff.setCPOfferID(title);
      cpoff.setName(title);
      cpoff.setDescription(getChildElementValue(elCh,  "description"));
      cpoff.setRightsHolder(getChildElementValue(elCh, "copyright"));
      cpoff.setProductName("OracleAS Syndication Services RSS Feed Import");
      cpoff.setAtomicUse(false);
      cpoff.setEditable(true);
      cpoff.setShowCredit(false);
      cpoff.setUsageRequired(false);
      
      alOffs.add(cpoff);
    }

    return alOffs.iterator();
  }

Each returned offer must have an offer ID set through the setCPOfferID method.

4.1.4 Content Packages Built

If a user (subscriber) subscribes to an offer, content updates will be delivered. Content updates are built by calling the buildPackage method of the connector associated with the offer for which the content is requested. The buildPackage method takes in the following two parameters:

  • CPPackageRequest: This method provides accessors for parameters related to this content package request. These parameter accessors include the ID of the content provider resource (CPOfferID) for which the content package is requested, (this is the same ID set by the content connector in the catalog building using the CPOffer.setCPOfferID method), the ID of the subscriber requesting the content package update, and the ID of the subscription associated with this content package request. This method also includes the parameters supplied by the user for this content package request, if any. The content connector will use this set of parameters to determine the resource updates.

  • CPRequestContext: This method can be used by the content connector to store resources that it had to reserve or allocate, or both, to build the returned CPPackage object. Such resources can be released in the closePackage package, when the same CPRequestContext instance will be resupplied to the content connector after the content package has been transmitted.

Example 4-4 shows how the sample RSSCPAdaptor class builds a content package. The content connector reads the RSS feed following the URL with which it has been configured. The content connector will then create a CPItem object for each RSS news item found in the feed. The CPItem content is accessible through ContentAccessors methods.

The CPPackage instances and CPItem instance must be created using the CPMessageFactory object supplied to the content connector through the CPContext object. The ContentAccessors methods are simple interface wrappers over Java InputStreams returning the item content. The CPMessageFactory object includes a few factory methods for the most common ContentAccessors interface (including the StringContentAccessor method used in the example). Developers can write their own implementations of the ContentAccessors interface, if needed.

Example 4-4 Building a Content Package

  public CPPackage buildPackage(CPPackageRequest req,
                                CPRequestContext ctx)
    throws CPException
  {
    // Parse the RSS document.
    Document docrss = parseRSS();
    Element   elrss = docrss.getDocumentElement();
    Element    elCh = getChannelElement(elrss, req.getOfferID());

    // Create a new content provider package and initialize
    // its properties by using RSS feed values.
    CPMessageFactory cpmf = _cpctx.getCPMessageFactory();
    CPPackage  pkg = cpmf.createCPPackage();

    // Check if the RSS feed has been modified since the
    // the last content package request.
    String oldState = req.getState();
    String newState = getChildElementValue(elCh, "lastBuildDate");

    Date dNewState = getDate(newState);
    Date dOldState = null;
    if (!oldState.equals(INITIAL_STATE)) {
      dOldState = getDate(oldState);
      if ((dOldState != null) && 
          (dNewState != null) && 
          dOldState.equals(dNewState)) {
          
        // The RSS feed has not been updated
        // since the last visit. Throw an exception
        // notifying that the content package sequence is
        // up-to-date and no update needs to be reported.
        throw new CPException(CP_PACKAGE_UP_TO_DATE);
      }
      // This is an incremental update over
      // the previous update that was sent.
      // Set the full update flag accordingly.
      pkg.setFullUpdate(false);
    }
    else {

      // This is a request for a full new update.
      // Set the full update flag accordingly.
      pkg.setFullUpdate(true);
    }
    pkg.setOldState(oldState);
    pkg.setNewState(newState);
    
    // Scan through the RSS news items building 
    // corresponding CPItem objects.    
    NodeList nlItems = elCh.getElementsByTagName("item");
    for (int i=0; i<nlItems.getLength(); i++) {

      Element elItem = (Element) nlItems.item(i);
      CPItem cpi = cpmf.createCPItem();

      // Sets item file name and content type.
      // Each RSS news item will have a dummy file name
      // and an XML code excerpt for the associated news.
      String itemFilename = "item"+i+".xml";
      cpi.setContentFilename(itemFilename);
      cpi.setContentType("text/xml");

      // Sets the Item ID.
      String guid = getChildElementValue(elItem, "guid");
      if (guid != null) {
        cpi.setID(guid);
      }

      // Sets the Item name.
      String title = getChildElementValue(elItem, "title");
      if (title != null) {
        cpi.setName(title);
      }
      else {
        // The Item name is mandatory
        // so you need to set it
        // to a value.
        cpi.setName(itemFilename);
      }

      // Sets the Item Activation Date, if any.
      String pubDate = getChildElementValue(elItem, "pubDate");
      if (pubDate != null) {
        Date dPubDate = getDate(pubDate);
        cpi.setActivation( new ISODateTime(dPubDate));
      }

      // Sets the Item content to the RSS Item XML element.
      try {
        StringWriter sw = new StringWriter();
        ((XMLElement) elItem).print( new PrintWriter(sw));
        ContentAccessor ca = cpmf.createContentAccessor(sw.toString());
        cpi.setContent(ca);
      }
      catch(Exception e) {
        throw new CPException(CP_GENERIC_ERROR, e, e);
      }

      // Add the Item to the content package.
      pkg.addPackageItem(cpi);
    }

    return pkg;
  }

  public void closePackage(CPRequestContext ctx)
    throws CPException
  {
  }

The following properties of the CPPackage and its CPPackageItem objects are mandatory and must be set by developers in order for the returned content package to be delivered:

  • CPPackage.oldstate and CPPackage.newstate: These properties specify the state of the subscription associated with this resource before and after delivery of this content package.

  • CPItem.ID and CPItem.name: The ID property uniquely identifies the item in the scope of the content package; the named property is a logical name. Developers should also consider setting the CPItem.subscriptionElement property, which uniquely identifies the item within the duration of the content provider resource and its associated subscriptions.

  • CPItem.contentFilename: The content file name property specifies the relative destination of the item, that is, relative to the subscription root, in which the subscription root is defined as a logical directory within which the subscriber will place all file-based content received from the syndicator for that subscription. Paths stated in the content file name must be represented using forward slashes as a path element delimiter.

  • CPItemRef.url: The URL property is to be used by the end user to retrieve the content.

  • CPItemGroup.ID: The ID property uniquely identifies the group within the scope of the containing content package.

  • CPItemRemove.subscriptionElement: This property uniquely identifies the item within the duration of the content provider resource and its associated subscriptions.


    Note:

    Propagation of deleted items -- Syndication Services supports the propagation of a deleted item if the content connector that is used supports item removal notification. This can be accomplished by creating CPItemRemove objects during the Content Package build operation (see Section 4.1.4 and the API documentation).

    When a content provider is registered using a content connector that cannot detect deleted items, removal of any item at the content source will never be revealed to the subscriber and as an effect, such items will be retained on the subscriber site.

    For example, using Oracle Application Server Portal as a content source for a content provider (configured on the portal page using a WebDAV content connector), the update of an image item will be seen as the deletion of the old item and the creation of a new one. Because the item deleted will not be propagated to the subscriber, both the old as well the new images will be retained in the subscription.

    As a workaround, the content connector can be improved (through custom development using the CPItemRemove class) to support item removal notification in the package creation.


4.1.5 Incremental Updates

When receiving requests for a new content package update, content providers have the option of returning only the set of items that have been added or modified since the last delivery. To perform incremental updates, the content connector APIs allow for setting a state token that identifies the current state of the resource. When the user requests a content update, the resource’s current state will be supplied to the content provider, which will use that state to compute the set of updates to be delivered. The content provider can then deliver the new state identifier in the returned content package.

For example, in a simple implementation, a content provider can decide to store the date of the update as a state identifier. When a content package request is received, the content provider can retrieve the subscription current state of the user by using the CPPackageRequest.getState() method, cast it to a date, and then scan the resource to find all the items that have been modified since that date. Then, after completing the scan, building the content package, and returning that package, the content provider will set the state (CPPackage.setNewState) to the current date and time.

The sample RSSCPAdaptor class uses a similar approach but refers to the RSS feed last modification date as a state identifier. Example 4-5 shows the following state management logic under the buildPackage code, and also shows how to handle the special INITIAL_STATE state identifier and the property settings of the fullUpdate flag for the content package.

Example 4-5 Performing Incremental Updates

    // Check if the RSS feed has been modified since the
    // the last content package request.
    String oldState = req.getState();
    String newState = getChildElementValue(elCh, "lastBuildDate");

    Date dNewState = getDate(newState);
    Date dOldState = null;
    if (!oldState.equals(INITIAL_STATE)) {

      dOldState = getDate(oldState);
      if ((dOldState != null) && 
          (dNewState != null) && 
          dOldState.equals(dNewState)) {
          
        // The RSS feed has not been updated
        // since the last visit. Throw an exception
        // notifying that the package sequence is
        // up-to-date and no update needs to be reported.
        throw new CPException(CP_PACKAGE_UP_TO_DATE);
      }
      // This is an incremental update over
      // the previous update that was sent.
      // Set the full update flag accordingly.
      pkg.setFullUpdate(false);
    }
    else {

      // This is a request for a full new update.
      // Set the full update flag accordingly.
      pkg.setFullUpdate(true);
    }
    pkg.setOldState(oldState);
    pkg.setNewState(newState);

4.1.6 Content Provider Closed

Content connector instances are cached by Syndication Services. When the property value of a content provider is changed by the administrator, the cached instance copy of the content provider is released by the infrastructure database and a new content provider instance is created and configured with the new property values. When releasing a content provider instance, the CPAdaptor.close method is called. Developers can use this method to release any resource acquired when using the init method.

4.1.7 Content Provider Event Push Support

When new content is available from a content provider, an event can be triggered to immediately push this new content to all subscriptions associated with the resource of that content provider. Section 4.1.7.1 is an overview of content provider event push support, and Section 4.1.7.2 describes the event syntax support.

4.1.7.1 Overview

In Syndication Services, content providers can raise an event when a new version of their content is available. This feature complements the time-based content updates described in Section 2.5.4. The conditions that trigger the raising of such an event are specific to each content provider, but they generally imply that a new version of the content associated with a content provider resource is available for delivery. Once such an event is raised, Syndication Services will push a content update to all eligible subscriptions.

The following list shows a possible scenario for a content provider event-based push:

  1. The Syndication Services administrator creates an offer offA for a resource rsrcB of content provider cpA.

  2. The administrator creates a contract conA, which enables the push delivery rule. A contract conA defines a time period duration of 24 hours for the push delivery rule, and specifies 0 (zero) as the number of updates. In this way, any time a push event is raised by the content provider, there will not be a time period violation, and therefore any event from the content provider will result in a content push to the subscriber.

  3. The administrator also grants the offer offA to subscriber sbbU using contract conA.

  4. The subscriber sbbU subscribes to an offer offA and gets a new subscription sbtE.

  5. The content provider cpA raises a content provider event for its resource rsrcB.

  6. Syndication Services pushes a content update to all subscriptions associated with the resource rsrcB of content provider cpA, including the new subscription sbtE.

4.1.7.2 Event Syntax

For Oracle Application Server release 10g (9.0.4) and later for the Portal and Wireless mid-tier installation type, a Syndication Services content provider event servlet is installed by default. The URL end point is

http://<host>:<port>/syndserver/cpevent

The interface Syndication Services provides is based on an HTTP GET request. The syntax is described as follows:

http:// <host>:<port>/syndserver/cpevent?cp-id=XXX&cp-offer-id=YYY

where XXX is the cp-id value and YYY is the cp-offer-id value.

Content providers, by providing the content provider ID (cp-id) and content provider offer ID (cp-offer-id), can trigger push delivery to all subscriptions that are based on a content provider offer with the given content provider offer ID cp-offer-id from the content provider with the given content provider ID cp-id.

For example, if the cp_id is 801 and the cp_offer_id is fcpatest, the URL would appear as:

http://<host>:<port>/syndserver/cpevent?cp-id=801&cp-offer-id=fcpatest

As an example, using the scenario described in Section 4.1.7.1, the following HTTP GET request will trigger a content provider event push to all subscriptions, including subscription sbtE, associated with rsrcB of content provider cpA:

http://syndication_services_host: syndication_services_port/syndserver/cpevent?cp-id=cpA&cp-offer-id=rsrcB

Alternatively, an event can be sent to Syndication Services to push content updates for a list of subscription IDs. The syntax is described as follows:

http:// <host>:<port>/syndserver/cpevent?sbt-ids=XX1,XX2,...

where XX1 is the first subscription ID, XX2 is the second subscription ID, and so forth. For example:

http://<host>:<port>/syndserver/cpevent?sbt-ids=C5E5702058205CC0E0340003BA1906A5,C5609B440D656B40E0340003BA1906A5

4.2 Managing Content Connectors

Section 4.2.1 describes how to package and install a new content connector, Section 4.2.2 describes how to list currently installed content connectors, and Section 4.2.3 describes how to uninstall a content connector.

4.2.1 Installing a New Content Connector

Content connectors are developed using the Java programming language. To develop a content connector, you have to build a JavaBean, which implements the CPAdaptor interface in the oracle.syndicate.server.cp package. After the coding has been completed, the content connector code can be compiled and prepared for installation. To compile a content connector class, make sure you include the syndserver.jar library in the classpath. The syndserver.jar library includes the definition of all the interfaces associated with the development of a content connector. For example, to compile the RSSCPAdaptor.java program, you can issue the command shown in Example 4-6.

Example 4-6 Compiling the RSSCPAdaptor.java File

On UNIX
javac -classpath ${ORACLE_HOME}/lib/xmlparserv2.jar:${ORACLE_HOME}/syndication/lib/syndserver.jar RSSCPAdaptor.java

On Windows
javac -classpath <ORACLE_HOME>\lib\xmlparserv2.jar:<ORACLE_HOME>\syndication\lib\syndserver.jar RSSCPAdaptor.java

It is recommended that content connector developers perform some unit test of the content connector before installing it in the Syndication Services system. A way to perform a unit test of the content connector is to add a main method in the content connector class, which calls the buildCatalog and buildPackage APIs. The sample RSSCPAdaptor.java program shows how this can be achieved. Once the content connector code is compiled, build a JAR file containing the compiled classes. Example 4-7 shows the command that builds a JAR file for the RSS content connector compiled previously in Example 4-6.

Example 4-7 Building an rsscp.jar File

jar cvf rsscp.jar *.class

The created rsscp.jar file should be copied into a directory where the Syndication Services runtime and administration components can find and load the library when necessary. The default location for content connector libraries is ${ORACLE_HOME}/syndication/lib/cp on UNIX systems or <ORACLE_HOME>\syndication\lib\cp on Windows systems. Any other library that the connector may depend on, if not already in the Syndication Services classpath, should be copied to this directory. The next step is to copy the JAR file; in a UNIX or Windows environment, the command would be as shown in Example 4-8.

Example 4-8 Copying the JAR File to the Syndication Services Load Directory

On UNIX
cp rsscp.jar ${ORACLE_HOME}/syndication/lib/cp

On Windows
cp rsscp.jar <ORACLE_HOME>\syndication\lib\cp

The new content connector can now be installed in Syndication Services. To complete this step, the command shown in Example 4-9 is executed.

Example 4-9 Installing the New Content Connector in Syndication Services

On UNIX
java -DORACLE_HOME=${ORACLE_HOME} -jar ${ORACLE_HOME}/syndication/lib/syndserver.jar -installConnector -name "RSSConnector" -description "Connector exposing RSS feeds as syndication offers" -className RSSCPAdaptor

On Windows
java -DORACLE_HOME=<ORACLE_HOME> -jar <ORACLE_HOME>\syndication\lib\syndserver.jar -installConnector -name "RSSConnector" -description "Connector exposing RSS feeds as syndication offers" -className RSSCPAdaptor


Note:

On a Solaris operating system, make sure that the LD_LIBRARY_PATH environment variable is set to <ORACLE_HOME>/lib before you execute the Java commands shown in Example 4-9.

A restart of the Syndication Services runtime component (that is, a restart of the OC4J_Portal instance where the Syndication Services application is deployed) and a restart of the administration component (that is, a restart of Oracle Enterprise Manager) is necessary to let these components detect the new library. Once the content connector has been properly installed and the OC4J containers for the Syndication Services runtime and administration components have been restarted, content providers based on the new content connector can be registered. See Section 2.4 for more information about the content provider registration process.

4.2.2 Listing Installed Content Connectors

The syndserver.jar library provides a few command line options to show the list of content connectors currently installed in the system and, eventually, uninstall them. Example 4-10 shows the command that returns the list of content connectors that are currently installed for Syndication Services.

Example 4-10 Listing the Installed Content Connectors

On UNIX
java -DORACLE_HOME=${ORACLE_HOME} -jar ${ORACLE_HOME}/syndication/lib/syndserver.jar -listConnectors 

On Windows
java -DORACLE_HOME=<ORACLE_HOME> -jar <ORACLE_HOME>\syndication\lib\syndserver.jar -listConnectors 

 Installed CPAs
----------------
ID   : 1
Name : FileConnector
Class: oracle.syndicate.server.cp.impl.file.FileCPAdaptor
Description: Content Provider Adaptor fetching content from a file system.

ID   : 2
Name : WebDAVConnector
Class: oracle.syndicate.server.cp.impl.dav.DAVCPAdaptor
Description: Content Provider Adaptor fetching content from repositories
 accessible through the WebDAV protocol.

ID   : 3
Name : RSS Connector
Class: RSSCPAdaptor
Description: Content Connector for RSS feeds

4.2.3 Uninstalling a Content Connector

To uninstall a content connector, first check to make sure that no content providers that use this content connector are currently registered. If there are some registered content providers that use this content connector, each must be deleted. Once this condition is met, the content connector can be uninstalled using the command shown in Example 4-11 by supplying the connector ID.

Example 4-11 Uninstalling the Content Connector Whose Connector ID Is 3

On UNIX
java -DORACLE_HOME=${ORACLE_HOME} -jar ${ORACLE_HOME}/syndication/lib/syndserver.jar -uninstallConnector 3

On Windows
java -DORACLE_HOME=<ORACLE_HOME> -jar <ORACLE_HOME>\syndication\lib\syndserver.jar -uninstallConnector 3