<commit_before>package com.almende.dialog.adapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.znerd.xmlenc.XMLOutputter;
import com.almende.dialog.LogLevel;
import com.almende.dialog.Settings;
import com.almende.dialog.accounts.AdapterConfig;
import com.almende.dialog.adapter.tools.Broadsoft;
import com.almende.dialog.agent.AdapterAgent;
import com.almende.dialog.model.Answer;
import com.almende.dialog.model.MediaProperty.MediaPropertyKey;
import com.almende.dialog.model.MediaProperty.MediumType;
import com.almende.dialog.model.Question;
import com.almende.dialog.model.Session;
import com.almende.dialog.model.ddr.DDRRecord;
import com.almende.dialog.util.DDRUtils;
import com.almende.dialog.util.ServerUtils;
import com.almende.dialog.util.TimeUtils;
import com.almende.util.myBlobstore.MyBlobStore;
import com.askfast.commons.entity.AccountType;
import com.askfast.commons.utils.PhoneNumberUtils;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;

@Path("/vxml/")
public class VoiceXMLRESTProxy {
	protected static final Logger log = Logger.getLogger(VoiceXMLRESTProxy.class.getName());
	protected static final com.almende.dialog.Logger dialogLog =  new com.almende.dialog.Logger();
	private static final int LOOP_DETECTION=10;
	private static final String DTMFGRAMMAR="dtmf2hash";
	private static final String PLAY_TRIAL_AUDIO_KEY = "playTrialAccountAudio";
	
	private static final int MAX_RETRIES=1;
	
	protected String TIMEOUT_URL="timeout";
	protected String EXCEPTION_URL="exception";
	private String host = "";
	
	public static void killSession(Session session){
		
		AdapterConfig config = session.getAdapterConfig();
		if(config!=null) {
			Broadsoft bs = new Broadsoft(config);
			bs.endCall(session.getExternalSession());
		}
	}
	
    /**
     * @Deprecated. Use broadcast calling mechanism instead. <br>
     *              {@link VoiceXMLRESTProxy#dial(Map, String, String, AdapterConfig)
     *              dial} method
     * 
     * @param address
     * @param url
     * @param config
     * @return
     */
    @Deprecated
    public static String dial(String address, String url, AdapterConfig config) {

        try {
            address = PhoneNumberUtils.formatNumber(address, PhoneNumberFormat.E164);
        }
        catch (Exception e) {
            log.severe(String.format("Phonenumber: %s is not valid", address));
            return "";
        }

        //avoid multiple calls to be made to the same number, from the same adapter. 
        Session session = Session.getSession(Session.getSessionKey(config, address));
        if(session != null) {
            String responseMessage = checkIfCallAlreadyInSession(address, config, session);
            if(responseMessage != null) {
                return responseMessage;
            }
            else {
                //create a session
                session = Session.getOrCreateSession(config, address);
            }
        }
        else {
            session = Session.getOrCreateSession(config, address);
        }
        session.killed = false;
        session.setStartUrl(url);
        session.setDirection("outbound");
        session.setRemoteAddress(address);
        session.setType(AdapterAgent.ADAPTER_TYPE_BROADSOFT);
        
        Question question = Question.fromURL(url, config.getConfigId(), address, config.getMyAddress(),
                                             session.getDdrRecordId(), session.getKey());
        session.setQuestion(question);
        session.storeSession();

        dialogLog.log(LogLevel.INFO, config,
                      String.format("Call started from: %s to: %s", config.getMyAddress(), address), session);
        Broadsoft bs = new Broadsoft(config);
        bs.startSubscription();

        String extSession = bs.startCall(address + "@outbound", session);

        session.setExternalSession(extSession);
        session.storeSession();

        return session.getKey();
    }

    /**
     * initiates a call to all the numbers in the addressNameMap and returns a
     * Map of <adress, SessionKey>
     * @return
     */
    public static HashMap<String, String> dial( Map<String, String> addressNameMap, String url, String senderName, AdapterConfig config )
    throws Exception
    {
        HashMap<String, String> resultSessionMap = new HashMap<String, String>();
        // If it is a broadcast don't provide the remote address because it is deceiving.
        String loadAddress = "";
        if(addressNameMap.size()==1)
            loadAddress = addressNameMap.keySet().iterator().next();

        //fetch the question
        Question question = Question.fromURL( url, config.getConfigId(), loadAddress, config.getMyAddress(), null, null );
        for ( String address : addressNameMap.keySet() )
        {
            try
            {
                String formattedAddress = PhoneNumberUtils.formatNumber( address, PhoneNumberFormat.E164 );
                //avoid multiple calls to be made to the same number, from the same adapter. 
                Session session = Session.getSession(Session.getSessionKey(config, formattedAddress));
                if(session != null) {
                    String responseMessage = checkIfCallAlreadyInSession(formattedAddress, config, session);
                    if(responseMessage != null) {
                        resultSessionMap.put(formattedAddress, responseMessage);
                        continue;
                    }
                    else {
                        //create a session
                        session = Session.getOrCreateSession(config, formattedAddress);
                    }
                }
                else {
                    session = Session.getOrCreateSession(config, formattedAddress);
                }
                session.killed=false;
                session.setStartUrl(url);
                session.setDirection("outbound");
                session.setRemoteAddress(formattedAddress);
                session.setType(AdapterAgent.ADAPTER_TYPE_BROADSOFT);
                session.setAdapterID(config.getConfigId());
                session.setQuestion( question );
                dialogLog.log(LogLevel.INFO, session.getAdapterConfig(), String.format("Outgoing call from: %s to: %s",
                                                                                       session.getLocalAddress(),
                                                                                       formattedAddress), session);
                String extSession = "";
                if ( !ServerUtils.isInUnitTestingEnvironment() )
                {
                    Broadsoft bs = new Broadsoft( config );
                    String subscriptiion = bs.startSubscription();
                    log.info(String.format("Calling subscription complete. Message: %s. Starting call.. ",
                                           subscriptiion));
                    extSession = bs.startCall( formattedAddress + "@outbound", session );
                }
                session.setExternalSession( extSession );
                session.storeSession();
                resultSessionMap.put( formattedAddress, session.getKey() );
            }
            catch ( Exception e )
            {
                log.severe( String.format( "Phonenumber: %s is not valid", address ) );
            }
        }
        return resultSessionMap;
    }
	
	public static ArrayList<String> getActiveCalls(AdapterConfig config) {
		Broadsoft bs = new Broadsoft(config);
		return bs.getActiveCalls();
	}
	
