How To Build Your Own Audit Rules

Written by Olivier Le Diouris, Oracle Corporation April 2004

Introduction

This document shows the basics steps involved in audit rules writing. First we'll give an overview of the audit framework in JDeveloper. Then, we'll step through the creation of a very simple rule, and we will show how to add features to it including automatic code fixing. Finally, we will show how to use a Wizard that automates building new rules.

Content

The Basics

Content

The rules run by the Audit Framework are implemented through a java object extending a specific class called oracle.jdeveloper.audit.analyzer.Analyzer. This class is available through the JDeveloper Extension SDK library.

The Analyzer class is implementing a design pattern called Visitor. The Rules you will define will have to be implemented as a JDeveloper Extension; as such, they will be managed by the Extension Manager. An Analyzer class will refer to a properties file that will hold the text associated with the Analyzer, including such properties as the rule name, the error message associated with the potentially broken rule, etc.

The implementation of your own set of rules will require at least:

  • A class extending the Analyzer, where your rules will be implemented
  • A properties-file, holding the strings and labels to be displayed
  • An Addin, to make this set of rule(s) available
  • The appropriate manifest-file associated with the Addin mentioned above.

The Javadoc for these classes contain more information that will help you. You can access it directly from inside JDeveloper.

A Simple Rule

Content

Design it

To write an Analyzer, you are going to extend the oracle.jdeveloper.audit.analyzer.Analyzer class, and possibly override the enter and exit methods. You might choose for these methods any signature you want, the parameter for these method is describing the Object that triggered it.
Theatrically, this Object could be any Object. Practically, it is going to be a JOT Object. JOT stands for Java Object Tool, it is the parser used by JDeveloper for the Java Code.
For example, if you have in your Analyzer a method like this one:

  public void exit (JotMethodCall obj)
  {
    // ...
  }        
       
after

The Example
  • Make sure that all the private variable names begin with an underscore character (_)
  • Unless they are private final static, in which case we need to check if their name is all in uppercase
package custom.rules;

import java.lang.reflect.Modifier;
import oracle.jdeveloper.audit.analyzer.Analyzer;
import oracle.jdeveloper.audit.analyzer.AuditContext;
import oracle.jdeveloper.audit.analyzer.Category;
import oracle.jdeveloper.audit.analyzer.Rule;
import oracle.jdeveloper.audit.analyzer.Severity;
import oracle.jdeveloper.audit.service.Localizer;
import oracle.jdeveloper.audit.analyzer.ViolationReport;
import oracle.jdeveloper.jot.JotField;

public class Variables extends Analyzer 
{
  // Refers to custom.rules.audit.properties
  private final static Localizer LOCALIZER = Localizer.instance("custom.rules.audit");
  private final Category SAMPLE_CATEGORY = new Category("sample-category", LOCALIZER);
  private final Rule NAME_VERIFICATION = 
                   new Rule("name-verification",
                            SAMPLE_CATEGORY,
                            Severity.CONVENTION,
                            LOCALIZER);
  public Variables()
  {
  }

  public Rule[] getRules()
  {
    return new Rule[] {
                       NAME_VERIFICATION
                      };
  }

  public void exit(AuditContext ctx, JotField obj)
  {
    if (NAME_VERIFICATION.isEnabled())
    {
      boolean ruleIsBroken = false;      
      
      if ((obj.getModifiers() & Modifier.PRIVATE) == Modifier.PRIVATE)
      {
        if ((obj.getModifiers() & Modifier.STATIC) == 0 &&
            (obj.getModifiers() & Modifier.FINAL) == 0)
        {
          // Starts with _
          if (!obj.getName().startsWith("_"))
            ruleIsBroken = true;
        }
        else
        {
          // All in uppercase
          if (!obj.getName().toUpperCase().equals(obj.getName()))
            ruleIsBroken = true;
        }
      }
      if (ruleIsBroken)
      {
         ViolationReport vr = ctx.report(NAME_VERIFICATION);
      }
    }
  }
}
       
category.sample-category.label=An Example
category.sample-category.tip=Tip for the example
category.sample-category.description=Description of the example

rule.name-verification.label=Variable Name Verification
rule.name-verification.tip=Tip for Variable Name Verification
rule.name-verification.description=Description for Variable Name Verification
rule.name-verification.message=That sucks!
       
