All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ehcache.transactions.xa.internal.EhcacheXAResource Maven / Gradle / Ivy

There is a newer version: 3.0.3
Show newest version
/*
 * Copyright Terracotta, Inc.
 *
 * 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.
 */

package org.ehcache.transactions.xa.internal;

import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.core.spi.store.Store;
import org.ehcache.transactions.xa.EhcacheXAException;
import org.ehcache.transactions.xa.internal.journal.Journal;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * The Ehcache {@link XAResource} implementation.
 *
 * @author Ludovic Orban
 */
public class EhcacheXAResource implements XAResource {

  private static final int DEFAULT_TRANSACTION_TIMEOUT_SECONDS = 30;

  private final Store> underlyingStore;
  private final Journal journal;
  private final XATransactionContextFactory transactionContextFactory;
  private volatile Xid currentXid;
  private volatile int transactionTimeoutInSeconds = DEFAULT_TRANSACTION_TIMEOUT_SECONDS;

  public EhcacheXAResource(Store> underlyingStore, Journal journal, XATransactionContextFactory transactionContextFactory) {
    this.underlyingStore = underlyingStore;
    this.journal = journal;
    this.transactionContextFactory = transactionContextFactory;
  }

  @Override
  public void commit(Xid xid, boolean onePhase) throws XAException {
    if (currentXid != null) {
      throw new EhcacheXAException("Cannot commit a non-ended start on : " + xid, XAException.XAER_PROTO);
    }
    TransactionId transactionId = new TransactionId(xid);
    XATransactionContext transactionContext = transactionContextFactory.get(transactionId);

    try {
      if (onePhase) {
        if (transactionContext == null) {
          throw new EhcacheXAException("Cannot commit in one phase unknown XID : " + xid, XAException.XAER_NOTA);
        }
        try {
          transactionContext.commitInOnePhase();
        } catch (XATransactionContext.TransactionTimeoutException tte) {
          throw new EhcacheXAException("Transaction timed out", XAException.XA_RBTIMEOUT);
        }
      } else {
        XATransactionContext commitContext = transactionContext;
        if (commitContext == null) {
          // recovery commit
          commitContext = new XATransactionContext(new TransactionId(new SerializableXid(xid)), underlyingStore, journal, null, 0L);
        }
        commitContext.commit(transactionContext == null);
      }
    } catch (IllegalArgumentException iae) {
      throw new EhcacheXAException("Cannot commit unknown XID : " + xid, XAException.XAER_NOTA);
    } catch (IllegalStateException ise) {
      throw new EhcacheXAException("Cannot commit XID : " + xid, XAException.XAER_PROTO, ise);
    } catch (StoreAccessException cae) {
      throw new EhcacheXAException("Cannot commit XID : " + xid, XAException.XAER_RMERR, cae);
    } finally {
      if (transactionContext != null) {
        transactionContextFactory.destroy(transactionId);
      }
    }
  }

  @Override
  public void forget(Xid xid) throws XAException {
    TransactionId transactionId = new TransactionId(xid);
    if (journal.isInDoubt(transactionId)) {
      throw new EhcacheXAException("Cannot forget in-doubt XID : " + xid, XAException.XAER_PROTO);
    }
    if (!journal.isHeuristicallyTerminated(transactionId)) {
      throw new EhcacheXAException("Cannot forget unknown XID : " + xid, XAException.XAER_NOTA);
    }
    journal.forget(transactionId);
  }

  @Override
  public int getTransactionTimeout() throws XAException {
    return transactionTimeoutInSeconds;
  }

  @Override
  public boolean isSameRM(XAResource xaResource) throws XAException {
    return xaResource == this;
  }

  @Override
  public int prepare(Xid xid) throws XAException {
    if (currentXid != null) {
      throw new EhcacheXAException("Cannot prepare a non-ended start on : " + xid, XAException.XAER_PROTO);
    }
    TransactionId transactionId = new TransactionId(xid);
    XATransactionContext transactionContext = transactionContextFactory.get(transactionId);
    if (transactionContext == null) {
      throw new EhcacheXAException("Cannot prepare unknown XID : " + xid, XAException.XAER_NOTA);
    }

    boolean destroyContext = false;
    try {
      destroyContext = transactionContext.prepare() == 0;
      return destroyContext ? XA_RDONLY : XA_OK;
    } catch (XATransactionContext.TransactionTimeoutException tte) {
      destroyContext = true;
      throw new EhcacheXAException("Transaction timed out", XAException.XA_RBTIMEOUT);
    } catch (IllegalStateException ise) {
      throw new EhcacheXAException("Cannot prepare XID : " + xid, XAException.XAER_PROTO, ise);
    } catch (StoreAccessException cae) {
      throw new EhcacheXAException("Cannot prepare XID : " + xid, XAException.XAER_RMERR, cae);
    } finally {
      if (destroyContext) {
        transactionContextFactory.destroy(transactionId);
      }
    }
  }

  @Override
  public Xid[] recover(int flags) throws XAException {
    if (flags != XAResource.TMNOFLAGS && flags != XAResource.TMSTARTRSCAN && flags != XAResource.TMENDRSCAN && flags != (XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN)) {
      throw new EhcacheXAException("Recover flags not supported : " + xaResourceFlagsToString(flags), XAException.XAER_INVAL);
    }

    if ((flags & XAResource.TMSTARTRSCAN) == XAResource.TMSTARTRSCAN) {
      List xids = new ArrayList();
      Set transactionIds = journal.recover().keySet();
      for (TransactionId transactionId : transactionIds) {
        // filter-out in-flight tx
        if (!transactionContextFactory.contains(transactionId)) {
          xids.add(transactionId.getSerializableXid());
        }
      }
      return xids.toArray(new Xid[xids.size()]);
    }
    return new Xid[0];
  }