	public static ArrayList<String> getActiveCallsInfo(AdapterConfig config) {
		Broadsoft bs = new Broadsoft(config);
		return bs.getActiveCallsInfo();
	}
	
	public static boolean killActiveCalls(AdapterConfig config) {
		Broadsoft bs = new Broadsoft(config);
		return bs.killActiveCalls();
	}
	
	@Path("dtmf2hash")
	@GET
	@Produces("application/srgs+xml")
	public Response getDTMF2Hash(@QueryParam("minlength") String minLength, @QueryParam("maxlength") String maxLength) {
	    minLength = (minLength != null && !minLength.isEmpty()) ? minLength : "1";
	    maxLength = (maxLength != null && !maxLength.isEmpty()) ? maxLength : "";
	    String repeat = minLength.equals( maxLength ) ? minLength : (minLength + "-" +  maxLength);
		String result = "<?xml version=\"1.0\"?> "+
						"<grammar mode=\"dtmf\" version=\"1.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd\" xmlns=\"http://www.w3.org/2001/06/grammar\"  root=\"untilHash\" > "+
							"<rule id=\"digit\"> "+
								"<one-of> "+
									"<item> 0 </item> "+
									"<item> 1 </item> "+
									"<item> 2 </item> "+
									"<item> 3 </item> "+
									"<item> 4 </item> "+
									"<item> 5 </item> "+
									"<item> 6 </item> "+
									"<item> 7 </item> "+
									"<item> 8 </item> "+
									"<item> 9 </item> "+
									"<item> * </item> "+
								"</one-of> "+
							"</rule> "+
							"<rule id=\"untilHash\" scope=\"public\"> "+
								"<one-of> "+
									"<item repeat=\"" + repeat + "\"><ruleref uri=\"#digit\"/></item> "+
									"<item> # </item> "+
								"</one-of> "+
							"</rule> "+
						"</grammar> ";
		return Response.ok(result).build();
	}
	
    @Path("new")
    @GET
    @Produces("application/voicexml")
    public Response getNewDialog(@QueryParam("direction") String direction,@QueryParam("remoteID") String remoteID,@QueryParam("localID") String localID, @Context UriInfo ui)
    {
        log.info("call started:"+direction+":"+remoteID+":"+localID);
        this.host=ui.getBaseUri().toString().replace(":80", "");
        
        AdapterConfig config = AdapterConfig.findAdapterConfig(AdapterAgent.ADAPTER_TYPE_BROADSOFT, localID);
        String formattedRemoteId = remoteID;
        //format the remote number
        try
        {
            formattedRemoteId = PhoneNumberUtils.formatNumber( remoteID.split( "@" )[0], PhoneNumberFormat.E164 );
        }
        catch ( Exception e1 )
        {
            log.severe( "Remote number formatting failed: "+ remoteID.split( "@" )[0] );
        }
        String sessionKey = AdapterAgent.ADAPTER_TYPE_BROADSOFT+"|"+localID+"|"+ formattedRemoteId;
        Session session = Session.getSession(sessionKey);
        
        String url = "";
        if ( session != null && direction.equalsIgnoreCase("outbound")) {
                url = session.getStartUrl();
            dialogLog.log(LogLevel.INFO, config,
                          String.format("Outgoing Call from: %s to: %s", config.getMyAddress(), formattedRemoteId),
                          session);
        }
        else if(direction.equals("inbound")) {
            //create a session for incoming only
            session = Session.getOrCreateSession(config, formattedRemoteId);
            url = config.getURLForInboundScenario();
            Broadsoft bs = new Broadsoft( config );
            bs.startSubscription();
            dialogLog.log(LogLevel.INFO, config,
                          String.format("Incoming Call received from: %s at: %s", formattedRemoteId, config.getMyAddress()),
                          session);
        }
        
        if(session != null) {
            session.setStartUrl( url );
            session.setDirection( direction );
            session.setRemoteAddress( formattedRemoteId );
            session.setType( AdapterAgent.ADAPTER_TYPE_BROADSOFT );
            session.setAccountId( config.getOwner() );
            session.setAdapterID( config.getConfigId() );
        }
        else {
            log.severe(String.format("Session %s not found", sessionKey));
            return null;
        }
        
        Question question = session.getQuestion();
        if(question == null) {
            question = Question.fromURL(url, session.getAdapterConfig().getConfigId(), formattedRemoteId, localID,
                                        session.getDdrRecordId(), session.getKey());
        }
        session.setQuestion(question);
        
        if (session.getQuestion() != null) {
            //play trial account audio if the account is trial
            if(config.getAccountType() != null && config.getAccountType().equals(AccountType.TRIAL)){
                session.getExtras().put(PLAY_TRIAL_AUDIO_KEY, "true");
            }
            //create ddr record
            DDRRecord ddrRecord = null;
            try {
                if (direction.equalsIgnoreCase("outbound")) {
                    ddrRecord = DDRUtils.createDDRRecordOnOutgoingCommunication(config, formattedRemoteId, 1, url);
                }
                else {
                    ddrRecord = DDRUtils.createDDRRecordOnIncomingCommunication(config, formattedRemoteId, 1, url);
                }
                session.setDdrRecordId( ddrRecord != null ? ddrRecord.getId() : null);
                ddrRecord.addAdditionalInfo(Session.TRACKING_TOKEN_KEY, session.getTrackingToken());
            }
            catch (Exception e) {
                String errorMessage = String.format("Creating DDR records failed. Direction: %s for adapterId: %s with address: %s remoteId: %s and localId: %s",
                                                    direction, config.getConfigId(), config.getMyAddress(), formattedRemoteId,
                                                    localID);
                log.severe(errorMessage);
                dialogLog.severe(config.getConfigId(), errorMessage, ddrRecord != null ? ddrRecord.getId() : null,
                                 sessionKey);
            }
            finally {
            	ddrRecord.createOrUpdate();
                session.storeSession();
            }
            return handleQuestion( question, config, formattedRemoteId, sessionKey );
        }
        else {
            return Response.ok().build();
        }
    }
	
