Friday, August 8, 2008

Documentum Search Audit Trails

Q) How to collect audit trails of searches performed by users?
Documentum does not capture any audit events for searches performed. However, search statistics and reports can also be used to identify frequently used keywords and tune the search engine to provide accurate results.

The statistics can also be used for creating management reports if needed.

Design Approach:
1. Create a new persistent object (”sp_search_log”) to store Search log information
2. Customize the search component’s behaviour class’s onRenderEnd() method to create a new “sp_search_log” object
3. Save the object before displaying the JSP

Alternate Approaches:
1. Use JDBC to capture the information in a database table. Complicated approach involving opening database connections.
2. Create custom audit trails to create dm_audittrail objects. I have not yet studied the implications of this.

CREATING A NEW TYPE to store Search Logs:
CREATE TYPE "sp_search_log"
( “r_search_id” ID,
“userid” CHAR(10),
“userdisplayname” CHAR(200),
“deptcode” CHAR(6),
“keyword” CHAR(100) REPEATING,
“location” CHAR(250) REPEATING,
“attrib_namevalue” CHAR(250) REPEATING,
“starttimeofsearch” DATE,
“endtimeofsearch” DATE,
“noofresults” INT,
“noofvieweddocs” INT
) WITH SUPERTYPE NULL PUBLISH

OUTPUT OF DQL > new_object_ID 030004d2800001b9

ALTER TYPE “sp_search_log” DROP_FTINDEX ON “userid”
ALTER TYPE “sp_search_log” DROP_FTINDEX ON “userdisplayname”
ALTER TYPE “sp_search_log” DROP_FTINDEX ON “deptcode”
ALTER TYPE “sp_search_log” DROP_FTINDEX ON “location”
ALTER TYPE “sp_search_log” DROP_FTINDEX ON “attrib_namevalue”

Use this DQL to drop any fields if needed:
ALTER TYPE “sp_search_log” DROP “Field-Name” PUBLISH

Use this DQL to add new fields if needed later:
ALTER TYPE “sp_search_log” ADD “New-Field-Name” DATE PUBLISH
Note:
- “attrib_namevalue” CHAR(250) REPEATING will be used to store the params from advanced search in the form date=22/01/2006, etc.
- If the user uses a phrase search like “new york”, it can be stored in one keyword. If new york is used without quotes, it will be stored as two keywords

SAMPLE JAVA SOURCE CODE FRAGMENTS
Note: This code is meant to prove the concept. This may not be the best approach for performance.
A better approach could be to store the “starttimeofsearch” in an instance variable then, create & save the

sp_search_log object only once after the search operation is completed.

public class SearchEx extends com.documentum.dam.search.SearchEx
implements IControlListener, IDfQueryListener, Observer,
IReturnListener, IDragDropDataProvider, IDragSource, IDropTarget
{
private boolean m_loggedToDB = false;
private boolean m_loggedNoOfResultsToDB = false;
private boolean m_isFirstCall = true;public void onInit(ArgumentList args)
{
System.out.println(”## Inside custom search”);
String strQuery = args.get(”query”);
System.out.println(”## strQuery: ” + strQuery);
super.onInit(args);
}

public void onRenderEnd()
{
super.onRenderEnd();

if(m_loggedToDB == false && m_isFirstCall==true) {
createSearchLogObject();
m_isFirstCall = false;
}

if(m_loggedToDB == true && m_isFirstCall==false && m_loggedNoOfResultsToDB==false) {
updateSearchLogObject();
}
}

private void createSearchLogObject(){
String objectId = null;

IDfSession sess = this.getDfSession();

String userid = “Not found”;
try {
userid = sess.getLoginUserName();
System.out.println(”### userid: ” + userid);

String queryDesc = getQueryDescription();
System.out.println(”### queryDesc: ” + queryDesc);

IDfPersistentObject searchLog =
(IDfPersistentObject)sess.newObject(”sp_search_log”);
searchLog.setString(”userid”, userid);
searchLog.setString(”userdisplayname”, userid);
//searchLog.setString(”deptcode”, “DEPT_CODE GOES HERE”);

IDfTime timeNow = new DfTime();
searchLog.setTime(”starttimeofsearch”, timeNow);
searchLog.setInt(”noofresults”,-1);
setNewValuesForAttribute(”keyword”, queryDesc, ” “, searchLog);

String searchLocations = getSearchSources();
setNewValuesForAttribute(”location”, searchLocations, “,”, searchLog);

searchLog.save();

m_NewSearchLogObjectId = searchLog.getObjectId().getId();
System.out.println(”************ Saved Search Log ************” + objectId);
m_loggedToDB = true;
} catch (DfException e) {
e.printStackTrace();
}

}

private void updateSearchLogObject(){
System.out.println(”### Updating the record”);

Datagrid datagrid = (Datagrid)getControl(”doclistgrid”,

com.documentum.web.form.control.databound.Datagrid.class);
//Get total number of results available from the underlying DataHandler
//Note that a value of -1 indicates that the DataHandler does not support results counting.
int noOfResults = datagrid.getDataProvider().getResultsCount();
System.out.println(”Datagrid noOfResults: ” + noOfResults );
if(noOfResults != -1) {
IDfSession sess = this.getDfSession();

IDfClientX clientx = new DfClientX();
try {
IDfPersistentObject searchLog = (IDfPersistentObject)sess.getObject(
clientx.getId(m_NewSearchLogObjectId));

IDfTime timeNow = new DfTime();
searchLog.setTime(”endtimeofsearch”, timeNow);
searchLog.setInt(”noofresults”,noOfResults);

searchLog.save();
m_loggedNoOfResultsToDB=true;
System.out.println(”************ Updated Search Log ************”);
} catch (DfException e) {
e.printStackTrace();
}
}

private void setNewValuesForAttribute(String attributeName,
String queryString, String delimiter, IDfPersistentObject obj) throws DfException {

StringTokenizer st = new StringTokenizer(queryString, delimiter);
for (int i = 0; st.hasMoreTokens(); i++) {
obj.appendString(attributeName, st.nextToken());
}
}

No comments: