An implementation of Apache Isis' AuditingService3 API, persisting an AuditEntry record (through Isis' JDO ObjectStore) for each change to each object property.
Java HTML Shell Other
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
app
dom
fixture
images
integtests
webapp
.gitattributes
.gitignore
.travis.yml
LICENSE
README.adoc
bumpver.sh
bumpver_isis.sh
interim-release.sh
pom-jdo-enhance-all.xml
pom.xml
release.sh

README.adoc

isis-module-audit

Build Status

This module, intended for use within Apache Isis, provides an implementation of Isis' AuditerService SPI (introduced in 1.13.0, replacing the earlier AuditingService3 SPI). The implementation persists audit entries using Isis' own (JDO) objectstore. Typically this will be to a relational database; the module’s AuditEntry entity is mapped to the "isisaudit.AuditEntry" table.

Screenshots

The following screenshots show an example app’s usage of the module.

Installing the Fixture Data

Install some sample fixture data …

01 install fixtures

Object with initial (creation) audit entries

Because the example entity is annotated with @DomainObject(auditing = Auditing.ENABLED), the initial creation of that object already results in some audit entries:

02 first object with initial audit entries

As the screenshot shows, the demo app lists the audit entries for the example entity (a polymorphic association utilizing the BookmarkService).

Audit Entry for each changed property

Changing two properties on an object:

03 changing two properties on object

which results in two audit entries created, one for each property:

04 two audit entries created

Audit entry

The audit entry is an immutable record, can also inspect other audit entries created in the same transaction:

05 navigate to audit entry see other audit entries

It is of course also possible to navigate back to audited object:

06 navigate back to audited object

How to run the Demo App

The prerequisite software is:

  • Java JDK 8 (>= 1.9.0) or Java JDK 7 (<= 1.8.0)

    • note that the compile source and target remains at JDK 7

  • maven 3 (3.2.x is recommended).

To build the demo app:

git clone https://github.com/isisaddons/isis-module-audit.git
mvn clean install

To run the demo app:

cd webapp
mvn jetty:run

Then log on using user: sven, password: pass

Relationship to Apache Isis Core

Isis Core 1.6.0 included the org.apache.isis.module:isis-module-audit-jdo:1.6.0 Maven artifact. This module is a direct copy of that code, with the following changes:

  • package names have been altered from org.apache.isis to org.isisaddons.module.audit

  • the persistent-unit (in the JDO manifest) has changed from isis-module-audit-jdo to org-isisaddons-module-audit-dom

Otherwise the functionality is identical; warts and all!

Isis 1.7.0 (and later) no longer ships the org.apache.isis.module:isis-module-audit-jdo module; use this addon module instead.

How to Configure/Use

You can either use this module "out-of-the-box", or you can fork this repo and extend to your own requirements.

"Out-of-the-box"

To use "out-of-the-box":

  • update your classpath by adding this dependency in your project’s dom module’s pom.xml:

    <dependency>
        <groupId>org.isisaddons.module.audit</groupId>
        <artifactId>isis-module-audit-dom</artifactId>
        <version>1.14.0</version>
    </dependency>
  • if using AppManifest, then update its getModules() method:

    @Override
    public List<Class<?>> getModules() {
        return Arrays.asList(
                ...
                org.isisaddons.module.audit.AuditModule.class,
        );
    }
  • otherwise, update your WEB-INF/isis.properties:

    isis.services-installer=configuration-and-annotation
    isis.services.ServicesInstallerFromAnnotation.packagePrefix=\
                    ...,\
                    org.isisaddons.module.audit.dom,\
                    ...

Notes: * Check for later releases by searching Maven Central Repo.

+ For audit entries to be created when an object is changed, some configuration is required. This can be either on a case-by-case basis, or globally:

  • by default no object is treated as being audited unless it has explicitly annotated using @Audited. This is the option used in the example app described above.

  • alternatively, auditing can be globally enabled by adding a key to isis.properties:

    isis.services.audit.objects=all

An individual entity can then be explicitly excluded from being audited using @Audited(disabled=true).

"Out-of-the-box" (-SNAPSHOT)

If you want to use the current -SNAPSHOT, then the steps are the same as above, except:

  • when updating the classpath, specify the appropriate -SNAPSHOT version:

    <version>1.15.0-SNAPSHOT</version>
  • add the repository definition to pick up the most recent snapshot (we use the Cloudbees continuous integration service). We suggest defining the repository in a <profile>:

    <profile>
        <id>cloudbees-snapshots</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <repositories>
            <repository>
                <id>snapshots-repo</id>
                <url>http://repository-estatio.forge.cloudbees.com/snapshot/</url>
                <releases>
                    <enabled>false</enabled>
                </releases>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
        </repositories>
    </profile>

Forking the repo

