/* The contents of this file are subject to the license and copyright terms * detailed in the license directory at the root of the source tree (also * available online at http://fedora-commons.org/license/). */ package fedora.server; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import org.apache.log4j.Logger; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.xml.sax.SAXException; import fedora.common.Constants; import fedora.common.FaultException; import fedora.common.MalformedPIDException; import fedora.common.PID; import fedora.server.config.ServerConfiguration; import fedora.server.config.ServerConfigurationParser; import fedora.server.errors.GeneralException; import fedora.server.errors.MalformedPidException; import fedora.server.errors.ModuleInitializationException; import fedora.server.errors.ModuleShutdownException; import fedora.server.errors.ServerInitializationException; import fedora.server.errors.ServerShutdownException; import fedora.server.errors.authorization.AuthzException; import fedora.server.security.Authorization; import fedora.server.utilities.DateUtility; import fedora.server.utilities.status.ServerState; import fedora.server.utilities.status.ServerStatusFile; import fedora.utilities.Log4J; /** * The starting point for working with a Fedora repository. This class handles * loading, starting, and stopping modules (the module lifecycle), and provides * access to core constants. * * @author Chris Wilper */ public abstract class Server extends Pluggable { public static final boolean USE_CACHE = true; public static final boolean USE_DEFINITIVE_STORE = false; public static final boolean GLOBAL_CHOICE = false; /** Logger for this class. */ private static Logger LOG; /** * The ResourceBundle that provides access to constants from * fedora/server/resources/Server.properties. */ private static ResourceBundle s_const = ResourceBundle.getBundle("fedora.server.resources.Server"); /** The version of this release. */ public static String VERSION = s_const.getString("version"); /** The build date of this release. */ public static String BUILD_DATE = s_const.getString("buildDate"); /** The build number of this release. */ public static String BUILD_NUMBER = s_const.getString("buildNumber"); /** The name of the property that tells the server where it's based. */ public static String HOME_PROPERTY = s_const.getString("home.property"); /** The internal XML storage format for digital objects */ public static String STORAGE_FORMAT = s_const.getString("format.storage"); /** The directory where server configuration is stored, relative to home. */ public static String CONFIG_DIR = s_const.getString("config.dir"); /** * The startup log file. This file will include all log messages regardless * of their <code>Level</code>. */ public static String LOG_STARTUP_FILE = s_const.getString("log.startup.file"); /** The configuration filename. */ public static String CONFIG_FILE = s_const.getString("config.file"); /** The directory where server extensions are stored, relative to home. */ public static String EXTENSION_DIR = s_const.getString("extension.dir"); /** The directory where server executables are stored, relative to home. */ public static String BIN_DIR = s_const.getString("bin.dir"); /** * The prefix to all fedora-defined namespaces for this version. * 0={version.major}, 1={version.minor} */ public static String NAMESPACE_PREFIX = MessageFormat.format(s_const.getString("namespace.prefix"), new Object[] {"1", "0"}); // so config namespace uses 1/0/ /** The configuration file elements' namespace. 0={namespace.prefix} */ public static String CONFIG_NAMESPACE = MessageFormat.format(s_const.getString("config.namespace"), new Object[] {NAMESPACE_PREFIX}); /** The configuration file root element's name. */ public static String CONFIG_ELEMENT_ROOT = s_const.getString("config.element.root"); /** The configuration file comment element's name. */ public static String CONFIG_ELEMENT_COMMENT = s_const.getString("config.element.comment"); /** The configuration file datastore element's name. */ public static String CONFIG_ELEMENT_DATASTORE = s_const.getString("config.element.datastore"); /** The configuration file module element's name. */ public static String CONFIG_ELEMENT_MODULE = s_const.getString("config.element.module"); /** The configuration file param element's name. */ public static String CONFIG_ELEMENT_PARAM = s_const.getString("config.element.param"); /** * The configuration file's class-specifying attribute for server and module * elements. */ public static String CONFIG_ATTRIBUTE_CLASS = s_const.getString("config.attribute.class"); /** * The configuration file's role-specifying attribute for module elements. */ public static String CONFIG_ATTRIBUTE_ROLE = s_const.getString("config.attribute.role"); /** The configuration file param element's name attribute. */ public static String CONFIG_ATTRIBUTE_NAME = s_const.getString("config.attribute.name"); /** The configuration file param element's value attribute. */ public static String CONFIG_ATTRIBUTE_VALUE = s_const.getString("config.attribute.value"); /** The configuration file datastore element's id attribute. */ public static String CONFIG_ATTRIBUTE_ID = s_const.getString("config.attribute.id"); /** The required server constructor's first parameter's class. */ public static String SERVER_CONSTRUCTOR_PARAM1_CLASS = s_const.getString("server.constructor.param1.class"); /** The required server constructor's second parameter's class. */ public static String SERVER_CONSTRUCTOR_PARAM2_CLASS = s_const.getString("server.constructor.param2.class"); /** The required module constructor's first parameter's class. */ public static String MODULE_CONSTRUCTOR_PARAM1_CLASS = s_const.getString("module.constructor.param1.class"); /** The required module constructor's second parameter's class. */ public static String MODULE_CONSTRUCTOR_PARAM2_CLASS = s_const.getString("module.constructor.param2.class"); /** The required module constructor's third parameter's class. */ public static String MODULE_CONSTRUCTOR_PARAM3_CLASS = s_const.getString("module.constructor.param3.class"); /** The name of the default Server implementation class */ public static String DEFAULT_SERVER_CLASS = s_const.getString("default.server.class"); /** Indicates that an XML parser could not be found. */ public static String INIT_XMLPARSER_SEVERE_MISSING = s_const.getString("init.xmlparser.severe.missing"); /** * Indicates that the config file could not be read. 0=config file full * path, 1=additional info from underlying exception */ public static String INIT_CONFIG_SEVERE_UNREADABLE = s_const.getString("init.config.severe.unreadable"); /** * Indicates that the config file has malformed XML. 0=config file full * path, 1=additional info from underlying exception */ public static String INIT_CONFIG_SEVERE_MALFORMEDXML = s_const.getString("init.config.severe.malformedxml"); /** * Indicates that the config file has a mis-named root element. 0=config * file full path, 1={config.element.root}, 2=actual root element name */ public static String INIT_CONFIG_SEVERE_BADROOTELEMENT = s_const.getString("init.config.severe.badrootelement"); /** * Indicates that an invalid element was found in the configuration xml. * 1=the invalid element's name */ public static String INIT_CONFIG_SEVERE_BADELEMENT = s_const.getString("init.config.severe.badelement"); /** * Indicates that a CONFIG_ELEMENT_DATASTORE didn't specify the required * CONFIG_ATTRIBUTE_ID. 0={config.element.datastore}, * 1={config.attribute.id} */ public static String INIT_CONFIG_SEVERE_NOIDGIVEN = MessageFormat.format(s_const .getString("init.config.severe.noidgiven"), new Object[] { CONFIG_ELEMENT_DATASTORE, CONFIG_ATTRIBUTE_ID}); /** * Indicates that the config file's element's namespace does not match * {config.namespace}. 0=config file full path, 1={config.namespace} */ public static String INIT_CONFIG_SEVERE_BADNAMESPACE = s_const.getString("init.config.severe.badnamespace"); /** * Indicates that a module element in the server configuration did not * specify a role, but should. 0={config.element.module}, * 1={config.attribute.role} */ public static String INIT_CONFIG_SEVERE_NOROLEGIVEN = MessageFormat.format(s_const .getString("init.config.severe.norolegiven"), new Object[] { CONFIG_ELEMENT_MODULE, CONFIG_ATTRIBUTE_ROLE}); /** * Indicates that a module element in the server configuration did not * specify an implementing class, but should. 0={config.element.module}, * 1={config.attribute.class} */ public static String INIT_CONFIG_SEVERE_NOCLASSGIVEN = MessageFormat .format(s_const .getString("init.config.severe.noclassgiven"), new Object[] {CONFIG_ELEMENT_MODULE, CONFIG_ATTRIBUTE_CLASS}); /** * Indicates that an attribute of an element was assigned the same value as * a previously specified element's attribute, and that this constitutes a * disallowed reassignment. 0=the common element, 1=the common attribute's * name, 2=the common attribute's value. */ public static String INIT_CONFIG_SEVERE_REASSIGNMENT = s_const.getString("init.config.severe.reassignment"); /** * Indicates that a parameter element in the config file is missing a * required element. 0={config.element.param}, 1={config.attribute.name}, * 2={config.attribute.value} */ public static String INIT_CONFIG_SEVERE_INCOMPLETEPARAM = MessageFormat .format(s_const .getString("init.config.severe.incompleteparam"), new Object[] {CONFIG_ELEMENT_PARAM, CONFIG_ATTRIBUTE_NAME, CONFIG_ATTRIBUTE_VALUE}); /** * Tells which config element is being looked at in order to load its * parameters into memory. 0=name of element being examined, * 1=distinguishing attribute (name="value"), or empty string if * no distinguishing attribute. */ public static String INIT_CONFIG_CONFIG_EXAMININGELEMENT = s_const.getString("init.config.config.examiningelement"); /** * Tells the name and value of a parameter loaded from the config file. * 0=param name, 1=param value */ public static String INIT_CONFIG_CONFIG_PARAMETERIS = s_const.getString("init.config.config.parameteris"); /** * Indicates that the server class could not be found. 0=server class * specified in config root element */ public static String INIT_SERVER_SEVERE_CLASSNOTFOUND = s_const.getString("init.server.severe.classnotfound"); /** * Indicates that the server class couldn't be accessed due to security * misconfiguration. 0=server class specified in config root element */ public static String INIT_SERVER_SEVERE_ILLEGALACCESS = s_const.getString("init.server.severe.illegalaccess"); /** * Indicates that the server class constructor was invoked improperly due to * programmer error. 0=server class specified in config root element */ public static String INIT_SERVER_SEVERE_BADARGS = s_const.getString("init.server.severe.badargs"); /** * Indicates that the server class doesn't have a constructor matching * Server(NodeList, File), but needs one. 0=server class specified in config * root element. */ public static String INIT_SERVER_SEVERE_MISSINGCONSTRUCTOR = s_const.getString("init.server.severe.missingconstructor"); /** * Indicates that a module role required to be fulfilled by this server was * not fulfilled because the configuration did not specify a module with * that role. 0=the role */ public static String INIT_SERVER_SEVERE_UNFULFILLEDROLE = s_const.getString("init.server.severe.unfulfilledrole"); public static String INIT_MODULE_SEVERE_UNFULFILLEDROLE = s_const.getString("init.module.severe.unfulfilledrole"); /** * Indicates that the server class was abstract, but shouldn't be. 0=server * class specified in config root element */ public static String INIT_SERVER_SEVERE_ISABSTRACT = s_const.getString("init.server.severe.isabstract"); /** * Indicates that the module class could not be found. 0=module class * specified in config */ public static String INIT_MODULE_SEVERE_CLASSNOTFOUND = s_const.getString("init.module.severe.classnotfound"); /** * Indicates that the module class couldn't be accessed due to security * misconfiguration. 0=module class specified in config */ public static String INIT_MODULE_SEVERE_ILLEGALACCESS = s_const.getString("init.module.severe.illegalaccess"); /** * Indicates that the module class constructor was invoked improperly due to * programmer error. 0=module class specified in config */ public static String INIT_MODULE_SEVERE_BADARGS = s_const.getString("init.module.severe.badargs"); /** * Indicates that the module class doesn't have a constructor matching * Module(Map, Server, String), but needs one. 0=module class specified in * config */ public static String INIT_MODULE_SEVERE_MISSINGCONSTRUCTOR = s_const.getString("init.module.severe.missingconstructor"); /** * Indicates that the module class was abstract, but shouldn't be. 0=module * class specified in config */ public static String INIT_MODULE_SEVERE_ISABSTRACT = s_const.getString("init.module.severe.isabstract"); /** * Indicates that the startup log could not be written to its usual place * for some reason, and that we're falling back to stderr. 0=usual place, * 1=exception message */ public static String INIT_LOG_WARNING_CANTWRITESTARTUPLOG = s_const.getString("init.log.warning.cantwritestartuplog"); /** * The server-wide default locale, obtained via <code>getLocale()</code>. */ private static Locale s_locale; /** * Holds an instance of a <code>Server</code> for each distinct * <code>File</code> given as a parameter to <code>getInstance(...)</code> */ protected static Map<File, Server> s_instances = new HashMap<File, Server>(); /** * The server's home directory. */ private File m_homeDir; /** * Datastore configurations initialized from the server config file. */ private Map<String, DatastoreConfig> m_datastoreConfigs; /** * Modules that have been loaded. */ private Map<String, Module> m_loadedModules; /** * Is the server running? */ private boolean m_initialized; /** * The server status File. */ private ServerStatusFile m_statusFile; /** * What server profile should be used? */ private static String s_serverProfile = System.getProperty("fedora.serverProfile"); /** * Initializes the Server based on configuration. * <p> * </p> * Reads and schema-validates the configuration items in the given DOM * <code>NodeList</code>, validates required server params, initializes * the <code>Server</code>, then initializes each module, validating its * required params, then verifies that the server's required module roles * have been met. * * @param rootConfigElement * The root <code>Element</code> of configuration. * @param homeDir * The home directory of fedora, used to interpret relative paths * used in configuration. * @throws ServerInitializationException * If there was an error starting the server. * @throws ModuleInitializationException * If there was an error starting a module. */ protected Server(Element rootConfigElement, File homeDir) throws ServerInitializationException, ModuleInitializationException { try { m_initialized = false; m_loadedModules = new HashMap<String, Module>(); m_homeDir = new File(homeDir, "server"); m_statusFile = new ServerStatusFile(m_homeDir); File logDir = new File(m_homeDir, "logs"); if (!logDir.exists()) { logDir.mkdir(); // try to create dir if doesn't exist } File configFile = new File(m_homeDir + File.separator + CONFIG_DIR + File.separator + CONFIG_FILE); LOG.info("Server home is " + m_homeDir.toString()); if (s_serverProfile == null) { LOG .debug("fedora.serverProfile property not set... will always " + "use param 'value' attributes from configuration for param values."); } else { LOG.debug("fedora.serverProfile property was '" + s_serverProfile + "'... will use param '" + s_serverProfile + "value' attributes from " + "configuration for param values, falling back to " + "'value' attributes where unspecified."); } LOG.debug("Loading and validating configuration file \"" + configFile + "\""); // do the parsing and validation of configuration Map serverParams = loadParameters(rootConfigElement, ""); // get the module and datastore info, remove the holding element, // and set the server params so they can be seen via getParameter() ArrayList mdInfo = (ArrayList) serverParams.get(null); HashMap moduleParams = (HashMap) mdInfo.get(0); HashMap moduleClassNames = (HashMap) mdInfo.get(1); HashMap datastoreParams = (HashMap) mdInfo.get(2); serverParams.remove(null); setParameters(serverParams); // ensure server's module roles are met String[] reqRoles = getRequiredModuleRoles(); for (String element : reqRoles) { if (moduleParams.get(element) == null) { throw new ServerInitializationException(MessageFormat .format(INIT_SERVER_SEVERE_UNFULFILLEDROLE, new Object[] {element})); } } // initialize the server m_statusFile.append(ServerState.STARTING, "Initializing Server"); initServer(); // create the datastore configs and set the instance variable // so they can be seen with getDatastoreConfig(...) Iterator dspi = datastoreParams.keySet().iterator(); m_datastoreConfigs = new HashMap(); while (dspi.hasNext()) { String id = (String) dspi.next(); m_datastoreConfigs .put(id, new DatastoreConfig((HashMap) datastoreParams .get(id))); } // initialize each module m_statusFile.append(ServerState.STARTING, "Initializing Modules"); Iterator mRoles = moduleParams.keySet().iterator(); while (mRoles.hasNext()) { String role = (String) mRoles.next(); String className = (String) moduleClassNames.get(role); LOG.info("Initializing " + className); try { Class moduleClass = Class.forName(className); Class param1Class = Class.forName(MODULE_CONSTRUCTOR_PARAM1_CLASS); Class param2Class = Class.forName(MODULE_CONSTRUCTOR_PARAM2_CLASS); Class param3Class = Class.forName(MODULE_CONSTRUCTOR_PARAM3_CLASS); LOG.debug("Getting constructor " + className + "(" + MODULE_CONSTRUCTOR_PARAM1_CLASS + "," + MODULE_CONSTRUCTOR_PARAM2_CLASS + "," + MODULE_CONSTRUCTOR_PARAM3_CLASS + ")"); Constructor moduleConstructor = moduleClass.getConstructor(new Class[] { param1Class, param2Class, param3Class}); Module inst = (Module) moduleConstructor .newInstance(new Object[] { moduleParams.get(role), (Server) this, role}); m_loadedModules.put(role, inst); } catch (ClassNotFoundException cnfe) { throw new ModuleInitializationException(MessageFormat .format(INIT_MODULE_SEVERE_CLASSNOTFOUND, new Object[] {className}), role); } catch (IllegalAccessException iae) { // improbable throw new ModuleInitializationException(MessageFormat .format(INIT_MODULE_SEVERE_ILLEGALACCESS, new Object[] {className}), role); } catch (IllegalArgumentException iae) { // improbable throw new ModuleInitializationException(MessageFormat .format(INIT_MODULE_SEVERE_BADARGS, new Object[] {className}), role); } catch (InstantiationException ie) { throw new ModuleInitializationException(MessageFormat .format(INIT_MODULE_SEVERE_MISSINGCONSTRUCTOR, new Object[] {className}), role); } catch (NoSuchMethodException nsme) { throw new ModuleInitializationException(MessageFormat .format(INIT_MODULE_SEVERE_ISABSTRACT, new Object[] {className}), role); } catch (InvocationTargetException ite) { // throw the constructor's thrown exception, if any try { throw ite.getCause(); // as of java 1.4 } catch (ModuleInitializationException mie) { throw mie; } catch (Throwable t) { // a runtime error..shouldn't happen, but if it does... StringBuffer s = new StringBuffer(); s.append(t.getClass().getName()); s.append(": "); for (int i = 0; i < t.getStackTrace().length; i++) { s.append(t.getStackTrace()[i] + "\n"); } throw new ModuleInitializationException(s.toString(), role); } } } // Do postInitModule for all Modules, verifying beforehand that // the required module roles (dependencies) have been fulfilled // for that module. m_statusFile.append(ServerState.STARTING, "Post-Initializing Modules"); mRoles = moduleParams.keySet().iterator(); while (mRoles.hasNext()) { String r = (String) mRoles.next(); Module m = getModule(r); LOG.info("Post-Initializing " + m.getClass().getName()); reqRoles = m.getRequiredModuleRoles(); LOG.debug("verifying dependencies have been loaded..."); for (String element : reqRoles) { if (getModule(element) == null) { throw new ModuleInitializationException(MessageFormat .format(INIT_MODULE_SEVERE_UNFULFILLEDROLE, new Object[] {element}), r); } } LOG.debug(reqRoles.length + " dependencies, all loaded, ok."); m.postInitModule(); } // Do postInitServer for the Server instance LOG.debug("Post-initializing server"); postInitServer(); // flag that we're done initting LOG.info("Server startup complete"); m_initialized = true; } catch (ServerInitializationException sie) { // these are caught and rethrown for two reasons: // 1) so they can be logged in the startup log, and // 2) so an attempt can be made to free resources tied up thus far // via shutdown() LOG.fatal("Server failed to initialize", sie); try { shutdown(null); } catch (Throwable th) { LOG.warn("Error shutting down server after failed startup", th); } throw sie; } catch (ModuleInitializationException mie) { LOG.fatal("Module (" + mie.getRole() + ") failed to initialize", mie); try { shutdown(null); } catch (Throwable th) { LOG.warn("Error shutting down server after failed startup", th); } throw mie; } catch (Throwable th) { String msg = "Fatal error while starting server"; LOG.fatal(msg, th); try { shutdown(null); } catch (Throwable oth) { LOG .warn("Error shutting down server after failed startup", oth); } throw new RuntimeException(msg, th); } } protected boolean overrideModuleRole(String moduleRole) { return false; } protected String overrideModuleClass(String moduleClass) { return null; } /** * Configures Log4J using FEDORA_HOME/config/log4j.properties. */ protected static void configureLog4J(String extension) throws ServerInitializationException { File fedoraHome = new File(Constants.FEDORA_HOME); File serverDir = new File(fedoraHome, "server"); File logDir = new File(serverDir, "logs"); logDir.mkdirs(); Map<String, String> options = new HashMap<String, String>(); options.put("logDir", logDir.getPath()); options.put("extension", extension); File propFile = new File(serverDir, "config/log4j.properties"); try { Log4J.initFromPropFile(propFile, options); } catch (Exception e) { throw new ServerInitializationException("Error initializing from " + "log4j configuration file: " + propFile.getPath(), e); } LOG = Logger.getLogger(Server.class.getName()); } /** * Builds and returns a <code>Map</code> of parameter name-value pairs * defined as children of the given <code>Element</code>, according to * the server configuration schema. * <p> * </p> * If the given element is a CONFIG_ELEMENT_ROOT, this method will return * (along with the server's parameter name-value pairs) a <code>null</code>-keyed * value, which is an <code>ArrayList</code> of three <code>HashMap</code> * objects. The first will contain the name-value pair HashMaps of each of * the CONFIG_ELEMENT_MODULE elements found (in a <code>HashMap</code> * keyed by <i>role</i>), the second will contain a <code>HashMap</code> * mapping module <i>role</i>s to implementation classnames, and the third * will contain the the name-value pair <code>HashMaps</code> of each of * the CONFIG_ELEMENT_DATASTORE elements found (keyed by * CONFIG_ATTRIBUTE_ID). * * @param element * The element containing the name-value pair defintions. * @param dAttribute * The name of the attribute of the <code>Element</code> whose * value will distinguish this element from others that may occur in * the <code>Document</code>. If there is no distinguishing * attribute, this should be an empty string. */ private final Map loadParameters(Element element, String dAttribute) throws ServerInitializationException { Map params = new HashMap(); if (element.getLocalName().equals(CONFIG_ELEMENT_ROOT)) { ArrayList moduleAndDatastreamInfo = new ArrayList(3); moduleAndDatastreamInfo.add(new HashMap()); moduleAndDatastreamInfo.add(new HashMap()); moduleAndDatastreamInfo.add(new HashMap()); params.put(null, moduleAndDatastreamInfo); } LOG.debug(MessageFormat.format(INIT_CONFIG_CONFIG_EXAMININGELEMENT, new Object[] {element.getLocalName(), dAttribute})); for (int i = 0; i < element.getChildNodes().getLength(); i++) { Node n = element.getChildNodes().item(i); if (n.getNodeType() == Node.ELEMENT_NODE) { if (n.getLocalName().equals(CONFIG_ELEMENT_PARAM)) { // if name-value pair, save in the HashMap NamedNodeMap attrs = n.getAttributes(); Node nameNode = attrs.getNamedItemNS(CONFIG_NAMESPACE, CONFIG_ATTRIBUTE_NAME); if (nameNode == null) { nameNode = attrs.getNamedItem(CONFIG_ATTRIBUTE_NAME); } Node valueNode = null; if (s_serverProfile != null) { valueNode = attrs.getNamedItemNS(CONFIG_NAMESPACE, s_serverProfile + "value"); if (valueNode == null) { valueNode = attrs.getNamedItem(s_serverProfile + "value"); } } if (valueNode == null) { valueNode = attrs.getNamedItemNS(CONFIG_NAMESPACE, CONFIG_ATTRIBUTE_VALUE); if (valueNode == null) { valueNode = attrs.getNamedItem(CONFIG_ATTRIBUTE_VALUE); } if (nameNode == null || valueNode == null) { throw new ServerInitializationException(INIT_CONFIG_SEVERE_INCOMPLETEPARAM); } } if (nameNode.getNodeValue().equals("") || valueNode.getNodeValue().equals("")) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_INCOMPLETEPARAM, new Object[] {CONFIG_ELEMENT_PARAM, CONFIG_ATTRIBUTE_NAME, CONFIG_ATTRIBUTE_VALUE})); } if (params.get(nameNode.getNodeValue()) != null) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_REASSIGNMENT, new Object[] {CONFIG_ELEMENT_PARAM, CONFIG_ATTRIBUTE_NAME, nameNode.getNodeValue()})); } params.put(nameNode.getNodeValue(), valueNode .getNodeValue()); LOG.debug(MessageFormat .format(INIT_CONFIG_CONFIG_PARAMETERIS, new Object[] {nameNode.getNodeValue(), valueNode.getNodeValue()})); } else if (!n.getLocalName().equals(CONFIG_ELEMENT_COMMENT)) { if (element.getLocalName().equals(CONFIG_ELEMENT_ROOT)) { if (n.getLocalName().equals(CONFIG_ELEMENT_MODULE)) { NamedNodeMap attrs = n.getAttributes(); Node roleNode = attrs.getNamedItemNS(CONFIG_NAMESPACE, CONFIG_ATTRIBUTE_ROLE); if (roleNode == null) { roleNode = attrs .getNamedItem(CONFIG_ATTRIBUTE_ROLE); if (roleNode == null) { throw new ServerInitializationException(INIT_CONFIG_SEVERE_NOROLEGIVEN); } } String moduleRole = roleNode.getNodeValue(); if (moduleRole.equals("")) { throw new ServerInitializationException(INIT_CONFIG_SEVERE_NOROLEGIVEN); } if (overrideModuleRole(moduleRole)) { continue; } HashMap moduleImplHash = (HashMap) ((ArrayList) params.get(null)) .get(1); if (moduleImplHash.get(moduleRole) != null) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_REASSIGNMENT, new Object[] { CONFIG_ELEMENT_MODULE, CONFIG_ATTRIBUTE_ROLE, moduleRole})); } Node classNode = attrs .getNamedItemNS(CONFIG_NAMESPACE, CONFIG_ATTRIBUTE_CLASS); if (classNode == null) { classNode = attrs .getNamedItem(CONFIG_ATTRIBUTE_CLASS); if (classNode == null) { throw new ServerInitializationException(INIT_CONFIG_SEVERE_NOCLASSGIVEN); } } String moduleClass = classNode.getNodeValue(); if (overrideModuleClass(moduleClass) != null) { moduleClass = overrideModuleClass(moduleClass); } if (moduleClass.equals("")) { throw new ServerInitializationException(INIT_CONFIG_SEVERE_NOCLASSGIVEN); } moduleImplHash.put(moduleRole, moduleClass); ((HashMap) ((ArrayList) params.get(null)).get(0)) .put(moduleRole, loadParameters((Element) n, CONFIG_ATTRIBUTE_ROLE + "=\"" + moduleRole + "\"")); } else if (n.getLocalName() .equals(CONFIG_ELEMENT_DATASTORE)) { NamedNodeMap attrs = n.getAttributes(); Node idNode = attrs.getNamedItemNS(CONFIG_NAMESPACE, CONFIG_ATTRIBUTE_ID); if (idNode == null) { idNode = attrs.getNamedItem(CONFIG_ATTRIBUTE_ID); if (idNode == null) { throw new ServerInitializationException(INIT_CONFIG_SEVERE_NOIDGIVEN); } } String dConfigId = idNode.getNodeValue(); if (dConfigId.equals("")) { throw new ServerInitializationException(INIT_CONFIG_SEVERE_NOIDGIVEN); } HashMap dParamHash = (HashMap) ((ArrayList) params.get(null)) .get(2); if (dParamHash.get(dConfigId) != null) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_REASSIGNMENT, new Object[] { CONFIG_ELEMENT_DATASTORE, CONFIG_ATTRIBUTE_ID, dConfigId})); } dParamHash.put(dConfigId, loadParameters((Element) n, CONFIG_ATTRIBUTE_ID + "=\"" + dConfigId + "\"")); } else { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_BADELEMENT, new Object[] {n.getLocalName()})); } } } } // else { // ignore non-Element nodes } } return params; } /** * Tells whether the server (and loaded modules) have initialized. * <p> * </p> * This is useful for threaded <code>Modules</code> that need to wait * until all initialization has occurred before doing something. * * @return whether initialization has completed. */ public final boolean hasInitialized() { return m_initialized; } /** * Get the status file for the server. Important messages pertaining to * startup and shutdown go here. */ public ServerStatusFile getStatusFile() { return m_statusFile; } public final static boolean hasInstance(File homeDir) { return s_instances.get(homeDir) != null; } public final String status(Context context) throws AuthzException { ((Authorization) getModule("fedora.server.security.Authorization")) .enforceServerStatus(context); return "RUNNING"; } public final static Server getInstance(File homeDir, boolean okToStart) throws ServerInitializationException, ModuleInitializationException { if (okToStart) { return getInstance(homeDir); } else { Server instance = (Server) s_instances.get(homeDir); if (instance == null) { throw new ServerInitializationException("The Fedora server is not yet running."); } else { return instance; } } } /** * Provides an instance of the server specified in the configuration file at * homeDir/CONFIG_DIR/CONFIG_FILE, or DEFAULT_SERVER_CLASS if unspecified. * * @param homeDir * The base directory for the server. * @return The instance. * @throws ServerInitializationException * If there was an error starting the server. * @throws ModuleInitializationException * If there was an error starting a module. */ public final static synchronized Server getInstance(File homeDir) throws ServerInitializationException, ModuleInitializationException { // return an instance if already in memory Server instance = (Server) s_instances.get(homeDir); if (instance != null) { return instance; } configureLog4J(".log"); LOG.info("Starting up server"); // else instantiate a new one given the class provided in the // root element in the config file and return it File configFile = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); configFile = new File(homeDir + File.separator + "server" + File.separator + CONFIG_DIR + File.separator + CONFIG_FILE); // suck it in Element rootElement = builder.parse(configFile).getDocumentElement(); // ensure root element name ok if (!rootElement.getLocalName().equals(CONFIG_ELEMENT_ROOT)) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_BADROOTELEMENT, new Object[] {configFile, CONFIG_ELEMENT_ROOT, rootElement.getLocalName()})); } // ensure namespace specified properly if (!rootElement.getNamespaceURI().equals(CONFIG_NAMESPACE)) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_BADNAMESPACE, new Object[] { configFile, CONFIG_NAMESPACE})); } // select <server class="THIS_PART"> .. </server> String className = rootElement.getAttribute(CONFIG_ATTRIBUTE_CLASS); if (className.equals("")) { className = rootElement.getAttributeNS(CONFIG_NAMESPACE, CONFIG_ATTRIBUTE_CLASS); if (className.equals("")) { className = DEFAULT_SERVER_CLASS; } } try { Class serverClass = Class.forName(className); Class param1Class = Class.forName(SERVER_CONSTRUCTOR_PARAM1_CLASS); Class param2Class = Class.forName(SERVER_CONSTRUCTOR_PARAM2_CLASS); Constructor serverConstructor = serverClass.getConstructor(new Class[] {param1Class, param2Class}); Server inst = (Server) serverConstructor.newInstance(new Object[] { rootElement, homeDir}); s_instances.put(homeDir, inst); return inst; } catch (ClassNotFoundException cnfe) { throw new ServerInitializationException(MessageFormat .format(INIT_SERVER_SEVERE_CLASSNOTFOUND, new Object[] {className})); } catch (IllegalAccessException iae) { // improbable throw new ServerInitializationException(MessageFormat .format(INIT_SERVER_SEVERE_ILLEGALACCESS, new Object[] {className})); } catch (IllegalArgumentException iae) { // improbable throw new ServerInitializationException(MessageFormat .format(INIT_SERVER_SEVERE_BADARGS, new Object[] {className})); } catch (InstantiationException ie) { throw new ServerInitializationException(MessageFormat .format(INIT_SERVER_SEVERE_MISSINGCONSTRUCTOR, new Object[] {className})); } catch (NoSuchMethodException nsme) { throw new ServerInitializationException(MessageFormat .format(INIT_SERVER_SEVERE_ISABSTRACT, new Object[] {className})); } catch (InvocationTargetException ite) { // throw the constructor's thrown exception, if any try { throw ite.getCause(); // as of java 1.4 } catch (ServerInitializationException sie) { throw sie; } catch (ModuleInitializationException mie) { throw mie; } catch (Throwable t) { // a runtime error..shouldn't happen, but if it does... StringBuffer s = new StringBuffer(); s.append(t.getClass().getName()); s.append(": "); for (int i = 0; i < t.getStackTrace().length; i++) { s.append(t.getStackTrace()[i] + "\n"); } throw new ServerInitializationException(s.toString()); } } } catch (ParserConfigurationException pce) { throw new ServerInitializationException(INIT_XMLPARSER_SEVERE_MISSING); } catch (FactoryConfigurationError fce) { throw new ServerInitializationException(INIT_XMLPARSER_SEVERE_MISSING); } catch (IOException ioe) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_UNREADABLE, new Object[] { configFile, ioe.getMessage()})); } catch (IllegalArgumentException iae) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_UNREADABLE, new Object[] { configFile, iae.getMessage()})); } catch (SAXException saxe) { throw new ServerInitializationException(MessageFormat .format(INIT_CONFIG_SEVERE_MALFORMEDXML, new Object[] { configFile, saxe.getMessage()})); } } /** * Gets the server's home directory. * * @return The directory. */ public final File getHomeDir() { return m_homeDir; } /** * Gets a loaded <code>Module</code>. * * @param role * The role of the <code>Module</code> to get. * @return The <code>Module</code>, <code>null</code> if not found. */ public final Module getModule(String role) { return (Module) m_loadedModules.get(role); } /** * Gets a <code>DatastoreConfig</code>. * * @param id * The id as given in the server configuration. * @return The <code>DatastoreConfig</code>, <code>null</code> if not * found. */ public final DatastoreConfig getDatastoreConfig(String id) { return (DatastoreConfig) m_datastoreConfigs.get(id); } public Iterator<String> datastoreConfigIds() { return m_datastoreConfigs.keySet().iterator(); } /** * Gets an <code>Iterator</code> over the roles that have been loaded. * * @return (<code>String</code>s) The roles. */ public final Iterator<String> loadedModuleRoles() { return m_loadedModules.keySet().iterator(); } /** * Performs any server start-up tasks particular to this type of Server. * <p> * </p> * This is guaranteed to be run before any modules are loaded. The default * implementation does nothing. * * @throws ServerInitializationException * If a severe server startup-related error occurred. */ protected void initServer() throws ServerInitializationException { if (1 == 2) { throw new ServerInitializationException(null); } } /** * Second stage of Server initialization. * <p> * </p> * This is guaranteed to be run after all Modules have been loaded and all * module initialization (initModule() and postInitModule()) has taken * place. The default implementation does nothing. * * @throws ServerInitializationException * If a severe server startup-related error occurred. */ protected void postInitServer() throws ServerInitializationException { if (1 == 2) { throw new ServerInitializationException(null); } } /** * Performs shutdown tasks for the modules and the server. * <p> * </p> * All loaded modules' shutdownModule() methods are called, then * shutdownServer is called. * <p> * </p> * <h3>How to Ensure Clean Server Shutdown</h3> * <p> * </p> * After having used a <code>Server</code> instance, if you know your * program is the only client of the <code>Server</code> in the VM * instance, you should make an explicit call to this method so that you can * catch and handle its exceptions properly. If you are usure or know that * there may be at least one other client of the <code>Server</code> in * the VM instance, you should call <code>System.runFinalization()</code> * after ensuring you no longer have a reference. In this case, if there is * no other reference to the object in the VM, finalization will be called * (but you will be unable to catch <code>ShutdownException</code> * variants, if thrown). * <p> * </p> * Right before this is finished, the instance is removed from the server * instances map. * * @throws ServerShutdownException * If a severe server shutdown-related error occurred. * USER_REPRESENTED = addName(new XacmlName(this, * "subjectRepresented")); * @throws ModuleShutdownException * If a severe module shutdown-related error occurred. */ public final void shutdown(Context context) throws ServerShutdownException, ModuleShutdownException, AuthzException { Iterator roleIterator = loadedModuleRoles(); LOG.info("Shutting down server"); ModuleShutdownException mse = null; while (roleIterator.hasNext()) { Module m = getModule((String) roleIterator.next()); String mName = m.getClass().getName(); try { LOG.info("Shutting down " + mName); m.shutdownModule(); } catch (ModuleShutdownException e) { LOG.warn("Error shutting down module " + mName, e); mse = e; } } shutdownServer(); LOG.info("Server shutdown complete"); s_instances.remove(getHomeDir()); if (mse != null) { throw mse; } } /** * Performs shutdown tasks for the server itself. This should be written so * that system resources are always freed, regardless of whether there is an * error. If an error occurs, it should be thrown as a * <code>ServerShutdownException</code> after attempts to free every * resource have been made. * * @throws ServerShutdownException * If a severe server shutdown-related error occurred. */ protected void shutdownServer() throws ServerShutdownException { if (1 == 2) { throw new ServerShutdownException(null); } } /** * Calls <code>shutdown()</code> when finalization occurs. * * @throws ServerShutdownException * If a severe server shutdown-related error occurred. * @throws ModuleShutdownException * If a severe module shutdown-related error occurred. */ public final void finalize() throws ServerShutdownException, ModuleShutdownException { shutdownServer(); } public final static Locale getLocale() { if (s_locale == null) { String language = System.getProperty("locale.language"); String country = System.getProperty("locale.country"); String variant = System.getProperty("locale.variant"); if (language != null && country != null) { if (variant != null) { s_locale = new Locale(language, country, variant); } else { s_locale = new Locale(language, country); } } else { s_locale = Locale.getDefault(); } } return s_locale; } public String getConfigSummary() { int i; StringBuffer out = new StringBuffer(); out.append("[ Fedora Server Configuration Summary ]\n\n"); out.append("Server class : " + this.getClass().getName() + "\n"); out.append("Required modules : "); String padding = " "; String[] roles = getRequiredModuleRoles(); if (roles.length == 0) { out.append("<none>\n"); } else { for (i = 0; i < roles.length; i++) { if (i > 0) { out.append(padding); } out.append(roles[i] + "\n"); } } out.append("Parameters : "); Iterator iter = parameterNames(); i = 0; while (iter.hasNext()) { String name = (String) iter.next(); String value = getParameter(name); if (i > 0) { out.append(padding); } out.append(name + "=" + value + "\n"); i++; } if (i == 0) { out.append("<none>\n"); } iter = loadedModuleRoles(); while (iter.hasNext()) { String role = (String) iter.next(); out.append("\nLoaded Module : " + role + "\n"); Module module = getModule(role); out.append("Class : " + module.getClass().getName() + "\n"); out.append("Dependencies : " + module.getRequiredModuleRoles().length + "\n"); for (i = 0; i < module.getRequiredModuleRoles().length; i++) { out.append("Dependency : " + module.getRequiredModuleRoles()[i] + "\n"); } out.append("Parameters : "); padding = " "; i = 0; Iterator iter2 = module.parameterNames(); while (iter2.hasNext()) { String name = (String) iter2.next(); String value = module.getParameter(name); if (i > 0) { out.append(padding); } out.append(name + "=" + value + "\n"); i++; } if (i == 0) { out.append("<none>\n"); } } iter = datastoreConfigIds(); while (iter.hasNext()) { String id = (String) iter.next(); out.append("\nDatastore Cfg : " + id + "\n"); out.append("Parameters : "); padding = " "; i = 0; Iterator iter2 = ((DatastoreConfig) getDatastoreConfig(id)).parameterNames(); while (iter2.hasNext()) { String name = (String) iter2.next(); String value = ((DatastoreConfig) getDatastoreConfig(id)) .getParameter(name); if (i > 0) { out.append(padding); } out.append(name + "=" + value + "\n"); i++; } if (i == 0) { out.append("<none>\n"); } } return out.toString(); } // Wraps PID constructor, throwing a ServerException instead public static PID getPID(String pidString) throws MalformedPidException { try { return new PID(pidString); } catch (MalformedPIDException e) { throw new MalformedPidException(e.getMessage()); } } // Wraps PID.fromFilename, throwing a ServerException instead public static PID pidFromFilename(String filename) throws MalformedPidException { try { return PID.fromFilename(filename); } catch (MalformedPIDException e) { throw new MalformedPidException(e.getMessage()); } } /** * Get the current date from the context. If the context doesn't specify a * value for the current date, or the specified value cannot be parsed as an * ISO8601 date string, a GeneralException will be thrown. */ public static Date getCurrentDate(Context context) throws GeneralException { String propName = Constants.ENVIRONMENT.CURRENT_DATE_TIME.uri; String dateTimeValue = context.getEnvironmentValue(propName); if (dateTimeValue == null) { throw new GeneralException("Missing value for environment " + "context attribute: " + propName); } Date currentDate = DateUtility.convertStringToDate(dateTimeValue); if (currentDate == null) { throw new GeneralException("Unparsable dateTime string: '" + dateTimeValue + "'"); } else { return currentDate; } } /** * Gets the server configuration. * * @return the server configuration. */ public static ServerConfiguration getConfig() { try { InputStream fcfg = new FileInputStream( new File(Constants.FEDORA_HOME, "server/config/fedora.fcfg")); ServerConfigurationParser parser = new ServerConfigurationParser(fcfg); return parser.parse(); } catch (IOException e) { throw new FaultException("Error loading server configuration", e); } } }