Flush the Cache Droplet Upon CA Deployments

Hopefully you’re already using the ATG Cache Droplet extensively in your ATG eCommerce application, as I recommended in my ATG Performance Tuning post on Improving JSP Serving Time for an ATG Application.  If you are, you’re probably using a smaller value for the cacheCheckSeconds parameter than you’d like, in order to prevent stale data after CA deployments update the catalog, media, or promo repositories.

You can solve this problem by using a component triggered by Deployment Events from the DeploymentAgent, which after a successful deployment flushes the Cache Droplet’s cache.  This should allow you to set a very long cache expiration time using the cacheCheckSeconds param, and not have to worry about displaying outdated data.

I’ve added this code into the open source ATG eCommerce framework Foundation, hosted by Spark::red, the best ATG Hosting Company :)

There are three parts to this solution: the Java class, it’s properties file, and adding it to the DeploymentAgent’s list of event listeners.

DeploymentEventCacheDropletInvalidator.java

/**
 * Copyright 2009 Devon Hillard (devon@digitalsanctuary.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.foundation.deployment;

import atg.deployment.common.event.DeploymentEvent;
import atg.deployment.common.event.DeploymentEventListener;
import atg.droplet.Cache;
import atg.nucleus.GenericService;
import atg.nucleus.ServiceException;

/**
 * The Class DeploymentEventCacheDropletInvalidator will flush the Cache droplet's cache upon a successful CA
 * deployment.
 * 
 * @author Devon Hillard
 */
public class DeploymentEventCacheDropletInvalidator extends GenericService implements DeploymentEventListener {

    /** The Cache Droplet. */
    private Cache mCacheDroplet;

    /** The event state. */
    private int mEventState;

    /** The active. */
    private boolean mActive;

    /**
     * Deployment event handling method. This will flush the Cache droplet's cache after a CA deployment completes.
     * 
     * @param pEvent the deployment event
     * 
     * @see atg.deployment.common.event.DeploymentEventListener#deploymentEvent(atg.deployment.common.event.DeploymentEvent)
     */
    public void deploymentEvent(final DeploymentEvent pEvent) {
        if (isActive() && (pEvent.getNewState() == getEventState())) {
            if (isLoggingInfo()) {
                logInfo("DeploymentEventCacheDropletInvalidator.deploymentEvent:" + "Deployment has completed.");
            }
            getCacheDroplet().flushCache();
            if (isLoggingInfo()) {
                logInfo("DeploymentEventCacheDropletInvalidator.deploymentEvent"
                        + "Cache droplet cache has been flushed.";);
            }
        }
    }

    /**
     * Do start service.
     * 
     * @throws ServiceException the service exception
     * 
     * @see atg.nucleus.GenericService#doStartService()
     */
    @Override
    public void doStartService() throws ServiceException {
        if (isLoggingInfo()) {
            logInfo("DeploymentEventCacheDropletInvalidator.doStartService:" + "starting up.");
        }
        if (getCacheDroplet() == null) {
            throw new ServiceException("DeploymentEventCacheDropletInvalidator: cache droplet was not set.";);
        }
        if (getEventState() == 0) {
            throw new ServiceException("DeploymentEventCacheDropletInvalidator: event state was not set.");
        }
    }

    /**
     * Do stop service.
     * 
     * @throws ServiceException the service exception
     * 
     * @see atg.nucleus.GenericService#doStopService()
     */
    @Override
    public void doStopService() throws ServiceException {
        if (isLoggingInfo()) {
            logInfo("DeploymentEventCacheDropletInvalidator.doStopService:" + "stopping.");
        }
    }

    /**
     * Gets the Cache Droplet.
     * 
     * @return the cacheDroplet
     */
    public Cache getCacheDroplet() {
        return this.mCacheDroplet;
    }

    /**
     * Sets the Cache Droplet.
     * 
     * @param pCacheDroplet the cacheDroplet to set
     */
    public void setCacheDroplet(final Cache pCacheDroplet) {
        this.mCacheDroplet = pCacheDroplet;
    }

    /**
     * Gets the event state.
     * 
     * @return the event state
     */
    public int getEventState() {
        return this.mEventState;
    }

    /**
     * Sets the event state.
     * 
     * @param pEventState the new event state
     */
    public void setEventState(final int pEventState) {
        this.mEventState = pEventState;
    }

    /**
     * Checks if is active.
     * 
     * @return true, if is active
     */
    public boolean isActive() {
        return this.mActive;
    }

    /**
     * Sets the active.
     * 
     * @param pActive the new active
     */
    public void setActive(final boolean pActive) {
        this.mActive = pActive;
    }

}

DeploymentEventCacheDropletInvalidator.properties

