RE: Note to Cayenne/Tapestry users ...

From: Philip Miller (philip.mille..bc.co.uk)
Date: Mon Jan 09 2006 - 11:38:56 EST

  • Next message: Andrus Adamchik: "Cayenne PetStore"

    Thanks Bryan that looks very useful. I'll let you know how I get on.

    > -----Original Message-----
    > From: Bryan Lewis [mailto:brya..aine.rr.com]
    > Sent: 06 January 2006 17:57
    > To: cayenne-use..bjectstyle.org
    > Subject: Re: Note to Cayenne/Tapestry users ...
    >
    > Philip Miller wrote:
    >
    > >Unfortunately I can only find documentation for v3.0. If anyone has
    > >successfully implemented squeezers in Tapestry 4 and can
    > explain to a
    > >newbie, I'd be delighted to hear.
    > >
    > I've been upgrading our apps from Tap3 to 4, including a
    > DataSqueezer.
    > I think I fgiured it out from the source code for the
    > built-in DataSqueezers.
    >
    > I created a new class ModelObjectAdaptor, so named because
    > all my DataObjects derive from a ModelObject "wedge" class.
    > Could've called it CayenneDataObjectAdaptor, I guess.
    >
    >
    > import org.apache.tapestry.services.DataSqueezer;
    > import org.apache.tapestry.util.io.SqueezeAdaptor;
    >
    > import model.Model;
    > import model.ModelObject;
    > import model.UserContext;
    >
    > /**
    > * Squeezes a ModelObject, our version of a CayenneDataObject.
    > * Allows our page code to deal only with objects, not oids.
    > * It uses the prefix 'k', resulting in a squeezed parameter like
    > * "kCompany-5885".
    > */
    > public class ModelObjectAdaptor implements SqueezeAdaptor {
    > public String getPrefix()
    > {
    > return Model.DATASQUEEZER_PREFIX;
    > }
    >
    > public Class getDataClass()
    > {
    > return ModelObject.class;
    > }
    >
    > public String squeeze(DataSqueezer squeezer, Object object)
    > {
    > return Model.getDataSqueezerKeyFromObject(object);
    > }
    >
    > public Object unsqueeze(DataSqueezer squeezer, String key)
    > {
    > // Magically get the current request's DataContext.
    > return
    > Model.getObjectFromDataSqueezerKey(UserContext.getDataContext(), key);
    > }
    > }
    >
    > I have some static methods in a Model utility class for
    > historical reasosn. Come to think of it, I could've included
    > these methods in the ModelObjectAdaptor code... they're not
    > needed anywhere else. They are:
    >
    >
    > public static final String DATASQUEEZER_PREFIX = "k";
    > private static final String DATASQUEEZER_SEPARATOR = "-";
    >
    > /** Returns the key String needed by our custom
    > ModelObject DataSqueezer;
    > * see Engine.createDataSqueezer(). The string will
    > start with a "k"
    > * prefix, then the entityName, a hyphen and the oid, e.g.,
    > * "kCompany-5885".
    > */
    > public static String getDataSqueezerKeyFromObject(Object object)
    > {
    > ModelObject modelObject = (ModelObject) object;
    >
    > // This works only if the object has a single-column
    > primary key. It
    > // could probably be generalized to any ObjectId -- see
    > // compoundPKForObject() -- but this should satisfy our needs.
    > Object pkObject = null;
    > try {
    > pkObject = DataObjectUtils.pkForObject(modelObject);
    > }
    > catch (Exception ex) {
    > log.error("getDataSqueezerKeyFromObject(): object
    > that doesn't have a single-column pk?! " + ex);
    > return null;
    > }
    >
    > // There might be a simpler way to do this. Get the
    > class name and
    > // strip off the "model." prefix.
    > String className = modelObject.getClass().getName();
    > int index = className.lastIndexOf('.');
    > String entityName = className.substring(index + 1);
    >
    > StringBuffer sb = new StringBuffer();
    >
    > sb.append(DATASQUEEZER_PREFIX).append(entityName);
    > sb.append(DATASQUEEZER_SEPARATOR).append(pkObject.toString());
    > return sb.toString();
    > }
    >
    > /** Returns a ModelObject corresponding to the given key. */
    > public static ModelObject getObjectFromDataSqueezerKey(DataContext
    > dc, String key)
    > {
    > // A minimum key would be 4 characters long, "kX-1".
    > assert key != null && key.length() >= 4;
    > assert dc != null;
    >
    > int index = key.indexOf(DATASQUEEZER_SEPARATOR);
    > assert index > 1;
    > // We drop the initial 'k' prefix too.
    > String entityName = key.substring(1, index);
    >
    > // So far all our oids are Integers. Some day we
    > might need to deal
    > // with another type, either by creating another
    > DataSqueezer or by
    > // asking Cayenne for the entity's pk type and doing
    > the right thing.
    > Integer oid = new Integer(key.substring(index + 1));
    >
    > // Call the nice utility method that gets the object
    > from the cache.
    > return (ModelObject) DataObjectUtils.objectForPK(dc,
    > entityName, oid);
    > }
    >
    > The UserContext class is the same trick from Tapestry 3, a
    > ThreadLocal to hold the DataContext where anyone can get it.
    >
    > public final class UserContext
    > {
    > private static final ThreadLocal dataContextLocal = new
    > ThreadLocal();
    >
    > // Disable construction.
    > private UserContext() {}
    >
    > /** Engine.requestDestroyed() calls this. It prevents
    > lingering object
    > * references by nullifying all threadLocals.
    > */
    > public static void reset()
    > {
    > setDataContext(null);
    > setAppName(null);
    > }
    >
    > public static DataContext getDataContext()
    > {
    > return (DataContext) dataContextLocal.get();
    > }
    > public static void setDataContext(DataContext dc)
    > {
    > dataContextLocal.set(dc);
    > }
    > }
    >
    > I tell hivemind to add my DataSqueezer to the list with this
    > snippet of hivemodule.xml in the same package as the
    > ModelObjectAdapter code:.
    >
    > <?xml version="1.0"?>
    > <module id="cview" version="1.0.0">
    > <contribution configuration-id="tapestry.data.SqueezeAdaptors">
    > <adaptor object="instance:cview.base.ModelObjectAdaptor"/>
    > </contribution>
    > </module>
    >
    > The final trick came from Cayenne 1.2's WebApplicationContextProvider.
    > You can use that directly if you don't have a custom
    > configuration. I have this code in my Engine class:
    >
    > public static final String DATA_CONTEXT_KEY =
    > "cayenne.datacontext";
    >
    > /** This method is called by the servlet container when a
    > session is
    > * created. It creates a DataContext and stores it in
    > the session for
    > * later retrieval by requestInitialized(). This is the
    > same approach
    > * used by Cayenne 1.2's WebApplicationContextProvider,
    > except calling
    > * our custom getConfiguration().
    > */
    > public void sessionCreated(HttpSessionEvent se)
    > {
    > log.info("sessionCreated, no login yet; creating
    > DataContext");
    >
    > Configuration config = Model.getConfiguration();
    > DataContext dataContext =
    > config.getDomain().createDataContext();
    >
    > // Store the DC in the session.
    > se.getSession().setAttribute(DATA_CONTEXT_KEY, dataContext);
    > }
    >
    > /** This is called at the beginning of each request; we
    > use it as we did
    > * the old Tapestry 3 setupForRequest(). Primarily it
    > retrieves the
    > * DataContext stored earlier in the HttpSession and
    > binds it to the
    > * current thread.
    > */
    > public void requestInitialized(ServletRequestEvent sre)
    > {
    > // Get the DataContext from the session and bind to
    > the thread.
    > DataContext dc = null;
    > ServletRequest req = sre.getServletRequest();
    > HttpSession session = null;
    > if (req instanceof HttpServletRequest) {
    > session = ((HttpServletRequest) req).getSession();
    > dc = (DataContext) session.getAttribute(DATA_CONTEXT_KEY);
    > }
    > UserContext.setDataContext(dc);
    > }
    >
    > In order to get notified about the session and request
    > initialization, the Engine implements HttpSessionListener and
    > ServletRequestListener.
    > Each web-app's web.xml specifies Engine as a listener:
    >
    > <listener>
    > <listener-class>cview.base.Engine</listener-class>
    > </listener>
    >
    > I think that's it. It seems like a bit of plumbing, eh? I
    > wouldn't be surprised (or offended :-) if someone pointed out
    > how it could be more elegant. It works.
    >
    >
    >

    http://www.bbc.co.uk/

    This e-mail (and any attachments) is confidential and may contain
    personal views which are not the views of the BBC unless specifically
    stated.
    If you have received it in error, please delete it from your system.
    Do not use, copy or disclose the information in any way nor act in
    reliance on it and notify the sender immediately. Please note that the
    BBC monitors e-mails sent or received.
    Further communication will signify your consent to this.



    This archive was generated by hypermail 2.0.0 : Mon Jan 09 2006 - 11:39:49 EST