On Asset Event Listeners And Other Ways of Hooking-up Ad-hoc Logic Into WCS' Asset Save Processing Flow
DISCLAIMER (/ SPOILER): in this blog entry, we will not explain what an asset event listener is nor how to create one; they have been around since WCS' stone age and there is extensive documentation out there explaining that in detail, including - but not limited to - WCS 11g's official documentation and Java API:
- Developer's Guide: https://docs.oracle.com/cd/E29542_01/doc.1111/e29634/events_assetapi.htm#WBCSD2525
- Java API: https://docs.oracle.com/cd/E29542_01/apirefs.1111/e39356/com/openmarket/basic/event/AssetEventListener.html
Asset event listeners are just one of the mechanisms WCS provides for plugging ad-hoc logic into its OOTB asset save (create / update / delete) processing flow.
Other known mechanisms are:
- Flex Filters
- RealTime Publishing's CacheUpdater
- Publishing Event Listeners
- PreUpdate / PostUpdate Elements
Let's review some fundamental aspects about each of these mechanisms as well as their suitability for the aforementioned purpose.
Flex Filters are the best alternative when your custom logic generates attribute values or modifies existing ones on-the-fly before the asset save operation - ergo, any asset event listener - is invoked.
They can be easily plugged into WCS' asset save processing flow by means of the FlexFilters DB table, which makes this an upgrade-friendly way to customize this logic.
On the other hand, they are not suitable when you require your ad-hoc logic being executed in a fully transactional manner or, at the very least, strictly after the asset save operation has been successfully completed.
They get invoked whenever an asset is saved, regardless of what triggered the save operation: Contributor UI, Admin UI, XMLPost, REST API, etc...
Some custom logic can only be performed synchronously -- for instance, when it should preclude the actual save operation from being commited in the first place.
It is highly advisable that, whenever possible, you integrate your custom logic into an asset event listener using an asynchronous design / architecture.
Such approach minimizes the impact - in terms of performance - of your custom logic being executed upon every asset save operation, for the sake of quality of service and user experience altogether.
Note, however, that, in this context, "asynchronous" doesn't mean just setting the "blocking" value accordingly in the asset event listeners' registration table; it also means implementing your code in such way that processing takes place asynchronously, e.g. by means of a queueing / messaging solution. JMS and WCS' SystemEvents are some of the typical building blocks you'll find in such architectures.
The aforementioned approach also protects your WCS instance's stability by ensuring that asset save operations are not impacted by transient issues such as network outages / delays, 3rd-party services crashing and the like.
In some cases, you don't have an alternative but to execute your custom logic asynchronously. For instance, whenever your custom logic may trigger another asset-specific event which ultimately gets an asset event listener's method invoked within the same transaction of the original event being processed; WCS' Asset API doesn't support (nor expose) nested transactions, so it is not rare for such scenarios to end up crashing the original asset save operation.
That is the main reason why you typically want to avoid modifying the very same asset that triggered an asset event listener in the first place.
Also, an Asset Event Listener per se is not "atomic", from a transactional standpoint. The transactional behaviour of an asset event listener varies depending on how it got invoked in the first place. For instance:
- When the asset gets saved through the Contributor UI (typically, on a MANAGEMENT instance), the DB transaction is not commited until all asset event listeners triggered during that same asset save operation get executed successfully.
- When the asset gets saved during a publishing session, the DB transaction is not committed until all asset event listeners get executed successfully for each asset in the same publishing group (we'll describe this later) and the "save" operation is completed for each of those assets.
Hence, even when a given Asset Event Listener completes successfully, it is not guaranteed that any changes it may have done on data (DB) will not be rolled back, for instance, in case another asset event listener crashes.
Therefore, if an asset event listener succeeds and so do all other asset events listeners that may get triggered during the same asset save operation, you can be reasonably sure that your asset is going to get saved and the due database changes won't be rolled back. That is, of course, unless disaster hits in the form of WCS, DB and/or network crashing right when transaction is about to get committed.
The only 2 mechanisms built into WCS which are truly transactional in that sense are: PostUpdate elements and CacheUpdaters.
A CacheUpdater provides a hook-up point for executing ad-hoc logic every time an asset gets published (via RealTime publishing) on the publishing destination's side only (e.g. on DELIVERY instances)
Moreover, using a cache updater is the only way to hook up ad-hoc logic in a truly transactional way at publishing time -- again, on the publishing destination's side.
The CacheUpdater is managed as a Spring bean and abides to a supported API / contract, which makes it relatively easy to replace with your own implementation, in an upgrade-friendly manner.
RealTime publishing splits up the workload in "Groups" of assets that are (processed and) committed or rolled back together in the publishing destination.
When a cache updater is invoked, it gets the list of assets that got SUCCESSFULLY published (including the related DB changes getting committed) so that pagelets cache can be flushed accordingly.
Hence, by plugging your ad-hoc logic into the cache updater's method handling cache flush you implicitly enforce that asset-specific logic only gets executed for those assets in the aforementioned list. Sure, you can ignore this contract and do whatever you want inside this (or any other) method, too.
When one of the assets in a publishing group fails to get processed (e.g. saved), the whole group gets rolled back. That is why plugging ad-hoc logic into an Asset Event Listener sometimes is not the best approach for DELIVERY (a.k.a. LIVE) instances.
The above limitation doesn't apply to MANAGEMENT instances - e.g. instances where you save assets via the Contributor UI - since assets get saved individually, each within its own transaction.
PreUpdate elements are a legacy mechanism which, essentially, allows your hooking into the Contributor UI's processing logic your own ad-hoc logic, to be executed right before the asset save operation is executed.
You can customize the OOTB PreUpdate element of any asset type by overriding it using the "CustomElements" mechanism built into WCS.
This approach is not as elegant as Spring-driven components (such as CacheUpdater) or even configuration-driven ones (such as FlexFilters), but it still somewhat upgrade-friendly, in the sense that your custom code is not going to be overwritten by the installer when upgrading to a more recent version / patch.
It is not a suitable solution if your ad-hoc logic must be executed upon publishing (e.g. on a DELIVERY instance) since PreUpdate elements are only invoked when saving an asset through the Contributor UI; they are not invoked when an asset is saved during a RealTime publishing session.
Needless to say, since the PreUpdate element is invoked before asset save actually kicks in, it is not a suitable solution whenever you need your custom logic to be executed strictly after the asset has been saved.
PostUpdate elements work exactly the same as PreUpdate elements, only that they kick in after the asset save operation has been completed.
It's the only mechanism that is truly transactional in the sense that you are 100% sure that your ad-hoc logic gets executed only if the concerned asset got saved - and all database changes committed - without issues.
PostUpdate elements are not transactional during RealTime publishing sessions. Also, they don't always get invoked (for ex.: REST API).
Publishing Event Listeners are only triggered during publishing and, more importantly, they are not asset-oriented nor asset-driven but (publishing) task-oriented.
In other words, publishing event listeners are not thought for performing asset-specific actions, let alone in a transactional fashion. Hence, we won't dwelve into how we could (ab)use WCS' API / contract so to plug ad-hoc logic into a publishing event listener.
Asset Data Gets Saved
Transaction Gets Commited
|Plugged In Via||Requires Re-Deploying JAR?|
|Asset Event Listeners||No||No||DB Table||Yes|
So which mechanisms we prefer?
I'm sure anybody can make a good case for using (almost) all of the above based in a specific set of requirements / constraints. Having said that, based in our experience, in most scenarios the following rule of thumb rarely proves wrong:
- For MANAGEMENT instances, in general:
- If you don't care about your ad-hoc logic being executed transactionally nor that it gets executed after the asset data has been saved (even if not committed yet), then Flex Filter is your way. If all you need to do is modifying some attribute's value or generating its value on-the-fly, this is most probably your best option.
- If you can live with the slight uncertainty surrounding Asset Event Listeners' execution - from a transactional standpoint - as explained above, you will normally opt for using them when plugging custom logic into the OOTB asset save processing flow. If performance and atomicity of your ad-logic (and the asset save operation as a whole) is a concern, an asynchronous asset event listener is probably your best choice.
- If you really need to be 100% sure that your custom logic never gets invoked before the asset save operation is irrevocably committed and you are OK with that logic not being invoked when the save operation is triggered via REST API, then your only (supported) choice - on a MANAGEMENT instance - would be plugging it into the due PostUpdate element instead of an asset event listener.
- For DELIVERY instances, in general, the only (supported) alternative that is 100% transactionally safe is plugging your custom logic into the cache flush phase of RealTime publishing sessions by means of a custom CacheUpdater.
So, handle the above suggestions with care, each project is a world of its own. Let your best judgement tell you the most appropriate solution for your specific set of requirements.
In an upcoming post, I will provide an example of how to benefit from some of these mechanism for integrating WCS with 3rd-party services / platforms.
Hope you've found this one useful!