Object Oriented Design Course : Exercise 5

 

Object Oriented Design Course


Exercise 5 - Aspect-Oriented Programming in Java

Deadline

June 8rd, 2005 at midnight

Description

In this exercise you will:

·         Use Reflection and Dynamic Proxies in Java to create a simple generic logging framework

·         Use the AspectWerkz framework to create a similar framework using aspects

·         Compare the above two approaches to implementing aspect-oriented programming

Requirements

Many applications require the ability to create a log file of their operations. Logs are widely used in server applications, and are used for crash analysis, security hazard analysis and debugging issues that happen in an application's production use but don't happen in its development environment.

Although in this exercise the log framework will be simple - append lines of text to a file - in reality logging can be more complex, since it can involve writing over a network, combining several applications into a single log file, maintaining a maximum file size and so on. This is why creating a generic logging framework, that takes care of such issues, can be a time-saving idea. In this exercise you will implement such a framework in two different ways.

For example, assume that you would like to log calls to objects implementing the following interface:

interface Command {
 
  public void execute();
  public void undo();
  public void setParameter(String param, Object value);
  public Object getParameter(String param);
 
}

***This is only an example for an interface that can be logged. You are required to make your framework generic- it should work with any interface. Your Command design pattern from ex3 does not have to look like this for the specific implementation of the logger; it just needs to have an interface. Moreover, execute, undo, setParameter and getParameter are only examples for methods- no need for treating them differently from other methods. Your framework should work with methods with any name.

The code that defines how to write the log naturally needs to be separate from the usual application code. In our framework, a class needs to be written for each logged interface, which defines how to write the log. Such a class must implement the following interface

interface Logger {
 
  /**
   * Return the interface for which the logger defines how to write the log.
   */
  public Class[] getLoggedInterface();
 
}

A class that actually defines how to write the log for the above Command interface can be, for example:

class CommandsLogger implements Logger {
 
  public Class getLoggedInterface() {
    return Command.class;
  }
 
  public String before_execute(Command c) {
    return "Starting to execute command "+c.getClass().getName();
  }
 
  public String after_execute(Command c) {
    return "Finished executing command "+c.getClass().getName();
  }
 
  public String before_setParameter(Command c, String param, Object value) {
    return "setParameter is called to set "+param+" to "+value.toString();
  }
 
  public String before_getParameter(Command c, String param) {
    return "getParameter is called to get "+param;
  }
  public String after_getParameter(Command c, String param, Object returnValue) {
    return "getParameter returned the value "+returnValue.toString()+" for "+param;
  }
 
}

The rules and conventions for a logger class are as follows:

·         It must implement the Logger interface, and return the class that it logs in the getLoggedInterface method.

·         To write a message to the log before method XY is called, define a public method called before_XY. This method's first parameter's type  must be the logged interface (Command in the above example), and its other parameters must be of the same type, order and number as in the original logged method. This is how the logging framework will pass the logger the parameters of each call so they can be logged.

·         To write a message to the log after method XY has returned, define a public method called after_XY. This method's first parameter's type must be the logged interface; its next parameters must match the parameters of the original logged method; and if the method's return type is not void, then its last parameter is the method's return value. Therefore, in the above example after_execute only takes the executed Command as a parameter, but after_getParameter takes the Command, the parameter, and its returned value.

The logging framework abides the following rules:

·         Once an object that implements an interface is logged by a given logger (how to activate this is defined in the next sections), it calls the appropriate before_XY and after_XY methods, and writes the strings that they return to the log file. Each time a logged method is called, another line should be added to the log file. This line should contain the current time, a tab character, the returned log message, and a newline character. For example:
21/5/05 12:05:39       Starting to execute command UpcaseCommand
21/5/05 12:05:40       Finished executing command UpcaseCommand

·         A logger class doesn't have to define logging code for every method, or to define both before- and after- logging for a logged method. Therefore, if the logger class doesn't contain a method with the exact naming convention and parameter lists and the above conventions defined, the framework assumes that no logging should be done, and quietly ignores this.

·         The framework should correctly work with overloaded methods, i.e. logging a class that has two methods that have the same name but a different arguments list.

·         The after_XY method is only called if the method returns normally. If a method that has an after- method terminates with an exception, then instead of calling the after_XY method an error message should be written to the log, containing the terminated method's name and class name, and the error's message (e.getMessage()). For example:
21/5/05 12:05:39       Starting to execute command UpcaseCommand
21/5/05 12:05:40       Method 'execute' of interface 'Command' terminated with an exception: java.lang.ArrayIndexOutOfBoundsException

This should only happen for methods that have after- methods in the logger, and not just any method in the logged object that returns with an exception.

·         The framework does not need to support the following: class constructrors, finalizers, static methods, inner classes, or concrete classes (only interfaces can be logged).

·         Note that this framework requires reflection, to match between the caught method “methodName” and the appropriate before_methodName and after_methodName (similar code in dynamic proxies and aspectwerkz).

Logging Using Dynamic Proxies

