What are Squeezers?

Squeezing is the way in which Tapestry passes data between requests. Data is "squeezed" in some way into the page data, more specifically into hidden form objects, hyperlinks, textfields or anywhere a value is required to be held for some subsequent action to be performed. By default any page data that needs to be squeezed is squeezed as Base64. Fair enough. But what about when you want to use those nice Tapestry features such as throwing lists of Cayenne DataObjects into your page? To illustrate...

See this code:

   <span jwcid="@For" source="ognl:results" value="ognl:currentRow" index="ognl:rowIndex">

            <a jwcid="@DirectLink"
                       listener="ognl:listeners.delete"
                       parameters="ognl:currentRow"
                       onclick="return window.confirm('Are you sure you want remove this record?');">
                        Delete
                    </a>

            <a jwcid="@DirectLink"
                       listener="ognl:listeners.update"
                       parameters="ognl:currentRow">
                        Update
            </a>

Will generate this:

  <tr>
     <td class="buttonText">
            <a href="/crud-web/app?component=border.%24CrudMenu.%24DirectLink&amp;page=CrudList&amp;service=direct&amp;session=T&amp;sp=ZH4sIAAAAAAAAAHVSv2%2FTQBR%2BcRLyg4j%2BQJShpQQ2FltCQgwdoHKIGuSqQgkMWeBiH7HLxefenROHAcH%2FwMLAf4CEWFgQA2JgpgsSsAEjEgtITPDOThsXCUs%2B3333ve997%2Fk9%2Bw5lKaDJxdB0ReyZLh9FPKShkqaN522qSCe8y1c%2FPPyzsL5nGtDoQ4MwxidBOLR5NO3DicOjoETRHNCijB4BbkYeMjpQdzmLR2E3uI%2BHakSGVG%2F7cEr6KW%2FTG5PQpV6XEuH6DhzX3rYo8aiQCs45aNfSkDW3a9lzyoYDa97ATnPINhetQEaMTK8lLotlwEMFi84uGRMrVgGznEAqjDidj7gRB%2B69LPsePAADLXiZSI8mSsFyFs9IOLS6SqBlVKhpzGZEoselHCGFNpKogA8ArONbLmDXz%2F6v67MywssXP%2F9afW4YOn%2FdT0Ftduao4h80ZCFXzTaJMBWqn9SYqTFzUwgy1ZHJo%2F0zT96Rp0UodKAksedJhG5gUsKlnmDU0jxqi0gf1cqVj2%2Fertx5XwSjDXXGidcmruKiAzXlC4p%2FjHlJdOWq1oHGpIrrot4mUvus5eX%2FBRTUe1SqZo8MGB0LWDlsCI4JSVuh71%2B%2BLjULP7%2BAgaIC1o6SSKy4efuA%2BvuHmzSXP13KqBc0lQ92qaukmjJqumRKw5CadvZtocBOev3CGj1%2Btf%2FtaxGK12FBhiTCutQt7C6OiwPVTKTjKTifDl9O1JqJWjszDk7CsTFhMU3rrSTRX3LUBHhlAwAA&amp;state:CrudList=BrO0ABXcfAAAAAQEADCRDcnVkUmVzdWx0cwAKbGFzdFBhZ2VOcnNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAC">
                Test Table
            </a>
     </td>
  </tr>
  <tr>
        <td class="buttonText">
            <a href="/crud-web/app?component=border.%24CrudMenu.%24DirectLink&amp;page=CrudList&amp;service=direct&amp;session=T&amp;sp=ZH4sIAAAAAAAAAHVTPW8TQRAdf8WOY%2BIkiFAkgKGjuZOQEEUKiGysGDmKkANFGljfLfaF9e5ld845UyD4DzQU%2FAMkREODKBAFNWmQgA4okWhAooLZOydxhLLS7e1793bmzeze8x9QMBpqSvccT0e%2B46lBqCSXaJw64XWOrCXvqaWPj%2F5Wz%2B44WahsQYUJoXYD2aurcLQFswdQc4Z8gmhwwY8Qt0KfFC0oe0pEA9kJHhAohazH7XILTpl%2Bolv1h0x63O9wpr1%2BG2astzXOfK4Nwvk22XUt5R7adeuHkpU2LPvdepLDNJVuBCYUbHQ99kRkAiUR5trbbMjcCAPhtgODtOP05I6bUeDdT7PvwEPIkgU%2FDbLJY0RYSPcLJntuBzVZpgjTlqsLZsjj%2FIQgoVbiMEMDAKboyWWo6%2BeO6%2Fq4DHnl0pffSy%2ByWZu%2F3E9Ia3bsqNjfb0h1opp1FlIqin7Sco7lnFWt2cjujB%2FvnXn6nj3LQaYFeUM9j0NyU9jN01xGKAYSVxE1QtlXUVfwFMwMbSOYTlHJnmG6LHaDRB9TvvnDfGvM9MlHofjp7bvFux9ykG1CWSjmN5mHSrdgGvua01kLPw6vXgM7KrslmuesG1vfCYSptLEWzVCqlkTe49rCKvnb1BF3m0wYbpkKyRuJY4tmEfINMhkbi6Zt%2BLTC%2FwmEyiY3WBu3fKhh8eBUqE6WnIdVvHqTr2V%2BfYUsQKxh%2BaiIRaicO%2FvSPz%2B9uLbw%2BXIqvWilqrvNPTQ4Etzx2IhLyZ16%2BiajbCP5%2FNIdPHm99%2F1bDnI3oGokC6lFeJts0Z1tQykN0vIRLiR%2FwERQdxzU3Rhr6DpODZmIeFJxMQ7%2FAanX3oHqAwAA&amp;state:CrudList=BrO0ABXcfAAAAAQEADCRDcnVkUmVzdWx0cwAKbGFzdFBhZ2VOcnNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAC">

                Test Headers
            </a>
        </td>
  </tr>
  <tr>
        <td class="buttonText">
            <a href="/crud-web/app?component=border.%24CrudMenu.%24DirectLink&amp;page=CrudList&amp;service=direct&amp;session=T&amp;sp=ZH4sIAAAAAAAAAHVSv2%2FTQBR%2BcRLyoxFpiyhDSwlsLLaEhBg6QOQQNchVhVIYssDFPmKXi8%2B9OycOA4L%2FgYWB%2FwAJsbAgBsTATBckYANGJBaQmOCdnTTpgCWf733%2B3ve%2Be%2B%2Be%2F4CiFNDgYmC6IvZMlw8jHtJQSdPGeIcq0gnv8fWPj%2F7WNw9MA2o9qBHG%2BDgIBzaPJj04eRQKShRdAFqU0WPArchDRgeqLmfxMOwGDzAoR2RA9bYHp6Wf8preiIQu9bqUCNd3YEl726bEo0IqOO%2BgXUtD1tyuZc8pWw5seH07rSHbXLQCGTEyuZ64LJYBDxUsO%2FtkRKxYBcxyAqkw48xixs04cO9n1Q%2FgIRhowctE9miiFKxm%2BYyEA6urBFpGhYrGbEYkelxZIKTQVhLl8AGATXyLOez6uf91fXqM8MqlL7%2FXXxiGrl%2F1U1CbnToq%2BbOG1BdOs0MiLIXqpzRmasxsCkEmOjN5fHj26XvyLA%2B5DhQk9jyJ0A2MC7hUE8xamWdtE%2BmjWrH06e27tbsf8mC0oco48drEVVx0oKJ8QXFizEuiq9e0DtTGZVyX9TaR2mcFd0Ymr2BppBtKRFMpoaDq8bjPqA7m3JkVHFE2jQadTW0kYO2oY3iPSNqrPSrVqzeFRu7XVzCwqoCN4yQSK27emVH%2F%2FHSTxurnyxn1oqby%2Fj51lVQTRk2XTGgYUtPOvi0U2E1%2Fv7SGT14ffv%2BWh%2FwNqMuQRHhwdRvbj84cKGciHU%2FBhfR2LohaU1Frd8rBq3JiRFhM01OXkugf30kPbIYDAAA%3D&amp;state:CrudList=BrO0ABXcfAAAAAQEADCRDcnVkUmVzdWx0cwAKbGFzdFBhZ2VOcnNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAC">
                Column exclusion
            </a>
        </td>
  </tr>

