/*
* Jitsi Videobridge, OpenSource video conferencing.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jitsi.videobridge.openfire;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.util.*;
import org.jitsi.videobridge.xmpp.*;
import org.jivesoftware.openfire.*;
import org.jivesoftware.openfire.container.*;
import org.jivesoftware.util.*;
import org.slf4j.*;
import org.slf4j.Logger;
import org.xmpp.component.*;
import org.xmpp.component.ComponentManager;
import org.xmpp.component.ComponentManagerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
/**
* Implements <tt>org.jivesoftware.openfire.container.Plugin</tt> to integrate
* Jitsi Video Bridge into Openfire.
*
* @author Lyubomir Marinov
* @author Damian Minkov
*/
public class PluginImpl
implements PropertyEventListener
{
/**
* The logger.
*/
private static final Logger Log = LoggerFactory.getLogger(PluginImpl.class);
/**
* The name of the property that contains the maximum port number that we'd
* like our RTP managers to bind upon.
*/
public static final String MAX_PORT_NUMBER_PROPERTY_NAME
= "org.jitsi.videobridge.media.MAX_PORT_NUMBER";
/**
* The name of the property that contains the minimum port number that we'd
* like our RTP managers to bind upon.
*/
public static final String MIN_PORT_NUMBER_PROPERTY_NAME
= "org.jitsi.videobridge.media.MIN_PORT_NUMBER";
/**
* The minimum port number default value.
*/
public static final int MIN_PORT_DEFAULT_VALUE = 5000;
/**
* The maximum port number default value.
*/
public static final int MAX_PORT_DEFAULT_VALUE = 6000;
/**
* The name of the property that contains the name of video conference application
*/
public static final String CHECKREPLAY_PROPERTY_NAME = "org.jitsi.videobridge.video.srtpcryptocontext.checkreplay";
/**
* The name of the property that contains the name of video conference application
*/
public static final String NAT_HARVESTER_LOCAL_ADDRESS = "org.jitsi.videobridge.nat.harvester.local.address";
/**
* The name of the property that contains the name of video conference application
*/
public static final String NAT_HARVESTER_PUBLIC_ADDRESS = "org.jitsi.videobridge.nat.harvester.public.address";
/**
* The name of the property that contains the maximum port number that we'd
* like our RTP managers to bind upon.
*/
public static final String RECORD_PROPERTY_NAME = "org.jitsi.videobridge.ofmeet.media.record";
/**
* The Jabber component which has been added to {@link #componentManager}
* i.e. Openfire.
*/
public static ComponentImpl component;
/**
* The <tt>ComponentManager</tt> to which the {@link #component} of this
* <tt>Plugin</tt> has been added.
*/
private ComponentManager componentManager;
/**
* The subdomain of the address of {@link #component} with which it has been
* added to {@link #componentManager}.
*/
private String subdomain;
/**
* Destroys this <tt>Plugin</tt> i.e. releases the resources acquired by
* this <tt>Plugin</tt> throughout its life up until now and prepares it for
* garbage collection.
*
* @see Plugin#destroyPlugin()
*/
public void destroyPlugin()
{
Log.info("PluginImpl destroyPlugin");
PropertyEventDispatcher.removeListener(this);
if ((componentManager != null) && (subdomain != null))
{
try
{
componentManager.removeComponent(subdomain);
}
catch (ComponentException ce)
{
// TODO Auto-generated method stub
}
componentManager = null;
subdomain = null;
SLF4JBridgeHandler.uninstall();
}
}
/**
* Initializes this <tt>Plugin</tt>.
*
* @param manager the <tt>PluginManager</tt> which loads and manages this
* <tt>Plugin</tt>
* @param pluginDirectory the directory into which this <tt>Plugin</tt> is
* located
* @see Plugin#initializePlugin(PluginManager, File)
*/
public void initializePlugin(ComponentManager componentManager, PluginManager manager, File pluginDirectory)
{
this.componentManager = componentManager;
// Remove existing handlers attached to j.u.l root logger
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
Log.info("PluginImpl initializePlugin "+ pluginDirectory);
String enableRecording = JiveGlobals.getProperty("org.jitsi.videobridge.ofmeet.media.record", "false");
String recordingPath = JiveGlobals.getProperty("org.jitsi.videobridge.ofmeet.recording.path", pluginDirectory.getAbsolutePath() + File.separator + "recordings");
String recordingSecret = JiveGlobals.getProperty("org.jitsi.videobridge.ofmeet.recording.secret", "secret");
String ourIpAddress = "127.0.0.1";
String ourHostname = XMPPServer.getInstance().getServerInfo().getHostname();
try {
ourIpAddress = InetAddress.getByName(ourHostname).getHostAddress();
} catch (Exception e) {
}
String localAddress = JiveGlobals.getProperty(NAT_HARVESTER_LOCAL_ADDRESS, ourIpAddress);
String publicAddress = JiveGlobals.getProperty(NAT_HARVESTER_PUBLIC_ADDRESS, ourIpAddress);
System.setProperty("net.java.sip.communicator.SC_HOME_DIR_LOCATION", pluginDirectory.getPath());
System.setProperty("net.java.sip.communicator.SC_HOME_DIR_NAME", ".");
System.setProperty("org.jitsi.impl.neomedia.transform.srtp.SRTPCryptoContext.checkReplay", JiveGlobals.getProperty(CHECKREPLAY_PROPERTY_NAME, "false"));
System.setProperty("org.jitsi.videobridge.ENABLE_MEDIA_RECORDING", enableRecording);
System.setProperty("org.jitsi.videobridge.MEDIA_RECORDING_PATH", recordingPath);
System.setProperty("org.jitsi.videobridge.MEDIA_RECORDING_TOKEN", recordingSecret);
System.setProperty("org.jitsi.videobridge.NAT_HARVESTER_LOCAL_ADDRESS", localAddress);
System.setProperty("org.jitsi.videobridge.NAT_HARVESTER_PUBLIC_ADDRESS", publicAddress);
System.setProperty("org.jitsi.videobridge.defaultOptions", "2"); // allow videobridge access without focus
PropertyEventDispatcher.addListener(this);
// Let's check for custom configuration
String maxVal = JiveGlobals.getProperty(MAX_PORT_NUMBER_PROPERTY_NAME);
String minVal = JiveGlobals.getProperty(MIN_PORT_NUMBER_PROPERTY_NAME);
if(maxVal != null)
setIntProperty(
DefaultStreamConnector.MAX_PORT_NUMBER_PROPERTY_NAME,
maxVal);
if(minVal != null)
setIntProperty(
DefaultStreamConnector.MIN_PORT_NUMBER_PROPERTY_NAME,
minVal);
checkNatives(pluginDirectory);
String subdomain = "ofmeet-jitsi-videobridge"; //ComponentImpl.SUBDOMAIN;
PluginImpl.component = new ComponentImpl();
boolean added = false;
try
{
componentManager.addComponent(subdomain, PluginImpl.component);
added = true;
}
catch (ComponentException ce)
{
ce.printStackTrace(System.err);
}
if (added)
{
this.subdomain = subdomain;
}
else
{
this.subdomain = null;
}
}
/**
* Checks whether we have folder with extracted natives, if missing
* find the appropriate jar file and extract them. Normally this is
* done once when plugin is installed or updated.
* If folder with natives exist add it to the java.library.path so
* libjitsi can use those native libs.
*/
private void checkNatives(File pluginDirectory)
{
// Find the root path of the class that will be our plugin lib folder.
try
{
String nativeLibsJarPath = pluginDirectory.getAbsolutePath() + File.separator + "lib";
File nativeLibFolder = new File(nativeLibsJarPath, "native");
if(!nativeLibFolder.exists())
{
nativeLibFolder.mkdirs();
// lets find the appropriate jar file to extract and
// extract it
String jarFileSuffix = null;
if(OSUtils.IS_LINUX32)
{
jarFileSuffix = "jitsi-videobridge-native-linux-32.jar";
}
else if(OSUtils.IS_LINUX64)
{
jarFileSuffix = "jitsi-videobridge-native-linux-64.jar";
}
else if(OSUtils.IS_WINDOWS32)
{
jarFileSuffix = "jitsi-videobridge-native-windows-32.jar";
}
else if(OSUtils.IS_WINDOWS64)
{
jarFileSuffix = "jitsi-videobridge-native-windows-64.jar";
}
else if(OSUtils.IS_MAC)
{
jarFileSuffix = "jitsi-videobridge-native-macosx.jar";
}
JarFile jar = new JarFile(nativeLibsJarPath + File.separator + jarFileSuffix);
Enumeration en = jar.entries();
while (en.hasMoreElements())
{
try
{
JarEntry file = (JarEntry) en.nextElement();
File f = new File(nativeLibFolder, file.getName());
if (file.isDirectory())
{
continue;
}
InputStream is = jar.getInputStream(file);
FileOutputStream fos = new FileOutputStream(f);
while (is.available() > 0)
{
fos.write(is.read());
}
fos.close();
is.close();
}
catch(Throwable t)
{}
}
Log.info("Native lib folder created and natives extracted");
}
else
Log.info("Native lib folder already exist.");
String newLibPath = nativeLibFolder.getCanonicalPath() + File.pathSeparator + System.getProperty("java.library.path");
System.setProperty("java.library.path", newLibPath);
// this will reload the new setting
Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(System.class.getClassLoader(), null);
}
catch (Exception e)
{
Log.error(e.getMessage(), e);
}
}
/**
* Returns the value of max port if set or the default one.
* @return the value of max port if set or the default one.
*/
public String getMaxPort()
{
String val = System.getProperty(
DefaultStreamConnector.MAX_PORT_NUMBER_PROPERTY_NAME);
if(val != null)
return val;
else
return String.valueOf(MAX_PORT_DEFAULT_VALUE);
}
/**
* Returns the value of min port if set or the default one.
* @return the value of min port if set or the default one.
*/
public String getMinPort()
{
String val = System.getProperty(
DefaultStreamConnector.MIN_PORT_NUMBER_PROPERTY_NAME);
if(val != null)
return val;
else
return String.valueOf(MIN_PORT_DEFAULT_VALUE);
}
/**
* A property was set. The parameter map <tt>params</tt> will contain the
* the value of the property under the key <tt>value</tt>.
*
* @param property the name of the property.
* @param params event parameters.
*/
public void propertySet(String property, Map params)
{
if(property.equals(MAX_PORT_NUMBER_PROPERTY_NAME))
{
setIntProperty(
DefaultStreamConnector.MAX_PORT_NUMBER_PROPERTY_NAME,
(String)params.get("value"));
}
else if(property.equals(MIN_PORT_NUMBER_PROPERTY_NAME))
{
setIntProperty(
DefaultStreamConnector.MIN_PORT_NUMBER_PROPERTY_NAME,
(String)params.get("value"));
}
}
/**
* Sets int property.
* @param property the property name.
* @param value the value to change.
*/
private void setIntProperty(String property, String value)
{
try
{
// let's just check that value is integer
int port = Integer.valueOf(value);
if(port >= 1 && port <= 65535)
System.setProperty(property, value);
}
catch(NumberFormatException ex)
{
Log.error("Error setting port", ex);
}
}
/**
* A property was deleted.
*
* @param property the name of the property deleted.
* @param params event parameters.
*/
public void propertyDeleted(String property, Map params)
{
if(property.equals(MAX_PORT_NUMBER_PROPERTY_NAME))
{
System.setProperty(
DefaultStreamConnector.MAX_PORT_NUMBER_PROPERTY_NAME,
String.valueOf(MAX_PORT_DEFAULT_VALUE));
}
else if(property.equals(MIN_PORT_NUMBER_PROPERTY_NAME))
{
System.setProperty(
DefaultStreamConnector.MIN_PORT_NUMBER_PROPERTY_NAME,
String.valueOf(MIN_PORT_DEFAULT_VALUE));
}
}
/**
* An XML property was set. The parameter map <tt>params</tt> will contain the
* the value of the property under the key <tt>value</tt>.
*
* @param property the name of the property.
* @param params event parameters.
*/
public void xmlPropertySet(String property, Map params)
{
propertySet(property, params);
}
/**
* An XML property was deleted.
*
* @param property the name of the property.
* @param params event parameters.
*/
public void xmlPropertyDeleted(String property, Map params)
{
propertyDeleted(property, params);
}
}