You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@cayenne.apache.org by Andrus Adamchik <an...@objectstyle.org> on 2014/08/01 15:22:27 UTC

Re: How to link to user record who made changes in audit trail

Hi Tim,

Yes you are going in the right direction with AuditableProcessor. 

> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.

This seems suspect. In a typical web app, all processing happens in request thread. Cayenne listeners are processed in the same thread as ObjectContext commit, which is normally your request thread. Could you possibly print thread names from within setTheUserId and doPrePersist methods ? Maybe that will give you a hint. Request threads in Tomcat and Jetty have easily identifiable names.

Andrus


On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:

> Hi all
> 
> I want to set up a simple audit trail which basically links who was the person to create a record to that record. I am using Cayenne 3.2M1 and Tapestry 5.3.7. I figure I need to set up a data channel filter to catch changes to that record and then save a link to the user who made the change. 
> 
> The problem is, if I save the user in the data channel filter object when someone logs in, then all created records link to the last logged in user. 
> 
> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
> 
> I have been watching the excellent and now freely available podcast by Andrus Adamchik presented to WebObjects developers "Advanced Apache Cayenne" where he talks about lifecycle events, (callbacks, listeners), caching, data channel filters, clustering, in cayenne 3.2M1.
> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
> In Andrus's sample code he uses AuditableProcessor, but I couldn't think how to use it to solve this problem.
> 
> Here is a copy of my listener/data channel filter with the ThreadLocal code.
> 
> Thanks 
> 
> Tim
> 
> 
> public class AuditListener implements DataChannelFilter {
> 
>  private static final Logger logger = LoggerFactory.getLogger(AuditListener.class);
> 
>  private ThreadLocal<Integer> tlUserId;
> 
>  @PrePersist(entityAnnotations=TagCreateCancel.class)
>  void doPrePersist(DataObject object) {
>    if ( object instanceof AuditableCreateCancel ) {
>      AuditableCreateCancel acc = (AuditableCreateCancel) object;
>      TblPerson user = getTheUser(object.getObjectContext());
>      acc.setTblPersonCreate(user);
>    }
>  }
> 
>  private TblPerson getTheUser(ObjectContext oc) {
>    Thread t = Thread.currentThread();
>    Integer idUser = tlUserId.get();
>    if ( idUser == null ) {
>      logger.info("Thread " + t.getId() + " idUser == null ");
>      return null;
>    }
>    logger.info("Thread " + t.getId() + " Looking for TblPerson " + idUser);
>    TblPerson p = Cayenne.objectForPK(oc, TblPerson.class, idUser.intValue());
>    return p;
>  }
> 
>  @Override
>  public void init(DataChannel channel) {
>    tlUserId = new ThreadLocal<Integer>();
>  }
> 
>  @Override
>  public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) {
>    return filterChain.onQuery(originatingContext, query);
>  }
> 
>  @Override
>  public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelFilterChain filterChain) {
>    try {
>      return filterChain.onSync(originatingContext, changes, syncType);
>    } finally {
>      //
>    }
>  }
> 
>  public void setTheUserId(int idUser) {
>    Thread t = Thread.currentThread();
>    logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
>    tlUserId.set(Integer.valueOf(idUser));
>  }
> 
> }
> 


Re: How to link to user record who made changes in audit trail

Posted by Michael Gentry <mg...@masslight.net>.
Hi Tim,

Tapestry does a redirect-after-post, so I believe it is pretty natural that
you'd see different threads involved.  One thread handles the POST (form
submission) and then another thread handles the rendering of the response,
which occurs after the redirect.  Perhaps this FAQ will help:

http://tapestry.apache.org/component-events-faq.html

mrg



On Mon, Aug 4, 2014 at 9:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:

