package net.sourceforge.gjtapi;
/*
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.
*/
/**
* This is the GJTAPI implementation of the JTAPI peer.
* It's main capabilities are its abilities to plug in the appropriate
* low-level proxy (delegate)
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.telephony.JtapiPeer;
import javax.telephony.Provider;
import javax.telephony.ProviderUnavailableException;
import net.sourceforge.gjtapi.raw.CoreTpi;
import net.sourceforge.gjtapi.raw.ProviderFactory;
public class GenericJtapiPeer implements JtapiPeer, ResourceFinder {
// dictionary of raw providers
private final static String RESOURCE_NAME = "GenericResources.props";
private final static String DEFAULT_PROVIDER = "DefaultProvider";
private final static String PROV_PREFIX = "PROVIDER_";
private final static String PROV_CLASS_KEY = "ProviderClass";
private final static String PROV_PROP_SUFFIX = ".gjtapi";
private final static String PROV_AUTOPROPERTY = "provider.gjtapi";
/** key for the property for disconnecting a Connection when a MediaService releases. */
private final String MEDIA_RELEASE_DISCONNECT = "mediaReleaseDisconnect";
/** System property key for looking up resource directory. */
private final static String RESOURCE_DIR = "net.sourceforge.gjtapi.resourceDir";
private Properties properties = null;
private final Hashtable<String, Object> providers = new Hashtable<String, Object>();
/** Should we disconnect the Connection when the MediaService releases (JTAPI spec. says yes). */
private boolean disconnectMediaOnRelease = true;
/**
* Create a version of the Peer and load the provider's Resource Bundle
**/
public GenericJtapiPeer() {
super();
// read generic peers from my property file
this.loadResources();
}
/**
* If no provider class name is passed to the JtapiPeer.getProvider() method, return the
* default provider.
*<P>Subclases may override this to provide their own default provider lookup mechanism.
* Creation date: (2000-04-05 10:01:48)
* @author: Richard Deadman
* @return The class name of the RawProvider implementor class to hook in if no class name
* or provider lookup name is passed to the JtapiPeer.getProvider() method.
*/
protected String getDefaultProvider() {
return (String) this.getProperties().get(GenericJtapiPeer.DEFAULT_PROVIDER);
}
/**
* Get the fully qualified class name for the Peer
**/
public String getName() {
return this.getClass().getName();
}
/**
* Return a Property set for the Peer that maps provider names to class names and
* details the default provider.
* Creation date: (2000-02-04 10:29:21)
* @author:
* @return java.util.Properties
*/
private java.util.Properties getProperties() {
return properties;
}
/**
* Returns an instance of a <CODE>Provider</CODE> object given a string
* argument which
* contains the desired service name. Optional arguments may also be
* provided in this string, with the following format:
* <p>
* < service name > ; arg1 = val1; arg2 = val2; ...
* <p>
* Where < service name > is not optional, and each optional argument
* pair which follows is separated by a semi-colon. The keys for these
* arguments is implementation specific, except for two standard-defined
* keys:
* <OL>
* <LI>login: provides the login user name to the Provider.
* <LI>passwd: provides a password to the Provider.
* </OL>
* <p>
* If the argument is null, this method returns some default Provider as
* determined by the JtapiPeer object. The returned Provider is in the
* <CODE>Provider.OUT_OF_SERVICE</CODE> state.
* <p>
* The service name may be one of:
* <ol>
* <li>null to load the default provider indicated in the GenericResources.props file.
* <li>A service name that is mapped in the GenericResources.props file to a class file.
* <li>A service name that is mapped in the GenericResources.props file to a service properties file.
* <li>A class name for a class that implements TelephonyProvider.
* </ol>
* <B>Post-conditions:</B>
* <OL>
* <LI>this.getProvider().getState() == Provider.OUT_OF_SERVICE
* </OL>
* @param providerString The name of the desired service plus an optional
* arguments. The provider name parsed from string may be looked up straight or used as a class itself.
* @return An instance of the Provider object.
* @exception ProviderUnavailableException Indicates a Provider corresponding
* to the given string is unavailable.
*/
@SuppressWarnings("unchecked")
public Provider getProvider(String params) throws ProviderUnavailableException {
// parse the parameters
String[] parts = this.split(params);
String provName = null;
if (parts.length > 0)
provName = this.parseService(parts[0]);
// If providerName is null, look for the default
if (provName == null) {
provName = this.getDefaultProvider();
}
// look up provider in loaded Properties
Object providerHandle = this.getProviders().get(provName);
InputStream is = null;
String providerFileName = null;
if (providerHandle instanceof URL) {
try {
is = ((URL)providerHandle).openStream();
} catch (IOException ioe) {
ProviderUnavailableException pue = new ProviderUnavailableException(ProviderUnavailableException.CAUSE_UNKNOWN, "Could not open resource: " + providerHandle);
pue.setStackTrace(ioe.getStackTrace());
throw pue;
}
} else {
providerFileName = (String)providerHandle;
// if the name is not in the resources list
if (providerFileName == null)
providerFileName = provName;
is = this.findResource(providerFileName);
if (is == null) {
// try to load from the file system
File file = new File(providerFileName);
if(file.exists() && file.canRead() && file.isFile()) {
try {
is = new FileInputStream(file);
} catch (FileNotFoundException fnfe) {
// fall through
}
}
}
}
String providerClassName = null;
// See if this is a Property file or a call name
Properties provProps = new Properties();
if (is != null) {
try {
provProps.load(is);
if (provProps.containsKey(GenericJtapiPeer.PROV_CLASS_KEY))
providerClassName = (String)provProps.get(GenericJtapiPeer.PROV_CLASS_KEY);
} catch (IOException ioe) {
// No provider property file by that name, assume it is a class
is = null;
}
} else {
providerClassName = providerFileName;
}
// test if we've located the provider class
if (providerClassName == null)
// try using the name from the provider string
providerClassName = provName;
// parse the remaining name-value pairs and add to the provider properties set
provProps.putAll(this.parse(parts, 1));
// load raw provider
TelephonyProvider rp = null;
try {
ClassLoader loader = getClass().getClassLoader();
Class<CoreTpi> providerClass =
(Class<CoreTpi>) loader.loadClass(providerClassName);
rp = ProviderFactory.createProvider(providerClass.newInstance());
} catch (Exception e) {
throw new ProviderUnavailableException("Error loading raw provider "
+ providerClassName + ": " + e.getMessage());
}
// initialize
rp.initialize(provProps);
if (rp instanceof ResourceConfigurable) {
final ResourceConfigurable configurable =
(ResourceConfigurable) rp;
configurable.initializeResources(provProps, this);
}
// Create the high-level provider and return it
GenericProvider gp = new GenericProvider(provName, rp, provProps);
gp.setDisconnectOnMediaRelease(this.disconnectMediaOnRelease);
return gp;
}
/**
* Return the dictionary of internally known raw providers
* Creation date: (2000-02-11 11:12:15)
* @author: Richard Deadman
* @return java.util.Hashtable
*/
private Hashtable<String, Object> getProviders() {
return providers;
}
/**
* Return the list of known raw provider names
*
* @return An array or known provider names.
**/
public String[] getServices() {
return (String[])this.getProviders().keySet().toArray(new String[0]);
}
/**
* This method loads the Peer's initial values, including names of RawProvider classes.
* Creation date: (2000-02-04 10:11:41)
* @author: Richard Deadman
*/
private void loadResources() {
// If this fails, the Properties object will be left null, which will signal getProvider() to throw
// an exception.
Properties props = new Properties();
try {
props.load(this.findResource(GenericJtapiPeer.RESOURCE_NAME));
this.setProperties(props);
} catch (IOException ioe) {
// don't set properties then...
return;
}
// now look for providers and move them to the providers dictionary
Hashtable<String, Object> provs = this.getProviders();
Enumeration<Object> e = props.keys();
while (e.hasMoreElements()) {
String key = (String)e.nextElement();
if (key.startsWith(GenericJtapiPeer.PROV_PREFIX)) {
String name = key.substring(GenericJtapiPeer.PROV_PREFIX.length());
provs.put(name, (String)props.get(key));
}
// now see if we should change the MediaService.release() behaviour
if (key.equals(this.MEDIA_RELEASE_DISCONNECT)) {
String disconnect = (String)props.get(key);
if (disconnect != null && disconnect.length() > 0 && Character.toLowerCase(disconnect.charAt(0)) != 't')
this.disconnectMediaOnRelease = false;
}
}
// Now look for any providers property files in the resource directory with the right suffix
// or in the jar files
this.findAutoProviders(provs);
}
/**
* Parse a set of name-value pairs into a dictionary.
* So if a String equals "a = b", "a" is the key and "b" the value.
*
* @param parts The array of name-value pairs
* @param index The array element to start parsing at
* @return A dictionary with the parse values
**/
private Map<String, String> parse(String[] parts, int index) {
Hashtable<String, String> tab = new Hashtable<String, String>();
for (int i = index; i < parts.length; i++) {
StringTokenizer tok = new StringTokenizer(parts[i], "=");
if (tok.countTokens() > 1) {
tab.put(tok.nextToken().trim(), tok.nextToken().trim());
}
}
return tab;
}
/**
* Parse the service name away from the angle brackets, if they exist.
* This is to clean up any mis-reading of the JTAPI spec.
**/
String parseService(String args) {
String ret = args;
int start = args.indexOf('<');
if (start >= 0) {
int end = args.indexOf('>', start);
if (end > start) {
ret = args.substring(start + 1, end);
}
}
return ret.trim();
}
/**
* Set the properties file
* Creation date: (2000-02-04 10:29:21)
* @author: Richard Deadman
* @param newProperties The Properties object containing Provider names and other initializations
*/
private void setProperties(java.util.Properties newProperties) {
properties = newProperties;
}
/**
* Split a parameter string into its parts
**/
private String[] split(String line) {
if (line == null)
return new String[0];
StringTokenizer tok = new StringTokenizer(line, ";");
String ret[] = new String[tok.countTokens()];
for (int i = 0; i < ret.length; i++) {
ret[i] = tok.nextToken().trim();
}
return ret;
}
/**
* Find a resource. All resources used to be only looked up on the
* classpath in the base "package", but this method refactores the
* search so that it can also use an environment variable.
* <P>The algorithm looks for the named resource
* <ol>
* <li>In the directory specified by the net.sourceforge.gjtapi.resourceDir
* <li>In the application's current working directory
* <li>In the classloader base package.
* </ul>
* @param resourceName The name of the resource that we want to find
* @return An InputStream for reading the resource, or null if none is found
* @author rdeadman
*
*/
public InputStream findResource(String resourceName) {
// first we see if we should check for a resource directory
String resourceDir = System.getProperty(RESOURCE_DIR);
if (resourceDir != null) {
File resource = new File(resourceDir + File.separator + resourceName);
if (resource.exists() && resource.isFile()) {
try {
return new FileInputStream(resource);
} catch (FileNotFoundException fnfe) {
// should never get here unless the resource is not readable -- let the class loader look for it then
}
}
}
// it wasn't on the Resource_Dir path, let's check the current working directory
File resource = new File(System.getProperty("user.dir") + File.separator + resourceName);
if (resource.exists() && resource.isFile()) {
try {
return new FileInputStream(resource);
} catch (FileNotFoundException fnfe) {
// should never get here unless the resource is not readable -- let the class loader look for it then
}
}
// we didn't find the resource in the resource directory or the working directory
// now let's check the classpath
return GenericJtapiPeer.class.getResourceAsStream("/" + resourceName);
}
/**
* Find providers in the resource directories based on the naming suffix
* @param providers The name-class lookup table
*/
private void findAutoProviders(Hashtable<String, Object> providers) {
// load all resource files ending in .gjtapi on the resource or user directory
// first we see if we should check for a resource directory
HashSet<File> directories = new HashSet<File>();
String resourceDir = System.getProperty(RESOURCE_DIR);
if (resourceDir != null) {
File resourceDirectory = new File(resourceDir);
if (resourceDirectory.exists() && resourceDirectory.isDirectory()) {
directories.add(resourceDirectory);
}
}
// now add the home directory
File userDirectory = new File(System.getProperty("user.dir"));
if (userDirectory.exists() && userDirectory.isDirectory()) {
directories.add(userDirectory);
}
Iterator<File> it = directories.iterator();
while(it.hasNext()) {
File resourceDirectory = (File)it.next();
// find all the auto-load files
File[] resources = resourceDirectory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(PROV_PROP_SUFFIX);
}
});
// load each file
for(int i = 0; i<resources.length; i++) {
try {
File resource = resources[i];
String fullName = resource.getName();
String name = fullName.substring(0, fullName.length()-PROV_PROP_SUFFIX.length());
// only add if name is unique
if (!providers.containsKey(name)) {
providers.put(name, resource.getCanonicalPath());
}
} catch (IOException ioe) {
// no-op
}
}
}
// Now look for provider.gjtapi classes on the classpath
try {
Enumeration<URL> cpResources = ClassLoader.getSystemResources(PROV_AUTOPROPERTY);
while(cpResources.hasMoreElements()) {
URL cpResource = (URL)cpResources.nextElement();
// find the name as the jar part of the resource
String jarPath = cpResource.getPath();
if(jarPath != null) {
// check if we have a jar file or a local file
int tailLocation = jarPath.lastIndexOf(".jar!/" + PROV_AUTOPROPERTY);
if(tailLocation != -1) {
jarPath = jarPath.substring(0, tailLocation+4);
// find the name of the jar file
// test for fully qualified name
tailLocation = jarPath.lastIndexOf('/');
if(tailLocation == -1) {
// or relative name
tailLocation = jarPath.lastIndexOf(':');
}
if(tailLocation != -1) {
jarPath = jarPath.substring(tailLocation+1);
}
}
else {
// find the suffix of the file
tailLocation = jarPath.lastIndexOf('/');
if(tailLocation != -1) {
jarPath = jarPath.substring(tailLocation+1);
tailLocation = jarPath.lastIndexOf(PROV_PROP_SUFFIX);
if(tailLocation != -1) {
jarPath = jarPath.substring(0, jarPath.length()-PROV_PROP_SUFFIX.length());
}
}
}
}
// Now add if new
if ((jarPath != null) && (!providers.containsKey(jarPath))) {
providers.put(jarPath, cpResource);
}
}
} catch (IOException ioe) {
// no-op
}
}
}