Java tutorial
/* * Jitsi Videobridge, OpenSource video conferencing. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jitsi.jigasi.openfire; import java.io.*; import java.lang.reflect.*; import java.net.*; import java.util.*; import java.util.jar.*; import java.util.concurrent.*; import java.security.cert.Certificate; import javax.media.*; import javax.media.protocol.*; import javax.media.format.*; import org.jivesoftware.util.*; import org.jivesoftware.openfire.container.Plugin; import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.SessionManager; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.muc.*; import org.jivesoftware.openfire.session.*; import org.jivesoftware.database.DbConnectionManager; import java.sql.*; import org.slf4j.*; import org.slf4j.Logger; import net.java.sip.communicator.impl.protocol.jabber.extensions.rayo.*; import net.java.sip.communicator.util.*; import org.dom4j.*; import org.jitsi.jigasi.*; import org.osgi.framework.*; import org.xmpp.component.*; import org.xmpp.packet.*; import org.jitsi.jigasi.xmpp.*; import org.jitsi.videobridge.xmpp.*; import org.jitsi.videobridge.*; import org.jitsi.impl.neomedia.*; import org.jitsi.impl.neomedia.format.*; import org.jitsi.impl.neomedia.device.*; import org.jitsi.impl.neomedia.conference.*; import org.jitsi.impl.neomedia.jmfext.media.renderer.audio.*; import org.jitsi.service.neomedia.*; import org.jitsi.service.neomedia.device.*; import org.jitsi.service.neomedia.event.*; import org.jitsi.service.neomedia.format.*; import org.jitsi.service.libjitsi.*; import org.jitsi.util.*; import org.jitsi.videobridge.openfire.PluginImpl; import org.ifsoft.*; import org.ifsoft.sip.*; import net.sf.fmj.media.rtp.*; import org.ifsoft.rtp.*; import uk.nominet.DDDS.*; public class CallControlComponent extends AbstractComponent { private static final Logger Log = LoggerFactory.getLogger(JigasiPlugin.class); public SSRCFactoryImpl ssrcFactory = new SSRCFactoryImpl(); private long initialLocalSSRC = ssrcFactory.doGenerateSSRC() & 0xFFFFFFFFL; public ConcurrentHashMap<String, CallSession> callSessions = new ConcurrentHashMap<String, CallSession>(); public ConcurrentHashMap<String, String> conferences = new ConcurrentHashMap<String, String>(); public ConcurrentHashMap<String, String> registrations = new ConcurrentHashMap<String, String>(); public static CallControlComponent self; private SipService sipService = null; private MultiUserChatManager mucManager = XMPPServer.getInstance().getMultiUserChatManager(); private Videobridge getVideobridge() { return PluginImpl.component.getVideobridge(); } public CallControlComponent(File pluginDirectory) { Log.info("CallControlComponent " + pluginDirectory); self = this; Properties properties = new Properties(); String hostName = JiveGlobals.getProperty("org.jitsi.videobridge.nat.harvester.public.address", XMPPServer.getInstance().getServerInfo().getXMPPDomain()); String logDir = pluginDirectory.getAbsolutePath() + File.separator + ".." + File.separator + ".." + File.separator + "logs" + File.separator; String port = JiveGlobals.getProperty("org.jitsi.videobridge.sip.port.number", "5060"); properties.setProperty("com.voxbone.kelpie.hostname", hostName); properties.setProperty("com.voxbone.kelpie.ip", hostName); properties.setProperty("com.voxbone.kelpie.sip_port", port); properties.setProperty("javax.sip.IP_ADDRESS", hostName); properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "99"); properties.setProperty("gov.nist.javax.sip.SERVER_LOG", logDir + "sip_server.log"); properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", logDir + "sip_debug.log"); if (JiveGlobals.getBooleanProperty("org.jitsi.videobridge.ofmeet.sip.enabled", true)) { Log.info("CallControlComponent - enabling SIP gateway "); sipService = new SipService(properties); } else { Log.info("CallControlComponent -enabling SIP gateway"); } } @Override protected String[] discoInfoFeatureNamespaces() { return new String[] { "http://jitsi.org/protocol/jigasi", "urn:xmpp:rayo:0" }; } @Override public String getDescription() { return "Call control component"; } @Override public String getName() { return "Call control"; } public void stop() { for (CallSession callSession : callSessions.values()) { callSession.mediaStream.stop(); callSession.mediaStream.close(); } callSessions.clear(); if (sipService != null) sipService.stop(); } public void recordCall(Conference conference, String token, String state) { String focusJid = conference.getFocus(); String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); Log.info("CallControlComponent - recordCall " + token + " " + state + " " + focusJid); IQ iq = new IQ(IQ.Type.set); iq.setTo("ofmeet-jitsi-videobridge." + domain); iq.setFrom(focusJid); Element colibri = iq.setChildElement("conference", "http://jitsi.org/protocol/colibri"); colibri.addAttribute("id", conference.getID()); colibri.addElement("recording").addAttribute("state", state).addAttribute("token", token); sendPacket(iq); } private void makeCall(Conference conference, String confJid, String to, String callId, String username, long startTimestamp) { Log.info("CallControlComponent - makeCall " + confJid + " " + to + " " + callId); try { String hostname = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); String callerId = (new JID(confJid)).getNode(); String focusJid = conference.getFocus(); MediaService mediaService = LibJitsi.getMediaService(); MediaStream mediaStream = mediaService.createMediaStream(null, org.jitsi.service.neomedia.MediaType.AUDIO, mediaService.createSrtpControl(SrtpControlType.MIKEY)); mediaStream.setName("rayo-" + System.currentTimeMillis()); Content content = conference.getOrCreateContent("audio"); boolean audioMixer = "true" .equals(JiveGlobals.getProperty("org.jitsi.videobridge.ofmeet.audio.mixer", "false")); if (audioMixer) { mediaStream.setSSRCFactory(ssrcFactory); mediaStream.setDevice(content.getMixer()); } else { mediaStream.setRTPTranslator(content.getRTPTranslator()); } content.createRtpChannel(null, null, null); CallSession cs = new CallSession(mediaStream, hostname, this, callId, focusJid, confJid); callSessions.put(callId, cs); boolean toSip = to.indexOf("sip:") == 0; boolean toPhone = to.indexOf("tel:") == 0; String from = null; if (!toSip && !toPhone) { String sipUri = null; if (to.length() == 8) { toSip = true; to = "sip:8835100" + to + "@81.201.82.25"; } else { if (to.indexOf("+") != 0) to = "+" + to; Log.info("CallControlComponent - makeCall looking up " + to); ENUM mEnum = new ENUM("e164.arpa"); Rule[] rules = mEnum.lookup(to); for (Rule rule : rules) { String temp = rule.evaluate(); Log.info("CallControlComponent - makeCall found " + temp); if (temp.indexOf("sip:") == 0) sipUri = temp; } if (sipUri != null) { toSip = true; to = sipUri; } else { to = "tel:" + to; toPhone = true; } } } if (toSip) { from = "sip:" + callerId + "@" + hostname; Log.info("CallControlComponent - makeCall with direct sip " + to + " " + from); } else { to = to.substring(4); if (registrations.containsKey(to)) { to = registrations.get(to); from = "sip:" + callerId + "@" + hostname; Log.info("CallControlComponent - makeCall with registration " + to + " " + from); } else { String outboundProxy = JiveGlobals.getProperty("voicebridge.default.proxy.outboundproxy", null); String sipUsername = JiveGlobals.getProperty("voicebridge.default.proxy.sipauthuser", null); if (outboundProxy != null && sipUsername != null && !"".equals(outboundProxy.trim()) && !"".equals(sipUsername.trim())) { to = "sip:" + to + "@" + outboundProxy; from = "sip:" + sipUsername + "@" + outboundProxy; Log.info("CallControlComponent - makeCall with outbound proxy " + to + " " + from); } else { Log.error( "SIP proxy not setup with voicebridge.default.proxy.outboundproxy and voicebridge.default.proxy.sipauthuser"); return; } } } cs.jabberRemote = to; cs.jabberLocal = from; cs.username = username; cs.startTimestamp = startTimestamp; SipService.sendInvite(cs); } catch (Exception e) { Log.error("CallControlComponent makeCall", e); } } public void hangupCall(String callId) { Log.info("hangupCall " + callId); CallSession cs = callSessions.remove(callId); if (cs != null) { SipService.sendBye(cs); updateCallRecord(cs.startTimestamp, (int) (System.currentTimeMillis() - cs.startTimestamp)); } else { Log.error("CallControlComponent hangup. cannot fine callid " + callId); } } public CallSession findCreateSession(String from, String to, String destination) { Log.info("CallControlComponent - findCreateSession " + from + " " + to + " " + destination); CallSession session = null; String hostname = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); String callerId = to; String confJID = null; Conference conference = null; boolean allowDirectSIP = "true" .equals(JiveGlobals.getProperty("org.jitsi.videobridge.ofmeet.allow.direct.sip", "false")); if (!allowDirectSIP || !registrations.containsKey(from)) { Log.warn("CallControlComponent - call rejected from " + from + " " + to); return null; // only accept calls from registered SIP user endpoint } if (callerId.indexOf("+") == 0) callerId = callerId.substring(1); for (MultiUserChatService service : mucManager.getMultiUserChatServices()) { for (MUCRoom room : service.getChatRooms()) { if (!room.isPasswordProtected() && (room.getDescription().indexOf(callerId) > -1 || room.getJID().getNode().equals(callerId))) { confJID = room.getJID().toString(); // description has telephone no or room name is sip url target break; // and not password protected } } if (confJID != null) break; } if (confJID != null && conferences.containsKey(confJID)) { String confId = conferences.get(confJID); Log.info("CallControlComponent - findCreateSession conference id " + confJID + " " + confId); for (Conference conf : getVideobridge().getConferences()) { if (conf.getID().equals(confId)) { conference = conf; break; } } } if (conference != null) { Log.info("CallControlComponent - findCreateSession conference " + conference.getFocus()); try { MediaService mediaService = LibJitsi.getMediaService(); MediaStream mediaStream = mediaService.createMediaStream(null, org.jitsi.service.neomedia.MediaType.AUDIO, mediaService.createSrtpControl(SrtpControlType.MIKEY)); mediaStream.setName("rayo-" + System.currentTimeMillis()); Content content = conference.getOrCreateContent("audio"); boolean audioMixer = "true" .equals(JiveGlobals.getProperty("org.jitsi.videobridge.ofmeet.audio.mixer", "false")); if (audioMixer) { mediaStream.setSSRCFactory(ssrcFactory); mediaStream.setDevice(content.getMixer()); } else { mediaStream.setRTPTranslator(content.getRTPTranslator()); } content.createRtpChannel(null, null, null); String callId = Long.toHexString(System.currentTimeMillis()); session = new CallSession(mediaStream, hostname, this, callId, conference.getFocus(), confJID); session.jabberRemote = from; session.jabberLocal = to; callSessions.put(callId, session); } catch (Exception e) { Log.error("CallControlComponent findCreateSession", e); } } if (session != null) { long startTimestamp = System.currentTimeMillis(); session.startTimestamp = startTimestamp; createCallRecord("admin", from, confJID, startTimestamp, 0, "received"); } return session; } public void inviteEvent(boolean accepted, String callId) { Log.info("CallControlComponent - inviteEvent " + accepted + " " + callId); if (callSessions.containsKey(callId)) { CallSession session = callSessions.get(callId); try { JID confJID = new JID(session.roomJID); String conferenceId = confJID.getNode(); MultiUserChatService service = mucManager.getMultiUserChatService(confJID); if (service != null) { if (service.hasChatRoom(conferenceId)) { MUCRoom room = service.getChatRoom(conferenceId); if (room != null) { for (MUCRole role : room.getOccupants()) { Presence presence = new Presence(); presence.setFrom(callId + "@" + getJID()); presence.setTo(role.getUserAddress()); if (accepted) { Element answered = presence.addChildElement("answered", "urn:xmpp:rayo:1"); answered.addElement("header").addAttribute("name", "caller_id") .addAttribute("value", session.jabberRemote); answered.addElement("header").addAttribute("name", "called_id") .addAttribute("value", session.jabberLocal); } else { Element hangup = presence.addChildElement("hangup", "urn:xmpp:rayo:1"); hangup.addElement("header").addAttribute("name", "caller_id") .addAttribute("value", session.jabberRemote); hangup.addElement("header").addAttribute("name", "called_id") .addAttribute("value", session.jabberLocal); callSessions.remove(callId); } sendPacket(presence); } return; } } } // no valid muc service or room. send events to requestor String username = (new JID(session.focusJID)).getNode(); Collection<ClientSession> sessions = SessionManager.getInstance().getSessions(username); for (ClientSession clientSession : sessions) { Presence presence = new Presence(); presence.setFrom(callId + "@" + getJID()); presence.setTo(clientSession.getAddress()); if (accepted) { Element answered = presence.addChildElement("answered", "urn:xmpp:rayo:1"); answered.addElement("header").addAttribute("name", "caller_id").addAttribute("value", session.jabberRemote); answered.addElement("header").addAttribute("name", "called_id").addAttribute("value", session.jabberLocal); } else { Element hangup = presence.addChildElement("hangup", "urn:xmpp:rayo:1"); hangup.addElement("header").addAttribute("name", "caller_id").addAttribute("value", session.jabberRemote); hangup.addElement("header").addAttribute("name", "called_id").addAttribute("value", session.jabberLocal); CallSession cs = callSessions.remove(callId); if (cs != null) { updateCallRecord(cs.startTimestamp, (int) (System.currentTimeMillis() - cs.startTimestamp)); } } sendPacket(presence); } } catch (Exception e) { Log.error("CallControlComponent inviteEvent. error" + session.roomJID, e); } } else { Log.error("CallControlComponent inviteEvent. cannot find callid " + callId); } } @Override public IQ handleIQSet(IQ iq) throws Exception { IQ reply = IQ.createResultIQ(iq); String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); try { Log.info("CallControlComponent - handleIQSet\n" + iq); Element element = iq.getChildElement(); String namespace = element.getNamespaceURI(); String request = element.getName(); String confJid = null; String confId = null; if ("dial".equals(request) && "urn:xmpp:rayo:1".equals(namespace)) { Log.info("CallControlComponent - Dial"); String from = element.attributeValue("from"); String to = element.attributeValue("to"); for (Iterator i = element.elementIterator("header"); i.hasNext();) { Element header = (Element) i.next(); String name = header.attributeValue("name"); String value = header.attributeValue("value"); if ("JvbRoomId".equals(name)) confId = value; if ("JvbRoomName".equals(name)) confJid = value; } if (confJid == null && confId == null) { reply.setError(PacketError.Condition.item_not_found); Log.error("No JvbRoomName or JvbRoomId header found"); } else { if (confId == null) { if (conferences.containsKey(confJid)) { confId = conferences.get(confJid); } } if (confJid == null) { confJid = confId + "@conference." + XMPPServer.getInstance().getServerInfo().getXMPPDomain(); } if (confId != null) { String callId = Long.toHexString(System.currentTimeMillis()); Log.info("Got dial request " + from + " -> " + to + " confId " + confId + " callId " + callId); String callResource = "xmpp:" + callId + "@" + getJID(); final Element childElement = reply.setChildElement("ref", "urn:xmpp:rayo:1"); childElement.addAttribute("uri", (String) "xmpp:" + callId + "@" + getJID()); childElement.addAttribute("id", (String) callId); Conference conference = null; for (Conference conf : getVideobridge().getConferences()) { if (conf.getID().equals(confId)) { conference = conf; break; } } if (conference != null) { String username = iq.getFrom().getNode(); long startTimestamp = System.currentTimeMillis(); makeCall(conference, confJid, to, callId, username, startTimestamp); createCallRecord(username, confJid, to, startTimestamp, 0, "dialed"); } else { Log.error("CallControlComponent - can't find conference " + confId); reply.setError(PacketError.Condition.item_not_found); } } else { Log.error("CallControlComponent - focus not ready " + confJid); reply.setError(PacketError.Condition.item_not_found); } } } else if ("accept".equals(request) && "urn:xmpp:rayo:1".equals(namespace)) { Log.info("CallControlComponent - Accept"); String confName = null; for (Iterator i = element.elementIterator("header"); i.hasNext();) { Element header = (Element) i.next(); String name = header.attributeValue("name"); String value = header.attributeValue("value"); if ("JvbRoomId".equals(name)) confId = value; if ("JvbRoomName".equals(name)) confName = value; } if (confId != null && confName != null) { Log.info("CallControlComponent - Accept register " + confId + " " + confName); conferences.put(confName, confId); } else { reply.setError(PacketError.Condition.item_not_found); Log.error("No JvbRoomName or JvbRoomId header found"); } } else if ("hangup".equals(request) && "urn:xmpp:rayo:1".equals(namespace)) { Log.info("CallControlComponent - HangUp"); String callId = iq.getTo().getNode(); hangupCall(callId); } else if ("record".equals(request) && "urn:xmpp:rayo:record:1".equals(namespace)) { Log.info("CallControlComponent - Record"); String token = null; String state = null; String confName = null; for (Iterator i = element.elementIterator("hint"); i.hasNext();) { Element hint = (Element) i.next(); String name = hint.attributeValue("name"); String value = hint.attributeValue("value"); if ("JvbToken".equals(name)) token = value; if ("JvbState".equals(name)) state = value; if ("JvbRoomName".equals(name)) confName = value; } if (token != null && state != null && confName != null) { if (conferences.containsKey(confName)) { confId = conferences.get(confName); Conference conference = null; for (Conference conf : getVideobridge().getConferences()) { if (conf.getID().equals(confId)) { conference = conf; break; } } if (conference != null) { recordCall(conference, token, state); } else { Log.error("CallControlComponent - can't find conference " + confId); reply.setError(PacketError.Condition.item_not_found); } } else { Log.error("CallControlComponent - focus not ready " + confName); reply.setError(PacketError.Condition.item_not_found); } } else { reply.setError(PacketError.Condition.item_not_found); Log.error("No JvbRoomName, JvbToken or JvbState headers found"); } } else { Log.warn("CallControlComponent - Unknown"); reply.setError(PacketError.Condition.item_not_found); } } catch (Exception e) { Log.error("CallControlComponent handleIQSet", e); reply.setError(PacketError.Condition.internal_server_error); } return reply; } private long ssrcFactoryGenerateSSRC(String cause, int i) { if (initialLocalSSRC != -1) { if (i == 0) return (int) initialLocalSSRC; else if (cause.equals(GenerateSSRCCause.REMOVE_SEND_STREAM.name())) return Long.MAX_VALUE; } return ssrcFactory.doGenerateSSRC(); } private class SSRCFactoryImpl implements SSRCFactory { private int i = 0; /** * The <tt>Random</tt> instance used by this <tt>SSRCFactory</tt> to * generate new synchronization source (SSRC) identifiers. */ private final Random random = new Random(); public int doGenerateSSRC() { return random.nextInt(); } /** * {@inheritDoc} */ @Override public long generateSSRC(String cause) { return ssrcFactoryGenerateSSRC(cause, i++); } } private void sendPacket(Packet packet) { try { ComponentManagerFactory.getComponentManager().sendPacket(this, packet); } catch (Exception e) { Log.error("CallControlComponent sendPacket ", e); } } private void createCallRecord(String username, String addressFrom, String addressTo, long datetime, int duration, String calltype) { boolean sipPlugin = XMPPServer.getInstance().getPluginManager().getPlugin("sip") != null; if (sipPlugin) { Log.info("createCallRecord " + username + " " + addressFrom + " " + addressTo + " " + datetime); String sql = "INSERT INTO ofSipPhoneLog (username, addressFrom, addressTo, datetime, duration, calltype) values (?, ?, ?, ?, ?, ?)"; Connection con = null; PreparedStatement psmt = null; ResultSet rs = null; try { con = DbConnectionManager.getConnection(); psmt = con.prepareStatement(sql); psmt.setString(1, username); psmt.setString(2, addressFrom); psmt.setString(3, addressTo); psmt.setLong(4, datetime); psmt.setInt(5, duration); psmt.setString(6, calltype); psmt.executeUpdate(); } catch (SQLException e) { Log.error(e.getMessage(), e); } finally { DbConnectionManager.closeConnection(rs, psmt, con); } } } private void updateCallRecord(long datetime, int duration) { boolean sipPlugin = XMPPServer.getInstance().getPluginManager().getPlugin("sip") != null; if (sipPlugin) { Log.info("updateCallRecord " + datetime + " " + duration); String sql = "UPDATE ofSipPhoneLog SET duration = ? WHERE datetime = ?"; Connection con = null; PreparedStatement psmt = null; try { con = DbConnectionManager.getConnection(); psmt = con.prepareStatement(sql); psmt.setInt(1, duration); psmt.setLong(2, (datetime / 1000)); psmt.executeUpdate(); } catch (SQLException e) { Log.error(e.getMessage(), e); } finally { DbConnectionManager.closeConnection(psmt, con); } } } }