Author: mbooth Date: 2004-11-11 22:44:25 +0100 (Thu, 11 Nov 2004) New Revision: 96 Added: ccm-user-preferences/ ccm-user-preferences/trunk/ ccm-user-preferences/trunk/application.xml ccm-user-preferences/trunk/etc/ ccm-user-preferences/trunk/pdl/ ccm-user-preferences/trunk/pdl/com/ ccm-user-preferences/trunk/pdl/com/arsdigita/ ccm-user-preferences/trunk/pdl/com/arsdigita/london/ ccm-user-preferences/trunk/pdl/com/arsdigita/london/userprefs/ ccm-user-preferences/trunk/pdl/com/arsdigita/london/userprefs/UserPrefs.pdl ccm-user-preferences/trunk/sql/ ccm-user-preferences/trunk/sql/ccm-user-preferences/ ccm-user-preferences/trunk/sql/ccm-user-preferences/oracle-se-create.sql ccm-user-preferences/trunk/sql/ccm-user-preferences/postgres-create.sql ccm-user-preferences/trunk/src/ ccm-user-preferences/trunk/src/ccm-user-preferences.config ccm-user-preferences/trunk/src/ccm-user-preferences.load ccm-user-preferences/trunk/src/com/ ccm-user-preferences/trunk/src/com/arsdigita/ ccm-user-preferences/trunk/src/com/arsdigita/london/ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/Initializer.java ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/PersistentUserPrefs.java ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/Pref.java ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/UserPrefs.java ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/UserPrefsSessionListener.java ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/ui/ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/ui/UserPrefsComponent.java Log: Initial import Added: ccm-user-preferences/trunk/application.xml =================================================================== --- ccm-user-preferences/trunk/application.xml 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/application.xml 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> + +<ccm:application name="ccm-user-preferences" + prettyName="User Preferences" + version="1.0.0" + release="1" + xmlns:ccm="http://ccm.redhat.com/ccm-project"> + + <ccm:dependencies> + <ccm:requires name="ccm-core" version="6.1.0"/> + </ccm:dependencies> + + <ccm:contacts> + <ccm:contact uri="http://www.redhat.com/software/rhea" type="website"/> + <ccm:contact uri="mailto:rh...@re..." type="support"/> + </ccm:contacts> + + <ccm:description> + The Red Hat Web Application Framework is a platform for writing + database-backed web applications in Java. + </ccm:description> + +</ccm:application> Added: ccm-user-preferences/trunk/pdl/com/arsdigita/london/userprefs/UserPrefs.pdl =================================================================== --- ccm-user-preferences/trunk/pdl/com/arsdigita/london/userprefs/UserPrefs.pdl 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/pdl/com/arsdigita/london/userprefs/UserPrefs.pdl 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,37 @@ +// +//This library is free software; you can redistribute it and/or +//modify it under the terms of the GNU Lesser General Public License +//as published by the Free Software Foundation; either version 2.1 of +//the License, or (at your option) any later version. +// +//This library is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//Lesser General Public License for more details. +// +//You should have received a copy of the GNU Lesser General Public +//License along with this library; if not, write to the Free Software +//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +model com.arsdigita.london.userprefs; + +import com.arsdigita.kernel.User; + +object type PersistentUserPrefs { + BigDecimal[1..1] id = user_prefs.id; + + unique User[0..1] user = join user_prefs.user_id to users.user_id; + unique Long[0..1] cookie = user_prefs.cookie; + component Pref[0..n] prefs = join user_prefs.id to user_pref.prefs_id; + + object key ( id ); +} + +object type Pref { + BigDecimal[1..1] id = user_pref.id; + + String[1..1] key = user_pref.key; + String[0..1] value = user_pref.value; + + object key ( id ); +} Added: ccm-user-preferences/trunk/sql/ccm-user-preferences/oracle-se-create.sql =================================================================== --- ccm-user-preferences/trunk/sql/ccm-user-preferences/oracle-se-create.sql 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/sql/ccm-user-preferences/oracle-se-create.sql 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,3 @@ +@ ddl/oracle-se/create.sql +@ ddl/oracle-se/deferred.sql + Added: ccm-user-preferences/trunk/sql/ccm-user-preferences/postgres-create.sql =================================================================== --- ccm-user-preferences/trunk/sql/ccm-user-preferences/postgres-create.sql 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/sql/ccm-user-preferences/postgres-create.sql 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,4 @@ +begin; +\i ddl/postgres/create.sql +\i ddl/postgres/deferred.sql +end; Added: ccm-user-preferences/trunk/src/ccm-user-preferences.config =================================================================== --- ccm-user-preferences/trunk/src/ccm-user-preferences.config 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/src/ccm-user-preferences.config 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<registry> + <config class="com.arsdigita.london.atoz.AtoZConfig" + storage="ccm-ldn-atoz/atoz.properties"/> +</registry> Added: ccm-user-preferences/trunk/src/ccm-user-preferences.load =================================================================== --- ccm-user-preferences/trunk/src/ccm-user-preferences.load 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/src/ccm-user-preferences.load 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,18 @@ +<load> + <requires> + <table name="inits"/> + <table name="acs_objects"/> + <initializer class="com.arsdigita.core.Initializer"/> + </requires> + <provides> + <table name="user_prefs"/> + <table name="user_pref"/> + <initializer class="com.arsdigita.london.userprefs.Initializer"/> + </provides> + <scripts> + <schema directory="ccm-user-preferences"/> + <!-- + <data class="com.arsdigita.london.userprefs.Loader"/> + --> + </scripts> +</load> Added: ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/Initializer.java =================================================================== --- ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/Initializer.java 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/Initializer.java 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,47 @@ +/* +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2.1 of +the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package com.arsdigita.london.userprefs; + +import com.arsdigita.db.DbHelper; +import com.arsdigita.persistence.pdl.ManifestSource; +import com.arsdigita.persistence.pdl.ManifestSource; +import com.arsdigita.persistence.pdl.NameFilter; +import com.arsdigita.runtime.CompoundInitializer; +import com.arsdigita.runtime.DomainInitEvent; +import com.arsdigita.runtime.PDLInitializer; +import com.arsdigita.runtime.RuntimeConfig; + +/** + * Initializes the user preferences system + * @version $Id$ + */ +public class Initializer extends CompoundInitializer { + public Initializer() { + final String url = RuntimeConfig.getConfig().getJDBCURL(); + final int database = DbHelper.getDatabaseFromURL(url); + + add(new PDLInitializer + (new ManifestSource + ("ccm-user-preferences.pdl.mf", + new NameFilter(DbHelper.getDatabaseSuffix(database), "pdl")))); + } + + public void init( DomainInitEvent evt ) { + PersistentUserPrefs.domainInit(); + Pref.domainInit(); + } +} Added: ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/PersistentUserPrefs.java =================================================================== --- ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/PersistentUserPrefs.java 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/PersistentUserPrefs.java 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,229 @@ +/* +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2.1 of +the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package com.arsdigita.london.userprefs; + +import com.arsdigita.db.Sequences; +import com.arsdigita.domain.DomainCollection; +import com.arsdigita.domain.DomainObject; +import com.arsdigita.domain.DomainObjectFactory; +import com.arsdigita.domain.DomainObjectInstantiator; +import com.arsdigita.kernel.User; +import com.arsdigita.persistence.DataAssociation; +import com.arsdigita.persistence.DataCollection; +import com.arsdigita.persistence.DataObject; +import com.arsdigita.persistence.OID; +import com.arsdigita.persistence.SessionManager; +import com.arsdigita.util.UncheckedWrapperException; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +import org.apache.log4j.Logger; + +public class PersistentUserPrefs extends DomainObject { + private static final Logger s_log = + Logger.getLogger( PersistentUserPrefs.class ); + + public static final String BASE_DATA_OBJECT_TYPE = + "com.arsdigita.london.userprefs.PersistentUserPrefs"; + + public static final String ID = "id"; + public static final String USER = "user"; + public static final String PREFS = "prefs"; + public static final String COOKIE = "cookie"; + + PersistentUserPrefs() { + super( BASE_DATA_OBJECT_TYPE ); + + try { + set( ID, Sequences.getNextValue() ); + } catch( SQLException ex ) { + throw new UncheckedWrapperException( ex ); + } + } + + public PersistentUserPrefs( OID oid ) { + super( oid ); + } + + public PersistentUserPrefs( DataObject obj ) { + super( obj ); + } + + static void domainInit() { + DomainObjectFactory.registerInstantiator( + BASE_DATA_OBJECT_TYPE, + new DomainObjectInstantiator() { + protected DomainObject doNewInstance( DataObject obj ) { + return new PersistentUserPrefs( obj ); + } + } + ); + } + + static PersistentUserPrefs retrieveForUser( User user ) { + DataCollection pups = SessionManager.getSession().retrieve + ( BASE_DATA_OBJECT_TYPE ); + pups.addEqualsFilter( USER + "." + User.ID, user.getID() ); + + PersistentUserPrefs prefs = null; + if( pups.next() ) { + prefs = new PersistentUserPrefs( pups.getDataObject() ); + s_log.warn( "User " + user.getOID() + " has multiple user " + + "preferences" ); + } + pups.close(); + + return prefs; + } + + static PersistentUserPrefs retrieveForCookie( Long cookie ) { + DataCollection pups = SessionManager.getSession().retrieve + ( BASE_DATA_OBJECT_TYPE ); + pups.addEqualsFilter( COOKIE, cookie ); + + PersistentUserPrefs prefs = null; + if( pups.next() ) { + prefs = new PersistentUserPrefs( pups.getDataObject() ); + if( pups.next() ) { + s_log.warn( "Cookie " + cookie + " has multiple user " + + "preferences" ); + } + } + pups.close(); + + return prefs; + } + + public String getValue( String key ) { + DomainCollection prefs = getAllValues(); + prefs.addEqualsFilter( Pref.KEY, key ); + + String value = null; + if( prefs.next() ) { + value = (String) prefs.get( Pref.VALUE ); + + if( prefs.next() ) { + s_log.warn( "Users prefs " + getOID() + " has multiple " + + "values for " + key ); + } + } + prefs.close(); + + return value; + } + + public DomainCollection getAllValues() { + DataAssociation prefs = (DataAssociation) get( PREFS ); + return new DomainCollection( prefs.getDataAssociationCursor() ); + } + + public void removeValue( String key ) { + DomainCollection prefs = getAllValues(); + prefs.addEqualsFilter( Pref.KEY, key ); + + while( prefs.next() ) { + prefs.getDomainObject().delete(); + } + } + + public void setValue( String key, String value ) { + DomainCollection prefs = getAllValues(); + prefs.addEqualsFilter( Pref.KEY, key ); + + Pref pref; + if( prefs.next() ) { + pref = (Pref) prefs.getDomainObject(); + + if( prefs.next() ) { + s_log.warn( "Users prefs " + getOID() + " has multiple " + + "values for " + key ); + } + } else { + pref = new Pref(); + pref.setKey( key ); + } + prefs.close(); + + pref.setValue( value ); + if( pref.isNew() ) { + add( PREFS, pref ); + } + } + + public void setAllValues( Map newPrefs ) { + if( newPrefs.isEmpty() ) { + s_log.debug( "All values removed. Deleting prefs object." ); + delete(); + return; + } + + s_log.debug( "Setting all values" ); + + HashSet newKeys = new HashSet( newPrefs.keySet() ); + DomainCollection prefs = getAllValues(); + + while( prefs.next() ) { + String key = (String) prefs.get( Pref.KEY ); + String value = (String) prefs.get( Pref.VALUE ); + + if( !newKeys.contains( key ) ) { + prefs.getDomainObject().delete(); + } + + else { + String newValue = (String) newPrefs.get( key ); + if( !value.equals( newValue ) ) { + Pref pref = (Pref) prefs.getDomainObject(); + pref.setValue( newValue ); + } + + newKeys.remove( key ); + } + } + + Iterator i = newKeys.iterator(); + while( i.hasNext() ) { + String key = (String) i.next(); + + Pref pref = new Pref(); + pref.setKey( key ); + pref.setValue( (String) newPrefs.get( key ) ); + + add( PREFS, pref ); + } + } + + void setUser( User user ) { + setAssociation( USER, user ); + } + + User getUser() { + return (User) DomainObjectFactory.newInstance + ( (DataObject) get( USER ) ); + } + + void setCookie( Long cookie ) { + set( COOKIE, cookie ); + } + + Long getCookie() { + return (Long) get( COOKIE ); + } +} Added: ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/Pref.java =================================================================== --- ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/Pref.java 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/Pref.java 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,85 @@ +/* +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2.1 of +the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package com.arsdigita.london.userprefs; + +import com.arsdigita.db.Sequences; +import com.arsdigita.domain.DomainObject; +import com.arsdigita.domain.DomainObjectFactory; +import com.arsdigita.domain.DomainObjectInstantiator; +import com.arsdigita.persistence.DataObject; +import com.arsdigita.persistence.OID; +import com.arsdigita.util.UncheckedWrapperException; + +import java.sql.SQLException; + +import org.apache.log4j.Logger; + +public class Pref extends DomainObject { + private static final Logger s_log = Logger.getLogger( Pref.class ); + + public static final String ID = "id"; + public static final String KEY = "key"; + public static final String VALUE = "value"; + + public static final String BASE_DATA_OBJECT_TYPE = + "com.arsdigita.london.userprefs.Pref"; + + Pref() { + super( BASE_DATA_OBJECT_TYPE ); + + try { + set( ID, Sequences.getNextValue() ); + } catch( SQLException ex ) { + throw new UncheckedWrapperException( ex ); + } + } + + public Pref( OID oid ) { + super( oid ); + } + + public Pref( DataObject obj ) { + super( obj ); + } + + static void domainInit() { + DomainObjectFactory.registerInstantiator( + BASE_DATA_OBJECT_TYPE, + new DomainObjectInstantiator() { + protected DomainObject doNewInstance( DataObject obj ) { + return new Pref( obj ); + } + } + ); + } + + public String getKey() { + return get( KEY ).toString(); + } + + void setKey( String key ) { + set( KEY, key ); + } + + public String getValue() { + return get( VALUE ).toString(); + } + + void setValue( String value ) { + set( VALUE, value ); + } +} Added: ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/UserPrefs.java =================================================================== --- ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/UserPrefs.java 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/UserPrefs.java 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,378 @@ +/* +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2.1 of +the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package com.arsdigita.london.userprefs; + +import com.arsdigita.domain.DataObjectNotFoundException; +import com.arsdigita.domain.DomainCollection; +import com.arsdigita.kernel.Kernel; +import com.arsdigita.kernel.User; +import com.arsdigita.persistence.OID; +import com.arsdigita.persistence.SessionManager; +import com.arsdigita.persistence.TransactionContext; +import com.arsdigita.util.UncheckedWrapperException; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Random; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionActivationListener; + +import org.apache.log4j.Logger; + +/** + * <p>User Preferences. An object for storing user preferences in the current + * session.</p> + * + * <p>This object is an abstraction of PersistentUserPreferences, which is a + * <code>DomainObject</code> stored in the database. <code>UserPrefs</code> is + * cached in the java Session and will not touch the database on every + * request.</p> + * + * <p>If the current user is not logged in, <code>UserPrefs</code> will use its + * own cookie. This means that preferences can be saved even when a user doesn't + * log in.</p> + * + * @author Matthew Booth <mb...@re...> + */ + +// LAZY PERSISTENCE + +// My original intention was that save would not save immediately, but rather +// only save when the session expires. This is not terribly safe for the data, +// but these are only user preferences and I didn't want to unnecessarily hit +// the database. As long as data is not usually lost, that's fine. + +// I was originally going to achieve this by implementing +// HttpSessionBindingListener in the understanding that objects would be unbound +// before the session is destroyed. They're not. Instead I tried +// HttpSessionActivationListener which is called when a Session is migrated +// between servers. This isn't used either, but I left that in because it +// seems like a good idea. + +// Finally I tried HttpSessionListener. It looks like I should have tried this +// first, as it appears to be the correct interface for the job. However, the +// sessionDestroyed() method is only called *after* the Session object is +// invalidated. This means that not only can you not retrieve attributes from +// it, but you can't even get the session ID. You know that a session was just +// destroyed, but you have no way of knowing which one. In fact, it's utterly, +// utterly, utterly useless. It's completely without merit. A waste of bits. +// Who on earth came up with that? + +// There seems to be some talk of this being fixed in the Servlet 2.4 spec. + +// Matt <mb...@re...> + +public class UserPrefs implements HttpSessionActivationListener { + private static final Logger s_log = Logger.getLogger( UserPrefs.class ); + + private static final String COOKIE = "WAF_USER_PREFS"; + static final String SESSION_ATTRIBUTE = UserPrefs.class.getName(); + + private OID m_user = null; + private OID m_persistentPrefs = null; + private Long m_cookie = null; + + private HashMap m_prefs = new HashMap(); + + private UserPrefs() {}; + + private UserPrefs( PersistentUserPrefs persistentPrefs ) { + m_persistentPrefs = persistentPrefs.getOID(); + m_cookie = persistentPrefs.getCookie(); + m_user = persistentPrefs.getUser() == null ? + null : persistentPrefs.getUser().getOID(); + + DomainCollection prefs = persistentPrefs.getAllValues(); + while( prefs.next() ) { + Pref pref = (Pref) prefs.getDomainObject(); + m_prefs.put( pref.getKey(), pref.getValue() ); + } + } + + /** + * <p>Retrieve a UserPrefs object for the current request.</p> + * + * <p>In order, this will: + * + * <ul> + * <li>Look for prefs in the http session</li> + * <li>Look for prefs for the currently logged in user</li> + * <li>Look for prefs for a supplied cookie (from DB)</li> + * <li>Create a new preferences object</li> + * </ul> + * </p> + */ + public static UserPrefs retrieve( HttpServletRequest req, + HttpServletResponse res ) { + UserPrefs prefs = (UserPrefs) + req.getSession().getAttribute( SESSION_ATTRIBUTE ); + + if( null != prefs ) return prefs; + + User user = (User) Kernel.getContext().getParty(); + if( null != user ) { + PersistentUserPrefs persistentPrefs = + PersistentUserPrefs.retrieveForUser( user ); + if( null != persistentPrefs ) { + prefs = new UserPrefs( persistentPrefs ); + } + } + + if( null != prefs ) { + s_log.debug( "Got prefs for user" ); + req.getSession().setAttribute( SESSION_ATTRIBUTE, prefs ); + return prefs; + } + + Long cookie = getCookie( req ); + if( null != cookie ) { + PersistentUserPrefs persistentPrefs = + PersistentUserPrefs.retrieveForCookie( cookie ); + + // Remove a bogus cookie + if( null == persistentPrefs ) { + removeCookie( res ); + } + + else { + prefs = new UserPrefs( persistentPrefs ); + } + } + + if( null != prefs ) { + s_log.debug( "Got prefs for cookie" ); + req.getSession().setAttribute( SESSION_ATTRIBUTE, prefs ); + return prefs; + } + + prefs = new UserPrefs(); + + if( null != user ) { + prefs.m_user = user.getOID(); + } else { + try { + SecureRandom random = SecureRandom.getInstance( "SHA1PRNG" ); + cookie = new Long( random.nextLong() ); + } catch( NoSuchAlgorithmException ex ) { + s_log.warn( "Unable to get SecureRandom for SHA1PRNG. " + + "Falling back to insecure random generator." ); + cookie = new Long( new Random().nextLong() ); + } + + prefs.m_cookie = cookie; + setCookie( res, cookie.toString() ); + } + + s_log.debug( "Created new prefs" ); + req.getSession().setAttribute( SESSION_ATTRIBUTE, prefs ); + return prefs; + } + + /** + * Retrieve the value of a user preference entry. + * + * @param key The identifier of the preference to be retrieved + * @return The value of the requsted preference, or null if it is not set + */ + public String get( String key ) { + return (String) m_prefs.get( key ); + } + + /** + * Retrieve all stored user preferences. + * + * @return An Iterator of Map.Entry objects containing key/value pairs for + * the user's current preferences + */ + public Iterator getAll() { + return m_prefs.entrySet().iterator(); + } + + /** + * Set a preference. This will not cause the preference to be immediately + * persisted. + * + * <p><strong>NB: Lazy persistence is not currently working, so this will + * persist immediately for the time being.</strong></p> + * + * @param key The identifier of the preference to be stored + * @param value The value of the preference to be stored + */ + public void set( String key, String value ) { + m_prefs.put( key, value ); + + // XXX: remove the line below when lazy persistence works + getPersistent().setValue( key, value ); + } + + /** + * Set a preference. Optionally save the preference immediately. + * + * <p><strong>NB: Lazy persistence is not currently working, so this will + * persist immediately for the time being.</strong></p> + * + * @param key The identifier of the preference to be stored + * @param value The value of the preference to be stored + * @param persist true if the value is to be immediately saved + */ + public void set( String key, String value, boolean persist ) { + set( key, value ); + + //if( persist ) { + getPersistent().setValue( key, value ); + //} + } + + /** + * Remove a user preference. This change will not be saved immediately. + * + * @param key The identifier of the preference to be removed + */ + public void remove( String key ) { + m_prefs.remove( key ); + } + + /** + * Remove a user preference. Optionally save this change immediately. + * + * <p><strong>NB: Lazy persistence is not currently working, so this will + * persist immediately for the time being.</strong></p> + * + * @param key The identifier of the preference to be removed + * @param persist true if the change is to be immediately saved + */ + public void remove( String key, boolean persist ) { + remove( key ); + + if( persist ) { + getPersistent().removeValue( key ); + } + } + + /** + * Save user preferences to the database + */ + public void persist() { + s_log.info( "Persisting session" ); + + PersistentUserPrefs prefs = getPersistent(); + prefs.setAllValues( m_prefs ); + prefs.save(); + + s_log.debug( "Session persisted" ); + } + + public void sessionDidActivate(HttpSessionEvent event) { + // Not interested in this event + s_log.debug( "Session activated" ); + } + + public void sessionWillPassivate(HttpSessionEvent event) { + s_log.debug( "Session about to be passivated" ); + persist(); + } + + /** + * Get a PersistentUserPrefs object. Create one if necessary. + */ + private PersistentUserPrefs getPersistent() { + PersistentUserPrefs prefs; + + if( null == m_persistentPrefs ) { + prefs = new PersistentUserPrefs(); + } else { + try { + prefs = new PersistentUserPrefs( m_persistentPrefs ); + } catch( DataObjectNotFoundException ex ) { + s_log.warn( "User preferences object contained bogus " + + "persistent preferences OID" ); + prefs = new PersistentUserPrefs(); + } + } + + // If this is newly persisted, set appropriately + if( prefs.isNew() ) { + if( s_log.isDebugEnabled() ) { + s_log.debug( "Initializing new user preferences: " + + prefs.getOID() ); + } + + if( null != m_user ) { + prefs.setUser( new User( m_user ) ); + } + + else if( null != m_cookie ) { + prefs.setCookie( m_cookie ); + } + + else { + throw new UncheckedWrapperException ( "User preferences object doesn't contain either a user or a cookie object" ); + } + } + + // If we're saving something and we have a user object now, use that + // in preference + if( null == m_user ) { + User user = (User) Kernel.getContext().getParty(); + if( null != user ) { + prefs.setUser( user ); + prefs.setCookie( null ); + } + } + + m_persistentPrefs = prefs.getOID(); + + return prefs; + } + + private static Long getCookie( HttpServletRequest req ) { + Cookie[] cookieJar = req.getCookies(); + if( null == cookieJar ) return null; + + for( int i = 0; i < cookieJar.length; i++ ) { + if( COOKIE.equals( cookieJar[i].getName() ) ) { + try { + return Long.valueOf( cookieJar[i].getValue() ); + } catch( NumberFormatException ex ) { + s_log.warn( "Bogus cookie value: " + + cookieJar[i].getValue() ); + // Might as well keep looking + } + } + } + + return null; + } + + private static void setCookie( HttpServletResponse res, String value ) { + Cookie cookie = new Cookie( COOKIE, value ); + cookie.setMaxAge( Integer.MAX_VALUE ); + res.addCookie( cookie ); + } + + private static void removeCookie( HttpServletResponse res ) { + Cookie cookie = new Cookie( COOKIE, "" ); + cookie.setMaxAge( 0 ); + res.addCookie( cookie ); + } +} Added: ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/UserPrefsSessionListener.java =================================================================== --- ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/UserPrefsSessionListener.java 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/UserPrefsSessionListener.java 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,46 @@ +/* +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2.1 of +the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package com.arsdigita.london.userprefs; + +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; + +import org.apache.log4j.Logger; + +public class UserPrefsSessionListener implements HttpSessionListener { + private static final Logger s_log = + Logger.getLogger( UserPrefsSessionListener.class ); + + public void sessionCreated( HttpSessionEvent se ) { + s_log.debug( "Session created" ); + } + + public void sessionDestroyed( HttpSessionEvent se ) { + s_log.debug( "Session destroyed" ); + + HttpSession session = se.getSession(); + + try { + UserPrefs prefs = (UserPrefs) session.getAttribute + ( UserPrefs.SESSION_ATTRIBUTE ); + prefs.persist(); + } catch( Throwable t ) { + s_log.error( t ); + } + } +} Added: ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/ui/UserPrefsComponent.java =================================================================== --- ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/ui/UserPrefsComponent.java 2004-11-11 20:15:31 UTC (rev 95) +++ ccm-user-preferences/trunk/src/com/arsdigita/london/userprefs/ui/UserPrefsComponent.java 2004-11-11 21:44:25 UTC (rev 96) @@ -0,0 +1,110 @@ +/* +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License +as published by the Free Software Foundation; either version 2.1 of +the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package com.arsdigita.london.userprefs.ui; + +import com.arsdigita.london.userprefs.UserPrefs; + +import com.arsdigita.bebop.Page; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.SimpleComponent; +import com.arsdigita.bebop.event.RequestEvent; +import com.arsdigita.bebop.event.RequestListener; +import com.arsdigita.bebop.parameters.BooleanParameter; +import com.arsdigita.bebop.parameters.StringParameter; +import com.arsdigita.xml.Element; + +import java.util.Iterator; +import java.util.Map; + +import org.apache.log4j.Logger; + +public class UserPrefsComponent extends SimpleComponent { + private static final Logger s_log = + Logger.getLogger( UserPrefsComponent.class ); + + public static final String XMLNS = "http://xmlns.redhat.com/userprefs/1.0"; + + public UserPrefsComponent() { + super(); + } + + private StringParameter m_setKey = new StringParameter( "pref.set.key" ); + private StringParameter m_setValue = new StringParameter( "pref.set.value" ); + private StringParameter m_remove = new StringParameter( "pref.remove" ); + private BooleanParameter m_immediate = new BooleanParameter( "pref.immediate" ); + + public void generateXML( PageState ps, Element parent ) { + UserPrefs prefs = UserPrefs.retrieve( ps.getRequest(), + ps.getResponse() ); + + Element rootE = parent.newChildElement( "up:userPreferences", XMLNS ); + + Iterator values = prefs.getAll(); + while( values.hasNext() ) { + Map.Entry pref = (Map.Entry) values.next(); + + String key = (String) pref.getKey(); + String value = (String) pref.getValue(); + + Element prefE = rootE.newChildElement( "up:preference", XMLNS ); + prefE.addAttribute( "key", key ); + + if( null != value ) { + prefE.addAttribute( "value", value ); + } + } + } + + public void register( Page p ) { + super.register( p ); + + p.addGlobalStateParam( m_setKey ); + p.addGlobalStateParam( m_setValue ); + p.addGlobalStateParam( m_remove ); + p.addGlobalStateParam( m_immediate ); + + p.addRequestListener( new RequestListener() { + public void pageRequested( RequestEvent ev ) { + PageState ps = ev.getPageState(); + + String setKey = (String) ps.getValue( m_setKey ); + String setValue = (String) ps.getValue( m_setValue ); + String remove = (String) ps.getValue( m_remove ); + Boolean immediate = (Boolean) ps.getValue( m_immediate ); + + UserPrefs prefs = UserPrefs.retrieve( ps.getRequest(), + ps.getResponse() ); + + if( null != setKey ) { + if( Boolean.TRUE.equals( immediate ) ) { + prefs.set( setKey, setValue, true ); + } else { + prefs.set( setKey, setValue ); + } + } + + if( null != remove ) { + if( Boolean.TRUE.equals( immediate ) ) { + prefs.remove( remove, true ); + } else { + prefs.remove( remove ); + } + } + } + } ); + } +} |