001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package nextapp.echo2.webcontainer;
031:
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.Map;
035: import java.util.WeakHashMap;
036:
037: import nextapp.echo2.app.ApplicationInstance;
038: import nextapp.echo2.app.Component;
039: import nextapp.echo2.app.TaskQueueHandle;
040: import nextapp.echo2.app.update.UpdateManager;
041: import nextapp.echo2.webcontainer.util.IdTable;
042: import nextapp.echo2.webrender.Connection;
043: import nextapp.echo2.webrender.UserInstance;
044:
045: /**
046: * Web application container user instance.
047: */
048: public class ContainerInstance extends UserInstance {
049:
050: /**
051: * Default asynchronous monitor callback interval (in milliseconds).
052: */
053: private static final int DEFAULT_CALLBACK_INTERVAL = 500;
054:
055: /**
056: * Returns the base HTML element id that should be used when rendering the
057: * specified <code>Component</code>.
058: *
059: * @param component the component
060: * @return the base HTML element id
061: */
062: public static String getElementId(Component component) {
063: return "c_" + component.getRenderId();
064: }
065:
066: /**
067: * Creates a new Web Application Container instance using the provided
068: * client <code>Connection</code>. The instance will automatically
069: * be stored in the relevant <code>HttpSession</code>
070: *
071: * @param conn the client/server <code>Connection</code> for which the
072: * instance is being instantiated
073: */
074: public static void newInstance(Connection conn) {
075: new ContainerInstance(conn);
076: }
077:
078: private ApplicationInstance applicationInstance;
079: private Map componentToRenderStateMap = new HashMap();
080: private transient IdTable idTable;
081: private boolean initialized = false;
082: private Map initialRequestParameterMap;
083: private transient Map taskQueueToCallbackIntervalMap;
084:
085: /**
086: * Creates a new <code>ContainerInstance</code>.
087: *
088: * @param conn the client/server <code>Connection</code> for which the
089: * instance is being instantiated
090: * @see #newInstance(nextapp.echo2.webrender.Connection)
091: */
092: private ContainerInstance(Connection conn) {
093: super (conn);
094: setServerDelayMessage(DefaultServerDelayMessage.INSTANCE);
095: initialRequestParameterMap = new HashMap(conn.getRequest()
096: .getParameterMap());
097: }
098:
099: /**
100: * Returns the corresponding <code>ApplicationInstance</code>
101: * for this user instance.
102: *
103: * @return the relevant <code>ApplicationInstance</code>
104: */
105: public ApplicationInstance getApplicationInstance() {
106: return applicationInstance;
107: }
108:
109: //BUGBUG. current method of iterating weak-keyed map of task queues
110: // is not adequate. If the application were to for whatever reason hold on
111: // to a dead task queue, its interval setting would effect the
112: // calculation.
113: // One solution: add a getTaskQueues() method to ApplicationInstance
114: /**
115: * Determines the application-specified asynchronous monitoring
116: * service callback interval.
117: *
118: * @return the callback interval, in ms
119: */
120: public int getCallbackInterval() {
121: if (taskQueueToCallbackIntervalMap == null
122: || taskQueueToCallbackIntervalMap.size() == 0) {
123: return DEFAULT_CALLBACK_INTERVAL;
124: }
125: Iterator it = taskQueueToCallbackIntervalMap.values()
126: .iterator();
127: int returnInterval = Integer.MAX_VALUE;
128: while (it.hasNext()) {
129: int interval = ((Integer) it.next()).intValue();
130: if (interval < returnInterval) {
131: returnInterval = interval;
132: }
133: }
134: return returnInterval;
135: }
136:
137: /**
138: * Retrieves the <code>Component</code> with the specified element id.
139: *
140: * @param elementId the element id, e.g., "c_42323"
141: * @return the component (e.g., the component whose id is "42323")
142: */
143: public Component getComponentByElementId(String elementId) {
144: try {
145: return applicationInstance.getComponentByRenderId(elementId
146: .substring(2));
147: } catch (IndexOutOfBoundsException ex) {
148: throw new IllegalArgumentException(
149: "Invalid component element id: " + elementId);
150: }
151: }
152:
153: /**
154: * Retrieves the <code>IdTable</code> used by this
155: * <code>ContainerInstance</code> to assign weakly-referenced unique
156: * identifiers to arbitrary objects.
157: *
158: * @return the <code>IdTable</code>
159: */
160: public IdTable getIdTable() {
161: if (idTable == null) {
162: idTable = new IdTable();
163: }
164: return idTable;
165: }
166:
167: /**
168: * Returns an immutable <code>Map</code> containing the HTTP form
169: * parameters sent on the initial request to the application.
170: *
171: * @return the initial request parameter map
172: */
173: public Map getInitialRequestParameterMap() {
174: return initialRequestParameterMap;
175: }
176:
177: /**
178: * Retrieves the <code>RenderState</code> of the specified
179: * <code>Component</code>.
180: *
181: * @param component the component
182: * @return the rendering state
183: */
184: public RenderState getRenderState(Component component) {
185: return (RenderState) componentToRenderStateMap.get(component);
186: }
187:
188: /**
189: * Convenience method to retrieve the application's
190: * <code>UpdateManager</code>, which is used to synchronize
191: * client and server states.
192: * This method is equivalent to invoking
193: * <code>getApplicationInstance().getUpdateManager()</code>.
194: *
195: * @return the <code>UpdateManager</code>
196: */
197: public UpdateManager getUpdateManager() {
198: return applicationInstance.getUpdateManager();
199: }
200:
201: /**
202: * Initializes the <code>ContainerInstance</code>, creating an instance
203: * of the target <code>ApplicationInstance</code> and initializing the state
204: * of the application.
205: *
206: * @param conn the relevant <code>Connection</code>
207: */
208: public void init(Connection conn) {
209: if (initialized) {
210: throw new IllegalStateException(
211: "Attempt to invoke ContainerInstance.init() on initialized instance.");
212: }
213: WebContainerServlet servlet = (WebContainerServlet) conn
214: .getServlet();
215: applicationInstance = servlet.newApplicationInstance();
216:
217: ContainerContext containerContext = new ContainerContextImpl(
218: this );
219: applicationInstance.setContextProperty(
220: ContainerContext.CONTEXT_PROPERTY_NAME,
221: containerContext);
222:
223: try {
224: ApplicationInstance.setActive(applicationInstance);
225: applicationInstance.doInit();
226: } finally {
227: ApplicationInstance.setActive(null);
228: }
229: initialized = true;
230: }
231:
232: /**
233: * Determines if the <code>ContainerInstance</code> has been initialized,
234: * i.e., whether its <code>init()</code> method has been invoked.
235: *
236: * @return true if the <code>ContainerInstance</code> is initialized
237: */
238: boolean isInitialized() {
239: return initialized;
240: }
241:
242: /**
243: * Removes the <code>RenderState</code> of the specified
244: * <code>Component</code>.
245: *
246: * @param component the component
247: */
248: public void removeRenderState(Component component) {
249: componentToRenderStateMap.remove(component);
250: }
251:
252: /**
253: * Sets the <code>RenderState</code> of the specified
254: * <code>Component</code>.
255: *
256: * @param component the component
257: * @param renderState the render state
258: */
259: public void setRenderState(Component component,
260: RenderState renderState) {
261: componentToRenderStateMap.put(component, renderState);
262: }
263:
264: /**
265: * Sets the interval between asynchronous callbacks from the client to check
266: * for queued tasks for a given <code>TaskQueue</code>. If multiple
267: * <code>TaskQueue</code>s are active, the smallest specified interval should
268: * be used. The default interval is 500ms.
269: * Application access to this method should be accessed via the
270: * <code>ContainerContext</code>.
271: *
272: * @param taskQueue the <code>TaskQueue</code>
273: * @param ms the number of milliseconds between asynchronous client
274: * callbacks
275: * @see nextapp.echo2.webcontainer.ContainerContext#setTaskQueueCallbackInterval(nextapp.echo2.app.TaskQueueHandle, int)
276: */
277: public void setTaskQueueCallbackInterval(TaskQueueHandle taskQueue,
278: int ms) {
279: if (taskQueueToCallbackIntervalMap == null) {
280: taskQueueToCallbackIntervalMap = new WeakHashMap();
281: }
282: taskQueueToCallbackIntervalMap.put(taskQueue, new Integer(ms));
283: }
284: }
|