If instead you want to extend this module’s functionality, then we recommend that you fork this repo. The repo is structured as follows:

  • pom.xml - parent pom

  • app - the app module used for bootstrapping, containing the AppManifest; depends on dom and fixture

  • dom - the module implementation, depends on Isis applib

  • fixture - fixtures, holding a sample domain objects and fixture scripts; depends on dom

  • integtests - integration tests for the module; depends on app

  • webapp - demo webapp (see above screenshots); depends on app

Only the dom project is released to Maven Central Repo. The versions of the other modules are purposely left at 0.0.1-SNAPSHOT because they are not intended to be released.

API

The AuditerService defines the following API:

@Programmatic
public void audit(
        final UUID transactionId,
        final int sequence,
        final String targetClass,
        final Bookmark target,
        final String memberIdentifier,
        final String propertyId,
        final String preValue,
        final String postValue,
        final String user,
        final java.sql.Timestamp timestamp);

Isis will automatically call this method on the service implementation if configured. The method is called often, once for every individual property of a domain object that is changed.

Implementation

The AuditerService API is implemented in this module by the org.isisaddons.module.audit.AuditerServiceUsingJdo class. This implementation simply persists an audit entry (AuditEntry) each time it is called. This results in a fine-grained audit trail.

The AuditEntry properties directly correspond to parameters of the AuditerService audit() API:

public class AuditEntry
    ...
    private UUID transactionId;
    private int sequence;
    private String targetClass;
    private String targetStr;
    private String memberIdentifier;
    private String propertyId;
    private String preValue;
    private String postValue;
    private String user;
    private Timestamp timestamp;
    ...
}

where:

  • transactionId is a unique identifier (a GUID) of the transaction in which this audit entry was persisted.

  • timestamp is the timestamp for the transaction

  • targetClass holds the class of the audited object, eg com.mycompany.myapp.Customer

  • targetStr stores a serialized form of the Bookmark, in other words a provides a mechanism to look up the audited object, eg CUS:1234 to identify customer with id 1234. ("CUS" corresponds to the @ObjectType annotation/facet).

  • memberIdentifier is the fully-qualified class and property Id, similar to the way that Javadoc words, eg com.mycompany.myapp.Customer#firstName

  • propertyId is the property identifier, eg firstName

  • preValue holds a string representation of the property’s value prior to it being changed. If the object has been created then it holds the value "[NEW]". If the string is too long, it will be truncated with ellipses '…'.

  • postValue holds a string representation of the property’s value after it was changed. If the object has been deleted then it holds the value "[DELETED]". If the string is too long, it will be truncated with ellipses '…'.

The combination of transactionId, targetStr and propertyId make up an alternative key to uniquely identify an audit entry. However, there is (deliberately) no uniqueness constraint to enforce this rule.

The AuditEntry entity is designed such that it can be rendered on an Isis user interface if required.

Supporting Services and Mixins

As well as the AuditingService service (that implements the AuditingService3 API), the module also provides two further domain services:

  • The AuditingServiceMenu provides actions to search for `AuditEntry`s, underneath an 'Activity' menu on the secondary menu bar.

  • AuditingServiceRepository provides the ability to search for persisted (AuditEntry) audit entries. None of its actions are visible in the user interface (they are all @Programmatic) and so this service is automatically registered.

  • HasTransactionId_auditEntries mixin contribues the auditEntries collection to the HasTransactionId interface. This will therefore display all audit entries that occurred in a given transaction, in other words whenever a command, a published event or another audit entry is displayed.

(As of 1.8.x and later) these services are automatically registered, meaning that any UI functionality they provide will appear in the user interface. If this is not required, then either use security permissions or write a vetoing subscriber on the event bus to hide this functionality, eg:

@DomainService(nature = NatureOfService.DOMAIN)
public class HideIsisAddonsAuditingFunctionality extends AbstractSubscriber {
    @Programmatic @Subscribe
    public void on(final AuditingModule.ActionDomainEvent<?> event) { event.hide(); }
}

As well as defining the AuditingService3 API, Isis' applib also defines several other closely related services. Implementations of these services are referenced by the Isis Add-ons website.

The CommandContext defines the Command class which provides request-scoped information about an action invocation. Commands can be thought of as being the cause of an action; they are created "before the fact". Some of the parameters passed to AuditingService3 - such as target, user, and timestamp - correspond exactly to the Command class.

