/* * 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.impl.kernel.StandardProperties; import com.sun.sgs.service.TransactionProxy; import java.util.ArrayDeque; import java.util.Properties; import java.util.Queue; import java.util.logging.Handler; import java.util.logging.Logger; import java.util.logging.LogManager; /** * A {@code LogManager} class that adds transactional semantics to {@code * Logger} instances in the application's namespace. This class will either * infer the application's name space from the package used by the main {@code * AppListener}, or it can be specified manually by the following property: * * <p><dl style="margin-left: 1em"> * * <dt> <i>Property:</i> <b> * {@code com.sun.sgs.logging.app.namespace} * </b><br> * <i>Default:</i> the package named used in the {@code * com.sun.sgs.app.listener} property. * * <dd style="padding-top: .5em">This property specifies the root of the * application namespace. All loggers under this root will be have * transactional-semantics. If this property is left unset the system will use * the namespace specified in the {@code com.sun.sgs.app.listener} property * value. <p> * * </dl> * * In order to load this class as the default {@code LogManager}, applications * must set the {@code java.util.logging.manager} system property to {@code * com.sun.sgs.impl.kernel.logging.TransactionAwareLogManager} prior to JVM * start up. * * <p> * * All {@code Logger} instances outside of the application's name space will * retain their default, non-transactional semantics. * * @see TransactionalHandler */ public final class TransactionAwareLogManager extends LogManager { /** * The namespace prefix used by all the properties for this class. */ private static final String PROPERTIES_PREFIX = "com.sun.sgs.logging"; /** * The property specified in the system properties for denoting the root * namespace of the application. */ private static final String APP_NAMESPACE_PROPERTY = PROPERTIES_PREFIX + ".app.namespace"; /** * The {@code TransactionProxy} used by the {@link TransactionalHandler} * handlers. */ private TransactionProxy txnProxy; /** * The listing of {@code TransactionalLogger} instances that have yet to be * configured with a {@link TransactionProxy}, but were created prior to * this {@code TransactionAwareLogManager} being configured. Note that not * all of these instances will end up being transactional. However, due to * the order in which the {@code LogManager} is created in the JVM, some * {@code Logger} instances will be created prior to this component being * configured. For this reason, we keep this list to later reconfigure * them when the {@code TransactionProxy} is available. */ private final Queue<TransactionalLogger> unconfiguredLoggers; /** * The namespace of the application, which is used to determine which * {@code Logger} instances should be transactional. Any namespace under * this will have transactional semantics. */ private String appNamespace; /** * Default constructor used by the JVM at startup. */ public TransactionAwareLogManager() { super(); unconfiguredLoggers = new ArrayDeque<TransactionalLogger>(); appNamespace = null; } /** * Configures this {@code LogManager} with the provided properties and uses * the {@code TransactionProxy} to add transactional semantics to any of * the specified application {@code Logger} instances. * * <p> * * Note that prior to this call, the application's namespace is not known * so any {@code Logger} instances will be of type {@code * TransactionalLogger}, but will be configured as non-transactional. * After this call, any of these instances that were in the application's * namespace will be transactional. * * @param properties the properties for configuring this component * @param txnProxy the transaction proxy */ public synchronized void configure(Properties properties, TransactionProxy txnProxy) { this.txnProxy = txnProxy; String appListener = properties.getProperty(StandardProperties.APP_LISTENER); int lastDotBeforeClass = appListener.lastIndexOf("."); // Check to ensure that the main class has a package if (lastDotBeforeClass < 0) { // in the event that the main class has no package, set the // position of the now phantom dot to the length of the app // Listener's name, which ensures that any loggers created based on // the class's name will be transactional lastDotBeforeClass = appListener.length(); } String defaultAppNamespace = appListener.substring(0, lastDotBeforeClass); // if the applicate does not specify a specific namespace, we use the // namespace provided by the application listener. appNamespace = properties.getProperty(APP_NAMESPACE_PROPERTY, defaultAppNamespace); // Now that the namespace is known, check any non-transaction // TransactionalLoggers that were created prior to this LogManager // being configured. This could have happened if the Loggers were // created statically when the application's classes were loaded. If // any of these Loggers are in the application's namespace, have them // configured to be transactional. for (TransactionalLogger lgr : unconfiguredLoggers) { // This list will likely include any of the servers loggers that // were created statically, so we test to see whether the Logger // belongs to the app's namespace before configuring its handlers if (lgr.getName().startsWith(appNamespace)) { lgr.configure(txnProxy); lgr.config("This logger now has transactional semantics"); } } unconfiguredLoggers.clear(); } /** * Returns the existing {@code Logger} for the provided name or creates a * new instance if none is found. Note that <i>unlike the default * <tt>LogManager</tt> implementation</i>, this call will create a new * {@code Logger} instance. * * @param name the name of the logger * * @return the existing {@code Logger} for the provided name, or a new * instance that was created by this call. */ // NOTE: we rely on the fact that Logger.getLogger() will in turn call // LogManager.demandLogger(). LogManager.demandLogger() first checks to // see if a Logger has already been created by calling // LogManager.getLogger() and checking the results. In order to subvert // the default LogManager behavior, we override that method to create a // new, possibly transaction-aware Logger instance. public synchronized Logger getLogger(String name) { // The root logger will have a 0-length name, in which case we should // return null and let the default LogManager code create the // RootLogger instance correctly. if (name == null || name.length() == 0) { return null; } Logger result = super.getLogger(name); if (result == null) { boolean configured = (txnProxy != null); // if no current Logger associated with that name, create the // appropriate type of Logger. If the transactionProxy is null, we // haven't been configured yet, so create a TransactionalLogger but // mark it non-transactional. // // If we have been configured, see if the requested Logger's name // is in the application's namespace. result = (!configured || name.startsWith(appNamespace)) ? new TransactionalLogger(name, null, txnProxy) : new SimpleLogger(name, null); // there is a chance that an application may demand a Logger prior // to the TxnAwareLogManager being configured with the // TransactionProxy and the application's namespace. Therefore, we // add any such Loggers to a list of unconfigured ones and revisit // them upon the manager's configuration. if (!configured && (result instanceof TransactionalLogger)) { unconfiguredLoggers.add((TransactionalLogger) result); } else { result.config("This logger now has transactional semantics"); } // this call is necessary to install all the handlers associated // with the Logger. addLogger(result); } return result; } /** * A utility class that exposes the {@code protected} constructor of the * {@code Logger} class. We need this class so that we can create new * {@code Logger} instances in the {@link * TransactionAwareLogManager#getLogger(String)} method. */ private static final class SimpleLogger extends Logger { public SimpleLogger(String name, String resourceBundleName) { super(name, resourceBundleName); } } /** * A {@code Logger} class that provides optional transactional semantics. * Instances of this class will provide transaction semantics if they are * constructed with a valid, non-{@code null} instance of a {@link * TransactionProxy}, or if they are configured after construction with a * valid, non-{@code null} instance of a {@code TransactionProxy}. * * <p> * * This class does not interact with the transactions directly but instead * relies on {@link TransactionalHandler} instances to do so. Adding a * {@link Handler} at run-time will function as expected and will not * change the output semantics. */ private static final class TransactionalLogger extends Logger { /** * The proxy that allows any {@link TransactionalHandler} associated * with this {@code Logger} to join the current transaction. Note that * if this {@code Logger} was created before the {@link * TransactionAwareLogManager#configure(Properties,TransactionProxy}} * has been called, this will be {@code null}. */ private TransactionProxy txnProxy; /** * Constructs a {@code TransactionalLogger} that will have * transactional semantics if {@code txnProxy} is valid and non-{@code * null}. * * @param name the name of this logger * @param resourceBundleName the name of a {@link ResourceBundle} to be * used for localizing messages for this * logger. May be {@code null} if none of * the messages require localization. * @param txnProxy the {@code TransactionProxy} used to join the * current transaction when a report is logged. */ public TransactionalLogger(String name, String resourceBundleName, TransactionProxy txnProxy) { super(name, resourceBundleName); this.txnProxy = txnProxy; } /** * {@inheritDoc} * * If the provided {@code Handler} is an instance of {@link * TransactionalHander} it will be added immediately. Otherwise, the * provided {@code Handler} will be wrapped by a {@code * TransactionalHandler} and that handler will be added instead. */ public void addHandler(Handler handler) { if (!(handler instanceof TransactionalHandler)) { // in the event that this Logger was created prior to the // LogManager being configured, the TransactionProxy will be // null. In this case, we add the handler as normal and then // wait for the configure() call to wrap it in a // TransactionalHandler if (txnProxy == null) { super.addHandler(handler); } else { // wrap the original handler in one that has transactional // semantics super.addHandler(new TransactionalHandler(txnProxy, handler)); } } else { // if we were passed an existing TransactionalHandler, use it // as is. This case could occur if the handler had already // been created for another Logger. super.addHandler(handler); } } /** * Searches the logger parent hierarchy of the provided {@code Logger} * until a parent is found who has {@code Handler} instances, and then * adds those handlers to the provided logger. Note that this method * also modifies the logger to not use its parent handlers in order to * prevent duplicate log entries. */ private void attachParentHandlers() { Logger parent = this; do { parent = parent.getParent(); } while (parent != null && parent.getHandlers().length == 0); if (parent == null) { // NOTE: this case should never happen since we would // eventually hit the LogManager$RootLogger which by default // has a Handler. However a developer could feasibly adjust // some settings so that the RootLogger had no handler and none // of the child Loggers did as well. return; } // Add all of the parent handlers to this Logger so that we can // later wrap it. Handler[] arr = parent.getHandlers(); for (Handler h : arr) { addHandler(h); } // Now that we are using the same handler as the parent, avoid // propagating the call to the parent Logger as this will result in // duplicate log entries to the handler. setUseParentHandlers(false); } /** * Configures this {@code Logger} with the provided {@code * TransactionProxy} so that all messages logged will have transaction * semantics. This method is used for reconfiguring loggers that were * created prior to the {@link TransactionAwareLogManager} being * configured. * * @param txnProxy the {@code TransactionProxy} used to join the * current transaction when a report is logged. * * @see TransactionAwareLogManager#configure(Properties, * TransactionProxy); */ // NOTE: This method does not have a race condition with addHandler due // the separate call chain having already acquired lock a on the // TxnAwareLogManager. Therefore neither methods of this class require // locks void configure(TransactionProxy txnProxy) { if (txnProxy == null) { return; } this.txnProxy = txnProxy; // In the event that no handlers have been specified for this // logger, we walk the Logger hierarchy until we find a parent that // does have a handler set. The attachParentHandlers will bind // those handlers to the current Logger, which results in them // being wrapped by a TransactionalHandler if (getHandlers().length == 0) { attachParentHandlers(); } else { // If handlers have been assigned to this Logger, wrap them in // TransactionalHandlers for (Handler h : getHandlers()) { // check that we aren't already dealing with a handler that // has already been made transactional. if (h instanceof TransactionalHandler) { continue; } super.addHandler(new TransactionalHandler(txnProxy, h)); removeHandler(h); // ensure that any log calls to this logger don't work // their way up the logger hierarchy, which could result in // non-transactional logging setUseParentHandlers(false); } } // lastly, note in the logging stream that this Logger is now // transactional. config("This logger now has transactional semantics"); } /** * Sets the parent of this {@code Logger} and calls {@link * #attachParentHandlers()} if this logger has been configured to be * transactional and does not yet have any handlers. * * @param {@inheritDoc} */ public void setParent(Logger parent) { super.setParent(parent); if (txnProxy != null && getHandlers().length == 0) { attachParentHandlers(); } } /** * Returns the type and name of this {@code Logger} if this instance has * transaction semantics, or otherwise returns the default {@link * Logger#toString()}. * * @return a description of this logger */ public String toString() { return (txnProxy == null) ? "Non-transactional Logger:" + getName() : "TransactionalLogger:" + getName(); } } }