Pages

Monday, February 14, 2011

Dynament Reference (Tips)

Grab file name from url string 

<rde-dm:attribute mode="write"
                attribute="request:tmpvar"
                value="/cps/rde/project/xsl/hs.xsl/-/html_files/index.htm"
                tag="notag" />
<rde-dm:attribute mode="read"
                attribute="request:tmpvar"
                tag="notag" inline-function="split('/', -1)" />

This will take /cps/rde/project/xsl/hs.xsl/-/html_files/index.htm and rip out the index.htm.

Friday, November 19, 2010

Pull google weather and display in Delivery Server - Part 2

As shown in part 1 of pulling google weather and displaying within delivery server, a number of very simple but important foundation concepts were covered.

  1. Conditional Tags
  2. Setting request attributes
  3. Reading and using User Attributes
In this example, we will make a jump and build our own customized delivery server tags (iolets) to pull weather from google.  As seen in example one, a key issue when relying on external feeds will be connectivity, reliability and speed. It is possible that the feed is down for a few hours or you have issues connecting to the server.  A key issue I have had to support is the internal activity of my site.  We support 7,000 people who access the site daily and with 30-60k page hits a day, that is a lot of external traffic to an external feed.

To address these issues I decided to build a custom DS tag that would call the weather feed and store a cached instance for each location requested.

If 1000 people access the site within a 2 hour period (500 from Toronto and 500 from New York), the IOLet will save an exact copy of the XML for both Toronto and New York.  The first person from New York that access the site will download a copy where as the next 499 people will access that local copy. (Same for Toronto)

The Basic Idea

Technologies:
  1. EHCache
  2. Log4j

weather.xml
<rde-dm:iolet name="weatherfeed" method="GetWeatherOutput" tag="notag">
    <location>[#user:preferences.widget.weather.location#]</location>
</rde-dm:iolet>

Here you find the magic.
WeatherIOLet.class
public class WeatherIOLet extends IoletAdapter {
    public String location;
    
    public String BuildErrorResponse(String error) {
        String xml = "<error>" +
                     "    <note>"+error+"</note>" +
                     "</error>";
        return xml;
    }
    
    public IoletResponse doGetWeather(CoaSession session, IoletRequest request) {
        //CoaUser sessionUser = session.getCoaUser();
        // can we get other Attributes from the the session User?
        if (request.getParameter("location") == null || request.getParameter("location").toString().equals("")) {
            // error
            IoletResponse response = IoletResponseFactory.getIoletResponse(this.BuildErrorResponse("Location not specified"));
            response.setParameter(IoletResponse.XML_TEXT, Boolean.TRUE);
            return response;            
        } else {
            this.location = (String) request.getParameter("location");
            WeatherService ws = new WeatherService(this.location);
            IoletResponse response = IoletResponseFactory.getIoletResponse(ws.getXML());
            response.setParameter(IoletResponse.XML_TEXT, Boolean.TRUE);
            return response;
        }        
    }
}

The WeatherXMLDAO is always called by the IOLet - each time the user accesses a page with the weather widget on it, this class will check the cache for a valid instance and if one exists it will return the full XML back to the IOLet.  If the cache is empty or out of date (determined by the cache - I defined at 2 hours) then it will make a call to the WeatherCacheItem class which will make a URL request against the weather service.

WeatherXMLDAO.class
public class WeatherXMLDAO implements XMLDAOIF {
    ...    
    public WeatherXMLDAO(String cache_name) {
        super();
        this.cache_name = cache_name;
        this.cache_init();
    }
    
    @Override
    public String getXMLOutput() {
        // can not pass in parameters as it may be to difficult to pull out - maybe use HashMap with a key/value pairs
        _logger.debug("grabbing XML from cache");
        WeatherCacheItem wci = new WeatherCacheItem(wr.getParams());
        SelfPopulatingCache spc = new SelfPopulatingCache(cache, wci);
        if (spc.isElementInMemory(wr.getLocation())) {
            _logger.info("Item is in memory ");
        }
        return (String) spc.get(wr.getLocation()).getObjectValue();
    }
    
    private void cache_init() {
        _logger.info("initializing cache: " + this.cache_name);
        if (this.cache == null) {
            _logger.debug("Cache does not exist: Creating new cache object");
            CacheManager manager = CacheManager.getInstance();
            cache = manager.getEhcache(this.cache_name);            
        }
    }
}

WeatherCacheItem.class
public class WeatherCacheItem implements CacheEntryFactory { 
    ...
    public Object createEntry(Object arg0) throws Exception {
        String url = "http://" + props.getProperty("weather.host") + props.getProperty("weather.uri") + this.params;
        URL u = null;
        try {
            u = new URL(url);
            URLConnection conn = u.openConnection();
            _logger.debug("Opening Weather URL: " + url );
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(),"ISO-8859-1"));
            sb = new StringBuilder(16384);
            try {
                String line;
                while ((line = br.readLine()) != null) {
                    sb.append(line);
                    sb.append('\n');
                }
            } finally {
                br.close();
            }
            _logger.debug("Weather XML Output: " + sb.toString());
            
        } catch (MalformedURLException mue) {
            _logger.fatal("Malformed Exception: " + mue.toString());
        } catch (IOException ioe) {
            _logger.fatal("IO Exception: " + ioe.toString());
        } 
    
        return sb.toString();
    }  
}   

Enhance the Idea
To enhance this functionality would be to optimize the code to support additional external data feeds and apply the same caching process and described above. I have enhanced the application to support RSS external news feeds, stock feeds, and Career Opportunity feeds. As each feed requires a different set of requirements, I have enhanced how the process works slightly.

