/* * Copyright (C) 2005-2010 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ifsoft.rayo; import java.io.*; import java.lang.reflect.*; import java.net.*; import java.util.*; import java.util.jar.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.jivesoftware.openfire.container.Plugin; import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.openfire.session.LocalClientSession; import org.jivesoftware.openfire.session.ClientSession; import org.jivesoftware.openfire.session.Session; import org.jivesoftware.openfire.event.SessionEventDispatcher; import org.jivesoftware.openfire.event.SessionEventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.component.ComponentException; import org.xmpp.component.ComponentManager; import org.xmpp.component.ComponentManagerFactory; import org.xmpp.jnodes.*; import org.xmpp.jnodes.nio.LocalIPResolver; import org.xmpp.jnodes.nio.PublicIPResolver; import org.xmpp.packet.*; import org.jitsi.util.*; import com.rayo.core.verb.*; import org.voicebridge.*; import com.sun.voip.server.*; import com.sun.voip.*; public class RayoPlugin implements Plugin, SessionEventListener { private static final Logger Log = LoggerFactory.getLogger(RayoPlugin.class); public static final String JN_PUB_IP_PROPERTY = "rayo.publicip"; private ComponentManager componentManager; private final ConcurrentHashMap<String, RelayChannel> channels = new ConcurrentHashMap<String, RelayChannel>(); private final long timeout = 60000; private final AtomicInteger ids = new AtomicInteger(0); private final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); private final String serviceName = "rayo"; private final boolean bindAllInterfaces; private boolean hasPublicIP = false; private Application bridge = new Application(); public static RayoComponent component = null; public RayoPlugin() { final String os = System.getProperty("os.name"); bindAllInterfaces = !(os != null && os.toLowerCase().indexOf("win") > -1); } public String getName() { return "rayo"; } public String getDescription() { return "Rayo Plugin"; } public void initializePlugin(PluginManager manager, File pluginDirectory) { componentManager = ComponentManagerFactory.getComponentManager(); component = new RayoComponent(this); try { componentManager.addComponent(serviceName, component); bridge.appStart(pluginDirectory); checkNatives(pluginDirectory); checkRecordingFolder(pluginDirectory); SessionEventDispatcher.addListener(this); } catch (ComponentException e) { Log.error("Could NOT load " + component.getName()); } setup(); component.doStart(); } private void setup() { executor.scheduleWithFixedDelay(new Runnable() { public void run() { for (final RelayChannel c : channels.values()) { final long current = System.currentTimeMillis(); final long da = current - c.getLastReceivedTimeA(); final long db = current - c.getLastReceivedTimeB(); if (da > timeout || db > timeout) { removeChannel(c); } } } }, timeout, timeout, TimeUnit.MILLISECONDS); Log.info("Rayo Plugin Loaded."); verifyNetwork(); } public void verifyNetwork() { final String localAddress = JiveGlobals.getProperty(JN_PUB_IP_PROPERTY, LocalIPResolver.getLocalIP()); LocalIPResolver.setOverrideIp(localAddress); final InetSocketAddress publicAddress = PublicIPResolver.getPublicAddress("stun.l.google.com", 19302); hasPublicIP = publicAddress != null && publicAddress.getAddress().getHostAddress().equals(localAddress); } private void closeAllChannels() { for (final RelayChannel c : channels.values()) { removeChannel(c); } } public RelayChannel getRelayChannel(String id) { if (channels.containsKey(id)) return channels.get(id); else return null; } public void handleVoiceBridge(String id, String operation, String parameter) { Log.info("handleVoiceBridge " + id); bridge.manageCallParticipant(id, operation, parameter); } public RelayChannel createRelayChannel(JID jid, Handset handset) { RelayChannel rc = null; try { rc = RelayChannel.createLocalRelayChannel(bindAllInterfaces ? "0.0.0.0" : LocalIPResolver.getLocalIP(), 30000, 50000); final int id = ids.incrementAndGet(); final String sId = JID.escapeNode(jid.toString()); rc.setAttachment(sId); rc.setFrom(jid, component); rc.setCrypto(handset); channels.put(sId, rc); } catch (IOException e) { Log.error("Could Not Create Channel.", e); } return rc; } public void removeChannel(final RelayChannel c) { channels.remove(c.getAttachment()); c.close(); } public String getServiceName() { return serviceName; } public void destroyPlugin() { try { componentManager.removeComponent(serviceName); bridge.appStop(); SessionEventDispatcher.removeListener(this); } catch (ComponentException e) { Log.error("Could NOT Remove " + serviceName + " Component"); } closeAllChannels(); executor.shutdownNow(); component.doStop(); } public boolean hasPublicIP() { return hasPublicIP; } public int getActiveChannelCount() { return channels.size(); } /** * 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 * rayo 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"; Log.info("checkNatives." + nativeLibsJarPath); 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 = "rayo-native-linux-32.jar"; } else if(OSUtils.IS_LINUX64) { jarFileSuffix = "rayo-native-linux-64.jar"; } else if(OSUtils.IS_WINDOWS32) { jarFileSuffix = "rayo-native-windows-32.jar"; } else if(OSUtils.IS_WINDOWS64) { jarFileSuffix = "rayo-native-windows-64.jar"; } else if(OSUtils.IS_MAC) { jarFileSuffix = "rayo-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); } } private void checkRecordingFolder(File pluginDirectory) { String rayoHome = JiveGlobals.getHomeDirectory() + File.separator + "resources" + File.separator + "spank" + File.separator + "rayo"; try { File rayoFolderPath = new File(rayoHome); if(!rayoFolderPath.exists()) { rayoFolderPath.mkdirs(); } File recordingFolderPath = new File(rayoHome + File.separator + "recordings"); if(!recordingFolderPath.exists()) { recordingFolderPath.mkdirs(); } File soundsFolderPath = new File(rayoHome + File.separator + "sounds"); if(!soundsFolderPath.exists()) { soundsFolderPath.mkdirs(); } } catch (Exception e) { Log.error(e.getMessage(), e); } } public void anonymousSessionCreated(Session session) { Log.debug("RayoPlugin anonymousSessionCreated "+ session.getAddress().toString() + "\n" + ((ClientSession) session).getPresence().toXML()); } public void anonymousSessionDestroyed(Session session) { Log.debug("RayoPlugin anonymousSessionDestroyed "+ session.getAddress().toString() + "\n" + ((ClientSession) session).getPresence().toXML()); CallHandler.hangupOwner(session.getAddress().toString(), "User has ended session"); } public void resourceBound(Session session) { Log.debug("RayoPlugin resourceBound "+ session.getAddress().toString() + "\n" + ((ClientSession) session).getPresence().toXML()); } public void sessionCreated(Session session) { Log.debug("RayoPlugin sessionCreated "+ session.getAddress().toString() + "\n" + ((ClientSession) session).getPresence().toXML()); } public void sessionDestroyed(Session session) { Log.debug("RayoPlugin sessionDestroyed "+ session.getAddress().toString() + "\n" + ((ClientSession) session).getPresence().toXML()); CallHandler.hangupOwner(session.getAddress().toString(), "User has ended session"); } }