wiki:TechnicalInfo/Extensions/AddingModelClasses
Last modified 5 years ago Last modified on 12/09/12 21:03:43

Adding A Custom System Event Type

This page described the steps necessary to extend Raptor with a new, custom, event class type. In overview, the following steps are necessary:

  1. Download the existing Raptor Model library ready for extension.
  2. Construct a new Event Type class which extends an existing base event type.
    1. Add necessary fields to the class.
    2. Add a valid hashcode and equals method that generates the business key of the class.
    3. Add a valid copy constructor for the class.
  3. Construct a hibernate xml file that describes how the new event type class will be persistent in the database.
  4. Package the new information model library using maven.
  5. Add the new library to the MUA or ICA's class path.

Download the Existing Raptor Model

Make sure your system has Apache Maven installed (http://maven.apache.org/). Create a directory on your hard drive, and checkout the latest Raptor information model into it e.g. change to the new directory you have created and on the command line type:

svn checkout https://iam.cf.ac.uk/repos/RAPTOR/raptor-information-model/tags .

You now have the latest version of the Raptor information model ready to add a new class into the uk/ac/cardiff/model/event directory. An example class is shown in the section below.

Constructing a new Event Type classs

A template class to construct a GenericEventType is shown below:

package uk.ac.cardiff.model.event;

import uk.ac.cardiff.utility.EqualsUtil;
import uk.ac.cardiff.utility.HashCodeUtil;
import uk.ac.cardiff.utility.StringUtils;

/**
 * The Class GenericAuthenticationEvent.
 * 
 */
public class GenericAuthenticationEvent extends AuthenticationEvent {

    /**
     * The name of a field not included in any of the superclasses and specific to this event.
     */
    private String someField;

    /**
     * Instantiates a new Generic Authentication Event.
     */
    public GenericAuthenticationEvent() {
        super();
    }

    /**
     * New instance.
     * 
     * @return the Generic Authentication Event
     */
    public static GenericAuthenticationEvent newInstance() {
        return new GenericAuthenticationEvent();
    }

    /**
     * Copy constructor.
     * 
     * @param event
     *            the event to copy
     */
    protected GenericAuthenticationEvent(GenericAuthenticationEvent event) {
        super(event);
        someField = event.getSomeField();

    }

    /**
     * Copy method. Alternative to clone. Returns a copied version of this event.
     * 
     * @return the Generic Authentication Event
     */
    public GenericAuthenticationEvent copy() {
        return new GenericAuthenticationEvent(this);
    }

    /**
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if ((obj == null) || (obj.getClass() != this.getClass()))
            return false;
        GenericAuthenticationEvent that = (GenericAuthenticationEvent) obj;
        boolean areEqual = EqualsUtil.areEqual(this.getEventTimeMillis(), that.getEventTimeMillis()) && EqualsUtil.areEqual(this.getEventId(), that.getEventId())
                && EqualsUtil.areEqual(this.getAuthenticationType(), that.getAuthenticationType()) && EqualsUtil.areEqual(this.getServiceHost(), that.getServiceHost())
                && EqualsUtil.areEqual(this.getResourceHost(), that.getResourceHost()) && EqualsUtil.areEqual(this.getPrincipalName(), that.getPrincipalName())
                && EqualsUtil.areEqual(this.getServiceId(), that.getServiceId()) && EqualsUtil.areEqual(this.getEventType(), that.getEventType())
                && EqualsUtil.areEqual(this.getResourceId(), that.getResourceId()) &&
                // all new fields should be included in the quality method under here.
                EqualsUtil.areEqual(this.getSomeField(), that.getSomeField());

        return areEqual;
    }

    /**
     * For hibernate, so the hashcode can be persisted.
     * 
     * @return the hash code
     */
    public int getHashCode() {
        return hashCode();
    }

    /**
     * For hibernate, does nothing as the hascode is computed on the fly from the <code>hashCode</code> method.
     * 
     * @param hashCode
     *            the new hash code
     */
    public void setHashCode(int hashCode) {

    }

    /**
     * create a unique hash, with as uniform a distribution as possible.
     * 
     * @return the int
     */
    @Override
    public int hashCode() {
        int hash = HashCodeUtil.SEED;
        // all inherited fields are hashed here.
        hash = HashCodeUtil.hash(hash, getEventTimeMillis());
        hash = HashCodeUtil.hash(hash, getAuthenticationType());
        hash = HashCodeUtil.hash(hash, getEventId());
        hash = HashCodeUtil.hash(hash, getServiceHost());
        hash = HashCodeUtil.hash(hash, getResourceHost());
        hash = HashCodeUtil.hash(hash, getPrincipalName());
        hash = HashCodeUtil.hash(hash, getEventType());
        hash = HashCodeUtil.hash(hash, getServiceId());
        hash = HashCodeUtil.hash(hash, getResourceId());
        // all new fields part of the business key should be hashed below.
        hash = HashCodeUtil.hash(hash, getSomeField());

        return hash;

    }

    /**
     * Automatic construction of a basic to string method for this class using reflection. Does not require modification unless different behavior is desired.
     */
    public String toString() {
        return StringUtils.buildToString(this);
    }

    /**
     * @param someField
     *            the someField to set
     */
    public void setSomeField(String someField) {
        this.someField = someField;
    }

    /**
     * @return the someField
     */
    public String getSomeField() {
        return someField;
    }

}

The class should be changed to whatever you desire, although the class should be created in the normal Cardiff Event package and hence the package should stay the same (allowing other package names may be added in the future).

The new class should contain any new fields which are specific to the new event, but not the same as fields in any of the superclasses. The fields already in the AuthenticationEvent and Event classes are:

  1. authenticationType - AuthenticationEvent.
  2. principalName - AuthenticationEvent.
  3. principalInformation.school - AuthenticationEvent.
  4. principalInformation.affiliation - AuthenticationEvent.
  5. eventTime - Event.
  6. serviceId - Event.
  7. eventType - Event.
  8. serviceHost - Event.
  9. resourceHost - Event.
  10. resourceId - Event.

Each new field should have associated GET and SET methods e.g.:

/**
     * @param someField
     *            the someField to set
     */
    public void setSomeField(String someField) {
        this.someField = someField;
    }

    /**
     * @return the someField
     */
    public String getSomeField() {
        return someField;
    }

In addition to a blank public constructor (required otherwise the class can not be instantiated dynamically), a copy constructor must be added. The copy constructor (see the comments in the code), should contain a mapping from the input event class to the current event class e.g.

    /**
     * Copy constructor.
     * 
     * @param event
     *            the event to copy
     */
    protected GenericAuthenticationEvent(GenericAuthenticationEvent event) {
        super(event);
        someField = event.getSomeField();
        ...
        otherField = event.getOtherField();

    }

The hashcode() and equals() methods should be completed to include the new fields e.g.

public int hashCode() {
        int hash = HashCodeUtil.SEED;
        // all inherited fields are hashed here.
        hash = HashCodeUtil.hash(hash, getEventTimeMillis());
        hash = HashCodeUtil.hash(hash, getAuthenticationType());
        hash = HashCodeUtil.hash(hash, getEventId());
        hash = HashCodeUtil.hash(hash, getServiceHost());
        hash = HashCodeUtil.hash(hash, getResourceHost());
        hash = HashCodeUtil.hash(hash, getPrincipalName());
        hash = HashCodeUtil.hash(hash, getEventType());
        hash = HashCodeUtil.hash(hash, getServiceId());
        hash = HashCodeUtil.hash(hash, getResourceId());
        // all new fields part of the business key should be hashed below.
        hash = HashCodeUtil.hash(hash, getSomeField());

        return hash;

    }

and

 public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if ((obj == null) || (obj.getClass() != this.getClass()))
            return false;
        GenericAuthenticationEvent that = (GenericAuthenticationEvent) obj;
        boolean areEqual = EqualsUtil.areEqual(this.getEventTimeMillis(), that.getEventTimeMillis()) && EqualsUtil.areEqual(this.getEventId(), that.getEventId())
                && EqualsUtil.areEqual(this.getAuthenticationType(), that.getAuthenticationType()) && EqualsUtil.areEqual(this.getServiceHost(), that.getServiceHost())
                && EqualsUtil.areEqual(this.getResourceHost(), that.getResourceHost()) && EqualsUtil.areEqual(this.getPrincipalName(), that.getPrincipalName())
                && EqualsUtil.areEqual(this.getServiceId(), that.getServiceId()) && EqualsUtil.areEqual(this.getEventType(), that.getEventType())
                && EqualsUtil.areEqual(this.getResourceId(), that.getResourceId()) &&
                // all new fields should be included in the equality method under here.
                EqualsUtil.areEqual(this.getSomeField(), that.getSomeField()) &&
                .....;

        return areEqual;
    }

