How to use caching to eliminate retrieving data each time a page request is made.
You can run this technique code from:
NOTE First make sure that database is running on your localhost SilverStream Server | |
See the chapter on Business Objects Basics in the Programmer's Guide |
This technique shows how to cache data on the server so that the data is fetched once, but not re-fetched unless the data changes. It uses a single business object, boCacheDepartmentList, that implements AgiServerListener, AgiInvokedListener, and AgiTableListener. This object declares 3 variables in the General section of the code:
// boolean that is reset in afterTableChanged evt boolean m_bDataChanged = false; // Stores cached data Vector m_vctDepartments = null; // Stores timestamp when data is retrieved long m_timestamp = 0;
The code for the serverStarted event instantiates an AgaData and caches it on the server. It does this by passing a user-defined method, retrieveTheData()
, to getAgaData()
.
The user-defined method stores the result in the Class variable m_vctDepartments, which makes the data available to the invoked event.
public void serverStarted(AgoServerStartEvent evt) { // Retrieve the Departments try { AgaData dataDepartments = evt.getAgaData("dataDepartments"); retrieveTheData( dataDepartments ); } catch (Exception e) { } return; }
In the PageRequestBegin event on pgDepartmentListCached, the code gets the last modified time of the data during the client session, and calls the business object that manages the department list cache, passing the cached page last modified time.
This code fragment shows how the page invokes the business object, passing it the last modified time of the cached page.
protected void pageRequestBegin(AgiHttpServletRequest req, AgiHttpServletResponse res) throws Exception { AgoHttpRequestEvent hRequest = (AgoHttpRequestEvent) req; AgiHttpServletResponse hResponse = (AgiHttpServletResponse) res; // Get time cached page was last modified long lTime = hRequest.getDateHeader("If-Modified- Since")/1000*1000; try // invoke the business object { m_vctDepartments = (Vector) invokeBusinessObject( "com.examples.utilityobjects.boCacheDepartmentList", new Long(lTime)); } catch (NullPointerException npe) { throw new Exception(MSG_OBJECT_NOT_FOUND); } catch (Exception e) { throw new Exception(e.toString()); }
If the data cached by the server has changed since this page was last sent to the browser, the invoked object returns a new vector. If it hasn't changed, it returns null. If it returns null, the page prevents a re-fetch of the data by throwing an exception, which is handled under handlePagException:
... if (m_vctDepartments == null) { // Prevent building of the page and set the // response header to not-modified. // To do this, throw an Exception and handle it in the // handlePageException method. throw new Exception("NOT_MODIFIED"); }
This is how the exception is handled:
public boolean handlePageException(Exception exception, AgiHttpServletRequest req, AgiHttpServletResponse res) { // See if this is an Exception thrown in pageRequestBegin // so to avoid rebuilding the page. if (exception.getMessage().equals("NOT_MODIFIED")) { AgiHttpServletResponse hResponse = (AgiHttpServletResponse) res; hResponse.setStatus(hResponse.SC_NOT_MODIFIED); return (true); } // account for other Exception else { return(false); } }
Finally, the response header resets the Last-Modified date on the response. This is done under the code section for pageRequestEnd:
protected void pageRequestEnd(AgiHttpServletRequest req, AgiHttpServletResponse res) throws Exception { AgiHttpServletResponse hResponse = (AgiHttpServletResponse) res; // Update the 'Last-Modified' date in the response // header before leaving. long lCurrentTime = System.currentTimeMillis()/1000*1000; // Update the response header fields hResponse.setHeader("Cache-Control", null); hResponse.setHeader("Pragma", null); hResponse.setDateHeader("Last-Modified", lCurrentTime); hResponse.setStatus(hResponse.SC_OK); }
The code for the invoked event checks to see if the cached data was modified. If the data is unchanged it returns null. If the data has changed it invokes retrieveThedata()
and sets the result on m_vctDepartments.
public void invoked(AgoInvokedEvent evt) throws Exception { Long longTimeStamp = (Long) evt.getParameter(); long lTimeStamp = 0; if (longTimeStamp != null) lTimeStamp = longTimeStamp.longValue(); // // If data was not inially retrieved, or it has changed... if (m_vctDepartments == null || m_bDataChanged) { AgaData dataDepartments = evt.getAgaData("dataDepartments"); retrieveTheData( dataDepartments ); evt.setResult(m_vctDepartments); return; } else { if (lTimeStamp <= 0 || lTimeStamp < m_timestamp) { evt.setResult(m_vctDepartments); return; } else { // return null if the data has not changed evt.setResult(null); return; } } }
When invoked, the afterTableChanged event simply resets the boolean class variable. This value is checked in the invoked event.
public void afterTableChange(AgoTableEvent evt) { m_bDataChanged = true; return; }
This is the method that is initially called by the serverStart event Note the importance of storing the result in a class variable (m_vctDepartments), where it is accessible to the invoked event on the client.
public void retrieveTheData(AgaData phAgData) throws java.io.IOException { // Query the database try { phAgData.query(null); m_vctDepartments = new Vector(); boolean bResult = phAgData.gotoFirst(); int iCount =0; while (bResult) // get the data { String[] sRowValues = { (String) ( (Integer) phAgData.getProperty("departmentid")).toString(), (String) phAgData.getProperty("departmentname") }; // populate the Class vector variable with the // data when the server is started. // This provides access to the data // from the invoked event m_vctDepartments.addElement(sRowValues); bResult = phAgData.gotoNext(); } // reinitialize the Class variable to false m_bDataChanged = false; // reset the Class variable timestamp value m_timestamp = System.currentTimeMillis(); } catch (Exception e) { // reset Class variable to null m_vctDepartments = null; throw new java.io.IOException(e.toString()); } }