All that data and that was only for a small data object!

It would be much better to get the result to look more like:

 <td class="buttonText">
   <a href="/crud-web/app?component=%24CrudResults.%24DirectLink_4&amp;page=CrudList&amp;service=direct&amp;session=T&amp;sp=Pnt%2FCrudTest%2F1&amp;state:CrudList=BrO0ABXcfAAAAAQEADCRDcnVkUmVzdWx0cwAKbGFzdFBhZ2VOcnNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAC" onclick="return window.confirm('Are you sure you want remove this record?');">
      Delete
   </a>
 </td>
 <td class="buttonText">
    <a href="/crud-web/app?component=%24CrudResults.%24DirectLink_5&amp;page=CrudList&amp;service=direct&amp;session=T&amp;sp=Pnt%2FCrudTest%2F1&amp;state:CrudList=BrO0ABXcfAAAAAQEADCRDcnVkUmVzdWx0cwAKbGFzdFBhZ2VOcnNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAC">
        Update
    </a>
 </td>
 <td class="buttonText">
    <a href="/crud-web/app?component=%24CrudResults.%24DirectLink_6&amp;page=CrudList&amp;service=direct&amp;session=T&amp;sp=Pnt%2FCrudTest%2F1&amp;state:CrudList=BrO0ABXcfAAAAAQEADCRDcnVkUmVzdWx0cwAKbGFzdFBhZ2VOcnNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAC" onclick="return window.confirm('Are you sure you want copy this record?');">
        Copy
    </a>
 </td>

