mirror of https://github.com/sipwise/jitsi.git
Implements support for handling SIP URIs as launch parameters
Adds a lock mechanism to prevent from running multiple instances of SIP Communicator
Adds a mechanism for a second instance of SC to pass its launch
parameters to a one that's already running.
Adds a DefaultSecurityAuthority class (in use by the systray and SIP URI handler)
cusax-fix
parent
45288c7024
commit
88a0036d18
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.argdelegation;
|
||||
|
||||
import net.java.sip.communicator.service.gui.*;
|
||||
import net.java.sip.communicator.service.version.*;
|
||||
import net.java.sip.communicator.util.launchutils.*;
|
||||
|
||||
import org.osgi.framework.*;
|
||||
|
||||
/**
|
||||
* Activates the <tt>ArgDelegationService</tt> and registers a URI delegation
|
||||
* peer with the util package arg manager so that we would be notified when the
|
||||
* application receives uri arguments.
|
||||
*
|
||||
* @author Emil Ivov
|
||||
*/
|
||||
public class ArgDelegationActivator
|
||||
implements BundleActivator
|
||||
{
|
||||
/**
|
||||
* A reference to the bundle context that is currently in use.
|
||||
*/
|
||||
private static BundleContext bundleContext = null;
|
||||
|
||||
/**
|
||||
* A reference to the delegation peer implementation that is currently
|
||||
* handling uri arguments.
|
||||
*/
|
||||
private UriDelegationPeerImpl delegationPeer = null;
|
||||
|
||||
/**
|
||||
* A reference to the <tt>UIService</tt> currently in use in
|
||||
* SIP Communicator.
|
||||
*/
|
||||
private static UIService uiService = null;
|
||||
|
||||
/**
|
||||
* Starts the arg delegation bundle and registers the delegationPeer with
|
||||
* the util package URI manager.
|
||||
*
|
||||
* @param bc a reference to the currently active bundle context.
|
||||
*/
|
||||
@Override
|
||||
public void start(BundleContext bc) throws Exception
|
||||
{
|
||||
bundleContext = bc;
|
||||
delegationPeer = new UriDelegationPeerImpl(bc);
|
||||
bc.addServiceListener(delegationPeer);
|
||||
|
||||
//register our instance of delegation peer.
|
||||
LaunchArgHandler.getInstance().setDelegationPeer(delegationPeer);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets the delegation peer instance that we set when we start this
|
||||
* bundle.
|
||||
*
|
||||
* @param bc an instance of the currently valid bundle context.
|
||||
*/
|
||||
@Override
|
||||
public void stop(BundleContext bc) throws Exception
|
||||
{
|
||||
uiService = null;
|
||||
bc.removeServiceListener(delegationPeer);
|
||||
delegationPeer = null;
|
||||
LaunchArgHandler.getInstance().setDelegationPeer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to an UIService implementation currently registered
|
||||
* in the bundle context or null if no such implementation was found.
|
||||
*
|
||||
* @return a reference to an UIService implementation currently registered
|
||||
* in the bundle context or null if no such implementation was found.
|
||||
*/
|
||||
public static UIService getUIService()
|
||||
{
|
||||
if(uiService == null)
|
||||
{
|
||||
ServiceReference versionServiceReference
|
||||
= bundleContext.getServiceReference(
|
||||
UIService.class.getName());
|
||||
uiService = (UIService)bundleContext
|
||||
.getService(versionServiceReference);
|
||||
}
|
||||
return uiService;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.argdelegation;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.osgi.framework.*;
|
||||
|
||||
import net.java.sip.communicator.service.argdelegation.*;
|
||||
import net.java.sip.communicator.service.gui.*;
|
||||
import net.java.sip.communicator.service.version.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
import net.java.sip.communicator.util.launchutils.*;
|
||||
|
||||
|
||||
/**
|
||||
* Implements the <tt>UriDelegationPeer</tt> interface from our argument handler
|
||||
* utility. We use this handler to relay arguments to URI handlers that have
|
||||
* been registered from other services such as the SIP provider for example.
|
||||
*
|
||||
* @author Emil Ivov
|
||||
*/
|
||||
public class UriDelegationPeerImpl
|
||||
implements UriDelegationPeer, ServiceListener
|
||||
{
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(UriDelegationPeerImpl.class);
|
||||
|
||||
/**
|
||||
* The list of uriHandlers that we are currently aware of.
|
||||
*/
|
||||
private Map<String, UriHandler> uriHandlers
|
||||
= new Hashtable<String, UriHandler>();
|
||||
|
||||
/**
|
||||
* Creates an instance of this peer and scans <tt>bundleContext</tt> for all
|
||||
* existing <tt>UriHandler</tt>s
|
||||
*
|
||||
* @param bundleContext a reference to a currently valid instance of a
|
||||
* bundle context.
|
||||
*/
|
||||
public UriDelegationPeerImpl(BundleContext bundleContext)
|
||||
{
|
||||
ServiceReference[] uriHandlerRefs = null;
|
||||
|
||||
synchronized (uriHandlers)
|
||||
{
|
||||
try
|
||||
{
|
||||
uriHandlerRefs = bundleContext.getServiceReferences(
|
||||
UriHandler.class.getName(), null);
|
||||
}
|
||||
catch (InvalidSyntaxException exc)
|
||||
{
|
||||
// this shouldn't happen because we aren't using a filter
|
||||
// but let's log just the same.
|
||||
logger.info("An error occurred while retrieving UriHandlers",
|
||||
exc);
|
||||
return;
|
||||
}
|
||||
|
||||
if(uriHandlerRefs == null)
|
||||
{
|
||||
//none URI handlers are registered at this point. Some might
|
||||
//come later.
|
||||
return;
|
||||
}
|
||||
|
||||
for (ServiceReference uriHandlerRef : uriHandlerRefs)
|
||||
{
|
||||
UriHandler uriHandler = (UriHandler) bundleContext
|
||||
.getService(uriHandlerRef);
|
||||
uriHandlers.put(uriHandler.getProtocol(), uriHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for <tt>UriHandlers</tt> that are registered in the bundle
|
||||
* context after we had started so that we could add them to the list
|
||||
* of currently known handlers.
|
||||
*
|
||||
* @param event the event containing the newly (un)registered service.
|
||||
*/
|
||||
public void serviceChanged(ServiceEvent event)
|
||||
{
|
||||
synchronized (uriHandlers)
|
||||
{
|
||||
BundleContext bc = event.getServiceReference().getBundle()
|
||||
.getBundleContext();
|
||||
|
||||
Object service = bc.getService(event.getServiceReference());
|
||||
|
||||
//we are only interested in UriHandler-s
|
||||
if(!(service instanceof UriHandler) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getType() == ServiceEvent.MODIFIED
|
||||
|| event.getType() == ServiceEvent.REGISTERED)
|
||||
{
|
||||
UriHandler uriHandler = (UriHandler) bc.getService(event
|
||||
.getServiceReference());
|
||||
|
||||
uriHandlers.put(uriHandler.getProtocol(), uriHandler);
|
||||
}
|
||||
else if (event.getType() == ServiceEvent.UNREGISTERING)
|
||||
{
|
||||
UriHandler uriHandler = (UriHandler) bc.getService(event
|
||||
.getServiceReference());
|
||||
|
||||
if(uriHandlers.get(uriHandler.getProtocol()) == uriHandler)
|
||||
uriHandlers.remove(uriHandler.getProtocol());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Relays <tt>uirArg</tt> to the corresponding handler or shows an error
|
||||
* message in case no handler has been registered for the corresponding
|
||||
* protocol.
|
||||
*
|
||||
* @param uriArg the uri that we've been passed and that we'd like to
|
||||
* delegate to the corresponding provider.
|
||||
*/
|
||||
public void handleUri(String uriArg)
|
||||
{
|
||||
logger.trace("Handling URI: " + uriArg);
|
||||
//first parse the uri and determine the scheme/protocol
|
||||
//the parsing is currently a bit oversimplified so we'd probably need
|
||||
//to revisit it at some point.
|
||||
int colonIndex = uriArg.indexOf(":");
|
||||
|
||||
if( colonIndex == -1)
|
||||
{
|
||||
//no scheme, we don't know how to handle the URI
|
||||
ArgDelegationActivator.getUIService().getPopupDialog()
|
||||
.showMessagePopupDialog(
|
||||
"Could not determine how to handle: " + uriArg
|
||||
+ ".\nNo protocol scheme found.",
|
||||
"Error handling URI",
|
||||
PopupDialog.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
String scheme = uriArg.substring(0, colonIndex);
|
||||
|
||||
UriHandler handler = uriHandlers.get(scheme);
|
||||
|
||||
//if handler is null we need to tell the user.
|
||||
if(handler == null)
|
||||
{
|
||||
logger.trace("Couldn't open " + uriArg
|
||||
+ "No handler found for protocol"+ scheme);
|
||||
ArgDelegationActivator.getUIService().getPopupDialog()
|
||||
.showMessagePopupDialog(
|
||||
"\"" + scheme + "\" URIs are currently not supported.",
|
||||
"Error handling URI",
|
||||
PopupDialog.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
//we're all set. let's do the handling now.
|
||||
try
|
||||
{
|
||||
handler.handleUri(uriArg);
|
||||
}
|
||||
//catch every possible exception
|
||||
catch(Throwable thr)
|
||||
{
|
||||
ArgDelegationActivator.getUIService().getPopupDialog()
|
||||
.showMessagePopupDialog(
|
||||
"Error handling " + uriArg,
|
||||
"Error handling URI",
|
||||
PopupDialog.ERROR_MESSAGE);
|
||||
logger.error("Failed to handle \""+ uriArg +"\"", thr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
Bundle-Activator: net.java.sip.communicator.impl.argdelegation.ArgDelegationActivator
|
||||
Bundle-Name: Argument Delegation
|
||||
Bundle-Description: A bundle that delegates invocation arguments to register handler services
|
||||
Bundle-Vendor: sip-communicator.org
|
||||
Bundle-Version: 0.0.1
|
||||
Import-Package: org.osgi.framework,
|
||||
net.java.sip.communicator.util,
|
||||
net.java.sip.communicator.util.launchutils,
|
||||
net.java.sip.communicator.service.configuration,
|
||||
net.java.sip.communicator.service.argdelegation,
|
||||
net.java.sip.communicator.service.protocol,
|
||||
net.java.sip.communicator.service.protocol.event,
|
||||
net.java.sip.communicator.service.gui
|
||||
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.gui.main.login;
|
||||
|
||||
import net.java.sip.communicator.impl.gui.*;
|
||||
import net.java.sip.communicator.service.gui.*;
|
||||
import net.java.sip.communicator.service.protocol.*;
|
||||
|
||||
/**
|
||||
* Utility class that can be used in cases where components other than the main
|
||||
* user interface may need to launch provider registration. At the time I am
|
||||
* writing this, the <tt>DefaultSecurityAuthority</tt> is being used by the
|
||||
* systray and
|
||||
*
|
||||
* @author Emil Ivov
|
||||
*/
|
||||
public class DefaultSecurityAuthority
|
||||
implements SecurityAuthority
|
||||
{
|
||||
private boolean isUserNameEditable = false;
|
||||
|
||||
/**
|
||||
* The provider that this authority would be responsible for.
|
||||
*/
|
||||
private ProtocolProviderService provider = null;
|
||||
|
||||
/**
|
||||
* Creates this authority for a particular provider.
|
||||
*/
|
||||
public DefaultSecurityAuthority(ProtocolProviderService provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to login to the protocol providers
|
||||
*
|
||||
* @param realm the realm that the credentials are needed for
|
||||
* @param userCredentials the values to propose the user by default
|
||||
* @return The Credentials associated with the speciefied realm
|
||||
*/
|
||||
public UserCredentials obtainCredentials(
|
||||
String realm,
|
||||
UserCredentials userCredentials)
|
||||
{
|
||||
return obtainCredentials( realm,
|
||||
userCredentials,
|
||||
SecurityAuthority.AUTHENTICATION_REQUIRED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to login to the protocol providers
|
||||
*
|
||||
* @param realm the realm that the credentials are needed for
|
||||
* @param userCredentials the values to propose the user by default
|
||||
* @param reasonCode the reason for which we're asking for credentials
|
||||
* @return The Credentials associated with the speciefied realm
|
||||
*/
|
||||
public UserCredentials obtainCredentials(
|
||||
String realm,
|
||||
UserCredentials userCredentials,
|
||||
int reasonCode)
|
||||
{
|
||||
ExportedWindow loginWindow
|
||||
= GuiActivator.getUIService()
|
||||
.getAuthenticationWindow(provider,
|
||||
realm,
|
||||
userCredentials,
|
||||
isUserNameEditable);
|
||||
|
||||
loginWindow.setVisible(true);
|
||||
|
||||
return userCredentials;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the userNameEditable property, which should indicate to the
|
||||
* implementations of this interface if the user name could be changed
|
||||
* by user or not.
|
||||
*
|
||||
* @param isUserNameEditable indicates if the user name could be changed
|
||||
* by user in the implementation of this interface.
|
||||
*/
|
||||
public void setUserNameEditable(boolean isUserNameEditable)
|
||||
{
|
||||
this.isUserNameEditable = isUserNameEditable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the user name is currently editable, i.e. could be
|
||||
* changed by user or not.
|
||||
*
|
||||
* @return <tt>true</tt> if the user name could be changed and
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
public boolean isUserNameEditable()
|
||||
{
|
||||
return isUserNameEditable;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.protocol.sip;
|
||||
|
||||
import java.net.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import javax.sip.*;
|
||||
import javax.sip.address.*;
|
||||
import javax.sip.header.*;
|
||||
import javax.sip.message.*;
|
||||
|
||||
import net.java.sip.communicator.service.protocol.*;
|
||||
import net.java.sip.communicator.service.protocol.event.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
/**
|
||||
* Allows SIP communicator to create SIP accounts without a registrar. We use
|
||||
* this class as a replacement of the SipRegistrarConnection for accounts that
|
||||
* do not have a configured registrar.
|
||||
*
|
||||
* @author Emil Ivov
|
||||
*/
|
||||
public class SipRegistrarlessConnection
|
||||
extends SipRegistrarConnection
|
||||
{
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(SipRegistrarlessConnection.class);
|
||||
|
||||
/**
|
||||
* A reference to the sip provider that created us.
|
||||
*/
|
||||
private ProtocolProviderServiceSipImpl sipProvider = null;
|
||||
|
||||
/**
|
||||
* Keeps our current registration state.
|
||||
*/
|
||||
private RegistrationState currentRegistrationState
|
||||
= RegistrationState.UNREGISTERED;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class.
|
||||
*
|
||||
* @param sipProviderCallback a reference to the
|
||||
* ProtocolProviderServiceSipImpl instance that created us.
|
||||
*
|
||||
*/
|
||||
public SipRegistrarlessConnection(
|
||||
ProtocolProviderServiceSipImpl sipProviderCallback)
|
||||
{
|
||||
this.sipProvider = sipProviderCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply sets the state of the connection to REGISTERED without doing
|
||||
* anything else.
|
||||
*
|
||||
* @throws OperationFailedException never thrown
|
||||
*/
|
||||
@Override
|
||||
void register()
|
||||
throws OperationFailedException
|
||||
{
|
||||
setRegistrationState(RegistrationState.REGISTERED,
|
||||
RegistrationStateChangeEvent.REASON_USER_REQUEST,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply sets the state of the connection to UNREGISTERED without doing
|
||||
* anything else.
|
||||
*
|
||||
* @throws OperationFailedException never thrown.
|
||||
*/
|
||||
@Override
|
||||
public void unregister() throws OperationFailedException
|
||||
{
|
||||
setRegistrationState(RegistrationState.UNREGISTERED,
|
||||
RegistrationStateChangeEvent.REASON_USER_REQUEST,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of this connection.
|
||||
*
|
||||
* @return a RegistrationState instance indicating the state of our
|
||||
* registration with the corresponding registrar.
|
||||
*/
|
||||
@Override
|
||||
public RegistrationState getRegistrationState()
|
||||
{
|
||||
return currentRegistrationState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets our registration state to <tt>newState</tt> and dispatches an event
|
||||
* through the protocol provider service impl.
|
||||
* <p>
|
||||
* @param newState a reference to the RegistrationState that we're currently
|
||||
* detaining.
|
||||
* @param reasonCode one of the REASON_XXX error codes specified in
|
||||
* {@link RegistrationStateChangeEvent}.
|
||||
* @param reason a reason String further explaining the reasonCode.
|
||||
*/
|
||||
@Override
|
||||
public void setRegistrationState(RegistrationState newState,
|
||||
int reasonCode,
|
||||
String reason)
|
||||
{
|
||||
if( currentRegistrationState.equals(newState) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegistrationState oldState = currentRegistrationState;
|
||||
this.currentRegistrationState = newState;
|
||||
|
||||
sipProvider.fireRegistrationStateChanged(
|
||||
oldState, newState, reasonCode, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of this connection's registrar.
|
||||
*
|
||||
* @return the InetAddress of our registrar server.
|
||||
*/
|
||||
@Override
|
||||
public InetAddress getRegistrarAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
return InetAddress.getByAddress("2001:1890:1112:1::20",
|
||||
new byte[]{(byte) 20, (byte) 01, (byte) 18, (byte) 90,
|
||||
(byte) 11, (byte) 11, (byte) 12, (byte) 00,
|
||||
(byte) 01, (byte) 00, (byte) 00, (byte) 00,
|
||||
(byte) 00, (byte) 00, (byte) 00, (byte) 20});
|
||||
} catch (UnknownHostException ex)
|
||||
{
|
||||
logger.error("Failed to generate a dummy registrar addr", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the listening point that should be used for communication with our
|
||||
* current registrar.
|
||||
*
|
||||
* @return the listening point that should be used for communication with our
|
||||
* current registrar.
|
||||
*/
|
||||
@Override
|
||||
public ListeningPoint getRegistrarListeningPoint()
|
||||
{
|
||||
return sipProvider.getDefaultListeningPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this connection instance
|
||||
* instance including information that would permit to distinguish it among
|
||||
* other sip listeners when reading a log file.
|
||||
* <p>
|
||||
* @return a string representation of this operation set.
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String className = getClass().getName();
|
||||
try
|
||||
{
|
||||
className = className.substring(className.lastIndexOf('.') + 1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// we don't want to fail in this method because we've messed up
|
||||
//something with indexes, so just ignore.
|
||||
}
|
||||
return className + "-[dn=" + sipProvider.getOurDisplayName()
|
||||
+" addr="+sipProvider.getOurSipAddress() + "]";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,524 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.impl.protocol.sip;
|
||||
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.osgi.framework.*;
|
||||
|
||||
import net.java.sip.communicator.impl.systray.*;
|
||||
import net.java.sip.communicator.service.argdelegation.*;
|
||||
import net.java.sip.communicator.service.gui.*;
|
||||
import net.java.sip.communicator.service.protocol.*;
|
||||
import net.java.sip.communicator.service.protocol.event.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
/**
|
||||
* The sip implementation of the URI handler. This class handles sip URIs by
|
||||
* trying to establish a call to them.
|
||||
*
|
||||
* @author Emil Ivov
|
||||
*/
|
||||
public class UriHandlerSipImpl
|
||||
implements UriHandler,
|
||||
ServiceListener
|
||||
|
||||
{
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(UriHandlerSipImpl.class);
|
||||
|
||||
/**
|
||||
* The protocol provider factory that created us.
|
||||
*/
|
||||
private ProtocolProviderFactory protoFactory = null;
|
||||
|
||||
/**
|
||||
* A reference to the OSGi registration we create with this handler.
|
||||
*/
|
||||
private ServiceRegistration ourServiceRegistration = null;
|
||||
|
||||
/**
|
||||
* The object that we are using to synchronize our service registration.
|
||||
*/
|
||||
private Object registrationLock = new Object();
|
||||
|
||||
/**
|
||||
* Creates an instance of this uri handler, so that it would start handling
|
||||
* URIs by passing them to the providers registered by <tt>protoFactory</tt>.
|
||||
*
|
||||
* @param parentProvider the provider that created us.
|
||||
*
|
||||
* @throws NullPointerException if <tt>protoFactory</tt> is <tt>null</tt>.
|
||||
*/
|
||||
protected UriHandlerSipImpl(ProtocolProviderFactory protoFactory)
|
||||
throws NullPointerException
|
||||
{
|
||||
if(protoFactory == null)
|
||||
{
|
||||
throw new NullPointerException(
|
||||
"The ProtocolProviderFactory that a UriHandler is created with "
|
||||
+ " cannot be null.");
|
||||
}
|
||||
|
||||
this.protoFactory = protoFactory;
|
||||
|
||||
//we listen for service events so that we can disable ourselves in
|
||||
//case our protocol factory decides to leave.
|
||||
SipActivator.bundleContext.addServiceListener(this);
|
||||
|
||||
registerHandlerService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this UriHandler with the bundle context so that it could
|
||||
* start handling URIs
|
||||
*/
|
||||
private void registerHandlerService()
|
||||
{
|
||||
synchronized(registrationLock)
|
||||
{
|
||||
if (ourServiceRegistration != null)
|
||||
{
|
||||
// ... we are already registered (this is probably
|
||||
// happening during startup)
|
||||
return;
|
||||
}
|
||||
|
||||
Hashtable<String, String> registrationProperties
|
||||
= new Hashtable<String, String>();
|
||||
|
||||
registrationProperties.put(UriHandler.PROTOCOL_PROPERTY,
|
||||
getProtocol());
|
||||
|
||||
ourServiceRegistration = SipActivator.bundleContext
|
||||
.registerService(UriHandler.class.getName(), this,
|
||||
registrationProperties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this UriHandler from the bundle context.
|
||||
*/
|
||||
private void unregisterHandlerService()
|
||||
{
|
||||
synchronized(registrationLock)
|
||||
{
|
||||
ourServiceRegistration.unregister();
|
||||
ourServiceRegistration = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the protocol that this handler is responsible for or "sip" in
|
||||
* other words.
|
||||
*
|
||||
* @return the "sip" string to indicate that this handler is responsible
|
||||
* for handling "sip" uris.
|
||||
*/
|
||||
public String getProtocol()
|
||||
{
|
||||
return "sip";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the specified URI and creates a call with the currently active
|
||||
* telephony operation set.
|
||||
*
|
||||
* @param uri the SIP URI that we have to call.
|
||||
*/
|
||||
public void handleUri(String uri)
|
||||
{
|
||||
ProtocolProviderService provider;
|
||||
try
|
||||
{
|
||||
provider = selectHandlingProvider(uri);
|
||||
}
|
||||
catch (OperationFailedException exc)
|
||||
{
|
||||
// The operation has been canceled by the user. Bail out.
|
||||
logger.trace("User canceled handling of uri " + uri);
|
||||
return;
|
||||
}
|
||||
|
||||
//if provider is null then we need to tell the user to create an account
|
||||
if(provider == null)
|
||||
{
|
||||
showErrorMessage(
|
||||
"You need to configure at least one "
|
||||
+ "SIP" +" account \n"
|
||||
+"to be able to call " + uri,
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
OperationSetBasicTelephony telephonyOpSet
|
||||
= (OperationSetBasicTelephony) provider
|
||||
.getOperationSet(OperationSetBasicTelephony.class);
|
||||
|
||||
try
|
||||
{
|
||||
telephonyOpSet.createCall(uri);
|
||||
}
|
||||
catch (OperationFailedException exc)
|
||||
{
|
||||
//make sure that we prompt for registration only if it is really
|
||||
//required by the provider.
|
||||
if(exc.getErrorCode()
|
||||
== OperationFailedException.PROVIDER_NOT_REGISTERED)
|
||||
{
|
||||
promptForRegistration(uri, provider);
|
||||
}
|
||||
showErrorMessage("Failed to create a call to " + uri, exc);
|
||||
}
|
||||
catch (ParseException exc)
|
||||
{
|
||||
showErrorMessage(
|
||||
uri + " does not appear to be a valid SIP address",
|
||||
exc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs the user that they need to be registered before placing calls
|
||||
* and asks them whether they would like us to do it for them.
|
||||
*
|
||||
* @param uri the uri that the user would like us to call after registering.
|
||||
* @param provider the provider that we may have to reregister.
|
||||
*/
|
||||
private void promptForRegistration(String uri,
|
||||
ProtocolProviderService provider)
|
||||
{
|
||||
int answer = SipActivator.getUIService()
|
||||
.getPopupDialog().showConfirmPopupDialog(
|
||||
"You need to be online in order to make a call and your "
|
||||
+ "account is currently offline. Do want to connect now?",
|
||||
"Account is currently offline",
|
||||
PopupDialog.YES_NO_OPTION);
|
||||
|
||||
if(answer == PopupDialog.YES_OPTION)
|
||||
{
|
||||
new ProtocolRegistrationThread(uri, provider).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The point of implementing a service listener here is so that we would
|
||||
* only register our own uri handling service and thus only handle URIs
|
||||
* while the factory is available as an OSGi service. We remove ourselves
|
||||
* when our factory unregisters its service reference.
|
||||
*
|
||||
* @param event the OSGi <tt>ServiceEvent</tt>
|
||||
*/
|
||||
public void serviceChanged(ServiceEvent event)
|
||||
{
|
||||
Object sourceService = SipActivator.bundleContext
|
||||
.getService(event.getServiceReference());
|
||||
|
||||
//ignore anything but our protocol factory.
|
||||
if( ! (sourceService instanceof ProtocolProviderFactorySipImpl)
|
||||
|| (sourceService != protoFactory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(event.getType() == ServiceEvent.REGISTERED)
|
||||
{
|
||||
//our factory has just been registered as a service ...
|
||||
registerHandlerService();
|
||||
}
|
||||
else if(event.getType() == ServiceEvent.UNREGISTERING)
|
||||
{
|
||||
//our factory just died - seppuku.
|
||||
unregisterHandlerService();
|
||||
}
|
||||
else if(event.getType() == ServiceEvent.MODIFIED)
|
||||
{
|
||||
//we don't care.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the <tt>UIService</tt> to show an error <tt>message</tt> and log
|
||||
* and <tt>exception</tt>.
|
||||
*
|
||||
* @param message the message that we'd like to show to the user.
|
||||
* @param exc the exception that we'd like to log
|
||||
*/
|
||||
private void showErrorMessage(String message, Exception exc)
|
||||
{
|
||||
SipActivator.getUIService().getPopupDialog().showMessagePopupDialog(
|
||||
message,
|
||||
"Failed to create call!",
|
||||
PopupDialog.ERROR_MESSAGE);
|
||||
logger.error(message, exc);
|
||||
}
|
||||
|
||||
/**
|
||||
* We use this class when launching a provider registration by ourselves in
|
||||
* order to track for provider registration states and retry uri handling,
|
||||
* once the provider is registered.
|
||||
*
|
||||
*/
|
||||
private class ProtocolRegistrationThread
|
||||
extends Thread
|
||||
implements SecurityAuthority,
|
||||
RegistrationStateChangeListener
|
||||
{
|
||||
|
||||
private boolean isUserNameEditable = false;
|
||||
private ProtocolProviderService handlerProvider = null;
|
||||
|
||||
/**
|
||||
* The URI that we'd need to re-call.
|
||||
*/
|
||||
private String uri = null;
|
||||
|
||||
/**
|
||||
* Configures this thread register our parent provider and re-attempt
|
||||
* connection to the specified <tt>uri</tt>.
|
||||
*
|
||||
* @param uri the uri that we need to handle.
|
||||
* @param handlerProvider the provider that we are going to make
|
||||
* register and that we are going to use to handle the <tt>uri</tt>.
|
||||
*/
|
||||
public ProtocolRegistrationThread(
|
||||
String uri,
|
||||
ProtocolProviderService handlerProvider)
|
||||
{
|
||||
super("UriHandlerProviderRegistrationThread:uri=" + uri);
|
||||
this.uri = uri;
|
||||
this.handlerProvider = handlerProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to login to the protocol providers
|
||||
*
|
||||
* @param realm the realm that the credentials are needed for
|
||||
* @param userCredentials the values to propose the user by default
|
||||
* @return The Credentials associated with the speciefied realm
|
||||
*/
|
||||
public UserCredentials obtainCredentials(
|
||||
String realm,
|
||||
UserCredentials userCredentials)
|
||||
{
|
||||
return obtainCredentials( realm,
|
||||
userCredentials,
|
||||
SecurityAuthority.AUTHENTICATION_REQUIRED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to login to the protocol providers
|
||||
*
|
||||
* @param realm the realm that the credentials are needed for
|
||||
* @param userCredentials the values to propose the user by default
|
||||
* @param reasonCode the reason for which we're asking for credentials
|
||||
* @return The Credentials associated with the speciefied realm
|
||||
*/
|
||||
public UserCredentials obtainCredentials(
|
||||
String realm,
|
||||
UserCredentials userCredentials,
|
||||
int reasonCode)
|
||||
{
|
||||
ExportedWindow loginWindow
|
||||
= SystrayActivator.getUIService()
|
||||
.getAuthenticationWindow(handlerProvider,
|
||||
realm,
|
||||
userCredentials,
|
||||
isUserNameEditable);
|
||||
|
||||
loginWindow.setVisible(true);
|
||||
|
||||
return userCredentials;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the userNameEditable property, which should indicate to the
|
||||
* implementations of this interface if the user name could be changed
|
||||
* by user or not.
|
||||
*
|
||||
* @param isUserNameEditable indicates if the user name could be changed
|
||||
* by user in the implementation of this interface.
|
||||
*/
|
||||
public void setUserNameEditable(boolean isUserNameEditable)
|
||||
{
|
||||
this.isUserNameEditable = isUserNameEditable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the user name is currently editable, i.e. could be
|
||||
* changed by user or not.
|
||||
*
|
||||
* @return <tt>true</tt> if the user name could be changed and
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
public boolean isUserNameEditable()
|
||||
{
|
||||
return isUserNameEditable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the registration process, ads this class as a registration
|
||||
* listener and then tries to rehandle the uri this thread was initiaded
|
||||
* with.
|
||||
*/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
handlerProvider.addRegistrationStateChangeListener(this);
|
||||
|
||||
try
|
||||
{
|
||||
handlerProvider.register(this);
|
||||
}
|
||||
catch (OperationFailedException exc)
|
||||
{
|
||||
logger.error("Failed to manually register provider.");
|
||||
logger.warn(exc.getMessage(), exc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the parent provider passes into the registration state, the method
|
||||
* re-handles the URI that this thread was initiated with. The method
|
||||
* would only rehandle the uri if the event shows successful
|
||||
* registration. It would ignore intermediate states such as
|
||||
* REGISTERING. Disconnection and failure events would simply cause this
|
||||
* listener to remove itself from the list of registration listeners.
|
||||
*
|
||||
* @param evt the <tt>RegistrationStateChangeEvent</tt> that this
|
||||
* thread was initiated with.
|
||||
*/
|
||||
public void registrationStateChanged(RegistrationStateChangeEvent evt)
|
||||
{
|
||||
if (evt.getNewState() == RegistrationState.REGISTERED)
|
||||
{
|
||||
Thread uriRehandleThread = new Thread()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
handleUri(uri);
|
||||
}
|
||||
};
|
||||
|
||||
uriRehandleThread.setName("UriRehandleThread:uri="+uri);
|
||||
uriRehandleThread.start();
|
||||
}
|
||||
|
||||
//we're only interested in a single event so we stop listening
|
||||
//(unless this was a REGISTERING notification)
|
||||
if(evt.getNewState() == RegistrationState.REGISTERING)
|
||||
return;
|
||||
|
||||
handlerProvider.removeRegistrationStateChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default provider that we are supposed to handle URIs through
|
||||
* or null if there aren't any. Depending on the implementation this
|
||||
* method may require user intervention so make sure you don't rely on
|
||||
* a quick outcome when calling it.
|
||||
*
|
||||
* @param uri the uri that we'd like to handle with the provider that we are
|
||||
* about to select.
|
||||
*
|
||||
* @return the provider that we should handle URIs through.
|
||||
*
|
||||
* @throws OperationFailedException with code <tt>OPERATION_CANCELED</tt>
|
||||
* if the users.
|
||||
*/
|
||||
public ProtocolProviderService selectHandlingProvider(String uri)
|
||||
throws OperationFailedException
|
||||
{
|
||||
ArrayList<AccountID> registeredAccounts
|
||||
= protoFactory.getRegisteredAccounts();
|
||||
|
||||
//if we don't have any providers - return null.
|
||||
if(registeredAccounts.size() == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
//if we only have one provider - select it
|
||||
if(registeredAccounts.size() == 1)
|
||||
{
|
||||
ServiceReference providerReference
|
||||
= protoFactory.getProviderForAccount(registeredAccounts.get(0));
|
||||
|
||||
ProtocolProviderService provider = (ProtocolProviderService)
|
||||
SipActivator.getBundleContext().getService(providerReference);
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
||||
//otherwise - ask the user.
|
||||
ArrayList<ProviderComboBoxEntry> providers
|
||||
= new ArrayList<ProviderComboBoxEntry>();
|
||||
for (AccountID accountID : registeredAccounts)
|
||||
{
|
||||
ServiceReference providerReference
|
||||
= protoFactory.getProviderForAccount(accountID);
|
||||
|
||||
ProtocolProviderService provider = (ProtocolProviderService)
|
||||
SipActivator.getBundleContext().getService(providerReference);
|
||||
|
||||
providers.add( new ProviderComboBoxEntry( provider ) );
|
||||
}
|
||||
|
||||
Object result = SipActivator.getUIService().getPopupDialog()
|
||||
.showInputPopupDialog(
|
||||
"Please select the account that you would like \n"
|
||||
+ "to use to call "
|
||||
+ uri,
|
||||
"Account Selection",
|
||||
PopupDialog.OK_CANCEL_OPTION,
|
||||
providers.toArray(),
|
||||
providers.get(0));
|
||||
|
||||
if( result == null)
|
||||
{
|
||||
throw new OperationFailedException(
|
||||
"Operation cancelled",
|
||||
OperationFailedException.OPERATION_CANCELED);
|
||||
}
|
||||
|
||||
return ((ProviderComboBoxEntry)result).provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that we use to wrap providers before showing them to the user
|
||||
* through a selection popup dialog from the UIService.
|
||||
*/
|
||||
private class ProviderComboBoxEntry
|
||||
{
|
||||
public ProtocolProviderService provider;
|
||||
|
||||
public ProviderComboBoxEntry(ProtocolProviderService provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human readable <tt>String</tt> representing the
|
||||
* provider encapsulated by this class.
|
||||
*
|
||||
* @return a human readable string representing the provider.
|
||||
*/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return provider.getAccountID().getAccountAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
|
||||
package net.java.sip.communicator.service.argdelegation;
|
||||
|
||||
/**
|
||||
* This interface is meant to be implemented by all bundles that wish to handle
|
||||
* URIs passed as invocation arguments.
|
||||
*
|
||||
* @author Emil Ivov <emcho at sip-communicator.org>
|
||||
*/
|
||||
public interface UriHandler
|
||||
{
|
||||
/**
|
||||
* The name of the property that we use in the service registration
|
||||
* properties to store a protocol name when registering <tt>UriHandler</tt>s
|
||||
*/
|
||||
public static final String PROTOCOL_PROPERTY = "ProtocolName";
|
||||
|
||||
/**
|
||||
* Returns the protocol that this handler is responsible for.
|
||||
*/
|
||||
public String getProtocol();
|
||||
|
||||
/**
|
||||
* Handles/opens the URI.
|
||||
*
|
||||
* @param uri the URI that the handler has to open.
|
||||
*/
|
||||
public void handleUri(String uri);
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
Bundle-Name: Argument Delegation Service
|
||||
Bundle-Description: A service that allows bundles to register as handlers for specific command line arguments
|
||||
Bundle-Vendor: sip-communicator.org
|
||||
Bundle-Version: 0.0.1
|
||||
Export-Package: net.java.sip.communicator.service.argdelegation
|
||||
@ -0,0 +1,404 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
|
||||
package net.java.sip.communicator.util.launchutils;
|
||||
|
||||
import java.util.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* The <tt>LauncherArgHandler</tt> class handles invocation arguments that have
|
||||
* been passed to us when running SIP Communicator. The class supports a fixed
|
||||
* set of options and also allows for registration of delegates.
|
||||
*
|
||||
* @author Emil Ivov <emcho at sip-communicator.org>
|
||||
*/
|
||||
public class LaunchArgHandler
|
||||
{
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(LaunchArgHandler.class);
|
||||
|
||||
/**
|
||||
* The name of the property that contains the location of the SC
|
||||
* configuration directory.
|
||||
*/
|
||||
private static final String PNAME_SC_HOME_DIR_LOCATION =
|
||||
"net.java.sip.communicator.SC_HOME_DIR_LOCATION";
|
||||
|
||||
/**
|
||||
* The name of the property that contains the name of the SC configuration
|
||||
* directory.
|
||||
*/
|
||||
private static final String PNAME_SC_HOME_DIR_NAME =
|
||||
"net.java.sip.communicator.SC_HOME_DIR_NAME";
|
||||
|
||||
/**
|
||||
* Returned by the <tt>handleArgs</tt> methods when the arguments that have
|
||||
* been parsed do not require for SIP Communicator to be started and the
|
||||
* Launcher is supposed to exit. That could happen when "SIP Communicator"
|
||||
* is launched with a --version argument for example or when trying to
|
||||
* run the application after an instance was already launched.
|
||||
*/
|
||||
public static final int ACTION_EXIT = 0;
|
||||
|
||||
/**
|
||||
* Returned by the <tt>handleArgs</tt> methods when all arguments have been
|
||||
* parsed and the SIP Communicator launch can continue.
|
||||
*/
|
||||
public static final int ACTION_CONTINUE = 1;
|
||||
|
||||
/**
|
||||
* Returned by the <tt>handleArgs</tt> method when parsing the arguments
|
||||
* has failed or if no arguments were passed and an instance of SC was
|
||||
* already launched. If this is the code returned by handleArgs, then the
|
||||
* <tt>getErrorCode</tt> method would return an error code indicating what
|
||||
* the error was.
|
||||
*/
|
||||
public static final int ACTION_ERROR = 2;
|
||||
|
||||
/**
|
||||
* Returned by the <tt>handleArgs</tt> methods when all arguments have been
|
||||
* successfully parsed and one of them indicates that the user has requested
|
||||
* a multi instance launch.
|
||||
*/
|
||||
public static final int ACTION_CONTINUE_MULTIINSTANCE = 3;
|
||||
|
||||
/**
|
||||
* The error code returned when we couldn't parse one of the options.
|
||||
*/
|
||||
public static final int ERROR_CODE_UNKNOWN_ARG = 1;
|
||||
|
||||
/**
|
||||
* The error code returned when we try to launch SIP Communicator while
|
||||
* there is already a running instance and there were no arguments that we
|
||||
* forward to that instance.
|
||||
*/
|
||||
public static final int ERROR_CODE_ALREADY_STARTED = 2;
|
||||
|
||||
/**
|
||||
* The error code that we return when we fail to create a directory that has
|
||||
* been specified with the -c|--config option.
|
||||
*/
|
||||
public static final int ERROR_CODE_CREATE_DIR_FAILED = 3;
|
||||
|
||||
/**
|
||||
* The property name containing the name of the application
|
||||
* (e.g. SIP Communicator)
|
||||
*/
|
||||
private static final String PNAME_APPLICATION_NAME = "APPLICATION_NAME";
|
||||
|
||||
/**
|
||||
* The property name containing the current version.
|
||||
*/
|
||||
private static final String PNAME_VERSION = "APPLICATION_VERSION";
|
||||
|
||||
/**
|
||||
* The name of the file containing version properties for use with the
|
||||
* argument handler.
|
||||
*/
|
||||
private static final String VERSION_PROPERTIES = "version.properties";
|
||||
|
||||
/**
|
||||
* The errorCode identifying the error that occurred last time
|
||||
* <tt>handleArgs</tt> was called.
|
||||
*/
|
||||
private int errorCode = 0;
|
||||
|
||||
/**
|
||||
* A reference to the instance of the
|
||||
*/
|
||||
private UriArgManager uriArgManager = new UriArgManager();
|
||||
|
||||
/**
|
||||
* The singleton instance of this handler.
|
||||
*/
|
||||
private static LaunchArgHandler argHandler = null;
|
||||
|
||||
private Properties versionProperties = new Properties();
|
||||
|
||||
/**
|
||||
* Creates the sole instance of this class;
|
||||
*/
|
||||
private LaunchArgHandler()
|
||||
{
|
||||
try
|
||||
{
|
||||
versionProperties.load(
|
||||
getClass().getResourceAsStream(VERSION_PROPERTIES));
|
||||
}
|
||||
catch(IOException exc)
|
||||
{
|
||||
//no need to worry the user, so only print if we're in FINEST
|
||||
logger.trace("Couldn't open version.properties");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a singleton instance of the LauncherArgHandler if necessary and
|
||||
* returns a reference to it.
|
||||
*
|
||||
* @return the singleton instance of the LauncherArgHandler.
|
||||
*/
|
||||
public static LaunchArgHandler getInstance()
|
||||
{
|
||||
if(argHandler == null)
|
||||
{
|
||||
argHandler = new LaunchArgHandler();
|
||||
}
|
||||
|
||||
return argHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual argument handling.
|
||||
*
|
||||
* @param args the arguments the way we have received them from the main()
|
||||
* method.
|
||||
*
|
||||
* @return one of the ACTION_XXX fields defined here, intended to indicate
|
||||
* to the caller they action that they are supposed as a result of the arg
|
||||
* handling.
|
||||
*/
|
||||
public int handleArgs(String[] args)
|
||||
{
|
||||
int returnAction = ACTION_CONTINUE;
|
||||
|
||||
for(int i = 0; i < args.length; i++)
|
||||
{
|
||||
logger.trace("handling arg " + i);
|
||||
|
||||
if (args[i].equals("--version") || args[i].equals("-v"))
|
||||
{
|
||||
handleVersionArg();
|
||||
//we're supposed to exit after printing version info
|
||||
returnAction = ACTION_EXIT;
|
||||
break;
|
||||
}
|
||||
else if (args[i].equals("--help") || args[i].equals("-h"))
|
||||
{
|
||||
handleHelpArg();
|
||||
//we're supposed to exit after printing the help message
|
||||
returnAction = ACTION_EXIT;
|
||||
break;
|
||||
}
|
||||
else if (args[i].equals("--debug") || args[i].equals("-d"))
|
||||
{
|
||||
handleDebugArg(args[i]);
|
||||
continue;
|
||||
}
|
||||
else if (args[i].startsWith("--config="))
|
||||
{
|
||||
returnAction = handleConfigArg(args[i]);
|
||||
|
||||
if(returnAction == ACTION_ERROR)
|
||||
break;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
else if (args[i].equals("-c"))
|
||||
{
|
||||
//make sure we have at least one more argument left.
|
||||
if( i == args.length - 1)
|
||||
{
|
||||
System.out.println("The \"-c\" option expects a directory parameter.");
|
||||
returnAction = ACTION_ERROR;
|
||||
break;
|
||||
}
|
||||
handleConfigArg(args[++i]);
|
||||
continue;
|
||||
}
|
||||
else if (args[i].equals("--multiple") || args[i].equals("-m"))
|
||||
{
|
||||
handleMultipleArg(args[i]);
|
||||
continue;
|
||||
}
|
||||
//if this is the last arg and it's not an option then it's probably
|
||||
//an URI
|
||||
else if ( i == args.length - 1
|
||||
&& !args[i].startsWith("-"))
|
||||
{
|
||||
handleUri(args[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleUnknownArg(args[i]);
|
||||
|
||||
errorCode = ERROR_CODE_UNKNOWN_ARG;
|
||||
returnAction = ACTION_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return returnAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes <tt>uriArg</tt> to our uri manager for handling.
|
||||
*
|
||||
* @param arg the uri that we'd like to pass to
|
||||
*/
|
||||
private void handleUri(String uri)
|
||||
{
|
||||
logger.trace("Handling uri "+ uri);
|
||||
uriArgManager.handleUri(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs SIP Communicator to print logging messages to the console.
|
||||
*/
|
||||
private void handleDebugArg(String arg)
|
||||
{
|
||||
System.out.println("Option " + arg + " is not yet implemented!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs SIP Communicator to allow for more than a single running
|
||||
* instance.
|
||||
*/
|
||||
private void handleMultipleArg(String arg)
|
||||
{
|
||||
System.out.println("Option " + arg + " is not yet implemented!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs SIP Communicator to allow for more than a single running
|
||||
* instance.
|
||||
*
|
||||
* @return either ACTION_ERROR or ACTION_CONTINUE depending on whether or
|
||||
* not parsing the option went fine.
|
||||
*/
|
||||
private int handleConfigArg(String configArg)
|
||||
{
|
||||
if (configArg.startsWith("--config="))
|
||||
{
|
||||
configArg = configArg.substring("--config=".length());
|
||||
|
||||
}
|
||||
|
||||
File configDir = new File(configArg);
|
||||
|
||||
configDir.mkdirs();
|
||||
|
||||
if(!configDir.isDirectory())
|
||||
{
|
||||
System.out.println("Failed to create directory " + configArg);
|
||||
errorCode = ERROR_CODE_CREATE_DIR_FAILED;
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
System.setProperty(PNAME_SC_HOME_DIR_LOCATION, configDir.getParent());
|
||||
System.setProperty(PNAME_SC_HOME_DIR_NAME, configDir.getName());
|
||||
|
||||
return ACTION_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the name and the version of this application. This method uses the
|
||||
* version.properties file which is created by ant during the build process.
|
||||
* If this file does not exist the method would print a default name and
|
||||
* version string.
|
||||
*/
|
||||
private void handleVersionArg()
|
||||
{
|
||||
String name = getApplicationName();
|
||||
String version = getVersion();
|
||||
|
||||
if (name == null || name.trim().length() == 0)
|
||||
{
|
||||
name = "SIP Communicator";
|
||||
}
|
||||
|
||||
if (version == null || version.trim().length() == 0)
|
||||
{
|
||||
version = "build.by.SVN";
|
||||
}
|
||||
System.out.println(name + " " + version);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the SIP Communicator instance that we are
|
||||
* currently running.
|
||||
*
|
||||
* @return a String containing the version of the SC instance we are
|
||||
* currently running.
|
||||
*/
|
||||
private String getVersion()
|
||||
{
|
||||
String version = versionProperties.getProperty(PNAME_VERSION);
|
||||
|
||||
return version == null
|
||||
? "build.by.SVN"
|
||||
: version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the application. That should be SIP Communicator
|
||||
* most of the time but who knows ..
|
||||
*
|
||||
* @return the name of the application (i.e. SIP Communicator until we
|
||||
* change our name some day.)
|
||||
*/
|
||||
private String getApplicationName()
|
||||
{
|
||||
String name = versionProperties.getProperty(PNAME_APPLICATION_NAME);
|
||||
|
||||
return name == null
|
||||
? "SIP Communicator"
|
||||
: name;
|
||||
}
|
||||
/**
|
||||
* Prints an error message and then prints the help message.
|
||||
*/
|
||||
public void handleUnknownArg(String arg)
|
||||
{
|
||||
System.out.println("Unknown argument: " + arg);
|
||||
handleHelpArg();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a help message containing usage instructions and descriptions of
|
||||
* all options currently supported by SIP Communicator.
|
||||
*/
|
||||
public void handleHelpArg()
|
||||
{
|
||||
handleVersionArg();
|
||||
|
||||
System.out.println("Usage: sip-communicator [OPTIONS] [uri-to-call]");
|
||||
System.out.println("");
|
||||
System.out.println(" -c, --config=DIR use DIR for config files");
|
||||
System.out.println(" -d, --debug print debugging messages to stdout");
|
||||
System.out.println(" -h, --help display this help message and exit");
|
||||
System.out.println(" -m, --multiple do not ensure single instance");
|
||||
System.out.println(" -v, --version display the current version and exit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error code that could help identify an error when
|
||||
* <tt>handleArgs</tt> returns ACTION_ERROR or 0 if everything went fine.
|
||||
*
|
||||
* @return an error code that could help identify an error when
|
||||
* <tt>handleArgs</tt> returns ACTION_ERROR or 0 if everything went fine.
|
||||
*/
|
||||
public int getErrorCode()
|
||||
{
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the <tt>delegationPeer</tt> that would be handling all URIs passed
|
||||
* as command line arguments to SIP Communicator.
|
||||
*
|
||||
* @param delegationPeer the <tt>delegationPeer</tt> that should handle URIs
|
||||
* or <tt>null</tt> if we'd like to unset a previously set peer.
|
||||
*/
|
||||
public void setDelegationPeer(UriDelegationPeer delegationPeer)
|
||||
{
|
||||
this.uriArgManager.setDelegationPeer(delegationPeer);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,784 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.util.launchutils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.java.sip.communicator.launcher.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
import net.java.sip.communicator.util.launchutils.*;
|
||||
|
||||
/**
|
||||
* This class is used to prevent from running multiple instances of SIP
|
||||
* Communicator. The class binds a socket somewhere on the localhost domain and
|
||||
* records its socket address in the SIP Communicator configuration directory.
|
||||
*
|
||||
* All following instances of SIP Communicator (and hence this class) will look
|
||||
* for this record in the configuration directory and try to connect to the
|
||||
* original instance through the socket address in there.
|
||||
*
|
||||
* @author Emil Ivov
|
||||
*/
|
||||
public class SipCommunicatorLock extends Thread
|
||||
{
|
||||
private static final Logger logger = Logger
|
||||
.getLogger(SipCommunicatorLock.class);
|
||||
|
||||
/**
|
||||
* Indicates that something went wrong. More information will probably be
|
||||
* available in the console ... if anyone cares at all.
|
||||
*/
|
||||
public static final int LOCK_ERROR = 300;
|
||||
|
||||
/**
|
||||
* Returned by the soft start method to indicate that we have successfully
|
||||
* started and locked the configuration directory.
|
||||
*/
|
||||
public static final int SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* Returned by the soft start method to indicate that an instance of SIP
|
||||
* Communicator has been already started and we should exit. This return
|
||||
* code also indicates that all arguments were passed to that new instance.
|
||||
*/
|
||||
public static final int ALREADY_STARTED = 301;
|
||||
|
||||
/**
|
||||
* The name of the file that we use to store the address and port that this
|
||||
* lock is bound on.
|
||||
*/
|
||||
private static final String LOCK_FILE_NAME = ".lock";
|
||||
|
||||
/**
|
||||
* The name of the property that we use to store the address that we bind on
|
||||
* in this class.
|
||||
*/
|
||||
private static final String PNAME_LOCK_ADDRESS = "lockAddress";
|
||||
|
||||
/**
|
||||
* The name of the property that we use to store the address that we bind on
|
||||
* in this class.
|
||||
*/
|
||||
private static final String PNAME_LOCK_PORT = "lockPort";
|
||||
|
||||
/**
|
||||
* The header preceding each of the arguments that we toss around between
|
||||
* instances of SIP Communicator
|
||||
*/
|
||||
private static final String ARGUMENT = "Argument";
|
||||
|
||||
/**
|
||||
* The name of the header that contains the number of arguments that we send
|
||||
* from one instance to another.
|
||||
*/
|
||||
private static final String ARG_COUNT = "Arg-Count";
|
||||
|
||||
/**
|
||||
* The name of the header that contains any error messages resulting from
|
||||
* remote argument handling.
|
||||
*/
|
||||
private static final String ERROR_ARG = "ERROR";
|
||||
|
||||
/**
|
||||
* The carriage return, line feed sequence (\r\n).
|
||||
*/
|
||||
private static final String CRLF = "\r\n";
|
||||
|
||||
/**
|
||||
* The number of milliseconds that we should wait for a remote SC instance
|
||||
* to come back to us.
|
||||
*/
|
||||
private long LOCK_COMMUNICATION_DELAY = 50;
|
||||
|
||||
/**
|
||||
* The socket that we use for cross instance lock and communication.
|
||||
*/
|
||||
private ServerSocket instanceServerSocket = null;
|
||||
|
||||
/**
|
||||
* Tries to lock the configuration directory. If lock-ing is not possible
|
||||
* because a previous instance is already running, then it transmits the
|
||||
* list of args to that running instance.
|
||||
* <p>
|
||||
* There are three possible outcomes of this method. 1. We lock
|
||||
* successfully; 2. We fail to lock because another instance of SIP
|
||||
* Communicator is already running; 3. We fail to lock for some unknown
|
||||
* error. Each of these cases is represented by an error code returned as a
|
||||
* result.
|
||||
*
|
||||
* @param args
|
||||
* the array of arguments that we are to submit in case an
|
||||
* instance of SIP Communicator has already been started.
|
||||
*
|
||||
* @return an error or success code indicating the outcome of the lock
|
||||
* operation.
|
||||
*/
|
||||
public int tryLock(String[] args)
|
||||
{
|
||||
// first check whether we have a file.
|
||||
File lockFile = getLockFile();
|
||||
|
||||
if (lockFile.exists())
|
||||
{
|
||||
InetSocketAddress lockAddress = readLockFile(lockFile);
|
||||
|
||||
if (lockAddress != null)
|
||||
{
|
||||
// we have a valid lockAddress and hence possibly an already
|
||||
// running instance of SC. Try to communicate with it.
|
||||
if (interInstanceConnect(lockAddress, args) == SUCCESS)
|
||||
{
|
||||
return ALREADY_STARTED;
|
||||
}
|
||||
}
|
||||
|
||||
// our lockFile is probably stale and left from a previous instance.
|
||||
// or an instance that is still running but is not responding.
|
||||
lockFile.delete();
|
||||
}
|
||||
|
||||
// if we get here then this means that we should go for a real lock
|
||||
// initialization
|
||||
// create a new socket,
|
||||
// right the bind address in the file
|
||||
try
|
||||
{
|
||||
lockFile.createNewFile();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Failed to create lock file", e);
|
||||
}
|
||||
|
||||
lockFile.deleteOnExit();
|
||||
|
||||
return lock(lockFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the configuration directory by binding our lock socket and
|
||||
* recording the lock file into the configuration directory. Returns SUCCESS
|
||||
* if everything goes well and ERROR if something fails. This method does
|
||||
* not return the ALREADY_RUNNING code as it is assumed that this has
|
||||
* already been checked before calling this method.
|
||||
*
|
||||
* @param lockFile
|
||||
* the file that we should use to lock the configuration
|
||||
* directory.
|
||||
*
|
||||
* @return the SUCCESS or ERROR codes defined by this class.
|
||||
*/
|
||||
private int lock(File lockFile)
|
||||
{
|
||||
InetAddress lockAddress = getRandomBindAddress();
|
||||
|
||||
if (lockAddress == null)
|
||||
{
|
||||
return LOCK_ERROR;
|
||||
}
|
||||
|
||||
int port = getRandomPortNumber();
|
||||
|
||||
InetSocketAddress serverSocketAddress = new InetSocketAddress(
|
||||
lockAddress, port);
|
||||
|
||||
writeLockFile(lockFile, serverSocketAddress);
|
||||
|
||||
startLockServer(serverSocketAddress);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and binds a socket on <tt>lockAddress</tt> and then starts a
|
||||
* <tt>LockServer</tt> instance so that we would start interacting with
|
||||
* other instances of SIP Communicator that are trying to start.
|
||||
*
|
||||
* @return the <tt>ERROR</tt> code if something goes wrong and
|
||||
* <tt>SUCCESS</tt> otherwise.
|
||||
*/
|
||||
private int startLockServer(InetSocketAddress localAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
// check config directory
|
||||
instanceServerSocket = new ServerSocket();
|
||||
}
|
||||
catch (IOException exc)
|
||||
{
|
||||
// Just checked the impl and this doesn't seem to ever be thrown
|
||||
// .... ignore ...
|
||||
logger.error("Couldn't create server socket", exc);
|
||||
return LOCK_ERROR;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
instanceServerSocket.bind(localAddress, 16);// Why 16? 'cos I say
|
||||
// so.
|
||||
}
|
||||
catch (IOException exc)
|
||||
{
|
||||
logger.error("Couldn't create server socket", exc);
|
||||
return LOCK_ERROR;
|
||||
}
|
||||
|
||||
LockServer lockServ = new LockServer(instanceServerSocket);
|
||||
|
||||
lockServ.start();
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a randomly chosen socket address using a loopback interface (or
|
||||
* another one in case the loopback is not available) that we should bind
|
||||
* on.
|
||||
*
|
||||
* @return an InetAddress (most probably a loopback) that we can use to bind
|
||||
* our semaphore socket on.
|
||||
*/
|
||||
private InetAddress getRandomBindAddress()
|
||||
{
|
||||
NetworkInterface loopback;
|
||||
try
|
||||
{
|
||||
// find a loopback interface
|
||||
Enumeration<NetworkInterface> interfaces;
|
||||
try
|
||||
{
|
||||
interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
}
|
||||
catch (SocketException exc)
|
||||
{
|
||||
// I don't quite understand why this would happen ...
|
||||
logger.error(
|
||||
"Failed to obtain a list of the local interfaces.",
|
||||
exc);
|
||||
return null;
|
||||
}
|
||||
|
||||
loopback = null;
|
||||
while (interfaces.hasMoreElements())
|
||||
{
|
||||
NetworkInterface iface = interfaces.nextElement();
|
||||
|
||||
if (iface.isLoopback())
|
||||
{
|
||||
loopback = iface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't find a loopback (unlikely but possible)
|
||||
// return the first available interface on this machine
|
||||
if (loopback == null)
|
||||
{
|
||||
loopback = NetworkInterface.getNetworkInterfaces()
|
||||
.nextElement();
|
||||
}
|
||||
}
|
||||
catch (SocketException exc)
|
||||
{
|
||||
// I don't quite understand what could possibly cause this ...
|
||||
logger.error("Could not find the loopback interface", exc);
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the first address on the loopback.
|
||||
InetAddress addr = loopback.getInetAddresses().nextElement();
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random port number that we can use to bind a socket on.
|
||||
*
|
||||
* @return a random port number that we can use to bind a socket on.
|
||||
*/
|
||||
private int getRandomPortNumber()
|
||||
{
|
||||
return (int) (Math.random() * 64509) + 1025;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the <tt>lockFile</tt> into a standard Properties Object and
|
||||
* verifies it for completeness. The method also tries to validate the
|
||||
* contents of <tt>lockFile</tt> and asserts presence of all properties
|
||||
* mandated by this version.
|
||||
*
|
||||
* @param lockFile
|
||||
* the file that we are to parse.
|
||||
*
|
||||
* @return the <tt>SocketAddress</tt> that we should use to communicate with
|
||||
* a possibly already running version of SIP Communicator.
|
||||
*/
|
||||
private InetSocketAddress readLockFile(File lockFile)
|
||||
{
|
||||
Properties lockProperties = new Properties();
|
||||
|
||||
try
|
||||
{
|
||||
lockProperties.load(new FileInputStream(lockFile));
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logger.error("Failed to read lock properties.", exc);
|
||||
return null;
|
||||
}
|
||||
|
||||
String lockAddressStr = lockProperties.getProperty(PNAME_LOCK_ADDRESS);
|
||||
if (lockAddressStr == null)
|
||||
{
|
||||
logger.error("Lock file contains no lock address.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String lockPort = lockProperties.getProperty(PNAME_LOCK_PORT);
|
||||
if (lockPort == null)
|
||||
{
|
||||
logger.error("Lock file contains no lock port.");
|
||||
return null;
|
||||
}
|
||||
|
||||
InetAddress lockAddress = findLocalAddress(lockAddressStr);
|
||||
|
||||
if (lockAddress == null)
|
||||
{
|
||||
logger.error(lockAddressStr + " is not a valid local address.");
|
||||
return null;
|
||||
}
|
||||
|
||||
int port;
|
||||
try
|
||||
{
|
||||
port = Integer.parseInt(lockPort);
|
||||
}
|
||||
catch (NumberFormatException exc)
|
||||
{
|
||||
logger.error(lockPort + " is not a valid port number.", exc);
|
||||
return null;
|
||||
}
|
||||
|
||||
InetSocketAddress lockSocketAddress = new InetSocketAddress(
|
||||
lockAddress, port);
|
||||
|
||||
return lockSocketAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records our <tt>lockAddress</tt> into <tt>lockFile</tt> using the
|
||||
* standard properties format.
|
||||
*
|
||||
* @param lockFile
|
||||
* the file that we should store the address in.
|
||||
* @param lockAddress
|
||||
* the address that we have to record.
|
||||
*
|
||||
* @return <tt>SUCCESS</tt> upon success and <tt>ERROR</tt> if we fail to
|
||||
* store the file.
|
||||
*/
|
||||
private int writeLockFile(File lockFile, InetSocketAddress lockAddress)
|
||||
{
|
||||
Properties lockProperties = new Properties();
|
||||
|
||||
lockProperties.setProperty(PNAME_LOCK_ADDRESS, lockAddress.getAddress()
|
||||
.getHostAddress());
|
||||
|
||||
lockProperties.setProperty(PNAME_LOCK_PORT, Integer
|
||||
.toString(lockAddress.getPort()));
|
||||
|
||||
try
|
||||
{
|
||||
lockProperties.store(new FileOutputStream(lockFile),
|
||||
"SIP Communicator lock file. This file will be automatically"
|
||||
+ "removed when execution of SIP Communicator terminates.");
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Failed to create lock file.", e);
|
||||
return LOCK_ERROR;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the file that we should be using to lock SIP
|
||||
* Communicator's home directory, whether it exists or not.
|
||||
*
|
||||
* @return a reference to the file that we should be using to lock SIP
|
||||
* Communicator's home directory.
|
||||
*/
|
||||
private File getLockFile()
|
||||
{
|
||||
String homeDirLocation = System
|
||||
.getProperty(SIPCommunicator.PNAME_SC_HOME_DIR_LOCATION);
|
||||
String homeDirName = System
|
||||
.getProperty(SIPCommunicator.PNAME_SC_HOME_DIR_NAME);
|
||||
|
||||
String fileSeparator = System.getProperty("file.separator");
|
||||
|
||||
String fullLockFileName = homeDirLocation + fileSeparator + homeDirName
|
||||
+ fileSeparator + LOCK_FILE_NAME;
|
||||
|
||||
return new File(fullLockFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an <tt>InetAddress</tt> instance corresponding to
|
||||
* <tt>addressStr</tt> or <tt>null</tt> if no such address exists on the
|
||||
* local interfaces.
|
||||
*
|
||||
* @param addressStr
|
||||
* the address string that we are trying to resolve into an
|
||||
* <tt>InetAddress</tt>
|
||||
*
|
||||
* @return an <tt>InetAddress</tt> instance corresponding to
|
||||
* <tt>addressStr</tt> or <tt>null</tt> if none of the local
|
||||
* interfaces has such an address.
|
||||
*/
|
||||
private InetAddress findLocalAddress(String addressStr)
|
||||
{
|
||||
Enumeration<NetworkInterface> ifaces;
|
||||
|
||||
try
|
||||
{
|
||||
ifaces = NetworkInterface.getNetworkInterfaces();
|
||||
}
|
||||
catch (SocketException exc)
|
||||
{
|
||||
logger.error(
|
||||
"Could not extract the list of local intefcaces.",
|
||||
exc);
|
||||
return null;
|
||||
}
|
||||
|
||||
// loop through local interfaces
|
||||
while (ifaces.hasMoreElements())
|
||||
{
|
||||
NetworkInterface iface = ifaces.nextElement();
|
||||
|
||||
Enumeration<InetAddress> addreses = iface.getInetAddresses();
|
||||
|
||||
// loop iface addresses
|
||||
while (addreses.hasMoreElements())
|
||||
{
|
||||
InetAddress addr = addreses.nextElement();
|
||||
|
||||
if (addr.getHostAddress().equals(addressStr))
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a client TCP socket, connects if to <tt>sockAddr</tt> and
|
||||
* sends all <tt>args</tt> to it.
|
||||
*
|
||||
* @param sockAddr the address that we are to connect to.
|
||||
* @param args the args that we need to send to <tt>sockAddr</tt>.
|
||||
*
|
||||
* @return <tt>SUCCESS</tt> upond success and <tt>ERROR</tt> if anything
|
||||
* goes wrong.
|
||||
*/
|
||||
private int interInstanceConnect(InetSocketAddress sockAddr, String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket interInstanceSocket = new Socket(sockAddr.getAddress(),
|
||||
sockAddr.getPort());
|
||||
|
||||
LockClient lockClient = new LockClient(interInstanceSocket);
|
||||
lockClient.start();
|
||||
|
||||
PrintStream printStream = new PrintStream(interInstanceSocket
|
||||
.getOutputStream());
|
||||
|
||||
printStream.print(ARG_COUNT + "=" + args.length + CRLF);
|
||||
|
||||
for (int i = 0; i < args.length; i++)
|
||||
{
|
||||
printStream.print(ARGUMENT + "=" + args[i] + CRLF);
|
||||
}
|
||||
|
||||
lockClient.waitForReply(LOCK_COMMUNICATION_DELAY);
|
||||
|
||||
//NPEs are handled in catch so no need to check whether or not we
|
||||
//actually have a reply.
|
||||
String serverReadArgCountStr = lockClient.message
|
||||
.substring((ARG_COUNT + "=").length());
|
||||
|
||||
int serverReadArgCount = Integer.parseInt(serverReadArgCountStr);
|
||||
logger.debug("Server read " + serverReadArgCount + " args.");
|
||||
|
||||
if(serverReadArgCount != args.length)
|
||||
return LOCK_ERROR;
|
||||
|
||||
printStream.flush();
|
||||
printStream.close();
|
||||
interInstanceSocket.close();
|
||||
}
|
||||
//catch IOExceptions, NPEs and NumberFormatExceptions here.
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.debug("Failed to connect to a running sc instance.");
|
||||
return LOCK_ERROR;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* We use this thread to communicate with an already running instance of SIP
|
||||
* Communicator. This thread will listen for a reply to a message that we've
|
||||
* sent to the other instance. We will wait for this message for a maximum
|
||||
* of <tt>runDuration</tt> milliseconds and then consider the remote
|
||||
* instance dead.
|
||||
*/
|
||||
private class LockClient extends Thread
|
||||
{
|
||||
/**
|
||||
* The <tt>String</tt> that we've read from the socketInputStream
|
||||
*/
|
||||
public String message = null;
|
||||
|
||||
/**
|
||||
* The socket that this <tt>LockClient</tt> is created to read from.
|
||||
*/
|
||||
private Socket interInstanceSocket = null;
|
||||
|
||||
/**
|
||||
* Creates a <tt>LockClient</tt> that should read whatever data we
|
||||
* receive on <tt>sockInputStream</tt>.
|
||||
*
|
||||
* @param commSocket
|
||||
* the socket that this client should be reading from.
|
||||
*/
|
||||
public LockClient(Socket commSocket)
|
||||
{
|
||||
super(LockClient.class.getName());
|
||||
setDaemon(true);
|
||||
this.interInstanceSocket = commSocket;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blocks until a reply has been received or until run<tt>Duration</tt>
|
||||
* milliseconds had passed.
|
||||
*
|
||||
* @param runDuration the number of seconds to wait for a reply from
|
||||
* the remote instance
|
||||
*/
|
||||
public void waitForReply(long runDuration)
|
||||
{
|
||||
try
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
//return if we have already received a message.
|
||||
if(message != null)
|
||||
return;
|
||||
|
||||
wait(runDuration);
|
||||
}
|
||||
|
||||
logger.debug("Done waiting. Will close socket");
|
||||
interInstanceSocket.close();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.error("Failed to close our inter instance input stream",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply collects everything that we read from the InputStream that
|
||||
* this <tt>InterInstanceCommunicationClient</tt> was created with.
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
BufferedReader lineReader = new BufferedReader(
|
||||
new InputStreamReader(interInstanceSocket
|
||||
.getInputStream()));
|
||||
|
||||
//we only need to read a single line and then bail out.
|
||||
message = lineReader.readLine();
|
||||
logger.debug("Message is " + message);
|
||||
synchronized(this)
|
||||
{
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
catch (IOException exc)
|
||||
{
|
||||
// does not necessarily mean something is wrong. Could be
|
||||
// that we got tired of waiting and want to quit.
|
||||
logger.info("An IOException is thrown while reading sock", exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We start this thread when running SIP Communicator as a means of
|
||||
* notifying others that this is
|
||||
*/
|
||||
private class LockServer extends Thread
|
||||
{
|
||||
private boolean keepAccepting = true;
|
||||
|
||||
/**
|
||||
* The socket that we use for cross instance lock and communication.
|
||||
*/
|
||||
private ServerSocket lockSocket = null;
|
||||
|
||||
/**
|
||||
* Creates an instance of this <tt>LockServer</tt> wrapping the
|
||||
* specified <tt>serverSocket</tt>. It is expected that the serverSocket
|
||||
* will be already bound and ready to accept.
|
||||
*
|
||||
* @param serverSocket
|
||||
* the serverSocket that we should use for inter instance
|
||||
* communication.
|
||||
*/
|
||||
public LockServer(ServerSocket serverSocket)
|
||||
{
|
||||
super(LockServer.class.getName());
|
||||
setDaemon(true);
|
||||
this.lockSocket = serverSocket;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (keepAccepting)
|
||||
{
|
||||
Socket instanceSocket = lockSocket.accept();
|
||||
|
||||
new LockServerConnectionProcessor(instanceSocket).start();
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
logger.warn("Someone tried ", exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We use this thread to handle individual messages in server side inter
|
||||
* instance communication.
|
||||
*/
|
||||
private class LockServerConnectionProcessor extends Thread
|
||||
{
|
||||
/**
|
||||
* The socket that we will be using to communicate with the fellow SIP
|
||||
* Communicator instance..
|
||||
*/
|
||||
private Socket connectionSocket = null;
|
||||
|
||||
/**
|
||||
* Creates an instance of <tt>LockServerConnectionProcessor</tt> that
|
||||
* would handle parameters received through the
|
||||
* <tt>connectionSocket</tt>.
|
||||
*
|
||||
* @param connectedSocket
|
||||
* the socket that we will be using to read arguments from
|
||||
* the remote SIP Communicator instance.
|
||||
*/
|
||||
public LockServerConnectionProcessor(Socket connectionSocket)
|
||||
{
|
||||
this.connectionSocket = connectionSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts reading messages arriving through the connection socket.
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
InputStream is;
|
||||
PrintWriter printer;
|
||||
try
|
||||
{
|
||||
is = connectionSocket.getInputStream();
|
||||
printer = new PrintWriter(connectionSocket
|
||||
.getOutputStream());
|
||||
}
|
||||
catch (IOException exc)
|
||||
{
|
||||
logger.warn("Failed to read arguments from another SC instance",
|
||||
exc);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<String> argsList = new ArrayList<String>();
|
||||
|
||||
logger.debug("Handling incoming connection");
|
||||
|
||||
int argCount = 1024;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
BufferedReader lineReader = new BufferedReader(
|
||||
new InputStreamReader(is));
|
||||
String line = lineReader.readLine();
|
||||
|
||||
logger.debug(line);
|
||||
|
||||
if (line.startsWith(ARG_COUNT))
|
||||
{
|
||||
argCount = Integer.parseInt(line
|
||||
.substring((ARG_COUNT + "=").length()));
|
||||
}
|
||||
else if (line.startsWith(ARGUMENT))
|
||||
{
|
||||
String arg = line.substring((ARGUMENT + "=").length());
|
||||
argsList.add(arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ignore unknown headers.
|
||||
}
|
||||
|
||||
if (argCount <= argsList.size())
|
||||
break;
|
||||
}
|
||||
|
||||
// first tell the remote application that everything went OK
|
||||
// and end the connection so that it could exit
|
||||
printer.print(ARG_COUNT + "=" + argCount + CRLF);
|
||||
printer.close();
|
||||
connectionSocket.close();
|
||||
|
||||
// now let's handle what we've got
|
||||
String[] args = new String[argsList.size()];
|
||||
LaunchArgHandler.getInstance().handleArgs(
|
||||
argsList.toArray(args));
|
||||
}
|
||||
catch (IOException exc)
|
||||
{
|
||||
logger.info("An IOException is thrown while "
|
||||
+ "processing remote args", exc);
|
||||
|
||||
printer.print(ERROR_ARG + "=" + exc.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license. See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.util.launchutils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
/**
|
||||
* The <tt>UriArgManager</tt> implements an utility for handling URIs that have
|
||||
* been passed as command line arguments. The class maintains a list of
|
||||
* registered delegates that do the actual URI handling. The UriArgDelegator
|
||||
* is previewed for use with SIP Communicator argdelegation service. It would
|
||||
* therefore record all URIs until the corresponding DelegationPeer has been
|
||||
* registered with the UriArgManager.
|
||||
*
|
||||
* @author Emil Ivov
|
||||
*/
|
||||
class UriArgManager
|
||||
{
|
||||
private static final Logger logger = Logger.getLogger(UriArgManager.class);
|
||||
|
||||
/**
|
||||
* The delegation peer that we pass arguments to. This peer is going to
|
||||
* get set only after Felix starts and all its services have been properly
|
||||
* loaded.
|
||||
*/
|
||||
private UriDelegationPeer uriDelegationPeer = null;
|
||||
|
||||
/**
|
||||
* We use this list to store arguments that we have been asked to handle
|
||||
* before we had a registered delegation peer.
|
||||
*/
|
||||
private List<String> recordedArgs = new LinkedList<String>();
|
||||
|
||||
/**
|
||||
* Passes the <tt>uriArg</tt> to the uri delegation peer or, in case
|
||||
* no peer is currently registered, stores it and keeps it until one
|
||||
* appears.
|
||||
*
|
||||
* @param uriArg the uri argument that we'd like to delegate to our peer.
|
||||
*/
|
||||
protected void handleUri(String uriArg)
|
||||
{
|
||||
synchronized(recordedArgs)
|
||||
{
|
||||
if(uriDelegationPeer == null)
|
||||
{
|
||||
recordedArgs.add(uriArg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uriDelegationPeer.handleUri(uriArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a delegation peer that we can now use to pass arguments to and
|
||||
* makes it handle all arguments that have been already registered.
|
||||
*
|
||||
* @param delegationPeer the delegation peer that we can use to deliver
|
||||
* command line URIs to.
|
||||
*/
|
||||
public void setDelegationPeer(UriDelegationPeer delegationPeer)
|
||||
{
|
||||
synchronized(recordedArgs)
|
||||
{
|
||||
logger.trace("Someone set a delegationPeer. "
|
||||
+"Will dispatch "+ recordedArgs.size() +" args");
|
||||
this.uriDelegationPeer = delegationPeer;
|
||||
|
||||
for (String arg : recordedArgs)
|
||||
{
|
||||
logger.trace("Dispatching arg: " + arg);
|
||||
uriDelegationPeer.handleUri(arg);
|
||||
}
|
||||
|
||||
recordedArgs.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
|
||||
*
|
||||
* Distributable under LGPL license.
|
||||
* See terms of license at gnu.org.
|
||||
*/
|
||||
package net.java.sip.communicator.util.launchutils;
|
||||
|
||||
/**
|
||||
* The <tt>UriDelegationPeer</tt> is used as a mechanism to pass arguments from
|
||||
* the UriArgManager which resides in "launcher space" to our argument
|
||||
* delegation service implementation that lives as an osgi bundle. An instance
|
||||
* of this peer is created from within the argument delegation service impl
|
||||
* and is registered with the UriArgManager.
|
||||
*
|
||||
* @author Emil Ivov
|
||||
*/
|
||||
public interface UriDelegationPeer
|
||||
{
|
||||
/**
|
||||
* Handles <tt>uriArg</tt> in whatever way it finds fit.
|
||||
*
|
||||
* @param uriArg the uri argument that this delegate has to handle.
|
||||
*/
|
||||
public void handleUri(String uriArg);
|
||||
}
|
||||
Loading…
Reference in new issue