/*
* 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.kernel.logging;
import com.sun.sgs.service.NonDurableTransactionParticipant;
import com.sun.sgs.service.Transaction;
import com.sun.sgs.service.TransactionProxy;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.ErrorManager;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
/**
* A {@code Handler} implementation that given a backing {@code
* Handler}, provides transactional semantics for the {@code
* #publish(LogRecord)} method. This class is used by the {@link
* TransactionAwareLogManager} to wrap existing handlers as specificed
* by the application.
*
* @see com.sun.sgs.impl.kernel.logging.TransactionAwareLogManager
*/
public class TransactionalHandler extends Handler
implements NonDurableTransactionParticipant {
/**
* A mapping from transaction to the list of records waiting to be
* published on transaction commit.
*/
private final ConcurrentMap<Transaction, Queue<LogRecord>>
bufferedRecords;
/**
* The proxy used to join transactions upon the first {@link
* #publish(LogRecord)} call for that transaction.
*/
private final TransactionProxy proxy;
/**
* The backing handler for this instance. All modification calls
* are passed through to this handler.
*/
private final Handler handler;
/**
* Constructs a new {@code TransactionalHandler} with the provided
* {@code proxy} for joining transactions a the backing handler
* for performing the actual logging.
*
* @param proxy the proxy used to join transactions
* @param backingHandler the handler used to perform the actual
* logging at commit time
*
* @throws NullPointerException if the {@code backingHandler} or
* {@code proxy} is {@code null}.
*/
TransactionalHandler(TransactionProxy proxy, Handler backingHandler) {
if (proxy == null || backingHandler == null) {
throw new NullPointerException();
}
this.proxy = proxy;
this.handler = backingHandler;
bufferedRecords =
new ConcurrentHashMap<Transaction, Queue<LogRecord>>();
}
/**
* Removes any buffered records for this transaction and performs
* no logging.
*
* @param txn the failed transaction that in the past performed
* some logging operation
*/
public void abort(Transaction txn) {
bufferedRecords.remove(txn);
}
/**
* {@inheritDoc}
*/
public void close() {
handler.close();
}
/**
* Removes any buffered records for this transaction and logs all
* buffered records using the backing {@code Handler}.
*
* @param txn the successful transaction that in the past
* performed some logging operation
*/
public void commit(Transaction txn) {
Queue<LogRecord> records = bufferedRecords.remove(txn);
for (LogRecord r : records) {
handler.publish(r);
}
}
/**
* {@inheritDoc}
*/
public void flush() {
handler.flush();
}
/**
* Returns the {@code Handler} used to publish all buffered {@code
* LogRecord}s when a transaction commmits.
*
* @return the backing handler used for actual record publication
*/
public Handler getBackingHandler() {
return handler;
}
/**
* {@inheritDoc}
*/
public String getEncoding() {
return handler.getEncoding();
}
/**
* {@inheritDoc}
*/
public ErrorManager getErrorManager() {
return handler.getErrorManager();
}
/**
* {@inheritDoc}
*/
public Filter getFilter() {
return handler.getFilter();
}
/**
* {@inheritDoc}
*/
public Formatter getFormatter() {
return handler.getFormatter();
}
/**
* {@inheritDoc}
*/
public Level getLevel() {
return handler.getLevel();
}
/**
* Returns the class name of the {@code Handler} that this
* instance wraps.
*
* @return the class name of the wrapped {@code Handler}
*/
// NOTE: This name is may not be unique for all the participants.
// By using the backing handler's name, we increase the chance of
// it being unique, but it is feasible that some application could
// have two Handlers of the same type for the same Logger. As of
// 07/06/08, no code relies on these names being unique, so this
// is not a problem. However if in the future they are required
// then a new naming scheme should be devised. -dj202934
public String getTypeName() {
return handler.getClass().getName();
}
/**
* {@inheritDoc}
*/
public boolean isLoggable(LogRecord record) {
return handler.isLoggable(record);
}
/**
* {@inheritDoc}
*
* This participant always returns {@code false}.
*
* @return {@code false}
*/
public boolean prepare(Transaction txn) {
return false;
}
/**
* Removes any buffered records for this transaction and logs all
* buffered records using the backing {@code Handler}.
*
* @param txn the successful transaction that in the past
* performed some logging operation
*/
public void prepareAndCommit(Transaction txn) {
commit(txn);
}
/**
* Joins the current transaction and buffers the provided record
* for later publication until transaction commit time.
*
* @param record the record to be buffer and later publication
* upon transaction success.
*/
public void publish(LogRecord record) {
// If we're not in a transaction at all, just publish the record.
// No need to buffer it.
if (!proxy.inTransaction()) {
handler.publish(record);
return;
}
Transaction txn = proxy.getCurrentTransaction();
if (txn == null) {
// in the event that a TransactionalHandler is used
// outside the scope of a transaction (which could happen
// if it is used by certain classes like DataStoreImpl),
// then we just pass the log record on through without
// buffering
handler.publish(record);
} else {
Queue<LogRecord> records = bufferedRecords.get(txn);
if (records == null) {
txn.join(this);
records = new ArrayDeque<LogRecord>();
// this code path is guaranteed to be unique by way of
// the transaction's uniqueness, so we don't need to
// worry about a race condition with putting the queue
// into the map
bufferedRecords.put(txn, records);
}
records.add(record);
}
}
/**
* {@inheritDoc}
*/
public void reportError(String msg, Exception ex, int code) {
// NOTE: we can't call report error on the handler directly
// because it has protected access, so we emulate the code in
// Hander.java directly here, including the catch block
try {
handler.getErrorManager().error(msg, ex, code);
} catch (Exception ex2) {
System.err.println("TransactionalHandler.reportError() caught:");
ex2.printStackTrace();
}
}
/**
* {@inheritDoc}
*/
public void setEncoding(String encoding)
throws java.io.UnsupportedEncodingException {
handler.setEncoding(encoding);
}
/**
* {@inheritDoc}
*/
public void setErrorManager(ErrorManager em) {
handler.setErrorManager(em);
}
/**
* {@inheritDoc}
*/
public void setFilter(Filter newFilter) {
handler.setFilter(newFilter);
}
/**
* {@inheritDoc}
*/
public void setFormatter(Formatter newFormatter) {
handler.setFormatter(newFormatter);
}
/**
* {@inheritDoc}
*/
public void setLevel(Level newLevel) {
handler.setLevel(newLevel);
}
}