001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.pageflow.requeststate;
020:
021: import javax.servlet.http.HttpSession;
022: import java.io.IOException;
023: import java.io.ObjectInputStream;
024: import java.lang.ref.WeakReference;
025: import java.util.ArrayList;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.Map;
029: import java.util.Set;
030:
031: import org.apache.beehive.netui.util.internal.ServletUtils;
032:
033: /**
034: * This class implements a service that will name and track objects which implement the
035: * <code>INameable</code> interface. The typical use of this class is in the XmlHttpRequest
036: * request processing to lookup the object that should handle the request.
037: */
038: final public class NameService implements java.io.Serializable {
039: private static final String NAME_SERVICE = "netui.nameService";
040: public static final String NAME_SERVICE_MUTEX_ATTRIBUTE = NameService.class
041: .getName()
042: + ".MUTEX";
043:
044: // This is support for walking the _nameMap and reclaiming entries where
045: // the weak reference object has been reclaimed by the garbage collector.
046: // We will always reclaim at the reclaimPoint and after we run the reclaim we
047: // set the next reclaimPoint to be the resulting size plus the reclaim increment.
048: private final int _reclaimIncrement = 5;
049: private int _reclaimPoint = _reclaimIncrement;
050:
051: // static value for situation where this is not stored in the session.
052: private static NameService _nameService;
053:
054: private transient HashMap/*<String,WeakReference>*/_nameMap = new HashMap();
055: private int _nextValue;
056: private ArrayList _listeners;
057:
058: /**
059: * private constructor allowing for a factory method to access NameService objects.
060: */
061: private NameService() {
062: _nextValue = 0;
063: }
064:
065: /**
066: * This will return the session specific instance of a NameService. There
067: * will only be a single NameService per session.
068: * @param session the HttpSession that contains the NameService
069: * @return the NameService associated with the session.
070: */
071: public static NameService instance(HttpSession session) {
072: if (session == null)
073: throw new IllegalArgumentException(
074: "Session must not be null");
075:
076: // Synchronize on a session scoped mutex to ensure that only a single
077: // NameService object is created within a specific user session
078: Object sessionMutex = ServletUtils.getSessionMutex(session,
079: NAME_SERVICE_MUTEX_ATTRIBUTE);
080: synchronized (sessionMutex) {
081: NameService nameService = (NameService) session
082: .getAttribute(NAME_SERVICE);
083: if (nameService == null) {
084: nameService = new NameService();
085: session.setAttribute(NAME_SERVICE, nameService);
086: }
087: return nameService;
088: }
089: }
090:
091: /**
092: * This will return a create a static name service. This is used mainly to test the Name service
093: * class.
094: * @return The statically scoped <code>NameService</code>
095: */
096: public static synchronized NameService staticInstance() {
097: if (_nameService == null)
098: _nameService = new NameService();
099: return _nameService;
100: }
101:
102: /**
103: * Deserialize an instance of this class.
104: * @param in ObjectInputStream to deserialize from.
105: * @throws IOException
106: * @throws ClassNotFoundException
107: */
108: private synchronized void readObject(ObjectInputStream in)
109: throws IOException, ClassNotFoundException {
110: in.defaultReadObject();
111:
112: // the transient _nameMap is null after an instance of
113: // NameService has been serialized.
114: if (_nameMap == null) {
115: _nameMap = new HashMap();
116: }
117: }
118:
119: /**
120: * This method will add a <code>NamingObjectListener</code> to the set of listeners for the NamingObject event.
121: * @param nol The <code>NamingObjectListener</code> to add as a listener. This must not be null.
122: * @throws IllegalArgumentException when nol is null.
123: */
124: public void addNamingObjectListener(NamingObjectListener nol) {
125: if (nol == null)
126: throw new IllegalArgumentException(
127: "The NameingObjectListener must not be null");
128:
129: ArrayList listener = getListener();
130:
131: synchronized (listener) {
132: if (listener.contains(nol))
133: return;
134: listener.add(nol);
135: }
136: }
137:
138: /**
139: * This method will remove a <code>NamingObjectListener</code> from the set of listeners. If the
140: * It is safe to call this if the NamingObjectListener hasn't been added to the listener list.
141: * @param nol The <code>NamingObjectListener</code> to remove as a listener. This must not be null.
142: * @throws IllegalArgumentException when nol is null.
143: */
144: public void removeNamingObjectListener(NamingObjectListener nol) {
145: if (nol == null)
146: throw new IllegalArgumentException(
147: "The NameingObjectListener must not be null");
148:
149: ArrayList listener = getListener();
150: synchronized (listener) {
151: if (!listener.contains(nol))
152: return;
153: listener.remove(nol);
154: }
155: }
156:
157: /**
158: * This method will create a unique name for an INameable object. The name
159: * will be unque within the session. This will throw an IllegalStateException
160: * if INameable.setObjectName has previously been called on object.
161: * @param namePrefix The prefix of the generated name.
162: * @param object the INameable object.
163: * @throws IllegalStateException if this method is called more than once for an object
164: */
165: public synchronized void nameObject(String namePrefix,
166: INameable object) {
167: String name = namePrefix + Integer.toString(_nextValue++);
168: object.setObjectName(name);
169: }
170:
171: /**
172: * This is a debug method that will set the next integer value. This is used
173: * so tests can force the name.
174: * @param val the integer value that will be forced to be the next value.
175: */
176: public void debugSetNameIntValue(int val) {
177: _nextValue = val;
178: }
179:
180: /**
181: * This method will store an INameable object into the <code>NameService</code>. The name
182: * is obtained from the INameable. The object will be stored in the <code>NameService</code>
183: * with a <code>WeakReference</code> so the <code>NameService</code> will not keep an object alive.
184: * @param object The <code>INameable</code> to be stored in the name service.
185: */
186: public void put(INameable object) {
187: if (object == null)
188: throw new IllegalStateException("object must not be null");
189:
190: reclaimSpace();
191:
192: String name = object.getObjectName();
193: if (name == null)
194: throw new IllegalStateException("object has not been named");
195:
196: TrackingObject to = new TrackingObject();
197: to.setINameable(new WeakReference(object));
198:
199: synchronized (_nameMap) {
200: _nameMap.put(name, to);
201: }
202:
203: // fire the fact that we just added a nameable to be tracked
204: if (_listeners != null)
205: fireNamingObjectEvent(to);
206: }
207:
208: /**
209: * Given the name, return the <code>INameable</code> object stored by the <code>NameService</code>. Objects
210: * are stored in the <code>NameService</code> using <code>WeakReference</code>s so this will not keep an object
211: * alive. If the object is not found or has been reclaimed, this method will return null.
212: * @param name The name of the object to get from the <code>NameService</code>
213: * @return INameable If the named object is stored by the name service, it will be returned otherwise
214: * <code>null</code> is returned.
215: */
216: public INameable get(String name) {
217: if (name == null)
218: throw new IllegalStateException("name must not be null");
219:
220: if (_nameMap == null)
221: return null;
222:
223: TrackingObject to = (TrackingObject) _nameMap.get(name);
224:
225: // The object wasn't found
226: if (to == null)
227: return null;
228:
229: // If the object has been reclaimed, then we remove the named object from the map.
230: WeakReference wr = to.getWeakINameable();
231: INameable o = (INameable) wr.get();
232: if (o == null) {
233: synchronized (_nameMap) {
234: _nameMap.remove(name);
235: }
236: return null;
237: }
238: return o;
239: }
240:
241: /**
242: * This method will return the state map associated with the Nameable object if the
243: * object has been stored in the <code>NameService</code> and something has been stored
244: * into the <code>Map</code>. Otherwise this will return null indicating that the map
245: * is empty. If the <code>create</code> parameter is true, we will always return the
246: * <code>Map</code> object.
247: * @param name The name of the object to return the named object. This must not be null.
248: * @param create This will create the map if necessary.
249: * @return A Map Object for the named object. This will return null if nothing has been stored in
250: * the map and <code>create</code> is false.
251: */
252: public Map getMap(String name, boolean create) {
253: if (name == null)
254: throw new IllegalStateException("name must not be null");
255:
256: if (_nameMap == null)
257: return null;
258:
259: TrackingObject to = (TrackingObject) _nameMap.get(name);
260:
261: // The object wasn't found
262: if (to == null)
263: return null;
264:
265: // If the object has been reclaimed, then we remove the named object from the map.
266: WeakReference wr = to.getWeakINameable();
267: INameable o = (INameable) wr.get();
268: if (o == null) {
269: synchronized (_nameMap) {
270: _nameMap.remove(name);
271: }
272: return null;
273: }
274: if (create)
275: return to;
276:
277: return to.isMapCreated() ? to : null;
278: }
279:
280: /**
281: * This mehtod will check the name map for entries where the
282: * WeakReference object has been reclaimed. When they are found it will
283: * reclaim the entry in the map.
284: */
285: private void reclaimSpace() {
286: if (_nameMap == null)
287: return;
288:
289: if (_nameMap.size() > 0 && _nameMap.size() % _reclaimPoint == 0) {
290: synchronized (_nameMap) {
291: Set s = _nameMap.entrySet();
292: Iterator it = s.iterator();
293: while (it.hasNext()) {
294: Map.Entry e = (Map.Entry) it.next();
295:
296: TrackingObject to = (TrackingObject) e.getValue();
297:
298: // If the object has been reclaimed, then we remove the named object from the map.
299: WeakReference wr = to.getWeakINameable();
300: INameable o = (INameable) wr.get();
301: if (o == null) {
302: it.remove();
303: }
304:
305: }
306: _reclaimPoint = _nameMap.size() + _reclaimIncrement;
307: }
308: }
309: }
310:
311: /**
312: * This method will fire the NamingObject event. This event is triggered when
313: * we have added an element to be tracked.
314: * @param to The <code>TrackingObject</code> that acts as the Map and also contains the INameable.
315: */
316: private void fireNamingObjectEvent(TrackingObject to) {
317: Object[] copy;
318: if (_listeners == null)
319: return;
320:
321: // create a copy of the listeners so that there isn't any modifications while we
322: // fire the events.
323: synchronized (_listeners) {
324: copy = _listeners.toArray();
325: }
326: INameable o = (INameable) to.getWeakINameable().get();
327:
328: for (int i = 0; i < copy.length; i++) {
329: ((NamingObjectListener) copy[i]).namingObject(o, to);
330: }
331: }
332:
333: /**
334: * This method will return the event listener <code>ArrayList</code> when called. The event listener is
335: * lazily created.
336: * @return The listener array list.
337: */
338: private ArrayList getListener() {
339: if (_listeners != null)
340: return _listeners;
341: synchronized (this ) {
342: if (_listeners != null)
343: return _listeners;
344: _listeners = new ArrayList();
345: }
346: return _listeners;
347: }
348:
349: final private class TrackingObject extends LazyMap {
350: private WeakReference _nameable;
351:
352: public void setINameable(WeakReference nameable) {
353: _nameable = nameable;
354: }
355:
356: public WeakReference getWeakINameable() {
357: return _nameable;
358: }
359: }
360: }
|