package pt.ist.fenixframework; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pt.ist.fenixframework.backend.BackEndId; import pt.ist.fenixframework.dml.DmlCompilerException; import pt.ist.fenixframework.dml.DomainModel; import pt.ist.fenixframework.util.NodeBarrier; /** * <p>This is the main class for the Fenix Framework. It is the central point for obtaining most of * the APIs that the user of the framework (a programmer) should need. * * <p>Before being able to use the framework, a program must initialize it, by providing a * configuration. There are two (disjoint) alternative methods to initialize the FenixFramework: * * <ul> * * <li><strong>By convention</strong>: When the FenixFramework class is loaded, its static * initialization code will check for the presence of a properties file that provides the * configuration information or the existence of system properties (given through the * <code>-D</code> switch).</li> * * <li><strong>Explicitly</strong>: The programmer either creates a {@link Config} or {@link * MultiConfig} instance and then explicitly calls the corresponding initialization method: {@link * FenixFramework#initialize(Config)} or {@link FenixFramework#initialize(MultiConfig)}.</li> * * </ul> * * <p>Using the configuration by convention, the framework will first load the properties from the * file <code>fenix-framework.properties</code> if it exists. Then it will load the properties from * the file <code>fenix-framework-<NNN>.properties</code>, if it exists (where * <code><NNN></code> is the name of the BackEnd that generated the domain-specific code). * Files are looked up in the application's classpath using * <code>Thread.currentThread().getContextClassLoader().getResource()</code>. Finally, it will load * any system properties that start with the prefix <code>fenixframework.</code> (and discard that * prefix). Whenever the same property is defined more than once, the last setting prevails. After * the previous steps have been taken, if there is any property set, then the framework will attempt * to create a {@link Config} instance and automatically invoke {@link * FenixFramework#initialize(Config)}. * * The syntax for the configuration file is to have each line in the form: * * <blockquote> * * <code>property=value</code> * * </blockquote> * * where each <code>property</code> must be the name of an existing configuration field (when * providind the properties via a system property use <code>-Dfenixframework.property=value</code>). * Additionally, there is one optional special property named <code>config.class</code>. By * default, the config parser creates an instance of the {@link BackEndId#getDefaultConfigClass()} * provided by the current BackEndId, but this property can be used to choose a different (albeit * compatible) configuration class. The config instance is then populated with each property * (except with the <code>config.class</code> property), using the following algorithm: * * <ol> * * <li> Confirm that the config has a field with the same name as the <code>property</code>. If * not, ignore the property with a warning.</li> * * <li> If the config class provides a method (can be private) in the format * <code><property>FromString(String)</code>, then such method will be invoked to set the * property. The {@link String} argument will be the <code>value</code>.</li> * * <li> Else, attempt to directly set the property on the field assigning it the <code>value</code> * String.</li> * * <li> If the previous attempts to set the value fail, throw a {@link ConfigError}.</li> * * </ol> * * <p>The rationale supporting the previous mechanism is to allow the config class to process the * String provided in the <code>value></code> using the <code>*FromString</code> method. * * <p> After population of the config finishes with success, the {@link * FenixFramework#initialize(Config)} method is invoked with the created Config instance. From this * point on, the initialization process continues just as if the programmer had explicitly invoked * that initialization method. * * <p>The explicit configuration maintains the original configuration style (since Fenix Framework * 1.0) with compile-time checking of the config's attributes. To use it, read the documentation in * the {@link Config} class. It also adds the possibility for the programmer to provide multiple * configurations and then let the initialization process decide which to use based on the current * {@link pt.ist.fenixframework.backend.BackEnd} (see {@link MultiConfig} for more details). * * <p>After initialization completes with success, the framework is ready to manage operations on * the domain objects. Also, it is possible to get an instance of the {@link DomainModel} class * representing the structure of the application's domain. * * @see Config * @see dml.DomainModel */ public class FenixFramework { private static final Logger logger = LoggerFactory.getLogger(FenixFramework.class); public static final String FENIX_FRAMEWORK_SYSTEM_PROPERTY_PREFIX = "fenixframework."; private static final String FENIX_FRAMEWORK_CONFIG_RESOURCE_DEFAULT = "fenix-framework.properties"; private static final String FENIX_FRAMEWORK_CONFIG_RESOURCE_PREFIX = "fenix-framework-"; private static final String FENIX_FRAMEWORK_CONFIG_RESOURCE_SUFFIX = ".properties"; private static final String FENIX_FRAMEWORK_LOGGING_CONFIG = "fenix-framework-log4j.properties"; private static final Object INIT_LOCK = new Object(); // private static boolean bootstrapped = false; private static boolean initialized = false; private static Config config; /** This is initialized on first invocation of {@link FenixFramework#getDomainModel()}, which * can only be invoked after the framework is initialized. */ private static DomainModel domainModel = null; private static NodeBarrier barrier; // private static Logger logger = null; static { // System.out.println("out.ERROR?: " + logger.isErrorEnabled()); // System.out.println("out.WARN?: " + logger.isWarnEnabled()); // System.out.println("out.INFO?: " + logger.isInfoEnabled()); // System.out.println("out.DEBUG?: " + logger.isDebugEnabled()); // System.out.println("out.TRACE?: " + logger.isTraceEnabled()); // System.err.println("err.ERROR?: " + logger.isErrorEnabled()); // System.err.println("err.WARN?: " + logger.isWarnEnabled()); // System.err.println("err.INFO?: " + logger.isInfoEnabled()); // System.err.println("err.DEBUG?: " + logger.isDebugEnabled()); // System.err.println("err.TRACE?: " + logger.isTraceEnabled()); // logger.error("INIT FF"); // logger.warn("INIT FF"); // logger.info("INIT FF"); // logger.debug("INIT FF"); // logger.trace("INIT FF"); logger.trace("Static initializer block for FenixFramework class [BEGIN]"); synchronized (INIT_LOCK) { logger.info("Trying auto-initialization with configuration by convention"); tryAutoInit(); } logger.trace("Static initializer block for FenixFramework class [END]"); } /** * Attempts to automatically initialize the framework by reading the configuration parameters * from a resource. The name of the resource depends on the name of the current backend. If * such resource is not found, a default fenix-framework.properties will still be attempted. If * neither a backend specific nor a default properties file is found, then the auto * initialization process gives up. */ private static void tryAutoInit() { /* first load the default configuration if it exists */ Properties props = loadProperties(FENIX_FRAMEWORK_CONFIG_RESOURCE_DEFAULT, new Properties()); logger.debug("Fenix Framework properties after reading default config file:" + props.toString()); /* look up current backend's name */ String currentBackEndName = BackEndId.getBackEndId().getBackEndName(); logger.debug("CurrentBackEndName = " + currentBackEndName); /* then override with the backend-specific config file */ props = loadProperties(FENIX_FRAMEWORK_CONFIG_RESOURCE_PREFIX + currentBackEndName + FENIX_FRAMEWORK_CONFIG_RESOURCE_SUFFIX, props); logger.debug("Fenix Framework properties after reading backend config file:" + props.toString()); /* finally, enforce any system properties */ props = loadSystemProperties(props); logger.debug("Fenix Framework properties after enforcing system properties:" + props.toString()); // try auto init for the given properties. If none exists just skip if (props.isEmpty() || ! tryAutoInit(props)) { logger.info("Skipping configuration by convention."); } } /** * Return a Properties setup from the given resourceName, backed by the default values if * any. */ private static Properties loadProperties(String resourceName, Properties defaults) { Properties props = new Properties(); props.putAll(defaults); InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName); if (in == null) { logger.info("Resource '" + resourceName + "' not found"); return props; } logger.info("Found configuration by convention in resource '" + resourceName + "'."); // add the new properties try { props.load(in); } catch (IOException e) { logger.warn("Failed auto initialization with " + resourceName, e); return defaults; } finally { try { in.close(); } catch (Throwable ignore) {} } return props; } private static Properties loadSystemProperties(Properties props) { Properties newProps = new Properties(); newProps.putAll(props); Properties systemProps = System.getProperties(); for (String propertyName : systemProps.stringPropertyNames()) { if (propertyName.startsWith(FENIX_FRAMEWORK_SYSTEM_PROPERTY_PREFIX)) { String value = systemProps.getProperty(propertyName); String realPropertyName = propertyName.substring(FENIX_FRAMEWORK_SYSTEM_PROPERTY_PREFIX.length()); logger.debug("Enforcing property from system: " + realPropertyName + "=" + value); newProps.setProperty(realPropertyName, value); } } return newProps; } /** * Attempt to automatically initialize the framework with the given set of properties. */ private static boolean tryAutoInit(Properties props) { Config config = null; try { config = createConfigFromProperties(props); } catch (ConfigError e) { logger.info("ConfigError", e); throw e; } FenixFramework.initialize(config); return true; } private static Config createConfigFromProperties(Properties props) { // get the config instance Config config = null; try { Class<? extends Config> configClass = null; // first check for possible overriding in the config file String configClassName = props.getProperty(Config.PROPERTY_CONFIG_CLASS); if (configClassName != null) { try { configClass = (Class<? extends Config>)Class.forName(configClassName); } catch (ClassNotFoundException e) { // here, we could ignore and attempt the default config class, but it's best if // the programmer understands that the configuration is flawed logger.error(ConfigError.CONFIG_CLASS_NOT_FOUND + configClassName, e); throw new ConfigError(ConfigError.CONFIG_CLASS_NOT_FOUND, configClassName); } } else { // fallback to the current backend's default configClass = BackEndId.getBackEndId().getDefaultConfigClass(); } config = configClass.newInstance(); } catch (InstantiationException e) { throw new ConfigError(e); } catch (IllegalAccessException e) { throw new ConfigError(e); } // populate config from properties config.populate(props); return config; } /** * @return whether the <code>FenixFramework.initialize</code> method has * already been invoked */ public static boolean isInitialized() { synchronized (INIT_LOCK) { return initialized; } } /** This method initializes the FenixFramework. It must be the first method * to be called, and it should be invoked only once. It needs to be called * before starting to access any Transactions/DomainObjects, etc. * * @param newConfig The configuration that will be used by this instance of the framework. */ public static void initialize(Config newConfig) { synchronized (INIT_LOCK) { if (initialized) { throw new ConfigError(ConfigError.ALREADY_INITIALIZED); } if (newConfig == null) { logger.warn("Initialization with a 'null' config instance."); throw new ConfigError("A configuration must be provided"); } logger.info("Initializing Fenix Framework with config.class=" + newConfig.getClass().getName()); FenixFramework.config = newConfig; // domainModelURLs should have been set by now FenixFramework.config.checkForDomainModelURLs(); // set the domain model. This must be done before calling FenixFramework.config.initialize() try { domainModel = DmlCompiler.getDomainModel(Arrays.asList(FenixFramework.config.getDomainModelURLs())); } catch (Exception ex) { ex.printStackTrace(); throw new ConfigError(ex); } try { FenixFramework.config.initialize(); } catch (RuntimeException e) { logger.error("Could not initialize Fenix Framework", e); e.printStackTrace(); throw e; } catch (ConfigError e) { logger.error("Could not initialize Fenix Framework", e); e.printStackTrace(); throw e; } // DataAccessPatterns.init(FenixFramework.config); initialized = true; } logger.info("Initialization of Fenix Framework is now complete."); } public static void initialize(MultiConfig configs) { synchronized (INIT_LOCK) { // look up current backend's name String currentBackEndName = BackEndId.getBackEndId().getBackEndName(); logger.debug("CurrentBackEndName = " + currentBackEndName); // get the correct config for the current backend Config config = configs.get(currentBackEndName); // initialize FenixFramework.initialize(config); } } public static Config getConfig() { if (config == null) { throw new ConfigError(ConfigError.MISSING_CONFIG); } return config; } /** * Gets the model of the domain classes. Should only be invoked after the framework is * initialized. * * @return The current {@link DomainModel} in use. * @throws ConfigError If this method is invoked before the framework is initialized or if a * {@link DmlCompilerException} occurs (only possible on first invocation). */ public static DomainModel getDomainModel() { return domainModel; } /** * Always gets a well-known singleton instance of {@link DomainRoot}. The intended use of this * instance is to provide a single entry point to the graph of {@link DomainObject}s. The user * of the framework may connect (via DML) any {@link DomainObject} to this class. */ public static DomainRoot getDomainRoot() { return getConfig().getBackEnd().getDomainRoot(); } /** * Get any {@link DomainObject} given its external identifier. * * The external identifier must have been obtained by a previous invocation to {@link * DomainObject#getExternalId}. If the external identifier is tampered with (in which case a * valid {@link DomainObject} cannot be found), the result of calling this method is undefined. * * @param externalId The external identifier of the domain object to get * @return The domain object requested * */ public static <T extends DomainObject> T getDomainObject(String externalId) { return getConfig().getBackEnd().getDomainObject(externalId); } public static TransactionManager getTransactionManager() { return getConfig().getBackEnd().getTransactionManager(); } public static Transaction getTransaction() { return getTransactionManager().getTransaction(); } /** * Inform the framework components that the application intends to shutdown. This allows for an * orderly termination of any running components. The default implementation delegates to the * backend the task of shutting down the framework. After invoking this method there is no * guarantee that the Fenix Framework is able to provide any more services. */ public static synchronized void shutdown() { if (barrier != null) { barrier.shutdown(); } getConfig().shutdown(); } private static synchronized NodeBarrier getNodeBarrier() throws Exception { //TODO: add jgroups configuration file to config if (barrier == null) { barrier = new NodeBarrier(getConfig().getJGroupsConfigFile()); } return barrier; } public static void barrier(String barrierName, int expectedMembers) throws Exception { getNodeBarrier().blockUntil(barrierName, expectedMembers); } }