To use the log framework when it is implementing using dynamic proxies, its users must know another class called LoggerProxy, that contains the following two static methods:

class LoggerProxy implements InvocationHandler {
 
  /**
   * Wraps the given object implementation with a dynamic proxy, that logs calls to the
   * object according to the given logger.
   * Throws an exception if objectImpl does not implement the interface that logger logs.
   */
  public static Object getLoggedObject(Object objectImpl, Logger logger);
  /**
   * Sets the file name to which the log must be written.
   * If the file doesn't exist, it will be created. If the file does exist, log messages will
   * be appended to its end, so the previous content of the file will not be deleted.
   * This method must be called at least once before the first call to getLoggedObject().
   * This is a global setting - changing the log file immediately affects all logged objects
   * in the program, both existing and future ones.
   */
  public static void setLogFileName(String logFileName);
}

To initialize the framework, the following code must be called (~/tmp/commads/log is an example):

LoggerProxy.setLogFileName("~/temp/commands.log");
Logger logger = new CommandsLogger();

And to create a new logged object, the following code must be called:

Command c = (Command) LoggerProxy.getLoggedObject(new UpcaseCommand(), logger);

Running the Dynamic Proxy should be an ant task which is not called by default and its name is runDynamicProxy.

Tip: This is very similar to the dynamic proxy example from class.

Logging Using AspectWerkz

The second part of the exercise is implementing the class XMLILoggerAspect which will inject the logging code to the desired place. Your Aspectwerkz code for the connection between pointcuts and advices should be written in the xml format, in a file called aspectwerkz.xml (Not as API). Your advices should be implemented in a java file called XMLLoggerAspect.java.

Assume that the user will supply the aspectwerkz.xml defining which classes will be logged (note that you are also considered users when you test and run this, so you should supply aspectwerkz.xml that fits to the command design pattern of ex3). The name of the log file will be set by the user in the logger.filename system property (See here to learn about system properties). This should be done as a property of aspectwerkz.xml with logger.filename=”log file name”. In contrast to the dynamic proxies’ implementation, here you do not need to support switching to another log file at runtime. Remember that in Aspectwerkz, unlike dynamic proxy, you don't need to add code that wraps each logged object (by calling LoggerProxy.getLoggedObject) to the logged application. It is done by the aspectwerkz.xml.

You should use “call” of aspectwerkz and not “execute” pointcuts.

Running the Aspectwerkz should be an ant task which is not called by default and its name is runAspectwerkz.

Using the Logging Framework within Xmli

To demonstrate your framework, you will embed it in the xmli project from ex3.

Your xmli project used commands in order to manipulate the display in one of several views, with the possibility to undo commands. For that, it was natural to use the Command design pattern. This is an excellent place to try and apply the logging framework.

Your logger framework should log each command the user is entering, with as little change of the existing code as you can. For each command, a line in the log file should be created, containing at least the date, name of the command, its input parameters, and its effect (the string written to the result label for that command). The log file should be called xmli.log in the current directory.

This should be done both for dynamic proxy and aspectwerkz. They both should use the same concrete logger for the commands.

Technical help with aspectwerkz

This is an example for an ant task to run aspectwerkz:

<target name="AspectTest" description="run the Aspect tests" depends="compile">

               <exec executable="/cs/course/2003/ood/aspectwerkz/bin/aspectwerkz" >

                       <arg line="-cp ${classpath} -Daspectwerkz.definition.file=aspectwerkz.xml           
                   ex5.Aspectwerkz"/>

               </exec>

        </target>

Here we run a test with aspectwerkz. This build file and aspectwerkz.xml are in the same place, one folder below ex5, which is the name of the project (you may pick any name). The test is running a file named Aspectwerkz.java (with the tests) which is in folder ex5.

Design Questions

In your README file, answer the two following questions:

1.      Compare the two techniques for implementing the generic logging framework. Specifically, address the following criteria: Ease of use (for both the framework developer and framework user), performance, flexibility and amount of code.

2.      Suggest a way to thoroughly test the generic framework you have written. You built a very generic framework, and only tested it on the simple Command interface of ex3, then many of its features remain untested (overloaded methods, two loggers for the same object, changing the log file dynamically)

 

Submission

 

·         You should not create UML diagrams for this exercise

·         You should submit unit tests that check that your code works, and the logging is done as it should.

·         Submit a zip file contains all your sources, a README.html, the build.xml and the shell script.

·         The default ant task should only compile and run unit tests. These should be unit tests from ex1, ex3 and one for each framework- dynamic proxy and aspectwerkz (four tasks+default). runDynamicProxy runAspectwerkz tasks should run the dynamic proxy and aspectwerkz respectively (two tasks).

·         Don’t forget to submit an aspectwerkz.xml that is appropriate to running your framework with aspectwerkz on the Command design pattern from ex3.

Resources

·         Dynamic Proxy Classes.

·         Explore the Dynamic Proxy API.

·         AspectWerkz homepage.

·         Ant's manual.

·         Guidelines.