KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > web > tomcat > tc6 > session > FieldBasedClusteredSession


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.web.tomcat.tc6.session;
23
24 import org.jboss.aspects.patterns.observable.Observer;
25 import org.jboss.aspects.patterns.observable.Subject;
26
27 import java.util.Collection JavaDoc;
28 import java.util.Collections JavaDoc;
29 import java.util.HashMap JavaDoc;
30 import java.util.HashSet JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.Map JavaDoc;
33 import java.util.Set JavaDoc;
34
35 import javax.servlet.http.HttpSessionActivationListener JavaDoc;
36
37 /**
38  * <p>
39  * Implementation of a clustered session for JBossCacheManager. The replication granularity
40  * level is field based; that is, we replicate only the dirty field in a POJO that is part of
41  * a session attribute. E.g., once a user do setAttribute("pojo", pojo), pojo will be monitored
42  * automatically for field changes and accessing. It offers couple of advantages:
43  * <ul>
44  * <li>pojo.setName(), for example, will only replicate the name field in the pojo. And thus is more efficient.</li>
45  * <li>If pojo has a complex object graph, we will handle that automtically providing that the
46  * children object is also aspectized.</li>
47  * </ul>
48  * Note that in current version, all the attributes and its associated childre graph objects are
49  * required to be aspectized. That is, you can't simply declare them as Serializable. This is restricted
50  * because of the marshalling/unmarshalling issue.</p>
51  *
52  * <p>We use JBossCache for our internal, replicated data store.
53  * The internal structure is like in JBossCache:
54  * <pre>
55  * /JSESSION
56  * /hostname
57  * /web_app_path (path + session id is unique)
58  * /id Map(id, session)
59  * (VERSION, version)
60  * /ATTRIBUTE Map(can be empty)
61  * /pojo Map(field name, field value) (pojo naming is by field.getName())
62  *
63  * </pre>
64  * <p/>
65  *
66  * @author Ben Wang
67  * @author Brian Stansberry
68  *
69  * @version $Revision: 58586 $
70  */

