/* * Copyright 2007-2010 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.util; import com.sun.sgs.app.ExceptionRetryStatus; import com.sun.sgs.app.TransactionNotActiveException; import com.sun.sgs.impl.sharedutil.LoggerWrapper; import com.sun.sgs.service.NonDurableTransactionParticipant; import com.sun.sgs.service.Transaction; import com.sun.sgs.service.TransactionParticipant; import com.sun.sgs.service.TransactionProxy; import java.util.logging.Level; import java.util.logging.Logger; /** * Utility class for participating in a transaction, parameterized by * a type of {@link TransactionContext} to be used with a participant. * * @param <T> a type of transaction context */ public abstract class TransactionContextFactory<T extends TransactionContext> { /** The logger for this class. */ private static final LoggerWrapper logger = new LoggerWrapper( Logger.getLogger( TransactionContextFactory.class.getName())); /** The transaction context map. */ private final TransactionContextMap<T> contextMap; /** The type name of the participants, used for profiling. */ private final String participantName; /** Lock for access to the participant field. */ private final Object lock = new Object(); /** The transaction participant. */ private TransactionParticipant participant; /** * Constructs an instance of this class with the given {@code * TransactionContextMap}. * * @param contextMap the transaction context map * @param participantName the type name of the transaction participants */ protected TransactionContextFactory(TransactionContextMap<T> contextMap, String participantName) { if (contextMap == null) { throw new NullPointerException("null contextMap"); } this.contextMap = contextMap; this.participantName = participantName; } /** * Constructs an instance of this class, using the specified {@code * TransactionProxy} to create the {@link TransactionContextMap}. Use this * constructor if access to the {@code TransactionContextMap} is not * needed. * * @param txnProxy the transaction proxy * @param participantName the type name of the transaction participants */ protected TransactionContextFactory(TransactionProxy txnProxy, String participantName) { contextMap = new TransactionContextMap<T>(txnProxy); this.participantName = participantName; } /** * Makes sure the participant is joined to the current transaction and * returns the associated context. <p> * * The default implementation calls {@link * TransactionContextMap#joinTransaction joinTransaction} on the {@link * TransactionContextMap} supplied to, or created by, the constructor, * passing this instance as the argument. * * @return the context for the current transaction * @throws TransactionNotActiveException if no transaction is active * @throws IllegalStateException if there is a problem with the * state of the transaction. */ public T joinTransaction() { return contextMap.joinTransaction(this); } /** * Returns the transaction participant for use with this factory. <p> * * The default implementation creates a new transaction participant by * calling {@link #createParticipant createParticipant} the first time the * participant is requested. * * @return the transaction participant */ public TransactionParticipant getParticipant() { synchronized (lock) { if (participant == null) { participant = createParticipant(); } return participant; } } /** * Returns a new {@code TransactionContext} to hold state for the * specified transaction, {@code txn}. * * @param txn a transaction * * @return a new transaction context */ protected abstract T createContext(Transaction txn); /** * Returns a new transaction participant that operates on the * currently active transaction context. <p> * * The default implementation of this method returns a transaction * participant that implements {@link * NonDurableTransactionParticipant}. * * @return a transaction participant */ protected TransactionParticipant createParticipant() { return new NonDurableParticipant(); } /* -- Implement TransactionParticipant -- */ /** Provides a durable transaction participant. */ protected class Participant implements TransactionParticipant { /** Creates an instance of this class. */ public Participant() { } /** {@inheritDoc} */ public boolean prepare(Transaction txn) throws Exception { try { T context = contextMap.checkTransaction(txn); if (context.isPrepared()) { throw new TransactionNotActiveException("Already prepared"); } boolean readOnly = context.prepare(); if (readOnly) { contextMap.clearContext(); } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINER, "prepare txn:{0} returns {1}", txn, readOnly); } return readOnly; } catch (Exception e) { if (logger.isLoggable(Level.FINER)) { logger.logThrow(Level.FINER, e, "prepare txn:{0} throws", txn); } throw e; } } /** {@inheritDoc} */ public void commit(Transaction txn) { try { T context = contextMap.checkTransaction(txn); try { if (!context.isPrepared()) { RuntimeException e = new IllegalStateException( "transaction not prepared"); if (logger.isLoggable(Level.WARNING)) { logger.logThrow( Level.WARNING, e, "commit: not yet prepared txn:{0}", txn); } throw e; } } finally { contextMap.clearContext(); } context.commit(); logger.log(Level.FINER, "commit txn:{0} returns", txn); } catch (RuntimeException e) { logger.logThrow( Level.WARNING, e, "commit txn:{0} throws", txn); throw e; } } /** {@inheritDoc} */ public void prepareAndCommit(Transaction txn) throws Exception { try { T context = contextMap.checkTransaction(txn); if (context.isPrepared()) { throw new TransactionNotActiveException("Already prepared"); } context.prepareAndCommit(); contextMap.clearContext(); logger.log(Level.FINER, "prepareAndCommit txn:{0} returns", txn); } catch (Exception e) { if (logger.isLoggable(Level.FINER)) { logger.logThrow(Level.FINER, e, "prepareAndCommit txn:{0} throws", txn); } throw e; } } /** {@inheritDoc} */ public void abort(Transaction txn) { try { T context = contextMap.checkTransaction(txn); contextMap.clearContext(); context.abort(isRetryable(txn.getAbortCause())); logger.log(Level.FINER, "abort txn:{0} returns", txn); } catch (RuntimeException e) { logger.logThrow(Level.WARNING, e, "abort txn:{0} throws", txn); throw e; } } /** {@inheritDoc} */ public String getTypeName() { return participantName; } } /** Provides a non-durable transaction participant. */ protected class NonDurableParticipant extends Participant implements NonDurableTransactionParticipant { /** Creates an instance of this class. */ public NonDurableParticipant() { } } /* -- Other methods -- */ /** * Returns {@code true} if the given {@code Throwable} is a * "retryable" exception, meaning that it implements {@code * ExceptionRetryStatus}, and invoking its {@link * ExceptionRetryStatus#shouldRetry shouldRetry} method returns * {@code true}. * * @param t a throwable * * @return {@code true} if the given {@code Throwable} is * retryable, and {@code false} otherwise */ private static boolean isRetryable(Throwable t) { return t instanceof ExceptionRetryStatus && ((ExceptionRetryStatus) t).shouldRetry(); } }