  @Override
  public void rollback(Xid xid) throws XAException {
    if (currentXid != null) {
      throw new EhcacheXAException("Cannot rollback a non-ended start on : " + xid, XAException.XAER_PROTO);
    }
    TransactionId transactionId = new TransactionId(xid);
    XATransactionContext transactionContext = transactionContextFactory.get(transactionId);

    try {
      XATransactionContext rollbackContext = transactionContext;
      if (rollbackContext == null) {
        // recovery rollback
        rollbackContext = new XATransactionContext(new TransactionId(new SerializableXid(xid)), underlyingStore, journal, null, 0L);
      }

      rollbackContext.rollback(transactionContext == null);
    } catch (IllegalStateException ise) {
      throw new EhcacheXAException("Cannot rollback unknown XID : " + xid, XAException.XAER_NOTA);
    } catch (StoreAccessException cae) {
      throw new EhcacheXAException("Cannot rollback XID : " + xid, XAException.XAER_RMERR, cae);
    } finally {
      if (transactionContext != null) {
        transactionContextFactory.destroy(transactionId);
      }
    }
  }

  @Override
  public boolean setTransactionTimeout(int seconds) throws XAException {
    if (seconds < 0) {
      throw new EhcacheXAException("Transaction timeout must be >= 0", XAException.XAER_INVAL);
    }
    if (seconds == 0) {
      this.transactionTimeoutInSeconds = DEFAULT_TRANSACTION_TIMEOUT_SECONDS;
    } else {
      this.transactionTimeoutInSeconds = seconds;
    }
    return true;
  }

  @Override
  public void start(Xid xid, int flag) throws XAException {
    if (flag != XAResource.TMNOFLAGS && flag != XAResource.TMJOIN) {
      throw new EhcacheXAException("Start flag not supported : " + xaResourceFlagsToString(flag), XAException.XAER_INVAL);
    }
    if (currentXid != null) {
      throw new EhcacheXAException("Already started on : " + xid, XAException.XAER_PROTO);
    }

    TransactionId transactionId = new TransactionId(xid);
    XATransactionContext transactionContext = transactionContextFactory.get(transactionId);
    if (flag == XAResource.TMNOFLAGS) {
      if (transactionContext == null) {
        transactionContext = transactionContextFactory.createTransactionContext(transactionId, underlyingStore, journal, transactionTimeoutInSeconds);
      } else {
        throw new EhcacheXAException("Cannot start in parallel on two XIDs : starting " + xid, XAException.XAER_RMERR);
      }
    } else {
      if (transactionContext == null) {
        throw new EhcacheXAException("Cannot join unknown XID : " + xid, XAException.XAER_NOTA);
      }
    }

    if (transactionContext.hasTimedOut()) {
      transactionContextFactory.destroy(transactionId);
      throw new EhcacheXAException("Transaction timeout for XID : " + xid, XAException.XA_RBTIMEOUT);
    }
    currentXid = xid;
  }

  @Override
  public void end(Xid xid, int flag) throws XAException {
    if (flag != XAResource.TMSUCCESS && flag != XAResource.TMFAIL) {
      throw new EhcacheXAException("End flag not supported : " + xaResourceFlagsToString(flag), XAException.XAER_INVAL);
    }
    if (currentXid == null) {
      throw new EhcacheXAException("Not started on : " + xid, XAException.XAER_PROTO);
    }
    TransactionId transactionId = new TransactionId(currentXid);
    XATransactionContext transactionContext = transactionContextFactory.get(transactionId);
    if (transactionContext == null) {
      throw new EhcacheXAException("Cannot end unknown XID : " + xid, XAException.XAER_NOTA);
    }

    boolean destroyContext = false;
    if (flag == XAResource.TMFAIL) {
      destroyContext = true;
    }
    currentXid = null;

    try {
      if (transactionContext.hasTimedOut()) {
        destroyContext = true;
        throw new EhcacheXAException("Transaction timeout for XID : " + xid, XAException.XA_RBTIMEOUT);
      }
    } finally {
      if (destroyContext) {
        transactionContextFactory.destroy(transactionId);
      }
    }
  }

  public XATransactionContext getCurrentContext() {
    if (currentXid == null) {
      return null;
    }
    return transactionContextFactory.get(new TransactionId(currentXid));
  }

  private static String xaResourceFlagsToString(int flags) {
    StringBuilder sb = new StringBuilder();

    Set> entries = XARESOURCE_FLAGS_TO_NAMES.entrySet();
    for (Map.Entry entry : entries) {
      int constant = entry.getKey();
      String name = entry.getValue();
      if (constant != 0 && (flags & constant) == constant) {
        sb.append(name).append("|");
      }
    }

    if (sb.length() > 0) {
      sb.deleteCharAt(sb.length() - 1);
    } else {
      sb.append(XARESOURCE_FLAGS_TO_NAMES.get(0));
    }

    return sb.toString();
  }

  private static final Map XARESOURCE_FLAGS_TO_NAMES = new HashMap();
  static {
    try {
      for (Field field : XAResource.class.getFields()) {
        String name = field.getName();
        if (field.getType().equals(int.class) && name.startsWith("TM")) {
          Integer contant = (Integer) field.get(XAResource.class);
          XARESOURCE_FLAGS_TO_NAMES.put(contant, name);
        }
      }
    } catch (IllegalAccessException iae) {
      throw new RuntimeException("Cannot initialize XAResource flags map", iae);
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy