package org.csc.phynixx.phynixx.testconnection;
/*
* #%L
* phynixx-connection
* %%
* Copyright (C) 2014 csc
* %%
* 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.
* #L%
*/
import org.csc.phynixx.common.exceptions.DelegatedRuntimeException;
import org.csc.phynixx.common.io.LogRecordReader;
import org.csc.phynixx.common.io.LogRecordWriter;
import org.csc.phynixx.common.logger.IPhynixxLogger;
import org.csc.phynixx.common.logger.PhynixxLogManager;
import org.csc.phynixx.loggersystem.logrecord.IDataRecord;
import org.csc.phynixx.loggersystem.logrecord.IDataRecordReplay;
import org.csc.phynixx.loggersystem.logrecord.IXADataRecorder;
import java.util.HashMap;
import java.util.Map;
/**
* the initial value of the counter is the defines the rollback state. Every increrment of the counter via {@link #act} increases the counter.
* The counter at commit are commitRollforward data.
* TestConnection provides a mechanism to activate predetermined points of interruption.
* These points leads the current work to interrupt.
* You can simulate abnormal situation like system crashes.
* The points of interruption are defined by the call of {@link #interrupt(org.csc.phynixx.phynixx.testconnection.TestInterruptionPoint)}.
* You can define a gate value that define how often the interruption point is reached till the exception is thrown.
* Feassable interruption pints are
* REQUIRES_TRANSACTION - after the rollback data is written and before the counter is incremented
* COMMIT - after the rollforward dat is written
* ROLLBACK -
* PREPARE
* CLOSE
*
* @author christoph
*/
public class TestConnection implements ITestConnection {
private Map<TestInterruptionPoint, Integer> interruptionFlags = new HashMap<TestInterruptionPoint, Integer>();
private void resetInterruptionFlags() {
TestInterruptionPoint[] values = TestInterruptionPoint.values();
for (int i = 0; i < values.length; i++) {
interruptionFlags.put(values[i], 0);
}
}
private IPhynixxLogger log = PhynixxLogManager.getLogger("test");
private Object connectionId = null;
private int increment = 0;
private int initialValue = 0;
private IXADataRecorder messageLogger = null;
private boolean autoCommit=false;
public boolean isAutoCommit() {
return autoCommit;
}
public void setAutoCommit(boolean autoCommit) {
this.autoCommit = autoCommit;
}
public IXADataRecorder getXADataRecorder() {
return messageLogger;
}
public void setXADataRecorder(IXADataRecorder messageLogger) {
this.messageLogger = messageLogger;
}
public TestConnection(Object id) {
resetInterruptionFlags();
this.connectionId = id;
}
/**
* sets the counter to the initial value
* this value has to be restored if the connection is rollbacked
*/
public void setInitialCounter(int value) {
this.increment = 0;
this.initialValue = value;
this.getXADataRecorder().writeRollbackData(Integer.toString(value).getBytes());
}
@Override
public boolean isInterruptFlag(TestInterruptionPoint interruptionPoint) {
return this.interruptionFlags.get(interruptionPoint) <= 0;
}
@Override
public void setInterruptFlag(TestInterruptionPoint interruptionPoint, int gate) {
interruptionFlags.put(interruptionPoint, gate);
}
@Override
public void setInterruptFlag(TestInterruptionPoint interruptionPoint) {
this.setInterruptFlag(interruptionPoint, 1);
}
public int getCounter() {
return this.initialValue + this.increment;
}
/* (non-Javadoc)
* @see de.csc.xaresource.sample.ITestConnection#getConnectionId()
*/
public Object getConnectionId() {
return connectionId;
}
/* (non-Javadoc)
* @see de.csc.xaresource.sample.ITestConnection#act()
*/
public void act(int inc) {
interrupt(TestInterruptionPoint.ACT);
this.increment = this.increment + inc;
log.info("TestConnection " + connectionId + " counter incremented to " + inc + " counter=" + this.getCounter());
}
@Override
public void reset() {
privReset();
}
/**
* reset without being tracked by the Listeners
*/
private void privReset() {
resetInterruptionFlags();
this.increment = 0;
this.initialValue=0;
}
public void close() {
this.interrupt(TestInterruptionPoint.CLOSE);
this.privReset();
log.info("TestConnection " + connectionId + " closed");
}
public void commit() {
try {
byte[] bytes = new LogRecordWriter().writeInt(this.initialValue).writeInt(this.increment).toByteArray();
if (this.getXADataRecorder() != null) {
this.getXADataRecorder().writeRollforwardData(bytes);
}
} catch (Exception e) {
throw new DelegatedRuntimeException(e);
}
interrupt(TestInterruptionPoint.COMMIT);
this.initialValue = this.initialValue+ increment;
this.increment =0;
log.info("TestConnection " + connectionId + " is committed");
}
public void prepare() {
interrupt(TestInterruptionPoint.PREPARE);
log.info("TestConnection " + connectionId + " is prepared");
}
public void rollback() {
interrupt(TestInterruptionPoint.ROLLBACK);
this.increment = 0;
log.info("TestConnection " + connectionId + " rollbacked");
}
public String toString() {
return "TestConnection " + connectionId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestConnection that = (TestConnection) o;
if (connectionId != null ? !connectionId.equals(that.connectionId) : that.connectionId != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return connectionId != null ? connectionId.hashCode() : 0;
}
private void interrupt(TestInterruptionPoint interruptionPoint) {
Integer gate = this.interruptionFlags.get(interruptionPoint) - 1;
if (gate == 0) {
throw new ActionInterruptedException();
}
// refresh gate
this.interruptionFlags.put(interruptionPoint, gate);
}
public void recover() {
this.getXADataRecorder().replayRecords(new MessageReplay(this));
}
@Override
public IDataRecordReplay recoverReplayListener() {
return new MessageReplay(this);
}
/**
* for debugging purpose
*/
static class MessageReplay implements IDataRecordReplay {
private TestConnection con;
MessageReplay(TestConnection con) {
this.con = con;
}
@Override
public void notifyNoMoreData() {
}
public void replayRollback(IDataRecord message) {
int initialValue = Integer.parseInt(new String(message.getData()[0]));
this.con.increment = initialValue;
}
public void replayRollforward(IDataRecord message) {
try {
LogRecordReader logRecordReader = new LogRecordReader(message.getData()[0]);
this.con.initialValue = logRecordReader.readInt();
this.con.increment = logRecordReader.readInt();
} catch (Exception e) {
throw new DelegatedRuntimeException(e);
}
}
}
}