Clearly we need a better solution. What we need is to implement our own Squeezer!

The Cayenne Squeezer

Luckily like most problems there is already an answer out there. I'll walk through this one solution provided primarily by Robert Zeigler http://equalitylearning.org/Tassel/app?service=external/ViewComponent&sp=SCayenneDataObjectSqueezeAdaptor.

Once you have your project setup to use a squeezer its use will become totally transparent.

The term Squeezer in this solution is just a fancy way of saying:

  1. When we put a Cayenne DataObject into a page we will just store a reference to it, in the case its primary key.
  2. When we get the Cayenne DataObject back from the page (back into Java) we de-reference it by getting Cayenne to get the object back based on its primary key.

    Note: This is not a secure solution. A malicious user could hack the page and alter the primary key. If you want a secure solution check some of the discussions on the Cayenne mailing lists. One idea is to provide a one-way has of the key or to use keys based on hashes or GUID's.

The Squeezer

Roberts original squeezer code is included with this package under the source package org.rz.squeezer. I won't go into more depth about its inner workings except to say that it converts objects to primary keys and back again

Register squeezer with HiveMind

Once we have the Squeezer code in place all we have to do is register it with the Tapestry engine via a HiveMind configuration point. So in your hivemodule.xml add the following:

    <!-- Declare the Squeezer code as a service point -->
    <service-point id="CayenneAdapter" interface="org.rz.squeezer.IDataObjectAdapter">
        <invoke-factory>
            <construct class="org.rz.squeezer.DataObjectAdaptor"/>
        </invoke-factory>
        <!--<interceptor service-id="hivemind.LoggingInterceptor"/>-->
    </service-point>

    <!--Register the new squeezer with Tapestry-->
    <contribution configuration-id="tapestry.data.SqueezeAdaptors">
        <adaptor object="service:CayenneAdapter"/>
    </contribution>

Now when the page developer puts or gets objects of type DataObject onto pages the CayenneSqueezer will handle the putting and getting. Objects of other types will still be handled using the default - Base64.