/**
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the latest version of the GNU Lesser General
* Public License as published by the Free Software Foundation;
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program (LICENSE.txt); if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.jamwiki;
import info.bliki.gae.db.PropertyService;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.jamwiki.utils.SortedProperties;
import org.jamwiki.utils.Utilities;
import org.jamwiki.utils.WikiLogger;
/**
* The <code>Environment</code> class is instantiated as a singleton to provides
* access to JAMWiki property values stored in the
* <code>jamwiki.properties</code> file.
*/
public class Environment {
private static final WikiLogger logger = WikiLogger
.getLogger(Environment.class.getName());
public static final String PROP_BASE_COOKIE_EXPIRE = "cookie-expire";
public static final String PROP_BASE_DEFAULT_TOPIC = "default-topic";
public static final String PROP_BASE_FILE_DIR = "homeDir";
public static final String PROP_BASE_INITIALIZED = "props-initialized";
public static final String PROP_BASE_LOGO_IMAGE = "logo-image";
public static final String PROP_BASE_META_DESCRIPTION = "meta-description";
public static final String PROP_BASE_PERSISTENCE_TYPE = "persistenceType";
public static final String PROP_BASE_SEARCH_ENGINE = "search-engine";
public static final String PROP_BASE_WIKI_VERSION = "wiki-version";
public static final String PROP_CACHE_INDIVIDUAL_SIZE = "cache-individual-size";
public static final String PROP_CACHE_MAX_AGE = "cache-max-age";
public static final String PROP_CACHE_MAX_IDLE_AGE = "cache-max-idle-age";
public static final String PROP_CACHE_TOTAL_SIZE = "cache-total-size";
public static final String PROP_DB_DRIVER = "driver";
public static final String PROP_DB_PASSWORD = "db-password";
public static final String PROP_DB_TYPE = "database-type";
public static final String PROP_DB_URL = "url";
public static final String PROP_DB_USERNAME = "db-user";
public static final String PROP_DBCP_MAX_ACTIVE = "dbcp-max-active";
public static final String PROP_DBCP_MAX_IDLE = "dbcp-max-idle";
public static final String PROP_DBCP_MIN_EVICTABLE_IDLE_TIME = "dbcp-min-evictable-idle-time";
public static final String PROP_DBCP_NUM_TESTS_PER_EVICTION_RUN = "dbcp-num-tests-per-eviction-run";
public static final String PROP_DBCP_TEST_ON_BORROW = "dbcp-test-on-borrow";
public static final String PROP_DBCP_TEST_ON_RETURN = "dbcp-test-on-return";
public static final String PROP_DBCP_TEST_WHILE_IDLE = "dbcp-test-while-idle";
public static final String PROP_DBCP_TIME_BETWEEN_EVICTION_RUNS = "dbcp-time-between-eviction-runs";
public static final String PROP_DBCP_WHEN_EXHAUSTED_ACTION = "dbcp-when-exhausted-action";
public static final String PROP_EMAIL_REPLY_ADDRESS = "reply-address";
public static final String PROP_EMAIL_SMTP_HOST = "smtp-host";
public static final String PROP_EMAIL_SMTP_PASSWORD = "smtp-password";
public static final String PROP_EMAIL_SMTP_USERNAME = "smtp-username";
public static final String PROP_ENCRYPTION_ALGORITHM = "encryption-algorithm";
public static final String PROP_EXTERNAL_LINK_NEW_WINDOW = "external-link-new-window";
public static final String PROP_FILE_BLACKLIST = "file-blacklist";
public static final String PROP_FILE_BLACKLIST_TYPE = "file-blacklist-type";
public static final String PROP_FILE_DIR_FULL_PATH = "file-dir-full-path";
public static final String PROP_FILE_DIR_RELATIVE_PATH = "file-dir-relative-path";
public static final String PROP_FILE_MAX_FILE_SIZE = "max-file-size";
public static final String PROP_FILE_SERVER_URL = "file-server-url";
public static final String PROP_FILE_WHITELIST = "file-whitelist";
public static final String PROP_IMAGE_RESIZE_INCREMENT = "image-resize-increment";
public static final String PROP_MAX_TOPIC_VERSION_EXPORT = "max-topic-version-export";
public static final String PROP_PARSER_ALLOW_HTML = "allowHTML";
public static final String PROP_PARSER_ALLOW_JAVASCRIPT = "allow-javascript";
public static final String PROP_PARSER_ALLOW_TEMPLATES = "allow-templates";
public static final String PROP_PARSER_CLASS = "parser";
public static final String PROP_PARSER_SIGNATURE_DATE_PATTERN = "signature-date";
public static final String PROP_PARSER_SIGNATURE_USER_PATTERN = "signature-user";
public static final String PROP_PARSER_TOC = "allow-toc";
public static final String PROP_PARSER_TOC_DEPTH = "toc-depth";
public static final String PROP_PATTERN_INVALID_ROLE_NAME = "pattern-role-name-invalid";
public static final String PROP_PATTERN_INVALID_TOPIC_NAME = "pattern-topic-name-invalid";
public static final String PROP_PATTERN_VALID_USER_LOGIN = "pattern-login-valid";
public static final String PROP_PRINT_NEW_WINDOW = "print-new-window";
public static final String PROP_RECENT_CHANGES_NUM = "recent-changes-days";
public static final String PROP_RSS_ALLOWED = "rss-allowed";
public static final String PROP_RSS_TITLE = "rss-title";
public static final String PROP_SERVER_URL = "server-url";
public static final String PROP_SITE_NAME = "site-name";
public static final String PROP_TOPIC_EDITOR = "default-editor";
// FIXME - this property can be removed once the abilitity to upgrade to 0.6.0
// is removed
public static final String PROP_TOPIC_FORCE_USERNAME = "force-username";
// FIXME - this property can be removed once the abilitity to upgrade to 0.6.0
// is removed
public static final String PROP_TOPIC_NON_ADMIN_TOPIC_MOVE = "non-admin-redirect";
public static final String PROP_TOPIC_SPAM_FILTER = "use-spam-filter";
public static final String PROP_TOPIC_USE_PREVIEW = "use-preview";
public static final String PROP_TOPIC_USE_SHOW_CHANGES = "use-show-changes";
/* Lookup properties file location from system properties first. */
private static final String PROPERTY_FILE_NAME = System.getProperty(
"jamwiki.property.file", "jamwiki.properties");
private static Properties defaults = null;
private static Environment instance = null; // NOPMD instanciated and used
private static SortedProperties props = null;
// initialize the singleton instance
static {
instance = new Environment();
}
/**
* The constructor loads property values from the property file.
*/
private Environment() {
initDefaultProperties();
logger.fine("Default properties initialized: " + defaults.toString());
props = loadProperties(PROPERTY_FILE_NAME, defaults);
if ("true".equals(System.getProperty("jamwiki.override.file.properties"))) {
overrideFromSystemProperties();
}
logger.fine("JAMWiki properties initialized: " + props.toString());
}
/**
* Overrides file properties from system properties. Iterates over all
* properties and checks if application server has defined overriding
* property. System wide properties are prefixed with "jamwiki". These
* properties may be used to define dynamic runtime properties (eg. upload
* path depends on environment).
*/
private void overrideFromSystemProperties() {
logger.info("Overriding file properties with system properties.");
Enumeration properties = props.propertyNames();
while (properties.hasMoreElements()) {
String property = String.valueOf(properties.nextElement());
String value = System.getProperty("jamwiki." + property);
if (value != null) {
props.setProperty(property, value);
logger.info("Replaced property " + property + " with value: " + value);
}
}
}
/**
* Load a property file. First check for the file in the path from which the
* application was started, then check other classpath locations.
*
* @param filename
* The name of the property file to be loaded. This name can be
* either absolute or relative; if relative then the file will be
* loaded from the class path or from the directory from which the
* JVM was loaded.
* @return A File object containing the properties file instance.
* @throws FileNotFoundException
* Thrown if the specified property file cannot be located.
*/
private static File findProperties(String filename)
throws FileNotFoundException {
// read in properties file
File file = new File(filename);
if (file.exists()) {
return file; // NOPMD
}
// search for file in class loader path
return Environment.retrievePropertyFile(filename);
}
/**
* Initialize the default property values.
*/
private static void initDefaultProperties() {
defaults = new Properties();
defaults.setProperty(PROP_BASE_COOKIE_EXPIRE, "31104000");
defaults.setProperty(PROP_BASE_DEFAULT_TOPIC, "MainPage");
defaults.setProperty(PROP_BASE_FILE_DIR, "");
defaults.setProperty(PROP_BASE_INITIALIZED, Boolean.FALSE.toString());
defaults.setProperty(PROP_BASE_LOGO_IMAGE, "logo_oliver.gif");
defaults.setProperty(PROP_BASE_META_DESCRIPTION, "");
// defaults.setProperty(PROP_BASE_PERSISTENCE_TYPE,
// WikiBase.PERSISTENCE_INTERNAL);
// defaults.setProperty(PROP_BASE_SEARCH_ENGINE,
// SearchEngine.SEARCH_ENGINE_LUCENE);
defaults.setProperty(PROP_BASE_WIKI_VERSION, "0.0.0");
defaults.setProperty(PROP_CACHE_INDIVIDUAL_SIZE, "500");
defaults.setProperty(PROP_CACHE_MAX_AGE, "300");
defaults.setProperty(PROP_CACHE_MAX_IDLE_AGE, "150");
defaults.setProperty(PROP_CACHE_TOTAL_SIZE, "1000");
defaults.setProperty(PROP_DB_DRIVER, "");
defaults.setProperty(PROP_DB_PASSWORD, "");
defaults.setProperty(PROP_DB_TYPE, "");
defaults.setProperty(PROP_DB_URL, "");
defaults.setProperty(PROP_DB_USERNAME, "");
defaults.setProperty(PROP_DBCP_MAX_ACTIVE, "10");
defaults.setProperty(PROP_DBCP_MAX_IDLE, "3");
defaults.setProperty(PROP_DBCP_MIN_EVICTABLE_IDLE_TIME, "600");
defaults.setProperty(PROP_DBCP_NUM_TESTS_PER_EVICTION_RUN, "5");
defaults.setProperty(PROP_DBCP_TEST_ON_BORROW, Boolean.TRUE.toString());
defaults.setProperty(PROP_DBCP_TEST_ON_RETURN, Boolean.TRUE.toString());
defaults.setProperty(PROP_DBCP_TEST_WHILE_IDLE, Boolean.TRUE.toString());
defaults.setProperty(PROP_DBCP_TIME_BETWEEN_EVICTION_RUNS, "120");
defaults.setProperty(PROP_DBCP_WHEN_EXHAUSTED_ACTION, String
.valueOf(GenericObjectPool.WHEN_EXHAUSTED_GROW));
defaults.setProperty(PROP_EMAIL_REPLY_ADDRESS, "");
defaults.setProperty(PROP_EMAIL_SMTP_HOST, "");
defaults.setProperty(PROP_EMAIL_SMTP_PASSWORD, "");
defaults.setProperty(PROP_EMAIL_SMTP_USERNAME, "");
defaults.setProperty(PROP_ENCRYPTION_ALGORITHM, "SHA-512");
defaults.setProperty(PROP_EXTERNAL_LINK_NEW_WINDOW, Boolean.FALSE
.toString());
defaults.setProperty(PROP_FILE_BLACKLIST,
"bat,bin,exe,htm,html,js,jsp,php,sh");
// defaults.setProperty(PROP_FILE_BLACKLIST_TYPE,
// String.valueOf(WikiBase.UPLOAD_BLACKLIST));
defaults.setProperty(PROP_FILE_DIR_FULL_PATH, Environment
.retrieveDefaultUploadDirectory());
defaults.setProperty(PROP_FILE_DIR_RELATIVE_PATH, Environment
.retrieveDefaultRelativeUploadDirectory());
// size is in bytes
defaults.setProperty(PROP_FILE_MAX_FILE_SIZE, "2000000");
defaults.setProperty(PROP_FILE_SERVER_URL, "");
defaults.setProperty(PROP_FILE_WHITELIST,
"bmp,gif,jpeg,jpg,pdf,png,properties,svg,txt,zip");
defaults.setProperty(PROP_IMAGE_RESIZE_INCREMENT, "100");
defaults.setProperty(PROP_MAX_TOPIC_VERSION_EXPORT, "200");
defaults.setProperty(PROP_PARSER_ALLOW_HTML, Boolean.TRUE.toString());
defaults
.setProperty(PROP_PARSER_ALLOW_JAVASCRIPT, Boolean.FALSE.toString());
defaults.setProperty(PROP_PARSER_ALLOW_TEMPLATES, Boolean.TRUE.toString());
defaults.setProperty(PROP_PARSER_CLASS,
"org.jamwiki.parser.bliki.BlikiParser");
// "org.jamwiki.parser.jflex.JFlexParser");
defaults.setProperty(PROP_PARSER_SIGNATURE_DATE_PATTERN,
"dd-MMM-yyyy HH:mm zzz");
defaults.setProperty(PROP_PARSER_SIGNATURE_USER_PATTERN, "[[{0}|{4}]]");
defaults.setProperty(PROP_PARSER_TOC, Boolean.TRUE.toString());
defaults.setProperty(PROP_PARSER_TOC_DEPTH, "5");
defaults.setProperty(PROP_PATTERN_INVALID_ROLE_NAME, "([A-Za-z0-9_]+)");
defaults.setProperty(PROP_PATTERN_INVALID_TOPIC_NAME,
"([\\n\\r\\\\<>\\[\\]?#]+)");
defaults.setProperty(PROP_PATTERN_VALID_USER_LOGIN, "([A-Za-z0-9_]+)");
defaults.setProperty(PROP_PRINT_NEW_WINDOW, Boolean.FALSE.toString());
defaults.setProperty(PROP_RECENT_CHANGES_NUM, "100");
defaults.setProperty(PROP_RSS_ALLOWED, Boolean.TRUE.toString());
defaults.setProperty(PROP_RSS_TITLE, "Wiki Recent Changes");
defaults.setProperty(PROP_SERVER_URL, "");
defaults.setProperty(PROP_SITE_NAME, "JAMWiki");
// FIXME - hard coding
defaults.setProperty(PROP_TOPIC_EDITOR, "toolbar");
defaults.setProperty(PROP_TOPIC_SPAM_FILTER, Boolean.TRUE.toString());
defaults.setProperty(PROP_TOPIC_USE_PREVIEW, Boolean.TRUE.toString());
defaults.setProperty(PROP_TOPIC_USE_SHOW_CHANGES, Boolean.TRUE.toString());
}
/**
* Get the value of a boolean property. Returns <code>true</code> if the
* property is equal, ignoring case, to the string "true". Returns false in
* all other cases (eg: "false", "yes", "1")
*
* @param name
* The name of the property whose value is to be retrieved.
* @return The value of the property.
*/
public static boolean getBooleanValue(String name) {
return Boolean.valueOf(getValue(name));
}
/**
* Return an instance of the current properties object. The property instance
* returned should not be directly modified.
*
* @return Returns an instance of the current system properties.
*/
public static Properties getInstance() {
return props;
}
/**
* Get the value of an integer property.
*
* @param name
* The name of the property whose value is to be retrieved.
* @return The value of the property.
*/
public static int getIntValue(String name) {
int value = NumberUtils.toInt(getValue(name), -1);
if (value == -1) {
logger.warning("Invalid integer property " + name + " with value "
+ value);
}
// FIXME - should this otherwise indicate an invalid property?
return value;
}
/**
* Get the value of a long property.
*
* @param name
* The name of the property whose value is to be retrieved.
* @return The value of the property.
*/
public static long getLongValue(String name) {
long value = NumberUtils.toLong(getValue(name), -1);
if (value == -1) {
logger.warning("Invalid long property " + name + " with value " + value);
}
// FIXME - should this otherwise indicate an invalid property?
return value;
}
/**
* Returns the value of a property.
*
* @param name
* The name of the property whose value is to be retrieved.
* @return The value of the property.
*/
public static String getValue(String name) {
return props.getProperty(name);
}
/**
* Given a property file name, load the property file and return an object
* representing the property values.
*
* @param propertyFile
* The name of the property file to load.
* @return The loaded SortedProperties object.
*/
public static SortedProperties loadProperties(String propertyFile) {
return loadProperties(propertyFile, null);
}
/**
* Given a property file name, load the property file and return an object
* representing the property values.
*
* @param propertyFile
* The name of the property file to load.
* @param def
* Default property values, or <code>null</code> if there are no
* defaults.
* @return The loaded SortedProperties object.
*/
public static SortedProperties loadProperties(String propertyFile,
Properties def) {
SortedProperties properties = new SortedProperties();
if (def != null) {
properties = new SortedProperties(def);
}
File file = null;
FileInputStream fis = null;
try {
file = findProperties(propertyFile);
if (file == null) {
logger.warning("Property file " + propertyFile + " does not exist");
} else if (!file.exists()) {
logger.warning("Property file " + file.getPath() + " does not exist");
} else {
logger.config("Loading properties from " + file.getPath());
fis = new FileInputStream(file);
properties.load(fis);
}
} catch (IOException e) {
logger.severe("Failure while trying to load properties file "
+ file.getPath(), e);
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// NOPMD
}
}
}
properties.loadFromDatastore();
return properties;
}
/**
* Return the default relative upload directory (/context/upload/) as a
* String.
*
* @return The default relative upload directory (/context/upload/) as a
* String.
*/
private static String retrieveDefaultRelativeUploadDirectory() {
try {
return "/" + Utilities.getWebappRoot().getName() + "/upload/";
} catch (FileNotFoundException e) {
logger.severe(
"Failure while trying to retrieve default file upload directory", e);
}
return "";
}
/**
* Return the default upload directory (/webapp-root/upload/) as a String.
*
* @return The default upload directory (/webapp-root/upload/) as a String.
*/
private static String retrieveDefaultUploadDirectory() {
try {
return new File(Utilities.getWebappRoot(), "upload").getPath();
} catch (FileNotFoundException e) {
logger.severe(
"Failure while trying to retrieve default file upload directory", e);
}
return "";
}
/**
* Utility methods for retrieving property files from the class path, based on
* code from the org.apache.log4j.helpers.Loader class.
*
* @param filename
* Given a filename return a File object for the file. The filename
* may be relative to the class path or the directory from which the
* JVM was initialized.
* @return Returns a file representing the filename, or <code>null</code> if
* the file cannot be found.
*/
private static File retrievePropertyFile(String filename) {
File file = null;
try {
file = Utilities.getClassLoaderFile(filename);
return file;
} catch (FileNotFoundException e) {
// NOPMD file might not exist
}
try {
file = new File(Utilities.getClassLoaderRoot(), filename);
return file;
} catch (FileNotFoundException e) {
logger.severe("Error while searching for resource " + filename, e);
}
return null;
}
/**
* Save the current Wiki system properties to the filesystem.
*
* @throws IOException
* Thrown if the file cannot be found or if an I/O error occurs.
*/
public static void saveProperties() throws IOException {
Environment.saveProperties(PROPERTY_FILE_NAME, props, null);
}
/**
* Save the specified property values to the filesystem.
*
* @param propertyFile
* The name of the property file to save.
* @param properties
* The properties object that is to be saved.
* @param comments
* A comment to save in the properties file.
* @throws IOException
* Thrown if the file cannot be found or if an I/O error occurs.
*/
public static void saveProperties(String propertyFile,
SortedProperties properties, String comments) throws IOException {
PropertyService.saveAll(properties);
// File file = findProperties(propertyFile);
// FileOutputStream out = null;
// try {
// out = new FileOutputStream(file);
// properties.store(out, comments);
// } finally {
// if (out != null) {
// try {
// out.close();
// } catch (IOException e) {
// // NOPMD ignore, unimportant if a close fails
// }
// }
// }
}
/**
* Set a new boolean value for the given property name.
*
* @param name
* The name of the property whose value is to be set.
* @param value
* The value of the property being set.
*/
public static void setBooleanValue(String name, boolean value) {
props.setProperty(name, Boolean.toString(value));
}
/**
* Sets a new integer value for the given property name.
*
* @param name
* The name of the property whose value is to be set.
* @param value
* The value of the property being set.
*/
public static void setIntValue(String name, int value) {
props.setProperty(name, Integer.toString(value));
}
/**
* Sets a new value for the given property name.
*
* @param name
* The name of the property whose value is to be set.
* @param value
* The value of the property being set.
*/
public static void setValue(String name, String value) {
// it is invalid to set a property value null, so convert to empty string
if (value == null) {
value = "";
}
props.setProperty(name, value);
}
}