First point to notice

Now, to have this piece of code available through the Audit Framework, you need to provide an Extension - that will in fact be an Addin - to expose it.

package custom.rules.launcher;

import oracle.jdeveloper.audit.AbstractAuditAddin;
import custom.rules.Variables;

public class StartRule extends AbstractAuditAddin
{
  private static final Class[] ANALYZERS = new Class[] { Variables.class };

  public Class[] getAnalyzers()
{
    return ANALYZERS;
  }
}
       
Second point to notice
oracle.jdeveloper.audit.AbstractAuditAddin
<?xml version='1.0' encoding='windows-1252'?>
<extensions xmlns="http://xmlns.oracle.com/jdeveloper/905/extensions">
  <feature>
    <group name="Additional Audit Rules">
      <group name="ForExample">
        <description>Generated as an Example</description>
        <extension>
          <addin>custom.rules.launcher.StartRule</addin>
          <dependency_addin>oracle.jdevimpl.audit.core.AuditMetricsAddin</dependency_addin>
        </extension>
      </group>
    </group>
  </feature>
</extensions>
       
Third point to notice

All those files are tightly related to each other, and have to be packaged as a jar-file. The jar file needs to be placed in the [JDEV_HOME]/jdev/lib/ext directory.

Run it

Just like for any regular extension, JDeveloper has to be restarted after dropping the jar-file in the right place.

Once this is done, the new Analyzer Addin can be seen by the Extension Manager:

Extension Manager

Watch a demo

Adding a Custom Message

Content
      if (ruleIsBroken)
      {
         ViolationReport vr = ctx.report(NAME_VERIFICATION);
      }
     
      if (ruleIsBroken)
      {
         String vName = obj.getName();
         ViolationReport vr = ctx.report(NAME_VERIFICATION);
         vr.addParameter("name", vName);
      }
     
rule.name-verification.message=That sucks!
     
rule.name-verification.message={name} breaks the naming convention
     

Custom Message

Using a User's Parameters

Content

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegExprTest 
{
  public RegExprTest()
  {
  // Compile regular expression
    String patternStr = "_[a-z]{3}_[A-Z]{4}";
    Pattern pattern = Pattern.compile(patternStr);
    // Determine if there is an exact match
    CharSequence inputStr = "anything";
    Matcher matcher = pattern.matcher(inputStr);
    boolean matchFound = matcher.matches();
    System.out.println(inputStr + (matchFound?" matches ":" does not match ") + patternStr);
    // Try a different input
    inputStr = "_abc_ABCD";
    matcher.reset(inputStr);
    matchFound = matcher.matches();
    System.out.println(inputStr + (matchFound?" matches ":" does not match ") + patternStr);
}

  public static void main(String[] args)
  {
    RegExprTest regExprTest = new RegExprTest();
  }
}     
  public static class SampleRule extends Rule
  {  
    String userPattern;
    
    public SampleRule(String name, 
                      Category category, 
                      Severity severity, 
                      Localizer localizer)
    {
      super(name, category, severity, localizer);
    }
    
    public String getUserPattern()
    { return userPattern; }
    
    public void setUserPattern(String str)
    { 
      String oldValue = userPattern;
      userPattern = str; 
      firePropertyChange("userPattern", oldValue, str);
    }
  }
      
  private final Rule NAME_VERIFICATION = 
                   new  
                              
