/*
* Copyright 2005 Joe Walker
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.dwr.ticketcenter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import jsx3.GI;
import jsx3.app.Server;
import jsx3.gui.Form;
import jsx3.gui.LayoutGrid;
import jsx3.gui.Matrix;
import jsx3.gui.Select;
import jsx3.gui.TextBox;
import jsx3.xml.CdfDocument;
import jsx3.xml.Record;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.Browser;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.ScriptSessions;
import org.directwebremoting.ServerContextFactory;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.ui.browser.Window;
import com.example.dwr.people.RandomData;
/**
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class CallCenter implements Runnable
{
/**
* Create a new publish thread and start it
*/
public CallCenter()
{
// Start with some calls waiting
addRandomKnownCall();
addRandomUnknownCall();
addRandomUnknownCall();
addRandomUnknownCall();
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
//noinspection ThisEscapedInObjectConstruction
executor.scheduleAtFixedRate(this, 2, 2, TimeUnit.SECONDS);
}
/**
* Called once every couple of seconds to take some random action
*/
public void run()
{
try
{
synchronized (calls)
{
switch (random.nextInt(5))
{
case 0:
case 1:
addRandomUnknownCall();
break;
case 2:
addRandomKnownCall();
break;
case 3:
removeRandomCall();
break;
default:
break;
}
updateAll();
}
}
catch (Exception ex)
{
log.warn("Random event failure", ex);
}
}
/**
* Called when the page first loads to ensure we have an up-to-date screen
*/
public void load()
{
deselect();
update();
}
/**
*
*/
public void alertSupervisor(Call fromWeb)
{
// This is the ScriptSession of the agent that wishes to alert a supervisor
ScriptSession session = WebContextFactory.get().getScriptSession();
// We store the ID of the call we are working on in the ScriptSession
Object handlingId = session.getAttribute("handlingId");
if (handlingId == null)
{
Window.alert("No call found");
return;
}
synchronized (calls)
{
// Check to see that the caller has not hung up since the last update
Call call = findCaller(handlingId);
if (call == null)
{
Window.alert("That caller hung up, please select another");
return;
}
// The user isn't handling this call any more
session.removeAttribute("handlingId");
// Update the server details from those passed in
call.setName(fromWeb.getName());
call.setAddress(fromWeb.getAddress());
call.setNotes(fromWeb.getNotes());
call.setHandlerId(null);
call.setSupervisorAlert(true);
// Update the screen of the current user
deselect();
// Update everyone else's screen
updateAll();
}
}
/**
*
*/
public void completeHandling(Call fromWeb)
{
ScriptSession session = WebContextFactory.get().getScriptSession();
Object handlingId = session.getAttribute("handlingId");
if (handlingId == null)
{
Window.alert("No call found");
return;
}
synchronized (calls)
{
Call call = findCaller(handlingId);
if (call == null)
{
Window.alert("That caller hung up, please select another");
return;
}
session.removeAttribute("handlingId");
calls.remove(call);
log.debug("Properly we should book a ticket for " + fromWeb.getPhoneNumber());
deselect();
updateAll();
}
}
/**
*
*/
public void cancelHandling()
{
ScriptSession session = WebContextFactory.get().getScriptSession();
Object handlingId = session.getAttribute("handlingId");
if (handlingId == null)
{
Window.alert("That caller hung up, please select another");
return;
}
synchronized (calls)
{
Call call = findCaller(handlingId);
if (call == null)
{
log.debug("Cancel handling of call that hung up");
return;
}
session.removeAttribute("handlingId");
call.setHandlerId(null);
deselect();
updateAll();
}
}
/**
*
*/
public void beginHandling(String id)
{
ScriptSession session = WebContextFactory.get().getScriptSession();
Object handlingId = session.getAttribute("handlingId");
if (handlingId != null)
{
Window.alert("Please finish handling the current call before selecting another");
return;
}
synchronized (calls)
{
Call call = findCaller(id);
if (call == null)
{
log.debug("Caller not found: " + id);
Window.alert("That caller hung up, please select another");
}
else
{
if (call.getHandlerId() != null)
{
Window.alert("That call is being handled, please select another");
return;
}
session.setAttribute("handlingId", id);
call.setHandlerId(session.getId());
select(call);
updateAll();
}
}
}
/**
*
*/
private Call findCaller(Object attribute)
{
try
{
int id = Integer.parseInt(attribute.toString());
// We could optimize this, but since there are less than 20 people
// in the queue ...
for (Call call : calls)
{
if (call.getId() == id)
{
return call;
}
}
return null;
}
catch (NumberFormatException ex)
{
log.warn("Illegal number format: " + attribute.toString(), ex);
return null;
}
}
/**
*
*/
protected void removeRandomCall()
{
if (!calls.isEmpty())
{
synchronized (calls)
{
int toDelete = random.nextInt(calls.size());
Call removed = calls.remove(toDelete);
String sessionId = removed.getHandlerId();
if (sessionId != null)
{
Browser.withSession(sessionId, new Runnable()
{
public void run()
{
ScriptSessions.removeAttribute("handlingId");
Window.alert("It appears that this caller has hung up. Please select another.");
deselect();
}
});
}
updateAll();
}
// log.info("Random Event: Caller hangs up: " + removed.getPhoneNumber());
}
}
/**
*
*/
protected void addRandomKnownCall()
{
if (calls.size() < 10)
{
Call call = new Call();
call.setId(getNextId());
call.setName(RandomData.getFullName());
String[] addressAndNumber = RandomData.getAddressAndNumber();
call.setAddress(addressAndNumber[0]);
call.setPhoneNumber(addressAndNumber[1]);
calls.add(call);
// log.info("Random Event: New caller: " + call.getName());
}
}
/**
*
*/
protected void addRandomUnknownCall()
{
if (calls.size() < 10)
{
String phoneNumber = RandomData.getPhoneNumber(random.nextInt(3) != 0);
Call call = new Call();
call.setPhoneNumber(phoneNumber);
call.setId(getNextId());
calls.add(call);
// log.info("Random Event: New caller: " + call.getPhoneNumber());
}
}
/**
*
*/
protected void updateAll()
{
String contextPath = ServerContextFactory.get().getContextPath();
if (contextPath == null)
{
return;
}
String page = contextPath + "/gi/ticketcenter.html";
Browser.withPage(page, new Runnable()
{
public void run()
{
update();
}
});
}
/**
*
*/
protected void update()
{
// Populate a CDF document with data about our calls
CdfDocument cdfdoc = new CdfDocument("jsxroot");
for (Call call : calls)
{
cdfdoc.appendRecord(new Record(call));
}
// Put the CDF doc into the client side cache, and repaint the table
Server tc = GI.getServer("ticketcenter");
tc.getCache().setDocument("callers", cdfdoc);
tc.getJSXByName("listCallers", Matrix.class).repaint(null);
}
/**
*
*/
private void select(Call call)
{
Server ticketcenter = GI.getServer("ticketcenter");
ticketcenter.getJSXByName("textPhone", TextBox.class).setValue(call.getPhoneNumber());
ticketcenter.getJSXByName("textName", TextBox.class).setValue(call.getName());
ticketcenter.getJSXByName("textNotes", TextBox.class).setValue(call.getNotes());
ticketcenter.getJSXByName("selectEvent", Select.class).setValue(null);
setFormEnabled(true);
}
/**
* Set the form to show no caller
*/
private void deselect()
{
Server ticketcenter = GI.getServer("ticketcenter");
ticketcenter.getJSXByName("textPhone", TextBox.class).setValue("");
ticketcenter.getJSXByName("textName", TextBox.class).setValue("");
ticketcenter.getJSXByName("textNotes", TextBox.class).setValue("");
ticketcenter.getJSXByName("selectEvent", Select.class).setValue(null);
setFormEnabled(false);
}
/**
* Disable all the elements in the form
* @param enabled True to enable the elements/false ...
*/
private void setFormEnabled(boolean enabled)
{
int state = enabled ? Form.STATEENABLED : Form.STATEDISABLED;
Server ticketcenter = GI.getServer("ticketcenter");
for (String element : ELEMENTS)
{
ticketcenter.getJSXByName(element, TextBox.class).setEnabled(state, true);
}
LayoutGrid layoutForm = ticketcenter.getJSXByName("layoutForm", LayoutGrid.class);
layoutForm.setBackgroundColor(enabled ? "#FFF" : "#EEE", true);
}
/**
* The form fields that we enable and disable
*/
private static final String[] ELEMENTS = new String[]
{
"textPhone", "textName", "textAddress", "textPayment", "textNotes",
"selectEvent", "selectPaymentType", "buttonBook", "buttonSupervisor",
"buttonCancel"
};
/**
* Get the next unique ID in a thread safe way
* @return a unique id
*/
public static synchronized int getNextId()
{
return nextId++;
}
/**
* The next ID, to get around serialization issues
*/
private static int nextId = 1;
/**
* The set of people in our database
*/
private final List<Call> calls = Collections.synchronizedList(new ArrayList<Call>());
/**
* Used to generate random data
*/
private final Random random = new Random();
/**
* The log stream
*/
private static final Log log = LogFactory.getLog(CallCenter.class);
}