> Thanks Andrus. I guess this is becoming a Tapestry question now. I tried
> calling setTheUserId in setupRender() and clearing it in cleanupRender()
> but AuditListener is running in a different thread to these two methods.
>
> The following is logging from a single click of the "save" button when a
> new record is being created. There are 2 threads being used and
> AuditListener.doPrePersist is called in a different thread and prior to
> setupRender().
>
> 2014-08-04 23:19:27,376 INFO
> [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread
> qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
> 2014-08-04 23:19:27,376 INFO
> [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread
> qtp1880825967-22 idUser == null
> 2014-08-04 23:19:27,376 INFO
> [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread
> qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
> 2014-08-04 23:19:27,376 INFO
> [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread
> qtp1880825967-22 idUser == null
> 2014-08-04 23:19:27,414 INFO
> [au.com.tramanco.chekway.base.TmcssComponent] setupRender() qtp1880825967-21
> 2014-08-04 23:19:27,414 INFO
> [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21
> qtp1880825967-21 setTheUserId 200
> 2014-08-04 23:19:27,448 INFO
> [au.com.tramanco.chekway.base.TmcssComponent] cleanupRender()
> qtp1880825967-21
> 2014-08-04 23:19:27,448 INFO
> [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21
> qtp1880825967-21 setTheUserId null
>
> Tim
>
> On 4 Aug 2014, at 16:04, Andrus Adamchik <an...@objectstyle.org> wrote:
>
> Maybe the request is running in a different thread each time.
>
>
> Of course. Jetty has a thread pool and each requests gets an available
> thread from the pool semi-randomly. Note that qtp1248572294-23 and
> qtp1248572294-19 are also Jetty threads, so they are called within a
> request. So make sure you call ‘setTheUserId’ in every single request (and
> reset it to null at the end of that request).
>
> Andrus
>
> On Aug 2, 2014, at 2:48 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>
> Hi Andrus
>
> Thanks for your help. Here are logs with thread names as well. I logged in
> in Thread 24 and created 5 new records. AuditListener is running in
> different threads. I am running this in Eclipse 4.3.2 with the RunJettyRun
> plugin running Jetty 8.1.2. Log statement is
>
> logger.info("Thread " + t.getId() + " " + t.getName() + " Looking for
> TblPerson " + idUser);
> //where t = Thread.currentThread()
>
> 2014-08-02 09:37:30,563 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 setTheUserId 220
> 2014-08-02 09:38:14,064 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:38:14,064 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:38:14,064 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:38:14,065 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:38:31,045 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> 2014-08-02 09:38:31,045 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> 2014-08-02 09:38:31,045 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> 2014-08-02 09:38:31,046 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> 2014-08-02 09:38:57,932 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19
> qtp1248572294-19 idUser == null
> 2014-08-02 09:38:57,932 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19
> qtp1248572294-19 idUser == null
> 2014-08-02 09:38:57,932 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19
> qtp1248572294-19 idUser == null
> 2014-08-02 09:38:57,933 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19
> qtp1248572294-19 idUser == null
> 2014-08-02 09:39:16,048 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> 2014-08-02 09:39:16,048 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> 2014-08-02 09:39:16,048 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> 2014-08-02 09:39:16,048 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> 2014-08-02 09:39:41,670 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:39:41,670 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:39:41,670 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:39:41,670 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
>
> Maybe the request is running in a different thread each time. I will do
> some more checking.
>
> Tim
>
>
> On 1 Aug 2014, at 23:22, Andrus Adamchik <an...@objectstyle.org> wrote:
>
> Hi Tim,
>
> Yes you are going in the right direction with AuditableProcessor.
>
> If I save the user in a ThreadLocal in the data channel filter object, I
> get a different user each time (often blank) because the data channel
> filter seem to run in its own thread which changes each time.
>
>
> This seems suspect. In a typical web app, all processing happens in
> request thread. Cayenne listeners are processed in the same thread as
> ObjectContext commit, which is normally your request thread. Could you
> possibly print thread names from within setTheUserId and doPrePersist
> methods ? Maybe that will give you a hint. Request threads in Tomcat and
> Jetty have easily identifiable names.
>
> Andrus
>
>
> On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>
> Hi all
>
> I want to set up a simple audit trail which basically links who was the
> person to create a record to that record. I am using Cayenne 3.2M1 and
> Tapestry 5.3.7. I figure I need to set up a data channel filter to catch
> changes to that record and then save a link to the user who made the
> change.
>
> The problem is, if I save the user in the data channel filter object when
> someone logs in, then all created records link to the last logged in user.
>
> If I save the user in a ThreadLocal in the data channel filter object, I
> get a different user each time (often blank) because the data channel
> filter seem to run in its own thread which changes each time.
>
> I have been watching the excellent and now freely available podcast by
> Andrus Adamchik presented to WebObjects developers "Advanced Apache
> Cayenne" where he talks about lifecycle events, (callbacks, listeners),
> caching, data channel filters, clustering, in cayenne 3.2M1.
> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
> In Andrus's sample code he uses AuditableProcessor, but I couldn't think
> how to use it to solve this problem.
>
> Here is a copy of my listener/data channel filter with the ThreadLocal
> code.
>
> Thanks
>
> Tim
>
>
> public class AuditListener implements DataChannelFilter {
>
> private static final Logger logger =
> LoggerFactory.getLogger(AuditListener.class);
>
> private ThreadLocal<Integer> tlUserId;
>
> @PrePersist(entityAnnotations=TagCreateCancel.class)
> void doPrePersist(DataObject object) {
>  if ( object instanceof AuditableCreateCancel ) {
>    AuditableCreateCancel acc = (AuditableCreateCancel) object;
>    TblPerson user = getTheUser(object.getObjectContext());
>    acc.setTblPersonCreate(user);
>  }
> }
>
> private TblPerson getTheUser(ObjectContext oc) {
>  Thread t = Thread.currentThread();
>  Integer idUser = tlUserId.get();
>  if ( idUser == null ) {
>    logger.info("Thread " + t.getId() + " idUser == null ");
>    return null;
>  }
>  logger.info("Thread " + t.getId() + " Looking for TblPerson " + idUser);
>  TblPerson p = Cayenne.objectForPK(oc, TblPerson.class, idUser.intValue());
>  return p;
> }
>
> @Override
> public void init(DataChannel channel) {
>  tlUserId = new ThreadLocal<Integer>();
> }
>
> @Override
> public QueryResponse onQuery(ObjectContext originatingContext, Query
> query, DataChannelFilterChain filterChain) {
>  return filterChain.onQuery(originatingContext, query);
> }
>
> @Override
> public GraphDiff onSync(ObjectContext originatingContext, GraphDiff
> changes, int syncType, DataChannelFilterChain filterChain) {
>  try {
>    return filterChain.onSync(originatingContext, changes, syncType);
>  } finally {
>    //
>  }
> }
>
> public void setTheUserId(int idUser) {
>  Thread t = Thread.currentThread();
>  logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
>  tlUserId.set(Integer.valueOf(idUser));
> }
>
> }
>
>
>
>
>
>

Re: How to link to user record who made changes in audit trail

Posted by Michael Gentry <mg...@masslight.net>.
Hi Tim,

I noticed a while back that the links on the Tapestry Security site were
broken, but fortunately the code is all on GitHub now, so you can browse it
easily:

https://github.com/tynamo/tapestry-security

mrg



On Mon, Aug 4, 2014 at 6:33 PM, D Tim Cummings <ti...@triptera.com.au> wrote:

> Thanks Robert and Andrus for your help. I will definitely look at
> Dispatcher and tapestry-security. I had started to look at
> tapestry-security before but all the links to sample code were broken.
>
> And thanks to Michael explaining why my POST was generating 2 threads.
>
> Cheers
>
> Tim
>
>
> On 5 Aug 2014, at 0:09, Robert Zeigler <ro...@roxanemy.com>
> wrote:
>
> > Dispatcher is a great place for security code, as long as you don't have
> application components that live outside tapestry which also need the
> security constraints.
> >
> > You might consider looking into the tapestry-security module from the
> tynamo project, which integrates shiro.  A little time reading the docs
> will likely save you a lot if coding time.
> >
> > Robert
> >
> > GATAATGCTATTTCTTTAATTTTCGAA
> >
> >> On Aug 4, 2014, at 9:02 AM, Andrus Adamchik <an...@objectstyle.org>
> wrote:
> >>
> >> Yeah, I guess you’ll need to debug your ’setupRender’.
> >>
> >> I usually implement any security-related code in a servlet Filter that
> wraps around Tapestry filter (and intercepts all pages). This creates some
> inconvenience, as I can’t use injection outside Tapestry, but otherwise I
> can be 100% sure that security code is always executed.
> >>
> >> Perhaps you can do something similar using a custom T5 dispatcher:
> http://wiki.apache.org/tapestry/Tapestry5HowToCreateADispatcher
> >>
> >> Andrus
> >>
> >>> On Aug 4, 2014, at 4:23 PM, D Tim Cummings <ti...@triptera.com.au>
> wrote:
> >>>
> >>> Thanks Andrus. I guess this is becoming a Tapestry question now. I
> tried calling setTheUserId in setupRender() and clearing it in
> cleanupRender() but AuditListener is running in a different thread to these
> two methods.
> >>>
> >>> The following is logging from a single click of the "save" button when
> a new record is being created. There are 2 threads being used and
> AuditListener.doPrePersist is called in a different thread and prior to
> setupRender().
> >>>
> >>> 2014-08-04 23:19:27,376 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist()
> Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
> >>> 2014-08-04 23:19:27,376 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread
> qtp1880825967-22 idUser == null
> >>> 2014-08-04 23:19:27,376 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist()
> Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
> >>> 2014-08-04 23:19:27,376 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread
> qtp1880825967-22 idUser == null
> >>> 2014-08-04 23:19:27,414 INFO
>  [au.com.tramanco.chekway.base.TmcssComponent] setupRender()
> qtp1880825967-21
> >>> 2014-08-04 23:19:27,414 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21
> qtp1880825967-21 setTheUserId 200
> >>> 2014-08-04 23:19:27,448 INFO
>  [au.com.tramanco.chekway.base.TmcssComponent] cleanupRender()
> qtp1880825967-21
> >>> 2014-08-04 23:19:27,448 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21
> qtp1880825967-21 setTheUserId null
> >>>
> >>> Tim
> >>>
> >>> On 4 Aug 2014, at 16:04, Andrus Adamchik <an...@objectstyle.org>
> wrote:
> >>>
> >>>>> Maybe the request is running in a different thread each time.
> >>>>
> >>>> Of course. Jetty has a thread pool and each requests gets an
> available thread from the pool semi-randomly. Note that qtp1248572294-23
> and qtp1248572294-19 are also Jetty threads, so they are called within a
> request. So make sure you call ‘setTheUserId’ in every single request (and
> reset it to null at the end of that request).
> >>>>
> >>>> Andrus
> >>>>
> >>>>> On Aug 2, 2014, at 2:48 AM, D Tim Cummings <ti...@triptera.com.au>
> wrote:
> >>>>>
> >>>>> Hi Andrus
> >>>>>
> >>>>> Thanks for your help. Here are logs with thread names as well. I
> logged in in Thread 24 and created 5 new records. AuditListener is running
> in different threads. I am running this in Eclipse 4.3.2 with the
> RunJettyRun plugin running Jetty 8.1.2. Log statement is
> >>>>>
> >>>>> logger.info("Thread " + t.getId() + " " + t.getName() + " Looking
> for TblPerson " + idUser);
> >>>>> //where t = Thread.currentThread()
> >>>>>
> >>>>> 2014-08-02 09:37:30,563 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 setTheUserId 220
> >>>>> 2014-08-02 09:38:14,064 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> >>>>> 2014-08-02 09:38:14,064 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> >>>>> 2014-08-02 09:38:14,064 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> >>>>> 2014-08-02 09:38:14,065 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> >>>>> 2014-08-02 09:38:31,045 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> >>>>> 2014-08-02 09:38:31,045 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> >>>>> 2014-08-02 09:38:31,045 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> >>>>> 2014-08-02 09:38:31,046 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> >>>>> 2014-08-02 09:38:57,932 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19
> qtp1248572294-19 idUser == null
> >>>>> 2014-08-02 09:38:57,932 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19
> qtp1248572294-19 idUser == null
> >>>>> 2014-08-02 09:38:57,932 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19
> qtp1248572294-19 idUser == null
> >>>>> 2014-08-02 09:38:57,933 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19
> qtp1248572294-19 idUser == null
> >>>>> 2014-08-02 09:39:16,048 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> >>>>> 2014-08-02 09:39:16,048 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> >>>>> 2014-08-02 09:39:16,048 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> >>>>> 2014-08-02 09:39:16,048 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23
> qtp1248572294-23 idUser == null
> >>>>> 2014-08-02 09:39:41,670 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> >>>>> 2014-08-02 09:39:41,670 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> >>>>> 2014-08-02 09:39:41,670 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> >>>>> 2014-08-02 09:39:41,670 INFO
>  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24
> qtp1248572294-24 Looking for TblPerson 220
> >>>>>
> >>>>> Maybe the request is running in a different thread each time. I will
> do some more checking.
> >>>>>
> >>>>> Tim
> >>>>>
> >>>>>
> >>>>>> On 1 Aug 2014, at 23:22, Andrus Adamchik <an...@objectstyle.org>
> wrote:
> >>>>>>
> >>>>>> Hi Tim,
> >>>>>>
> >>>>>> Yes you are going in the right direction with AuditableProcessor.
> >>>>>>
> >>>>>>> If I save the user in a ThreadLocal in the data channel filter
> object, I get a different user each time (often blank) because the data
> channel filter seem to run in its own thread which changes each time.
> >>>>>>
> >>>>>> This seems suspect. In a typical web app, all processing happens in
> request thread. Cayenne listeners are processed in the same thread as
> ObjectContext commit, which is normally your request thread. Could you
> possibly print thread names from within setTheUserId and doPrePersist
> methods ? Maybe that will give you a hint. Request threads in Tomcat and
> Jetty have easily identifiable names.
> >>>>>>
> >>>>>> Andrus
> >>>>>>
> >>>>>>
> >>>>>>> On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au>
> wrote:
> >>>>>>>
> >>>>>>> Hi all
> >>>>>>>
> >>>>>>> I want to set up a simple audit trail which basically links who
> was the person to create a record to that record. I am using Cayenne 3.2M1
> and Tapestry 5.3.7. I figure I need to set up a data channel filter to
> catch changes to that record and then save a link to the user who made the
> change.
> >>>>>>>
> >>>>>>> The problem is, if I save the user in the data channel filter
> object when someone logs in, then all created records link to the last
> logged in user.
> >>>>>>>
> >>>>>>> If I save the user in a ThreadLocal in the data channel filter
> object, I get a different user each time (often blank) because the data
> channel filter seem to run in its own thread which changes each time.
> >>>>>>>
> >>>>>>> I have been watching the excellent and now freely available
> podcast by Andrus Adamchik presented to WebObjects developers "Advanced
> Apache Cayenne" where he talks about lifecycle events, (callbacks,
> listeners), caching, data channel filters, clustering, in cayenne 3.2M1.
> >>>>>>>
> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
> >>>>>>> In Andrus's sample code he uses AuditableProcessor, but I couldn't
> think how to use it to solve this problem.
> >>>>>>>
> >>>>>>> Here is a copy of my listener/data channel filter with the
> ThreadLocal code.
> >>>>>>>
> >>>>>>> Thanks
> >>>>>>>
> >>>>>>> Tim
> >>>>>>>
> >>>>>>>
> >>>>>>> public class AuditListener implements DataChannelFilter {
> >>>>>>>
> >>>>>>> private static final Logger logger =
> LoggerFactory.getLogger(AuditListener.class);
> >>>>>>>
> >>>>>>> private ThreadLocal<Integer> tlUserId;
> >>>>>>>
> >>>>>>> @PrePersist(entityAnnotations=TagCreateCancel.class)
> >>>>>>> void doPrePersist(DataObject object) {
> >>>>>>> if ( object instanceof AuditableCreateCancel ) {
> >>>>>>>  AuditableCreateCancel acc = (AuditableCreateCancel) object;
> >>>>>>>  TblPerson user = getTheUser(object.getObjectContext());
> >>>>>>>  acc.setTblPersonCreate(user);
> >>>>>>> }
> >>>>>>> }
> >>>>>>>
> >>>>>>> private TblPerson getTheUser(ObjectContext oc) {
> >>>>>>> Thread t = Thread.currentThread();
> >>>>>>> Integer idUser = tlUserId.get();
> >>>>>>> if ( idUser == null ) {
> >>>>>>>  logger.info("Thread " + t.getId() + " idUser == null ");
> >>>>>>>  return null;
> >>>>>>> }
> >>>>>>> logger.info("Thread " + t.getId() + " Looking for TblPerson " +
> idUser);
> >>>>>>> TblPerson p = Cayenne.objectForPK(oc, TblPerson.class,
> idUser.intValue());
> >>>>>>> return p;
> >>>>>>> }
> >>>>>>>
> >>>>>>> @Override
> >>>>>>> public void init(DataChannel channel) {
> >>>>>>> tlUserId = new ThreadLocal<Integer>();
> >>>>>>> }
> >>>>>>>
> >>>>>>> @Override
> >>>>>>> public QueryResponse onQuery(ObjectContext originatingContext,
> Query query, DataChannelFilterChain filterChain) {
> >>>>>>> return filterChain.onQuery(originatingContext, query);
> >>>>>>> }
> >>>>>>>
> >>>>>>> @Override
> >>>>>>> public GraphDiff onSync(ObjectContext originatingContext,
> GraphDiff changes, int syncType, DataChannelFilterChain filterChain) {
> >>>>>>> try {
> >>>>>>>  return filterChain.onSync(originatingContext, changes, syncType);
> >>>>>>> } finally {
> >>>>>>>  //
> >>>>>>> }
> >>>>>>> }
> >>>>>>>
> >>>>>>> public void setTheUserId(int idUser) {
> >>>>>>> Thread t = Thread.currentThread();
> >>>>>>> logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
> >>>>>>> tlUserId.set(Integer.valueOf(idUser));
> >>>>>>> }
> >>>>>>>
> >>>>>>> }
> >>
>
>

Re: How to link to user record who made changes in audit trail

Posted by D Tim Cummings <ti...@triptera.com.au>.
Thanks Robert and Andrus for your help. I will definitely look at Dispatcher and tapestry-security. I had started to look at tapestry-security before but all the links to sample code were broken.

And thanks to Michael explaining why my POST was generating 2 threads.

Cheers

Tim


On 5 Aug 2014, at 0:09, Robert Zeigler <ro...@roxanemy.com> wrote:

> Dispatcher is a great place for security code, as long as you don't have application components that live outside tapestry which also need the security constraints. 
> 
> You might consider looking into the tapestry-security module from the tynamo project, which integrates shiro.  A little time reading the docs will likely save you a lot if coding time. 
> 
> Robert
> 
> GATAATGCTATTTCTTTAATTTTCGAA
> 
>> On Aug 4, 2014, at 9:02 AM, Andrus Adamchik <an...@objectstyle.org> wrote:
>> 
>> Yeah, I guess you’ll need to debug your ’setupRender’. 
>> 
>> I usually implement any security-related code in a servlet Filter that wraps around Tapestry filter (and intercepts all pages). This creates some inconvenience, as I can’t use injection outside Tapestry, but otherwise I can be 100% sure that security code is always executed. 
>> 
>> Perhaps you can do something similar using a custom T5 dispatcher: http://wiki.apache.org/tapestry/Tapestry5HowToCreateADispatcher
>> 
>> Andrus
>> 
>>> On Aug 4, 2014, at 4:23 PM, D Tim Cummings <ti...@triptera.com.au> wrote:
>>> 
>>> Thanks Andrus. I guess this is becoming a Tapestry question now. I tried calling setTheUserId in setupRender() and clearing it in cleanupRender() but AuditListener is running in a different thread to these two methods.
>>> 
>>> The following is logging from a single click of the "save" button when a new record is being created. There are 2 threads being used and AuditListener.doPrePersist is called in a different thread and prior to setupRender().
>>> 
>>> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
>>> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread qtp1880825967-22 idUser == null 
>>> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
>>> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread qtp1880825967-22 idUser == null 
>>> 2014-08-04 23:19:27,414 INFO  [au.com.tramanco.chekway.base.TmcssComponent] setupRender() qtp1880825967-21
>>> 2014-08-04 23:19:27,414 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21 qtp1880825967-21 setTheUserId 200
>>> 2014-08-04 23:19:27,448 INFO  [au.com.tramanco.chekway.base.TmcssComponent] cleanupRender() qtp1880825967-21
>>> 2014-08-04 23:19:27,448 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21 qtp1880825967-21 setTheUserId null
>>> 
>>> Tim
>>> 
>>> On 4 Aug 2014, at 16:04, Andrus Adamchik <an...@objectstyle.org> wrote:
>>> 
>>>>> Maybe the request is running in a different thread each time.
>>>> 
>>>> Of course. Jetty has a thread pool and each requests gets an available thread from the pool semi-randomly. Note that qtp1248572294-23 and qtp1248572294-19 are also Jetty threads, so they are called within a request. So make sure you call ‘setTheUserId’ in every single request (and reset it to null at the end of that request).
>>>> 
>>>> Andrus
>>>> 
>>>>> On Aug 2, 2014, at 2:48 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>>>>> 
>>>>> Hi Andrus
>>>>> 
>>>>> Thanks for your help. Here are logs with thread names as well. I logged in in Thread 24 and created 5 new records. AuditListener is running in different threads. I am running this in Eclipse 4.3.2 with the RunJettyRun plugin running Jetty 8.1.2. Log statement is 
>>>>> 
>>>>> logger.info("Thread " + t.getId() + " " + t.getName() + " Looking for TblPerson " + idUser); 
>>>>> //where t = Thread.currentThread()
>>>>> 
>>>>> 2014-08-02 09:37:30,563 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 setTheUserId 220
>>>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>>> 2014-08-02 09:38:14,065 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>>> 2014-08-02 09:38:31,046 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>>>> 2014-08-02 09:38:57,933 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>>> 
>>>>> Maybe the request is running in a different thread each time. I will do some more checking.
>>>>> 
>>>>> Tim
>>>>> 
>>>>> 
>>>>>> On 1 Aug 2014, at 23:22, Andrus Adamchik <an...@objectstyle.org> wrote:
>>>>>> 
>>>>>> Hi Tim,
>>>>>> 
>>>>>> Yes you are going in the right direction with AuditableProcessor. 
>>>>>> 
>>>>>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>>>>> 
>>>>>> This seems suspect. In a typical web app, all processing happens in request thread. Cayenne listeners are processed in the same thread as ObjectContext commit, which is normally your request thread. Could you possibly print thread names from within setTheUserId and doPrePersist methods ? Maybe that will give you a hint. Request threads in Tomcat and Jetty have easily identifiable names.
>>>>>> 
>>>>>> Andrus
>>>>>> 
>>>>>> 
>>>>>>> On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>>>>>>> 
>>>>>>> Hi all
>>>>>>> 
>>>>>>> I want to set up a simple audit trail which basically links who was the person to create a record to that record. I am using Cayenne 3.2M1 and Tapestry 5.3.7. I figure I need to set up a data channel filter to catch changes to that record and then save a link to the user who made the change. 
>>>>>>> 
>>>>>>> The problem is, if I save the user in the data channel filter object when someone logs in, then all created records link to the last logged in user. 
>>>>>>> 
>>>>>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>>>>>> 
>>>>>>> I have been watching the excellent and now freely available podcast by Andrus Adamchik presented to WebObjects developers "Advanced Apache Cayenne" where he talks about lifecycle events, (callbacks, listeners), caching, data channel filters, clustering, in cayenne 3.2M1.
>>>>>>> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
>>>>>>> In Andrus's sample code he uses AuditableProcessor, but I couldn't think how to use it to solve this problem.
>>>>>>> 
>>>>>>> Here is a copy of my listener/data channel filter with the ThreadLocal code.
>>>>>>> 
>>>>>>> Thanks 
>>>>>>> 
>>>>>>> Tim
>>>>>>> 
>>>>>>> 
>>>>>>> public class AuditListener implements DataChannelFilter {
>>>>>>> 
>>>>>>> private static final Logger logger = LoggerFactory.getLogger(AuditListener.class);
>>>>>>> 
>>>>>>> private ThreadLocal<Integer> tlUserId;
>>>>>>> 
>>>>>>> @PrePersist(entityAnnotations=TagCreateCancel.class)
>>>>>>> void doPrePersist(DataObject object) {
>>>>>>> if ( object instanceof AuditableCreateCancel ) {
>>>>>>>  AuditableCreateCancel acc = (AuditableCreateCancel) object;
>>>>>>>  TblPerson user = getTheUser(object.getObjectContext());
>>>>>>>  acc.setTblPersonCreate(user);
>>>>>>> }
>>>>>>> }
>>>>>>> 
>>>>>>> private TblPerson getTheUser(ObjectContext oc) {
>>>>>>> Thread t = Thread.currentThread();
>>>>>>> Integer idUser = tlUserId.get();
>>>>>>> if ( idUser == null ) {
>>>>>>>  logger.info("Thread " + t.getId() + " idUser == null ");
>>>>>>>  return null;
>>>>>>> }
>>>>>>> logger.info("Thread " + t.getId() + " Looking for TblPerson " + idUser);
>>>>>>> TblPerson p = Cayenne.objectForPK(oc, TblPerson.class, idUser.intValue());
>>>>>>> return p;
>>>>>>> }
>>>>>>> 
>>>>>>> @Override
>>>>>>> public void init(DataChannel channel) {
>>>>>>> tlUserId = new ThreadLocal<Integer>();
>>>>>>> }
>>>>>>> 
>>>>>>> @Override
>>>>>>> public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) {
>>>>>>> return filterChain.onQuery(originatingContext, query);
>>>>>>> }
>>>>>>> 
>>>>>>> @Override
>>>>>>> public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelFilterChain filterChain) {
>>>>>>> try {
>>>>>>>  return filterChain.onSync(originatingContext, changes, syncType);
>>>>>>> } finally {
>>>>>>>  //
>>>>>>> }
>>>>>>> }
>>>>>>> 
>>>>>>> public void setTheUserId(int idUser) {
>>>>>>> Thread t = Thread.currentThread();
>>>>>>> logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
>>>>>>> tlUserId.set(Integer.valueOf(idUser));
>>>>>>> }
>>>>>>> 
>>>>>>> }
>> 


Re: How to link to user record who made changes in audit trail

Posted by Robert Zeigler <ro...@roxanemy.com>.
Dispatcher is a great place for security code, as long as you don't have application components that live outside tapestry which also need the security constraints. 

You might consider looking into the tapestry-security module from the tynamo project, which integrates shiro.  A little time reading the docs will likely save you a lot if coding time. 

Robert

GATAATGCTATTTCTTTAATTTTCGAA

> On Aug 4, 2014, at 9:02 AM, Andrus Adamchik <an...@objectstyle.org> wrote:
> 
> Yeah, I guess you’ll need to debug your ’setupRender’. 
> 
> I usually implement any security-related code in a servlet Filter that wraps around Tapestry filter (and intercepts all pages). This creates some inconvenience, as I can’t use injection outside Tapestry, but otherwise I can be 100% sure that security code is always executed. 
> 
> Perhaps you can do something similar using a custom T5 dispatcher: http://wiki.apache.org/tapestry/Tapestry5HowToCreateADispatcher
> 
> Andrus
> 
>> On Aug 4, 2014, at 4:23 PM, D Tim Cummings <ti...@triptera.com.au> wrote:
>> 
>> Thanks Andrus. I guess this is becoming a Tapestry question now. I tried calling setTheUserId in setupRender() and clearing it in cleanupRender() but AuditListener is running in a different thread to these two methods.
>> 
>> The following is logging from a single click of the "save" button when a new record is being created. There are 2 threads being used and AuditListener.doPrePersist is called in a different thread and prior to setupRender().
>> 
>> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
>> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread qtp1880825967-22 idUser == null 
>> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
>> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread qtp1880825967-22 idUser == null 
>> 2014-08-04 23:19:27,414 INFO  [au.com.tramanco.chekway.base.TmcssComponent] setupRender() qtp1880825967-21
>> 2014-08-04 23:19:27,414 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21 qtp1880825967-21 setTheUserId 200
>> 2014-08-04 23:19:27,448 INFO  [au.com.tramanco.chekway.base.TmcssComponent] cleanupRender() qtp1880825967-21
>> 2014-08-04 23:19:27,448 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21 qtp1880825967-21 setTheUserId null
>> 
>> Tim
>> 
>> On 4 Aug 2014, at 16:04, Andrus Adamchik <an...@objectstyle.org> wrote:
>> 
>>>> Maybe the request is running in a different thread each time.
>>> 
>>> Of course. Jetty has a thread pool and each requests gets an available thread from the pool semi-randomly. Note that qtp1248572294-23 and qtp1248572294-19 are also Jetty threads, so they are called within a request. So make sure you call ‘setTheUserId’ in every single request (and reset it to null at the end of that request).
>>> 
>>> Andrus
>>> 
>>>> On Aug 2, 2014, at 2:48 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>>>> 
>>>> Hi Andrus
>>>> 
>>>> Thanks for your help. Here are logs with thread names as well. I logged in in Thread 24 and created 5 new records. AuditListener is running in different threads. I am running this in Eclipse 4.3.2 with the RunJettyRun plugin running Jetty 8.1.2. Log statement is 
>>>> 
>>>> logger.info("Thread " + t.getId() + " " + t.getName() + " Looking for TblPerson " + idUser); 
>>>> //where t = Thread.currentThread()
>>>> 
>>>> 2014-08-02 09:37:30,563 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 setTheUserId 220
>>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>> 2014-08-02 09:38:14,065 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>> 2014-08-02 09:38:31,046 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>>> 2014-08-02 09:38:57,933 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>>> 
>>>> Maybe the request is running in a different thread each time. I will do some more checking.
>>>> 
>>>> Tim
>>>> 
>>>> 
>>>>> On 1 Aug 2014, at 23:22, Andrus Adamchik <an...@objectstyle.org> wrote:
>>>>> 
>>>>> Hi Tim,
>>>>> 
>>>>> Yes you are going in the right direction with AuditableProcessor. 
>>>>> 
>>>>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>>>> 
>>>>> This seems suspect. In a typical web app, all processing happens in request thread. Cayenne listeners are processed in the same thread as ObjectContext commit, which is normally your request thread. Could you possibly print thread names from within setTheUserId and doPrePersist methods ? Maybe that will give you a hint. Request threads in Tomcat and Jetty have easily identifiable names.
>>>>> 
>>>>> Andrus
>>>>> 
>>>>> 
>>>>>> On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>>>>>> 
>>>>>> Hi all
>>>>>> 
>>>>>> I want to set up a simple audit trail which basically links who was the person to create a record to that record. I am using Cayenne 3.2M1 and Tapestry 5.3.7. I figure I need to set up a data channel filter to catch changes to that record and then save a link to the user who made the change. 
>>>>>> 
>>>>>> The problem is, if I save the user in the data channel filter object when someone logs in, then all created records link to the last logged in user. 
>>>>>> 
>>>>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>>>>> 
>>>>>> I have been watching the excellent and now freely available podcast by Andrus Adamchik presented to WebObjects developers "Advanced Apache Cayenne" where he talks about lifecycle events, (callbacks, listeners), caching, data channel filters, clustering, in cayenne 3.2M1.
>>>>>> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
>>>>>> In Andrus's sample code he uses AuditableProcessor, but I couldn't think how to use it to solve this problem.
>>>>>> 
>>>>>> Here is a copy of my listener/data channel filter with the ThreadLocal code.
>>>>>> 
>>>>>> Thanks 
>>>>>> 
>>>>>> Tim
>>>>>> 
>>>>>> 
>>>>>> public class AuditListener implements DataChannelFilter {
>>>>>> 
>>>>>> private static final Logger logger = LoggerFactory.getLogger(AuditListener.class);
>>>>>> 
>>>>>> private ThreadLocal<Integer> tlUserId;
>>>>>> 
>>>>>> @PrePersist(entityAnnotations=TagCreateCancel.class)
>>>>>> void doPrePersist(DataObject object) {
>>>>>> if ( object instanceof AuditableCreateCancel ) {
>>>>>>   AuditableCreateCancel acc = (AuditableCreateCancel) object;
>>>>>>   TblPerson user = getTheUser(object.getObjectContext());
>>>>>>   acc.setTblPersonCreate(user);
>>>>>> }
>>>>>> }
>>>>>> 
>>>>>> private TblPerson getTheUser(ObjectContext oc) {
>>>>>> Thread t = Thread.currentThread();
>>>>>> Integer idUser = tlUserId.get();
>>>>>> if ( idUser == null ) {
>>>>>>   logger.info("Thread " + t.getId() + " idUser == null ");
>>>>>>   return null;
>>>>>> }
>>>>>> logger.info("Thread " + t.getId() + " Looking for TblPerson " + idUser);
>>>>>> TblPerson p = Cayenne.objectForPK(oc, TblPerson.class, idUser.intValue());
>>>>>> return p;
>>>>>> }
>>>>>> 
>>>>>> @Override
>>>>>> public void init(DataChannel channel) {
>>>>>> tlUserId = new ThreadLocal<Integer>();
>>>>>> }
>>>>>> 
>>>>>> @Override
>>>>>> public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) {
>>>>>> return filterChain.onQuery(originatingContext, query);
>>>>>> }
>>>>>> 
>>>>>> @Override
>>>>>> public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelFilterChain filterChain) {
>>>>>> try {
>>>>>>   return filterChain.onSync(originatingContext, changes, syncType);
>>>>>> } finally {
>>>>>>   //
>>>>>> }
>>>>>> }
>>>>>> 
>>>>>> public void setTheUserId(int idUser) {
>>>>>> Thread t = Thread.currentThread();
>>>>>> logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
>>>>>> tlUserId.set(Integer.valueOf(idUser));
>>>>>> }
>>>>>> 
>>>>>> }
> 

Re: How to link to user record who made changes in audit trail

Posted by Andrus Adamchik <an...@objectstyle.org>.
Yeah, I guess you’ll need to debug your ’setupRender’. 

I usually implement any security-related code in a servlet Filter that wraps around Tapestry filter (and intercepts all pages). This creates some inconvenience, as I can’t use injection outside Tapestry, but otherwise I can be 100% sure that security code is always executed. 

Perhaps you can do something similar using a custom T5 dispatcher: http://wiki.apache.org/tapestry/Tapestry5HowToCreateADispatcher

Andrus

On Aug 4, 2014, at 4:23 PM, D Tim Cummings <ti...@triptera.com.au> wrote:

> Thanks Andrus. I guess this is becoming a Tapestry question now. I tried calling setTheUserId in setupRender() and clearing it in cleanupRender() but AuditListener is running in a different thread to these two methods.
> 
> The following is logging from a single click of the "save" button when a new record is being created. There are 2 threads being used and AuditListener.doPrePersist is called in a different thread and prior to setupRender().
> 
> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread qtp1880825967-22 idUser == null 
> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
> 2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread qtp1880825967-22 idUser == null 
> 2014-08-04 23:19:27,414 INFO  [au.com.tramanco.chekway.base.TmcssComponent] setupRender() qtp1880825967-21
> 2014-08-04 23:19:27,414 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21 qtp1880825967-21 setTheUserId 200
> 2014-08-04 23:19:27,448 INFO  [au.com.tramanco.chekway.base.TmcssComponent] cleanupRender() qtp1880825967-21
> 2014-08-04 23:19:27,448 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21 qtp1880825967-21 setTheUserId null
> 
> Tim
> 
> On 4 Aug 2014, at 16:04, Andrus Adamchik <an...@objectstyle.org> wrote:
> 
>>> Maybe the request is running in a different thread each time.
>> 
>> Of course. Jetty has a thread pool and each requests gets an available thread from the pool semi-randomly. Note that qtp1248572294-23 and qtp1248572294-19 are also Jetty threads, so they are called within a request. So make sure you call ‘setTheUserId’ in every single request (and reset it to null at the end of that request).
>> 
>> Andrus
>> 
>> On Aug 2, 2014, at 2:48 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>> 
>>> Hi Andrus
>>> 
>>> Thanks for your help. Here are logs with thread names as well. I logged in in Thread 24 and created 5 new records. AuditListener is running in different threads. I am running this in Eclipse 4.3.2 with the RunJettyRun plugin running Jetty 8.1.2. Log statement is 
>>> 
>>> logger.info("Thread " + t.getId() + " " + t.getName() + " Looking for TblPerson " + idUser); 
>>> //where t = Thread.currentThread()
>>> 
>>> 2014-08-02 09:37:30,563 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 setTheUserId 220
>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>> 2014-08-02 09:38:14,065 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>> 2014-08-02 09:38:31,046 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>> 2014-08-02 09:38:57,933 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>>> 
>>> Maybe the request is running in a different thread each time. I will do some more checking.
>>> 
>>> Tim
>>> 
>>> 
>>> On 1 Aug 2014, at 23:22, Andrus Adamchik <an...@objectstyle.org> wrote:
>>> 
>>>> Hi Tim,
>>>> 
>>>> Yes you are going in the right direction with AuditableProcessor. 
>>>> 
>>>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>>> 
>>>> This seems suspect. In a typical web app, all processing happens in request thread. Cayenne listeners are processed in the same thread as ObjectContext commit, which is normally your request thread. Could you possibly print thread names from within setTheUserId and doPrePersist methods ? Maybe that will give you a hint. Request threads in Tomcat and Jetty have easily identifiable names.
>>>> 
>>>> Andrus
>>>> 
>>>> 
>>>> On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>>>> 
>>>>> Hi all
>>>>> 
>>>>> I want to set up a simple audit trail which basically links who was the person to create a record to that record. I am using Cayenne 3.2M1 and Tapestry 5.3.7. I figure I need to set up a data channel filter to catch changes to that record and then save a link to the user who made the change. 
>>>>> 
>>>>> The problem is, if I save the user in the data channel filter object when someone logs in, then all created records link to the last logged in user. 
>>>>> 
>>>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>>>> 
>>>>> I have been watching the excellent and now freely available podcast by Andrus Adamchik presented to WebObjects developers "Advanced Apache Cayenne" where he talks about lifecycle events, (callbacks, listeners), caching, data channel filters, clustering, in cayenne 3.2M1.
>>>>> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
>>>>> In Andrus's sample code he uses AuditableProcessor, but I couldn't think how to use it to solve this problem.
>>>>> 
>>>>> Here is a copy of my listener/data channel filter with the ThreadLocal code.
>>>>> 
>>>>> Thanks 
>>>>> 
>>>>> Tim
>>>>> 
>>>>> 
>>>>> public class AuditListener implements DataChannelFilter {
>>>>> 
>>>>> private static final Logger logger = LoggerFactory.getLogger(AuditListener.class);
>>>>> 
>>>>> private ThreadLocal<Integer> tlUserId;
>>>>> 
>>>>> @PrePersist(entityAnnotations=TagCreateCancel.class)
>>>>> void doPrePersist(DataObject object) {
>>>>>  if ( object instanceof AuditableCreateCancel ) {
>>>>>    AuditableCreateCancel acc = (AuditableCreateCancel) object;
>>>>>    TblPerson user = getTheUser(object.getObjectContext());
>>>>>    acc.setTblPersonCreate(user);
>>>>>  }
>>>>> }
>>>>> 
>>>>> private TblPerson getTheUser(ObjectContext oc) {
>>>>>  Thread t = Thread.currentThread();
>>>>>  Integer idUser = tlUserId.get();
>>>>>  if ( idUser == null ) {
>>>>>    logger.info("Thread " + t.getId() + " idUser == null ");
>>>>>    return null;
>>>>>  }
>>>>>  logger.info("Thread " + t.getId() + " Looking for TblPerson " + idUser);
>>>>>  TblPerson p = Cayenne.objectForPK(oc, TblPerson.class, idUser.intValue());
>>>>>  return p;
>>>>> }
>>>>> 
>>>>> @Override
>>>>> public void init(DataChannel channel) {
>>>>>  tlUserId = new ThreadLocal<Integer>();
>>>>> }
>>>>> 
>>>>> @Override
>>>>> public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) {
>>>>>  return filterChain.onQuery(originatingContext, query);
>>>>> }
>>>>> 
>>>>> @Override
>>>>> public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelFilterChain filterChain) {
>>>>>  try {
>>>>>    return filterChain.onSync(originatingContext, changes, syncType);
>>>>>  } finally {
>>>>>    //
>>>>>  }
>>>>> }
>>>>> 
>>>>> public void setTheUserId(int idUser) {
>>>>>  Thread t = Thread.currentThread();
>>>>>  logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
>>>>>  tlUserId.set(Integer.valueOf(idUser));
>>>>> }
>>>>> 
>>>>> }
>>>>> 
>>>> 
>>> 
>> 
> 


Re: How to link to user record who made changes in audit trail

Posted by D Tim Cummings <ti...@triptera.com.au>.
Thanks Andrus. I guess this is becoming a Tapestry question now. I tried calling setTheUserId in setupRender() and clearing it in cleanupRender() but AuditListener is running in a different thread to these two methods.

The following is logging from a single click of the "save" button when a new record is being created. There are 2 threads being used and AuditListener.doPrePersist is called in a different thread and prior to setupRender().

2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread qtp1880825967-22 idUser == null 
2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] doPrePersist() Thread qtp1880825967-22 au.com.tramanco.chekway.cayenne.TblPerson
2014-08-04 23:19:27,376 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] getTheUser() Thread qtp1880825967-22 idUser == null 
2014-08-04 23:19:27,414 INFO  [au.com.tramanco.chekway.base.TmcssComponent] setupRender() qtp1880825967-21
2014-08-04 23:19:27,414 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21 qtp1880825967-21 setTheUserId 200
2014-08-04 23:19:27,448 INFO  [au.com.tramanco.chekway.base.TmcssComponent] cleanupRender() qtp1880825967-21
2014-08-04 23:19:27,448 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 21 qtp1880825967-21 setTheUserId null

