Event Filtering

If you would like to alter the default publish/subscribe behavior, COM+ provides a mechanism called event filtering. There are two kinds of filtering. The first, publisher filtering, lets you change the way events are published and therefore affect all the subscribers for an event class. The second, subscriber filtering, affects only the subscriber using that filter.

Both kinds of filters usually let you filter events without changing the publisher or the subscriber code. However, I find that event filtering is either cumbersome to use and implement, or limited and incomplete in what it offers. Those shortcomings are mitigated by the use of the COM+ Catalog wrapper object.

Publisher Filtering

Publisher filtering is a powerful mechanism that gives the publisher fine-grained control over event delivery. You can use a filter to publish to only certain subscribers, control the order in which subscribers get an event, and find out which subscribers did not get an event or had encountered an error processing it. The publisher-side filter intercepts the call the publisher makes to the event class, applies filtering logic on the call, and performs the actual publishing (see Figure 9-8).

A publisher filter

Figure 9-8.  A publisher filter

If you associate a filter with an event class, all events published using that class go through the filter first. You are responsible for implementing the filter (you will see how shortly) and to register it in the COM+ Catalog. The publisher filter CLSID is stored in the COM+ Catalog as a property of the event class that it filters. At any given time, an event class has at most one filter CLSID associated with it. As a result, installing a new filter overrides the existing one.

When a publisher fires events on the event class, COM+ creates the publisher object and lets it perform the filtering.

Implementing a publisher filter

A publisher-side filter is a COM object that implements an interface called IMultiInterfacePublisherFilter . The filter need not necessarily be a COM+ configured component. The filter interface name contains the word Multi because it filters all the events fired on all the interfaces of the event class. Another interface, called IPublisherFilter , allows you to associate a filter with just one sink interface supported by an event class. It is still mentioned in the documentation, but has been deprecated (i.e., don’t use it).

The definition for IMultiInterfacePublisherFilter is:

interface IMultiInterfacePublisherFilter : IUnknown 
{
   HRESULT Initialize([in]IMultiInterfaceEventControl* 
                      pMultiInterfaceEventControl);

   HRESULT PrepareToFire([in]IID* piidSink,[in]BSTR bstrMethodName,
                         [in]IFiringControl* pFiringControl);
}

Only COM+ calls the methods of IMultiInterfacePublisherFilter as part of the event publishing sequence. If an event class has a publisher filter object associated with it, COM+ CoCreates the filter object and calls the Initialize( ) method when the publisher CoCreates the event class.

Each time the publisher fires an event at the event class, instead of publishing the event to the subscribers, COM+ calls the PrepareToFire( ) method and lets you do the filtering. When the publisher releases the event class, COM+ releases the filter object.

When the Initialize( ) method is called, COM+ passes in as a parameter an interface pointer of type IMultiInterfaceEventControl , defined as:

interface IMultiInterfaceEventControl : IUnknown 
{
   HRESULT GetSubscriptions(
               [in] IID* piidSink, 
               [in] BSTR bstrMethodName, 
               [in] BSTR bstrCriteria, 
               [in] int* nOptionalErrorIndex, 
               [out, retval] IEventObjectCollection** ppCollection);
 //Other methods

}

The only method of IMultiInterfaceEventControl relevant to publisher-side filtering is GetSubscriptions( ) , used to get the list of subscribers at the time the event is published. Since COM+ calls the Initialize( ) method only once, you should cache the IMultiInterfaceEventControl pointer as a member variable of the filter object.

The actual filtering work is performed in the scope of the PrepareToFire( ) method. The first thing you need to do in the PrepareToFire( ) method is call the IMultiInterfaceEventControl::GetSubscriptions( ) method, passing an initial filtering criteria in as a parameter.

Filtering criteria are mere optimizations—a filter is used to inspect subscribers, and the filter may provide COM+ with an initial criterion of which subscribers to even consider for publishing.

The criterion is a BSTR containing some information about the subscribers. For example, consider a filtering criterion of the form:

_bstr_t bstrCriteria = "EventClassID == {F89859D1-6565-11D1-88C8-0080C7D771BF} AND 
MethodName = "OnNewOrder"";

This causes COM+ to retrieve only subscribers that have subscribed to the specified event class and for the method called OnNewOrder on one of the event class interfaces.

Another example of a criterion is ALL, meaning just get all the subscribers. See the IMultiInterfaceEventControl documentation for more information on the exact criteria syntax.

GetSubscriptions( ) returns an interface pointer of type IEventObjectCollection , which you use to access the subscribers collection.