    @Path( "answer" )
    @GET
    @Produces( "application/voicexml+xml" )
    public Response answer( @QueryParam( "questionId" ) String question_id,
        @QueryParam( "answerId" ) String answer_id, @QueryParam( "answerInput" ) String answer_input,
        @QueryParam( "sessionKey" ) String sessionKey, @Context UriInfo ui )
    {
        try
        {
            answer_input = answer_input != null ? URLDecoder.decode( answer_input, "UTF-8" ) : answer_input;
        }
        catch ( UnsupportedEncodingException e )
        {
            log.warning( String.format( "Answer input decode failed for: %s", answer_input) );
        }
        this.host = ui.getBaseUri().toString().replace( ":80", "" );
        String reply = "<vxml><exit/></vxml>";
        Session session = Session.getSession( sessionKey );
        if ( session != null )
        {
            Question question = session.getQuestion();
            if ( question != null )
            {
                String responder = session.getRemoteAddress();
                if ( session.killed )
                {
                    log.warning( "session is killed" );
                    return Response.status( Response.Status.BAD_REQUEST ).build();
                }
                dialogLog.log(LogLevel.INFO,
                              session.getAdapterConfig(),
                              String.format("Answer input: %s from: %s to question: %s", answer_input,
                                            session.getRemoteAddress(), question.getQuestion_expandedtext()), session);
                String answerForQuestion = question.getQuestion_expandedtext();
                question = question.answer( responder, session.getAdapterConfig().getConfigId(), answer_id,
                    answer_input, sessionKey );
                //reload the session
                session = Session.getSession( sessionKey );
                session.setQuestion( question );
                session.storeSession();
                //check if ddr is in session. save the answer in the ddr
                if(session.getDdrRecordId() != null) {
                    try {
                        DDRRecord ddrRecord = DDRRecord.getDDRRecord(session.getDdrRecordId(), session.getAccountId());
                        if(ddrRecord != null) {
                            ddrRecord.addAdditionalInfo(DDRRecord.ANSWER_INPUT_KEY + ":" + answerForQuestion,
                                                        answer_input);
                            ddrRecord.createOrUpdateWithLog();
                        }
                    }
                    catch (Exception e) {
                    }
                }
                return handleQuestion( question, session.getAdapterConfig(), responder, sessionKey );
            } else {
                log.warning( "No question found in session!" );
            }
        }
        else
        {
            log.warning( "No session found for: " + sessionKey );
            dialogLog.severe(null, "No session found!", session);
        }
        return Response.ok( reply ).build();
    }
	
    @Path( "timeout" )
    @GET
    @Produces( "application/voicexml+xml" )
    public Response timeout( @QueryParam( "questionId" ) String question_id,
        @QueryParam( "sessionKey" ) String sessionKey ) throws Exception
    {
        String reply = "<vxml><exit/></vxml>";
        Session session = Session.getSession( sessionKey );
        if ( session != null )
        {
            Question question = session.getQuestion();
            String responder = session.getRemoteAddress();
            if ( session.killed )
            {
                return Response.status( Response.Status.BAD_REQUEST ).build();
            }
            dialogLog.log(LogLevel.INFO,
                          session.getAdapterConfig(),
                          String.format("Timeout from: %s for question: %s", responder,
                                        question.getQuestion_expandedtext()), session);
            HashMap<String,Object> extras = new HashMap<String, Object>();
            extras.put( "sessionKey", sessionKey );
            question = question.event( "timeout", "No answer received", extras, responder );
            session.setQuestion( question );
            if (question != null) {
                String retryLimit = question.getMediaPropertyValue(MediumType.BROADSOFT, MediaPropertyKey.RETRY_LIMIT);
                retryLimit = retryLimit != null ? retryLimit : String.valueOf(Question.DEFAULT_MAX_QUESTION_LOAD);
                Integer retryCount = session.getRetryCount();
                retryCount = retryCount != null ? retryCount : 0;
                if (retryCount < Integer.parseInt(retryLimit)) {
                    session.setRetryCount(++retryCount);
                }
                else {
                    //hangup so set question to null
                    question = null;
                }
            }
            else {
                log.warning("No question found for this session :"+ sessionKey);
            }
            session.storeSession();
            return handleQuestion( question, session.getAdapterConfig(), responder, sessionKey );
        }
        else {
            log.warning("Strange that no session is found for: "+ sessionKey);
        }
        return Response.ok( reply ).build();
    }
	
    @Path("exception")
    @GET
    @Produces("application/voicexml+xml")
    public Response
        exception(@QueryParam("questionId") String question_id, @QueryParam("sessionKey") String sessionKey) {

        String reply = "<vxml><exit/></vxml>";
        Session session = Session.getSession(sessionKey);
        if (session != null && session.getQuestion() != null) {
            Question question = session.getQuestion();
            String responder = session.getRemoteAddress();

            if (session.killed) {
                return Response.status(Response.Status.BAD_REQUEST).build();
            }
            dialogLog.log(LogLevel.INFO,
                          session.getAdapterConfig(),
                          String.format("Wrong answer received from: %s for question: %s", responder,
                                        question.getQuestion_expandedtext()), session);

            HashMap<String, String> extras = new HashMap<String, String>();
            extras.put("sessionKey", sessionKey);
            question = question.event("exception", "Wrong answer received", extras, responder);
            //reload the session
            session = Session.getSession(sessionKey);
            session.setQuestion(question);
            session.storeSession();
            return handleQuestion(question, session.getAdapterConfig(), responder, sessionKey);
        }
        return Response.ok(reply).build();
    }

	
    /**
     * hang up a call based on the session.
     * 
     * @param session if null, doesnt trigger an hangup event. Also expects a question to be there in this session, or atleast a 
     * startURL from where the question can be fetched.
     * @return
     * @throws Exception
     */
    public Response hangup(Session session) throws Exception {

        if (session != null) {
            log.info("call hangup with:" + session.getDirection() + ":" + session.getRemoteAddress() + ":" +
                     session.getLocalAddress());
            if (session.getQuestion() == null) {
                Question question = Question.fromURL(session.getStartUrl(), session.getAdapterConfig().getConfigId(),
                                                     session.getRemoteAddress(), session.getLocalAddress(),
                                                     session.getDdrRecordId(), session.getKey());
                session.setQuestion(question);
            }
            if (session.getQuestion() != null && !isEventTriggered("hangup", session)) {
                
                HashMap<String, Object> timeMap = getTimeMap(session.getStartTimestamp(), session.getAnswerTimestamp(),
                                                             session.getReleaseTimestamp());
                timeMap.put("referredCalledId", session.getExtras().get("referredCalledId"));
                timeMap.put("sessionKey", session.getKey());
                if(session.getExtras() != null && !session.getExtras().isEmpty()) {
                    timeMap.putAll(session.getExtras());
                }
                Response hangupResponse = handleQuestion(null, session.getAdapterConfig(), session.getRemoteAddress(),
                                                         session.getKey());
                session.getQuestion().event("hangup", "Hangup", timeMap, session.getRemoteAddress());
                dialogLog.log(LogLevel.INFO, session.getAdapterConfig(),
                              String.format("Call hungup from: %s", session.getRemoteAddress()), session);
                return hangupResponse;
            }
            else {
                log.info("no question received");
            }
        }
        return Response.ok("").build();
    }

