package net.sourceforge.gjtapi.raw.modem; // NAME // $RCSfile$ // DESCRIPTION // [given below in javadoc format] // DELTA // $Revision$ // CREATED // $Date$ // COPYRIGHT // Westhawk Ltd // TO DO // import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URL; import java.util.*; import javax.telephony.*; import javax.telephony.media.*; import net.sourceforge.gjtapi.*; import net.sourceforge.gjtapi.media.SymbolConvertor; import net.sourceforge.gjtapi.raw.MediaTpi; /** * An implementation of a Jtapi provider which uses a voice capable * modem. * * @author <a href="mailto:ray@westhawk.co.uk">Ray Tran</a> * @version $Revision$ $Date$ */ public class ModemProvider implements MediaTpi, ModemListener { private final static String RESOURCE_NAME = "Modem.props"; private final static String ADDRESS_PREFIX = "Address"; private final static String SERIAL = "Serial";//used as a key to select the //serial port used by the modem // The plugged in Modem interface implementation lookup key private final static String MODEM_PROVIDER_CLASS = "ModemClass"; private final static String DEFAULT_MODEM_PROVIDER = "net.sourceforge.gjtapi.raw.modem.AccuraV92"; private Properties provProps; private List addresses; private TermData terminal; private TelephonyListener listener; //According to implementors guide we only need one! private Modem modem; /** * Raw constructor used by the GenericJtapiPeer factory */ public ModemProvider() { // read provider details and load the resources, if available Properties props = new Properties(); try { props.load(this.getClass().getResourceAsStream('/' + RESOURCE_NAME)); } catch (IOException ioe) { // ignore and hope that the initialize method sets my required properties } provProps = props; } //ModemListener implementation /** * The modem is ringing * * @return CallId - the id of the new call */ public CallId modemRinging(){ CallId id = null; if (listener != null){ String address = (String)addresses.get(0); try{ id = reserveCallId(address); listener.connectionAlerting(id, address, ConnectionEvent.CAUSE_NEW_CALL); listener.terminalConnectionRinging(id, address, terminal.terminal, TerminalConnectionEvent.CAUSE_NORMAL); }catch (InvalidArgumentException ex){ System.err.println("Invalid argument"); } } return id; } /** * The modem was ringing but the caller has hung up before we answered * * @param id - the id of the call which has just stopped */ public void ringingStopped(CallId id){ if (listener != null){ String address = (String)addresses.get(0); listener.terminalConnectionDropped(id, address, terminal.terminal, ConnectionEvent.CAUSE_CALL_CANCELLED); } releaseCallId(id); } public void modemConnected(CallId id){ if (listener != null){ String address = (String)addresses.get(0); listener.connectionConnected(id, address, ConnectionEvent.CAUSE_NORMAL); listener.terminalConnectionTalking(id, address, this.terminal.terminal, Event.CAUSE_NORMAL); } } public void modemDisconnected(CallId id){ if (listener != null){ String address = (String)addresses.get(0); listener.connectionDisconnected(id, address, ConnectionEvent.CAUSE_NORMAL); } } public void modemFailed(CallId id){ if (listener != null){ String address = (String)addresses.get(0); listener.connectionFailed(id, address, ConnectionEvent.CAUSE_DEST_NOT_OBTAINABLE); } } //BasicJtapiTpi implementation /** * Initialize the provider. * * The main task is setting the serial port and modem up. * * @param props Map describing how to initialise * @throws ProviderUnavailableException */ public void initialize(Map props) throws ProviderUnavailableException { if(provProps != null){ // first allow passed in props to override my defaults if (props != null) provProps.putAll(props); Object obj = provProps.get(SERIAL); if((obj != null) && (obj instanceof String)){ String portname = (String) obj; // get the class name for the plugged in Modem adapter String modemClassName = (String)provProps.get(ModemProvider.MODEM_PROVIDER_CLASS); if (modemClassName == null) modemClassName = ModemProvider.DEFAULT_MODEM_PROVIDER; // now try to instantiate //TODO: need to get the modem by reflection or similar try { // set the parameter type Class[] paramTypes = {ModemListener.class}; Constructor constructor = Class.forName(modemClassName).getConstructor(paramTypes); Object[] params = {this}; modem = (Modem)constructor.newInstance(params); } catch (ClassNotFoundException cnfe) { throw new ProviderUnavailableException("Modem implementation not found: " + modemClassName); } catch (NoSuchMethodException e) { throw new ProviderUnavailableException("Incorrect Constructor: " + modemClassName); } catch (InvocationTargetException e) { throw new ProviderUnavailableException("Incorrect target: " + modemClassName); } catch (IllegalAccessException iae) { throw new ProviderUnavailableException("Could not access: " + modemClassName); } catch (InstantiationException e) { throw new ProviderUnavailableException("Could not instantiate: " + modemClassName); } if (modem.initialize(portname) == false){ modem = null; throw new ProviderUnavailableException( ProviderUnavailableException.CAUSE_NOT_IN_SERVICE, "The modem on " + portname + " could not be initialized" ); } }else{ throw new ProviderUnavailableException( ProviderUnavailableException.CAUSE_INVALID_ARGUMENT, "The passed in serial port name was \"null\" or not a String" ); } }else{ throw new ProviderUnavailableException( ProviderUnavailableException.CAUSE_INVALID_ARGUMENT, "Parameter \"props\" was null" ); } //Now get all of the addresses addresses = new Vector(); Iterator iter = provProps.keySet().iterator(); while (iter.hasNext()){ String key = (String)iter.next(); if (key.startsWith(ADDRESS_PREFIX)){ addresses.add(provProps.get(key)); } } //Finally set up the terminal terminal = new TermData((String)provProps.get(SERIAL), true); } /** * Add an observer for RawEvents. * * We can only store a single listener because that is all that is required * by the architecture. * * @param ro TelephonyListener to register */ public void addListener(TelephonyListener ro) { if (listener == null){ listener = ro; }else{ System.err.println("Request to add a TelephonyListener to " + this.getClass().getName() + ", but one is already registered"); } } /** * Remove the observer for RawEvents. * * If the listener isn't the one registered nothing is done * * @param ro TelephonyListener to de-register */ public void removeListener(TelephonyListener ro) { if (ro == listener){ listener = null; }else{ System.err.println("Request to remove a TelephonyListener from " + this.getClass().getName() + ", but it wasn't registered"); } } public String[] getAddresses() throws ResourceUnavailableException { /*debug * Iterator iter = addresses.iterator(); * while (iter.hasNext()){ * System.err.println(" Address: " + iter.next()); * } */ return (String[]) addresses.toArray(new String[0]); } public String[] getAddresses(String terminal) throws InvalidArgumentException { //We only support a single terminal which maps to all addresses if (terminal.equals((String) provProps.get(SERIAL)) == false){ System.err.println("Terminal " + terminal + " is unknown, throwing exception"); throw new InvalidArgumentException("Terminal " + terminal + "is unknown"); } String[] result = null; try { result = getAddresses(); } catch (ResourceUnavailableException ex) { //This shouldn't happen! throw new InvalidArgumentException(); } return result; } public TermData[] getTerminals() throws ResourceUnavailableException { if (terminal != null){ return new TermData[]{terminal}; }else{ int reason = ResourceUnavailableException.ORIGINATOR_UNAVAILABLE; throw new ResourceUnavailableException(reason); } } public TermData[] getTerminals(String address) throws InvalidArgumentException { //We only support a single terminal which maps to all addresses if (addresses.contains(address) == false){ System.err.println("Address " + address + " is unknown, throwing exception"); throw new InvalidArgumentException("Address " + address + " is unknown"); } TermData[] tda = null; try { tda = getTerminals(); } catch (ResourceUnavailableException ex) { //This shouldn't happen! throw new InvalidArgumentException(); } return tda; } public Properties getCapabilities() { return provProps; } //Doc is a bit unclear on whether this parameter is an address or a terminal //we seem to get passed a terminal so for now I have disabled checking! public CallId reserveCallId(String address) throws InvalidArgumentException { //if (addresses.contains(address)){ return new ModemCallId(); //}else{ // throw new InvalidArgumentException("Invalid address"); //} } public void releaseCallId(CallId id) { //Nothing to do } public CallId createCall(CallId id, String address, String term, String dest) throws ResourceUnavailableException, PrivilegeViolationException, InvalidPartyException, InvalidArgumentException, RawStateException, MethodNotSupportedException { if (id instanceof ModemCallId == false){ throw new InvalidArgumentException("createCall requires a ModemCallId"); }else if (addresses.contains(address) == false){ throw new InvalidArgumentException("Invalid address"); }else if (terminal.terminal.equals(term) == false){ throw new InvalidArgumentException("Invalid terminal"); }else if (modem == null){ throw new RawStateException(id, address, term, RawStateException.TERMINAL_OBJECT, Call.INVALID, "Modem is null" ); }else if (modem.getState() != Modem.IDLE){ throw new RawStateException(id, address, term, RawStateException.TERMINAL_OBJECT, Call.INVALID, "Modem is not idle" ); } //Passed all tests so get the modem to make the call if (modem.call(id, dest) == false){ id = null; } return id; } public void answerCall(CallId id, String address, String term) throws PrivilegeViolationException, ResourceUnavailableException, MethodNotSupportedException, RawStateException { if (id instanceof ModemCallId == false){ throw new MethodNotSupportedException("createCall requires a ModemCallId"); }else if (addresses.contains(address) == false){ throw new MethodNotSupportedException("Invalid address"); }else if (terminal.terminal.equals(term) == false){ throw new MethodNotSupportedException("Invalid terminal"); }else if (modem == null){ throw new RawStateException(id, address, term, RawStateException.TERMINAL_OBJECT, Call.INVALID, "Modem is null" ); }else if (modem.getState() != Modem.RINGING){ throw new RawStateException(id, address, term, RawStateException.TERMINAL_OBJECT, Call.INVALID, "Modem is not ringing" ); } //Passed all test so get the modem to answer the call modem.answer(id); } public void release(String address, CallId call) throws PrivilegeViolationException, ResourceUnavailableException, MethodNotSupportedException, RawStateException { if ((modem != null) && (modem.getState() == Modem.BUSY) && (addresses.contains(address))) { modem.drop(call); } } public void shutdown() { if (modem != null){ modem.shutdown(); modem = null; } } //MediaTpi implementation public boolean allocateMedia(String terminal, int type, Dictionary resourceArgs) { return true; } public boolean freeMedia(String terminal, int type) { return true; } public boolean isMediaTerminal(String terminal) { return true; } public void play(String terminal, String[] streamIds, int offset,RTC[] rtcs, Dictionary optArgs) throws MediaResourceException { for (int i=0, len=streamIds.length; i<len; i++){ try{ URL url = new URL(streamIds[i]); InputStream is = url.openStream(); modem.play(is); is.close(); }catch (Exception ex){ System.err.println("Exception in play()"); ex.printStackTrace(); } } } public void record(String terminal, String streamId, RTC[] rtcs, Dictionary optArgs) throws MediaResourceException { try{ URI uri = new URI(streamId); File file = new File(uri); FileOutputStream fos = new FileOutputStream(file, false); modem.record(fos); fos.close(); }catch (Exception ex){ System.err.println("Exception in record()"); ex.printStackTrace(); } } public RawSigDetectEvent retrieveSignals(String terminal, int num, Symbol[] patterns, RTC[] rtcs, Dictionary optArgs) throws MediaResourceException { String sigs = modem.reportDTMF(num); return RawSigDetectEvent.maxDetected(terminal, SymbolConvertor.convert(sigs)); } public void sendSignals(String terminal, Symbol[] syms, RTC[] rtcs, Dictionary optArgs) throws MediaResourceException { String tones = SymbolConvertor.convert(syms); modem.sendDTMF(tones); } public void stop(String terminal) { } public void triggerRTC(String terminal, Symbol action) { } /** * Tells GJTAPI that we are starting to dial out * @see net.sourceforge.gjtapi.raw.modem.ModemListener#modemDialing(net.sourceforge.gjtapi.CallId) */ public void modemDialing(CallId id, String destinationAddress) { if (listener != null){ listener.connectionAlerting(id, destinationAddress, ConnectionEvent.CAUSE_NORMAL); } } public void modemConnected(CallId id, String remoteLeg) { if (listener != null){ listener.connectionConnected(id, remoteLeg, ConnectionEvent.CAUSE_NORMAL); } } /** * Note that an alerting connection failed (i.e. was busy) * @see net.sourceforge.gjtapi.raw.modem.ModemListener#modemFailed(net.sourceforge.gjtapi.CallId, java.lang.String) */ public void modemFailed(CallId id, String remoteLeg) { if (listener != null){ listener.connectionFailed(id, remoteLeg, ConnectionEvent.CAUSE_DEST_NOT_OBTAINABLE); } } }