Next, you call IEventObjectCollection::get_NewEnum( ) to get an enumerator of type IEnumEventObject to iterate over the subscribers collection. While you iterate, you get one subscriber at a time in the form of IEventSubscription . You retrieve the IEventSubscription properties (such as the subscriber’s name, description, IID), apply filtering logic, and decide if you want to publish to that subscriber. If you want to fire the event at that subscriber, use the last parameter passed to PrepareToFire( ), a pointer of type IFiringControl , passing in the Subscriber interface:

pFiringControl->FireSubscription(pSubscription);

At this point, you also get the exact success code of publishing to that particular subscriber. You then release the current subscriber and continue to iterate over the subscription collection.

If you want to publish to the subscribers in a different order than the one in which COM+ handed them to you, you should iterate over the entire collection, copy the subscribers to your own local list, sort the list to your liking, and then fire.

The CGenericFilter helper class

By now, you probably feel discouraged from implementing a publisher-side filter. The good news is that the filtering plumbing is generic, so I was able to implement all of it in an ATL COM object called CGenericFilter . CGenericFilter performs the messy interaction with the COM+ event system required of a publisher filter. All you have to do is provide the filtering logic (which is what a filter should do).

As part of the source files available with this book at O’Reilly’s web site, you will find the Filter project—an ATL project containing the implementation of the CGenericFilter class. CGenericFilter lets you control which subscribers to publish to. If you want a different filter, such as one that controls the publishing order, you can implement that filter yourself, using the source files as a starting point.

The CGenericFilter class definition is (with some code omitted for clarity):

class CGenericFilter: public CComObjectRootEx<CComSingleThreadModel>,
                      public CComCoClass<CGenericFilter,&CLSID_MyFilter>,
                      public IMultiInterfacePublisherFilter
{
   public:
   CGenericFilter(  );
   void FinalRelease(  );
   BEGIN_COM_MAP(CGenericFilter)
     COM_INTERFACE_ENTRY(IMultiInterfacePublisherFilter)
   END_COM_MAP(  )

   //IMultiInterfacePublisherFilter
   STDMETHOD(Initialize)(IMultiInterfaceEventControl* pMultiEventControl);
   STDMETHOD(PrepareToFire)(IID* piidSink, BSTR bstrMethodName,
                            IFiringControl* pFiringControl);

   //Helper methods, used for domain logic specific filtering
   HRESULT ExtractSubscriptionData(IEventSubscription* pSubscription,
                                   SubscriptionData* pSubscriptionData)const;     
   BOOL ShouldFire(const SubscriptionData& subscriptionData)const;
                   _bstr_t GetCriteria(  )const;

   IMultiInterfaceEventControl* m_pMultiEventControl;
};

The only thing you have to provide is the application domain-specific filtering logic, encapsulated in the two simple helper methods: CGenericFilter::ShouldFire( ) and CGenericFilter::GetCriteria( ). The CGenericFilter implementation calls GetCriteria( ) once per event to allow you to provide a filtering criteria. The default implementation returns ALL:

_bstr_t CGenericFilter::GetCriteria(  )const
{
   _bstr_t bstrCriteria = "ALL";//ALL means all the subscribers, 
                                //regardless of event classes 
   
   return bstrCriteria;
}

CGenericFilter::ShouldFire( ) is the most interesting method here. CGenericFilter calls the method once per subscriber for a particular event. It passes in as a parameter a custom struct of type SubscriptionData , which contains every available bit of information about the subscriber—including the name, description, and machine name:

struct SubscriptionData
{
   _bstr_t  bstrSubscriptionID;
   _bstr_t  bstrSubscriptionName;
   _bstr_t  bstrPublisherID;
   _bstr_t  bstrEventClassID;
   _bstr_t  bstrMethodName;
   _bstr_t  bstrOwnerSID;
   _bstr_t  bstrDescription;
   _bstr_t  bstrMachineName;
   BOOL     bPerUser;
   CLSID    clsidSubscriberCLSID;
   IID      iidSink;
   IID      iidInterfaceID;
};

ShouldFire( ) examines the subscriber and returns TRUE if you wish to publish to this subscriber or FALSE otherwise.

An example for implementing filtering logic in ShouldFire( ) is to publish only to subscribers whose description field in the Component Services Explorer says Paid Extra. See Example 9-2.

Example 9-2. Base your implementation of Shouldfire() on the information in SubscriptionData