    /**
     * used to trigger answered event unlike {@link VoiceXMLRESTProxy#answer(String, String, String, String, UriInfo)}
     * @return
     * @throws Exception 
     */
    public Response answered( String direction, String remoteID, String localID, String startTime,
        String answerTime) throws Exception
    {
        log.info( "call answered with:" + direction + "_" + remoteID + "_" + localID );
        String sessionKey = AdapterAgent.ADAPTER_TYPE_BROADSOFT+"|"+localID+"|"+remoteID.split( "@outbound" )[0]; //ignore the @outbound suffix
        Session session = Session.getSession(sessionKey);
        //for direction = transfer (redirect event), json should not be null        
        //make sure that the answered call is not triggered twice
        if (session != null && session.getQuestion() != null && !isEventTriggered("answered", session)) {
            String responder = session.getRemoteAddress();
            String referredCalledId = session.getExtras().get("referredCalledId");
            HashMap<String, Object> timeMap = getTimeMap(startTime, answerTime, null);
            timeMap.put("referredCalledId", referredCalledId);
            timeMap.put("sessionKey", sessionKey);
            session.getQuestion().event("answered", "Answered", timeMap, responder);
            dialogLog.log(LogLevel.INFO, session.getAdapterConfig(),
                          String.format("Call from: %s answered by: %s", session.getLocalAddress(), responder), session);
        }
        return Response.ok( "" ).build();
    }
	
