Creating Site Access Reports from Audit
In our previous blog we described how you enable auditing, create a custom Audit Application, create a custom data extractor and also how to check the custom audit application is working. In this blog we will describe the creation of a custom scheduled job that queries the audit service and reports on access for each share site.
Alfresco Scheduled Job Options
For this solution we considered two options:
- Option1: Use the runScriptAction job
The runScriptAction job allows executing a javascript file located in the alfresco repository. So we thought we would create a javascript file which would call the audit query webscript and then generate a report based on the returned json object.
- Option2: Use a custom scheduled job
Alfresco allows us to define a custom scheduled job from which we can call a java class that can either call the audit query webscript or query the audit using the AuditService bean to generate the audit report.
We initially thought using the runScriptAction option would be best but were surprised to find that the remote object that is used to call out to the audit webscript was not available in the repository js layer. Also note, we found we could enable the ‘remote’ object to be accessible for the repository webscript framework through javascript but not for the repository scheduled job framework.
Alfresco Scheduled Job
We decided to use the custom scheduled job option. The advantage of developing a solution in java is that it is a much cleaner solution to use the alfresco Audit Service rather than making a call to a webscript and processing the returned json as the AuditService bean presents the already extracted values to you in each iteration of the audit entries.
We first created our own custom alfresco scheduled job context file called custom-schedule-job-context.xml located in tomcat/shared/classes/alfresco/extension. We declared 2 beans in our context file. The first was the runCustomAuditJob bean, which registers our custom java class to call and also injects the required repository services. The Second bean was the customAuditJobTrigger bean which defines the cron expression to determine how often the job runs. The scheduled job context file is as follows;
<?xml version=’1.0′ encoding=’UTF-8′?>
<!DOCTYPE beans PUBLIC ‘-//SPRING//DTD BEAN//EN’ ‘http://www.springframework.org/dtd/spring-beans.dtd’>
<beans>
<bean id=”runCustomAuditJob” class=”org.springframework.scheduling.quartz.JobDetailBean”>
<property name=”jobClass”>
<!—The java class to call–>
<value>au.com.seedim.audit.auditjob.AuditSiteJob</value>
</property>
<property name=”jobDataAsMap”>
<map>
<!—The services required by java class to call–>
<entry key=”abstractAuthenticationService”>
<ref bean=”authenticationService” />
</entry>
<entry key=”auditService”>
<ref bean=”AuditService” />
</entry>
<entry key=”nodeService”>
<ref bean=”NodeService” />
</entry>
<entry key=”contentService”>
<ref bean=”ContentService” />
</entry>
<entry key=”searchService”>
<ref bean=”SearchService” />
</entry>
<entry key=”siteService”>
<ref bean=”SiteService” />
</entry>
</map>
</property>
</bean>
<!—The cron/schedule on which to execute our custom job–>
<bean id=”customAuditJobTrigger” class=”org.alfresco.util.CronTriggerBean”>
<property name=”jobDetail”>
<!—The bean id of custom job–>
<ref bean=”runCustomAuditJob”/>
</property>
<property name=”scheduler”>
<ref bean=”schedulerFactory”/>
</property>
<!—The job configured to run every 4 hours–>
<property name=”cronExpression”>
<value>0 0 0/4 * * ?</value>
</property>
</bean>
Custom Scheduled Job Implementation
In order for the java class to be executed from our custom schedule job, it needs to implement org.quartz.StatefulJob interface.The snippet of the execute method is shown below. The code queries the audit application we created in part 1, using the key of “siteaccess”. The query method accepts a callback object that is called for every entry returned when querying the audit log using the key. The third argument set to “0” specifies to return all audit entries;
public void execute(JobExecutionContext arg0) throws JobExecutionException {
if (logger.isDebugEnabled()) logger.debug(“Inside auditSiteJob execute Method”);
AuthenticationUtil.runAs(
new AuthenticationUtil.RunAsWork<Object>() {
public Object doWork() {
if (auditService.isAuditEnabled()== true){
if (logger.isDebugEnabled())logger.debug(“Audit Service is enabled”);
AuditQueryParameters params = new AuditQueryParameters();
params.setApplicationName(“siteaccess”);
Map<String, Object> sites = new HashMap<String, Object>();
SiteAccessAuditCallBack myCallback = new SiteAccessAuditCallBack(sites);
//logger.debug(params.getApplicationName());
auditService.auditQuery(myCallback, params, 0);
auditDataRendering(sites);
}
return null;
}
},AuthenticationUtil.getSystemUserName());
}
Note we have used ‘AuthenticationUtil.runAs’ so that the method is executed as the user ‘System’ and the two main methods which are auditQuery and auditDataRendering are discussed further below.
Audit Query Method
As discussed, the auditQuery method from the auditService class is called to get our audit data. The SiteAccessAuditCallBack class implements AuditService.AuditQueryCallback interface and is passed as one of the arguments of the auditQuery method. A snippet of the handleAuditEntry method which is defined in the SiteAccessAuditCallBack class is shown as follows. The handleAuditEntry method updates our target hashmaps with entries from the audit log and uses the site name as the key for the hashmap , while updating the count for reads, creates and updates as values against the site key;
public boolean handleAuditEntry(Long entryId,
String applicationName,
String user,
long time,
Map<String,Serializable> values) {
try{
if (logger.isDebugEnabled())logger.debug(“handleAuditEntry: Entered” );
String accessMethod = null;
String siteName = null;
String siteUser = null;
if (values != null)
{
if(values.containsKey(“/siteaccess/site”)){
siteName = (String)values.get(“/siteaccess/site”);
//logger.debug(“Site: ” + siteName);
}
if(values.containsKey(“/siteaccess/access”)){
accessMethod = (String)values.get(“/siteaccess/access”);
//logger.debug(“Access: ” + accessMethod);
}
if(values.containsKey(“/siteaccess/user”)){
siteUser = (String)values.get(“/siteaccess/user”);
//logger.debug(“User: ” + siteMember);
}
}
if(siteName != null){
SiteAuditAccessObject myVals = null;
if(sites.get(siteName) != null){
myVals = (SiteAuditAccessObject)sites.get(siteName);
}
else{
myVals = new SiteAuditAccessObject();
}
myVals.updateAccess(accessMethod);
myVals.updateSiteMembers(siteUser);
sites.put(siteName, myVals);
}
}finally{
if (logger.isDebugEnabled())logger.debug(“handleAuditEntry: Exited” );
}
return true;
}
Audit Report Generation
The auditDataRendering method calls the createContentNode method to create our report. The createContentNode method uses the Node Service and Content Service to create a node in the alfresco repository and populate its content with the audit data respectively.
private NodeRef createContentNode(NodeRef parent, String name, String text)
{
if (logger.isDebugEnabled()) logger.debug(“Inside auditSiteJob createContentNode Method”);
// Create a map to contain the values of the properties of the node
Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
props.put(ContentModel.PROP_NAME, name);
// use the node service to create a new node
NodeRef node = this.nodeService.createNode(
parent,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name),
ContentModel.TYPE_CONTENT,
props).getChildRef();
// Use the content service to set the content onto the newly created node
ContentWriter writer = this.contentService.getWriter(node, ContentModel.PROP_CONTENT, true);
//writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
writer.setEncoding(“UTF-8”);
writer.putContent(text);
// Return a node reference to the newly created node
return node;
}
Conclusion
This concludes the blog about creation of a custom scheduled job that queries the audit service and reports on access for each share site. It illustrates how to write jobs in Alfresco by implementing the org.quartz.StatefulJob and how to use the AuditService to query a custom audit application. In our next blog we will discuss the creation of a custom scheduled job that refreshes the audit data after a predefined period of time.