001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.getahead.dwrdemo.ticketcenter;
017:
018: import java.util.ArrayList;
019: import java.util.Collection;
020: import java.util.Collections;
021: import java.util.List;
022: import java.util.Random;
023: import java.util.concurrent.ScheduledThreadPoolExecutor;
024: import java.util.concurrent.TimeUnit;
025:
026: import jsx3.GI;
027: import jsx3.app.Server;
028: import jsx3.gui.Form;
029: import jsx3.gui.LayoutGrid;
030: import jsx3.gui.Matrix;
031: import jsx3.gui.Select;
032: import jsx3.gui.TextBox;
033: import jsx3.xml.CdfDocument;
034: import jsx3.xml.Record;
035:
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038: import org.directwebremoting.ScriptSession;
039: import org.directwebremoting.ServerContext;
040: import org.directwebremoting.ServerContextFactory;
041: import org.directwebremoting.WebContextFactory;
042: import org.directwebremoting.proxy.browser.Window;
043: import org.directwebremoting.util.SharedObjects;
044: import org.getahead.dwrdemo.util.RandomData;
045:
046: /**
047: * @author Joe Walker [joe at getahead dot ltd dot uk]
048: */
049: public class CallCenter implements Runnable {
050: /**
051: * Create a new publish thread and start it
052: */
053: public CallCenter() {
054: // Start with some calls waiting
055: addRandomKnownCall();
056: addRandomUnknownCall();
057: addRandomUnknownCall();
058: addRandomUnknownCall();
059:
060: ScheduledThreadPoolExecutor executor = SharedObjects
061: .getScheduledThreadPoolExecutor();
062: executor.scheduleAtFixedRate(this , 2, 2, TimeUnit.SECONDS);
063: }
064:
065: /**
066: * Called once every couple of seconds to take some random action
067: */
068: public void run() {
069: try {
070: synchronized (calls) {
071: switch (random.nextInt(5)) {
072: case 0:
073: case 1:
074: addRandomUnknownCall();
075: break;
076:
077: case 2:
078: addRandomKnownCall();
079: break;
080:
081: case 3:
082: removeRandomCall();
083: break;
084:
085: default:
086: break;
087: }
088:
089: updateAll();
090: }
091: } catch (Exception ex) {
092: log.warn("Random event failure", ex);
093: }
094: }
095:
096: /**
097: * Called when the page first loads to ensure we have an up-to-date screen
098: */
099: public void load() {
100: ScriptSession session = WebContextFactory.get()
101: .getScriptSession();
102: deselect(session);
103:
104: update(Collections.singleton(session));
105: }
106:
107: /**
108: *
109: */
110: public void alertSupervisor(Call fromWeb) {
111: // This is the ScriptSession of the agent that wishes to alert a supervisor
112: ScriptSession session = WebContextFactory.get()
113: .getScriptSession();
114: Window window = new Window(session);
115:
116: // We store the ID of the call we are working on in the ScriptSession
117: Object handlingId = session.getAttribute("handlingId");
118: if (handlingId == null) {
119: window.alert("No call found");
120: return;
121: }
122:
123: synchronized (calls) {
124: // Check to see that the caller has not hung up since the last update
125: Call call = findCaller(handlingId);
126: if (call == null) {
127: window
128: .alert("That caller hung up, please select another");
129: return;
130: }
131:
132: // The user isn't handling this call any more
133: session.removeAttribute("handlingId");
134:
135: // Update the server details from those passed in
136: call.setName(fromWeb.getName());
137: call.setAddress(fromWeb.getAddress());
138: call.setNotes(fromWeb.getNotes());
139: call.setHandlerId(null);
140: call.setSupervisorAlert(true);
141:
142: // Update the screen of the current user
143: deselect(session);
144:
145: // Update everyone else's screen
146: updateAll();
147: }
148: }
149:
150: /**
151: *
152: */
153: public void completeHandling(Call fromWeb) {
154: ScriptSession session = WebContextFactory.get()
155: .getScriptSession();
156: Window window = new Window(session);
157:
158: Object handlingId = session.getAttribute("handlingId");
159: if (handlingId == null) {
160: window.alert("No call found");
161: return;
162: }
163:
164: synchronized (calls) {
165: Call call = findCaller(handlingId);
166: if (call == null) {
167: window
168: .alert("That caller hung up, please select another");
169: return;
170: }
171:
172: session.removeAttribute("handlingId");
173:
174: calls.remove(call);
175: log.debug("Properly we should book a ticket for "
176: + fromWeb.getPhoneNumber());
177:
178: deselect(session);
179: updateAll();
180: }
181: }
182:
183: /**
184: *
185: */
186: public void cancelHandling() {
187: ScriptSession session = WebContextFactory.get()
188: .getScriptSession();
189: Window window = new Window(session);
190:
191: Object handlingId = session.getAttribute("handlingId");
192: if (handlingId == null) {
193: window.alert("That caller hung up, please select another");
194: return;
195: }
196:
197: synchronized (calls) {
198: Call call = findCaller(handlingId);
199: if (call == null) {
200: log.debug("Cancel handling of call that hung up");
201: return;
202: }
203:
204: session.removeAttribute("handlingId");
205: call.setHandlerId(null);
206:
207: deselect(session);
208: updateAll();
209: }
210: }
211:
212: /**
213: *
214: */
215: public void beginHandling(String id) {
216: ScriptSession session = WebContextFactory.get()
217: .getScriptSession();
218: Window window = new Window(session);
219:
220: Object handlingId = session.getAttribute("handlingId");
221: if (handlingId != null) {
222: window
223: .alert("Please finish handling the current call before selecting another");
224: return;
225: }
226:
227: synchronized (calls) {
228: Call call = findCaller(id);
229: if (call == null) {
230: log.debug("Caller not found: " + id);
231: window
232: .alert("That caller hung up, please select another");
233: } else {
234: if (call.getHandlerId() != null) {
235: window
236: .alert("That call is being handled, please select another");
237: return;
238: }
239:
240: session.setAttribute("handlingId", id);
241: call.setHandlerId(session.getId());
242:
243: select(session, call);
244: updateAll();
245: }
246: }
247: }
248:
249: /**
250: *
251: */
252: private Call findCaller(Object attribute) {
253: try {
254: int id = Integer.parseInt(attribute.toString());
255:
256: // We could optimize this, but since there are less than 20 people
257: // in the queue ...
258: for (Call call : calls) {
259: if (call.getId() == id) {
260: return call;
261: }
262: }
263:
264: return null;
265: } catch (NumberFormatException ex) {
266: log.warn("Illegal number format: " + attribute.toString(),
267: ex);
268: return null;
269: }
270: }
271:
272: /**
273: *
274: */
275: protected void removeRandomCall() {
276: if (calls.size() > 0) {
277: Call removed = null;
278:
279: synchronized (calls) {
280: int toDelete = random.nextInt(calls.size());
281: removed = calls.remove(toDelete);
282:
283: String sessionId = removed.getHandlerId();
284: if (sessionId != null) {
285: ScriptSession session = ServerContextFactory.get()
286: .getScriptSessionById(sessionId);
287:
288: if (session != null) {
289: session.removeAttribute("handlingId");
290:
291: Window window = new Window(session);
292: window
293: .alert("It appears that this caller has hung up. Please select another.");
294: deselect(session);
295: }
296: }
297:
298: updateAll();
299: }
300:
301: // log.info("Random Event: Caller hangs up: " + removed.getPhoneNumber());
302: }
303: }
304:
305: /**
306: *
307: */
308: protected void addRandomKnownCall() {
309: if (calls.size() < 10) {
310: Call call = new Call();
311: call.setId(getNextId());
312: call.setName(RandomData.getFullName());
313: String[] addressAndNumber = RandomData
314: .getAddressAndNumber();
315: call.setAddress(addressAndNumber[0]);
316: call.setPhoneNumber(addressAndNumber[1]);
317:
318: calls.add(call);
319:
320: // log.info("Random Event: New caller: " + call.getName());
321: }
322: }
323:
324: /**
325: *
326: */
327: protected void addRandomUnknownCall() {
328: if (calls.size() < 10) {
329: String phoneNumber = RandomData.getPhoneNumber(random
330: .nextInt(3) != 0);
331: Call call = new Call();
332: call.setPhoneNumber(phoneNumber);
333: call.setId(getNextId());
334: calls.add(call);
335:
336: // log.info("Random Event: New caller: " + call.getPhoneNumber());
337: }
338: }
339:
340: /**
341: *
342: */
343: protected void updateAll() {
344: ServerContext serverContext = ServerContextFactory.get();
345: String contextPath = serverContext.getContextPath();
346: if (contextPath == null) {
347: return;
348: }
349:
350: Collection<ScriptSession> sessions = serverContext
351: .getScriptSessionsByPage(contextPath
352: + "/gi/ticketcenter.html");
353:
354: update(sessions);
355: }
356:
357: /**
358: * @param sessions
359: */
360: protected void update(Collection<ScriptSession> sessions) {
361: // Populate a CDF document with data about our calls
362: CdfDocument cdfdoc = new CdfDocument("jsxroot");
363: for (Call call : calls) {
364: cdfdoc.appendRecord(new Record(call));
365: }
366:
367: // Put the CDF doc into the client side cache, and repaint the table
368: Server tc = GI.getServer(sessions, "ticketcenter");
369: tc.getCache().setDocument("callers", cdfdoc);
370: tc.getJSXByName("listCallers", Matrix.class).repaint(null);
371: }
372:
373: /**
374: * @param session
375: * @param call
376: */
377: private void select(ScriptSession session, Call call) {
378: Server ticketcenter = GI.getServer(session, "ticketcenter");
379:
380: ticketcenter.getJSXByName("textPhone", TextBox.class).setValue(
381: call.getPhoneNumber());
382: ticketcenter.getJSXByName("textName", TextBox.class).setValue(
383: call.getName());
384: ticketcenter.getJSXByName("textNotes", TextBox.class).setValue(
385: call.getNotes());
386: ticketcenter.getJSXByName("selectEvent", Select.class)
387: .setValue(null);
388:
389: setFormEnabled(session, true);
390: }
391:
392: /**
393: * Set the form to show no caller
394: * @param session The user that we are clearing
395: */
396: private void deselect(ScriptSession session) {
397: Server ticketcenter = GI.getServer(session, "ticketcenter");
398:
399: ticketcenter.getJSXByName("textPhone", TextBox.class).setValue(
400: "");
401: ticketcenter.getJSXByName("textName", TextBox.class).setValue(
402: "");
403: ticketcenter.getJSXByName("textNotes", TextBox.class).setValue(
404: "");
405: ticketcenter.getJSXByName("selectEvent", Select.class)
406: .setValue(null);
407:
408: setFormEnabled(session, false);
409: }
410:
411: /**
412: * Disable all the elements in the form
413: * @param enabled True to enable the elements/false ...
414: */
415: private void setFormEnabled(ScriptSession session, boolean enabled) {
416: int state = enabled ? Form.STATEENABLED : Form.STATEDISABLED;
417:
418: Server ticketcenter = GI.getServer(session, "ticketcenter");
419: for (String element : ELEMENTS) {
420: ticketcenter.getJSXByName(element, TextBox.class)
421: .setEnabled(state, true);
422: }
423:
424: LayoutGrid layoutForm = ticketcenter.getJSXByName("layoutForm",
425: LayoutGrid.class);
426: layoutForm.setBackgroundColor(enabled ? "#FFF" : "#EEE", true);
427: }
428:
429: /**
430: * The form fields that we enable and disable
431: */
432: private static final String[] ELEMENTS = new String[] {
433: "textPhone", "textName", "textAddress", "textPayment",
434: "textNotes", "selectEvent", "selectPaymentType",
435: "buttonBook", "buttonSupervisor", "buttonCancel" };
436:
437: /**
438: * Get the next unique ID in a thread safe way
439: * @return a unique id
440: */
441: public static synchronized int getNextId() {
442: return nextId++;
443: }
444:
445: /**
446: * The next ID, to get around serialization issues
447: */
448: private static int nextId = 1;
449:
450: /**
451: * The set of people in our database
452: */
453: protected List<Call> calls = Collections
454: .synchronizedList(new ArrayList<Call>());
455:
456: /**
457: * Used to generate random data
458: */
459: protected Random random = new Random();
460:
461: /**
462: * The log stream
463: */
464: protected static final Log log = LogFactory
465: .getLog(CallCenter.class);
466: }
|