SampleRule("name-verification",
                                  SAMPLE_CATEGORY,
                                  Severity.CONVENTION,
                                  LOCALIZER);
      
                            
      if ((obj.getModifiers() & Modifier.PRIVATE) == Modifier.PRIVATE)
      {
        if ((obj.getModifiers() & Modifier.STATIC) == 0 &&
            (obj.getModifiers() & Modifier.FINAL) == 0)
        {
          // Starts with _
          if (!obj.getName().startsWith("_"))
            ruleIsBroken = true;
        }
        else
          ...
      
      if ((obj.getModifiers() & Modifier.PRIVATE) == Modifier.PRIVATE)
      {
        if ((obj.getModifiers() & Modifier.STATIC) == 0 &&
            (obj.getModifiers() & Modifier.FINAL) == 0)
        {
          String patternStr = ((SampleRule)NAME_VERIFICATION).getUserPattern();
          String vName      = obj.getName();
          Pattern pattern = Pattern.compile(patternStr);
          Matcher matcher = pattern.matcher(vName);
          ruleIsBroken = !matcher.matches();
        }
        else
          ...
      

User input

_[a-z]{3}_[A-Z]{4}

Adding Automatic Code Fix

Content
oracle.jdeveloper.audit.transform.Transformapply

We are going to show how to implement a simple fix. Let us return the Analyzer code to its original form, suppressing the regular expression input from the user.

As two rules can be broken, we will generate two Transform. This is not mandatory; the two fixes could be implemented all in one Transform. We do it for clarity.

Let us take a look at the first transformer:

package custom.rules;

import oracle.jdeveloper.audit.model.Location;
import oracle.jdeveloper.audit.service.Localizer;
import oracle.jdeveloper.audit.transform.Transform;
import oracle.jdeveloper.jot.JotField;

public class UnderscoreFix extends Transform 
{
  boolean applied = false;
  
  public UnderscoreFix(String name, Localizer localizer)
  {
    super(name, localizer);
  }
  
  public void apply()
  {
    Object o = this.getConstruct();
    if (o instanceof JotField)
    {
      JotField jf = (JotField)o;
      String _old = jf.getName();
      String _new = "_" + _old;
      jf.setName(_new);
      applied = true;
    }
    else
    {
      System.out.println("Oops!");
      System.out.println("We are on a " + o.getClass().getName());
    }
  }
  
  public boolean isApplicable()
  {
    return true;
  }
  
  public boolean isApplied()
  {
    return applied;
  }
}
       


isApplicable()isApplied()


  private final static String ADD_UNDERSCORE      = "add-underscore";
     
transform.add-underscore.label=Add Underscore
transform.add-underscore.tip=Prefix name with an underscore
     
  private final Transform underscoreFix = new UnderscoreFix(CHANGE_TO_UPPERCASE, 
                                                            LOCALIZER);
     
  public void exit(AuditContext ctx, JotField obj)
  {
    if (NAME_VERIFICATION.isEnabled())
    {
      boolean ruleIsBroken = false;      
       
                              
Transform fix = null;
      
     
                            
        if ((obj.getModifiers() & Modifier.STATIC) == 0 &&
            (obj.getModifiers() & Modifier.FINAL) == 0)
        {
          // Starts with _
          if (!obj.getName().startsWith("_"))
          {
            ruleIsBroken = true;
             
                              
fix = underscoreFix;
          }
     
                            
      if (ruleIsBroken)
      {
         String vName = obj.getName();
         ViolationReport vr = ctx.report(NAME_VERIFICATION);
         vr.addParameter("name", vName);
          
                              
vr.addFix(fix);
          
                              
vr.setRecommendedFix(fix);
      }
     
                            

Watch a demo

Warning:

Running in Batch Mode, and from Ant

Content
Prompt> ojaudit
Oracle JDeveloper 10g Audit 9.0.5.1526
Copyright (c) 2003 Oracle Corporation. All Rights Reserved.

Audit source files.

Usage
  ojaudit option... file...

Parameters
  file                 Workspace, project, or source file to audit.

Options
  -encoding            Set character encoding for report.
  -help                Print command help (this message) and exit.           -h
  -output file         Set output file name.                                 -o
  -profile name        Set profile to use (required).                        -p
  -project file        Set project context for files to audit.
  -quiet               Suppress copyright message.                           -q
  -style file          Set XSLT style sheet to apply to report.              -s
  -title text          Set report title.
  -untitled            Set empty report title.
  -verbose             Print all messages.                                   -v
  -version             Print version and exit.
  -workspace file      Set workspace context for files to audit.             -w

* Repeat option to supply multiple values.
     
ojaudit -profile Example D:\Project\Location\SimpleSample.jpr
     
<?xml version="1.0" encoding="windows-1252" standalone="yes"?>
<!DOCTYPE audit [
  <!ATTLIST category id ID #REQUIRED>
  <!ATTLIST column id ID #REQUIRED>
  <!ATTLIST construct id ID #REQUIRED parent IDREF #REQUIRED root (false|true) "false">
  <!ATTLIST document id ID #REQUIRED>
  <!ATTLIST location document IDREF #REQUIRED>
  <!ATTLIST rule id ID #REQUIRED category IDREF #REQUIRED>
  <!ATTLIST value column IDREF #REQUIRED over-threshold (false|true) "false">
  <!ATTLIST violation rule IDREF #REQUIRED parent IDREF #REQUIRED>
]>

<audit xmlns="http://xmlns.oracle.com/jdeveloper/905/audit">
  <title>Audit Results for SimpleSample.jpr</title>
  <document-count>0</document-count>
  <violation-count>0</violation-count>
  <exception-count>0</exception-count>
  <locations>
    <location document="d0">
      <offset>0</offset>
      <length>2147483647</length>
      <line-number>1</line-number>
      <column-offset>0</column-offset>
    </location>
  </locations>
  <profile>
    <name>Example</name>
    <file>
      <url>file:/D:/JDev905.1506/jdev/system9.0.5.1.1506/audit/profiles/Example.xml</url>
      <path>D:\JDev905.1506\jdev\system9.0.5.1.1506\audit\profiles\Example.xml</path>
    </file>
  </profile>
  <columns>
    <column id="a0">
      <name>priority</name>
      <label>Priority</label>
      <description>column.priority.description: oracle.jdevimpl.audit.core.PriorityColumn@ba8879a4</description>
      <type>class oracle.jdeveloper.audit.analyzer.Priority</type>
      <threshold/>
    </column>
  </columns>
  <documents>
    <document id="d0">
      <project>
        <label>SimpleSample.jpr</label>
        <file>
          <url>file:/D:/___Oliv@work/AuditRules/SimpleSample/SimpleSample.jpr</url>
          <path>D:\___Oliv@work\AuditRules\SimpleSample\SimpleSample.jpr</path>
        </file>
      </project>
      <workspace>
        <label>audit0.jws</label>
        <file>
          <url>file:/D:/JDev905.1506/jdev/system9.0.5.1.1506/audit/temporary/audit0.jws</url>
          <path>D:\JDev905.1506\jdev\system9.0.5.1.1506\audit\temporary\audit0.jws</path>
        </file>
      </workspace>
      <label>SimpleSample.jpr</label>
    </document>
  </documents>
  <categories>
  </categories>
  <rules>
  </rules>
  <construct id="o0" parent="o0" root="true">
    <location document="d0">
      <offset>0</offset>
      <length>2147483647</length>
      <line-number>1</line-number>
      <column-offset>0</column-offset>
    </location>
    <type>class oracle.ide.model.Project</type>
    <kind>Project</kind>
    <name>file:/D:/___Oliv@work/AuditRules/SimpleSample/SimpleSample.jpr</name>
    <label>SimpleSample.jpr</label>
    <values>
      <value column="a0"/>
    </values>
    <children>
    </children>
  </construct>
</audit>
     
<?xml version = '1.0' encoding = 'windows-1252'?>
<project default="audit">
   <property name="project.name"  value="D:\File\Location\SimpleSample.jpr"/>
   <property name="output.name"   value="D:\File\Location\audit.xml"/>
   <property name="profile.name"  value="All"/>
   <property name="audit.home"    value="D:\JDev905.1506\jdev\bin"/>
   <property name="audit.exec"    value="ojaudit"/>

  <target name="audit">
     <exec executable="${audit.exec}" dir="${audit.home}"
           vmlauncher="false" os="Windows 2000">
       <arg line="-profile ${profile.name}"/>
       <arg line="-output ${output.name}"/>
       <arg line="-title 'Report Title'"/>
       <arg line="${project.name}"/>
     </exec>
  </target>
</project>
     
Prompt> ant
Buildfile: build.xml

audit:
     [exec] Oracle JDeveloper 10g Audit 9.0.5.1526
     [exec] Copyright (c) 2003 Oracle Corporation. All Rights Reserved.
     [exec]

BUILD SUCCESSFUL

Total time: 9 seconds
     

A Wizard

Content



How to use the Wizard
What to do after running the Wizard

If you're interested in the Wizard...

  • Download the executable, and drop the jar-file in the [JDEV_HOME]/jdev/lib/ext directory
  • Download the sources, and see for yourself.

false ,,,,,,,,,,,,,,,