package pt.ist.fenixframework;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import pt.ist.fenixframework.pstm.dml.FenixDomainModel;
/**
* An instance of the <code>Config</code> class bundles together the
* initialization parameters used by the Fenix Framework. Therefore, before
* initializing the framework (via the call to the
* <code>FenixFramework.initialize(Config)</code> method), the programmer should
* create an instance of <code>Config</code> with the correct values for each of
* the parameters.
*
* No constructor is provided for this class (other than the default
* constructor), because the <code>Config</code> class has several parameters,
* some of which are optional. But, whereas optional parameters do not need to
* be specified, the parameters that are required must be specified by the
* programmer before calling the <code>FenixFramework.initialize</code> method.
*
* To create an instance of this class with the proper values for its
* parameters, programmers should generally use code like this:
*
* <blockquote>
*
* <pre>
*
* Config config = new Config() {
* {
* this.appName = "MyAppName";
* this.domainModelPath = "path/to/domain.dml";
* this.dbAlias = "//somehost:3306/databaseName";
* this.dbUsername = "dbuser";
* this.dbPassword = "dpPass";
* }
* };
*
* </pre>
*
* </blockquote>
*
* Note the use of the double
*
* <pre>
* { {} }
* </pre>
*
* to delimit an instance initializer block for the anonymous inner class being
* created.
*
* Each of the parameters of the <code>Config</code> class is represented as a
* protected class field. Look at the documentation of each field to see what is
* it for, whether it is optional or required, and in the former case, what is
* its default value.
*
* The current set of required parameters are the following:
* <ul>
* <li>domainModelPath XOR domainModelPaths</li>
* <li>dbAlias</li>
* <li>dbUsername</li>
* <li>dbPassword</li>
* </ul>
*/
public class Config {
/**
* This <strong>required</strong> parameter specifies the path to the file
* (or resource) containing the DML code that corresponds to the domain
* model of the application. A non-null value must be specified for this
* parameter (or else, to the domainModelPaths, in case more than one DML
* file is used by the project).
*/
protected String domainModelPath = null;
/**
* This <strong>required</strong> parameter specifies the path to each file
* (or resource) containing the DML code that corresponds to the domain
* model of the application. A non-null value must be specified for this
* parameter (or else, to the domainModelPath parameter).
*/
protected String[] domainModelPaths = null;
/**
* This <strong>required</strong> parameter specifies the JDBC alias that
* will be used to access the database where domain entities are stored. The
* value of this parameter should not contain neither the protocol, nor the
* sub-protocol, but may contain any parameters that configure the
* connection to the MySQL database (e.g., a possible value for this
* parameter is
* "//localhost:3306/mydb?useUnicode=true&characterEncoding=latin1"). A
* non-null value must be specified for this parameter.
*/
protected String dbAlias = null;
/**
* This <strong>required</strong> parameter specifies the username that will
* be used to access the database where domain entities are stored. A
* non-null value must be specified for this parameter.
*/
protected String dbUsername = null;
/**
* This <strong>required</strong> parameter specifies the password that will
* be used to access the database where domain entities are stored. A
* non-null value must be specified for this parameter.
*/
protected String dbPassword = null;
/**
* This <strong>optional</strong> parameter specifies a name for the
* application that will be used by the framework in the statistical logs
* performed during the application execution. The default value for this
* parameter is <code>null</code>.
*/
protected String appName = null;
/**
* This <strong>optional</strong> parameter specifies whether an error
* should be thrown if during a transaction an object that was deleted
* during the transaction is subsequently changed. The default value of
* <code>true</code> will cause an <code>Error</code> to be thrown, whereas
* a value of <code>false</code> will cause only a warning to be issued.
*/
protected boolean errorIfChangingDeletedObject = true;
/**
* This <strong>optional</strong> parameter specifies whether the framework
* should initialize the persistent store if it detects that the store was
* not properly initialized (e.g., the database has no tables). The default
* value for this parameter is <code>true</code>, but a programmer may want
* to specify a value of <code>false</code>, if she wants to have control
* over the initialization of the persistent store. In this latter case,
* however, if the store is not properly initialized before initializing the
* framework, probably a runtime exception will be thrown during the
* framework initialization.
*/
protected boolean createRepositoryStructureIfNotExists = true;
/**
* This <strong>optional</strong> parameter indicates whether the database
* structure should be automatically updated with missing structure entries
* when the framework is initialized. Defaults to false. This is only
* relevant if some database structure already exists.
*/
protected boolean updateRepositoryStructureIfNeeded = false;
/**
* This <strong>optional</strong> parameter indicates the class of the Root
* object. If specified, the framework will initialize and persist an
* instance of this class whenever the application starts, if one does not
* exist already. Moreover, after initilization, the persistent instance of
* this class may be accessed using the FenixFramework.getRoot() method.
*/
protected Class rootClass = null;
/**
* This <strong>optional</strong> parameter indicates which subclass of
* FenixDomainModel should be created by the DmlCompiler when parsing a DML
* file.
*/
protected Class<? extends FenixDomainModel> domainModelClass = FenixDomainModel.class;
/**
* This <strong>optional</strong> parameter indicates whether the framework
* should collect information about the data-access patterns of the
* application.
*/
protected boolean collectDataAccessPatterns = false;
/**
* This <strong>optional</strong> parameter indicates where the framework
* will store the collected information about the data-access patterns of
* the application. Must end with a path separator character.
*/
protected String collectDataAccessPatternsPath = "";
/**
* This <strong>optional</strong> parameter indicates whether the framework
* should throw an exception when a DomainObject that is still connected to
* other objects is trying to be deleted or rather delete it.
*/
protected boolean errorfIfDeletingObjectNotDisconnected = false;
/**
* This <strong>optional</strong> parameter indicates whether the framework
* will load on startup additional plugins.
*/
protected FenixFrameworkPlugin[] plugins;
private static void checkRequired(Object obj, String fieldName) {
if (obj == null) {
missingRequired(fieldName);
}
}
private static void missingRequired(String fieldName) {
throw new Error("The required field '" + fieldName + "' was not specified in the FenixFramework config.");
}
private void insertConnectionEncoding(String encoding) {
StringBuilder encodingParams = new StringBuilder();
encodingParams.append("useUnicode=true&characterEncoding=" + encoding + "&clobCharacterEncoding=" + encoding
+ "&characterSetResults=" + encoding);
int questionMarkIndex = this.dbAlias.indexOf('?');
if (questionMarkIndex == -1) { // no parameters yet
this.dbAlias = this.dbAlias + '?' + encodingParams;
} else {
String prefix = this.dbAlias.substring(0, questionMarkIndex + 1); // include
// the
// question
// mark
String rest = this.dbAlias.substring(questionMarkIndex + 1);
this.dbAlias = prefix + encodingParams + '&' + rest;
}
}
public void checkConfig() {
if ((domainModelPath == null) && (domainModelPaths == null)) {
missingRequired("domainModelPath or domainModelPaths");
}
if ((domainModelPath != null) && (domainModelPaths != null)) {
throw new Error("It is not possible to specify both the 'domainModelPath' "
+ "and 'domainModelPaths' parameters in the FenixFramework config.");
}
checkRequired(dbAlias, "dbAlias");
checkRequired(dbUsername, "dbUsername");
checkRequired(dbPassword, "dbPassword");
checkRequired(domainModelClass, "domainModelClass");
insertConnectionEncoding("UTF-8");
}
public String[] getDomainModelPaths() {
// either domainModelPaths or domainModelPath is null
return (domainModelPath != null) ? new String[] { domainModelPath } : domainModelPaths;
}
public List<URL> getDomainModelURLs() {
final List<URL> urls = new ArrayList<URL>();
for (final String domainModelPath : getDomainModelPaths()) {
URL url = this.getClass().getResource(domainModelPath);
if (url == null) {
try {
url = new File(domainModelPath).toURI().toURL();
} catch (MalformedURLException mue) {
throw new Error("FenixFramework config error: wrong domainModelPath '" + domainModelPath + "'");
}
}
urls.add(url);
}
return urls;
}
public String getDbAlias() {
return dbAlias;
}
public String getDbUsername() {
return dbUsername;
}
public String getDbPassword() {
return dbPassword;
}
public String getAppName() {
return appName;
}
public boolean isErrorIfChangingDeletedObject() {
return errorIfChangingDeletedObject;
}
public boolean isErrorfIfDeletingObjectNotDisconnected() {
return errorfIfDeletingObjectNotDisconnected;
}
public boolean getCreateRepositoryStructureIfNotExists() {
return createRepositoryStructureIfNotExists;
}
public boolean getUpdateRepositoryStructureIfNeeded() {
return updateRepositoryStructureIfNeeded;
}
public Class getRootClass() {
return rootClass;
}
public boolean getCollectDataAccessPatterns() {
return collectDataAccessPatterns;
}
public Class<? extends FenixDomainModel> getDomainModelClass() {
return domainModelClass;
}
public FenixFrameworkPlugin[] getPlugins() {
return plugins;
}
public String getCollectDataAccessPatternsPath() {
return collectDataAccessPatternsPath;
}
}