/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition 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 org.projectforge.core;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.log4j.PropertyConfigurator;
import org.dom4j.Element;
import org.projectforge.AppVersion;
import org.projectforge.calendar.ConfigureHoliday;
import org.projectforge.common.BeanHelper;
import org.projectforge.common.FileHelper;
import org.projectforge.common.StringHelper;
import org.projectforge.common.TimeNotation;
import org.projectforge.excel.ExportConfig;
import org.projectforge.fibu.kost.AccountingConfig;
import org.projectforge.jira.JiraConfig;
import org.projectforge.jira.JiraIssueType;
import org.projectforge.ldap.LdapConfig;
import org.projectforge.mail.MailAccountConfig;
import org.projectforge.mail.SendMailConfig;
import org.projectforge.orga.ContractType;
import org.projectforge.storage.StorageConfig;
import org.projectforge.user.LoginDefaultHandler;
import org.projectforge.web.MenuEntryConfig;
import org.projectforge.web.MenuItemDef;
import org.projectforge.web.WebConfig;
import org.projectforge.xml.stream.AliasMap;
import org.projectforge.xml.stream.XmlField;
import org.projectforge.xml.stream.XmlHelper;
import org.projectforge.xml.stream.XmlObject;
import org.projectforge.xml.stream.XmlObjectReader;
import org.projectforge.xml.stream.XmlObjectWriter;
import org.projectforge.xml.stream.XmlOmitField;
/**
* Configure ProjectForge via config.xml in the application's base dir.<br/>
* The config.xml will never re-read automatically. Please call the web admin page to force a re-read.
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
@XmlObject(alias = "config")
public class ConfigXml
{
private static final String SECRET_PROPERTY_STRING = "******";
// If change this, please change it also in EmbeddedJetty. If true then no log4j is initialized.
private static final String SYSTEM_PROPERTY_STANDALONE = "ProjectForge.standalone";
private static transient final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(ConfigXml.class);
private static transient final Set<String> nonExistingResources = new HashSet<String>();
private static transient final Set<String> existingResources = new HashSet<String>();
private static transient ConfigXml instance;
private static final String LOG4J_PROPERTY_FILE = "log4j.properties";
private static final String LOG4J_PROPERTY_SOURCE_FILE = "appHomeDir-log4j.properties";
private transient final List<ConfigurationListener> listeners = new ArrayList<ConfigurationListener>();
@XmlOmitField
private String applicationHomeDir;
@XmlOmitField
private String applicationsResourcePath;
private String resourceDir;
private JiraConfig jiraConfig;
private String jiraBrowseBaseUrl;
private SecurityConfig securityConfig;
private StorageConfig storageConfig;
private String telephoneSystemUrl;
private String telephoneSystemNumber;
private String telephoneSystemOperatorPanelUrl;
private String smsUrl;
@ConfigXmlSecretField
private String receiveSmsKey;
@ConfigXmlSecretField
private String phoneLookupKey;
private MailAccountConfig mebMailAccount = new MailAccountConfig();
private String currencySymbol;
@XmlField(asElement = true)
private Locale defaultLocale;
@XmlField(asElement = true)
private TimeNotation defaultTimeNotation;
@XmlField(asElement = true)
private int firstDayOfWeek = Calendar.MONDAY;
@XmlField(asElement = true)
private String excelDefaultPaperSize;
private List<ConfigureHoliday> holidays;
private List<ContractType> contractTypes;
private transient File configFile;
private String databaseDirectory;
private String loggingDirectory;
private String fontsDirectory;
private String workingDirectory;
private String tempDirectory;
private String servletContextPath;
private String domain;
private String logoFile;
private String keystoreFile;
@ConfigXmlSecretField
private String keystorePassphrase;
private String cronExpressionHourlyJob;
private String cronExpressionNightlyJob;
private String cronExpressionMebPollingJob;
private MenuEntryConfig menuConfig;
private WebConfig webConfig;
private boolean portletMode;
private AccountingConfig accountingConfig;
private LdapConfig ldapConfig;
private String loginHandlerClass;
/**
* Separated list of main classes (separated by white chars and or ',').
*/
String pluginMainClasses;
// Please note: If you change the name of this member field don't forget to change the PLUGIN_CONFIGS_FIELD_NAME below.
private List<ConfigurationData> plugins;
private static final String PLUGIN_CONFIGS_FIELD_NAME = "plugins";
private transient SSLSocketFactory usersSSLSocketFactory;
@XmlField(alias = "sendMail")
private SendMailConfig sendMailConfiguration;
public static ConfigXml getInstance()
{
if (instance == null) {
throw new IllegalStateException("Configuration is not yet configured");
}
return instance;
}
public static boolean isInitialized()
{
return instance != null;
}
private void reset()
{
resourceDir = "resources";
jiraConfig = null;
jiraBrowseBaseUrl = null;
telephoneSystemUrl = null;
telephoneSystemNumber = null;
telephoneSystemOperatorPanelUrl = null;
smsUrl = null;
receiveSmsKey = null;
phoneLookupKey = null;
mebMailAccount = new MailAccountConfig();
currencySymbol = "€";
defaultLocale = Locale.ENGLISH;
defaultTimeNotation = null;
firstDayOfWeek = Calendar.MONDAY;
setExcelDefaultPaperSize("DINA4");
holidays = null;
contractTypes = null;
databaseDirectory = "database";
loggingDirectory = "logs";
workingDirectory = "work";
fontsDirectory = resourceDir + File.separator + "fonts";
tempDirectory = "tmp";
servletContextPath = null;
domain = null;
logoFile = null;
keystoreFile = null;
keystorePassphrase = null;
cronExpressionHourlyJob = null;
cronExpressionNightlyJob = null;
cronExpressionMebPollingJob = null;
menuConfig = null;
webConfig = null;
sendMailConfiguration = new SendMailConfig();
accountingConfig = new AccountingConfig();
accountingConfig.reset();
ldapConfig = new LdapConfig();
}
protected ConfigXml()
{
reset();
}
private boolean ensureDir(final File dir)
{
if (dir.exists() == false) {
log.info("Creating directory " + dir);
dir.mkdir();
}
if (dir.canRead() == false) {
log.fatal("Can't create directory: " + dir);
return false;
}
return true;
}
/**
* Loads the configuration file config.xml from the application's home dir if given, otherwise the default values will be assumed.
* Constructor is used by Spring instantiation.
*/
public ConfigXml(final String applicationHomeDir)
{
this.applicationHomeDir = applicationHomeDir;
log.info("Using application home dir: " + applicationHomeDir);
System.setProperty("base.dir", applicationHomeDir); // Needed by log4j
final File dir = new File(this.applicationHomeDir);
final boolean status = ensureDir(dir);
if (status == true) {
if ("true".equals(System.getProperty(SYSTEM_PROPERTY_STANDALONE)) == true) {
log.info("Do not initialize log4j.properties. It's done by the standalone application of " + AppVersion.APP_ID + ".");
} else {
// Initialize log4j (not in standalone version):
final File log4j = new File(this.applicationHomeDir, LOG4J_PROPERTY_FILE);
if (log4j.canRead() == false) {
try {
log.info("Creating new log4j.properties in application's home dir: " + LOG4J_PROPERTY_FILE);
final ClassLoader cLoader = getClass().getClassLoader();
final InputStream is = cLoader.getResourceAsStream(LOG4J_PROPERTY_SOURCE_FILE);
FileUtils.copyInputStreamToFile(is, log4j);
} catch (final IOException ex) {
log.error("Exception encountered while copiing " + LOG4J_PROPERTY_FILE + ": " + ex, ex);
}
}
if (log4j.canRead() == true) {
log.info("Read log4j configuration: " + log4j.getAbsolutePath());
PropertyConfigurator.configure(log4j.getAbsolutePath());
}
}
readConfiguration();
this.databaseDirectory = FileHelper.getAbsolutePath(applicationHomeDir, this.databaseDirectory);
ensureDir(new File(databaseDirectory));
this.loggingDirectory = FileHelper.getAbsolutePath(applicationHomeDir, this.loggingDirectory);
ensureDir(new File(loggingDirectory));
this.workingDirectory = FileHelper.getAbsolutePath(applicationHomeDir, this.workingDirectory);
ensureDir(new File(workingDirectory));
this.resourceDir = FileHelper.getAbsolutePath(applicationHomeDir, this.resourceDir);
ensureDir(new File(resourceDir));
this.fontsDirectory = FileHelper.getAbsolutePath(applicationHomeDir, this.fontsDirectory);
ensureDir(new File(fontsDirectory));
this.tempDirectory = FileHelper.getAbsolutePath(applicationHomeDir, this.tempDirectory);
ensureDir(new File(tempDirectory));
}
setupKeyStores();
if (menuConfig != null) {
menuConfig.setParents();
}
instance = this;
}
public void register(final ConfigurationListener listener)
{
listeners.add(listener);
}
/**
* Reads the configuration file (can be called after any modification of the config file).
*/
public String readConfiguration()
{
reset();
configFile = new File(applicationHomeDir, "config.xml");
String msg = "";
if (configFile.canRead() == false) {
msg = "Cannot read from config file: '" + getConfigFilePath() + "'. OK, assuming default values.";
log.info(msg);
} else {
final XmlObjectReader reader = getReader();
String xml = null;
try {
xml = FileUtils.readFileToString(configFile, "UTF-8");
} catch (final IOException ex) {
msg = "Cannot read config file '" + getConfigFilePath() + "' properly: " + ex;
log.fatal(msg, ex);
}
if (xml != null) {
try {
final ConfigXml cfg = (ConfigXml) reader.read(xml);
final String warnings = reader.getWarnings();
copyDeclaredFields(null, this.getClass(), cfg, this);
if (this.excelDefaultPaperSize != null) {
setExcelDefaultPaperSize(excelDefaultPaperSize);
}
if (CollectionUtils.isNotEmpty(cfg.plugins) == true) {
for (final ConfigurationData srcData : cfg.plugins) {
final ConfigurationData destData = this.getPluginConfig(srcData.getClass());
copyDeclaredFields(destData.getClass().getName() + ".", srcData.getClass(), srcData, destData);
}
}
msg = "Config file '" + getConfigFilePath() + "' successfully read.";
if (warnings != null) {
msg += "\n" + warnings;
}
log.info(msg);
} catch (final Throwable ex) {
msg = "Cannot read config file '" + getConfigFilePath() + "' properly: " + ex;
log.fatal(msg, ex);
}
}
}
for (final ConfigurationListener listener : listeners) {
listener.afterRead();
}
return msg;
}
public String exportConfiguration()
{
final XmlObjectWriter writer = new XmlObjectWriter() {
@Override
protected boolean ignoreField(final Object obj, final Field field)
{
if (field.getDeclaringClass().isAssignableFrom(ConfigXml.class) == true
&& StringHelper.isIn(field.getName(), "expireTime", "timeOfLastRefresh") == true) {
return true;
}
return super.ignoreField(obj, field);
}
/**
* @see org.projectforge.xml.stream.XmlObjectWriter#writeField(java.lang.reflect.Field, java.lang.Object, java.lang.Object,
* org.projectforge.xml.stream.XmlField, org.dom4j.Element)
*/
@Override
protected void writeField(final Field field, final Object obj, final Object fieldValue, final XmlField annotation,
final Element element)
{
if (field != null) {
if (field.isAnnotationPresent(ConfigXmlSecretField.class) == true) {
super.writeField(field, obj, SECRET_PROPERTY_STRING, annotation, element);
return;
}
}
super.writeField(field, obj, fieldValue, annotation, element);
}
};
final String xml = writer.writeToXml(this, true);
return XmlHelper.XML_HEADER + xml;
}
private static XmlObjectReader getReader()
{
final XmlObjectReader reader = new XmlObjectReader();
final AliasMap aliasMap = new AliasMap();
reader.setAliasMap(aliasMap);
reader.initialize(ConfigXml.class);
reader.initialize(ConfigureHoliday.class);
reader.initialize(ContractType.class);
reader.initialize(JiraIssueType.class);
AccountingConfig.registerXmlObjects(reader, aliasMap);
return reader;
}
private void setupKeyStores()
{
if (getKeystoreFile() != null) {
try {
File keystoreFile = new File(getKeystoreFile());
if (keystoreFile.canRead() == false) {
keystoreFile = new File(applicationHomeDir, getKeystoreFile());
}
if (keystoreFile.canRead() == false) {
log.error("Can't read keystore file: " + getKeystoreFile());
return;
}
final InputStream is = new FileInputStream(keystoreFile);
usersSSLSocketFactory = createSSLSocketFactory(is, this.keystorePassphrase);
log.info("Keystore successfully read from file: " + keystoreFile.getAbsolutePath());
} catch (final Throwable ex) {
log.error("Could not initialize your key store (see error message below)!");
log.error(ex.getMessage(), ex);
}
}
}
private SSLSocketFactory createSSLSocketFactory(final InputStream is, final String passphrase) throws Exception
{
final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(is, passphrase.toCharArray());
is.close();
final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
final X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
final SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] { defaultTrustManager}, null);
return context.getSocketFactory();
}
/**
* For test cases.
* @param config
*/
static void internalSetInstance(final String config)
{
final XmlObjectReader reader = getReader();
final ConfigXml cfg = (ConfigXml) reader.read(config);
instance = new ConfigXml();
copyDeclaredFields(null, instance.getClass(), cfg, instance);
}
/**
* Copies only not null values of the configuration.
*/
private static void copyDeclaredFields(final String prefix, final Class< ? > srcClazz, final Object src, final Object dest,
final String... ignoreFields)
{
final Field[] fields = srcClazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (final Field field : fields) {
if (ignoreFields != null && ArrayUtils.contains(ignoreFields, field.getName()) == false && accept(field)) {
try {
final Object srcFieldValue = field.get(src);
if (srcFieldValue == null) {
// Do nothing
} else if (srcFieldValue instanceof ConfigurationData) {
final Object destFieldValue = field.get(dest);
Validate.notNull(destFieldValue);
final StringBuffer buf = new StringBuffer();
if (prefix != null) {
buf.append(prefix);
}
String alias = null;
if (field.isAnnotationPresent(XmlField.class)) {
final XmlField xmlFieldAnn = field.getAnnotation(XmlField.class);
if (xmlFieldAnn != null) {
alias = xmlFieldAnn.alias();
}
}
if (alias != null) {
buf.append(alias);
} else {
buf.append(field.getClass().getName());
}
buf.append(".");
copyDeclaredFields(buf.toString(), srcFieldValue.getClass(), srcFieldValue, destFieldValue, ignoreFields);
} else if (PLUGIN_CONFIGS_FIELD_NAME.equals(field.getName()) == true) {
// Do nothing.
} else {
field.set(dest, srcFieldValue);
if (field.isAnnotationPresent(ConfigXmlSecretField.class) == true) {
log.info(StringUtils.defaultString(prefix) + field.getName() + " = " + SECRET_PROPERTY_STRING);
} else {
log.info(StringUtils.defaultString(prefix) + field.getName() + " = " + srcFieldValue);
}
}
} catch (final IllegalAccessException ex) {
throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
}
}
}
final Class< ? > superClazz = srcClazz.getSuperclass();
if (superClazz != null) {
copyDeclaredFields(prefix, superClazz, src, dest, ignoreFields);
}
}
/**
* PLEASE NOTE: Don't forget to close the returned InputStream for avoiding leaked resources!!!<br>
* Tries to get the given filename from the application's resource dir (file system). If not exist, the input stream will be taken as
* resource input stream.
* @param filename Filename (can include relative path settings): "test.xsl", "fo-styles/doit.xsl".
* @return Object[2]: First value is the InputStream and second value is the url in external form.
*/
@SuppressWarnings("resource")
public Object[] getInputStream(final String filename)
{
InputStream is = null;
String path = null;
final File base = new File(getResourcePath());
if (base.isDirectory() == true) {
final File file = new File(base, filename);
if (file.exists() == false) {
showNonExistingMessage(file, false);
} else {
try {
is = new FileInputStream(file);
path = file.toURI().toString();
} catch (final FileNotFoundException ex) {
log.error(file.getAbsoluteFile() + ": " + ex.getMessage(), ex); // Should not occur.
is = null;
}
showExistingMessage(file, false);
}
}
if (is == null) {
final ClassLoader cLoader = getClass().getClassLoader();
final URL url = cLoader.getResource(filename);
if (url != null) {
path = url.toExternalForm();
}
is = cLoader.getResourceAsStream(filename);
}
if (is == null) {
log.error("File '" + filename + "' not found (wether in file system under '" + base.getAbsolutePath() + "' nor in resource!)");
}
final Object[] result = new Object[2];
result[0] = is;
result[1] = path;
return result;
}
/**
* Tries to get the given filename from the application's resource dir (file system). If not exist, the content will be taken as resource
* input stream. Calls getInputStream(filename) and converts input stream to String.
* @param filename Filename (can include relative path settings): "test.xsl", "fo-styles/doit.xsl".
* @return Object[2]: First value is the content as string and second value is the url in external form.
* @see #getInputStream(String)
*/
public Object[] getContent(final String filename)
{
final Object[] result = getInputStream(filename);
final InputStream is = (InputStream) result[0];
if (is != null) {
try {
result[0] = IOUtils.toString(is, "UTF-8");
} catch (final IOException ex) {
log.error(ex.getMessage(), ex);
} finally {
IOUtils.closeQuietly(is);
}
}
return result;
}
private static void showNonExistingMessage(final File file, final boolean directory)
{
// Synchronized not needed, for concurrent calls, output entries exist twice in the worst case.
if (nonExistingResources.contains(file.getAbsolutePath()) == false) {
nonExistingResources.add(file.getAbsolutePath());
existingResources.remove(file.getAbsolutePath()); // If changed by administrator during application running.
final String type = directory == true ? "directory" : "file";
log.info("Using default " + type + " of ProjectForge, because " + type + "'" + file.getAbsolutePath() + "' does not exist (OK)");
}
}
private static void showExistingMessage(final File file, final boolean directory)
{
// Synchronized not needed, for concurrent calls, output entries exist twice in the worst case.
if (existingResources.contains(file.getAbsolutePath()) == false) {
existingResources.add(file.getAbsolutePath());
nonExistingResources.remove(file.getAbsolutePath()); // If changed by administrator during application running.
final String type = directory == true ? "directory" : "file";
log.info("Using existing " + type + ":" + file.getAbsolutePath());
}
}
/**
* Returns whether or not to append the given <code>Field</code>.
* <ul>
* <li>Ignore transient fields
* <li>Ignore static fields
* <li>Ignore inner class fields</li>
* </ul>
*
* @param field The Field to test.
* @return Whether or not to consider the given <code>Field</code>.
*/
protected static boolean accept(final Field field)
{
if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) {
// Reject field from inner class.
return false;
}
if (Modifier.isTransient(field.getModifiers()) == true) {
// transients.
return false;
}
if (Modifier.isStatic(field.getModifiers()) == true) {
// transients.
return false;
}
return true;
}
/**
* Base url for linking JIRA issues: https://jira.acme.com/jira/browse/PROJECTFORGE-222. The issue name UPPERCASE_LETTERS-### will be
* appended to this url. ProjectForge parses the user's text input for [A-Z][A-Z0-9*]-[0-9]* and displays a list of detected JIRA-issues
* with a link beside the text area containing such issues.<br/>
* Example: https://jira.acme.com/jira/browse/ (don't forget closing '/'). <br/>
* If null then no text input will be parsed and no JIRA link will be displayed.
*/
public String getJiraBrowseBaseUrl()
{
return jiraBrowseBaseUrl;
}
/**
* FOR INTERNAL USE ONLY (tests). Please configure this value via config.xml.
* @param jiraBrowseBaseUrl
*/
public void setJiraBrowseBaseUrl(final String jiraBrowseBaseUrl)
{
this.jiraBrowseBaseUrl = jiraBrowseBaseUrl;
}
public JiraConfig getJiraConfig()
{
return jiraConfig;
}
/**
* @return true if a JIRA browse base url is given.
*/
public final boolean isJIRAConfigured()
{
return StringUtils.isNotBlank(getJiraBrowseBaseUrl());
}
/**
* @return the securityConfig
*/
public SecurityConfig getSecurityConfig()
{
return securityConfig;
}
public boolean isSecurityConfigured()
{
return securityConfig != null && StringUtils.isNotBlank(securityConfig.getPasswordPepper());
}
/**
* @return the storageConfig
*/
public StorageConfig getStorageConfig()
{
return storageConfig;
}
public boolean isStorageConfigured()
{
return storageConfig != null && StringUtils.isNotBlank(storageConfig.getAuthenticationToken());
}
/**
* Format http://asterisk.acme.com/originatecall.php?source=#source&target=#target<br/>
* #source will be replaced by the current user's phone and #target by the chosen phone number to call.
*/
public String getTelephoneSystemUrl()
{
return telephoneSystemUrl;
}
/**
* For direct calls all numbers beginning with the this number will be stripped, e. g. for 0561316793: 056131679323 -> 23. So internal
* calls are supported.
*/
public String getTelephoneSystemNumber()
{
return telephoneSystemNumber;
}
public boolean isTelephoneSystemUrlConfigured()
{
return StringUtils.isNotEmpty(this.telephoneSystemUrl);
}
public String getTelephoneSystemOperatorPanelUrl()
{
return telephoneSystemOperatorPanelUrl;
}
/**
* Format "http://asterisk.acme.com/sms.php?number=#number&text=#text".<br/>
* #number will be replaced by the chosen mobile phone number and #text by the sms text (url encoded).
*/
public String getSmsUrl()
{
return smsUrl;
}
public boolean isSmsConfigured()
{
return StringUtils.isNotEmpty(smsUrl);
}
/**
* The SMS receiver verifies this key given as get parameter to the servlet call. <br/>
* The key should be an alpha numeric random value with at least 6 characters for security reasons.
*/
public String getReceiveSmsKey()
{
return receiveSmsKey;
}
/**
* The reverse phone lookup service verifies the key given as parameter to the servlet call against this key. The key should be an alpha
* numeric random value with at least 6 characters for security reasons.
* @return the receivePhoneLookupKey
*/
public String getPhoneLookupKey()
{
return phoneLookupKey;
}
/**
* The mail account for receiving mobile blogging entries by mail.
* @return
*/
public MailAccountConfig getMebMailAccount()
{
return mebMailAccount;
}
/**
* @return true if meb mail account with hostname is configured, otherwise false.
*/
public boolean isMebMailAccountConfigured()
{
return this.mebMailAccount != null && this.mebMailAccount.getHostname() != null;
}
/**
* The currency symbol of ProjectForge. ProjectForge supports currently one currency for the whole application. <br/>
* Please note: The deprecated stripes action only works with "€".
* @return the application wide currency symbol, e. g. "€".
*/
public String getCurrencySymbol()
{
return currencySymbol;
}
/**
* The default locale is currently used for getting the week of year in Calendar.
*/
public Locale getDefaultLocale()
{
return defaultLocale;
}
/**
* The default time notation (12-hour or 24-hour). This notation is used, if the user has not chosen his personal time notation. Default
* is 24-hour for locales starting with "de" (German), otherwise 12-hour.
*/
public TimeNotation getDefaultTimeNotation()
{
return defaultTimeNotation;
}
/**
* The default first day of week (1 - Sunday, 2 - Monday, ...)
* @return the firstDayOfWeek
*/
public int getFirstDayOfWeek()
{
return firstDayOfWeek;
}
public void setExcelDefaultPaperSize(final String excelDefaultPaperSize)
{
this.excelDefaultPaperSize = excelDefaultPaperSize;
ExportConfig.getInstance().setDefaultPaperSize(excelDefaultPaperSize);
}
/** ProjectForges home dir (for resources, images, configuration etc.). */
public String getApplicationHomeDir()
{
return applicationHomeDir;
}
/**
* Resource directory relative to application's home (default 'resources').
*/
public String getResourceDir()
{
return resourceDir;
}
/**
* Absolute path of resource directory (default '<app-home>/resources').
*/
public String getResourcePath()
{
if (this.applicationsResourcePath == null) {
final File file;
if (new File(resourceDir).isAbsolute() == true) {
file = new File(resourceDir);
} else {
file = new File(applicationHomeDir, resourceDir);
}
this.applicationsResourcePath = file.getAbsolutePath();
}
return applicationsResourcePath;
}
/**
* @return the databaseDirectory
*/
public String getDatabaseDirectory()
{
return databaseDirectory;
}
/**
* @param databaseDirectory the databaseDirectory to set absolute or relative to the application's home dir.
* @return this for chaining.
*/
public void setDatabaseDirectory(final String databaseDirectory)
{
this.databaseDirectory = databaseDirectory;
}
/**
* @return the loggingDirectory
*/
public String getLoggingDirectory()
{
return loggingDirectory;
}
/**
* @param loggingDirectory the loggingDirectory to set absolute or relative to the application's home dir.
* @return this for chaining.
*/
public void setLoggingDirectory(final String loggingDirectory)
{
this.loggingDirectory = loggingDirectory;
}
/**
* This directory is used for e. g. storing uploaded files. The absolute path will be returned. <br/>
* Default value: "work"
* @see #setWorkingDirectory(String)
*/
public String getWorkingDirectory()
{
return workingDirectory;
}
/**
* Sets the working dir as relative sub directory of the application's home dir or the absolute path if given.
* @param workingDirectory
*/
public void setWorkingDirectory(final String workingDirectory)
{
this.workingDirectory = workingDirectory;
}
/**
* Default value: "resources/fonts" (absolute path).
* @return the fontsDirectory
*/
public String getFontsDirectory()
{
return fontsDirectory;
}
/**
* @param fontsDirectory the fontsDirectory to set
* @return this for chaining.
*/
public void setFontsDirectory(final String fontsDirectory)
{
this.fontsDirectory = fontsDirectory;
}
/**
* This directory is used e. g. by the ImageCropper. The absolute path will be returned. <br/>
* Default value: "tmp"
* @see #setWorkingDirectory(String)
*/
public String getTempDirectory()
{
return tempDirectory;
}
/**
* Sets the temporary dir as relative sub directory of the application's home dir or the absolute path if given. This directory is used by
* ProjectForge to save temporary files such as images from the ImageCropper.
* @param tempDirectory
*/
public void setTempDirectory(final String tempDirectory)
{
this.tempDirectory = tempDirectory;
}
public String getConfigFilePath()
{
return configFile.getPath();
}
/**
* @return true if at least a send mail host is given, otherwise false.
*/
public boolean isSendMailConfigured()
{
return sendMailConfiguration != null && StringUtils.isNotBlank(sendMailConfiguration.getHost()) == true;
}
public SendMailConfig getSendMailConfiguration()
{
return sendMailConfiguration;
}
/**
* The servlet's context path, "/ProjectForge" at default. You should configure another context path such as "/" if the ProjectForge app
* runs in another context, such as root context.
*/
public String getServletContextPath()
{
return servletContextPath;
}
public void setServletContextPath(final String servletContextPath)
{
this.servletContextPath = servletContextPath;
}
/**
* Only given, if the administrator have configured this domain. Otherwise e. g. the ImageCropper uses
* req.getHttpServletRequest().getScheme() + "://" + req.getHttpServletRequest().getLocalName() + ":" +
* req.getHttpServletRequest().getLocalPort()
* @return domain (host) in form https://www.acme.de:8443/
*/
public String getDomain()
{
return domain;
}
public void setDomain(final String domain)
{
this.domain = domain;
}
/**
* If configured then this logo file is used for displaying at the top of the navigation menu.
* @return The path of the configured logo (relative to the image dir of the application's resource path, at default:
* '<app-home>/resources/images').
* @see #getResourcePath()
*/
public String getLogoFile()
{
return logoFile;
}
public List<ConfigureHoliday> getHolidays()
{
return holidays;
}
public List<ContractType> getContractTypes()
{
return contractTypes;
}
public SSLSocketFactory getUsersSSLSocketFactory()
{
return usersSSLSocketFactory;
}
/**
* Here you can define a list of main classes of type AbstractPlugin. These classes will be initialized on startup. Multiple entries
* should be separated by white chars and/or ','.
* @return
*/
public String[] getPluginMainClasses()
{
return StringUtils.split(pluginMainClasses, " \r\n\t,");
}
/**
* If no such plugin config exist, a new instance is created and returned.
* @return the pluginConfigs
*/
public ConfigurationData getPluginConfig(final Class< ? extends ConfigurationData> configClass)
{
if (plugins == null) {
plugins = new ArrayList<ConfigurationData>();
} else {
for (final ConfigurationData configData : plugins) {
if (configData != null && configClass.isAssignableFrom(configData.getClass()) == true) {
return configData;
}
}
}
final ConfigurationData config = (ConfigurationData) BeanHelper.newInstance(configClass);
plugins.add(config);
return config;
}
/**
* For additional certificates you can set the file name of the jssecert file in your ProjectForge home (config) directory (path of your
* confix.xml). <br/>
* If given then the key-store file is used.
*/
public String getKeystoreFile()
{
return keystoreFile;
}
/**
* For overwriting the default settings.<br/>
* Format for hourly *:00 is (see Quartz documentation for further information) "0 0 * * * ?"
*/
public String getCronExpressionHourlyJob()
{
return cronExpressionHourlyJob;
}
/**
* For overwriting the default settings.<br/>
* Format for nightly at 2:30 AM (UTC) is (see Quartz documentation for further information) "0 30 2 * * ?"
*/
public String getCronExpressionNightlyJob()
{
return cronExpressionNightlyJob;
}
/**
* For overwriting the settings of applicationContext-web.xml.<br/>
* Format for every 10 minutes (5, 15, 25, ...) is (see Quartz documentation for further information) "0 5/10 * * * ?"
*/
public String getCronExpressionMebPollingJob()
{
return cronExpressionMebPollingJob;
}
/**
* If given then this login handler will be used instead of {@link LoginDefaultHandler}. For ldap please use e. g.
* org.projectforge.ldap.LdapLoginHandler.
* @return the loginHandlerClass
*/
public String getLoginHandlerClass()
{
return loginHandlerClass;
}
/**
* Here you can add menu entries to be hidden or can build your own menu tree or just modify the existing one. If you don't configure this
* element, you will receive the standard ProjectForge menu containing all menu entries which are available for the system and the user. <br/>
* Please note: ProjectForge assures, that only such menu entries are visible, to which the user has the access to (independant from your
* definitions here)! <br/>
* If you want to make a menu entry invisible, you can add this to this root element like this:<br/>
*
* <pre>
* <menu-entry id="DEVELOPER_DOC" visible="false"/>
* <br/>
* See all the predefined id's here: {@link MenuItemDef}
* <br/>
* This root element will not be shown.
*/
public MenuEntryConfig getMenuConfig()
{
return menuConfig;
}
/**
* @return the webConfig
* @see WebConfig
*/
public WebConfig getWebConfig()
{
return webConfig;
}
/**
* Experimental and undocumented setting.
*/
public boolean isPortletMode()
{
return portletMode;
}
/**
* @return the accountingConfig
*/
public AccountingConfig getAccountingConfig()
{
return accountingConfig;
}
/**
* @return the ldapConfig
*/
public LdapConfig getLdapConfig()
{
return ldapConfig;
}
/**
* @param ldapConfig the ldapConfig to set
* @return this for chaining.
*/
public void setLdapConfig(final LdapConfig ldapConfig)
{
this.ldapConfig = ldapConfig;
}
/**
* Replaces field values with annotation {@link ConfigXmlSecretField} by "******".
* @param configObject
* @return String representation of the given object.
* @see ReflectionToStringBuilder#ReflectionToStringBuilder(Object)
*/
public static String toString(final Object configObject)
{
return new ReflectionToStringBuilder(configObject) {
@Override
protected Object getValue(final Field field) throws IllegalArgumentException, IllegalAccessException
{
if (field.isAnnotationPresent(ConfigXmlSecretField.class) == true) {
return SECRET_PROPERTY_STRING;
}
return super.getValue(field);
};
}.toString();
}
}