/** * Copyright 2010-2011 Voxeo Corporation * * 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 com.voxeo.moho; import java.io.File; import java.rmi.registry.Registry; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; import javax.media.mscontrol.MsControlFactory; import javax.sdp.SdpFactory; import javax.servlet.ServletContext; import javax.servlet.http.HttpServlet; import javax.servlet.sip.SipFactory; import javax.servlet.sip.SipServlet; import org.apache.log4j.Logger; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import com.voxeo.moho.common.event.DispatchableEventSource; import com.voxeo.moho.common.util.NetworkUtils; import com.voxeo.moho.common.util.Utils.DaemonThreadFactory; import com.voxeo.moho.conference.ConferenceDriverImpl; import com.voxeo.moho.conference.ConferenceManager; import com.voxeo.moho.media.dialect.MediaDialect; import com.voxeo.moho.remote.network.RemoteCommunicationImpl; import com.voxeo.moho.services.Service; import com.voxeo.moho.sip.RemoteParticipantImpl; import com.voxeo.moho.sip.SIPDriverImpl; import com.voxeo.moho.spi.ExecutionContext; import com.voxeo.moho.spi.ProtocolDriver; import com.voxeo.moho.spi.SpiFramework; import com.voxeo.moho.common.util.InheritLogContextThreadPoolExecutor; import com.voxeo.moho.util.ParticipantIDParser; import com.voxeo.moho.util.SDPUtils; import com.voxeo.moho.utils.EventListener; import com.voxeo.moho.voicexml.VoiceXMLDriverImpl; import com.voxeo.servlet.xmpp.XmppFactory; import com.voxeo.servlet.xmpp.XmppServlet; public class ApplicationContextImpl extends DispatchableEventSource implements ExecutionContext, SpiFramework { private static final Logger LOG = Logger.getLogger(ApplicationContextImpl.class); protected Map<String, ProtocolDriver> _driversByProtocol = new HashMap<String, ProtocolDriver>(); protected Map<String, ProtocolDriver> _driversBySchema = new HashMap<String, ProtocolDriver>(); protected SipServlet _sip; protected HttpServlet _http; protected XmppServlet _xmpp; protected Application _application; protected MsControlFactory _mcFactory; protected MediaServiceFactory _msFactory; protected ConferenceManager _confMgr; protected SipFactory _sipFactory; protected SdpFactory _sdpFactory; protected XmppFactory _xmppFactory; protected Map<String, String> _parameters = new ConcurrentHashMap<String, String>(); protected ServletContext _servletContext; protected InheritLogContextThreadPoolExecutor _executor; protected ScheduledThreadPoolExecutor _scheduledEcutor; protected org.springframework.context.support.AbstractApplicationContext _springContext; protected org.springframework.context.support.AbstractApplicationContext _appSpringContext; protected Map<String, Participant> _participants = new ConcurrentHashMap<String, Participant>(); // remote join protected Registry _registry = null; protected RemoteCommunicationImpl _remoteCommunication; protected String _remoteCommunicationRMIAddress; protected int _remoteCommunicationPort = 4231; protected String _remoteCommunicationAddress = NetworkUtils.getLocalAddress().toString(); protected String _remoteObject; protected String _schema = "moho"; protected Map<String, Mixer> _mixerNameMap = new ConcurrentHashMap<String, Mixer>(); private MediaDialect _dialect; @SuppressWarnings("unchecked") public ApplicationContextImpl(final Application app, final MsControlFactory mc, final SipServlet servlet) { super(); _context = this; _application = app; _mcFactory = mc; _sip = servlet; _servletContext = _sip.getServletContext(); _sipFactory = (SipFactory) _servletContext.getAttribute(SipServlet.SIP_FACTORY); _sdpFactory = (SdpFactory) _servletContext.getAttribute("javax.servlet.sdp.SdpFactory"); _xmppFactory = (XmppFactory) _servletContext.getAttribute(XmppServlet.XMPP_FACTORY); String serviceContextFilePath = "WEB-INF/service-context.xml"; final Enumeration<String> e = servlet.getInitParameterNames(); while (e.hasMoreElements()) { final String name = e.nextElement(); final String value = servlet.getInitParameter(name); setParameter(name, value); if (name.equalsIgnoreCase("service-context-file")) { serviceContextFilePath = value; } } try { String realPath = servlet.getServletContext().getRealPath(serviceContextFilePath); File file = new File(realPath); if (file.exists()) { _appSpringContext = new FileSystemXmlApplicationContext("file:" + realPath); } } catch (Exception ex) { LOG.warn("Error when loading service-context-file at:" + serviceContextFilePath, ex); } try { registerDriver(ProtocolDriver.PROTOCOL_SIP, SIPDriverImpl.class.getName()); registerDriver(ProtocolDriver.PROTOCOL_VXML, VoiceXMLDriverImpl.class.getName()); registerDriver(ProtocolDriver.PROTOCOL_CONF, ConferenceDriverImpl.class.getName()); } catch (Exception ex) { LOG.error("Moho is unable to register drivers: " + ex, ex); } for (String name : getProtocolFamilies()) { ProtocolDriver d = getDriverByProtocolFamily(name); LOG.info("Moho is initializing driver[" + d + "]"); d.init(this); } getServletContext().setAttribute(ApplicationContext.APPLICATION, app); getServletContext().setAttribute(ApplicationContext.APPLICATION_CONTEXT, this); getServletContext().setAttribute(ApplicationContext.FRAMEWORK, this); if (app instanceof EventListener<?>) { addListener((EventListener<?>) app); } else { addObserver(app); } Class<? extends MediaDialect> mediaDialectClass = com.voxeo.moho.media.dialect.GenericDialect.class; final String mediaDialectClassName = getParameters().get("mediaDialectClass"); try { if (mediaDialectClassName != null) { mediaDialectClass = (Class<? extends MediaDialect>) Class.forName(mediaDialectClassName); } _dialect = mediaDialectClass.newInstance(); LOG.info("Moho is creating media service with dialect (" + _dialect + ")."); } catch (Exception ex) { LOG.error("Moho is unable to create media dialect (" + mediaDialectClassName + ")", ex); } int eventDispatcherCorePoolSize = getParameterValue("eventDispatcherThreadPoolSize", 50); int eventDispatcherMaxPoolSize = getParameterValue("eventDispatcherMaxThreadPoolSize", Integer.MAX_VALUE); int eventDispatcherThreadTimeout = getParameterValue("eventDispatcherThreadTimeout", 60); LOG.info("Moho is creating event dispatcher with " + eventDispatcherCorePoolSize + " core threads. Max threads: " + eventDispatcherMaxPoolSize + ". Thread timeout: " + eventDispatcherThreadTimeout); _executor = new InheritLogContextThreadPoolExecutor(eventDispatcherCorePoolSize, eventDispatcherMaxPoolSize, eventDispatcherThreadTimeout, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new DaemonThreadFactory("MohoContext")); _scheduledEcutor = new ScheduledThreadPoolExecutor(10, new DaemonThreadFactory("MohoContext")); _dispatcher.setExecutor(_executor, false); _springContext = new ClassPathXmlApplicationContext("classpath:moho-service-context.xml"); Collection<Service> beans = null; if (_appSpringContext != null) { beans = _appSpringContext.getBeansOfType(Service.class).values(); for (Service service : beans) { try { service.init(this, getParameters()); } catch (Exception e1) { LOG.error("Error when initialize service" + service, e1); } } } beans = _springContext.getBeansOfType(Service.class).values(); for (Service service : beans) { try { service.init(this, getParameters()); } catch (Exception e1) { LOG.error("Error when initialize service" + service, e1); } } _msFactory = this.getService(MediaServiceFactory.class); _confMgr = this.getService(ConferenceManager.class); if (getParameter("remoteCommunicationAddress") != null) { _remoteCommunicationAddress = getParameter("remoteCommunicationAddress"); LOG.debug("Using remoteCommunicationAddress configuration:" + getParameter("remoteCommunicationAddress")); } else { if (_remoteCommunicationAddress.startsWith("/")) { _remoteCommunicationAddress = _remoteCommunicationAddress.substring(1); } LOG.debug("No remoteCommunicationAddress configuration, using the default:" + _remoteCommunicationAddress); } SDPUtils.init(this); // try { // if (getParameter("remoteCommunicationPort") != null) { // try { // _remoteCommunicationPort = Integer.valueOf(getParameter("remoteCommunicationPort")); // } // catch (NumberFormatException ex) { // LOG.warn("Wrong remoteCommunicationPort configuration:" + getParameter("remoteCommunicationPort") // + ", using the default:" + _remoteCommunicationPort); // } // } // // if (getParameter("remoteCommunicationAddress") != null) { // _remoteCommunicationAddress = getParameter("remoteCommunicationAddress"); // LOG.debug("Using remoteCommunicationAddress configuration:" + getParameter("remoteCommunicationAddress")); // } // else { // LOG.debug("No remoteCommunicationAddress configuration, using the default:" + _remoteCommunicationAddress); // } // // if (_remoteCommunicationAddress.startsWith("/")) { // _remoteCommunicationAddress = _remoteCommunicationAddress.substring(1); // } // _remoteObject = "RemoteCommunication"; // _remoteCommunication = new RemoteCommunicationImpl(this); // _registry = LocateRegistry.createRegistry(4231); // RemoteCommunication stub = (RemoteCommunication) UnicastRemoteObject.exportObject(_remoteCommunication, 0); // _registry.rebind(_remoteObject, stub); // _remoteCommunicationRMIAddress = "rmi://" + _remoteCommunicationAddress + ":" + _remoteCommunicationPort + "/" // + _remoteObject; // } // catch (RemoteException ex) { // LOG.error("Error when initialize remote communication", ex); // } } private int getParameterValue(String param, int defaultValue) { int intValue = defaultValue; final String paramValue = getParameter(param); if (paramValue != null) { intValue = Integer.valueOf(paramValue); } return intValue; } @Override public Application getApplication() { return _application; } private Endpoint getEndpoint(final String addr, String type) { if (addr == null) { throw new IllegalArgumentException("argument is null"); } if(!addr.contains(":")){ throw new IllegalArgumentException("Address must be in the form of URL or <URL>. [" + addr + "] is an invalid address"); } String schema = addr.split(":")[0]; if (schema == null || schema.trim().length() == 0) { throw new IllegalArgumentException("Address not prepended with schema. [" + addr + "]"); } //check if address include display name, ex: "name <sip:6505551234@192.168.57.6>" int laquot = schema.indexOf("<"); if(laquot > 0) { schema = schema.substring(laquot + 1); } LOG.debug("Getting driver by endpoint schema [" + schema +"]"); ProtocolDriver driver = getDriverByEndpointSechma(schema); if (driver == null) { throw new IllegalArgumentException("No suitable driver for this address format. [" + addr + "]"); } return driver.createEndpoint(addr); } @Override public Endpoint createEndpoint(String endpoint, String type) { return getEndpoint(endpoint, type); } @Override public Endpoint createEndpoint(String endpoint) { return getEndpoint(endpoint, null); } @Override public MsControlFactory getMSFactory() { return _mcFactory; } @Override public SipFactory getSipFactory() { return _sipFactory; } @Override public ConferenceManager getConferenceManager() { return _confMgr; } public void setConferenceManager(ConferenceManager conferenceManager) { _confMgr = conferenceManager; } @Override public Executor getExecutor() { return _executor; } @Override public Call getCall(final String cid) { Participant p = _participants.get(cid); if (p != null && p instanceof Call) { return (Call) p; } return null; } @Override public void addCall(final Call call) { _participants.put(call.getId(), call); } @Override public void removeCall(final String id) { _participants.remove(id); } @Override public String getParameter(final String name) { return _parameters.get(name); } @Override public Map<String, String> getParameters() { return Collections.unmodifiableMap(_parameters); } public void setParameter(final String name, final String value) { _parameters.put(name, value); } @Override public SdpFactory getSdpFactory() { return _sdpFactory; } @Override public ServletContext getServletContext() { return _servletContext; } @Override public String getRealPath(final String path) { return getServletContext().getRealPath(path); } @Override public void destroy() { getApplication().destroy(); _executor.shutdown(); Collection<ProtocolDriver> drivers = _driversByProtocol.values(); for (ProtocolDriver driver : drivers) { driver.destroy(); } Collection<Service> beans = _springContext.getBeansOfType(Service.class).values(); for (Service service : beans) { service.destroy(); } // try { // UnicastRemoteObject.unexportObject(_remoteCommunication, true); // UnicastRemoteObject.unexportObject(_registry, true); // } // catch (NoSuchObjectException e) { // LOG.warn("", e); // } } @Override public MediaServiceFactory getMediaServiceFactory() { return _msFactory; } @Override public SpiFramework getFramework() { return this; } @Override public void registerDriver(String protocol, String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException { ProtocolDriver driver = createProvider(className); registerDriver(driver); } protected void registerDriver(ProtocolDriver driver) { String protocol = driver.getProtocolFamily(); _driversByProtocol.put(protocol, driver); for (String schema : driver.getEndpointSchemas()) { _driversBySchema.put(schema, driver); } } @SuppressWarnings("rawtypes") private ProtocolDriver createProvider(String name) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class clz = null; try { clz = this.getClass().getClassLoader().loadClass(name); } catch (final Throwable t) { clz = Thread.currentThread().getContextClassLoader().loadClass(name); } return (ProtocolDriver) clz.newInstance(); } @Override public String[] getProtocolFamilies() { Set<String> s = _driversByProtocol.keySet(); return s.toArray(new String[s.size()]); } @Override public String[] getEndpointSchemas() { Set<String> s = _driversBySchema.keySet(); return s.toArray(new String[s.size()]); } @Override public ProtocolDriver getDriverByProtocolFamily(String protocol) { return _driversByProtocol.get(protocol); } @Override public ProtocolDriver getDriverByEndpointSechma(String schema) { return _driversBySchema.get(schema); } @Override public ExecutionContext getExecutionContext() { return _context; } @Override public SipServlet getSIPController() { return _sip; } public void setSIPController(SipServlet sip) { _sip = sip; } @Override public HttpServlet getHTTPController() { return _http; } public void setHTTPController(HttpServlet http) { _http = http; } @Override public <T extends Service> T getService(Class<T> def) { return find(def); } private <T> T find(Class<T> clazz) { Collection<T> beans = null; if (_appSpringContext != null) { beans = _appSpringContext.getBeansOfType(clazz).values(); if (!beans.isEmpty()) { return beans.iterator().next(); } } beans = _springContext.getBeansOfType(clazz).values(); if (!beans.isEmpty()) { return beans.iterator().next(); } return null; } @Override public <T extends Service> Collection<T> listServices() { Collection<T> ret = new ArrayList<T>(); if (_appSpringContext != null) { ret.addAll((Collection<T>) _appSpringContext.getBeansOfType(Service.class).values()); } ret.addAll((Collection<T>) _springContext.getBeansOfType(Service.class).values()); return ret; } @Override public <T extends Service> boolean containsService(Class<T> def) { return this.find(def) != null; } @Override public XmppServlet getXMPPController() { return _xmpp; } public void setXMPPController(XmppServlet xmpp) { _xmpp = xmpp; } @Override public XmppFactory getXmppFactory() { return _xmppFactory; } @Override public Participant getParticipant(String id) { String ip = ParticipantIDParser.getIpAddress(id); if (ip.equalsIgnoreCase(_remoteCommunicationAddress)) { return _participants.get(id); } else { return new RemoteParticipantImpl(this, id); } } public void addParticipant(Participant participant) { _participants.put(participant.getId(), participant); if (participant instanceof Mixer) { String name = ((Mixer) participant).getName(); if (name != null) { _mixerNameMap.put(name, ((Mixer) participant)); } } } public void removeParticipant(String id) { Participant participant = _participants.remove(id); if (participant instanceof Mixer) { String name = ((Mixer) participant).getName(); if (name != null) { _mixerNameMap.remove(name); } } } public Mixer getMixerByName(String name) { return _mixerNameMap.get(name); } public String generateID(String type, String id) { // TODO ADDRESS String compsitePart = null; if(_remoteCommunicationAddress.startsWith("/")){ compsitePart = ":/" + _remoteCommunicationAddress; } else{ compsitePart = "://" + _remoteCommunicationAddress; } String remoteAddress = _schema + compsitePart + ":" + _remoteCommunicationPort + "/" + type + "/" + id; return remoteAddress; } public RemoteCommunicationImpl getRemoteCommunication() { return _remoteCommunication; } public String getRemoteCommunicationRMIAddress() { return _remoteCommunicationRMIAddress; } public int getRemoteCommunicationPort() { return _remoteCommunicationPort; } public String getRemoteCommunicationAddress() { return _remoteCommunicationAddress; } public MediaDialect getDialect() { return _dialect; } public ScheduledThreadPoolExecutor getScheduledEcutor() { return _scheduledEcutor; } }