package com.linkedin.databus2.ggParser.XmlStateMachine;
/*
*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.apache.log4j.Logger;
import com.linkedin.databus.monitoring.mbean.GGParserStatistics.TransactionInfo;
import com.linkedin.databus2.core.DatabusException;
import com.linkedin.databus2.producers.gg.GGEventGenerationFactory;
public class TransactionState extends AbstractStateTransitionProcessor
{
public final static String MODULE = TransactionState.class.getName();
public final static Logger LOG = Logger.getLogger(MODULE);
public static final String TRANSACTIONTIMESTAMPATTR = "timestamp";
//when calculating transaction size we need to adjust it for the size of the first element. See parseElement()
public static final int TRANSACTION_ELEMENT_SIZE = "<transaction timestamp=\"2013-07-29:13:26:15.000000\">".length();
public TransactionSuccessCallBack _transactionSuccessCallBack;
private final long UNINITIALIZEDTS = -1;
private long _currentTimeStamp = UNINITIALIZEDTS;
private String _lastSeenTimestampStr = null;
private long _startTransProcessingTimeNs = 0; //nano seconds
private long _startTransLocation = 0;
private long _transactionSize = 0;
public TransactionState(TransactionSuccessCallBack transactionSuccessCallBack)
{
super(STATETYPE.STARTELEMENT,TRANSACTION);
_transactionSuccessCallBack = transactionSuccessCallBack;
}
public long getCurrentTimeStamp()
{
return _currentTimeStamp;
}
public String getLastSeenTxnTimestampStr()
{
return _lastSeenTimestampStr;
}
@Override
public void cleanUpState(StateMachine stateMachine, XMLStreamReader xmlStreamReader)
{
_currentTimeStamp = UNINITIALIZEDTS;
_startTransProcessingTimeNs = 0;
_transactionSize = 0;
_startTransLocation = 0;
}
@Override
public void onEndElement(StateMachine stateMachine, XMLStreamReader xmlStreamReader)
throws Exception
{
_currentStateType = STATETYPE.ENDELEMENT;
if(LOG.isDebugEnabled())
LOG.debug("The current transaction has " + stateMachine.dbUpdateState.getSourceDbUpdatesMap().size() + " DbUpdates");
if(_transactionSuccessCallBack == null)
{
throw new DatabusException("No callback specified for the transaction state! Cannot proceed without a callback");
}
long endTransactionLocation = xmlStreamReader.getLocation().getCharacterOffset();
_transactionSize = endTransactionLocation - _startTransLocation;
// collect stats
long trTime = System.nanoTime() - _startTransProcessingTimeNs;
long scn = stateMachine.dbUpdateState.getScn();
TransactionInfo trInfo = new TransactionInfo(_transactionSize, trTime, _currentTimeStamp, scn);
if(stateMachine.dbUpdateState.getSourceDbUpdatesMap().size() == 0)
{
if(LOG.isDebugEnabled())
LOG.debug("The current transaction contains no dbUpdates, giving empty callback");
_transactionSuccessCallBack.onTransactionEnd(null, trInfo);
}
else{
List<PerSourceTransactionalUpdate> dbUpdates = sortDbUpdates(stateMachine.dbUpdateState.getSourceDbUpdatesMap());
_transactionSuccessCallBack.onTransactionEnd(dbUpdates, trInfo);
}
stateMachine.dbUpdateState.cleanUpState(stateMachine,xmlStreamReader);
cleanUpState(stateMachine,xmlStreamReader);
xmlStreamReader.nextTag();
setNextStateProcessor(stateMachine,xmlStreamReader);
}
@Override
public void onStartElement(StateMachine stateMachine, XMLStreamReader xmlStreamReader)
throws DatabusException, XMLStreamException
{
_currentStateType = STATETYPE.STARTELEMENT;
for(int i = 0; i < xmlStreamReader.getAttributeCount() ; i++)
{
if(xmlStreamReader.getAttributeName(i).getLocalPart().equals(TRANSACTIONTIMESTAMPATTR))
{
StringBuilder timeStamp = new StringBuilder(xmlStreamReader.getAttributeValue(i));
_lastSeenTimestampStr = timeStamp.toString();
String correctedTimestamp = timeStamp.append("000").toString(); //The timestamp given by golden gate does not have nanoseconds accuracy needed by oracle timestamp
_currentTimeStamp = GGEventGenerationFactory.ggTimeStampStringToNanoSeconds(correctedTimestamp);
}
}
if(_currentTimeStamp == UNINITIALIZEDTS)
throw new DatabusException("Unable to locate timestamp in the transaction tag in the xml");
// start of new transaction
if(_startTransProcessingTimeNs == 0) {
_startTransProcessingTimeNs = System.nanoTime();
_startTransLocation = xmlStreamReader.getLocation().getCharacterOffset();
// this is location of the END of the <transaction timestamp="..."> entity
// so we need to adjust the size of the transactions in bytes
_startTransLocation -= TRANSACTION_ELEMENT_SIZE;
}
//create dbupdates list
stateMachine.dbUpdateState.setSourceDbUpdatesMap(new HashMap<Integer, HashSet<DbUpdateState.DBUpdateImage>>());
xmlStreamReader.nextTag();
setNextStateProcessor(stateMachine,xmlStreamReader);
}
public void setCallBack(TransactionSuccessCallBack transactionSuccessCallBack)
{
_transactionSuccessCallBack = transactionSuccessCallBack;
}
private List<PerSourceTransactionalUpdate> sortDbUpdates(HashMap<Integer, HashSet<DbUpdateState.DBUpdateImage>> dbUpdates)
{
List<PerSourceTransactionalUpdate> sourceTransactionalUpdates = new ArrayList<PerSourceTransactionalUpdate>(dbUpdates.size());
for(Map.Entry<Integer,HashSet<DbUpdateState.DBUpdateImage>> _entry: dbUpdates.entrySet())
{
sourceTransactionalUpdates.add(new PerSourceTransactionalUpdate(_entry.getKey(),_entry.getValue()));
}
Collections.sort(sourceTransactionalUpdates);
return sourceTransactionalUpdates;
}
/**
* The class is to hold the data associated with a particular source and the corrosponding dbUpdate
*/
public static class PerSourceTransactionalUpdate implements Comparable<PerSourceTransactionalUpdate>{
private int _sourceId;
private Set<DbUpdateState.DBUpdateImage> _dbUpdate;
public int getSourceId()
{
return _sourceId;
}
public Set<DbUpdateState.DBUpdateImage> getDbUpdatesSet()
{
return _dbUpdate;
}
public int getNumDbUpdates()
{
if ( null == _dbUpdate)
return 0;
return _dbUpdate.size();
}
public PerSourceTransactionalUpdate(int sourceId, Set<DbUpdateState.DBUpdateImage> dbUpdate)
{
this._sourceId = sourceId;
this._dbUpdate = dbUpdate;
}
@Override
public int compareTo(PerSourceTransactionalUpdate perSourceTransactionalUpdate)
{
if(this.getSourceId() == perSourceTransactionalUpdate.getSourceId())
{
LOG.error("The transactional update list cannot contain duplicate source IDs");
return 0;
}
else if (this.getSourceId() < perSourceTransactionalUpdate.getSourceId())
return -1;
else
return 1;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PerSourceTransactionalUpdate that = (PerSourceTransactionalUpdate) o;
if (_sourceId != that._sourceId) return false;
if (_dbUpdate != null ? !_dbUpdate.equals(that._dbUpdate) : that._dbUpdate != null) return false;
return true;
}
@Override
public int hashCode()
{
int result = _sourceId;
result = 31 * result + (_dbUpdate != null ? _dbUpdate.hashCode() : 0);
return result;
}
}
}