BOOL CGenericFilter::ShouldFire(const SubscriptionData& subscriptionData)const
{
   if(subscriptionData.bstrDescription == _bstr_t("Paid Extra"))
      return TRUE;
   else
      return FALSE;
}

Finally, Example 9-3 shows the CGenericFilter implementation of PrepareToFire( ) , which contains all the interaction with the COM+ event system outlined previously; some error-handling code was removed for clarity.

Example 9-3. CGenericFilter implementation of PrepareToFire( )

STDMETHODIMP CGenericFilter::PrepareToFire(IID* piidSink, BSTR bstrMethodName,
                                           IFiringControl* pFiringControl)
{
   HRESULT hres = S_OK;
   DWORD dwCount = 0;
   IEnumEventObject* pEnum = NULL;
   IEventSubscription* pSubscription = NULL;
   IEventObjectCollection* pEventCollection = NULL;

   _bstr_t bstrCriteria = GetCriteria(  );//You provide the criteria
 
   hres = m_pMultiEventControl->GetSubscriptions(piidSink,
                                                 bstrMethodName,
                                                 bstrCriteria,NULL,
                                                 &pEventCollection);

   //Iterate over the subscribers, and filter in this example by name
   hres = pEventCollection->get_NewEnum(&pEnum);
   pEventCollection->Release(  );

   while(TRUE)
   {  
      hres = pEnum->Next(1,(IUnknown**)&pSubscription,&dwCount);
      if(S_OK != hres)
      {
         //Returns S_FALSE when no more items
         if(S_FALSE == hres)
         { 
            hres = S_OK;
         }
         break;
      }
      long bEnabled = FALSE;
      hres = pSubscription->get_Enabled(&bEnabled);
      if(FAILED(hres) || bEnabled == FALSE)
      {
         pSubscription->Release(  );
         continue;
      }

      SubscriptionData subscriptionData;
      subscriptionData.iidSink = *piidSink;

      //A helper method for retrieving all of the subscription      
      //properties and packaging them in the handy SubscriptionData 
      hres = ExtractSubscriptionData(pSubscription,&subscriptionData);
      if(FAILED(hres))
      {
         pSubscription->Release(  );
         continue;
      }
      //You provide the filtering logic in ShouldFire(  )
      BOOL bFire = ShouldFire(subscriptionData);
      if(bFire)
      {
         pFiringControl->FireSubscription(pSubscription);
      }
      pSubscription->Release(  );
   }
   pEnum->Release(  );

   return hres;
}

Again, let me emphasize that all you have to provide is the filtering logic in ShouldFire( ) and GetCriteria( ); let CGenericFilter do the hard work for you.

Parameters-based publisher filtering

What begs an answer now (as I am sure you have already wondered) is why is PrepareToFire( ) called “Prepare” if the event is fired there? Why not just call it Fire( )? It is called Prepare to support filtering based on the event parameters as well. In PrepareToFire( ), COM+ only tells you what event is fired.

What if you need to examine the actual event parameters to make a sound decision on whether or not you want to publish? In that case, the publisher filter can implement the same sink interfaces as the event class it is filtering.

After calling PrepareToFire( ), COM+ queries the filter object for the sink interface. If the filter supports the event interface, COM+ only fires to the filter. The filter should cache the information from PrepareToFire( ) and perform the fine-tuned parameters-based filtering. In its implementation of the sink method, it uses IFireControl to fire the event to the client.

Custom subscription properties

Publisher-side filters usually base their filtering logic on the standard subscription properties—the subscription name, description, and so on. These properties are pre-defined and are available for every subscription. COM+ also allows you to define new custom properties for subscriptions and assign values to these properties, to be used by the publisher filter. Usually, you can take advantage of custom properties if you develop both the subscribing component and the publisher filter. You can define custom subscription properties administratively only for persistent subscribers.

To define a new custom property, display the subscription properties page, and select the Publisher Properties tab (the name is misleading). You can click the Add button to define a new property and specify its value (see Figure 9-9).

Defining new custom properties and assigning values on the Publisher Properties tab

Figure 9-9.  Defining new custom properties and assigning values on the Publisher Properties tab

Transient subscribers have to program against the component COM+ Catalog. Get hold of the transient subscription collection, find your transient subscription catalog object, and navigate from it to the PublisherProperties collection. You can then add or remove custom properties in the collection.

As explained before, when the publisher filter iterates over the subscription collection, it gets one subscriber at a time in the form of an IEventSubscription interface pointer. The filter can call IEventSubscription::GetPublisherProperty( ) , specify the custom property name, and retrieve its value.