Tim

On 4 Aug 2014, at 16:04, Andrus Adamchik <an...@objectstyle.org> wrote:

>> Maybe the request is running in a different thread each time.
> 
> Of course. Jetty has a thread pool and each requests gets an available thread from the pool semi-randomly. Note that qtp1248572294-23 and qtp1248572294-19 are also Jetty threads, so they are called within a request. So make sure you call ‘setTheUserId’ in every single request (and reset it to null at the end of that request).
> 
> Andrus
> 
> On Aug 2, 2014, at 2:48 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
> 
>> Hi Andrus
>> 
>> Thanks for your help. Here are logs with thread names as well. I logged in in Thread 24 and created 5 new records. AuditListener is running in different threads. I am running this in Eclipse 4.3.2 with the RunJettyRun plugin running Jetty 8.1.2. Log statement is 
>> 
>> logger.info("Thread " + t.getId() + " " + t.getName() + " Looking for TblPerson " + idUser); 
>> //where t = Thread.currentThread()
>> 
>> 2014-08-02 09:37:30,563 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 setTheUserId 220
>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>> 2014-08-02 09:38:14,065 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>> 2014-08-02 09:38:31,046 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>> 2014-08-02 09:38:57,933 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
>> 
>> Maybe the request is running in a different thread each time. I will do some more checking.
>> 
>> Tim
>> 
>> 
>> On 1 Aug 2014, at 23:22, Andrus Adamchik <an...@objectstyle.org> wrote:
>> 
>>> Hi Tim,
>>> 
>>> Yes you are going in the right direction with AuditableProcessor. 
>>> 
>>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>> 
>>> This seems suspect. In a typical web app, all processing happens in request thread. Cayenne listeners are processed in the same thread as ObjectContext commit, which is normally your request thread. Could you possibly print thread names from within setTheUserId and doPrePersist methods ? Maybe that will give you a hint. Request threads in Tomcat and Jetty have easily identifiable names.
>>> 
>>> Andrus
>>> 
>>> 
>>> On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>>> 
>>>> Hi all
>>>> 
>>>> I want to set up a simple audit trail which basically links who was the person to create a record to that record. I am using Cayenne 3.2M1 and Tapestry 5.3.7. I figure I need to set up a data channel filter to catch changes to that record and then save a link to the user who made the change. 
>>>> 
>>>> The problem is, if I save the user in the data channel filter object when someone logs in, then all created records link to the last logged in user. 
>>>> 
>>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>>> 
>>>> I have been watching the excellent and now freely available podcast by Andrus Adamchik presented to WebObjects developers "Advanced Apache Cayenne" where he talks about lifecycle events, (callbacks, listeners), caching, data channel filters, clustering, in cayenne 3.2M1.
>>>> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
>>>> In Andrus's sample code he uses AuditableProcessor, but I couldn't think how to use it to solve this problem.
>>>> 
>>>> Here is a copy of my listener/data channel filter with the ThreadLocal code.
>>>> 
>>>> Thanks 
>>>> 
>>>> Tim
>>>> 
>>>> 
>>>> public class AuditListener implements DataChannelFilter {
>>>> 
>>>> private static final Logger logger = LoggerFactory.getLogger(AuditListener.class);
>>>> 
>>>> private ThreadLocal<Integer> tlUserId;
>>>> 
>>>> @PrePersist(entityAnnotations=TagCreateCancel.class)
>>>> void doPrePersist(DataObject object) {
>>>>  if ( object instanceof AuditableCreateCancel ) {
>>>>    AuditableCreateCancel acc = (AuditableCreateCancel) object;
>>>>    TblPerson user = getTheUser(object.getObjectContext());
>>>>    acc.setTblPersonCreate(user);
>>>>  }
>>>> }
>>>> 
>>>> private TblPerson getTheUser(ObjectContext oc) {
>>>>  Thread t = Thread.currentThread();
>>>>  Integer idUser = tlUserId.get();
>>>>  if ( idUser == null ) {
>>>>    logger.info("Thread " + t.getId() + " idUser == null ");
>>>>    return null;
>>>>  }
>>>>  logger.info("Thread " + t.getId() + " Looking for TblPerson " + idUser);
>>>>  TblPerson p = Cayenne.objectForPK(oc, TblPerson.class, idUser.intValue());
>>>>  return p;
>>>> }
>>>> 
>>>> @Override
>>>> public void init(DataChannel channel) {
>>>>  tlUserId = new ThreadLocal<Integer>();
>>>> }
>>>> 
>>>> @Override
>>>> public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) {
>>>>  return filterChain.onQuery(originatingContext, query);
>>>> }
>>>> 
>>>> @Override
>>>> public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelFilterChain filterChain) {
>>>>  try {
>>>>    return filterChain.onSync(originatingContext, changes, syncType);
>>>>  } finally {
>>>>    //
>>>>  }
>>>> }
>>>> 
>>>> public void setTheUserId(int idUser) {
>>>>  Thread t = Thread.currentThread();
>>>>  logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
>>>>  tlUserId.set(Integer.valueOf(idUser));
>>>> }
>>>> 
>>>> }
>>>> 
>>> 
>> 
> 