    @Path("cc")
    @POST
    public Response receiveCCMessage(String xml) {
        
        log.info("Received cc: "+xml);
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            
            Document dom = db.parse(new ByteArrayInputStream(xml.getBytes("UTF-8")));
            Node subscriberId = dom.getElementsByTagName("subscriberId").item(0);
            
            AdapterConfig config = AdapterConfig.findAdapterConfigByUsername(subscriberId.getTextContent());
            
            Node eventData = dom.getElementsByTagName("eventData").item(0);
            // check if incall event
            if(eventData.getChildNodes().getLength()>1) {
                Node call = eventData.getChildNodes().item(1);
                Node personality = null;
                Node callState = null;
                Node remoteParty = null;
                Node releaseCause = null;
                Node answerTime = null;
                Node releaseTime = null;
                Node startTime = null;
                
                for ( int i = 0; i < call.getChildNodes().getLength(); i++ )
                {
                    Node node = call.getChildNodes().item( i );
                    if ( node.getNodeName().equals( "personality" ) )
                    {
                        personality = node;
                    }
                    else if ( node.getNodeName().equals( "callState" ) )
                    {
                        callState = node;
                    }
                    else if ( node.getNodeName().equals( "remoteParty" ) )
                    {
                        remoteParty = node;
                    }
                    else if ( node.getNodeName().equals( "releaseCause" ) )
                    {
                        releaseCause = node;
                    }
                    else if ( node.getNodeName().equals( "startTime" ) )
                    {
                        startTime = node;
                    }
                    else if ( node.getNodeName().equals( "answerTime" ) )
                    {
                        answerTime = node;
                    }
                    else if ( node.getNodeName().equals( "releaseTime" ) )
                    {
                        releaseTime = node;
                    }
                }              
                
                if(callState!=null && callState.getNodeName().equals("callState")) {

                    // Check if call
                    if ( callState.getTextContent().equals( "Released" )
                        || callState.getTextContent().equals( "Active" ) )
                    {
                        String startTimeString = startTime != null ? startTime.getTextContent()
                                                                  : null;
                        String answerTimeString = answerTime != null ? answerTime.getTextContent()
                                                                    : null;
                        String releaseTimeString = releaseTime != null ? releaseTime.getTextContent()
                                                                      : null;

                        // Check if a sip or network call
                        String type="";
                        String address="";
                        String fullAddress = "";
                        for(int i=0; i<remoteParty.getChildNodes().getLength();i++) {
                            Node rpChild = remoteParty.getChildNodes().item(i);
                            if(rpChild.getNodeName().equals("address")) {
                                address=rpChild.getTextContent();
                            }else if(rpChild.getNodeName().equals("callType")) {
                                type=rpChild.getTextContent();
                            }
                        }
                        
                        fullAddress = new String(address);
                        // Check if session can be matched to call
                        if(type.equals("Network") || type.equals("Group") || type.equals("Unknown")) {
                            
                            address = address.replace("tel:", "").replace("sip:", "");
                            
                            log.info("Going to format phone number: "+address);
                            
                            if(address.startsWith("+")) 
                            {
                                address = PhoneNumberUtils.formatNumber(address, null);
                            }
                            
                            String sessionKey = AdapterAgent.ADAPTER_TYPE_BROADSOFT+"|"+config.getMyAddress()+"|"+address.split( "@" )[0];
                            Session session = Session.getSession(sessionKey);
                            if (session != null) {

                                log.info("Session key: " + sessionKey);
                                String direction = "inbound";
                                if (personality.getTextContent().equals("Originator") && !address.contains("outbound")) {
                                    //address += "@outbound";
                                    direction = "transfer";
                                    log.info("Transfer detected????");

                                    //when the receiver hangs up, an active callstate is also triggered. 
                                    // but the releaseCause is also set to Temporarily Unavailable
                                    if (callState.getTextContent().equals("Active")) {
                                        if (releaseCause == null ||
                                            (releaseCause != null &&
                                             !releaseCause.getTextContent().equalsIgnoreCase("Temporarily Unavailable") && !releaseCause
                                                                            .getTextContent()
                                                                            .equalsIgnoreCase("User Not Found"))) {
                                            session.setDirection(direction);
                                            session.setAnswerTimestamp(answerTimeString);<commit_msg>fix: catch exception when @outbound tag is attached to a phonenumber<commit_after>package com.almende.dialog.adapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.znerd.xmlenc.XMLOutputter;
import com.almende.dialog.LogLevel;
import com.almende.dialog.Settings;
import com.almende.dialog.accounts.AdapterConfig;
import com.almende.dialog.adapter.tools.Broadsoft;
import com.almende.dialog.agent.AdapterAgent;
import com.almende.dialog.model.Answer;
import com.almende.dialog.model.MediaProperty.MediaPropertyKey;
import com.almende.dialog.model.MediaProperty.MediumType;
import com.almende.dialog.model.Question;
import com.almende.dialog.model.Session;
import com.almende.dialog.model.ddr.DDRRecord;
import com.almende.dialog.util.DDRUtils;
import com.almende.dialog.util.ServerUtils;
import com.almende.dialog.util.TimeUtils;
import com.almende.util.myBlobstore.MyBlobStore;
import com.askfast.commons.entity.AccountType;
import com.askfast.commons.utils.PhoneNumberUtils;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;

@Path("/vxml/")
public class VoiceXMLRESTProxy {
	protected static final Logger log = Logger.getLogger(VoiceXMLRESTProxy.class.getName());
	protected static final com.almende.dialog.Logger dialogLog =  new com.almende.dialog.Logger();
	private static final int LOOP_DETECTION=10;
	private static final String DTMFGRAMMAR="dtmf2hash";
	private static final String PLAY_TRIAL_AUDIO_KEY = "playTrialAccountAudio";
	
	private static final int MAX_RETRIES=1;
	
	protected String TIMEOUT_URL="timeout";
	protected String EXCEPTION_URL="exception";
	private String host = "";
	
	public static void killSession(Session session){
		
		AdapterConfig config = session.getAdapterConfig();
		if(config!=null) {
			Broadsoft bs = new Broadsoft(config);
			bs.endCall(session.getExternalSession());
		}
	}
	
    /**
     * @Deprecated. Use broadcast calling mechanism instead. <br>
     *              {@link VoiceXMLRESTProxy#dial(Map, String, String, AdapterConfig)
     *              dial} method
     * 
     * @param address
     * @param url
     * @param config
     * @return
     */
    @Deprecated
    public static String dial(String address, String url, AdapterConfig config) {

        try {
            address = PhoneNumberUtils.formatNumber(address, PhoneNumberFormat.E164);
        }
        catch (Exception e) {
            log.severe(String.format("Phonenumber: %s is not valid", address));
            return "";
        }

        //avoid multiple calls to be made to the same number, from the same adapter. 
        Session session = Session.getSession(Session.getSessionKey(config, address));
        if(session != null) {
            String responseMessage = checkIfCallAlreadyInSession(address, config, session);
            if(responseMessage != null) {
                return responseMessage;
            }
            else {
                //create a session
                session = Session.getOrCreateSession(config, address);
            }
        }
        else {
            session = Session.getOrCreateSession(config, address);
        }
        session.killed = false;
        session.setStartUrl(url);
        session.setDirection("outbound");
        session.setRemoteAddress(address);
        session.setType(AdapterAgent.ADAPTER_TYPE_BROADSOFT);
        
        Question question = Question.fromURL(url, config.getConfigId(), address, config.getMyAddress(),
                                             session.getDdrRecordId(), session.getKey());
        session.setQuestion(question);
        session.storeSession();

        dialogLog.log(LogLevel.INFO, config,
                      String.format("Call started from: %s to: %s", config.getMyAddress(), address), session);
        Broadsoft bs = new Broadsoft(config);
        bs.startSubscription();

        String extSession = bs.startCall(address + "@outbound", session);

        session.setExternalSession(extSession);
        session.storeSession();

        return session.getKey();
    }

    /**
     * initiates a call to all the numbers in the addressNameMap and returns a
     * Map of <adress, SessionKey>
     * @return
     */
    public static HashMap<String, String> dial( Map<String, String> addressNameMap, String url, String senderName, AdapterConfig config )
    throws Exception
    {
        HashMap<String, String> resultSessionMap = new HashMap<String, String>();
        // If it is a broadcast don't provide the remote address because it is deceiving.
        String loadAddress = "";
        if(addressNameMap.size()==1)
            loadAddress = addressNameMap.keySet().iterator().next();

        //fetch the question
        Question question = Question.fromURL( url, config.getConfigId(), loadAddress, config.getMyAddress(), null, null );
        for ( String address : addressNameMap.keySet() )
        {
            try
            {
                String formattedAddress = PhoneNumberUtils.formatNumber( address, PhoneNumberFormat.E164 );
                //avoid multiple calls to be made to the same number, from the same adapter. 
                Session session = Session.getSession(Session.getSessionKey(config, formattedAddress));
                if(session != null) {
                    String responseMessage = checkIfCallAlreadyInSession(formattedAddress, config, session);
                    if(responseMessage != null) {
                        resultSessionMap.put(formattedAddress, responseMessage);
                        continue;
                    }
                    else {
                        //create a session
                        session = Session.getOrCreateSession(config, formattedAddress);
                    }
                }
                else {
                    session = Session.getOrCreateSession(config, formattedAddress);
                }
                session.killed=false;
                session.setStartUrl(url);
                session.setDirection("outbound");
                session.setRemoteAddress(formattedAddress);
                session.setType(AdapterAgent.ADAPTER_TYPE_BROADSOFT);
                session.setAdapterID(config.getConfigId());
                session.setQuestion( question );
                dialogLog.log(LogLevel.INFO, session.getAdapterConfig(), String.format("Outgoing call from: %s to: %s",
                                                                                       session.getLocalAddress(),
                                                                                       formattedAddress), session);
                String extSession = "";
                if ( !ServerUtils.isInUnitTestingEnvironment() )
                {
                    Broadsoft bs = new Broadsoft( config );
                    String subscriptiion = bs.startSubscription();
                    log.info(String.format("Calling subscription complete. Message: %s. Starting call.. ",
                                           subscriptiion));
                    extSession = bs.startCall( formattedAddress + "@outbound", session );
                }
                session.setExternalSession( extSession );
                session.storeSession();
                resultSessionMap.put( formattedAddress, session.getKey() );
            }
            catch ( Exception e )
            {
                log.severe( String.format( "Phonenumber: %s is not valid", address ) );
            }
        }
        return resultSessionMap;
    }
	
	public static ArrayList<String> getActiveCalls(AdapterConfig config) {
		Broadsoft bs = new Broadsoft(config);
		return bs.getActiveCalls();
	}
	
	public static ArrayList<String> getActiveCallsInfo(AdapterConfig config) {
		Broadsoft bs = new Broadsoft(config);
		return bs.getActiveCallsInfo();
	}
	
	public static boolean killActiveCalls(AdapterConfig config) {
		Broadsoft bs = new Broadsoft(config);
		return bs.killActiveCalls();
	}
	
	@Path("dtmf2hash")
	@GET
	@Produces("application/srgs+xml")
	public Response getDTMF2Hash(@QueryParam("minlength") String minLength, @QueryParam("maxlength") String maxLength) {
	    minLength = (minLength != null && !minLength.isEmpty()) ? minLength : "1";
	    maxLength = (maxLength != null && !maxLength.isEmpty()) ? maxLength : "";
	    String repeat = minLength.equals( maxLength ) ? minLength : (minLength + "-" +  maxLength);
		String result = "<?xml version=\"1.0\"?> "+
						"<grammar mode=\"dtmf\" version=\"1.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd\" xmlns=\"http://www.w3.org/2001/06/grammar\"  root=\"untilHash\" > "+
							"<rule id=\"digit\"> "+
								"<one-of> "+
									"<item> 0 </item> "+
									"<item> 1 </item> "+
									"<item> 2 </item> "+
									"<item> 3 </item> "+
									"<item> 4 </item> "+
									"<item> 5 </item> "+
									"<item> 6 </item> "+
									"<item> 7 </item> "+
									"<item> 8 </item> "+
									"<item> 9 </item> "+
									"<item> * </item> "+
								"</one-of> "+
							"</rule> "+
							"<rule id=\"untilHash\" scope=\"public\"> "+
								"<one-of> "+
									"<item repeat=\"" + repeat + "\"><ruleref uri=\"#digit\"/></item> "+
									"<item> # </item> "+
								"</one-of> "+
							"</rule> "+
						"</grammar> ";
		return Response.ok(result).build();
	}
	
    @Path("new")
    @GET
    @Produces("application/voicexml")
    public Response getNewDialog(@QueryParam("direction") String direction,@QueryParam("remoteID") String remoteID,@QueryParam("localID") String localID, @Context UriInfo ui)
    {
        log.info("call started:"+direction+":"+remoteID+":"+localID);
        this.host=ui.getBaseUri().toString().replace(":80", "");
        
        AdapterConfig config = AdapterConfig.findAdapterConfig(AdapterAgent.ADAPTER_TYPE_BROADSOFT, localID);
        String formattedRemoteId = remoteID;
        //format the remote number
        try
        {
            formattedRemoteId = PhoneNumberUtils.formatNumber( remoteID.split( "@" )[0], PhoneNumberFormat.E164 );
        }
        catch ( Exception e1 )
        {
            log.severe( "Remote number formatting failed: "+ remoteID.split( "@" )[0] );
        }
        String sessionKey = AdapterAgent.ADAPTER_TYPE_BROADSOFT+"|"+localID+"|"+ formattedRemoteId;
        Session session = Session.getSession(sessionKey);
        
        String url = "";
        if ( session != null && direction.equalsIgnoreCase("outbound")) {
                url = session.getStartUrl();
            dialogLog.log(LogLevel.INFO, config,
                          String.format("Outgoing Call from: %s to: %s", config.getMyAddress(), formattedRemoteId),
                          session);
        }
        else if(direction.equals("inbound")) {
            //create a session for incoming only
            session = Session.getOrCreateSession(config, formattedRemoteId);
            url = config.getURLForInboundScenario();
            Broadsoft bs = new Broadsoft( config );
            bs.startSubscription();
            dialogLog.log(LogLevel.INFO, config,
                          String.format("Incoming Call received from: %s at: %s", formattedRemoteId, config.getMyAddress()),
                          session);
        }
        
        if(session != null) {
            session.setStartUrl( url );
            session.setDirection( direction );
            session.setRemoteAddress( formattedRemoteId );
            session.setType( AdapterAgent.ADAPTER_TYPE_BROADSOFT );
            session.setAccountId( config.getOwner() );
            session.setAdapterID( config.getConfigId() );
        }
        else {
            log.severe(String.format("Session %s not found", sessionKey));
            return null;
        }
        
        Question question = session.getQuestion();
        if(question == null) {
            question = Question.fromURL(url, session.getAdapterConfig().getConfigId(), formattedRemoteId, localID,
                                        session.getDdrRecordId(), session.getKey());
        }
        session.setQuestion(question);
        
        if (session.getQuestion() != null) {
            //play trial account audio if the account is trial
            if(config.getAccountType() != null && config.getAccountType().equals(AccountType.TRIAL)){
                session.getExtras().put(PLAY_TRIAL_AUDIO_KEY, "true");
            }
            //create ddr record
            DDRRecord ddrRecord = null;
            try {
                if (direction.equalsIgnoreCase("outbound")) {
                    ddrRecord = DDRUtils.createDDRRecordOnOutgoingCommunication(config, formattedRemoteId, 1, url);
                }
                else {
                    ddrRecord = DDRUtils.createDDRRecordOnIncomingCommunication(config, formattedRemoteId, 1, url);
                }
                session.setDdrRecordId( ddrRecord != null ? ddrRecord.getId() : null);
                ddrRecord.addAdditionalInfo(Session.TRACKING_TOKEN_KEY, session.getTrackingToken());
            }
            catch (Exception e) {
                String errorMessage = String.format("Creating DDR records failed. Direction: %s for adapterId: %s with address: %s remoteId: %s and localId: %s",
                                                    direction, config.getConfigId(), config.getMyAddress(), formattedRemoteId,
                                                    localID);
                log.severe(errorMessage);
                dialogLog.severe(config.getConfigId(), errorMessage, ddrRecord != null ? ddrRecord.getId() : null,
                                 sessionKey);
            }
            finally {
            	ddrRecord.createOrUpdate();
                session.storeSession();
            }
            return handleQuestion( question, config, formattedRemoteId, sessionKey );
        }
        else {
            return Response.ok().build();
        }
    }
	
    @Path( "answer" )
    @GET
    @Produces( "application/voicexml+xml" )
    public Response answer( @QueryParam( "questionId" ) String question_id,
        @QueryParam( "answerId" ) String answer_id, @QueryParam( "answerInput" ) String answer_input,
        @QueryParam( "sessionKey" ) String sessionKey, @Context UriInfo ui )
    {
        try
        {
            answer_input = answer_input != null ? URLDecoder.decode( answer_input, "UTF-8" ) : answer_input;
        }
        catch ( UnsupportedEncodingException e )
        {
            log.warning( String.format( "Answer input decode failed for: %s", answer_input) );
        }
        this.host = ui.getBaseUri().toString().replace( ":80", "" );
        String reply = "<vxml><exit/></vxml>";
        Session session = Session.getSession( sessionKey );
        if ( session != null )
        {
            Question question = session.getQuestion();
            if ( question != null )
            {
                String responder = session.getRemoteAddress();
                if ( session.killed )
                {
                    log.warning( "session is killed" );
                    return Response.status( Response.Status.BAD_REQUEST ).build();
                }
                dialogLog.log(LogLevel.INFO,
                              session.getAdapterConfig(),
                              String.format("Answer input: %s from: %s to question: %s", answer_input,
                                            session.getRemoteAddress(), question.getQuestion_expandedtext()), session);
                String answerForQuestion = question.getQuestion_expandedtext();
                question = question.answer( responder, session.getAdapterConfig().getConfigId(), answer_id,
                    answer_input, sessionKey );
                //reload the session
                session = Session.getSession( sessionKey );
                session.setQuestion( question );
                session.storeSession();
                //check if ddr is in session. save the answer in the ddr
                if(session.getDdrRecordId() != null) {
                    try {
                        DDRRecord ddrRecord = DDRRecord.getDDRRecord(session.getDdrRecordId(), session.getAccountId());
                        if(ddrRecord != null) {
                            ddrRecord.addAdditionalInfo(DDRRecord.ANSWER_INPUT_KEY + ":" + answerForQuestion,
                                                        answer_input);
                            ddrRecord.createOrUpdateWithLog();
                        }
                    }
                    catch (Exception e) {
                    }
                }
                return handleQuestion( question, session.getAdapterConfig(), responder, sessionKey );
            } else {
                log.warning( "No question found in session!" );
            }
        }
        else
        {
            log.warning( "No session found for: " + sessionKey );
            dialogLog.severe(null, "No session found!", session);
        }
        return Response.ok( reply ).build();
    }
	
    @Path( "timeout" )
    @GET
    @Produces( "application/voicexml+xml" )
    public Response timeout( @QueryParam( "questionId" ) String question_id,
        @QueryParam( "sessionKey" ) String sessionKey ) throws Exception
    {
        String reply = "<vxml><exit/></vxml>";
        Session session = Session.getSession( sessionKey );
        if ( session != null )
        {
            Question question = session.getQuestion();
            String responder = session.getRemoteAddress();
            if ( session.killed )
            {
                return Response.status( Response.Status.BAD_REQUEST ).build();
            }
            dialogLog.log(LogLevel.INFO,
                          session.getAdapterConfig(),
                          String.format("Timeout from: %s for question: %s", responder,
                                        question.getQuestion_expandedtext()), session);
            HashMap<String,Object> extras = new HashMap<String, Object>();
            extras.put( "sessionKey", sessionKey );
            question = question.event( "timeout", "No answer received", extras, responder );
            session.setQuestion( question );
            if (question != null) {
                String retryLimit = question.getMediaPropertyValue(MediumType.BROADSOFT, MediaPropertyKey.RETRY_LIMIT);
                retryLimit = retryLimit != null ? retryLimit : String.valueOf(Question.DEFAULT_MAX_QUESTION_LOAD);
                Integer retryCount = session.getRetryCount();
                retryCount = retryCount != null ? retryCount : 0;
                if (retryCount < Integer.parseInt(retryLimit)) {
                    session.setRetryCount(++retryCount);
                }
                else {
                    //hangup so set question to null
                    question = null;
                }
            }
            else {
                log.warning("No question found for this session :"+ sessionKey);
            }
            session.storeSession();
            return handleQuestion( question, session.getAdapterConfig(), responder, sessionKey );
        }
        else {
            log.warning("Strange that no session is found for: "+ sessionKey);
        }
        return Response.ok( reply ).build();
    }
	
    @Path("exception")
    @GET
    @Produces("application/voicexml+xml")
    public Response
        exception(@QueryParam("questionId") String question_id, @QueryParam("sessionKey") String sessionKey) {

        String reply = "<vxml><exit/></vxml>";
        Session session = Session.getSession(sessionKey);
        if (session != null && session.getQuestion() != null) {
            Question question = session.getQuestion();
            String responder = session.getRemoteAddress();

            if (session.killed) {
                return Response.status(Response.Status.BAD_REQUEST).build();
            }
            dialogLog.log(LogLevel.INFO,
                          session.getAdapterConfig(),
                          String.format("Wrong answer received from: %s for question: %s", responder,
                                        question.getQuestion_expandedtext()), session);

            HashMap<String, String> extras = new HashMap<String, String>();
            extras.put("sessionKey", sessionKey);
            question = question.event("exception", "Wrong answer received", extras, responder);
            //reload the session
            session = Session.getSession(sessionKey);
            session.setQuestion(question);
            session.storeSession();
            return handleQuestion(question, session.getAdapterConfig(), responder, sessionKey);
        }
        return Response.ok(reply).build();
    }

	
    /**
     * hang up a call based on the session.
     * 
     * @param session if null, doesnt trigger an hangup event. Also expects a question to be there in this session, or atleast a 
     * startURL from where the question can be fetched.
     * @return
     * @throws Exception
     */
    public Response hangup(Session session) throws Exception {

        if (session != null) {
            log.info("call hangup with:" + session.getDirection() + ":" + session.getRemoteAddress() + ":" +
                     session.getLocalAddress());
            if (session.getQuestion() == null) {
                Question question = Question.fromURL(session.getStartUrl(), session.getAdapterConfig().getConfigId(),
                                                     session.getRemoteAddress(), session.getLocalAddress(),
                                                     session.getDdrRecordId(), session.getKey());
                session.setQuestion(question);
            }
            if (session.getQuestion() != null && !isEventTriggered("hangup", session)) {
                
                HashMap<String, Object> timeMap = getTimeMap(session.getStartTimestamp(), session.getAnswerTimestamp(),
                                                             session.getReleaseTimestamp());
                timeMap.put("referredCalledId", session.getExtras().get("referredCalledId"));
                timeMap.put("sessionKey", session.getKey());
                if(session.getExtras() != null && !session.getExtras().isEmpty()) {
                    timeMap.putAll(session.getExtras());
                }
                Response hangupResponse = handleQuestion(null, session.getAdapterConfig(), session.getRemoteAddress(),
                                                         session.getKey());
                session.getQuestion().event("hangup", "Hangup", timeMap, session.getRemoteAddress());
                dialogLog.log(LogLevel.INFO, session.getAdapterConfig(),
                              String.format("Call hungup from: %s", session.getRemoteAddress()), session);
                return hangupResponse;
            }
            else {
                log.info("no question received");
            }
        }
        return Response.ok("").build();
    }

    /**
     * used to trigger answered event unlike {@link VoiceXMLRESTProxy#answer(String, String, String, String, UriInfo)}
     * @return
     * @throws Exception 
     */
    public Response answered( String direction, String remoteID, String localID, String startTime,
        String answerTime) throws Exception
    {
        log.info( "call answered with:" + direction + "_" + remoteID + "_" + localID );
        String sessionKey = AdapterAgent.ADAPTER_TYPE_BROADSOFT+"|"+localID+"|"+remoteID.split( "@outbound" )[0]; //ignore the @outbound suffix
        Session session = Session.getSession(sessionKey);
        //for direction = transfer (redirect event), json should not be null        
        //make sure that the answered call is not triggered twice
        if (session != null && session.getQuestion() != null && !isEventTriggered("answered", session)) {
            String responder = session.getRemoteAddress();
            String referredCalledId = session.getExtras().get("referredCalledId");
            HashMap<String, Object> timeMap = getTimeMap(startTime, answerTime, null);
            timeMap.put("referredCalledId", referredCalledId);
            timeMap.put("sessionKey", sessionKey);
            session.getQuestion().event("answered", "Answered", timeMap, responder);
            dialogLog.log(LogLevel.INFO, session.getAdapterConfig(),
                          String.format("Call from: %s answered by: %s", session.getLocalAddress(), responder), session);
        }
        return Response.ok( "" ).build();
    }
	
    @Path("cc")
    @POST
    public Response receiveCCMessage(String xml) {
        
        log.info("Received cc: "+xml);
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            
            Document dom = db.parse(new ByteArrayInputStream(xml.getBytes("UTF-8")));
            Node subscriberId = dom.getElementsByTagName("subscriberId").item(0);
            
            AdapterConfig config = AdapterConfig.findAdapterConfigByUsername(subscriberId.getTextContent());
            
            Node eventData = dom.getElementsByTagName("eventData").item(0);
            // check if incall event
            if(eventData.getChildNodes().getLength()>1) {
                Node call = eventData.getChildNodes().item(1);
                Node personality = null;
                Node callState = null;
                Node remoteParty = null;
                Node releaseCause = null;
                Node answerTime = null;
                Node releaseTime = null;
                Node startTime = null;
                
                for ( int i = 0; i < call.getChildNodes().getLength(); i++ )
                {
                    Node node = call.getChildNodes().item( i );
                    if ( node.getNodeName().equals( "personality" ) )
                    {
                        personality = node;
                    }
                    else if ( node.getNodeName().equals( "callState" ) )
                    {
                        callState = node;
                    }
                    else if ( node.getNodeName().equals( "remoteParty" ) )
                    {
                        remoteParty = node;
                    }
                    else if ( node.getNodeName().equals( "releaseCause" ) )
                    {
                        releaseCause = node;
                    }
                    else if ( node.getNodeName().equals( "startTime" ) )
                    {
                        startTime = node;
                    }
                    else if ( node.getNodeName().equals( "answerTime" ) )
                    {
                        answerTime = node;
                    }
                    else if ( node.getNodeName().equals( "releaseTime" ) )
                    {
                        releaseTime = node;
                    }
                }              
                
                if(callState!=null && callState.getNodeName().equals("callState")) {

                    // Check if call
                    if ( callState.getTextContent().equals( "Released" )
                        || callState.getTextContent().equals( "Active" ) )
                    {
                        String startTimeString = startTime != null ? startTime.getTextContent()
                                                                  : null;
                        String answerTimeString = answerTime != null ? answerTime.getTextContent()
                                                                    : null;
                        String releaseTimeString = releaseTime != null ? releaseTime.getTextContent()
                                                                      : null;

                        // Check if a sip or network call
                        String type="";
                        String address="";
                        String fullAddress = "";
                        for(int i=0; i<remoteParty.getChildNodes().getLength();i++) {
                            Node rpChild = remoteParty.getChildNodes().item(i);
                            if(rpChild.getNodeName().equals("address")) {
                                address=rpChild.getTextContent();
                            }else if(rpChild.getNodeName().equals("callType")) {
                                type=rpChild.getTextContent();
                            }
                        }
                        
                        fullAddress = new String(address);
                        // Check if session can be matched to call
                        if(type.equals("Network") || type.equals("Group") || type.equals("Unknown")) {
                            
                            address = address.replace("tel:", "").replace("sip:", "");
                            
                            log.info("Going to format phone number: "+address);
                            String[] addressArray = address.split("@");
                            try {
                                address = PhoneNumberUtils.formatNumber(addressArray[0], null);
                                if(addressArray.length > 1) {
                                    address += "@" + addressArray[1];
                                }
                            }
                            catch (Exception e) {
                                log.severe(String.format("Could not format phonenumber: %s", addressArray[0]));
                            }
                            
                            String sessionKey = AdapterAgent.ADAPTER_TYPE_BROADSOFT+"|"+config.getMyAddress()+"|"+ addressArray[0];
                            Session session = Session.getSession(sessionKey);
                            if (session != null) {

                                log.info("Session key: " + sessionKey);
                                String direction = "inbound";
                                if (personality.getTextContent().equals("Originator") && !address.contains("outbound")) {
                                    //address += "@outbound";
                                    direction = "transfer";
                                    log.info("Transfer detected????");

                                    //when the receiver hangs up, an active callstate is also triggered. 
                                    // but the releaseCause is also set to Temporarily Unavailable
                                    if (callState.getTextContent().equals("Active")) {
                                        if (releaseCause == null ||
                                            (releaseCause != null &&
                                             !releaseCause.getTextContent().equalsIgnoreCase("Temporarily Unavailable") && !releaseCause
                                                                            .getTextContent()
                                                                            .equalsIgnoreCase("User Not Found"))) {
                                            session.setDirection(direction);
                                            session.setAnswerTimestamp(answerTimeString);
{'fraction_non_alphanumeric': 0.1071194788192388, 'fraction_numerical': 0.0024001745581496837, 'mean_word_length': 1.711761971176197, 'pattern_counts': {'":': 14, '<': 131, '<?xml version=': 2, '>': 128, 'https://': 0, 'lorem ipsum': 0, 'www.': 8, 'xml': 48}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 127, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2407483', 'n_tokens_mistral': 16254, 'n_tokens_neox': 15187, 'n_words': 3367}