$class=org.foundation.deployment.DeploymentEventCacheDropletInvalidator
$scope=global

# If true, this service will invalidate the Cache droplet's cache when the appropriate deployment event is fired.
active=true

# The cache droplet to invalidate.  Defaults to the standard Cache droplet.
cacheDroplet=/atg/dynamo/droplet/Cache

# The deployment event's newState to trigger a cache flush of the Cache droplet.
# IDLE = 1;
# DEPLOYMENT_COMPLETE = 2;
# DEPLOYMENT_DELETED = 7;
# EVENT_INTERRUPT = 6;
# ERROR = 3;
# BEGIN_LOCK = 201;
# DONE_LOCK = 202;
# ERROR_LOCK = 203;
# BEGIN_PREPARE = 301;
# DONE_PREPARE = 302;
# ERROR_PREPARE = 303;
# BEGIN_CREATE = 401;
# DONE_CREATE = 402;
# ERROR_CREATE = 403;
# BEGIN_INSTALL = 501;
# DONE_INSTALL = 502;
# ERROR_INSTALL = 503;
# BEGIN_LOAD = 601;
# DONE_LOAD = 602;
# ERROR_LOAD = 603;
# BEGIN_APPLY = 701;
# BEGIN_APPLY_COMMITTED = 702;
# DONE_APPLY = 703;
# ERROR_APPLY = 704;
# ERROR_APPLY_COMMITTED = 705;
# BEGIN_ACTIVATE = 801;
# DONE_ACTIVATE = 803;
# ERROR_ACTIVATE = 804;
# BEGIN_STOP = 901;
# DONE_STOP = 902;
# ERROR_STOP = 903;
eventState=1

/atg/epub/DeploymentAgent.properties

deploymentEventListeners+=\
	/foundation/deployment/DeploymentEventCacheDropletInvalidator
	

——————————
Edit: at least in our cluster, the DEPLOYMENT_COMPLETE (2) event is never triggered on the client side by the DeploymentAgent, so I’ve switched this to use the transition to IDLE (1).

Updates to the ATG RSS Feed Module

Thanks to Doug Henderson sending me some code updates I have released a new version of the RSS Feed Droplet (first mentioned in this post) which supports configurable character encodings. This can be very useful if you have “interesting” characters in the content you are publishing.

I have also rolled the code into my ATG Open Source Module Pack, so that I can maintain everything in one location.

Also: please note that you cannot reformat the JSP code, or introduce any spaces as it will break the feed. I’ve made note of that in the module’s Readme as well.

Enjoy!

ATG RSS Generator Droplet

I sat down a few hours ago to start a droplet which would generate an RSS feed based on data in an ATG Repository. Surprisingly, I think I’m done.

Basically what it is is a droplet, which you put on an otherwise empty JSP page (it’s important that there is no whitespace on the JSP as well). You configure the droplet via it’s .properties file to point to a Repository, and you give it an item descriptor, which will be the primary item to use for the data in the feed. You then configure a bunch of mostly optional data points for the channel data of the RSS feed (this includes things like the main site link, the RSS feed title and description, and optional elements like author, copyright information, etc…

Then you provide the property names (and nested “.” driven properties are okay here) for the various elements of the RSS Item like title, link, description, publish date, etc… Currently it sorts based on the publish date property, to provide items in newest-to-oldest order. I may support a separate sort property later on if people want one.

Since many repository items may not have a usable link property, you can pass in a itemLink property value with an embedded property (also supporting nested properties, and supporting the special repositoryId). For instance you can do this:

itemLink=/catalog/product.jsp?productId=#{repositoryId}

or even something like:

itemLink=http://#{author.homepageURL}?referrer=mySite

The JSP page, when hit, generates a nice RSS 2.0 compliant feed based on the data in the Repository.

You can also include the following in your <head> element of your site’s main page to get the nice automatic feed discovery in many browsers:

<link href="http://mysite.com/rss.jsp" rel="alternate" type="application/rss+xml" title="RSS" />

You can download the entire package here. This includes the source, a heavily commented properties file, a jsp file, and a jar file to load into your classpath.

Some examples where you might want to use this: Blog, Newsletter, any site with updated content, a commerce site’s catalog, discussion forums, etc…..

Enjoy!

ItemToXMLDroplet

This is an extremely simple Droplet with source, a config file, and a sample JSP. All the droplet does it transform a passed-in RepositoryItem into XML and dump it out. It would be handy for vending data to client-side logic in JavaScript or Flash, when you don’t want to write-up a full WebService.

You can download it all here.

The example JSP takes in a query parameter userId and generates the XML for the user profile with that id. For instance, if you call:

http://alita.local:8840/test/test.jsp?userId=930003

you get this……

Continue reading