For example, here is how you retrieve a custom subscriber property called Company Name:

_bstr_t bstrPropertyName = "Company Name";
_variant_t varPropertyValue;
hres = pSubscription->GetPublisherProperty(bstrPropertyName,&varPropertyValue);

If the subscriber does not have this property defined, GetPublisherProperty( ) returns S_FALSE. You can even define method parameter names as custom properties and specify a value or range in the property data. If the filter is doing parameters-based filtering, it can be written to parse the custom property value and to publish to that subscriber only when the parameter value is in that range.

Installing a publisher filter

There are two ways for associating a publisher filter with an event class. In the absence of any names for these two ways from the COM+ team at Microsoft, I call the first static association and the second dynamic association.

Static association requires you to program against the COM+ Catalog and store the filter CLSID as a property of the event class. The filter will stay there until you remove it or override it with another CLSID. Static association affects all publishers that use that event class, in addition to all instances of the event class.

Dynamic association takes place at runtime. The publisher will not only create an event class, but also directly creates a filter object and associates it only with the instance of the event class it currently has. Dynamic association affects only the publishers that use that particular instance of the event class. Dynamic association does not persist beyond the lifetime of the event class object. Once you release the event class, the association is gone. Dynamic association allows a publisher to bind a particular instance of an event class with a particular instance of a filter; it overrides any static filter currently installed.

The main disadvantage of dynamic association is that you cannot dynamically associate a filter with an instance of a queued event class (discussed later on), since you are interacting with the recorder for the event class, not the event class itself.

Static association of a publisher filter with an event class

To statically associate a publisher filter CLSID with the event class you want it to filter, you have to follow these steps:

  1. Create the catalog object.

  2. Get the Applications collection.

  3. For each application in the collection, get the Components collection.

  4. Iterate through the Components collection looking for the event class. If the class is not found, get the next Application collection and scan its Components collection.

  5. Once you find the event class, set the MultiInterfacePublisherFilterCLSID event class property to the CLSID of the filter.

  6. Save changes on the Components collection and release everything.

Again, the Catalog wrapper helper object is useful, as it saves you the interaction with the COM+ Catalog. The helper object implements an interface called IFilterInstaller, defined as:

interface IFilterInstaller : IUnknown
{
  HRESULT Install([in]CLSID clsidEventClass,[in]CLSID clsidFilter);
  HRESULT Remove ([in]CLSID clsidEventClass);
};

IFilterInstaller makes adding a filter a breeze—just specify the CLSID of the event class and the CLSID of the filter, and it will do the rest for you:

HRESULT hres = S_OK;
   
hres = ::CoCreateInstance(CLSID_CatalogWrapper,NULL,CLSCTX_ALL,
                          IID_IFilterInstaller,(void**)&pFilterInstaller);

hres = pFilterInstaller->Install(CLSID_MyEventClass,CLSID_MyFilter);
   
pFilterInstaller->Release(  );

Note that you do not need to specify the application name as a parameter; just specify the event class and the filter CLSID. Use IFilterInstaller::Remove( ) to remove any filter associated with a specified event class.

Dynamic association of a publisher filter with an event class

To associate a publisher filter object with an event class instance dynamically, follow these steps:

  1. Create the event class and get the sink interface.

  2. Query the event class for IMultiInterfaceEventControl interface.

  3. Create the filter object.

  4. Call IMultiInterfaceEventControl::SetMultiInterfacePublisherFilter( ) and pass in the filter object.

  5. Release IMultiInterfaceEventControl.

  6. Publish events to the event class object. The events will go through the filter you have just set up.

  7. Release the event class and the filter when you are done publishing.

Example 9-4 shows some sample code that uses this technique.

Example 9-4. Installing a publisher-side filter dynamically

HRESULT hres = S_OK;
IMySink* pMySink = NULL;
IMultiInterfacePublisherFilter* pFilter = NULL;
IMultiInterfaceEventControl* pEventControl = NULL;

//Create the filter
hres = ::CoCreateInstance(CLSID_MyFilter,NULL,CLSCTX_ALL,
                          IID_IMultiInterfacePublisherFilter,(void**)&pFilter);
//Create the event class
hres = ::CoCreateInstance(CLSID_MyEventClass,NULL,CLSCTX_ALL,
                          IID_IMySink,(void**)&pMySink);

//Query the event class for IMultiInterfaceEventControl
hres = pMySink ->QueryInterface(IID_IMultiInterfaceEventControl,
                               (void**)pEventControl);