Hibernate Config File

Edit the hibernate config file uk/ac/cardiff/model/event/event.hbm.xml. This file contains the logic to map from the Java class to the database schema. The file already contains the mappings from the out of the box event types. These should be followed as a template to adding the new type.

Of importance, the new type must exist as a union-subclass type. This creates a single concrete table per type in the database, where all fields (including those from inherited classes) are contained in the table. e.g.

    <union-subclass extends="uk.ac.cardiff.model.event.AuthenticationEvent" name="uk.ac.cardiff.model.event.GenericAuthenticationEvent">
        <property name="someField" column="someField" type="string" />
    </union-subclass>


Sending the new event type

For the ICA to send the new event type, it must be listed as an 'allowed' type in the event-release.xml}} file on the ICA. That is the bean {{{serviceEndpointInterface must contain the new class in the allowedClassTypes property e.g.

<property name="allowedClassTypes">
            <list>
                <value type="java.lang.Class">uk.ac.cardiff.model.event.ShibbolethIdpAuthenticationEvent</value>
                <value type="java.lang.Class">uk.ac.cardiff.model.event.ShibbolethSpAuthenticationEvent</value>
                <value type="java.lang.Class">uk.ac.cardiff.model.event.EzproxyAuthenticationEvent</value>
                <value type="java.lang.Class">uk.ac.cardiff.model.event.RadiusAuthenticationEvent</value>
                <value type="java.lang.Class">uk.ac.cardiff.model.event.AuthenticationEvent</value>
                        <value type="java.lang.Class">uk.ac.cardiff.model.event.GenericAuthenticationEvent</value>  ----HERE
            </list>        
       </property>

In addition, in order for it to get to the MUA, it must be set as a supportedEvent on the endpoint for a given MUA. For example:

<property name="supportedEvents">
                        <list>
                            <value type="java.lang.Class">uk.ac.cardiff.model.event.ShibbolethIdpAuthenticationEvent</value>
                            <value type="java.lang.Class">uk.ac.cardiff.model.event.EzproxyAuthenticationEvent</value>
                            <value type="java.lang.Class">uk.ac.cardiff.model.event.RadiusAuthenticationEvent</value>
                            <value type="java.lang.Class">uk.ac.cardiff.model.event.ShibbolethSpAuthenticationEvent</value>
                               <value type="java.lang.Class">uk.ac.cardiff.model.event.GenericAuthenticationEvent</value>  ---HERE
                        </list>
                    </property>

Receiving the new event type

the mua-core.xml file needs to have the new event type registered with it for it to receive events. Add to the aegisBean.xml the new event type:

  <value>uk.ac.cardiff.model.event.AuthenticationEvent</value>
                        <value>uk.ac.cardiff.model.event.ShibbolethIdpAuthenticationEvent</value>
                        <value>uk.ac.cardiff.model.event.ShibbolethSpAuthenticationEvent</value>
                        <value>uk.ac.cardiff.model.event.EzproxyAuthenticationEvent</value>
                        <value>uk.ac.cardiff.model.event.OpenathenslaAuthenticationEvent</value>
                        <value>uk.ac.cardiff.model.event.RadiusAuthenticationEvent</value>
                        <value>uk.ac.cardiff.model.event.GenericAuthenticationEvent</value>

Add to the registeredConcreteEventTypes bean the new event type:

 <bean class="uk.ac.cardiff.raptor.registry.RegisteredEventType">
                    <property name="eventType"> <value type="java.lang.Class">uk.ac.cardiff.model.GenericAuthenticationEvent</value></property>                    
                    <property name="concrete" value="true"/>
                </bean>