Interceptors for auditing

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Interceptors for auditing

Ronald Albury
I read, with some interest, a discussion in this group on auditing.
One suggestion was to use p6spy - however their home web page has been
down for a while and that doesn't give me a whole lot of confidence.

The suggestion that seemed the best was to use the 'update'
interceptor for auditing.  I have been playing with it for a while and
have run into an issue that has me chasing my own tail.

It seemed at first that I could use the Invocation to get the
MappedStatement, and then the MappedStatement to get the BoundSql, and
then the BoundSql to get the parameterObject.  This was really good,
because it let me know exactly what mapped statement was being called
and what values were being applied to it.

However, I have run into a couple of cases where the parameterObject
is *not* the object I passed into the statement, but rather it was a
DefaultSqlSession.  I can see my parameter object hiding in there, but
I am having difficulty getting it out.  Also, is there any way to know
(other than instanceof) to know when I have easy access to my
parameter and when it has been hidden (in something like a
DefaultSqlSession).

So - put more concisely:  What is a clean, standard, repeatable way to
capture the parameter object I pass in when I call the insert/update/
delete method?

Also - since I haven't yet been able to consistently get my parameter,
I haven't addressed the issue of actually storing the audit data.  Can
I call another insert (to the audit table) *while* I am inside the
interceptor?  Do I have to, instead, log to a file or use some other
mechanism?  If I can call another insert, is there an easy way for the
interceptor on on that statement to know this is an audit call (and
not get caught in an endless loop) - or do I just need to check the
MappedStatement and confirm it is *not* an auditing mapped statement.

Any assistance would be greatly appreciated.  I'm sure someone's
solved this issue already, but I haven't had much look searching the
web.  Thanks

Ron
Reply | Threaded
Open this post in threaded view
|

Re: Interceptors for auditing

Andrius Juozapaitis
I use something like this to do lucene integration - it's an aspectj
solution. It checks if the parameter being passed is annotated with
Indexed annotation, and indexes/reindexes/deletes it according to the
statement being executed. The small problem with this approach is that
update method is being called by the insert and delete under the hood,
so I have to check that the update method invocation stack doesn't
have insert or delete methods in it.

Let me know if you need help with that - and I would also consider
using the mybatis interceptors instead, but so far I didn't find a way
to utilize them for the specific task at hand.

regards,
Andrius Juozapaitis
----
package com.ajaxelements.server.lucene.aspects;

import com.ajaxelements.server.LuceneObjectIndexer;
import com.ajaxelements.server.lucene.IndexerContainer;
import com.ajaxelements.shared.data.DatabaseEntity;
import com.ajaxelements.shared.lucene.Indexed;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.reflections.vfs.Vfs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;

@Aspect
@Component
@Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class LuceneIntegrationAspect {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    int depth = 0;
    @Autowired(required = true)
    private JmsTemplate jmsTemplate;

    @Autowired
    IndexerContainer luceneIndexContainer;

    @Autowired
    private LuceneObjectIndexer luceneObjectIndexer;

    /*
 org.apache.ibatis.session.SqlSession;
  int insert(String statement, Object parameter);
  int update(String statement, Object parameter);
  int delete(String statement, Object parameter);
     */

    private boolean isSelfInvocation() {
        boolean selfInvocation = false;
        for (StackTraceElement el : Thread.currentThread().getStackTrace()) {
            if (el.getClassName().equals("org.apache.ibatis.session.defaults.DefaultSqlSession")
&&
                    (el.getMethodName().startsWith("delete") ||
el.getMethodName().startsWith("insert"))) {
                selfInvocation = true;
                break;
            }
        }
        return selfInvocation;
    }
    @After(value = "execution(public *
org.apache.ibatis.session.defaults.DefaultSqlSession.update(*,*)) &&
args(s,o)", argNames = "s,o")
    public void updateIndex(String s, Object o) {
        if (!isSelfInvocation()) {

            if (o instanceof DatabaseEntity) {
                Indexed indexed = o.getClass().getAnnotation(Indexed.class);
                if (indexed != null) {
                    String indexTitle = indexed.indexTitle();

                    try {
                        Document doc = new Document();
                        IndexWriter writer =
luceneIndexContainer.getIndexer(indexTitle);
                        writer.updateDocument(new Term("id", "" +
((DatabaseEntity) o).getId()),
                                luceneObjectIndexer.index(doc, "",
(DatabaseEntity) o));
                    } catch (IOException e) {
                        logger.debug("Error deleting entry from index: " + e);
                    }
                }
            }
        }
    }


    @After(value = "execution(public *
org.apache.ibatis.session.defaults.DefaultSqlSession.insert(*,*)) &&
args(s,o)", argNames = "s,o")
    public void insertIndex(String s, final Object o) {

        if (o instanceof DatabaseEntity) {
            Indexed indexed = o.getClass().getAnnotation(Indexed.class);
            if (indexed != null) {
                String indexTitle = indexed.indexTitle();

                try {
                    Document doc = new Document();
                    IndexWriter writer =
luceneIndexContainer.getIndexer(indexTitle);
                    writer.addDocument(luceneObjectIndexer.index(doc,
"", (DatabaseEntity) o));
                } catch (IOException e) {
                    logger.debug("Error inserting entry to index: " + e);
                }
            }
        }
    }

    @After(value = "execution(public *
org.apache.ibatis.session.defaults.DefaultSqlSession.delete(*,*)) &&
args(s,o)", argNames = "s,o")
    public void deleteIndex(String s, Object o) {
        if (o instanceof DatabaseEntity) {
            Indexed indexed = o.getClass().getAnnotation(Indexed.class);
            if (indexed != null) {
                String indexTitle = indexed.indexTitle();

                try {
                    IndexWriter writer =
luceneIndexContainer.getIndexer(indexTitle);
                    writer.deleteDocuments(new Term("id", "" +
((DatabaseEntity) o).getId()));
                } catch (IOException e) {
                    logger.debug("Error deleting entry from index: " + e);
                }
            }
        }

//        logger.debug("updating index [delete] for {}", o);
    }
}



On Fri, Dec 3, 2010 at 3:58 AM, Ronald Albury <[hidden email]> wrote:

> I read, with some interest, a discussion in this group on auditing.
> One suggestion was to use p6spy - however their home web page has been
> down for a while and that doesn't give me a whole lot of confidence.
>
> The suggestion that seemed the best was to use the 'update'
> interceptor for auditing.  I have been playing with it for a while and
> have run into an issue that has me chasing my own tail.
>
> It seemed at first that I could use the Invocation to get the
> MappedStatement, and then the MappedStatement to get the BoundSql, and
> then the BoundSql to get the parameterObject.  This was really good,
> because it let me know exactly what mapped statement was being called
> and what values were being applied to it.
>
> However, I have run into a couple of cases where the parameterObject
> is *not* the object I passed into the statement, but rather it was a
> DefaultSqlSession.  I can see my parameter object hiding in there, but
> I am having difficulty getting it out.  Also, is there any way to know
> (other than instanceof) to know when I have easy access to my
> parameter and when it has been hidden (in something like a
> DefaultSqlSession).
>
> So - put more concisely:  What is a clean, standard, repeatable way to
> capture the parameter object I pass in when I call the insert/update/
> delete method?
>
> Also - since I haven't yet been able to consistently get my parameter,
> I haven't addressed the issue of actually storing the audit data.  Can
> I call another insert (to the audit table) *while* I am inside the
> interceptor?  Do I have to, instead, log to a file or use some other
> mechanism?  If I can call another insert, is there an easy way for the
> interceptor on on that statement to know this is an audit call (and
> not get caught in an endless loop) - or do I just need to check the
> MappedStatement and confirm it is *not* an auditing mapped statement.
>
> Any assistance would be greatly appreciated.  I'm sure someone's
> solved this issue already, but I haven't had much look searching the
> web.  Thanks
>
> Ron
Reply | Threaded
Open this post in threaded view
|

Re: Interceptors for auditing

Ronald Albury
OK - I gave up on MyBatis interceptors.  I considered various aspect
oriented programming packages out there, but came up with a very
simple solution.  I wrapped the Factory class in my own SCSFactory
class, and it hands out SCSSession objects (which are a wrapper around
the MyBatis session).   The auditing is done via the SCSSession object
- every call now requires you to pass in a UserCredential object.

I'm sure purists will object to my solution ... but a) very simple; b)
all my own code; c) can require UserCredential for each method call.
It all works great, and only took me about an hour to implement.
Reply | Threaded
Open this post in threaded view
|

Re: Interceptors for auditing

Larry Meadors
On Tue, Dec 7, 2010 at 9:10 AM, Ronald Albury <[hidden email]> wrote:
> I'm sure purists will object to my solution ...

Meh, purists are always wrong.

Good work coming up with an innovative solution that meets your needs,
and thanks for sharing it with us. :)

Larry
Reply | Threaded
Open this post in threaded view
|

Re: Interceptors for auditing

nvishy
In reply to this post by Ronald Albury
Hi Ronald,
Can you please send me the sample code as I am struggling like you to get the parameter value. I have mappedstatement from which I am seeing the actual SQL and when I see parameter objects - they are all null.

This is what I see in the logs
The SQL statement is delete from TM_GROUP_ITEM WHERE (  FK_IM_GROUP_DESC = ? and FK_SERIAL_GID = ? )
2017-08-22 14:55:32,301 INFO  [Persist-Pool-Thread-4] TestInterceptor.java:87 parameterMapping.getProperty():__frch_criterion_1.value"parameterMapping.getResultMapId:"null"parameterMapping.expression:"null
2017-08-22 14:55:32,301 INFO  [Persist-Pool-Thread-4] TestInterceptor.java:87  parameterMapping.getProperty():__frch_criterion_2.value"parameterMapping.getResultMapId:"null"parameterMapping.expression:"null

As you can see this query needs two values for FK_IM_GROUP_DESC -__frch_criterion_1.value and FK_SERIAL_GID __frch_criterion_2.value. I need to retrieve the actual values and have tried several ways but with no success.