package com.tesora.dve.server.messaging;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.tesora.dve.comms.client.messages.MessageType;
import com.tesora.dve.comms.client.messages.MessageVersion;
import com.tesora.dve.concurrent.CompletionHandle;
import com.tesora.dve.concurrent.PEDefaultPromise;
import com.tesora.dve.db.DBEmptyTextResultConsumer;
import com.tesora.dve.exceptions.PECodingException;
import com.tesora.dve.server.connectionmanager.PerHostConnectionManager;
import com.tesora.dve.server.connectionmanager.SSContext;
import com.tesora.dve.server.statistics.manager.LogSiteStatisticRequest;
import com.tesora.dve.sql.util.Pair;
import com.tesora.dve.worker.DevXid;
import com.tesora.dve.worker.MysqlTextResultCollector;
import com.tesora.dve.worker.Worker;
public class WorkerRecoverSiteRequest extends WorkerRequest {
static Logger logger = Logger.getLogger( WorkerRecoverSiteRequest.class );
private static final long serialVersionUID = 1L;
final Map<String, Boolean> commitMap;
public WorkerRecoverSiteRequest(SSContext ctx, Map<String, Boolean> commitMap) {
super(ctx);
this.commitMap = commitMap;
}
@Override
public void executeRequest(final Worker w, final CompletionHandle<Boolean> callersResults) {
try {
final MysqlTextResultCollector results = new MysqlTextResultCollector();
PEDefaultPromise<Boolean> recoverListSQL = new PEDefaultPromise<Boolean>(){
@Override
public void success(Boolean xaRecoverHadResults) {
if (xaRecoverHadResults){
List<Pair<String, Boolean>> xidsToRecover = buildRecoverList(w, results);
final Iterator<Pair<String, Boolean>> recoveryItemIterator = xidsToRecover.iterator();
recoverNextItem(w, recoveryItemIterator, callersResults);
} else {
Exception codingError = new PECodingException("XA RECOVER did not return results");
codingError.fillInStackTrace();
this.failure(codingError);
}
}
@Override
public void failure(Exception t) {
super.failure(t);
}
};
//TODO: ignores provided groupDispatch
this.withGroupDispatch(results);
this.execute(w, new SQLCommand(PerHostConnectionManager.INSTANCE.lookupConnection(this.getConnectionId()),"XA RECOVER"), recoverListSQL);
} catch (Exception e) {
callersResults.failure(e);
}
}
private void recoverNextItem(final Worker w, final Iterator<Pair<String, Boolean>> recoveryItemIterator, final CompletionHandle<Boolean> callersPromise) {
if (!recoveryItemIterator.hasNext()){
logger.info(w.getName() + ": Completed XA Transaction Recovery for site " + w.getWorkerSite());
callersPromise.success(true);
return; //finished.
}
Pair<String, Boolean> recoveryInfo = recoveryItemIterator.next();
String recoverStatement;
String xid = recoveryInfo.getFirst();
Boolean hasCommitted = recoveryInfo.getSecond();
CompletionHandle<Boolean> resultForCurrentItem = new PEDefaultPromise<Boolean>(){
@Override
public void success(Boolean returnValue) {
recoverNextItem(w, recoveryItemIterator, callersPromise);
}
@Override
public void failure(Exception t) {
callersPromise.failure(t);
}
};
if (hasCommitted)
recoverStatement = "XA COMMIT " + xid;
else
recoverStatement = "XA ROLLBACK " + xid;
//TODO: ignores provided groupDispatch
this.withGroupDispatch(DBEmptyTextResultConsumer.INSTANCE);
this.execute(w, new SQLCommand(PerHostConnectionManager.INSTANCE.lookupConnection(this.getConnectionId()),recoverStatement), resultForCurrentItem);
}
private List<Pair<String, Boolean>> buildRecoverList(Worker w, MysqlTextResultCollector results) {
List<Pair<String, Boolean>> xidsToRecover;
xidsToRecover = new ArrayList<Pair<String,Boolean>>();
logger.info(w.getName() + ": Beginning XA Transaction Recovery for site " + w.getWorkerSite());
//returned columns are {formatID,gtrid_length,bqual_length,data}
for (List<String> row : results.getRowData()) {
int formatId = Integer.parseInt(row.get(0));
int gtrid_length = Integer.parseInt(row.get(1));
//bqual_length = Integer.parseInt(row.get(1));
String xidString = row.get(3);
if (formatId == DevXid.FORMAT_ID) {
String gtrid = xidString.substring(0, gtrid_length);
String bqual = xidString.substring(gtrid_length);
String recoveryXid = "'" + gtrid + "','" + bqual + "'," + DevXid.FORMAT_ID;
if (commitMap.containsKey(gtrid))
xidsToRecover.add(new Pair<String, Boolean>(recoveryXid, commitMap.get(gtrid)));
}
else {
if (logger.isDebugEnabled())
logger.debug("Skipped recoverable transaction with format id " + formatId);
}
}
return xidsToRecover;
}
@Override
public LogSiteStatisticRequest getStatisticsNotice() {
return null;
}
@Override
public MessageType getMessageType() {
return null;
}
@Override
public MessageVersion getVersion() {
return null;
}
}