I built a "codec" to interpret and decode a number of parameters required for each feed.  These parameters further defined the number of cache stores. 

WeatherRequest.class
public class WeatherRequest implements RequestIF {
    String location;
    
    public WeatherRequest(String location) {
        this.location = location;
    }
    
    public String getLocation() {
        return this.location;
    }
    
    @Override
    public String getParams() {
        Configurator props = Configurator.getInstance();
        String param = props.getProperty("weather.params.location").replaceFirst("\\[\\#location\\#\\]", this.getLocation());
        return param;
    }
}


ehcache.xml
    <cache name="weather_cache"
           maxElementsInMemory="10000"
           maxElementsOnDisk="1000"
           eternal="false"
           overflowToDisk="true"
           diskSpoolBufferSizeMB="20"
           timeToIdleSeconds="7200"
           timeToLiveSeconds="10800"
           memoryStoreEvictionPolicy="LFU"
            />

    <cache name="stock_cache"
           maxElementsInMemory="100"
           maxElementsOnDisk="1000"
           eternal="false"
           overflowToDisk="true"
           diskSpoolBufferSizeMB="20"
           timeToIdleSeconds="180"
           timeToLiveSeconds="300"
           memoryStoreEvictionPolicy="LFU"
            />

Thursday, November 18, 2010

Pull google weather and display in Delivery Server - Part 1

I have found two ways to display weather within delivery server. The easy way and the efficient way.

Part 1 will show a very simple process to call an external weather feed that returns XML data to display in your site.

In Part 2, I will present an efficient way (using iolets and ehcache) to pull back an XML feed and cache the result for hours (reducing the number of out bound URL calls).

The Basic Idea

weather.xml
<dynaments>
  <rde-dm:include tag="weather_google" 
                  content="http://www.google.com/ig/api?weather=Calgary" />
</dynaments>

weather.xsl
<?xml version="1.0" encoding="UTF-8"?>
<templates>
    <xsl:template match="weather">
        <xsl:for-each select="forecast_conditions">
           <xsl:value-of select="condition/@data"/>
        </xsl:for-each>
    </xsl:template>
</templates>

You could add the weather.xsl to your primary xsl file (such as projectname.xml or hs.xsl) - this would then automatically present the weather properly on the screen.


Enhance the Idea

weather.xml
<dynaments>
  <rde-dm:attribute mode="condition" attribute="user:preferences.widget.weather.location" op="ne" value="" tag="notag">
 <rde-dm:if>
  <rde-dm:attribute mode="write" attribute="request:weather_url" value="http://www.google.com/ig/api?weather=" inline-function="concat(user:preferences.widget.weather.location)" tag="notag" />
  <rde-dm:include tag="weather_google" 
      content="request:weather_url" />
 </rde-dm:if>
 <rde-dm:else>
  <rde-dm:include content="NoWeatherWidget.htm" tag="notag" />
 </rde-dm:else>
  </attribute>
</dynaments>

Using user attributes (previously assigned when you log in) you can dynamically call the weather service to pull back customized weather.  The general idea is to create a temporary variable with the entire weather url and append the user attribute user:preferences.widget.weather.location.

1) If user attribute exists:
  Display the weather widget

2) If no user attribute defined
   Include HTML tag that allows users to customize location

Wednesday, November 17, 2010

Delivery Server: Basic Syntax


Delivery Server (DS) code is executed within the DS engine, and the plain HTML response is sent to the clients browser.

Your code can either be written directly within the Reddot templates pages them self or within an xml page managed within the Delivery Server.


All code managed with the DS engine must be encased by xml tags, such as <dynaments> code ... </dynaments>. These tags can literally be anything you like, but it is recommended to use the standard dynaments tag.

The Basic Idea

<dynaments>
  <rde-dm:attribute mode="write" 
                attribute="request:tmpvar" 
                value="Hello World!" 
                tag="notag" />
  <rde-dm:attribute mode="read" 
                attribute="request:tmpvar" 
                tag="notag" />
</dynaments>


Mode
Read: displays variable/content to the web page
Write: creates a variable and assigns value to attribute

Attribute
source : variable name

     Source
  • Request local variable used during a specific request
  • Users -  user define in memory cache or user database attributes
  • Cookie
  • Context - used within for-each loops
  • Content - Global variables
  • Groups
  • Sessions
  • System
  • Response - Over loading default header fields.
  • many more

     Variable
     A variable name - be careful and avoid key name. (like password, username, fullname etc)


Enhance the Idea

If Logic
<rde-dm:attribute mode="condition">
    <rde-dm:constraint>
        (user:profile.location EQ 'Calgary' OR user:profile.location EQ 'Alberta')
    </rde-dm:constraint>
    <rde-dm:if>
        You live in Alberta
    </rde-dm:if>
    <rde-dm:else>
        You live outside Alberta
    </rde-dm:else>
</rde-dm:attribute>

For Each
<rde-dm:attribute mode="for-each" attribute="user:profile.location" alias="location" tag="Locations">
    <rde-dm:attribute mode="read" attribute="context:location" />
</rde-dm:attribute>

Output
<Locations>
 <location>Calgary</location>
 <location>Alberta</location>
</Locations> 

Flush Cache
<rde-dm:attribute mode="flush" type="user" />

Very simple command, huge ramification.  If you happen to write content to the users attributes (such as adding a new location to the user list) then you must flush the cache to active the write.  You should avoid not specifying a type.  In this case, the entire cache is flushed - which could have a HUGE impact on performance.