//Setting the filter
hres = pEventControl->SetMultiInterfacePublisherFilter(pFilter);
pEventControl->Release(  );

//Firing the event 
hres = pMySink->OnEvent1(  );//The event is now filtered

pMySink->Release(  );
pFilter->Release(  );

Unfortunately, COM+ has a bug regarding correct handling of dynamically associating a publisher filter with an event class. COM+ does not call the filter method IMultiInterfacePublisherFilter::Initialize( ), and as a result, you can’t do much filtering. I hope this situation will be fixed in a future release of COM+.

This defect, plus dynamic association’s inability to work with queued event classes, renders it effectively useless. Avoid dynamic association of a publisher filter; use static association instead.

Subscriber-Side Filtering

Not all subscribers have meaningful operations to do as a response to every published event. Your subscriber may want to take action only if your favorite stock is trading, or perhaps only if it is trading above a certain mark. One possible course of action is to accept the event, examine the parameters and decide whether to process the event or discard it.

However, this action is inefficient if the subscriber is not interested in the event for the following reasons:

  • It forces a context switch to allow the subscriber to examine the event.

  • It adds redundant network round trips.

  • Writing extra examination code may introduce defects and require additional testing.

  • Event examination and processing policies change over time and between customers. You will chase your tail trying to satisfy everybody.

What you should really do is to put the filtering logic outside the scope of the subscriber. You should have an administrative, configurable, post-compilation, deployment-specific filtering ability. This is exactly what subscriber-side filtering is all about (see Figure 9-10). Subscribers that do not want to be notified of every event published to them, but want to be notified only if an event meets certain criteria, can specify filtering criteria.

Specifying filtering criteria for a persistent subscriber

Figure 9-10.  Specifying filtering criteria for a persistent subscriber

A subscriber-side filter is a string containing the filtering criteria. For example, suppose you subscribe to an event notifying you of a new user in your portfolio management system, and the method signature is:

HRESULT OnNewUser([in]BSTR bstrName,[in]BSTR bstrStatus);

You can specify such filtering criteria as:

bstrName = "Bill Gates" AND bstrStatus = "Rich"

The event will only be delivered to your object if the username is Bill Gates and his current status is Rich.

The filter criteria string recognizes relational operators for checking equality (==,!=), nested parentheses, and logical keywords AND, OR, and NOT. COM+ evaluates the expression and allows the call through only if the criteria are evaluated to be true.

Warning

If you have wrong parameters or spelling mistakes, or if the parameter names were changed, the subscriber will never be notified.

Because subscriber-side filtering occurs only after the event has been fired, if a publisher filter is used, then the event has to pass the publisher filter first. The obvious conclusion is that publisher-side filtering takes precedence over subscriber-side filtering.

Persistent subscriber-side filtering

Only persistent subscribers can specify a subscriber filter administratively. They can do so by displaying the persistent subscription properties page, selecting the Options tab, and specifying the Filter criteria (see Figure 9-11).

Subscriber-side filtering

Figure 9-11.  Subscriber-side filtering

Transient subscriber-side filtering

Transient subscribers have to program against the Catalog to set a transient subscription filter criteria, following similar steps to those performed when registering a transient subscription:

  1. Get hold of the Catalog interface.

  2. Get the transient subscription collection object.

  3. Find your transient subscription.

  4. Set a subscription property called FilterCriteria to the string value of your filter.

  5. Save changes and release everything.

The Catalog wrapper’s interface ITransientSubscription , discussed earlier, allows you to add (or remove) a subscriber-side filter to a transient subscription with the AddFilter( ) and RemoveFilter( ) methods. The methods accept the subscription name and a filtering string.

Example 9-5 demonstrates the same example from the persistent subscriber filter, but for a transient subscriber for the same event.

Example 9-5. Adding a transient subscription filtering criteria using the wrapper object

//Adding a transient subscription filter:
LPCWSTR pwzCriteria = L"bstrUser = "Bill Gates" AND bstrStatus = "Rich"" 

//"MySubs" is the transient subscription name

hres = pTransSubs->AddFilter(L"MySubs",pwzCriteria);


//Or removing the filter:
pTransSubs ->RemoveFilter(L"MySubs");

The main disadvantage of a transient subscriber filter compared to a persistent subscriber filter is that you hardcode a filter, which is sometimes deployment- or customer-specific. Persistent subscribers can always change the filtering criteria using the Component Services Explorer during deployment.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset