AS OF TAPESTRY 5.1 this code no longer seems to work. The classes Base64ObjectInputStream and Base64ObjectOutputStream no longer exist in org.apache.tapestry.internal.services.*;
Look at the Cookies API instead
http://tapestry.apache.org/tapestry5/tapestry-core/apidocs/org/apache/tapestry/services/Cookies.html
This example code creates a basic PersistentFieldStrategy for "cookie" and "flashcookie" @Persist values
This code is a 'trimmed back' version of what I use... Everything is b64 encoded. You can encode a key of some sort (ValueEncoder index, or class type / name) but it adds extra complexity.
The reason that I wanted to do this was because none of the existing persistence strategies fit my requirements.
The driving factor, as in so many web applications, was to avoid creating a session, and I really didn't like the large t:client:state cgi param.
I have this defined in my common base class:
@Meta("tapestry.persistence-strategy=flashcookie")
So, here you have it... my contribution to the tapestry corner. Enjoy!
Oh - a few Caveats, etc
first - when developing add a showAllCookies t:grid into your border component from RequestGlobals.
flashcookie is safe, it doesn't hang around. I recommend you use this for all flash / instant requirements
Here is an example code to add to your border or footer-type component, to display the cookies. Note that it displays all cookies, not just ones with a specific prefix.
@Inject private RequestGlobals _requestGlobals; @Component(id = "cookieJar", parameters = { "source=allCookies", "rowsPerPage=100", "pagerPosition=both", "row=currentCookie" }) private Grid cookieJar; @Property private Cookie currentCookie; public List<Cookie> getAllCookies() { List<Cookie> cookieList = new ArrayList<Cookie>(); Cookie cookies[] = _requestGlobals.getHTTPServletRequest().getCookies(); if (cookies != null) for (int i = 0; i < cookies.length; i++) cookieList.add(cookies[i]); return cookieList; }
Then add this to the corresponding tml:
<t:cookieJar />
cookie can hang around after sessions... I delete all cookies on logout... but if you don't login (or just exit the browser), they are still there. I don't think this is safe.
all cookies are mapped to the entire app (context root). I don't know if this is strictly necessary, but it works.
Possible extensions:
set cookie max age (inactivity time)... where to get session timeout value from?
how to make cookies expire as soon as session is closed
First of all, a contribution had to made in AppModule:
public void contributePersistentFieldManager(MappedConfiguration<String, PersistentFieldStrategy> configuration, RequestGlobals requestGlobals, Request request) { System.out.println("################## in contributePersistentFieldManager of " + PostbackModule.class.getName()); configuration.add(CookiePersistentField.COOKIE, new CookiePersistentField(requestGlobals, request, CookiePersistentField.COOKIE)); configuration.add(CookiePersistentField.FLASHCOOKIE, new CookiePersistentField(requestGlobals, request, CookiePersistentField.FLASHCOOKIE)); }
and the CookiePersistentField source (simplified to use B64 only)
/* * Created on 12 Apr 2008 * * @author nicholas[dot] krul _at_ gmail com * */ package koncept.postback.base; import static org.apache.tapestry.ioc.internal.util.Defense.notBlank; import java.util.*; import javax.servlet.http.*; import koncept.postback.base.encoders.*; import org.apache.tapestry.internal.services.*; import org.apache.tapestry.services.*; public class CookiePersistentField implements PersistentFieldStrategy { public static final String COOKIE = "cookie"; public static final String FLASHCOOKIE = "flashcookie"; private String persistenceType; private static final String SEPERATOR = "|"; private RequestGlobals requestGlobals; private Request request; private EncoderBase64 encoder; public CookiePersistentField(RequestGlobals requestGlobals, Request request, String persistenceType) { this.requestGlobals = requestGlobals; this.request = request; this.persistenceType = persistenceType; encoder = new EncoderBase64(); } public void postChange(String pageName, String componentId, String fieldName, Object newValue) { notBlank(pageName, "pageName"); notBlank(fieldName, "fieldName"); StringBuilder builder = new StringBuilder(persistenceType); builder.append(SEPERATOR); builder.append(pageName); builder.append(SEPERATOR); if (componentId != null) builder.append(componentId); builder.append(SEPERATOR); builder.append(fieldName); String key = builder.toString(); if (newValue != null) { String value = encoder.toClient(newValue); createCookie(key, value); } else { //newValue == null deleteCookie(key); } } private PersistentFieldChange buildChange(Cookie cookie) { String value = cookie.getValue(); if (value == null || value.isEmpty()) return null; //not needed String[] chunks = cookie.getName().split("\\" + SEPERATOR); //oops... regexp String componentId = chunks[2]; String fieldName = chunks[3]; Object attribute = encoder.toValue(value); return new PersistentFieldChangeImpl(componentId, fieldName, attribute); } public Collection<PersistentFieldChange> gatherFieldChanges(String pageName) { Collection<PersistentFieldChange> changes = new ArrayList<PersistentFieldChange>(); String fullPrefix = persistenceType + SEPERATOR + pageName + SEPERATOR; for (Cookie cookie: getCookiesStartingWith(fullPrefix)) { try { PersistentFieldChange fieldChange = buildChange(cookie); if (fieldChange != null) changes.add(fieldChange); if (persistenceType.equals(FLASHCOOKIE)) deleteCookie(cookie.getName()); } catch (RuntimeException e) { throw new RuntimeException("Error with cookie name: " + cookie.getName(),e); } } return changes; } public void discardChanges(String pageName) { String fullPrefix = persistenceType + SEPERATOR + pageName + SEPERATOR; for (Cookie cookie: getCookiesStartingWith(fullPrefix)) { deleteCookie(cookie.getName()); } } private List<Cookie> getCookiesStartingWith(String prefix) { List<Cookie> cookieList = new ArrayList<Cookie>(); Cookie cookies[] = requestGlobals.getHTTPServletRequest().getCookies(); if (cookies != null) for (int i = 0; i < cookies.length; i++) if (cookies[i].getName().startsWith(prefix)) cookieList.add(cookies[i]); return cookieList; } private void createCookie(String name, String value) { Cookie cookie = new Cookie(name, value); cookie.setPath(request.getContextPath()); requestGlobals.getHTTPServletResponse().addCookie(cookie); } private void deleteCookie(String name) { Cookie cookie = new Cookie(name, "_"); //'empty values may cause problems' cookie.setMaxAge(0); cookie.setPath(request.getContextPath()); requestGlobals.getHTTPServletResponse().addCookie(cookie); } }
And the EncoderBase64 class ...
/* * Created on 13 Apr 2008 * */ package koncept.postback.base.encoders; import java.io.*; import org.apache.tapestry.*; import org.apache.tapestry.internal.util.*; import org.apache.tapestry.ioc.internal.util.*; public class EncoderBase64 implements ValueEncoder<Object> { public Object toValue(String clientValue) { Object value = null; ObjectInputStream in = null; try { in = new Base64ObjectInputStream(clientValue); value = in.readObject(); } catch (Exception e) { throw new RuntimeException("client state corrupted", e); } finally { InternalUtils.close(in); } return value; } public String toClient(Object value) { Base64ObjectOutputStream os = null; try { os = new Base64ObjectOutputStream(); os.writeObject(value); } catch (Exception ex) { throw new RuntimeException(ex.getMessage(), ex); } finally { InternalUtils.close(os); } return os.toBase64(); } }
There you go - an easy solution for
@Persist("cookie") @Persist("flashcookie")
annotations.
--nK
--nicholas Krul