// // Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s). // All rights reserved. // package openadk.library; import java.io.IOException; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.ResourceBundle; import openadk.library.impl.HttpTransportPlugin; import openadk.library.impl.HttpsTransportPlugin; import openadk.library.impl.ISIFPrimitives; import openadk.library.impl.TransportPlugin; import openadk.library.log.DefaultServerLogModule; import openadk.library.log.ServerLog; import openadk.library.tools.xpath.SIFXPathContext; import openadk.util.GUIDGenerator; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; /** * A global class representing the SIFWorks® ADK class framework. * <p> * * Prior to calling any ADK methods or referencing any static constants, agents * must first initialize the ADK by calling the static * <code>ADK.initialize</code> method. The default <code>initialize</code> * method loads all SIF Data Object (SDO) modules and selects the latest version * of SIF as the default version used to render SIF Messages. If you use this * method, ensure the sdoall.jar file is on the Java classpath. Other forms of * the <code>initialize</code> method allow you to choose the specific SDO * modules that are loaded as well as the version of SIF that will be the * default for this agent session. * <p> * * @author Eric Petersen * @version 1.0 */ public class ADK { /** * The default pattern used for writing to the logging framework */ public static String DEFAULT_LOG4J_PATTERN = "%d %-5p [%c] %m%n"; /** * The Identifier that is used to identify the ADK itself for logging * operations ("ADK") */ public static final String LOG_IDENTIFIER = "ADK"; /** * ADK debugging flag indicates whether Transport setup is traced */ public static final int DBG_TRANSPORT = 0x00000004; /** * ADK debugging flag indicates whether Message Dispatching actions are * traced */ public static final int DBG_MESSAGING = 0x00000008; /** * ADK debugging flag indicates whether SIF_Event Message Dispatching * actions are traced */ public static final int DBG_MESSAGING_EVENT_DISPATCHING = 0x00000010; /** * ADK debugging flag indicates whether SIF_Response processing is traced */ public static final int DBG_MESSAGING_RESPONSE_PROCESSING = 0x00000020; /** * ADK debugging flag indicates whether SIF_SystemControl/SIF_GetMessage * messages are traced */ public static final int DBG_MESSAGING_PULL = 0x00000040; /** * ADK debugging flag indicates whether message headers are traced */ public static final int DBG_MESSAGING_DETAILED = 0x00000080; /** * ADK debugging flag indicates whether message content is traced */ public static final int DBG_MESSAGE_CONTENT = 0x00000100; /** * ADK debugging flag indicates whether provisioning activity is traced. * Note other debugging flags may cause provisioning messages to be logged * even when this flag is not set. */ public static final int DBG_PROVISIONING = 0x00000200; /** * ADK debugging flag indicates whether ADK policy is traced. */ public static final int DBG_POLICY = 0x00000400; /** * ADK debugging flag indicates whether Agent Runtime activity is traced. */ public static final int DBG_RUNTIME = 0x00001000; /** * ADK debugging flag indicates whether agent startup/shutdown and * initializaion activity is logged */ public static final int DBG_LIFECYCLE = 0x10000000; /** * ADK debugging flag indicates whether exceptions are logged */ public static final int DBG_EXCEPTIONS = 0x20000000; /** * ADK debugging flag indicates whether agent and zone properties are logged */ public static final int DBG_PROPERTIES = 0x40000000; /** * ADK debugging flag to enable all debugging output */ public static final int DBG_ALL = 0xFFFFFFFF; /** * ADK debugging flag to disable debugging output */ public static final int DBG_NONE = 0x0; /** * Minimal debugging flags (exceptions, provisioning) */ public static int DBG_MINIMAL = DBG_EXCEPTIONS | DBG_PROVISIONING; /** * Moderate debugging flags (exceptions, provisioning, messaging, lifecycle) */ public static int DBG_MODERATE = DBG_MINIMAL | DBG_MESSAGING | DBG_POLICY | DBG_LIFECYCLE; /** * Moderate debugging flags, with DBG_MESSAGING_PULL */ public static int DBG_MODERATE_WITH_PULL = DBG_MODERATE | DBG_MESSAGING_PULL; /** * Detailed debugging flags (exceptions, provisioning, messaging, detailed * messaging, transport) */ public static int DBG_DETAILED = DBG_MODERATE_WITH_PULL | DBG_TRANSPORT | DBG_MESSAGING_DETAILED; /** * Very detailed debugging flags (exceptions, provisioning, messaging, * detailed messaging, transport, event dispatching, properties) */ public static int DBG_VERY_DETAILED = DBG_DETAILED | DBG_MESSAGING_EVENT_DISPATCHING | DBG_MESSAGING_RESPONSE_PROCESSING | DBG_PROPERTIES; /** * The ADK debugging level determines which types of messages will be * submitted to the Log4j environment by the class framework. To eliminate * all ADK-generated log messages set this value to 0. The default is * DBG_VERY_DETAILED, which includes all debug flags except * DBG_MESSAGE_CONTENT. */ public static int debug = DBG_VERY_DETAILED; // This array must be in order from earliest to latest version private static final SIFVersion[] sSupportedVersionsArray = { SIFVersion.SIF11, SIFVersion.SIF15r1, SIFVersion.SIF20, SIFVersion.SIF20r1, SIFVersion.SIF21, SIFVersion.SIF22, SIFVersion.SIF23, SIFVersion.SIF24, SIFVersion.SIF25, SIFVersion.SIF26 }; /** * The root log Category. Subcategories exist for the Agent and each zone, * where the hierarchy is "ADK.Agent$<i>zoneId</i>". The ADK uses the root * Category when writing log events prior to the initialization of the Agent * class. */ protected static Logger log = Logger.getLogger(LOG_IDENTIFIER); /** * The root ServerLog instance. Subcategories exist for the Agent and each * zone, where the hierarchy is "ADK.Agent$<i>zoneId</i>". The ADK uses the * root ServerLog instance only to establish the global chain of loggers; no * actual logging is performed outside the context of a zone. */ protected static ServerLog serverLog = ServerLog.getInstance("ADK", null); /** Static single instance of the ADK object for this virtual machine */ private static ADK sSingleton; /** Global primitives object */ private ISIFPrimitives fImpl; /** The version of SIF used by the agent */ private SIFVersion fVersion; /** * The default SIFFormatter used by the agent if no formatter is specified. * By default, the formatter is selected based on the SIFVersion that the * ADK is currently running as default. */ private SIFFormatter fDefaultFormatter; /** The SIF version-dependent DTD object used by the SIFDataObject classes */ private static SIFDTD DTD = new SIFDTD(); /** The ADK product version */ private String fLibVersion; /** The ADK build revision */ private String fLibRevision; /** Installed Transport Protocol Plug-ins */ private HashMap<String, TransportPlugin> fTransports = new HashMap<String, TransportPlugin>(); // Protected constructor private ADK() { sSingleton = this; } /** * Initialize the ADK to use the latest version of SIF and all SIF Data * Object (SDO) libraries. * <p> * * Calling this method when the ADK has already been initialized has no * effect. * * @throws ADKException * If the ADK cannot be initialized */ public static void initialize() throws ADKException { initialize(SIFVersion.LATEST, SIFDTD.SDO_ALL); } /** * Initialize the ADK to use the specified version of SIF. * <p> * * Calling this method when the ADK has already been initialized has no * effect. * <p> * * This method must be called at agent startup to initialize various * resources of the ADK, establish global settings for the class framework, * and set the default version of SIF to which all messages originating from * the agent will conform. * <p> * * Beginning with ADK 1.5.0, this method also configures the global ADK * <code>ServerLog</code> instance with a logging module that will be * inherited by the ServerLog of all zones. It installs a single logging * module implementation: <code>DefaultServerLogModule</code>. The behavior * of this module is to report SIF_LogEntry objects to the zone integration * server via an Add SIF_Event message whenever * <code>ServerLog.reportLogEntry</code> is called on a zone and the agent * is running in SIF 1.5 or later. DefaultServerLogModule also echos server * log messages to the zone's local Log4j Category so that agents do not * need to duplicate logging to both the server and local agent log. If you * want to install a custom <i>ServerLogModule</i> implementation -- or need * to adjust the settings of the default module installed when the ADK is * initialized -- call the <code>ADK.getServerLog</code> method to obtain a * reference to the root of the <code>ServerLog</code> chain, then call its * methods to add and remove modules. Refer to the ServerLog class for more * information on server logging. * <p> * * @param version * The version of SIF that will be used by the agent this * session. Supported versions are enumerated by constants of the * <code>openadk.SIFVersion</code> class. Once * initialized, the version cannot be changed. * @param sdoLibraries * One or more of the constants defined by the SDOLibrary class, * identifying the SIF Data Object libraries to be loaded into * memory (e.g. SDOLibrary.STUDENT | SDOLibrary.HR ) * @throws ADKException * If the ADK cannot be initialized * * @exception ADKNotSupportedException * is thrown if the specified SIF version is not supported by * the ADK, or if the <i>sdoLibraries</i> parameter is * invalid */ public static void initialize(SIFVersion version, int sdoLibraries) throws ADKException { if (sSingleton != null) return; if (version == null) throw new IllegalArgumentException("SIFVersion cannot be null"); sSingleton = new ADK(); try { initLogging(); } catch (Exception e) { System.out.println(e.getMessage()); } // The default formatter for String APIs in the ADK is the SIF 1.x // formatter // for backwards compatibility sSingleton.fDefaultFormatter = SIFDTD.SIF_1X_FORMATTER; // Initialize the SIFXPathContext Class // TODO: Would like a more elegant way to initialize the JXPathContext // Subsystem, but this seems to be the only way that works all the time // What needs to be set is the default JXpathContextFactory, which the // ADK // overrides to create instances of SIFXPathContextFactory. SIFXPathContext.initialize(); // // Set up the ServerLog // ServerLog sl = getServerLog(); if (sl != null) { sl.addLogger(new DefaultServerLogModule()); } // // For some reason we sometimes get this error message in the SASIxp // agent: // "MessageDispatcher could not convert SIF_Ack response to an object: java.lang.ClassNotFoundException: org/apache/xerces/parsers/SAXParser" // Not sure why, but try forcing the SIFParser static blocks here to // clear // up this problem. // SIFParser.newInstance(); // HTTP and HTTPS transports always available TransportPlugin tp = new HttpTransportPlugin(); sSingleton.fTransports.put(tp.getProtocol(), tp); tp = new HttpsTransportPlugin(); sSingleton.fTransports.put(tp.getProtocol(), tp); ResourceBundle rb = ResourceBundle.getBundle("openadk.library.Library"); sSingleton.fLibVersion = rb.getString("Version"); sSingleton.fLibRevision = rb.getString("Revision"); if (debug != DBG_NONE) log.debug("Using ADK " + ADK.getADKVersion()); setVersion(version); DTD.loadLibraries(sdoLibraries); } private static boolean _initLogging = false; private static void initLogging() throws ADKException { if (!_initLogging) { _initLogging = true; String logFile = System.getProperty("adk.log.file"); if (logFile != null) { try { setLogFile(logFile); } catch (IOException ioe) { throw new ADKException("Could not initialize log file (\"+logFile+\"): " + ioe, null); } } else { // No logging to file is configured. Add a console appender, but // only if there are no // appenders currently configured in the logging framework and // no appenders configured in // The ADK's logger. The appender is added to the ADK's logger. // If there are other classes // in the application logging to log4net outside of the ADK's // log hierarchy, the appenders // for those logs will need to be added by the application // NOTE: If someone wishes to use their own custom appenders // completely for the ADK, they can // initialize log4net before calling Adk.Initialize(). This // method must make sure that it // does no initialization of log4net if it has already been // done. Enumeration rootAppenders = LogManager.getRootLogger().getAllAppenders(); if (!rootAppenders.hasMoreElements()) { if (!log.getAllAppenders().hasMoreElements()) { log.removeAllAppenders(); ConsoleAppender consoleApp = new ConsoleAppender(); consoleApp.setLayout(new PatternLayout(DEFAULT_LOG4J_PATTERN)); consoleApp.activateOptions(); log.addAppender(consoleApp); log.getLoggerRepository().setThreshold(Level.DEBUG); } } } } } /** * Returns the root Log4j Category for the ADK. * <p> * * Agents that wish to customize ADK logging may call this method to obtain * the root Log4j Category. * * @return The Logger used by the ADK */ public static Logger getLog() { return log; } /** * Gets the root ServerLog instance for the ADK. * <p> * * Agents that wish to customize ADK server-side logging may call this * method to obtain the class framework's root ServerLog instance. Call any * of the following methods to set up the chain of loggers that will be * inherited by the Agent and all Zones: * <p> * * <ul> * <li><code>addLogger</code></li> * <li><code>removeLogger</code></li> * <li><code>clearLoggers</code></li> * <li><code>getLoggers</code></li> * </ul> * * Unlike client-side logging, server logging requires a connection to a * Zone Integration Server. Because the current SIF 1.x infrastructure does * not allow connections to servers independent of a zone, the logging * methods of ServerLog are useful only when called within the context of a * zone. Therefore, calling any of the logging methods on the ServerLog * instance returned by this method will result in an IllegalStateException. * This method is provided only to set up the ServerLog logger chain at the * global ADK level. * <p> * * @return The ADK's root ServerLog instance * * @since ADK 1.5 */ public static ServerLog getServerLog() { return serverLog; } /** * Redirects all log output to the specified file. * <p> * * @param file * The log file * @throws IOException * If the File cannot be created or written to */ public static void setLogFile(String file) throws IOException { log.removeAllAppenders(); log.addAppender(new org.apache.log4j.FileAppender(new PatternLayout(DEFAULT_LOG4J_PATTERN), file)); } /** * Has the ADK been initialized? * * @return True if {@link #initialize()} has already been called */ public static boolean isInitialized() { return sSingleton != null; } /** * Sets the version of SIF the ADK will use. The version setting is global * and therefore applies to all SIF messaging activity for all Agents * instantiated in the current Java virtual machine. * <p> * * Calling this method after agent initialization is not recommended. * <p> * * @param version * The version of SIF that will be used by the agent. Supported * versions are enumerated by constants of the <code> * openadk.SIFVersion</code> class * * @throws ADKNotSupportedException * is thrown if the SIF version is not supported */ public static void setVersion(SIFVersion version) throws ADKNotSupportedException { if (!isSIFVersionSupported(version)) throw new ADKNotSupportedException("SIF " + sSingleton.fVersion.toString() + " is not supported by the ADK", null); sSingleton.fVersion = (version == null ? SIFVersion.LATEST : version); if (debug != DBG_NONE) { log.debug("Using SIF " + sSingleton.fVersion.toString()); } } /** * Gets the versions of SIF supported by the ADK * * @return An array of SIFVersion objects */ public static SIFVersion[] getSupportedSIFVersions() { return sSupportedVersionsArray; } /** * Returns the highest SIF_Version supported by the current instance of the * ADK from the list of candidate versions. This method is helpful to agents * during SIF_Request processing. A SIF_Request can contain multiple * SIF_Versions in it. * * @param candidates * If null, or zero-length, the ADK.getSIFVersion() value will be * returned * @return The latest version supported by the ADK from the list of * candidate versions */ public static SIFVersion getLatestSupportedVersion(SIFVersion[] candidates) { checkInit(); if (candidates == null || candidates.length == 0) { return ADK.getSIFVersion(); } SIFVersion returnVal = null; for (SIFVersion candidate : candidates) { if (returnVal == null || candidate.compareTo(returnVal) > 0) { returnVal = candidate; } } if (returnVal == null) { returnVal = ADK.getSIFVersion(); } return returnVal; } /** * Gets the transport protocols available to agents. * * @return An array of transport protocol strings (e.g. "http") */ public static String[] getTransportProtocols() { checkInit(); int i = 0; String[] arr = new String[sSingleton.fTransports.size()]; for (Iterator it = sSingleton.fTransports.values().iterator(); it.hasNext();) { TransportPlugin pi = (TransportPlugin) it.next(); if (!pi.isInternal()) arr[i++] = pi.getProtocol(); } return arr; } /** * Gets an installed transport protocol * * @param protocol * The transport protocol name (e.g. "http", "https", etc.) * @return The plugin class that represents this protocol, for internal use * by the class framework */ public static TransportPlugin getTransportProtocol(String protocol) { if (protocol == null) { throw new IllegalArgumentException("Protocol cannot be null"); } checkInit(); return sSingleton.fTransports.get(protocol.toLowerCase()); } /** * Installs a transport protocol * * @param tp * The Transport plugin to install */ public static void install(TransportPlugin tp) { if (tp == null) { throw new IllegalArgumentException("TransportPlugin cannot be null"); } checkInit(); sSingleton.fTransports.put(tp.getProtocol().toLowerCase(), tp); } /** * Utility method to generate a GUID for SIF Data Objects and messages. * * @return A GUID * @see openadk.util.GUIDGenerator */ public static String makeGUID() { return GUIDGenerator.makeGUID(); } /** * Gets the ADK build version * * @return The ADK build version string (e.g. "1.0.4") */ public static String getADKVersion() { if (sSingleton == null) return "Unknown"; return sSingleton.fLibVersion; } /** * Gets the version of SIF used by the agent * <p> * * @return The SIFVersion that the ADK is initialized to use */ public static SIFVersion getSIFVersion() { checkInit(); return sSingleton.fVersion; } /** * The SIFFormatter used by default for backwards-compatible non-typed APIs * in the ADK, such as {@link Element#getTextValue()}. The default formatter * used by the ADK is the SIF 1.x formatter for backwards compatibility. If * you are* using the strongly-typed APIs, such as * {@link Element#getSIFValue()}, this setting has no effect. * * @return the SIFFormatter used by default if no SIFVersion is provided */ public static SIFFormatter getTextFormatter() { checkInit(); return sSingleton.fDefaultFormatter; } /** * Sets the SIFFormatter used by default for rendering Text values of SIF * Elements when mapping SIF Data directly to a string value. * <p> * * The default SIFFormatter used by the ADK is the formatter for SIF 1.5. * This means that agents that were based on the 1.x version of the ADK will * continue to get the SIF 1.x string version of data fields. * <p> * * APIs that are affected by this setting include:<br> * {@link Element#getTextValue()} (@link * openadk.library.sifworks.tools.mapping.StringMapAdaptor} * * * @param formatter * The default formatter to be used that translates native data * types supported by SIF to their textual representation */ public static void setTextFormatter(SIFFormatter formatter) { sSingleton.fDefaultFormatter = formatter; } /** * Determines if the specified version of SIF is supported by the ADK * * @param version * The version of SIF * @return True if the specified version is supported by the ADK */ public static boolean isSIFVersionSupported(SIFVersion version) { return Arrays.binarySearch(sSupportedVersionsArray, version) > -1; } /** * Gets the default SIFDTD object for the version of SIF selected when the * ADK was initialized. * <p> * * @return The SIFDTD object for the version of SIF selected when the ADK * was initialized. */ public static SIFDTD DTD() { return ADK.DTD; } /** * Gets a reference to the global ISIFPrimitives object used for SIF * messaging * <p> * * @return The ISIFPrimitives implementation used by the ADK to send SIF * Messages */ public static ISIFPrimitives getPrimitives() { checkInit(); if (sSingleton.fImpl == null) sSingleton.fImpl = new openadk.library.impl.SIFPrimitives(); return sSingleton.fImpl; } /** * Utility to check that the ADK has been initialized. * * @throws InternalError * if the <code>initialize</code> function has not been * successfully called */ private static void checkInit() { if (sSingleton == null) throw new InternalError("The ADK is not initialized. Please call ADK.initialize()"); } /** * Displays the ADK Version * * @param args * The command line arguments are ignored */ public static void main(String[] args) { try { initialize(); System.out.println(ADK.getADKVersion()); } catch (Exception ex) { System.out.println(ex); } } }