| /* |
| * Conditions Of Use |
| * |
| * This software was developed by employees of the National Institute of |
| * Standards and Technology (NIST), an agency of the Federal Government. |
| * Pursuant to title 15 Untied States Code Section 105, works of NIST |
| * employees are not subject to copyright protection in the United States |
| * and are considered to be in the public domain. As a result, a formal |
| * license is not needed to use the software. |
| * |
| * This software is provided by NIST as a service and is expressly |
| * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED |
| * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT |
| * AND DATA ACCURACY. NIST does not warrant or make any representations |
| * regarding the use of the software or the results thereof, including but |
| * not limited to the correctness, accuracy, reliability or usefulness of |
| * the software. |
| * |
| * Permission to use this software is contingent upon your acceptance |
| * of the terms of this agreement |
| * |
| * . |
| * |
| */ |
| package gov.nist.javax.sip; |
| |
| import java.util.*; |
| import gov.nist.javax.sip.stack.*; |
| import gov.nist.javax.sip.message.*; |
| import javax.sip.message.*; |
| import javax.sip.*; |
| import gov.nist.core.ThreadAuditor; |
| |
| /* bug fixes SIPQuest communications and Shu-Lin Chen. */ |
| |
| /** |
| * Event Scanner to deliver events to the Listener. |
| * |
| * @version 1.2 $Revision: 1.41 $ $Date: 2009/11/18 02:35:17 $ |
| * |
| * @author M. Ranganathan <br/> |
| * |
| * |
| */ |
| class EventScanner implements Runnable { |
| |
| private boolean isStopped; |
| |
| private int refCount; |
| |
| // SIPquest: Fix for deadlocks |
| private LinkedList pendingEvents = new LinkedList(); |
| |
| private int[] eventMutex = { 0 }; |
| |
| private SipStackImpl sipStack; |
| |
| public void incrementRefcount() { |
| synchronized (eventMutex) { |
| this.refCount++; |
| } |
| } |
| |
| public EventScanner(SipStackImpl sipStackImpl) { |
| this.pendingEvents = new LinkedList(); |
| Thread myThread = new Thread(this); |
| // This needs to be set to false else the |
| // main thread mysteriously exits. |
| myThread.setDaemon(false); |
| |
| this.sipStack = sipStackImpl; |
| |
| myThread.setName("EventScannerThread"); |
| |
| myThread.start(); |
| |
| } |
| |
| public void addEvent(EventWrapper eventWrapper) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("addEvent " + eventWrapper); |
| synchronized (this.eventMutex) { |
| |
| pendingEvents.add(eventWrapper); |
| |
| // Add the event into the pending events list |
| |
| eventMutex.notify(); |
| } |
| |
| } |
| |
| /** |
| * Stop the event scanner. Decrement the reference count and exit the |
| * scanner thread if the ref count goes to 0. |
| */ |
| |
| public void stop() { |
| synchronized (eventMutex) { |
| |
| if (this.refCount > 0) |
| this.refCount--; |
| |
| if (this.refCount == 0) { |
| isStopped = true; |
| eventMutex.notify(); |
| |
| } |
| } |
| } |
| |
| /** |
| * Brutally stop the event scanner. This does not wait for the refcount to |
| * go to 0. |
| * |
| */ |
| public void forceStop() { |
| synchronized (this.eventMutex) { |
| this.isStopped = true; |
| this.refCount = 0; |
| this.eventMutex.notify(); |
| } |
| |
| } |
| |
| public void deliverEvent(EventWrapper eventWrapper) { |
| EventObject sipEvent = eventWrapper.sipEvent; |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "sipEvent = " + sipEvent + "source = " |
| + sipEvent.getSource()); |
| SipListener sipListener = null; |
| |
| if (!(sipEvent instanceof IOExceptionEvent)) { |
| sipListener = ((SipProviderImpl) sipEvent.getSource()).getSipListener(); |
| } else { |
| sipListener = sipStack.getSipListener(); |
| } |
| |
| if (sipEvent instanceof RequestEvent) { |
| try { |
| // Check if this request has already created a |
| // transaction |
| SIPRequest sipRequest = (SIPRequest) ((RequestEvent) sipEvent) |
| .getRequest(); |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "deliverEvent : " |
| + sipRequest.getFirstLine() |
| + " transaction " |
| + eventWrapper.transaction |
| + " sipEvent.serverTx = " |
| + ((RequestEvent) sipEvent) |
| .getServerTransaction()); |
| } |
| |
| // Discard the duplicate request if a |
| // transaction already exists. If the listener chose |
| // to handle the request statelessly, then the listener |
| // will see the retransmission. |
| // Note that in both of these two cases, JAIN SIP will allow |
| // you to handle the request statefully or statelessly. |
| // An example of the latter case is REGISTER and an example |
| // of the former case is INVITE. |
| |
| SIPServerTransaction tx = (SIPServerTransaction) sipStack |
| .findTransaction(sipRequest, true); |
| |
| if (tx != null && !tx.passToListener()) { |
| |
| // JvB: make an exception for a very rare case: some |
| // (broken) UACs use |
| // the same branch parameter for an ACK. Such an ACK should |
| // be passed |
| // to the listener (tx == INVITE ST, terminated upon sending |
| // 2xx but |
| // lingering to catch retransmitted INVITEs) |
| if (sipRequest.getMethod().equals(Request.ACK) |
| && tx.isInviteTransaction() && |
| ( tx.getLastResponse().getStatusCode()/100 == 2 || |
| sipStack.isNon2XXAckPassedToListener())) { |
| |
| if (sipStack.isLoggingEnabled()) |
| sipStack |
| .getStackLogger() |
| .logDebug( |
| "Detected broken client sending ACK with same branch! Passing..."); |
| } else { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "transaction already exists! " + tx); |
| return; |
| } |
| } else if (sipStack.findPendingTransaction(sipRequest) != null) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "transaction already exists!!"); |
| |
| return; |
| } else { |
| // Put it in the pending list so that if a repeat |
| // request comes along it will not get assigned a |
| // new transaction |
| SIPServerTransaction st = (SIPServerTransaction) eventWrapper.transaction; |
| sipStack.putPendingTransaction(st); |
| } |
| |
| // Set up a pointer to the transaction. |
| sipRequest.setTransaction(eventWrapper.transaction); |
| // Change made by SIPquest |
| try { |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger() |
| .logDebug( |
| "Calling listener " |
| + sipRequest.getFirstLine()); |
| sipStack.getStackLogger().logDebug( |
| "Calling listener " + eventWrapper.transaction); |
| } |
| if (sipListener != null) |
| sipListener.processRequest((RequestEvent) sipEvent); |
| |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Done processing Message " |
| + sipRequest.getFirstLine()); |
| } |
| if (eventWrapper.transaction != null) { |
| |
| SIPDialog dialog = (SIPDialog) eventWrapper.transaction |
| .getDialog(); |
| if (dialog != null) |
| dialog.requestConsumed(); |
| |
| } |
| } catch (Exception ex) { |
| // We cannot let this thread die under any |
| // circumstances. Protect ourselves by logging |
| // errors to the console but continue. |
| sipStack.getStackLogger().logException(ex); |
| } |
| } finally { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Done processing Message " |
| + ((SIPRequest) (((RequestEvent) sipEvent) |
| .getRequest())).getFirstLine()); |
| } |
| if (eventWrapper.transaction != null |
| && ((SIPServerTransaction) eventWrapper.transaction) |
| .passToListener()) { |
| ((SIPServerTransaction) eventWrapper.transaction) |
| .releaseSem(); |
| } |
| |
| if (eventWrapper.transaction != null) |
| sipStack |
| .removePendingTransaction((SIPServerTransaction) eventWrapper.transaction); |
| if (eventWrapper.transaction.getOriginalRequest().getMethod() |
| .equals(Request.ACK)) { |
| // Set the tx state to terminated so it is removed from the |
| // stack |
| // if the user configured to get notification on ACK |
| // termination |
| eventWrapper.transaction |
| .setState(TransactionState.TERMINATED); |
| } |
| } |
| |
| } else if (sipEvent instanceof ResponseEvent) { |
| try { |
| ResponseEvent responseEvent = (ResponseEvent) sipEvent; |
| SIPResponse sipResponse = (SIPResponse) responseEvent |
| .getResponse(); |
| SIPDialog sipDialog = ((SIPDialog) responseEvent.getDialog()); |
| try { |
| if (sipStack.isLoggingEnabled()) { |
| |
| sipStack.getStackLogger().logDebug( |
| "Calling listener for " |
| + sipResponse.getFirstLine()); |
| } |
| if (sipListener != null) { |
| SIPTransaction tx = eventWrapper.transaction; |
| if (tx != null) { |
| tx.setPassToListener(); |
| } |
| sipListener.processResponse((ResponseEvent) sipEvent); |
| } |
| |
| /* |
| * If the response for a request within a dialog is a 481 |
| * (Call/Transaction Does Not Exist) or a 408 (Request |
| * Timeout), the UAC SHOULD terminate the dialog. |
| */ |
| if ((sipDialog != null && (sipDialog.getState() == null || !sipDialog |
| .getState().equals(DialogState.TERMINATED))) |
| && (sipResponse.getStatusCode() == Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST || sipResponse |
| .getStatusCode() == Response.REQUEST_TIMEOUT)) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Removing dialog on 408 or 481 response"); |
| } |
| sipDialog.doDeferredDelete(); |
| } |
| |
| /* |
| * The Client tx disappears after the first 2xx response |
| * However, additional 2xx responses may arrive later for |
| * example in the following scenario: |
| * |
| * Multiple 2xx responses may arrive at the UAC for a single |
| * INVITE request due to a forking proxy. Each response is |
| * distinguished by the tag parameter in the To header |
| * field, and each represents a distinct dialog, with a |
| * distinct dialog identifier. |
| * |
| * If the Listener does not ACK the 200 then we assume he |
| * does not care about the dialog and gc the dialog after |
| * some time. However, this is really an application bug. |
| * This garbage collects unacknowledged dialogs. |
| * |
| */ |
| if (sipResponse.getCSeq().getMethod() |
| .equals(Request.INVITE) |
| && sipDialog != null |
| && sipResponse.getStatusCode() == 200) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Warning! unacknowledged dialog. " + sipDialog.getState()); |
| } |
| /* |
| * If we dont see an ACK in 32 seconds, we want to tear down the dialog. |
| */ |
| sipDialog.doDeferredDeleteIfNoAckSent(sipResponse.getCSeq().getSeqNumber()); |
| } |
| } catch (Exception ex) { |
| // We cannot let this thread die under any |
| // circumstances. Protect ourselves by logging |
| // errors to the console but continue. |
| sipStack.getStackLogger().logException(ex); |
| } |
| // The original request is not needed except for INVITE |
| // transactions -- null the pointers to the transactions so |
| // that state may be released. |
| SIPClientTransaction ct = (SIPClientTransaction) eventWrapper.transaction; |
| if (ct != null |
| && TransactionState.COMPLETED == ct.getState() |
| && ct.getOriginalRequest() != null |
| && !ct.getOriginalRequest().getMethod().equals( |
| Request.INVITE)) { |
| // reduce the state to minimum |
| // This assumes that the application will not need |
| // to access the request once the transaction is |
| // completed. |
| ct.clearState(); |
| } |
| // mark no longer in the event queue. |
| } finally { |
| if (eventWrapper.transaction != null |
| && eventWrapper.transaction.passToListener()) { |
| eventWrapper.transaction.releaseSem(); |
| } |
| } |
| |
| } else if (sipEvent instanceof TimeoutEvent) { |
| // Change made by SIPquest |
| try { |
| // Check for null as listener could be removed. |
| if (sipListener != null) |
| sipListener.processTimeout((TimeoutEvent) sipEvent); |
| } catch (Exception ex) { |
| // We cannot let this thread die under any |
| // circumstances. Protect ourselves by logging |
| // errors to the console but continue. |
| sipStack.getStackLogger().logException(ex); |
| } |
| |
| } else if (sipEvent instanceof DialogTimeoutEvent) { |
| try { |
| // Check for null as listener could be removed. |
| if (sipListener != null && sipListener instanceof SipListenerExt) { |
| ((SipListenerExt)sipListener).processDialogTimeout((DialogTimeoutEvent) sipEvent); |
| } |
| } catch (Exception ex) { |
| // We cannot let this thread die under any |
| // circumstances. Protect ourselves by logging |
| // errors to the console but continue. |
| sipStack.getStackLogger().logException(ex); |
| } |
| |
| } else if (sipEvent instanceof IOExceptionEvent) { |
| try { |
| if (sipListener != null) |
| sipListener.processIOException((IOExceptionEvent) sipEvent); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logException(ex); |
| } |
| } else if (sipEvent instanceof TransactionTerminatedEvent) { |
| try { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "About to deliver transactionTerminatedEvent"); |
| sipStack.getStackLogger().logDebug( |
| "tx = " |
| + ((TransactionTerminatedEvent) sipEvent) |
| .getClientTransaction()); |
| sipStack.getStackLogger().logDebug( |
| "tx = " |
| + ((TransactionTerminatedEvent) sipEvent) |
| .getServerTransaction()); |
| |
| } |
| if (sipListener != null) |
| sipListener |
| .processTransactionTerminated((TransactionTerminatedEvent) sipEvent); |
| } catch (AbstractMethodError ame) { |
| // JvB: for backwards compatibility, accept this |
| if (sipStack.isLoggingEnabled()) |
| sipStack |
| .getStackLogger() |
| .logWarning( |
| "Unable to call sipListener.processTransactionTerminated"); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logException(ex); |
| } |
| } else if (sipEvent instanceof DialogTerminatedEvent) { |
| try { |
| if (sipListener != null) |
| sipListener |
| .processDialogTerminated((DialogTerminatedEvent) sipEvent); |
| } catch (AbstractMethodError ame) { |
| // JvB: for backwards compatibility, accept this |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logWarning( |
| "Unable to call sipListener.processDialogTerminated"); |
| } catch (Exception ex) { |
| sipStack.getStackLogger().logException(ex); |
| } |
| } else { |
| |
| sipStack.getStackLogger().logFatalError("bad event" + sipEvent); |
| } |
| |
| } |
| |
| /** |
| * For the non-re-entrant listener this delivers the events to the listener |
| * from a single queue. If the listener is re-entrant, then the stack just |
| * calls the deliverEvent method above. |
| */ |
| |
| public void run() { |
| try { |
| // Ask the auditor to monitor this thread |
| ThreadAuditor.ThreadHandle threadHandle = sipStack.getThreadAuditor().addCurrentThread(); |
| |
| while (true) { |
| EventWrapper eventWrapper = null; |
| |
| LinkedList eventsToDeliver; |
| synchronized (this.eventMutex) { |
| // First, wait for some events to become available. |
| while (pendingEvents.isEmpty()) { |
| // There's nothing in the list, check to make sure we |
| // haven't |
| // been stopped. If we have, then let the thread die. |
| if (this.isStopped) { |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug( |
| "Stopped event scanner!!"); |
| return; |
| } |
| |
| // We haven't been stopped, and the event list is indeed |
| // rather empty. Wait for some events to come along. |
| try { |
| // Send a heartbeat to the thread auditor |
| threadHandle.ping(); |
| |
| // Wait for events (with a timeout) |
| eventMutex.wait(threadHandle.getPingIntervalInMillisecs()); |
| } catch (InterruptedException ex) { |
| // Let the thread die a normal death |
| if (sipStack.isLoggingEnabled()) |
| sipStack.getStackLogger().logDebug("Interrupted!"); |
| return; |
| } |
| } |
| |
| // There are events in the 'pending events list' that need |
| // processing. Hold onto the old 'pending Events' list, but |
| // make a new one for the other methods to operate on. This |
| // tap-dancing is to avoid deadlocks and also to ensure that |
| // the list is not modified while we are iterating over it. |
| eventsToDeliver = pendingEvents; |
| pendingEvents = new LinkedList(); |
| } |
| ListIterator iterator = eventsToDeliver.listIterator(); |
| while (iterator.hasNext()) { |
| eventWrapper = (EventWrapper) iterator.next(); |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logDebug( |
| "Processing " + eventWrapper + "nevents " |
| + eventsToDeliver.size()); |
| } |
| try { |
| deliverEvent(eventWrapper); |
| } catch (Exception e) { |
| if (sipStack.isLoggingEnabled()) { |
| sipStack.getStackLogger().logError( |
| "Unexpected exception caught while delivering event -- carrying on bravely", e); |
| } |
| } |
| } |
| } // end While |
| } finally { |
| if (sipStack.isLoggingEnabled()) { |
| if (!this.isStopped) { |
| sipStack.getStackLogger().logFatalError("Event scanner exited abnormally"); |
| } |
| } |
| } |
| } |
| |
| } |