Re: How to link to user record who made changes in audit trail

Posted by Andrus Adamchik <an...@objectstyle.org>.
> Maybe the request is running in a different thread each time.

Of course. Jetty has a thread pool and each requests gets an available thread from the pool semi-randomly. Note that qtp1248572294-23 and qtp1248572294-19 are also Jetty threads, so they are called within a request. So make sure you call ‘setTheUserId’ in every single request (and reset it to null at the end of that request).

Andrus

On Aug 2, 2014, at 2:48 AM, D Tim Cummings <ti...@triptera.com.au> wrote:

> Hi Andrus
> 
> Thanks for your help. Here are logs with thread names as well. I logged in in Thread 24 and created 5 new records. AuditListener is running in different threads. I am running this in Eclipse 4.3.2 with the RunJettyRun plugin running Jetty 8.1.2. Log statement is 
> 
> logger.info("Thread " + t.getId() + " " + t.getName() + " Looking for TblPerson " + idUser); 
> //where t = Thread.currentThread()
> 
> 2014-08-02 09:37:30,563 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 setTheUserId 220
> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:38:14,065 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
> 2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
> 2014-08-02 09:38:31,046 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
> 2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
> 2014-08-02 09:38:57,933 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
> 2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
> 2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
> 
> Maybe the request is running in a different thread each time. I will do some more checking.
> 
> Tim
> 
> 
> On 1 Aug 2014, at 23:22, Andrus Adamchik <an...@objectstyle.org> wrote:
> 
>> Hi Tim,
>> 
>> Yes you are going in the right direction with AuditableProcessor. 
>> 
>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>> 
>> This seems suspect. In a typical web app, all processing happens in request thread. Cayenne listeners are processed in the same thread as ObjectContext commit, which is normally your request thread. Could you possibly print thread names from within setTheUserId and doPrePersist methods ? Maybe that will give you a hint. Request threads in Tomcat and Jetty have easily identifiable names.
>> 
>> Andrus
>> 
>> 
>> On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
>> 
>>> Hi all
>>> 
>>> I want to set up a simple audit trail which basically links who was the person to create a record to that record. I am using Cayenne 3.2M1 and Tapestry 5.3.7. I figure I need to set up a data channel filter to catch changes to that record and then save a link to the user who made the change. 
>>> 
>>> The problem is, if I save the user in the data channel filter object when someone logs in, then all created records link to the last logged in user. 
>>> 
>>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>>> 
>>> I have been watching the excellent and now freely available podcast by Andrus Adamchik presented to WebObjects developers "Advanced Apache Cayenne" where he talks about lifecycle events, (callbacks, listeners), caching, data channel filters, clustering, in cayenne 3.2M1.
>>> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
>>> In Andrus's sample code he uses AuditableProcessor, but I couldn't think how to use it to solve this problem.
>>> 
>>> Here is a copy of my listener/data channel filter with the ThreadLocal code.
>>> 
>>> Thanks 
>>> 
>>> Tim
>>> 
>>> 
>>> public class AuditListener implements DataChannelFilter {
>>> 
>>> private static final Logger logger = LoggerFactory.getLogger(AuditListener.class);
>>> 
>>> private ThreadLocal<Integer> tlUserId;
>>> 
>>> @PrePersist(entityAnnotations=TagCreateCancel.class)
>>> void doPrePersist(DataObject object) {
>>>   if ( object instanceof AuditableCreateCancel ) {
>>>     AuditableCreateCancel acc = (AuditableCreateCancel) object;
>>>     TblPerson user = getTheUser(object.getObjectContext());
>>>     acc.setTblPersonCreate(user);
>>>   }
>>> }
>>> 
>>> private TblPerson getTheUser(ObjectContext oc) {
>>>   Thread t = Thread.currentThread();
>>>   Integer idUser = tlUserId.get();
>>>   if ( idUser == null ) {
>>>     logger.info("Thread " + t.getId() + " idUser == null ");
>>>     return null;
>>>   }
>>>   logger.info("Thread " + t.getId() + " Looking for TblPerson " + idUser);
>>>   TblPerson p = Cayenne.objectForPK(oc, TblPerson.class, idUser.intValue());
>>>   return p;
>>> }
>>> 
>>> @Override
>>> public void init(DataChannel channel) {
>>>   tlUserId = new ThreadLocal<Integer>();
>>> }
>>> 
>>> @Override
>>> public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) {
>>>   return filterChain.onQuery(originatingContext, query);
>>> }
>>> 
>>> @Override
>>> public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelFilterChain filterChain) {
>>>   try {
>>>     return filterChain.onSync(originatingContext, changes, syncType);
>>>   } finally {
>>>     //
>>>   }
>>> }
>>> 
>>> public void setTheUserId(int idUser) {
>>>   Thread t = Thread.currentThread();
>>>   logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
>>>   tlUserId.set(Integer.valueOf(idUser));
>>> }
>>> 
>>> }
>>> 
>> 
> 


Re: How to link to user record who made changes in audit trail

Posted by D Tim Cummings <ti...@triptera.com.au>.
Hi Andrus

Thanks for your help. Here are logs with thread names as well. I logged in in Thread 24 and created 5 new records. AuditListener is running in different threads. I am running this in Eclipse 4.3.2 with the RunJettyRun plugin running Jetty 8.1.2. Log statement is 

logger.info("Thread " + t.getId() + " " + t.getName() + " Looking for TblPerson " + idUser); 
//where t = Thread.currentThread()

2014-08-02 09:37:30,563 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 setTheUserId 220
2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
2014-08-02 09:38:14,064 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
2014-08-02 09:38:14,065 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
2014-08-02 09:38:31,045 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
2014-08-02 09:38:31,046 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
2014-08-02 09:38:57,932 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
2014-08-02 09:38:57,933 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 19 qtp1248572294-19 idUser == null 
2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
2014-08-02 09:39:16,048 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 23 qtp1248572294-23 idUser == null 
2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220
2014-08-02 09:39:41,670 INFO  [au.com.tramanco.chekway.cayenne.audit.AuditListener] Thread 24 qtp1248572294-24 Looking for TblPerson 220

Maybe the request is running in a different thread each time. I will do some more checking.

Tim


On 1 Aug 2014, at 23:22, Andrus Adamchik <an...@objectstyle.org> wrote:

