-
1. Re: Putting extra data in revinfo
adamw Oct 22, 2010 2:27 AM (in response to florin13)It will be the same thread. If there's no way to statically lookup the current component I don't have any other ideas.
I'd really like to provide some more integration with DI frameworks but I simply don't know how - either Envers must somehow obtain the instance of the listener, or the listener an instance of the components - in both cases a static lookup is needed.
Adam
-
-
3. Re: Putting extra data in revinfo
obastard Oct 26, 2010 1:53 PM (in response to florin13)ThreadLocal is your answer.
Here's what I did. I made a class called "ModelOperation.java" that holds the who, what, why, with what being an enum. Making a new one of these objects push itself into a ThreadLocal while remembering any previous value. Calling .pop on the model operation puts the previous value back (which will be null). In other words there's a stack of pending ModelOperations that hold the data:
ModelOperation mo = new ModelOperation(what, who, why);
// do stuff
mo.pop().
If there isn't a current ModelOperation object, I throw in the Listener, which forces all the code that commits to the database to have an audit trail setup. So you need to do auditing early in development, not later.
Pierce
P.S.
There's one issue with Envers that's kind of a pain, which is that it's kind of hard to set all that up with EJBs, which was why I was searching to see if someone had a better solution. I've ended up having to refactor things to wrap all EJB calls that require a transaction with the above code by having a wrapper EJB that calls the other EJB. Of course, its not too bad, because I need that for optimistic locking failure checks anyways, but it seems to be kind of a hole in the whole EJB spec that you can wrap an invocation, but you can't wrap the transaction commit.
Anyone have a better way of solving that problem? That is, wrapping the execution of the EJB method including the transaction stuff instead of inside the transaction stuff?
/*** Class to hold the current operation being performed on the model for Auditing purposes.** @author pierce*/public class ModelOperation{//~ Static fields/initializers -----------------------------------------------------------------/** DOCUMENT ME! */private static final ThreadLocal<ModelOperation> currentOperation =new ThreadLocal<ModelOperation>();//~ Instance fields ----------------------------------------------------------------------------/** DOCUMENT ME! */public ModelOperationType operationType;/** DOCUMENT ME! */public String who;/** DOCUMENT ME! */public String why;/** DOCUMENT ME! */public ModelOperation previous;//~ Constructors -------------------------------------------------------------------------------/*** Creates a new ModelOperation object.** @param operationType DOCUMENT ME!* @param who DOCUMENT ME!* @param why DOCUMENT ME!*/public ModelOperation(ModelOperationType operationType, String who, String why){super();this.operationType = operationType;this.who = who;this.why = why;push();}//~ Methods ------------------------------------------------------------------------------------/*** DOCUMENT ME!*/public void push(){previous = currentOperation.get();currentOperation.set(this);}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public ModelOperation pop(){currentOperation.set(previous);previous = null;return currentOperation.get();}/*** DOCUMENT ME!*/public static void popAll(){while (currentOperation.get() != null){currentOperation.get().pop();}}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public static ModelOperation current(){return currentOperation.get();}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public ModelOperationType getOperationType(){return this.operationType;}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public String getWho(){return who;}/*** DOCUMENT ME!** @param who DOCUMENT ME!*/public void setWho(String who){this.who = who;}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public String getWhy(){return why;}/*** DOCUMENT ME!** @param why DOCUMENT ME!*/public void setWhy(String why){this.why = why;}/*** DOCUMENT ME!** @param operationType DOCUMENT ME!*/public void setOperationType(ModelOperationType operationType){this.operationType = operationType;}} // end class ModelOperation/*** Class to hold the current operation being performed on the model for Auditing purposes.** @author pierce*/public class ModelOperation{//~ Static fields/initializers -----------------------------------------------------------------/** DOCUMENT ME! */private static final ThreadLocal<ModelOperation> currentOperation =new ThreadLocal<ModelOperation>();//~ Instance fields ----------------------------------------------------------------------------/** DOCUMENT ME! */public ModelOperationType operationType;/** DOCUMENT ME! */public String who;/** DOCUMENT ME! */public String why;/** DOCUMENT ME! */public ModelOperation previous;//~ Constructors -------------------------------------------------------------------------------/*** Creates a new ModelOperation object.** @param operationType DOCUMENT ME!* @param who DOCUMENT ME!* @param why DOCUMENT ME!*/public ModelOperation(ModelOperationType operationType, String who, String why){super();this.operationType = operationType;this.who = who;this.why = why;push();}//~ Methods ------------------------------------------------------------------------------------/*** DOCUMENT ME!*/public void push(){previous = currentOperation.get();currentOperation.set(this);}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public ModelOperation pop(){currentOperation.set(previous);previous = null;return currentOperation.get();}/*** DOCUMENT ME!*/public static void popAll(){while (currentOperation.get() != null){currentOperation.get().pop();}}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public static ModelOperation current(){return currentOperation.get();}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public ModelOperationType getOperationType(){return this.operationType;}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public String getWho(){return who;}/*** DOCUMENT ME!** @param who DOCUMENT ME!*/public void setWho(String who){this.who = who;}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public String getWhy(){return why;}/*** DOCUMENT ME!** @param why DOCUMENT ME!*/public void setWhy(String why){this.why = why;}/*** DOCUMENT ME!** @param operationType DOCUMENT ME!*/public void setOperationType(ModelOperationType operationType){this.operationType = operationType;}} // end class ModelOperation/*** Class to hold the current operation being performed on the model for Auditing purposes.** @author pierce*/public class ModelOperation{//~ Static fields/initializers -----------------------------------------------------------------/** DOCUMENT ME! */private static final ThreadLocal<ModelOperation> currentOperation =new ThreadLocal<ModelOperation>();//~ Instance fields ----------------------------------------------------------------------------/** DOCUMENT ME! */public ModelOperationType operationType;/** DOCUMENT ME! */public String who;/** DOCUMENT ME! */public String why;/** DOCUMENT ME! */public ModelOperation previous;//~ Constructors -------------------------------------------------------------------------------/*** Creates a new ModelOperation object.** @param operationType DOCUMENT ME!* @param who DOCUMENT ME!* @param why DOCUMENT ME!*/public ModelOperation(ModelOperationType operationType, String who, String why){super();this.operationType = operationType;this.who = who;this.why = why;push();}//~ Methods ------------------------------------------------------------------------------------/*** DOCUMENT ME!*/public void push(){previous = currentOperation.get();currentOperation.set(this);}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public ModelOperation pop(){currentOperation.set(previous);previous = null;return currentOperation.get();}/*** DOCUMENT ME!*/public static void popAll(){while (currentOperation.get() != null){currentOperation.get().pop();}}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public static ModelOperation current(){return currentOperation.get();}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public ModelOperationType getOperationType(){return this.operationType;}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public String getWho(){return who;}/*** DOCUMENT ME!** @param who DOCUMENT ME!*/public void setWho(String who){this.who = who;}/*** DOCUMENT ME!** @return DOCUMENT ME!*/public String getWhy(){return why;}/*** DOCUMENT ME!** @param why DOCUMENT ME!*/public void setWhy(String why){this.why = why;}/*** DOCUMENT ME!** @param operationType DOCUMENT ME!*/public void setOperationType(ModelOperationType operationType){this.operationType = operationType;}} // end class ModelOperation-
EnversAuditTrail.java.zip 734 bytes
-
AuditListener.java.zip 680 bytes
-
ModelOperation.java.zip 652 bytes
-
-
4. Re: Putting extra data in revinfo
florin13 Oct 27, 2010 9:34 PM (in response to obastard)Thanks guys, in the end I went with ThreadLocal. Seems like a simple enough solution for my needs.
-
5. Re: Putting extra data in revinfo
adamw Oct 29, 2010 4:59 AM (in response to obastard)Did you try registering a transaction synchronisation?
Adam
-
6. Re: Putting extra data in revinfo
obastard Nov 15, 2010 11:27 AM (in response to adamw)Didn't know such a thing existed. Googling. Ah, I see. I can register a class to get called after the transaction completes. Cool. Thanks for the tip. It's part of JTA not EJB.
Though currently, I've replaced all my Transaction.REQUIRED stuff with an interceptor solution instead, so that I could also implement retrying optimistic lock failures.