71 class FieldBasedClusteredSession
72    extends JBossCacheClusteredSession implements Observer
73 {
74    /** The serialVersionUID */
75    private static final long serialVersionUID = 8347544395334247623L;
76    
77    /**
78     * Descriptive information describing this Session implementation.
79     */

80    protected static final String JavaDoc info = "FieldBasedClusteredSession/1.0";
81    
82    protected transient Map JavaDoc attributes_ = Collections.synchronizedMap(new HashMap JavaDoc());
83
84    public FieldBasedClusteredSession(JBossCacheManager manager)
85    {
86       super(manager);
87    }
88
89    // ----------------------------------------------- Overridden Public Methods
90

91
92    /**
93     * Override the superclass to additionally reset this class' fields.
94     * <p>
95     * <strong>NOTE:</strong> It is not anticipated that this method will be
96     * called on a ClusteredSession, but we are overriding the method to be
97     * thorough.
98     * </p>
99     */

100    public void recycle()
101    {
102       super.recycle();
103
104       attributes_.clear();
105    }
106
107    /**
108     * Return a string representation of this object.
109     */

110    public String JavaDoc toString()
111    {
112
113       StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
114       sb.append("FieldBasedClusteredSession[");
115       sb.append(super.toString());
116       sb.append("]");
117       return (sb.toString());
118
119    }
120
121    // The superclass version of processSessionRepl is fine; it will remove
122
// the session metadata, and any attribute changes have been picked up
123
// for replication as they were made; no need to do anything here
124
// public synchronized void processSessionRepl()
125
// {
126
// super.processSessionRepl();
127
// }
128

129    public void removeMyself()
130    {
131       // This is a shortcut to remove session and it's child attributes.
132
// Note that there is no need to remove attribute first since caller
133
// will do that already.
134
proxy_.removeSession(realId);
135    }
136
137    public void removeMyselfLocal()
138    {
139       // Need to evict attribute first before session to clean up everything.
140
// Note that there is no need to remove attributes first since caller
141
// will do that already.
142
// BRIAN -- the attributes *are* already evicted, but we leave the
143
// removePojosLocal call here in order to evict the ATTRIBUTE node.
144
// Otherwise empty nodes for the session root and child ATTRIBUTE will
145
// remain in the tree and screw up our list of session names.
146
proxy_.removePojosLocal(realId);
147       proxy_.removeSessionLocal(realId);
148    }
149
150    // ------------------------------------------------ JBoss internal abstract method
151

152    /**
153     * Populate the attributes stored in the distributed store to the local
154     * transient map. Add ourself as an Observer to newly found attributes and
155     * remove ourself as an Observer to existing attributes that are no longer
156     * in the distributed store.
157     */

158    protected void populateAttributes()
159    {
160       // Preserve any local attributes that were excluded from replication
161
Map JavaDoc excluded = removeExcludedAttributes(attributes_);
162       
163       Set JavaDoc keys = proxy_.getPojoKeys(realId);
164       Set JavaDoc oldKeys = new HashSet JavaDoc(attributes_.keySet());
165       
166       // Since we are going to touch each attribute, might as well
167
// check if we have any HttpSessionActivationListener
168
boolean hasListener = false;
169       
170       if (keys != null)
171       {
172          oldKeys.removeAll(keys); // only keys that no longer exist are left
173

174          for (Iterator JavaDoc it = keys.iterator(); it.hasNext(); )
175          {
176             String JavaDoc name = (String JavaDoc) it.next();
177             
178             Object JavaDoc oldAttrib = null;
179             Object JavaDoc newAttrib = proxy_.getPojo(realId, name);
180             if (newAttrib != null)
181             {
182                oldAttrib = attributes_.put(name, newAttrib);
183             
184                if (oldAttrib != newAttrib)
185                {
186                   // Need to observe this pojo as well
187
// for any modification events.
188
proxy_.addObserver(this, newAttrib);
189                   
190                   // Stop observing the old pojo
191
proxy_.removeObserver(this, oldAttrib); // null pojo OK :)
192
}
193                
194                // Check if we have a listener
195
if (newAttrib instanceof HttpSessionActivationListener JavaDoc)
196                   hasListener = true;
197             }
198             else
199             {
200                // This shouldn't happen -- if we had a key, newAttrib s/b not null
201

202                oldAttrib = attributes_.remove(name);
203                // Stop observing this pojo
204
proxy_.removeObserver(this, oldAttrib); // null pojo OK :)
205

206             }
207          }
208       }
209       
210       hasActivationListener = hasListener ? Boolean.TRUE : Boolean.FALSE;
211       
212       // Cycle through remaining old keys and remove them
213
// and also remove ourself as Observer
214
for (Iterator JavaDoc it = oldKeys.iterator(); it.hasNext(); )
215       {
216          Object JavaDoc oldAttrib = attributes_.remove(it.next());
217          proxy_.removeObserver(this, oldAttrib);
218       }
219       
220       // Restore any excluded attributes
221
if (excluded != null)
222          attributes.putAll(excluded);
223    }
224  
225    protected Object JavaDoc getJBossInternalAttribute(String JavaDoc name)
226    {
227       // Check the local map first.
228
Object JavaDoc result = attributes_.get(name);
229       
230       // NOTE -- we no longer check with the store. Attributes are only
231
// loaded from store during populateAttributes() call at beginning
232
// of request when we notice we are outdated.
233

234       // Do dirty check even if result is null, as w/ SET_AND_GET null
235
// still makes us dirty (ensures timely replication w/o using ACCESS)
236
if (isGetDirty(result))
237       {
238          sessionAttributesDirty();
239       }
240       
241       return result;
242    }
243
244    /**
245     * Overrides the superclass to treat classes implementing Subject
246     * as "immutable", since as an Observer we will detect any changes
247     * to those types.
248     */

249    protected boolean isMutable(Object JavaDoc attribute)
250    {
251       boolean pojo = (attribute instanceof Subject);
252       boolean mutable = (!pojo && super.isMutable(attribute));
253       return mutable;
254    }
255
256    protected Object JavaDoc removeJBossInternalAttribute(String JavaDoc name, boolean localCall, boolean localOnly)
257    {
258       // Remove it from the underlying store
259
if (localCall && !replicationExcludes.contains(name))
260       {
261          if (localOnly)
262             proxy_.removePojoLocal(realId, name);
263          else
264             proxy_.removePojo(realId, name);
265          
266          sessionAttributesDirty();
267       }
268       Object JavaDoc result = attributes_.remove(name);
269       if(result == null)
270       {
271          log.warn("removeJBossInternalAttribute(): null value to remove with key: "+ name);
272          return null;
273       }
274       proxy_.removeObserver(this, result);
275          
276       return result;
277    }
278
279    protected Map JavaDoc getJBossInternalAttributes()
280    {
281       return attributes_;
282    }
283
284    protected Set JavaDoc getJBossInternalKeys()
285    {
286       return attributes_.keySet();
287    }
288
289    /**
290     * Method inherited from Tomcat. Return zero-length based string if not found.
291     */

292    protected String JavaDoc[] keys()
293    {
294       return ((String JavaDoc[]) getJBossInternalKeys().toArray(EMPTY_ARRAY));
295    }
296
297    /**
298     * Overrides the superclass to allow instrumented classes and
299     * non-serializable Collections and Maps.
300     */

301    protected boolean canAttributeBeReplicated(Object JavaDoc attribute)
302    {
303       return (attribute == null || Util.checkPojoType(attribute));
304    }
305
306    /**
307     * This is the hook for setAttribute. Note that in this FieldBasedClusteredSession using aop,
308     * user should not call setAttribute call too often since this will re-connect the attribute with the internal
309     * cache (and this is expensive).
310     * @param key
311     * @param value
312     * @return Object
313     */

314    protected Object JavaDoc setJBossInternalAttribute(String JavaDoc key, Object JavaDoc value)
315    {
316       Object JavaDoc oldVal = null;
317       if (!replicationExcludes.contains(key))
318       {
319          oldVal = proxy_.setPojo(realId, key, value);
320          if(oldVal != null)
321          { // We are done with the old one.
322
proxy_.removeObserver(this, oldVal);
323          }
324    
325          if(value != null)
326          {
327             // Special case for Collection classes.
328
if( value instanceof Map JavaDoc || value instanceof Collection JavaDoc)
329             {
330                // We need to obtain the proxy first.
331
value = proxy_.getPojo(realId, key);
332             }
333
334             // Need to use obj since it can return as a proxy.
335
proxy_.addObserver(this, value);
336          }
337
338          // Only mark session dirty if we can replicate the attribute
339
sessionAttributesDirty();
340       }
341       
342       // Still need to put it in the map to track locally.
343
oldVal = attributes_.put(key, value);
344       
345       return oldVal;
346    }
347
348    /**
349     * Call back handler for the aop Subject/Observer pattern.
350     * We subscribe to the event of field write and mark ourself dirty.
351     *
352     * @param subject the object we are Observing
353     */

354    public void fireChange(Subject subject)
355    {
356       // Currently we don't care who is modified, we will simply mark session is dirty for replication purpose.
357
if(log.isTraceEnabled())
358       {
359          log.trace("fireChange(): subject has changed: " +subject);
360       }
361       sessionAttributesDirty();
362    }
363 }
364
Popular Tags