package com.linkedin.databus.client.netty;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
import java.io.ByteArrayInputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.sql.SQLException;
import org.apache.log4j.Logger;
import com.linkedin.databus.client.ChunkedBodyReadableByteChannel;
import com.linkedin.databus.client.DatabusSourcesConnection;
import com.linkedin.databus.core.DbusErrorEvent;
import com.linkedin.databus.core.DbusEventBuffer;
import com.linkedin.databus.core.DbusEventFactory;
import com.linkedin.databus.core.DbusEventInternalReadable;
import com.linkedin.databus.core.DbusEventInternalWritable;
import com.linkedin.databus.core.DbusPrettyLogUtils;
import com.linkedin.databus.core.InvalidEventException;
import com.linkedin.databus.core.PullerRetriesExhaustedException;
import com.linkedin.databus.core.ScnNotFoundException;
import com.linkedin.databus2.core.container.DatabusHttpHeaders;
import com.linkedin.databus2.core.container.request.BootstrapDBException;
import com.linkedin.databus2.core.container.request.BootstrapDatabaseTooOldException;
import com.linkedin.databus2.core.container.request.BootstrapDatabaseTooYoungException;
public class RemoteExceptionHandler
{
public static final String MODULE = RemoteExceptionHandler.class.getName();
public static final Logger LOG = Logger.getLogger(MODULE);
private final DatabusSourcesConnection _sourcesConn;
private final DbusEventBuffer _dbusEventBuffer;
private final DbusEventFactory _eventFactory;
public RemoteExceptionHandler(DatabusSourcesConnection sourcesConn,
DbusEventBuffer dataEventsBuffer,
DbusEventFactory eventFactory)
{
_sourcesConn = sourcesConn;
_dbusEventBuffer = dataEventsBuffer;
_eventFactory = eventFactory;
}
public int getPendingEventSize(ChunkedBodyReadableByteChannel readChannel)
{
String result = readChannel.getMetadata(DatabusHttpHeaders.DATABUS_PENDING_EVENT_SIZE);
if (result == null)
{
// There was no such header.
return 0;
}
try
{
return Integer.parseInt(result);
}
catch(NumberFormatException e)
{
LOG.error("Could not parse pending event size:" + result);
return 0;
}
}
public static String getExceptionName(ChunkedBodyReadableByteChannel readChannel)
{
String result = readChannel.getMetadata(DatabusHttpHeaders.DATABUS_ERROR_CAUSE_CLASS_HEADER);
if (result==null)
{
result = readChannel.getMetadata(DatabusHttpHeaders.DATABUS_ERROR_CLASS_HEADER);
}
return result;
}
public static String getExceptionMessage(ChunkedBodyReadableByteChannel readChannel)
{
String exceptionName = getExceptionName(readChannel);
if (null == exceptionName) return null;
StringBuilder result = new StringBuilder(exceptionName.length() + 100);
result.append("Remote exception");
String reqId = readChannel.getMetadata(DatabusHttpHeaders.DATABUS_REQUEST_ID_HEADER);
if (null != reqId) result.append(" for request id");
result.append(":");
result.append(exceptionName);
return result.toString();
}
public Throwable getException(ChunkedBodyReadableByteChannel readChannel)
{
Throwable remoteException = null;
String err = getExceptionName(readChannel);
if (null != err)
{ // in theory, we shall be reading the actual exception from the read channel.
if (err.equalsIgnoreCase(ScnNotFoundException.class.getName()))
{
remoteException = new ScnNotFoundException();
}
else if (err.equalsIgnoreCase(BootstrapDatabaseTooOldException.class.getName()))
{
remoteException = new BootstrapDatabaseTooOldException();
}
else if (err.equalsIgnoreCase( PullerRetriesExhaustedException.class.getName()))
{
remoteException = new PullerRetriesExhaustedException();
}
else if (err.equalsIgnoreCase(BootstrapDatabaseTooYoungException.class.getName()))
{
remoteException = new BootstrapDatabaseTooYoungException();
}
else if (err.equalsIgnoreCase(BootstrapDBException.class.getName()))
{
remoteException = new BootstrapDBException();
}
else if (err.equalsIgnoreCase(SQLException.class.getName()))
{
remoteException = new SQLException();
}
else
{
LOG.error("Unexpected remote error received: " + err);
}
LOG.info("Remote exception received: " + remoteException);
}
return remoteException;
}
public void handleException(Throwable remoteException)
throws InvalidEventException, InterruptedException
{
if ((remoteException instanceof BootstrapDatabaseTooOldException) || (remoteException instanceof PullerRetriesExhaustedException))
{
suspendConnectionOnError(remoteException);
}
else
{
LOG.error("Unexpected exception received: " + remoteException);
}
}
private void suspendConnectionOnError(Throwable exception)
throws InvalidEventException, InterruptedException
{
// suspend pull threads
_sourcesConn.getConnectionStatus().suspendOnError(exception);
// send an error event to dispatcher through dbusEventBuffer
DbusEventInternalReadable errorEvent = null;
if (exception instanceof BootstrapDatabaseTooOldException)
{
errorEvent = _eventFactory.createErrorEvent(
new DbusErrorEvent(exception, DbusEventInternalWritable.BOOTSTRAPTOOOLD_ERROR_SRCID));
}
else if (exception instanceof PullerRetriesExhaustedException)
{
errorEvent = _eventFactory.createErrorEvent(
new DbusErrorEvent(exception, DbusEventInternalWritable.PULLER_RETRIES_EXPIRED));
}
else
{
throw new InvalidEventException("Got an unrecognizable exception ");
}
byte[] errorEventBytes = new byte[errorEvent.getRawBytes().limit()];
if (LOG.isDebugEnabled())
{
LOG.debug("error event size: " + errorEventBytes.length);
LOG.debug("error event:" + errorEvent.toString());
}
errorEvent.getRawBytes().get(errorEventBytes);
ByteArrayInputStream errIs = new ByteArrayInputStream(errorEventBytes);
ReadableByteChannel errRbc = Channels.newChannel(errIs);
boolean success = false;
int retryCounter = 0;
while (!success && retryCounter < 10)
{
String errMsg = "Sending an internal system event to dispatcher. Retry count = " + retryCounter;
DbusPrettyLogUtils.logExceptionAtInfo(errMsg, exception, LOG);
success = _dbusEventBuffer.readEvents(errRbc) > 0 ? true : false;
if (!success)
{
LOG.warn("Unable to send an internal system event to dispatcher. Will retry later " + retryCounter);
retryCounter ++;
Thread.sleep(1000);
}
}
}
}