Package com.sun.sgs.impl.service.transaction

Source Code of com.sun.sgs.impl.service.transaction.TransactionImpl

/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.sun.sgs.impl.service.transaction;

import com.sun.sgs.app.TransactionAbortedException;
import com.sun.sgs.app.TransactionNotActiveException;
import com.sun.sgs.app.TransactionTimeoutException;
import com.sun.sgs.impl.profile.ProfileCollectorHandle;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import com.sun.sgs.profile.ProfileCollector.ProfileLevel;
import com.sun.sgs.service.NonDurableTransactionParticipant;
import com.sun.sgs.service.Transaction;
import com.sun.sgs.service.TransactionListener;
import com.sun.sgs.service.TransactionParticipant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/** Provides an implementation of Transaction. */
final class TransactionImpl implements Transaction {

    /** Logger for this class. */
    private static final LoggerWrapper logger =
  new LoggerWrapper(Logger.getLogger(TransactionImpl.class.getName()));

    /** The possible states of a transaction. */
    private static enum State {
  /** In progress */
  ACTIVE,
  /** Begun preparation */
  PREPARING,
  /** Begun aborting */
  ABORTING,
  /** Completed aborting */
  ABORTED,
  /** Begun committing */
  COMMITTING,
  /** Completed committing */
  COMMITTED
    }

    /** The name of the profile participant for the transaction */
    private static final String TXN_PROFILE_NAME =
            TransactionImpl.class.getName();

    /** The transaction ID. */
    private final long tid;

    /** The time the transaction was created. */
    private final long creationTime;

    /** The length of time that this transaction is allowed to run.*/
    private final long timeout;

    /** The thread associated with this transaction. */
    private final Thread owner;

    /** Whether the prepareAndCommit optimization should be used. */
    private final boolean disablePrepareAndCommitOpt;
   
    /** The state of the transaction. */
    private State state;

    /**
     * The transaction participants.  If there is a durable participant, it
     * will be listed last.  Participants whose prepare method returns true
     * (read-only) are removed from this list.
     */
    private final List<TransactionParticipant> participants =
  new ArrayList<TransactionParticipant>();

    /** Whether this transaction has a durable participant. */
    private boolean hasDurableParticipant = false;

    /**
     * The exception that caused the transaction to be aborted, or null if no
     * abort occurred.  Callers should synchronize on the current instance when
     * accessing this field unless they have checked that they are being called
     * from the creating thread.
     */
    private Throwable abortCause = null;

    /** The collectorHandle used to report participant detail. */
    private final ProfileCollectorHandle collectorHandle;

    /** Collected profiling data on each participant, created only if
     *  global profiling is set to MEDIUM at the start of the transaction.
     */
    private final HashMap<String, ProfileParticipantDetailImpl> detailMap;

    /**
     * The registered {@code TransactionListener}s, or {@code null}.  The
     * listeners are stored and called in the order registered, to simplify
     * testing.
     */
    private List<TransactionListener> listeners = null;

    /**
     * Creates an instance with the specified transaction ID, timeout,
     * prepare and commit optimization flag, and collectorHandle.
     */
    TransactionImpl(long tid, long timeout, boolean usePrepareAndCommitOpt,
                    ProfileCollectorHandle collectorHandle)
    {
  this.tid = tid;
  this.timeout = timeout;
        this.disablePrepareAndCommitOpt = usePrepareAndCommitOpt;
  this.collectorHandle = collectorHandle;
  creationTime = System.currentTimeMillis();
  owner = Thread.currentThread();
  state = State.ACTIVE;
  if (collectorHandle.getCollector().
                getDefaultProfileLevel().ordinal() >=
                ProfileLevel.MEDIUM.ordinal())
        {
      detailMap = new HashMap<String, ProfileParticipantDetailImpl>();

            // create a fake detail entry for notification times
            detailMap.put(TXN_PROFILE_NAME,
                          new ProfileParticipantDetailImpl(TXN_PROFILE_NAME));
  } else {
      detailMap = null;
  }
  logger.log(Level.FINER, "create {0}", this);
    }

    /* -- Implement Transaction -- */

    /** {@inheritDoc} */
    public byte[] getId() {
  return longToBytes(tid);
    }

