package net.sourceforge.gjtapi.raw.mux; /* Copyright (c) 2002 8x8 Inc. (www.8x8.com) All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder. */ import javax.telephony.*; import javax.telephony.media.*; import net.sourceforge.gjtapi.*; import net.sourceforge.gjtapi.capabilities.*; import net.sourceforge.gjtapi.raw.*; import java.util.*; import java.io.*; /** * This is a pluggable provider that multiplexes several other providers together. * <P>Note that this decides which subprovider to delegate calls off to using Call, Address and * Terminal to subprovider maps. In the case where a sub-provider does not return a full set of Address * and Terminal information (throws ResourceUnavailableException for getAddresses() or allows dynamic * addresses by returning "dynamicAddresses = t" in its capabilities Properties object), we may not have * full delegation information. We have two options: * <ol> * <li>Broadcast all requests where the receiver isn't known * <li>Catch all address and terminal mappings in Listener events. * </ol> * Currently we do both, with no tuning to try to determine if the extra cost of Listener interception * is worth it. The three methods that provide broadcast are: * <ul> * <li>reportCallsOnAddress(String address, boolean flag) * <li>reportCallsOnTerminal(String terminal, boolean flag) * <li>reserveCallId(String address) * </ul> * Creation date: (2000-02-22 10:53:56) * @author: Richard Deadman */ public class MuxProvider implements TelephonyProvider { private final static String RESOURCE_NAME = "Mux.props"; private final static String PROVIDER_PREFIX = "Provider"; private final static String CLASS_PREFIX = "Class_"; private final static String PROPS_PREFIX = "Props_"; private final static int UNFLUSHED = 0; private final static int FLUSHED = 1; private final static int TOOBIG = 2; private Map addToSub = new HashMap(); private int addrFlag = UNFLUSHED; private int termFlag = UNFLUSHED; private HashSet termData = new HashSet(); // Set of TermData holders. private Map subToCaps = new HashMap(); // maps sub-providers to RawCapabilities sets /** * Set of MuxCallIds that hold 1 or more CallHolders. Each MuxCallId is a logical * call possibly bridged across multiple low-level sub-providers. **/ private Set calls = new HashSet(); private Map lowToLogicalMap = new HashMap(); private Map termToSub = new HashMap(); /** * Add a MuxCallId and its backwards lookup holder to my call set. * Creation date: (2000-09-26 16:13:08) * @param ch net.sourceforge.gjtapi.raw.mux.CallHolder * @param mci net.sourceforge.gjtapi.raw.mux.MuxCallId */ private void addCall(CallHolder ch, MuxCallId mci) { this.getCalls().add(mci); this.getLowToLogicalMap().put(ch, mci); } /** * Forward to remote provider */ public void addListener(TelephonyListener rl) { Iterator it = this.getSubProviders().iterator(); while (it.hasNext()) { TelephonyProvider rp = ((TelephonyProvider)it.next()); rp.addListener(new MuxListener(rl, this, rp)); } } /** * Allocate a media type resource to a sub-provider's terminal. * Creation date: (2000-03-09 10:52:06) * @param: terminal The terminal to be attached to media resources * @param type A flag telling the type of media to attach. See RawProvider for static types. * @param params Control paramters for the resource. * @return: true if the media was allocated. */ public boolean allocateMedia(String terminal, int type, Dictionary params) { TelephonyProvider rp = this.getTerminalSub(terminal); if (((RawCapabilities)this.getSubToCaps().get(rp)).allocateMedia) return rp.allocateMedia(terminal, type, params); else return true; // The rawProvider guaranteed that it does not need this call. } /** * Forward answerCall to remote provider */ public void answerCall(CallId call, String address, String terminal) throws PrivilegeViolationException, ResourceUnavailableException, MethodNotSupportedException, RawStateException { CallHolder ch = ((MuxCallId)call).getLeg(address); if (ch != null) { ch.getTpi().answerCall(ch.getCall(), address, terminal); } } /** * attachMedia method comment. */ public boolean attachMedia(net.sourceforge.gjtapi.CallId call, java.lang.String address, boolean onFlag) { CallHolder ch = ((MuxCallId)call).getLeg(address); if (ch != null) { return ch.getTpi().attachMedia(ch.getCall(), address, onFlag); } else return false; } /** * beep method comment. */ public void beep(net.sourceforge.gjtapi.CallId call) { Iterator it = ((MuxCallId)call).getCallHolders(); while (it.hasNext()) { CallHolder ch = (CallHolder)it.next(); ch.getTpi().beep(ch.getCall()); } } /** * Create a call from the given address and terminal to the remote address */ public CallId createCall(CallId id, String address, String term, String dest) throws ResourceUnavailableException, PrivilegeViolationException, InvalidPartyException, InvalidArgumentException, RawStateException, MethodNotSupportedException { MuxCallId logicalCall = (MuxCallId)id; CallHolder ch = logicalCall.getLeg(address); if (ch == null) { TelephonyProvider sub = this.getAddressSub(address); if (sub == null) throw new InvalidPartyException(InvalidPartyException.ORIGINATING_PARTY); else { ch = new CallHolder(sub.reserveCallId(address), sub); logicalCall.addCall(ch); } } return ch.getTpi().createCall(ch.getCall(), address, term, dest); } /** * find a logical CallId for the given low-level call. * Creation date: (2000-09-24 0:45:49) * @param subCall Low-level CallId * @param subTpi Low-level TelephonyProvider that holds the call, or null. */ MuxCallId findCall(CallId subCall, TelephonyProvider subTpi) { // Check for the logical CallId in the backward lookup table return this.findCall(new CallHolder(subCall, subTpi)); } /** * find a logical CallId for the given low-level call. * Creation date: (2000-09-24 0:45:49) * @param ch The CallHolder that represents the sub-call */ MuxCallId findCall(CallHolder ch) { // Check for the logical CallId in the backward lookup table return (MuxCallId)this.getLowToLogicalMap().get(ch); } /** * Free a media type resource from a sub-provider's terminal. * Creation date: (2000-03-09 10:52:06) * @param: terminal The terminal to be freed from media resources * @param type A flag telling the type of media to free. See RawProvider for static types. * @return: true if the media was freed. */ public boolean freeMedia(String terminal, int type) { TelephonyProvider rp = this.getTerminalSub(terminal); if (((RawCapabilities)this.getSubToCaps().get(rp)).allocateMedia) return rp.freeMedia(terminal, type); else return true; // The rawProvider guaranteed that it does not need this call. } /** * Take the values pointed to by the existing and new and return the greatest common denominator in String * format. * Creation date: (2000-03-14 15:20:48) * @author: Richard Deadman * @param existing An existing value, a String of form "txxx" or "fxxx". * @param update A new String representation of a boolean value. */ private Object gcd(Object existing, Object update) { if ((existing instanceof String && ((String)existing).length() > 0 && Character.toLowerCase(((String)existing).charAt(0)) == 't') || (existing instanceof Boolean && ((Boolean)existing).booleanValue())) // can stop now - already true return existing; // test if existing false and update exists -- replace with update if ((update instanceof String && ((String)update).length() > 0) || (update instanceof Boolean)) { return update; } // existing was false and update not valid return existing; } /** * Return the static set of addresses I manage, using lazy instantiation. * Creation date: (2000-02-22 15:32:00) * @author: Richard Deadman * @return The set of know addresses I manage */ public String[] getAddresses() throws ResourceUnavailableException { Map addMap = this.getAddToMap(); // test if we need to flush the addresses in if (this.addrFlag == MuxProvider.UNFLUSHED) { synchronized (this) { if (this.addrFlag == MuxProvider.UNFLUSHED) { // double check // ask each sub-provider Iterator it = this.getSubProviders().iterator(); while (it.hasNext()) { TelephonyProvider sub = (TelephonyProvider)it.next(); String[] subAddrs = null; try { subAddrs = sub.getAddresses(); } catch (ResourceUnavailableException rue) { this.addrFlag = MuxProvider.TOOBIG; // mark as not all available break; } int size = subAddrs.length; for (int i = 0; i < size; i++) { addMap.put(subAddrs[i], sub); } } if (this.addrFlag == MuxProvider.UNFLUSHED) this.addrFlag = FLUSHED; } } } // now test for too big if (this.addrFlag == MuxProvider.TOOBIG) { throw new ResourceUnavailableException(ResourceUnavailableException.UNKNOWN, "Some Sub-TelephonyProviders cannot return all addresses"); } // must now be set to FLUSHED return (String[])addMap.keySet().toArray(new String[0]); } /** * Return from the remote provider a set of address names associated with a terminal. */ public String[] getAddresses(String terminal) throws InvalidArgumentException { TelephonyProvider sub = this.getTerminalSub(terminal); String[] addrs = null; if (sub != null) { addrs = sub.getAddresses(terminal); } else { // broadcast boolean found = false; Iterator it = this.getSubProviders().iterator(); while (it.hasNext() && !found) { try { sub = (TelephonyProvider)it.next(); addrs = sub.getAddresses(terminal); // didn't throw exception -- success found = true; this.getTermToMap().put(terminal, sub); } catch (InvalidArgumentException iae) { // eat and move on to next provider } } if (!found) throw new InvalidArgumentException("No muxed subproviders know this Terminal: " + terminal); } // now map these to the sub-provider if (addrs != null) { Map addrMap = this.getAddToMap(); int size = addrs.length; for (int i = 0; i < size; i++) addrMap.put(addrs[i], sub); } // now return the set return addrs; } /** * Get the sub-provider mapped to by a certain Address * Creation date: (2000-03-09 12:26:30) * @author: Richard Deadman * @return The subprovider that handles the address * @param address A address name to find a subprovider for */ private TelephonyProvider getAddressSub(String address) { return (TelephonyProvider)this.getAddToMap().get(address); } /** * getAddressType method comment. */ public int getAddressType(java.lang.String name) { TelephonyProvider sub = this.getAddressSub(name); return sub.getAddressType(name); } /** * Private accessor for the Address to Subprovider map * Creation date: (2000-02-22 13:55:20) * @author: Richard Deadman * @return The map that maps addresses to sub-providers. */ private Map getAddToMap() { return addToSub; } /** * Find the call snapshot for a given call */ public CallData getCall(CallId id) { Iterator it = ((MuxCallId)id).getCallHolders(); Map connections = new HashMap(); // map of address to best ConnectionData holde -- local if found int state = Call.IDLE; // best known state. // now collect all connections and not most active call while (it.hasNext()) { CallHolder ch = (CallHolder)it.next(); TelephonyProvider sub = ch.getTpi(); CallData call = sub.getCall(ch.getCall()); // test if any branches are ACTIVE if (call.callState == Call.ACTIVE) state = call.callState; // now map these to the sub-provider if ((call != null) && (call.connections != null)) { // record all Addresses and Terminals int connSize = call.connections.length; for (int i = 0; i < connSize; i++) { ConnectionData cd = call.connections[i]; if (this.mapConnection(cd, sub)) { // note the local connections, overriding any remote entry // remote connections will be reported by other sub-providers connections.put(cd.address, cd); } else { // only add to connections if no local yet found if (!connections.containsKey(cd.address)) connections.put(cd.address, cd); } } } } // Merge the set of morphed sub-calls CallData mergedCall = null; int size = connections.size(); if (size > 0) { mergedCall = new CallData(id, state, (ConnectionData[])connections.values().toArray(new ConnectionData[size])); } // now return the set return mergedCall; } /** * Internal accessor for the set of logical calls I'm handling. * Creation date: (2000-02-22 16:06:01) * @author: Richard Deadman * @return The set of logical calls that may be bridged across more than 1 sub-provider calls. */ private Set getCalls() { return calls; } /** * Get a set of CallData snapshots for all calls that are attached to an address. * This will follow logical links to call legs in other sub-providers. */ public CallData[] getCallsOnAddress(String number) { TelephonyProvider sub = this.getAddressSub(number); if (sub != null) { CallData[] cd = sub.getCallsOnAddress(number); // now map these calls to the sub-provider if (cd != null) { int cdSize = cd.length; for (int i = 0; i < cdSize; i++) { // for each found call CallData call = cd[i]; // lazily create the logical call MuxCallId callId = this.noteCall(call.id, sub); // trace the call to other providers and store back in our array cd[i] = this.merge(call, this.traceCalls(callId, call.connections, new HashSet())); // record the sub-parts this.mapCall(callId, call, sub); } // return the traced calls return cd; } } // We found no calls return null; } /** * Get a set of CallData snapshots for all calls that are attached to a Terminal. * This will follow logical links to call legs in other sub-providers. */ public CallData[] getCallsOnTerminal(String name) { TelephonyProvider sub = this.getTerminalSub(name); if (sub != null) { CallData[] cd = sub.getCallsOnTerminal(name); // now map these calls to the sub-provider if (cd != null) { int cdSize = cd.length; for (int i = 0; i < cdSize; i++) { // for each found call CallData call = cd[i]; // lazily create the logical call MuxCallId callId = this.noteCall(call.id, sub); // trace the call to other providers and store back in our array cd[i] = this.merge(call, this.traceCalls(callId, call.connections, new HashSet())); // record the sub-parts this.mapCall(callId, call, sub); } // return the traced calls return cd; } } // We found no calls return null; } /** * Create the lowest common denominator of capabilities and return it. */ public java.util.Properties getCapabilities() { Properties merged = new Properties(); // load the set of capabilities Iterator subs = this.getSubToCaps().values().iterator(); while (subs.hasNext()) { Properties subProps = ((Properties)subs.next()); if (subProps != null) { // test each capability Iterator keys = subProps.keySet().iterator(); while (keys.hasNext()) { Object key = keys.next(); Object val = null; if (key.equals(Capabilities.THROTTLE) || key.equals(Capabilities.MEDIA) || key.equals(Capabilities.ALLOCATE_MEDIA) || key.equals(Capabilities.DYNAMIC_ADDRESSES) || ((key instanceof String) && (((String)key).endsWith(Capabilities.PD)))) val = this.gcd(merged.get(key), subProps.get(key)); else val = this.lcd(merged.get(key), subProps.get(key)); merged.put(key, val); } } } return merged; } /** * getDialledDigits method comment. */ public java.lang.String getDialledDigits(net.sourceforge.gjtapi.CallId id, java.lang.String address) { CallHolder ch = ((MuxCallId)id).getLeg(address); if (ch != null) { return ch.getTpi().getDialledDigits(ch.getCall(), address); } else return null; } /** * Insert the method's description here. * Creation date: (2000-09-25 16:27:57) * @return java.util.Map */ private java.util.Map getLowToLogicalMap() { return lowToLogicalMap; } /** * Find the sub-provider to forward the getPrivateData call to, and forward it. */ public Object getPrivateData(CallId call, String address, String terminal) { TelephonyProvider tp = null; // if single sub-provider identified CallId newCallId = null; Iterator it = null; // if call bridges sub-providers, or broadcast required // check if call ids the provider if (call != null) { if (address != null) { // Connection of TerminalConnection CallHolder ch = this.getSub(call, address); tp = ch.getTpi(); newCallId = ch.getCall(); } else { // Broadcast to all multiplexed Calls it = ((MuxCallId)call).getCallHolders(); Set set = new HashSet(); while (it.hasNext()) { CallHolder ch = (CallHolder)it.next(); Object o = ch.getTpi().getPrivateData(ch.getCall(), address, terminal); if (o != null) set.add(o); } return set.toArray(); } } else if (address != null) { // Call is null - just Address defined // check if the address will now do it tp = this.getAddressSub(address); } else if (terminal != null) { // Call is null - just Terminal defined // check if only the terminal will id it. tp = this.getTerminalSub(terminal); } else { // all null - Provider broadcast // all providers are required Set set = new HashSet(); it = this.getSubProviders().iterator(); while (it.hasNext()) { Object o = ((TelephonyProvider)it.next()).getPrivateData(call, address, terminal); if (o != null) set.add(o); } return set.toArray(); } // one provider found if (tp != null) { return tp.getPrivateData(newCallId, address, terminal); } // no providers found return null; } /** * Get the sub-provider for a logical call that is handling a particular address. * This allows a call being bridged across multiple sub-providers to find the correct sub-provider. * Creation date: (2000-03-09 12:26:30) * @author: Richard Deadman * @return The subprovider that handles the call's address * @param id A call id to find a subprovider for * @paran address An address that identifies a call-leg handled by one of the sub-providers. */ private CallHolder getSub(CallId id, String address) { return ((MuxCallId)id).getLeg(address); } /** * Internal accessor. * Creation date: (2000-02-22 15:37:42) * @author: Ricahrd Deadman * @return The set of RawProviders I delegate to. */ private Set getSubProviders() { return this.getSubToCaps().keySet(); } /** * Internal accessor * Creation date: (2000-02-22 16:06:01) * @author: Richard Deadman * @return The Sub-provider to RawCapabilites map. */ private Map getSubToCaps() { return subToCaps; } /** * Return the static set of terminals I manage, using lazy instantiation. * Creation date: (2000-02-22 15:32:00) * @author: Richard Deadman * @return The set of know addresses I manage */ public TermData[] getTerminals() throws ResourceUnavailableException { Map termMap = this.getTermToMap(); HashSet tdSet = this.termData; // test if we need to flush the addresses in if (this.termFlag == MuxProvider.UNFLUSHED) { synchronized (tdSet) { if (this.termFlag == MuxProvider.UNFLUSHED) { // double check // ask each sub-provider Iterator it = this.getSubProviders().iterator(); while (it.hasNext()) { TelephonyProvider sub = (TelephonyProvider)it.next(); TermData[] subTerms = null; try { subTerms = sub.getTerminals(); } catch (ResourceUnavailableException rue) { this.termFlag = MuxProvider.TOOBIG; // mark as not all available tdSet.clear(); // might as well clear the TermData holder this.termData = null; break; } int size = subTerms.length; for (int i = 0; i < size; i++) { TermData td = subTerms[i]; tdSet.add(td); termMap.put(td.terminal, sub); } } if (this.termFlag == MuxProvider.UNFLUSHED) this.termFlag = FLUSHED; } } } // now test for too big if (this.termFlag == MuxProvider.TOOBIG) { throw new ResourceUnavailableException(ResourceUnavailableException.UNKNOWN, "Some Sub-TelephonyProviders cannot return all addresses"); } // must now be set to FLUSHED return (TermData[])tdSet.toArray(new TermData[0]); } /** * Return from the remote provider a set of terminal names for an address. */ public TermData[] getTerminals(String address) throws InvalidArgumentException { TelephonyProvider sub = this.getAddressSub(address); TermData[] terms = null; if (sub != null) { terms = sub.getTerminals(address); } else { // broadcast boolean found = false; Iterator it = this.getSubProviders().iterator(); while (it.hasNext() && !found) { try { sub = (TelephonyProvider)it.next(); terms = sub.getTerminals(address); // didn't throw exception -- success found = true; this.getAddToMap().put(address, sub); } catch (InvalidArgumentException iae) { // eat and move on to next provider } } if (!found) throw new InvalidArgumentException("No muxed subproviders know this Address: " + address); } // now map these to the sub-provider if (terms != null) { Map termMap = this.getTermToMap(); int size = terms.length; for (int i = 0; i < size; i++) termMap.put(terms[i].terminal, sub); } // now return the set return terms; } /** * Get the sub-provider mapped to by a certain Terminal * Creation date: (2000-03-09 12:26:30) * @author: Richard Deadman * @return The subprovider that handles the terminal * @param address A terminal name to find a subprovider for */ private TelephonyProvider getTerminalSub(String terminal) { return (TelephonyProvider)this.getTermToMap().get(terminal); } /** * Private accessor for terminal map. * Creation date: (2000-03-09 10:37:55) * @author: Richard Deadman * @return The map that maps terminal names to sub-providers. */ private java.util.Map getTermToMap() { return termToSub; } /** * Send a hold message for a terminal to a remote provider. */ public void hold(CallId call, String address, String term) throws RawStateException, MethodNotSupportedException, PrivilegeViolationException, ResourceUnavailableException { TelephonyProvider sub = this.getAddressSub(address); if (sub != null) sub.hold(call, address, term); } /** * Load my properties and use to look-up my set of sub-providers * These properties could be used locally or sent to the server for the creation of a user-session. */ public void initialize(Map props) throws ProviderUnavailableException { Map m = null; Object value = null; // See if I also need to load the properties file boolean replace = false; if (props != null) { value = props.get("replace"); replace = net.sourceforge.gjtapi.capabilities.Capabilities.resolve(value); } if (replace) m = props; else { m = this.loadResources(MuxProvider.RESOURCE_NAME); if (props != null) m.putAll(props); } // Now load each sub-provider Map subCaps = this.getSubToCaps(); Iterator it = m.keySet().iterator(); while (it.hasNext()) { String key = (String)it.next(); if (key.startsWith(MuxProvider.PROVIDER_PREFIX)) { String em = (String)m.get(key); String cn = (String)m.get(MuxProvider.CLASS_PREFIX+em); String propfile = (String)m.get(MuxProvider.PROPS_PREFIX+em); // load the sub-provider TelephonyProvider rp = null; try { rp = ProviderFactory.createProvider((CoreTpi)Class.forName(cn).newInstance()); } catch (Exception ex) { throw new ProviderUnavailableException(); } rp.initialize(this.loadResources(propfile)); // add the provider's RawCapabilities subCaps.put(rp, rp.getCapabilities()); } } } /** * Delegate the call on to the appropriate sub-provider, if it registered for these calls. */ public boolean isMediaTerminal(String terminal) { TelephonyProvider rp = this.getTerminalSub(terminal); if (((RawCapabilities)this.getSubToCaps().get(rp)).allMediaTerminals) return true; // The rawProvider guaranteed that all terminals are media terminals. else return rp.isMediaTerminal(terminal); } /** * Determine if the given sub-provider throttles calls. */ /*private boolean isThrottled(TelephonyProvider rp) { Properties props = (Properties)this.getSubToCaps().get(rp); if (props != null) { return Capabilities.resolve(props.get(Capabilities.THROTTLE)); } return true; // Assume it does } */ /** * Tell the remote provider to join two calls */ public CallId join(CallId call1, CallId call2, String address, String terminal) throws RawStateException, InvalidArgumentException, MethodNotSupportedException, PrivilegeViolationException, ResourceUnavailableException { MuxCallId mCall1 = (MuxCallId)call1; MuxCallId mCall2 = (MuxCallId)call2; // find the common callholders CallHolder ch1 = mCall1.getLeg(address); CallHolder ch2 = mCall2.getLeg(address); // check that we have the same sub-provider TelephonyProvider sub = ch1.getTpi(); if (!sub.equals(ch2.getTpi())) { throw new InvalidArgumentException("Mux Join: No common TerminalConnection found"); } // tell the sub-provider to join the parts sub.join(ch1.getCall(), ch2.getCall(), address, terminal); // update the address to CallHolder set in call1 String[] adds = mCall2.getAddsForSubCall(ch2); int addSize = adds.length; for (int i = 0; i < addSize; i++) { mCall1.addLeg(adds[i], ch1); } // move the other calls from call23 to call1 Iterator it = mCall2.getCallHolders(); while (it.hasNext()) { CallHolder ch = (CallHolder)it.next(); if (!ch.equals(ch2)) { // add to mCall1 mCall1.addCall(ch); // update the address->CallHolder table in call 1 adds = mCall2.getAddsForSubCall(ch); addSize = adds.length; for (int i = 0; i < addSize; i++) { mCall1.addLeg(adds[i], ch); } // update the provider this.getLowToLogicalMap().put(ch, mCall1); } } // clear the old mux call mCall2.free(); // return the call return call1; } /** * Take the values pointed to by the existing and new and return the lowest common denominator in String * format. * Creation date: (2000-03-14 15:20:48) * @author: Richard Deadman * @param existing An existing value, a String of form "txxx" or "fxxx". * @param update A new String representation of a boolean value. */ private Object lcd(Object existing, Object update) { if ((existing instanceof String && ((String)existing).length() > 0 && Character.toLowerCase(((String)existing).charAt(0)) != 't') || (existing instanceof Boolean && !((Boolean)existing).booleanValue())) // can stop now - already false return existing; // test if existing true and update exists -- replace with update if ((update instanceof String && ((String)update).length() > 0) || (update instanceof Boolean)) { return update; } // existing was true and update not valid return existing; } /** * This method loads the Provider's values from a property file. * Creation date: (2000-02-22 10:11:41) * @author: Richard Deadman */ private Properties loadResources(String resName) { // We must be able to load the properties file Properties props = new Properties(); try { props.load(this.getClass().getResourceAsStream("/" + resName)); } catch (IOException ioe) { // return empty Properties } return props; } /** * Ensure that I have a logical CallId already for the given low-level call. * This will look for the call as an existing call, or if it doesn't exist: * <ol> * <li>Check if any other calls connect to this call * <li>Create a new logical call and trace it through any sub-providers for un-reported legs. * </ol> * Creation date: (2000-09-24 0:45:49) * @param subCall Low-level CallId * @param subTpi Low-level TelephonyProvider that holds the call. */ MuxCallId locateCall(CallId subCall, TelephonyProvider subTpi) { // Check for the logical CallId in the backward lookup table MuxCallId mci = this.findCall(subCall, subTpi); // test if the Logical CallId does not yet exist if (mci == null) { // first check if any other existing calls can be traced to this call CallData cd = subTpi.getCall(subCall); String[] remoteAddresses = cd.getRemoteAddresses(); int raSize = remoteAddresses.length; if (raSize > 0) { for (int i = 0; (i < raSize) && (mci == null); i++) { Iterator it = this.getCalls().iterator(); while (it.hasNext()) { MuxCallId testCall = (MuxCallId)it.next(); if (testCall.contains(new CallHolder(subCall, subTpi))) { mci = testCall; break; } } } } if (mci == null) { // otherwise create the call mci = this.noteCall(subCall, subTpi); // now trace this call through to link up other legs this.traceCalls(mci, cd.connections, new HashSet()); } } return mci; } /** * Ensure that a physical sub-call is properly recorded. * Creation date: (2000-09-27 15:49:07) */ private void mapCall(MuxCallId call, CallData callData, TelephonyProvider sub) { // Create and add a new CallHolder if needed CallHolder tmpHolder = new CallHolder(callData.id, sub); if (!call.contains(tmpHolder)) { call.addCall(tmpHolder); } ConnectionData[] connData = callData.connections; int cdSize = connData.length; // update the address and terminal mapping for (int i = 0; i < cdSize; i++) { this.mapConnection(connData[i], sub); } } /** * Given a Connection, ensure it is mapped into a sub-provider * @return true if the connection is in the sub-provider's address space */ private boolean mapConnection(ConnectionData cd, TelephonyProvider sub) { // now map these to the sub-provider if (cd.isLocal) { Map termMap = this.getTermToMap(); // record all Addresses and Terminals this.getAddToMap().put(cd.address, sub); // and the Connection's Terminals int tcSize = cd.terminalConnections.length; for (int j = 0; j < tcSize; j++) { TCData tcd = cd.terminalConnections[j]; termMap.put(tcd.terminal.terminal, sub); } } return cd.isLocal; } /** * Merge two CallData collections into one that represents the merged call. * Creation date: (2000-10-02 14:51:36) * @return The merged CallData representing the virtual call * @param cd1 The first call * @param cd2 The bridged call */ private CallData merge(CallData cd1, CallData cd2) { // null value test if (cd2 == null) { return cd1; } if (cd1 == null) { return cd2; } Set conns = new HashSet(); // the set of connections Set localAddresses = new HashSet(); // set of local addresses so we can remove remote later int i; // Create a set of Connections ConnectionData[] cdSet = cd1.connections; ConnectionData cd; for (i = 0; i < cdSet.length; i++) { cd = cdSet[i]; conns.add(cd); if (cd.isLocal) localAddresses.add(cd.address); } cdSet = cd2.connections; for (i = 0; i < cdSet.length; i++) { cd = cdSet[i]; conns.add(cd); if (cd.isLocal) localAddresses.add(cd.address); } // prune remote connections that have a local twin Iterator it = conns.iterator(); while (it.hasNext()) { cd = (ConnectionData)it.next(); if (!cd.isLocal && (localAddresses.contains(cd.address))) { it.remove(); } } // Now return the merged set of CallData return new CallData(cd1.id, cd1.callState, (ConnectionData[])conns.toArray(new ConnectionData[conns.size()])); } /** * Map an Address name from a sub-provider. * Creation date: (2000-08-09 0:38:22) * @author: Richard Deadman * @param id A name of a sub-provider's Address * @param sub The sub-provider that handles this address */ void noteAddress(String addressName, TelephonyProvider sub) { this.getAddToMap().put(addressName, sub); } /** * Ensure that I have a logical CallId already for the given low-level call. * This doesn't follow links since that may or may not require the return of CallData information. * Creation date: (2000-09-24 0:45:49) * @param subCall Low-level CallId * @param subTpi Low-level TelephonyProvider that holds the call. */ MuxCallId noteCall(CallId subCall, TelephonyProvider subTpi) { // Check for the logical CallId in the backward lookup table MuxCallId mci = this.findCall(subCall, subTpi); // test if the Logical CallId does not yet exist if (mci == null) { CallHolder ch = new CallHolder(subCall, subTpi); mci = new MuxCallId(); mci.addCall(ch); this.addCall(ch, mci); } return mci; } /** * Map a Terminal name from a sub-provider. * Creation date: (2000-08-09 0:38:22) * @author: Richard Deadman * @param id A name of a sub-provider's Terminal * @param sub The sub-provider that handles the terminal */ void noteTerminal(String terminalName, TelephonyProvider sub) { this.getTermToMap().put(terminalName, sub); } /** * Delegate the call onto the appropriate sub-provider */ public void play(String terminal, String[] streamIds, int offset, RTC[] rtcs, Dictionary optArgs) throws MediaResourceException { this.getTerminalSub(terminal).play(terminal, streamIds, offset, rtcs, optArgs); } /** * Delegate off to the appropriate sub-provider */ public void record(String terminal, String streamId, RTC[] rtcs, Dictionary optArgs) throws MediaResourceException { this.getTerminalSub(terminal).record(terminal, streamId, rtcs, optArgs); } /** * Create a logical CallId to handle any sub-provider muxing and register it with * the local lookup table. * Creation date: (2000-09-25 11:45:34) * @return net.sourceforge.gjtapi.raw.mux.MuxCallId * @param id net.sourceforge.gjtapi.CallId * @param sub net.sourceforge.gjtapi.TelephonyProvider */ private MuxCallId registerCall(CallId id, TelephonyProvider sub) { MuxCallId cid = new MuxCallId(); cid.addCall(id, sub); this.getCalls().add(cid); return cid; } /** * Tell the remote provider to release an address from a call. */ public void release(String address, CallId call) throws PrivilegeViolationException, ResourceUnavailableException, MethodNotSupportedException, RawStateException { CallHolder ch = this.getSub(call, address); if (ch != null) { ch.getTpi().release(address, ch.getCall()); } } /** * Release any CallId's that I have reserved. */ public void releaseCallId(CallId id) { MuxCallId mcid = (MuxCallId)id; // This better cast... Iterator chs = mcid.getCallHolders(); while (chs.hasNext()) { CallHolder call = (CallHolder)chs.next(); call.getTpi().releaseCallId(call.getCall()); } this.removeCall(mcid); } /** * Remove a MuxCallId and its backwards lookup holder to my call set. * Creation date: (2000-09-26 16:13:08) * @param mci net.sourceforge.gjtapi.raw.mux.MuxCallId */ boolean removeCall(MuxCallId mci) { boolean removed = this.getCalls().remove(mci); if (removed) { // logical call existed... Iterator it = this.getLowToLogicalMap().entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); if (entry.getValue().equals(mci)) { it.remove(); } } } return removed; } /** * Forward removeListener to remote provider. */ public void removeListener(TelephonyListener rl) { Iterator it = this.getSubProviders().iterator(); while (it.hasNext()) { TelephonyProvider rp = ((TelephonyProvider)it.next()); rp.removeListener(new MuxListener(rl, this, rp)); } } /** * Delegate off to the appropriate subprovider */ public void reportCallsOnAddress(String address, boolean flag) throws ResourceUnavailableException, InvalidArgumentException { TelephonyProvider rp = this.getAddressSub(address); if (rp != null) { if (((RawCapabilities)this.getSubToCaps().get(rp)).throttle) rp.reportCallsOnAddress(address, flag); } else { // poll each raw provider - should never do this since all addresses should have been recorded. Iterator it = this.getSubProviders().iterator(); while (it.hasNext()) { try { rp = (TelephonyProvider)it.next(); rp.reportCallsOnAddress(address, flag); // no exception - store rp this.getAddToMap().put(address, rp); } catch (InvalidArgumentException iae) { // eat this one and try next } } // didn't find any to handle the address throw new InvalidArgumentException("No muxed providers handle address: " + address); } } /** * Delegate off to the appropriate subprovider */ public void reportCallsOnTerminal(String terminal, boolean flag) throws ResourceUnavailableException, InvalidArgumentException { TelephonyProvider rp = this.getTerminalSub(terminal); if (rp != null) { if (((RawCapabilities)this.getSubToCaps().get(rp)).throttle) rp.reportCallsOnTerminal(terminal, flag); } else { // poll each raw provider - should never do this since all terminals should have been recorded. Iterator it = this.getSubProviders().iterator(); while (it.hasNext()) { try { rp = (TelephonyProvider)it.next(); rp.reportCallsOnAddress(terminal, flag); // no exception - store rp this.getTermToMap().put(terminal, rp); } catch (InvalidArgumentException iae) { // eat this one and try next } } // didn't find any to handle the address throw new InvalidArgumentException("No muxed providers handle terminal: " + terminal); } } /** * Reserve a call id on the remote server. */ public CallId reserveCallId(String address) throws InvalidArgumentException { TelephonyProvider rp = this.getAddressSub(address); CallId id = null; if (rp != null) { id = rp.reserveCallId(address); } else { // broadcast - we shouldn't see an Address that we haven't already recorded boolean found = false; Iterator it = this.getSubProviders().iterator(); while (it.hasNext() && !found) { try { rp = (TelephonyProvider)it.next(); id = rp.reserveCallId(address); found = true; } catch (InvalidArgumentException iae) { // eat and move on to next provider } } if (!found) throw new InvalidArgumentException("No muxed subproviders know this Address: " + address); } if (id != null) { // map the call id in our local table return this.registerCall(id, rp); } else return null; } /** * Delegate off to the appropriate subprovider */ public RawSigDetectEvent retrieveSignals(String terminal, int num, Symbol[] patterns, RTC[] rtcs, Dictionary optArgs) throws MediaResourceException { return this.getTerminalSub(terminal).retrieveSignals(terminal, num, patterns, rtcs, optArgs); } /** * Find the sub-provider to forward the sendPrivateData call to, and forward it. */ public Object sendPrivateData(CallId call, String address, String terminal, Object data) { TelephonyProvider tp = null; // used for unicast messages CallId newCallId = null; Iterator it = null; // used of broadcast messages // check if call ids the provider if (call != null) { if (address != null) { // Connection of TerminalConnection CallHolder ch = this.getSub(call, address); tp = ch.getTpi(); newCallId = ch.getCall(); } else { // Broadcast to all multiplexed Calls it = ((MuxCallId)call).getCallHolders(); Set set = new HashSet(); while (it.hasNext()) { CallHolder ch = (CallHolder)it.next(); Object o = ch.getTpi().sendPrivateData(ch.getCall(), address, terminal, data); if (o != null) set.add(o); } return set.toArray(); } } else if (address != null) { // check if the address will now do it tp = this.getAddressSub(address); } else if (terminal != null) { // check if only the terminal will id it. tp = this.getTerminalSub(terminal); } else { // all providers are required Set set = new HashSet(); it = this.getSubProviders().iterator(); while (it.hasNext()) { Object o = ((TelephonyProvider)it.next()).sendPrivateData(null, null, null, data); if (o != null) set.add(o); } return set.toArray(); } // one provider found if (tp != null) { return tp.sendPrivateData(newCallId, address, terminal, data); } // no providers found return null; } /** * Delegate off to the appropriate subprovider */ public void sendSignals(String terminal, Symbol[] syms, RTC[] rtcs, Dictionary optArgs) throws MediaResourceException { this.getTerminalSub(terminal).sendSignals(terminal, syms, rtcs, optArgs); } /** * We assume that the providers are at both ends of the range */ public void setLoadControl(java.lang.String startAddr, java.lang.String endAddr, double duration, double admissionRate, double interval, int[] treatment) throws javax.telephony.MethodNotSupportedException { if (startAddr != null) { this.getAddressSub(startAddr).setLoadControl(startAddr, endAddr, duration, admissionRate, interval, treatment); } if ((endAddr != null) && (!startAddr.equals(endAddr))) { this.getAddressSub(startAddr).setLoadControl(startAddr, endAddr, duration, admissionRate, interval, treatment); } } /** * Find the sub-provider to forward the setPrivateData call to, and forward it. */ public void setPrivateData(CallId call, String address, String terminal, Object data) { TelephonyProvider tp = null; // used for unicast messages CallId newCallId = null; Iterator it = null; // used for broadcast messages // check if call ids the provider if (call != null) { if (address != null) { // Connection of TerminalConnection CallHolder ch = this.getSub(call, address); tp = ch.getTpi(); newCallId = ch.getCall(); } else { // Broadcast to all multiplexed Calls it = ((MuxCallId)call).getCallHolders(); while (it.hasNext()) { CallHolder ch = (CallHolder)it.next(); ch.getTpi().setPrivateData(ch.getCall(), address, terminal, data); } } } else if (address != null) { // check if the address will now do it tp = this.getAddressSub(address); } else if (terminal != null) { // check if only the terminal will id it. tp = this.getTerminalSub(terminal); } else { // all providers are required it = this.getSubProviders().iterator(); while (it.hasNext()) { ((TelephonyProvider)it.next()).setPrivateData(null, null, null, data); } } // one provider found if (tp != null) { tp.setPrivateData(newCallId, address, terminal, data); } } /** * Tell the remote provider to shutdown. It may choose to ignore me. */ public void shutdown() { Iterator it = this.getSubProviders().iterator(); while (it.hasNext()) { ((TelephonyProvider)it.next()).shutdown(); } } /** * Pass on the stop media request to the correct sub-provider */ public void stop(String terminal) { this.getTerminalSub(terminal).stop(terminal); } /** * Delegate off to the appropriate sub-provider */ public boolean stopReportingCall(CallId call) { Iterator it = ((MuxCallId)call).getCallHolders(); boolean result = true; // assume that they all will unless one declines while (it.hasNext()) { CallHolder ch = (CallHolder)it.next(); result = result && ch.getTpi().stopReportingCall(ch.getCall()); } return result; } /** * Describe myself * @return a string representation of the receiver */ public String toString() { StringBuffer sb = new StringBuffer("Raw Multiplexor Provider managing: "); Iterator it = this.getSubProviders().iterator(); while (it.hasNext()) { TelephonyProvider rp = (TelephonyProvider)it.next(); sb.append(" ").append(rp.toString()); } return sb.toString(); } /** * This method assists in knitting call legs together that exist in two sub-providers * but that represent parts of one logical call. * Creation date: (2000-09-27 15:49:07) * @return net.sourceforge.gjtapi.CallData * @param muxCall The mux virtual call that holds all sub-call holders. * @param from The address set the call leg comes from. Used to help distinguish multiple calls at the destination. * @param to The address where the call leg should be. * @param state the Connection state of the call leg. Used to help distinguish leg that both come from the same source. * @param addressSet The set of currently found locally-resolved addresses. Used to stop cyclical recursion. */ private CallData traceCall(MuxCallId muxCall, String[] from, String to, int state, HashSet addressSet) { CallData found = null; int fromSize = from.length; // find the sub-provider and ask it for all calls on the address TelephonyProvider prov = this.getAddressSub(to); if (prov != null) { CallData[] subCalls = prov.getCallsOnAddress(to); int size = subCalls.length; // look at all sub-calls for (int i = 0; i < size; i++) { CallData cd = subCalls[i]; int connSize = cd.connections.length; for (int j = 0; j < connSize; j++) { ConnectionData conn = cd.connections[j]; for (int k = 0; k < fromSize; k++) { if ((conn.address.equals(from[k])) && (conn.connState == state)) { if (found == null) { found = cd; break; // don't check any more connections } else { // two possible calls -- return null to indicate failure return null; } } } if (found != null) break; // break out of connections } } // now we need to check if we should shut off reporting for the other returned Calls boolean shutOff = false; for (int i = 0; (i < size) && (!shutOff); i++) { CallData cd = subCalls[i]; CallId subCallId = cd.id; if ((!cd.equals(found)) && (this.findCall(subCallId, prov) == null)) { prov.stopReportingCall(subCallId); shutOff = true; } } // If we turned any call reporting off, we need to turn call reporting off for // future calls on this address if (shutOff) { // only catch non-Runtime exceptions -- we want others to blow things up Exception e = null; try { prov.reportCallsOnAddress(to, false); prov.getCall(found.id); // to ensure this is still being reported } catch (InvalidArgumentException iae) { e = iae; } catch (ResourceUnavailableException rue) { e = rue; } if (e != null) { System.out.println("Internal logic error during multiplexor call tracing:"); e.printStackTrace(); } } if (found != null) { // Add the new local addresses to the addressSet ConnectionData[] connData = found.connections; int cdSize = connData.length; for (int i = 0; i < cdSize; i++) { if (connData[i].isLocal) { addressSet.add(connData[i].address); } } // Create and add a new CallHolder if needed if (muxCall.getLeg(to) == null) { muxCall.addCall(found.id, prov); // update the address and terminal mapping for (int i = 0; i < cdSize; i++) { this.mapConnection(connData[i], prov); } } // finally, recursively merge in any other bridged connections found = this.merge(found, this.traceCalls(muxCall, connData, addressSet)); } } return found; } /** * This method assists in knitting call legs together that exist in two sub-providers by merging all remote connections in the logical mux call. * Only remote addresses not yet recorded in addressSet are followed. * but that represent parts of one logical call. * Creation date: (2000-09-27 15:49:07) * @return net.sourceforge.gjtapi.CallData * @param muxCall The mux virtual call that holds all sub-call holders. * @param addressSet The set of currently found locally-resolved addresses. Used to stop cyclical recursion. */ private CallData traceCalls(MuxCallId muxCall, ConnectionData[] conns, HashSet addressSet) { CallData found = null; int cdSize = conns.length; // finally, recursively merge in any other bridged connections for (int i = 0; i < cdSize; i++) { if (!conns[i].isLocal) { ConnectionData conn = conns[i]; // ensure we don't get in a recursive loop if (!addressSet.contains(conn.address)) found = this.merge(found, this.traceCall(muxCall, found.getLocalAddresses(), conn.address, conn.connState, addressSet)); } } return found; } /** * Pas on the triggerRTC method to the correct sub-provider. */ public void triggerRTC(String terminal, Symbol action) { this.getTerminalSub(terminal).triggerRTC(terminal, action); } /** * Tell the remote provider to unhold a terminal from a call */ public void unHold(CallId call, String address, String term) throws RawStateException, MethodNotSupportedException, PrivilegeViolationException, ResourceUnavailableException { CallHolder ch = this.getSub(call, address); if (ch != null) { ch.getTpi().unHold(ch.getCall(), address, term); } } }