This needs to go in a Wiki somewhere, as this is THE WAY to develop applications, particularly with a rich domain model.
Joel -----Original Message----- From: Bryan Lewis [mailto:[EMAIL PROTECTED] Sent: Tuesday, July 12, 2005 1:52 PM To: Tapestry users Subject: Re: Tapestry, Cayenne and Squeezer Good job; thanks for doing it. I'd like to add a couple of small details. 1. I found it necessary to set the visit in the UserContext from CustomEngine.createVisit() as well as from setupForRequest(), as in: protected Object createVisit(IRequestCycle cycle) { Visit visit = (Visit) super.createVisit(cycle); UserContext.setVisit(visit); } That might not be needed by some people, but I ran into a case where a user visited our site with a bookmark before logging in, and the visit was null at the time setupForRequest() was called. 2. For people who are using ordinary CayenneDataObjects (rather than disconnected objects as you are), the guts of the squeeze() and unsqueeze() methods boil down to calling Cayenne's DataObjectUtils methods pkForObject() and objectForPK(). They'll fetch the object from the database only when it isn't already in the cache, which is almost never since the object would usually have been fetched before there was any need to form a link to it. ----- Original Message ----- From: "Filip Balas" <[EMAIL PROTECTED]> To: "Tapestry users" <[email protected]> Sent: Tuesday, July 12, 2005 1:13 PM Subject: Re: Tapestry, Cayenne and Squeezer It seems to me that no where on the internet is this whole process explained in enough detail to make it easy to understand in under 15min. So here is my best attempt. I assume: a) You know what a Squeezer is and how to use it. If you do not, visit: http://wiki.apache.org/jakarta-tapestry/DataSqueezer b) You are familiar enough with Cayenne to understand that each user/session must have it's own, unique DataContext (whether you share the 'DataRowStore' or not) Now typically we store the Datacontext in the Visit class. That way each session has one unique data context for all pages. Peace of cake right? Wrong... When using direct links, the cleanest way to serialize and de-serialize objects is using your own SqueezerAdaptor. This is where the problem begins, the ISqueezeAdaptor interface does not provide a mechanism for accessing the Visit class, hence no access to your Datacontext which results in having no way to de-serialize your objects. This is where the Threadlocal solution mentioned above by my savvy colleagues comes in. This solution essentially makes the current visit class globaly available through access to the current running thread. I am not sure how the specifics work <someone can jump in with links to the deatails here> but this essentially gives us what we need to make this machine purrrrrr. Okay, code is worth a thousand words so I'm going to paste some code and explain as I go. First we create the class that gives us access to the current thread and will henceforth be used to provide global access to whatever the current visit class is: public class UserContext { static ThreadLocal _visitLocal = new ThreadLocal(); public static Object getVisit() { return (Visit) _visitLocal.get(); } public static void setVisit(Object visit) { _visitLocal.set(visit); } } You should take note that everything here is static and so can be accessed from anywhere. Next we need to Create a Custom Engine to initialize the UserContext with the Visit and to register our custom Squeeze adapter. NOTE: To register a custom engine, go to your foo.application file and change the engine in the line: <application name="foo" engine-class="com.acme.superApp.CustomEngine"> If you already knew how to do this, this may seem like a silly step to mention but I couldn't find a single place in the TIA book where it tells you this, nor could I find it on the net. public class CustomEngine extends BaseEngine { protected void setupForRequest(RequestContext context) { if (getVisit() != null) UserContext.setVisit(getVisit()); super.setupForRequest(context); } public DataSqueezer createDataSqueezer() { DataSqueezer defaultDataSqueezer = super.createDataSqueezer(); DataSqueezeAdaptor customAdaptor = new DataSqueezeAdaptor(); customAdaptor.register(defaultDataSqueezer); return defaultDataSqueezer; } } The main magic in the above is initializing the UserContext with the visit class currently registered with the Engine. Now because of this magic, we can access the Visit class in our adaptor unsqueeze method: public class DataSqueezeAdaptor implements ISqueezeAdaptor { private static final String prefix = "D"; public DataSqueezeAdaptor() { super(); } public String squeeze(DataSqueezer squeezer, Object data) throws IOException { if (data instanceof IDataObject) return prefix + getDataStore().put((IDataObject) data); return null; } public Object unsqueeze(DataSqueezer squeezer, String string) throws IOException { String id = string.substring(prefix.length()); try { return getDataStore().get(id, getDataContext()); } catch (Exception e) { return null; } } private Visit getVisit() { return (Visit) UserContext.getVisit(); } public void register(DataSqueezer squeezer) { squeezer.register(prefix, IDataObject.class, this); } private DataContext getDataContext() { return getVisit().getDataContext(); } private IDataStore getDataStore() { return getVisit().getDataStore(); } } And there you have it folks! If you're wondering what the IDataStore is, it's simply a Map that serves 2 purposes. First it generates unique IDs for each object and second is stores the objects while they are off on their trip to the client. Why don't I just use the DataContext for this you ask? Because I have chosen to make all of my objects follow a state pattern, so internally they can be either connected (Registered with the DataContext) or Disconnected (Just Pojo's masquerading as DataObjects). This allows my app to always work with objects as whole entities, whithout worrying about how to create and remove them from cayenne (each state does that). Thanks to everyone who helped me figure this one out. Filip On 7/11/05, Robert Zeigler <[EMAIL PROTECTED]> wrote: > Incidentally, I made the data squeezer implementation I sent to Todd > available awhile ago on Tassel. > > Robert > > Todd O'Bryan wrote: > > Robert Ziegler sent me a CayenneDataObjectSqueezeAdaptor.jar awhile > > ago. Check the list to see if he sent it through the list. Otherwise, > > I'd be happy to send it along if I get his permission. > > > > Todd > > > > On Jul 7, 2005, at 9:49 PM, Filip Balas wrote: > > > >> Has anybody been able to get the squeezer interface > >> to work with Cayenne? > >> > >> It seems like it is impossible to make the Datacontext > >> available to the datasqueezer. I can't get the visit class > >> from the squeezer and that is where the context resides?!?!? > >> > >> I tried to use the global map to solve this problem except > >> the map is not initialized until AFTER the squeezer is > >> created. I am at my wits end. > >> > >> HELP! > >> Filip > >> > >> --------------------------------------------------------------------- > >> To unsubscribe, e-mail: [EMAIL PROTECTED] > >> For additional commands, e-mail: [EMAIL PROTECTED] > >> > >> > > > > > > --------------------------------------------------------------------- > > To unsubscribe, e-mail: [EMAIL PROTECTED] > > For additional commands, e-mail: [EMAIL PROTECTED] > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: [EMAIL PROTECTED] > For additional commands, e-mail: [EMAIL PROTECTED] > > --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED] --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED] --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