    /** {@inheritDoc} */
    public long getCreationTime() {
  return creationTime;
    }

    /** {@inheritDoc} */
    public long getTimeout() {
  return timeout;
    }

    /** {@inheritDoc} */
    public void checkTimeout() {
  checkThread("checkTimeout");
  logger.log(Level.FINEST, "checkTimeout {0}", this);
  switch (state) {
  case ABORTED:
  case COMMITTED:
      throw new TransactionNotActiveException(
    "Transaction is not active: " + state);
  case ABORTING:
  case COMMITTING:
      return;
  case ACTIVE:
  case PREPARING:
      break;
  default:
      throw new AssertionError();
  }
  long runningTime = System.currentTimeMillis() - getCreationTime();
  if (runningTime > getTimeout()) {
      TransactionTimeoutException exception =
    new TransactionTimeoutException(
        "transaction timed out: " + runningTime + " ms");
      abort(exception);
      throw exception;
  }
    }

    /** {@inheritDoc} */
    public void join(TransactionParticipant participant) {
  checkThread("join");
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "join {0} participant:{1}", this,
           getParticipantInfo(participant));
  }
  if (participant == null) {
      throw new NullPointerException("Participant must not be null");
  } else if (state == State.ABORTED) {
      throw new TransactionNotActiveException(
    "Transaction is not active", abortCause);
  } else if (state != State.ACTIVE) {
      throw new IllegalStateException(
    "Transaction is not active: " + state);
  }
  if (!participants.contains(participant)) {
      if (participant instanceof NonDurableTransactionParticipant) {
    if (hasDurableParticipant) {
        participants.add(participants.size() - 1, participant);
    } else {
        participants.add(participant);
    }
      } else if (!hasDurableParticipant) {
    hasDurableParticipant = true;
    participants.add(participant);
      } else {
    throw new UnsupportedOperationException(
        "Attempt to add multiple durable participants");
      }
      if (detailMap != null) {
    String name = participant.getTypeName();
    detailMap.put(name, new ProfileParticipantDetailImpl(name));
      }
  }
    }

    /** {@inheritDoc} */
    public void abort(Throwable cause) {
  checkThread("abort");
  if (cause == null) {
      throw new NullPointerException("The cause cannot be null");
  }
  logger.log(Level.FINER, "abort {0}", this);
  switch (state) {
  case ACTIVE:
  case PREPARING:
      break;
  case ABORTING:
      return;
  case ABORTED:
      throw new TransactionNotActiveException(
    "Transaction is not active", abortCause);
  case COMMITTING:
  case COMMITTED:
      throw new IllegalStateException(
    "Transaction is not active: " + state, cause);
  default:
      throw new AssertionError();
  }
  state = State.ABORTING;
  synchronized (this) {
      abortCause = cause;
  }
  long startTime = 0;
  for (TransactionParticipant participant : participants) {
      if (logger.isLoggable(Level.FINEST)) {
    logger.log(Level.FINEST, "abort {0} participant:{1}",
         this, getParticipantInfo(participant));
      }
      if (detailMap != null) {
    startTime = System.currentTimeMillis();
      }
      try {
    participant.abort(this);
      } catch (RuntimeException e) {
    if (logger.isLoggable(Level.WARNING)) {
        logger.logThrow(
      Level.WARNING, e,
      "abort {0} participant:{1} failed",
      this, getParticipantInfo(participant));
    }
      }
      if (detailMap != null) {
    long finishTime = System.currentTimeMillis();
    ProfileParticipantDetailImpl detail =
        detailMap.get(participant.getTypeName());
    detail.setAborted(finishTime - startTime);
    collectorHandle.addParticipant(detail);
      }
  }
  state = State.ABORTED;

        startTime = System.currentTimeMillis();
  notifyListenersAfter(false);
        if (detailMap != null) {
            long finishTime = System.currentTimeMillis();
            ProfileParticipantDetailImpl txn = detailMap.get(TXN_PROFILE_NAME);
            txn.setAborted(finishTime - startTime);
            collectorHandle.addParticipant(txn);
        }
    }

    /** {@inheritDoc} */
    public synchronized boolean isAborted() {
  return abortCause != null;
    }

    /** {@inheritDoc} */
    public synchronized Throwable getAbortCause() {
  return abortCause;
    }

    /** {@inheritDoc} */
    public void registerListener(TransactionListener listener) {
  checkThread("registerListener");
  if (listener == null) {
      throw new NullPointerException("The listener must not be null");
  } else if (state != State.ACTIVE) {
      throw new TransactionNotActiveException(
    "Transaction is not active: " + state);
  }
  if (listeners == null) {
      listeners = new ArrayList<TransactionListener>();
      listeners.add(listener);
  } else if (!listeners.contains(listener)) {
      listeners.add(listener);
  }
    }

    /* -- Object methods -- */

    /**
     * Returns a string representation of this instance.
     *
     * @return  a string representation of this instance
     */
    public String toString() {
  return "TransactionImpl[tid:" + tid +
      ", creationTime:" + creationTime +
      ", timeout:" + timeout +
      ", state:" + state + "]";
    }

    /**
     * Returns <code>true</code> if the argument is an instance of the same
     * class with the same transaction ID.
     *
     * @return <code>true</code> if the argument equals this instance,
     *         otherwise <code>false</code>
     */
    public boolean equals(Object object) {
  return (object instanceof TransactionImpl) &&
      tid == ((TransactionImpl) object).tid;
    }

    /**
     * Returns a hash code value for this object.
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
  return (int) (tid >>> 32) ^ (int) tid;
    }

    /* -- Other methods -- */

    /**
     * Commits this transaction
     *
     * @throws  TransactionNotActiveException if the transaction has been
     *    aborted
     * @throws  TransactionAbortedException if a call to {@link
     *    TransactionParticipant#prepare prepare} on a transaction
     *    participant or to {@link TransactionListener#beforeCompletion
     *    beforeCompletion} on a transaction listener aborts the
     *    transaction but does not throw an exception
     * @throws  IllegalStateException if {@code prepare} has been called on any
     *    transaction participant and {@link Transaction#abort abort} has
     *    not been called on the transaction, or if called from a thread
     *    that is not the thread that created this transaction
     * @throws  Exception any exception thrown when calling {@code prepare} on
     *    a participant or {@code beforeCompletion} on a listener
     * @see  TransactionHandle#commit TransactionHandle.commit
     */
    void commit() throws Exception {
  checkThread("commit");
  logger.log(Level.FINER, "commit {0}", this);
  if (state == State.ABORTED) {
      throw new TransactionNotActiveException(
    "Transaction is not active", abortCause);
  } else if (state != State.ACTIVE) {
      throw new IllegalStateException(
    "Transaction is not active: " + state);
  }

        long startTime = System.currentTimeMillis();
  notifyListenersBefore();
       
        // create a fake participant with the before and after commit times
        if (detailMap != null) {
            ProfileParticipantDetailImpl txn = detailMap.get(TXN_PROFILE_NAME);
            txn.setPrepared(System.currentTimeMillis() - startTime, true);
        }

  state = State.PREPARING;
  ProfileParticipantDetailImpl detail = null;
  for (Iterator<TransactionParticipant> iter = participants.iterator();
       iter.hasNext(); )
  {
      TransactionParticipant participant = iter.next();
      if (detailMap != null) {
    detail = detailMap.get(participant.getTypeName());
    startTime = System.currentTimeMillis();
      }
      try {
    if (iter.hasNext() || disablePrepareAndCommitOpt) {
        boolean readOnly = participant.prepare(this);
        if (detail != null) {
      detail.setPrepared(System.currentTimeMillis() -
             startTime, readOnly);
        }
        if (readOnly) {
      iter.remove();
      if (detail != null) {
          collectorHandle.addParticipant(detail);
      }
        }
        if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
           "prepare {0} participant:{1} returns {2}",
           this, getParticipantInfo(participant),
           readOnly);
        }
    } else {
        participant.prepareAndCommit(this);
        if (detail != null) {
      detail.
          setCommittedDirectly(System.currentTimeMillis() -
             startTime);
      collectorHandle.addParticipant(detail);
        }
        iter.remove();
        if (logger.isLoggable(Level.FINEST)) {
      logger.log(
          Level.FINEST,
          "prepareAndCommit {0} participant:{1} returns",
          this, getParticipantInfo(participant));
        }
    }
      } catch (Exception e) {
    if (logger.isLoggable(Level.FINEST)) {
        logger.logThrow(
      Level.FINEST, e, "{0} {1} participant:{1} throws",
      iter.hasNext() ? "prepare" : "prepareAndCommit",
      this, getParticipantInfo(participant));
    }
    if (state != State.ABORTED) {
        abort(e);
    }
    throw e;
      }
      if (state == State.ABORTED) {
    throw new TransactionAbortedException(
        "Transaction has been aborted: " + abortCause, abortCause);
      }
  }
  state = State.COMMITTING;
  for (TransactionParticipant participant : participants) {
      if (logger.isLoggable(Level.FINEST)) {
    logger.log(Level.FINEST, "commit {0} participant:{1}",
         this, getParticipantInfo(participant));
      }
      if (detailMap != null) {
    detail = detailMap.get(participant.getTypeName());
    startTime = System.currentTimeMillis();
      }
      try {
    participant.commit(this);
    if (detail != null) {
        detail.setCommitted(System.currentTimeMillis() -
          startTime);
        collectorHandle.addParticipant(detail);
    }
      } catch (RuntimeException e) {
    if (logger.isLoggable(Level.WARNING)) {
        logger.logThrow(
      Level.WARNING, e, "commit {0} participant:{1} failed",
      this, getParticipantInfo(participant));
    }
      }
  }
  state = State.COMMITTED;
 
        startTime = System.currentTimeMillis();
        notifyListenersAfter(true);
        // create a fake participant with the before and after commit times
        if (detailMap != null) {
            ProfileParticipantDetailImpl txn = detailMap.get(TXN_PROFILE_NAME);
            txn.setCommitted(System.currentTimeMillis() - startTime);
            collectorHandle.addParticipant(txn);
        }
    }

    /** Returns a byte array that represents the specified long. */
    private byte[] longToBytes(long l) {
  return new byte[] {
      (byte) (l >>> 56), (byte) (l >>> 48), (byte) (l >>> 40),
      (byte) (l >>> 32), (byte) (l >>> 24), (byte) (l >>> 16),
      (byte) (l >>> 8), (byte) l };
    }

    /** Notify any listeners before preparing the transaction. */
    private void notifyListenersBefore() {
  if (listeners != null) {
      /*
       * Don't use foreach iteration here, so that we can handle the
       * possibility that a beforeCompletion call adds another listener.
       */
      for (int i = 0; i < listeners.size(); i++) {
    TransactionListener listener = listeners.get(i);
    try {
        listener.beforeCompletion();
    } catch (RuntimeException e) {
        if (logger.isLoggable(Level.FINEST)) {
      logger.logThrow(
          Level.FINEST, e,
          "beforeCompletion {0} listener:{1} failed",
          this, listener);
        }
        if (state != State.ABORTED) {
      abort(e);
        }
        throw e;
    }
    if (state == State.ABORTED) {
        throw new TransactionAbortedException(
      "Transaction has been aborted: " + abortCause,
      abortCause);
    }
      }
  }
    }

    /** Notify any listeners after completing the transaction. */
    private void notifyListenersAfter(boolean commited) {
  if (listeners != null) {
      for (TransactionListener listener : listeners) {
    try {
        listener.afterCompletion(commited);
    } catch (RuntimeException e) {
        if (logger.isLoggable(Level.WARNING)) {
      logger.logThrow(
          Level.WARNING, e,
          "afterCompletion {0} listener:{1} failed",
          this, listener);
        }
    }
      }
  }
    }

    /** Checks that current thread is the one that created this transaction. */
    private void checkThread(String methodName) {
  if (Thread.currentThread() != owner) {
      throw new IllegalStateException(
    "The " + methodName + " method must be called from the" +
    " thread that created the transaction");
  }
    }

    /**
     * Returns a string that describes the participant.  Returns null if the
     * participant is null.
     */
    private static String getParticipantInfo(
  TransactionParticipant participant)
    {
  return participant == null ? null
      : (participant.getTypeName() + " (" + participant + ")");
    }
}
TOP

Related Classes of com.sun.sgs.impl.service.transaction.TransactionImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.