> Hi Tim,
> 
> Yes you are going in the right direction with AuditableProcessor. 
> 
>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
> 
> This seems suspect. In a typical web app, all processing happens in request thread. Cayenne listeners are processed in the same thread as ObjectContext commit, which is normally your request thread. Could you possibly print thread names from within setTheUserId and doPrePersist methods ? Maybe that will give you a hint. Request threads in Tomcat and Jetty have easily identifiable names.
> 
> Andrus
> 
> 
> On Jul 20, 2014, at 2:23 AM, D Tim Cummings <ti...@triptera.com.au> wrote:
> 
>> Hi all
>> 
>> I want to set up a simple audit trail which basically links who was the person to create a record to that record. I am using Cayenne 3.2M1 and Tapestry 5.3.7. I figure I need to set up a data channel filter to catch changes to that record and then save a link to the user who made the change. 
>> 
>> The problem is, if I save the user in the data channel filter object when someone logs in, then all created records link to the last logged in user. 
>> 
>> If I save the user in a ThreadLocal in the data channel filter object, I get a different user each time (often blank) because the data channel filter seem to run in its own thread which changes each time.
>> 
>> I have been watching the excellent and now freely available podcast by Andrus Adamchik presented to WebObjects developers "Advanced Apache Cayenne" where he talks about lifecycle events, (callbacks, listeners), caching, data channel filters, clustering, in cayenne 3.2M1.
>> https://itunes.apple.com/podcast/webobjects-podcasts/id270165303?mt=2#
>> In Andrus's sample code he uses AuditableProcessor, but I couldn't think how to use it to solve this problem.
>> 
>> Here is a copy of my listener/data channel filter with the ThreadLocal code.
>> 
>> Thanks 
>> 
>> Tim
>> 
>> 
>> public class AuditListener implements DataChannelFilter {
>> 
>> private static final Logger logger = LoggerFactory.getLogger(AuditListener.class);
>> 
>> private ThreadLocal<Integer> tlUserId;
>> 
>> @PrePersist(entityAnnotations=TagCreateCancel.class)
>> void doPrePersist(DataObject object) {
>>   if ( object instanceof AuditableCreateCancel ) {
>>     AuditableCreateCancel acc = (AuditableCreateCancel) object;
>>     TblPerson user = getTheUser(object.getObjectContext());
>>     acc.setTblPersonCreate(user);
>>   }
>> }
>> 
>> private TblPerson getTheUser(ObjectContext oc) {
>>   Thread t = Thread.currentThread();
>>   Integer idUser = tlUserId.get();
>>   if ( idUser == null ) {
>>     logger.info("Thread " + t.getId() + " idUser == null ");
>>     return null;
>>   }
>>   logger.info("Thread " + t.getId() + " Looking for TblPerson " + idUser);
>>   TblPerson p = Cayenne.objectForPK(oc, TblPerson.class, idUser.intValue());
>>   return p;
>> }
>> 
>> @Override
>> public void init(DataChannel channel) {
>>   tlUserId = new ThreadLocal<Integer>();
>> }
>> 
>> @Override
>> public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) {
>>   return filterChain.onQuery(originatingContext, query);
>> }
>> 
>> @Override
>> public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelFilterChain filterChain) {
>>   try {
>>     return filterChain.onSync(originatingContext, changes, syncType);
>>   } finally {
>>     //
>>   }
>> }
>> 
>> public void setTheUserId(int idUser) {
>>   Thread t = Thread.currentThread();
>>   logger.info("Thread " + t.getId() + " setTheUserId " + idUser);
>>   tlUserId.set(Integer.valueOf(idUser));
>> }
>> 
>> }
>> 
>