The CommandService service is an optional service that acts as a Command factory and allows Command`s to be persisted. `CommandService’s API introduces the concept of a `transactionId; once again this is the same value as is passed to the AuditingService3.

The PublishingService is another optional service that allows an event to be published when either an object has changed or an actions has been invoked. There are some similarities between publishing to auditing; they both occur "after the fact". However the publishing service’s primary use case is to enable inter-system co-ordination (in DDD terminology, between bounded contexts). As such, publishing is much coarser-grained than auditing, and not every change need be published. Publishing also uses the transactionId.

The CommandService and PublishingService are optional; as with the AuditingService3, Isis will automatically use call each if the service implementation if discovered on the classpath.

If all these services are configured - such that commands, audit entries and published events are all persisted, then the transactionId that is common to all enables seamless navigation between each. (This is implemented through contributed actions/properties/collections; AuditEntry implements the HasTransactionId interface in Isis' applib, and it is this interface that each module has services that contribute to).

Known issues

In 1.6.0 through 1.9.x a call to DomainObjectContainer#flush() is required in order that any newly created objects are populated. Note that Isis automatically performs a flush prior to any repository call, so in many cases there may not be any need to call flush explicitly.

Change Log

  • 1.13.0 - updated for Isis 1.13.0, implementing the new AuditerService SPI rather than the now deprecated AuditingService3 SPI.

  • 1.12.1 - released against Isis 1.12.1, providing 'Metadata' fieldset, also using lombok and mixins internally

  • 1.12.0 - released against Isis 1.12.0

  • 1.11.0 - released against Isis 1.11.0

  • 1.10.0 - released against Isis 1.10.0

  • 1.9.0 - released against Isis 1.9.0; changed mapped of entities to 'isisaudit' schema; updated to use AppManifest

  • 1.8.2 - released against Isis 1.8.0; closes <a href="https://github.com/isisaddons/isis-module-audit/issues/1">#1</a>

  • 1.8.1 - released against Isis 1.8.0 (fixed).

  • 1.8.0 - released against Isis 1.8.0 (nb: this was a bad release, incorrectly referenced -SNAPSHOT version of Isis core).

  • 1.7.0 - released against Isis 1.7.0

  • 1.6.0 - re-released as part of isisaddons, with classes under package org.isisaddons.module.audit

Data Migration

The format of the AuditEntry table has changed from 1.12.x to 1.13.x, incorporating a new sequence column (which is also part of the primary key).

The following script updates the table (SQL Server syntax):

alter table isisaudit.AuditEntry
  add "sequence" int
go

update isisaudit.AuditEntry
   set "sequence" = 0
 where "sequence" is null
go

alter table isisaudit.AuditEntry
  alter column "sequence" not null
go

drop index AuditEntry_ak
  on isisaudit.AuditEntry
go

create index AuditEntry_ak
  on isisaudit.AuditEntry
(
   transactionId ASC,
   "sequence"    ASC,
   target        ASC,
   propertyId    ASC
)
go

License

Copyright 2014-2016 Dan Haywood

Licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.

Dependencies

There are no third-party dependencies.

Maven deploy notes

Only the dom module is deployed, and is done so using Sonatype’s OSS support (see user guide).

Release to Sonatype’s Snapshot Repo

To deploy a snapshot, use:

pushd dom
mvn clean deploy
popd

The artifacts should be available in Sonatype’s Snapshot Repo.

Release an Interim Build

If you have commit access to this project (or a fork of your own) then you can create interim releases using the interim-release.sh script.

The idea is that this will - in a new branch - update the dom/pom.xml with a timestamped version (eg 1.13.0.20161017-0738). It then pushes the branch (and a tag) to the specified remote.

A CI server such as Jenkins can monitor the branches matching the wildcard origin/interim/* and create a build. These artifacts can then be published to a snapshot repository.

For example:

sh interim-release.sh 1.15.0 origin

where

  • 1.15.0 is the base release

  • origin is the name of the remote to which you have permissions to write to.

Release to Maven Central

The release.sh script automates the release process. It performs the following:

  • performs a sanity check (mvn clean install -o) that everything builds ok

  • bumps the pom.xml to a specified release version, and tag

  • performs a double check (mvn clean install -o) that everything still builds ok

  • releases the code using mvn clean deploy

  • bumps the pom.xml to a specified release version

For example:

sh release.sh 1.14.0 \
              1.15.0-SNAPSHOT \
              dan@haywood-associates.co.uk \
              "this is not really my passphrase"

where * $1 is the release version * $2 is the snapshot version * $3 is the email of the secret key (~/.gnupg/secring.gpg) to use for signing * $4 is the corresponding passphrase for that secret key.

Other ways of specifying the key and passphrase are available, see the `pgp-maven-plugin’s documentation).

If the script completes successfully, then push changes:

git push origin master && git push origin 1.14.0

If the script fails to complete, then identify the cause, perform a git reset --hard to start over and fix the issue before trying again. Note that in the dom’s `pom.xml the nexus-staging-maven-plugin has the autoReleaseAfterClose setting set to true (to automatically stage, close and the release the repo). You may want to set this to false if debugging an issue.

According to Sonatype’s guide, it takes about 10 minutes to sync, but up to 2 hours to update search.