/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.container;
import com.ericsson.ssa.container.processor.QueuedProcessor;
import com.ericsson.ssa.container.processor.QueuedProcessor.ProcessorException;
import com.ericsson.ssa.container.processor.QueuedTask;
import com.ericsson.ssa.container.reporter.Reporter;
import com.ericsson.ssa.container.startup.SipMonitoring;
import com.ericsson.ssa.sip.Dispatcher;
import com.ericsson.ssa.sip.Header;
import com.ericsson.ssa.sip.Layer;
import com.ericsson.ssa.sip.LayerHelper;
import com.ericsson.ssa.sip.SipParser;
import com.ericsson.ssa.sip.SipServletMessageImpl;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetResolver;
import com.ericsson.ssa.sip.dns.TargetTuple;
import com.ericsson.ssa.sip.timer.GeneralTimer;
import com.ericsson.ssa.sip.timer.TimerServiceImpl;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jvnet.glassfish.comms.util.LogUtil;
/**
* @author ekrigro
* @reveiwed ehsroha 2006-nov-14
*/
public abstract class LinkBase implements Link {
protected static final int BUFFER_SIZE = 4096;
private Logger _log = LogUtil.SIP_LOGGER.getLogger();
protected OLDNetworkManager _networkManager;
protected SipContainerThreadPool _threadPool = SipContainerThreadPool.getInstance();
protected Layer _nextLayer;
protected SipParser _parser = SipParser.getInstance();
protected ByteBuffer _readBuffer = null;
protected ByteBuffer _writeBuffer = null;
protected ReentrantLock _readMutex = new ReentrantLock();
protected SipServletMessageImpl _message = null;
private QueuedProcessor _writeProcessor;
private GeneralTimer _linkAliveTimer;
private boolean _enableLinkAliveTimer;
private AtomicBoolean _linkActive = new AtomicBoolean(false);
public LinkBase(OLDNetworkManager networkManager, Layer next,
boolean enableLinkAliveTimer) {
_networkManager = networkManager;
_nextLayer = next;
// TODO: We have found that there might be problems if read buffers are
// placed in the thread pool queue.
// As a consequence the linkAliveTimer is disabled.
_enableLinkAliveTimer = enableLinkAliveTimer;
// The second argument (0) will cause the processor thread to die
// immediately when the queue is empty.
_writeProcessor = new QueuedProcessor(_networkManager.getSipLinkMaxQueueLength(),
0);
if (_enableLinkAliveTimer) {
_linkAliveTimer = TimerServiceImpl.getInstance()
.createTimer(this,
_networkManager.getSipLinkAliveTimeout() * 1000,
_networkManager.getSipLinkAliveTimeout() * 1000,
false, "Link alive timer");
}
}
public LinkBase(OLDNetworkManager networkManager, Layer next) {
this(networkManager, next, false);
}
public abstract void write(InetSocketAddress remote, ByteBuffer buffer)
throws IOException;
public void dispatch(SipServletRequestImpl req) {
Dispatcher nextDispatcher = req.popDispatcher();
if (nextDispatcher != null) {
nextDispatcher.dispatch(req);
return;
}
putTask(new WriteRequestTask(req));
}
/**
* Sends the error. Asynchronously, i.e. in a separate thread so that the
* caller thread is not blocked, if required.
*
* @param req
* @param async
* if true the error is sent asynchronously, i.e. in a separate
* thread so that the caller thread is not blocked.
*/
private void sendError(final SipServletRequestImpl req, boolean async) {
// RFC 3261, 8.1.3.1 If a fatal transport error is reported by the
// transport layer (generally, due to fatal ICMP errors in UDP or
// connection failures in TCP), the condition MUST be treated as a
// 503 (Service Unavailable) status code.
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE, "Send 503 due to failure when writing: " +
req);
}
if (req.getMethod().equals("ACK")) {
return;
}
if (async) {
_threadPool.execute(new Callable() {
public Object call() throws Exception {
SipServletResponseImpl resp = req.createTerminatingResponse(503);
//_nextLayer.next(resp);
LayerHelper.next(resp, _nextLayer, _nextLayer);
return null;
}
});
} else {
SipServletResponseImpl resp = req.createTerminatingResponse(503);
// _nextLayer.next(resp);
LayerHelper.next(resp, _nextLayer, _nextLayer);
}
}
public void dispatch(SipServletResponseImpl resp) {
Dispatcher nextDispatcher = resp.popDispatcher();
if (nextDispatcher != null) {
nextDispatcher.dispatch(resp);
return;
}
putTask(new WriteResponseTask(resp));
}
public final void close() throws IOException {
shutdownWriteProcessor();
closeImpl();
}
/**
* Shuts down the {@link #_writeProcessor} and cancels the link alive timer.
*/
protected void shutdownWriteProcessor() {
cancelLinkAliveTimer();
_writeProcessor.shutdown();
}
/**
* Implementation of close() to be provided by subclasses.
*
* @throws IOException
*/
protected abstract void closeImpl() throws IOException;
/**
* Called when the {@link #_linkAliveTimer} fires.
*/
public void timeout(GeneralTimer timer) {
if (!_linkActive.getAndSet(false)) {
_writeProcessor.shutdown();
try {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
LinkBase.this + " Closed link " + getInfo() +
" due to no activity for " +
_networkManager.getSipLinkAliveTimeout() + " seconds");
}
close();
} catch (IOException e) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE, "Failure when closing link: " + e);
}
}
}
}
/**
* Cancels the (possible) current {@link #_linkAliveTimer} and starts a new.
*/
public void markAsActive() {
_linkActive.set(true);
}
/**
* Cancel the {@link #_linkAliveTimer}, if existing.
*/
private void cancelLinkAliveTimer() {
if (_linkAliveTimer != null) {
_linkAliveTimer.cancel();
}
}
/**
* Puts a task for processing in the write processor.
*
* @param task
* @throws ProcessorException
*/
private void putTaskImpl(QueuedTask task) throws ProcessorException {
markAsActive();
_writeProcessor.put(task);
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE, "writeProcessor: " + _writeProcessor);
}
}
/**
* Puts a task for processing in the write processor and handles exceptions.
*
* @param task
*/
protected void putTask(QueuedTask task) {
try {
putTaskImpl(task);
} catch (ProcessorException e) {
_log.log(Level.FINE,
"Failed to put task for writing due to exceeded queue size.");
}
}
/**
* Puts a task for processing in the write processor.
*
* @param task
*/
private void putTask(WriteRequestTask task) {
try {
putTaskImpl(task);
} catch (ProcessorException e) {
SipServletRequestImpl request = ((WriteRequestTask) task).getRequest();
_log.log(Level.FINE,
"Network OUT request " + request.getMethod() +
" is dropped due to exceeded queue size --> \r\n" +
request.toString());
sendError(request, false);
}
}
protected void handleMessage(ByteBuffer buffer,
final InetSocketAddress local, final SipTransports transportProtocol,
final InetSocketAddress socketAddress) {
SipServletMessageImpl parsedMessage = null;
try {
final TargetTuple remote = new TargetTuple(transportProtocol,
socketAddress);
// TODO - change so that the parser can do next(), to avoid
// instanceof
int initialSize = 0;
int remaining = buffer.remaining();
while ((remaining > 0) && (initialSize != remaining)) {
initialSize = remaining;
SipParserErrorHandlerImpl sipParserErrorHandler = new SipParserErrorHandlerImpl(this);
sipParserErrorHandler.setErrorResponseEnabled(_networkManager.isErrorResponseEnabled()); // From
// preferences
parsedMessage = _parser.parseMessage(_message, buffer, local,
remote, sipParserErrorHandler);
remaining = buffer.remaining();
if ((parsedMessage != null) &&
parsedMessage.isMessageComplete() && (remaining > 0)) {
final SipServletMessageImpl msg = parsedMessage;
parsedMessage = null;
_threadPool.execute(new Callable() {
public Object call() throws Exception {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Executing Inner Task -> lock = " +
_readMutex.isHeldByCurrentThread());
}
processMessage(msg);
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Executing Inner Task -> lock = " +
_readMutex.isHeldByCurrentThread());
}
return null;
}
});
}
if ((remaining > 0) && (initialSize != remaining)) {
_message = parsedMessage;
buffer.mark(); // TODO - check if reset is called?
buffer.compact();
buffer.flip();
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Position = " + buffer.position() + " ,Remains = " +
buffer.remaining());
}
}
}
if (parsedMessage == null) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"The parsed request is null - not continuing!(" +
buffer.position() + ')');
}
int p = buffer.limit();
buffer.clear();
buffer.position(p);
_message = null;
if (SipMonitoring.isEnabled(SipMonitoring.NETWORK_MANAGER)) {
_networkManager.incrEasInvalidSipMessages();
}
return;
}
if (!parsedMessage.isMessageComplete()) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"The parsed request is not complete - not continuing! (" +
buffer.position() + ')');
}
if (buffer.hasRemaining()) {
buffer.compact();
} else {
buffer.clear();
}
_message = parsedMessage;
return;
}
buffer.clear();
_message = null;
} catch (Throwable t) {
if (t instanceof Exception) {
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO,
"Exception processing request " + getInfo() + ": " +
t.getMessage(), t);
}
} else {
// FIXME alarm for e.g. OutOfMemoryError
_log.log(Level.SEVERE, "Caught Throwable: ", t);
}
// TODO remove isLocked
if (_readMutex.isLocked() && _readMutex.isHeldByCurrentThread()) {
_message = null;
buffer.clear();
}
if (SipMonitoring.isEnabled(SipMonitoring.NETWORK_MANAGER)) {
_networkManager.incrEasInvalidSipMessages();
}
return;
} finally {
_readMutex.unlock();
}
processMessage(parsedMessage);
}
protected void processMessage(final SipServletMessageImpl message) {
if (message instanceof SipServletRequestImpl) {
SipServletRequestImpl req = (SipServletRequestImpl) message;
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Network IN request " + req.getMethod() + " --> \r\n" +
req.toString());
}
if (hasBodyWithoutContentType(message)) {
return;
}
// Respones should use the same link
req.pushTransactionDispatcher(this);
req.pushApplicationDispatcher(_networkManager);
// trigger reporter for network manager
Reporter reporter = _networkManager.getReporter();
if (reporter != null) {
reporter.logIncomingRequest(Reporter.InterceptionType.LAYER, req, NetworkManager.class.getSimpleName());
}
_nextLayer.next(req);
if (reporter != null) {
reporter.logPostIncomingRequest(Reporter.InterceptionType.LAYER, req, NetworkManager.class.getSimpleName(), null);
}
if (SipMonitoring.isEnabled(SipMonitoring.NETWORK_MANAGER)) {
_networkManager.incrEasReceivedSipRequests();
}
} else {
SipServletResponseImpl resp = (SipServletResponseImpl) message;
Header cseq = resp.getRawHeader(Header.CSEQ);
if (cseq == null) {
if (SipMonitoring.isEnabled(SipMonitoring.NETWORK_MANAGER)) {
_networkManager.incrEasInvalidSipMessages();
}
return; // Drop since missing required header
}
String c = cseq.getValue();
int index = c.indexOf(' ');
resp.setMethod(c.substring(index + 1));
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Network IN response " + resp.getStatus() + " " +
resp.getMethod() + " --> \r\n" + resp.toString());
}
// trigger reporter for network manager
Reporter reporter = _networkManager.getReporter();
if (reporter != null) {
reporter.logIncomingResponse(Reporter.InterceptionType.LAYER, resp, NetworkManager.class.getSimpleName());
}
_nextLayer.next(resp);
if (reporter != null) {
reporter.logPostIncomingResponse(Reporter.InterceptionType.LAYER, resp, NetworkManager.class.getSimpleName(), null);
}
if (SipMonitoring.isEnabled(SipMonitoring.NETWORK_MANAGER)) {
_networkManager.incrEasReceivedSipResponses();
}
}
}
public boolean hasBodyWithoutContentType(SipServletMessageImpl message) {
// The SIP-stack should refuse all request with body and without
// Content-Type header field.
if (message.hasBody() && (message.getContentType() == null)) {
try {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Missing Content-Type header field, the request has a body.");
}
SipServletRequestImpl req = (SipServletRequestImpl) message;
String phraze = "Missing Content-Type header field";
SipServletResponseImpl resp = req.createTerminatingResponse(400,
phraze);
// TR HH52078
if (resp != null) {
while (resp.popDispatcher() != null) {
} // After this layer it shouldn't go anywhere
TargetResolver tr = TargetResolver.getInstance();
TargetTuple tt = tr.resolveResponse(resp);
if (tt != null) {
resp.setRemote(tt);
dispatch(resp);
}
} else if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"failed to find out where to send error response " +
getInfo() + ".");
}
} catch (Exception ignore) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Unexpected exception " + getInfo() + ": " + ignore);
}
}
return true;
}
return false;
}
/**
* Common base class for write tasks sent to {@link #_writeProcessor}.
* Supervises the stale timeout, which is calculated timeout as
* min(SipLinkTimeout*SipLinkTimeoutRetries, 32 s)
*/
private abstract class WriteTask implements QueuedTask {
long timeout;
/**
* Constructor.
*/
public WriteTask() {
this.timeout = System.currentTimeMillis() +
Math.min(_networkManager.getSipLinkTimeout() * _networkManager.getSipLinkTimeoutRetries() * _networkManager.getSipLinkMaxQueueLength(),
32000);
}
/**
* Returns "write task is stale error" error if timeout is exceeded.
*/
public String getError() {
if (System.currentTimeMillis() > timeout) {
return "write task is stale";
} else {
return null;
}
}
}
/**
* Task for writing a response to the link.
*/
private class WriteResponseTask extends WriteTask {
SipServletResponseImpl response;
/**
* Constructor.
*
* @param response
* the response to write
*/
public WriteResponseTask(SipServletResponseImpl response) {
this.response = response;
}
/**
* Do the writing ({@link #writeResponse(SipServletResponseImpl)}).
*/
public void run() {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Network OUT response " + response.getMethod() +
" is going to be written to link: " + getInfo() +
" --> \r\n" + response.toString());
}
writeResponse(response);
}
/**
* Do error action (actaually nothing, just silent drop of response).
*/
public void doErrorAction(String cause) {
// Silently drop for responses
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Network OUT response " + response.getMethod() +
" is dropped due to " + cause + " --> \r\n" +
response.toString());
}
}
/**
* Implements the actual writing of a response.
*
* @param req
* the request to write
*/
private void writeResponse(SipServletResponseImpl resp) {
try {
resp.toBufferInit();
while (resp.toBufferHasRemaining()) {
_writeBuffer.clear();
resp.toBuffer(_writeBuffer);
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Sending:" + _writeBuffer.position() + " bytes");
}
_writeBuffer.flip();
write(resp.getRemote().getSocketAddress(), _writeBuffer);
}
} catch (IOException ioe) {
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO,
"Got IOException in write " + getInfo() + ": ", ioe);
}
} catch (Throwable t) {
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO,
"Got unexpected exception in dispatch " + getInfo() +
": ", t);
}
} finally {
if (!isOpen()) {
// Link has been closed, shutdown writeProcessor
shutdownWriteProcessor();
}
}
if (SipMonitoring.isEnabled(SipMonitoring.NETWORK_MANAGER)) {
_networkManager.incrEasSentSipResponses();
}
}
}
/**
* Task for writing a request to the link.
*/
private class WriteRequestTask extends WriteTask {
SipServletRequestImpl request;
/**
* Constructor.
*
* @param request
* the request to write
*/
public WriteRequestTask(SipServletRequestImpl request) {
this.request = request;
}
/**
* Gets the request to write.
*
* @return the request
*/
public SipServletRequestImpl getRequest() {
return request;
}
/**
* Do error action: send and error ({@link LinkBase#sendError(SipServletRequestImpl, boolean)}).
*/
public void doErrorAction(String cause) {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Network OUT request " + request.getMethod() +
" is dropped due to " + cause + " --> \r\n" +
request.toString());
}
sendError(request, true);
}
/**
* Do the writing.
*/
public void run() {
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Network OUT request " + request.getMethod() +
" is going to be written to link: " + getInfo() +
"--> \r\n" + request.toString());
}
writeRequest(request);
}
/**
* Implements the actual writing of a request.
*
* @param req
* the request to write
*/
private void writeRequest(SipServletRequestImpl req) {
try {
req.toBufferInit();
while (req.toBufferHasRemaining()) {
_writeBuffer.clear();
req.toBuffer(_writeBuffer);
if (_log.isLoggable(Level.FINE)) {
_log.log(Level.FINE,
"Sending:" + _writeBuffer.position() + " bytes");
}
_writeBuffer.flip();
write(req.getRemote().getSocketAddress(), _writeBuffer);
}
} catch (IOException ioe) {
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO,
"Got IOException in write " + getInfo() + ": ", ioe);
}
sendError(req, true);
return;
} catch (Throwable t) {
if (_log.isLoggable(Level.INFO)) {
_log.log(Level.INFO,
"Got unexpected exception in dispatch " + getInfo() +
": ", t);
}
sendError(req, true);
return;
} finally {
if (!isOpen()) {
// Link has been closed, shutdown writeProcessor
shutdownWriteProcessor();
}
}
if (SipMonitoring.isEnabled(SipMonitoring.NETWORK_MANAGER)) {
_networkManager.incrEasSentSipRequests();
}
}
}
}