/**
* 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.sip;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.media.mscontrol.MsControlFactory;
import javax.sdp.SdpFactory;
import javax.servlet.ServletException;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServlet;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.URI;
import org.apache.log4j.Logger;
import com.voxeo.moho.Call;
import com.voxeo.moho.Endpoint;
import com.voxeo.moho.Framework;
import com.voxeo.moho.IncomingCall;
import com.voxeo.moho.Participant;
import com.voxeo.moho.State;
import com.voxeo.moho.Subscription;
import com.voxeo.moho.common.event.MohoInputDetectedEvent;
import com.voxeo.moho.event.CallEvent;
import com.voxeo.moho.event.EventSource;
import com.voxeo.moho.event.JoinCompleteEvent;
import com.voxeo.moho.event.JoinCompleteEvent.Cause;
import com.voxeo.moho.event.NotifyEvent;
import com.voxeo.moho.event.Observer;
import com.voxeo.moho.event.PublishEvent;
import com.voxeo.moho.event.RegisterEvent;
import com.voxeo.moho.event.RequestEvent;
import com.voxeo.moho.event.SubscribeEvent;
import com.voxeo.moho.event.TextEvent;
import com.voxeo.moho.event.UnknownRequestEvent;
import com.voxeo.moho.reg.Registration;
import com.voxeo.moho.remote.sipbased.Constants;
import com.voxeo.moho.remote.sipbased.RemoteJoinIncomingCall;
import com.voxeo.moho.spi.ExecutionContext;
import com.voxeo.moho.spi.SIPDriver;
import com.voxeo.moho.spi.SpiFramework;
import com.voxeo.moho.util.SessionUtils;
public class SIPDriverImpl implements SIPDriver {
private static final Logger LOG = Logger.getLogger(SIPDriverImpl.class);
protected Framework _app = null;
protected SipFactory _sipFacory;
protected SdpFactory _sdpFactory;
protected MsControlFactory _mscFactory;
protected SipServlet _servlet;
protected static final String[] SCHEMAS = new String[] {"sip", "tel", "sips", "<sip", "<tel", "<sips", "fax", "<fax:"};
@Override
public void init(SpiFramework framework) {
_app = framework;
_sipFacory = framework.getExecutionContext().getSipFactory();
_sdpFactory = framework.getExecutionContext().getSdpFactory();
_mscFactory = framework.getExecutionContext().getMSFactory();
_servlet = framework.getSIPController();
}
@Override
public void destroy() {
}
@Override
public void doRequest(final SipServletRequest req) throws ServletException, IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Received request:" + req.toString().replace("\\", "\\\\").replace("\r", "\\r").replace("\n", "\\n"));
}
final String s = req.getMethod();
if ("INVITE".equals(s)) {
doInvite(req);
}
else if ("ACK".equals(s)) {
doAck(req);
}
else if ("OPTIONS".equals(s)) {
doOptions(req);
}
else if ("BYE".equals(s)) {
doBye(req);
}
else if ("CANCEL".equals(s)) {
doCancel(req);
}
else if ("REGISTER".equals(s)) {
doRegister(req);
}
else if ("SUBSCRIBE".equals(s)) {
doSubscribe(req);
}
else if ("NOTIFY".equals(s)) {
doNotify(req);
}
else if ("MESSAGE".equals(s)) {
doMessage(req);
}
else if ("INFO".equals(s)) {
doInfo(req);
}
else if ("UPDATE".equals(s)) {
doUpdate(req);
}
else if ("REFER".equals(s)) {
doRefer(req);
}
else if ("PUBLISH".equals(s)) {
doPublish(req);
}
else if ("PRACK".equals(s)) {
doPrack(req);
}
else {
doOthers(req);
}
}
protected void doInvite(final SipServletRequest req) throws ServletException, IOException {
if (req.isInitial()) {
// process remote join
if (req.getHeader(Constants.x_Join_Direction) != null && req.getHeader(Constants.x_Join_Direction).trim() != "") {
RemoteJoinIncomingCall joinCall = new RemoteJoinIncomingCall((ExecutionContext) this.getFramework()
.getApplicationContext(), req);
SipURI joineeURI = joinCall.getJoinee();
Participant participant = this.getFramework().getApplicationContext().getParticipant(joineeURI.getUser());
joinCall.addObserver(new Observer() {
@State
public void handleJoinComplete(JoinCompleteEvent event) {
if (event.getCause() == Cause.BUSY) {
LOG.warn("Join Policy violated when processing incoming remotejoin.");
try {
req.createResponse(SipServletResponse.SC_BUSY_HERE,
event.getException() != null ? event.getException().getMessage() : "").send();
}
catch (Exception ex) {
LOG.warn("Exception when sending back remotejoin response.", ex);
}
}
}
});
LOG.debug("Received remotejoin, joining. joiner:" + joinCall.getJoiner().getUser() + ". joinee:"
+ joinCall.getJoinee().getUser() + ". created RemoteJoinIncomingCall:" + joinCall);
joinCall.join(participant, joinCall.getX_Join_Type(), joinCall.getX_Join_Force(),
joinCall.getX_Join_Direction());
}
else {
final IncomingCall ev = _app.getApplicationContext().getService(IncomingCallFactory.class)
.createIncomingCall(req);
_app.dispatch(ev);
}
}
else {
final EventSource source = SessionUtils.getEventSource(req);
if (source != null && source instanceof Call) {
source.dispatch(new SIPReInviteEventImpl((Call) source, req));
}
else{
LOG.warn("Can't recognize event source for re-INVITE request:" + source + " request:" + req);
}
}
}
protected void doBye(final SipServletRequest req) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(req);
if (source != null && source instanceof SIPCall) {
source.dispatch(new SIPHangupEventImpl((SIPCall) source, req));
}
else {
LOG.warn("Can't recognize event source for BYE request:" + source + " request:" + req);
}
}
protected void doAck(final SipServletRequest req) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(req);
if (source instanceof SIPCallImpl) {
final SIPCallImpl call = (SIPCallImpl) source;
try {
call.doAck(req);
}
catch (final Exception e) {
LOG.warn("", e);
}
}
else{
LOG.warn("Can't recognize event source for ACK request:" + source + " request:" + req);
}
}
protected void doPrack(final SipServletRequest req) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(req);
if (source instanceof SIPIncomingCall) {
final SIPIncomingCall call = (SIPIncomingCall) source;
try {
call.doPrack(req);
}
catch (final Exception e) {
LOG.warn("", e);
}
}
}
protected void doCancel(final SipServletRequest req) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(req);
if (source instanceof SIPIncomingCall) {
final SIPIncomingCall call = (SIPIncomingCall) source;
try {
call.doCancel(req);
}
catch (final Exception e) {
LOG.warn("", e);
}
}
// if (source != null) {
// source.dispatch(new SIPCancelEventImpl((SIPCall) source, req));
// }
// else{
// LOG.warn("Can't find event source for CANCEL request:" + req);
// req.createResponse(200).send();
// }
}
protected void doRefer(final SipServletRequest req) throws ServletException, IOException {
EventSource source = null;
if (!req.isInitial()) {
source = SessionUtils.getEventSource(req);
}
if (source != null) {
if (source instanceof SIPCall) {
final CallEvent event = new SIPReferEventImpl((SIPCall) source, req);
source.dispatch(event);
}
else {
LOG.warn("SIP Refer is received on an unknown source: " + source);
}
}
else {
LOG.warn("SIP Refer is received as an initial message.");
}
}
protected void doNotify(final SipServletRequest req) throws ServletException, IOException {
EventSource source = null;
if (!req.isInitial()) {
source = SessionUtils.getEventSource(req);
}
if (source != null) {
final NotifyEvent<EventSource> event = new SIPNotifyEventImpl<EventSource>(source, req);
source.dispatch(event);
}
else {
final NotifyEvent<EventSource> event = new SIPNotifyEventImpl<EventSource>(_app, req);
_app.dispatch(event, new NoHandleHandler<EventSource>(event, req));
}
}
protected void doMessage(final SipServletRequest req) throws ServletException, IOException {
req.createResponse(200).send();
final String type = req.getContentType();
if (type != null && type.startsWith("text/")) {
EventSource source = null;
if (!req.isInitial()) {
source = SessionUtils.getEventSource(req);
}
if (source != null) {
final TextEvent<EventSource> event = new SIPTextEventImpl<EventSource>(source, req);
source.dispatch(event);
}
else {
final TextEvent<EventSource> event = new SIPTextEventImpl<EventSource>(_app, req);
_app.dispatch(event, new SessionDisposer(req));
}
}
else {
try {
req.getApplicationSession().invalidate();
}
catch (final Throwable t) {
;
}
}
}
protected void doRegister(final SipServletRequest req) throws ServletException, IOException {
URI uri = req.getRequestURI();
if (uri.isSipURI()) {
SipURI suri = (SipURI) uri;
if (suri.getUser() == null) {
final RegisterEvent event = new SIPRegisterEventImpl(_app, req);
_app.dispatch(event, new NoHandleHandler<Framework>(event, req));
return;
}
}
req.createResponse(SipServletResponse.SC_BAD_REQUEST, "Invalid Request URI").send();
}
protected void doSubscribe(final SipServletRequest req) throws ServletException, IOException {
final SubscribeEvent event = new SIPSubscribeEventImpl(_app, req);
_app.dispatch(event, new NoHandleHandler<Framework>(event, req));
}
protected void doUpdate(final SipServletRequest req) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(req);
if (source != null) {
source.dispatch(new SIPUpdateEventImpl((Call) source, req));
}
else{
LOG.warn("Can't find call for UPDATE message, discarding:" +req);
}
}
protected void doOptions(final SipServletRequest req) throws ServletException, IOException {
doOthers(req);
}
protected void doInfo(final SipServletRequest req) throws ServletException, IOException {
req.createResponse(200).send();
final String type = req.getContentType();
if (type != null && type.equalsIgnoreCase("application/dtmf-relay") || type.equalsIgnoreCase("application/dtmf")) {
EventSource source = SessionUtils.getEventSource(req);
if (source != null && source instanceof Call) {
final MohoInputDetectedEvent<Call> event = new MohoInputDetectedEvent<Call>((Call) source, getDTMFValue(req));
source.dispatch(event);
}
}
}
private String getDTMFValue(SipServletRequest req) throws IOException {
final String type = req.getContentType();
String content = new String(req.getRawContent(), "UTF-8").trim();
if (type.equalsIgnoreCase("application/dtmf-relay")) {
Matcher matcher = DtmfRelayPattern.pattern.matcher(content);
return matcher.group(1);
}
return content;
}
private static class DtmfRelayPattern {
public static Pattern pattern = Pattern.compile("Signal\\s*=\\s*([\\d|A|B|C|D|\\*|#])");
}
protected void doPublish(final SipServletRequest req) throws ServletException, IOException {
final PublishEvent event = new SIPPublishEventImpl(getFramework(), req);
_app.dispatch(event, new NoHandleHandler<Framework>(event, req));
}
protected void doOthers(final SipServletRequest req) {
EventSource source = null;
if (!req.isInitial()) {
source = SessionUtils.getEventSource(req);
}
if (source != null) {
if (source instanceof Call) {
final UnknownRequestEvent<Call> event = new SIPUnknownRequestEventImpl<Call>((Call) source, req);
source.dispatch(event);
}
else if (source instanceof Framework) {
final UnknownRequestEvent<Framework> event = new SIPUnknownRequestEventImpl<Framework>((Framework) source, req);
source.dispatch(event);
}
else {
final UnknownRequestEvent<EventSource> event = new SIPUnknownRequestEventImpl<EventSource>(source, req);
source.dispatch(event);
}
}
else {
final UnknownRequestEvent<Framework> event = new SIPUnknownRequestEventImpl<Framework>((Framework) _app, req);
_app.dispatch(event, new NoHandleHandler<Framework>(event, req));
}
}
@Override
public void doResponse(final SipServletResponse res) throws ServletException, IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Received response:" + res.toString().replace("\\", "\\\\").replace("\r", "\\r").replace("\n", "\\n"));
}
final EventSource source = SessionUtils.getEventSource(res);
if (source != null) {
final int i = res.getStatus();
if (i < 200) {
doProvisionalResponse(res);
}
else if (i < 300) {
doSuccessResponse(res);
}
else if (res.isBranchResponse()) {
doBranchResponse(res);
}
else if (i < 400) {
doRedirectResponse(res);
}
else {
doErrorResponse(res);
}
}
else {
final SipServletRequest req = (SipServletRequest) SIPHelper.getLinkSIPMessage(res.getRequest());
if (req != null) {
final SipServletResponse newRes = req.createResponse(res.getStatus(), res.getReasonPhrase());
SIPHelper.copyContent(res, newRes);
newRes.send();
}
else{
LOG.warn(res + " can't find event source, and no linked sip message, discarding it.");
}
}
}
protected void doBranchResponse(final SipServletResponse res) throws ServletException, IOException {
// do nothing right now
}
protected void doProvisionalResponse(final SipServletResponse res) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(res);
if (source != null) {
if (source instanceof SIPCall) {
final int status = res.getStatus();
if (SIPHelper.getRawContentWOException(res) != null && SIPHelper.needPrack(res)) {
source.dispatch(new SIPEarlyMediaEventImpl((SIPCall) source, res));
}
else if (status != SipServletResponse.SC_TRYING) {
if (source instanceof SIPCallImpl) {
final SIPCallImpl call = (SIPCallImpl) source;
try {
call.doResponse(res, null);
}
catch (final Exception e) {
LOG.warn("", e);
}
}
source.dispatch(new SIPRingEventImpl((SIPCall) source, res));
}
}
else {
LOG.warn(res + " is received for a non SIP Call source.");
}
}
else{
LOG.warn(res + " can't find event source: " + source);
}
}
protected void doSuccessResponse(final SipServletResponse res) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(res);
if (source != null) {
if (source instanceof SIPCallImpl) {
final SIPCallImpl call = (SIPCallImpl) source;
try {
call.doResponse(res, null);
}
catch (final Exception e) {
LOG.warn("", e);
}
if (SIPHelper.isInitial(res.getRequest())) {
source.dispatch(new SIPAnsweredEventImpl<Call>((SIPCall) source, res));
}
return;
}
else if (source instanceof Framework) {
source.dispatch(new SIPAnsweredEventImpl<Framework>((Framework) source, res));
return;
}
else if (source instanceof Registration) {
source.dispatch(new SIPAnsweredEventImpl<Registration>((Registration) source, res));
return;
}
else if (source instanceof Subscription) {
source.dispatch(new SIPAnsweredEventImpl<Subscription>((Subscription) source, res));
return;
}
else{
LOG.trace(res + " is received for a unknow source, dispatching: " + source);
source.dispatch(new SIPAnsweredEventImpl(source, res));
}
}
else{
LOG.warn(res + " can't find event source: " + source);
}
}
protected void doRedirectResponse(final SipServletResponse res) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(res);
if (source != null) {
if (source instanceof SIPCall) {
source.dispatch(new SIPRedirectEventImpl<Call>((SIPCall) source, res));
return;
}
else if (source instanceof Framework) {
source.dispatch(new SIPRedirectEventImpl<Framework>((Framework) source, res));
return;
}
else if (source instanceof Registration) {
source.dispatch(new SIPRedirectEventImpl<Registration>((Registration) source, res));
return;
}
else if (source instanceof Subscription) {
source.dispatch(new SIPRedirectEventImpl<Subscription>((Subscription) source, res));
return;
}
else {
LOG.trace(res + " is received for a unknow source, dispatching: " + source);
source.dispatch(new SIPRedirectEventImpl( source, res));
}
}
else{
LOG.warn(res + " can't find event source: " + source);
}
}
protected void doErrorResponse(final SipServletResponse res) throws ServletException, IOException {
final EventSource source = SessionUtils.getEventSource(res);
if(source != null){
if (source instanceof SIPCallImpl) {
final SIPCallImpl call = (SIPCallImpl) source;
source.dispatch(new SIPDeniedEventImpl<Call>((SIPCall) source, res));
try {
call.doResponse(res, null);
}
catch (final Exception e) {
LOG.warn("", e);
}
}
else if (source instanceof Registration) {
source.dispatch(new SIPDeniedEventImpl<Registration>((Registration) source, res));
}
else if (source instanceof Subscription) {
source.dispatch(new SIPDeniedEventImpl<Subscription>((Subscription) source, res));
}
else {
LOG.trace(res + " is received for a unknow source, dispatching: " + source);
source.dispatch(new SIPDeniedEventImpl( source, res));
}
}
else{
LOG.warn(res + " can't find event source: " + source);
}
}
private class NoHandleHandler<T extends EventSource> implements Runnable {
private RequestEvent<T> _event;
private SipServletRequest _req;
private boolean _invalidate = false;
public NoHandleHandler(final RequestEvent<T> event, final SipServletRequest req) {
this(event, req, false);
}
public NoHandleHandler(final RequestEvent<T> event, final SipServletRequest req, final boolean invalidate) {
_event = event;
_req = req;
}
public void run() {
if (!_event.isProcessed() && _req.isInitial() && !_req.isCommitted()) {
try {
_req.createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR, "Request not handled by app").send();
}
catch (final Throwable t) {
LOG.warn("", t);
}
}
if (_invalidate) {
try {
_req.getApplicationSession().invalidate();
}
catch (final Throwable t) {
LOG.warn("", t);
}
}
}
}
private class SessionDisposer implements Runnable {
private SipServletRequest _req;
public SessionDisposer(final SipServletRequest req) {
_req = req;
}
@Override
public void run() {
if (_req.isInitial()) {
try {
_req.getApplicationSession().invalidate();
}
catch (final Throwable t) {
LOG.warn("", t);
}
}
}
}
@Override
public String getProtocolFamily() {
return PROTOCOL_SIP;
}
@Override
public String[] getEndpointSchemas() {
return SCHEMAS;
}
@Override
public SpiFramework getFramework() {
return (SpiFramework) _app;
}
@Override
public Endpoint createEndpoint(String addr) {
try {
if (addr.startsWith("sip:") || addr.startsWith("sips:") || addr.startsWith("tel:") || addr.startsWith("fax:")) {
return new SIPEndpointImpl((ExecutionContext) _app, _sipFacory.createAddress(_sipFacory.createURI(addr)));
}
else {
return new SIPEndpointImpl((ExecutionContext) _app, _sipFacory.createAddress(addr));
}
}
catch (ServletParseException e) {
LOG.error("", e);
throw new IllegalArgumentException("not a legal sip address:" + addr, e);
}
}
}