com.scooterframework.admin.EnvConfig.java Source code

Java tutorial

Introduction

Here is the source code for com.scooterframework.admin.EnvConfig.java

Source

/*
 *   This software is distributed under the terms of the FSF
 *   Gnu Lesser General Public License (see lgpl.txt).
 *
 *   This program is distributed WITHOUT ANY WARRANTY. See the
 *   GNU General Public License for more details.
 */
package com.scooterframework.admin;

import java.io.File;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import com.scooterframework.common.logging.LogUtil;
import com.scooterframework.common.util.Converters;
import com.scooterframework.common.util.PropertyFileUtil;
import com.scooterframework.common.util.WordUtil;
import com.scooterframework.orm.activerecord.ActiveRecord;
import com.scooterframework.orm.activerecord.ActiveRecordUtil;
import com.scooterframework.orm.sqldataexpress.config.DatabaseConfig;
import com.scooterframework.web.controller.ActionContext;
import com.scooterframework.web.controller.NoViewFileException;

/**
 * EnvConfig class configures the application during startup time.
 * 
 * <p>
 * You can add more mappings by using the <tt>additional.mimetypes</tt> 
 * property in the <tt>environment.properties</tt> file. Or you can create 
 * your own content handler plugin by extending the <tt>Plugin</tt> class 
 * and implementing the <tt>ContentHandler</tt> interface.
 *
 * @author (Fei) John Chen
 */
public class EnvConfig implements Observer {
    private static LogUtil log = LogUtil.getLogger(EnvConfig.class.getName());

    public static final String EXTENSION_MIMETYPES_RESOURCE = "/com/scooterframework/admin/extension_mimetypes.properties";
    public static final String DATA_PROPERTIES_FILE = "environment.properties";
    public static final String DEFAULT_VALUE_defaultViewFilesDirectory = "builtin/crud";

    //****************************************************
    // Default values for keys in the properties file
    //****************************************************
    public static final String DEFAULT_VALUE_siteAdminUsername = null;
    public static final String DEFAULT_VALUE_siteAdminPassword = null;
    public static final String DEFAULT_VALUE_modelClassPrefix = "models";
    public static final String DEFAULT_VALUE_modelClassSuffix = "";
    public static final String DEFAULT_VALUE_controllerClassPrefix = "controllers";
    public static final String DEFAULT_VALUE_controllerClassSuffix = "Controller";
    public static final String DEFAULT_VALUE_allowDefaultControllerClass = "false";
    public static final String DEFAULT_VALUE_actionExtension = "";
    public static final String DEFAULT_VALUE_defaultActionMethod = "index";
    public static final String DEFAULT_VALUE_allowDefaultActionMethod = "true";
    public static final String DEFAULT_VALUE_webPageDirectoryName = "WEB-INF/views";
    public static final String DEFAULT_VALUE_allowForwardToControllerNameViewWhenControllerNotExist = "true";
    public static final String DEFAULT_VALUE_allowForwardToActionNameViewWhenActionNotExist = "true";
    public static final String DEFAULT_VALUE_runningEnvironment = Constants.RUNNING_ENVIRONMENT_DEVELOPMENT;
    public static final String DEFAULT_VALUE_passwordScheme = "";
    public static final String DEFAULT_VALUE_benchmark = "true";
    public static final String DEFAULT_VALUE_benchmarkInHeader = "true";
    public static final String DEFAULT_VALUE_locale_language = null;
    public static final String DEFAULT_VALUE_locale_country = null;
    public static final String DEFAULT_VALUE_locale_variant = null;
    public static final Locale DEFAULT_VALUE_gloabalLocale = null;
    public static final String DEFAULT_VALUE_messageResourcesFileBase = "messages";
    public static final String DEFAULT_VALUE_viewExtension = "jsp";
    public static final String DEFAULT_VALUE_rootURL = "/WEB-INF/views/index.jsp";
    public static final String DEFAULT_VALUE_errorPageURI = "/WEB-INF/views/error.jsp";
    public static final String DEFAULT_VALUE_compileErrorPageURI = "/WEB-INF/views/compileError.jsp";
    public static final String DEFAULT_VALUE_allowDisplayingErrorDetails = "true";
    public static final String DEFAULT_VALUE_allowDataBrowser = "true";
    public static final String DEFAULT_VALUE_additionalMimeTypes = "";
    public static final String DEFAULT_VALUE_additionalSinglePlural = "";
    public static final String DEFAULT_VALUE_defaultCacheProvider = null;
    public static final String DEFAULT_VALUE_defaultCacheName = ApplicationConfig.getInstance().getContextName();
    public static final String DEFAULT_VALUE_useRequestCache = "true";
    public static final String DEFAULT_VALUE_useSecondLevelCache = "false";
    public static final String DEFAULT_VALUE_flushCacheOnChange = "true";
    public static final String DEFAULT_VALUE_localUseCacheExceptions = null;
    public static final String DEFAULT_VALUE_localFlushCacheExceptions = null;
    public static final String DEFAULT_VALUE_allowCacheAssociatedObjects = "false";
    public static final String DEFAULT_VALUE_upload_file_repository = null;
    public static final String DEFAULT_VALUE_maximum_bytes_per_uploaded_file_in_memory = "10240";
    public static final String DEFAULT_VALUE_maximum_total_bytes_per_upload_request = "-1";
    public static final String DEFAULT_VALUE_maximum_bytes_per_uploaded_file = "-1";

    private static EnvConfig me;
    private Properties appProperties = null;
    private static Map<String, String> defaultMimeTypesMap = new HashMap<String, String>();

    private String siteAdminUsername = DEFAULT_VALUE_siteAdminUsername;
    private String siteAdminPassword = DEFAULT_VALUE_siteAdminPassword;
    private String modelClassPrefix = DEFAULT_VALUE_modelClassPrefix;
    private String modelClassSuffix = DEFAULT_VALUE_modelClassSuffix;
    private String controllerClassPrefix = DEFAULT_VALUE_controllerClassPrefix;
    private String controllerClassSuffix = DEFAULT_VALUE_controllerClassSuffix;
    private String allowDefaultControllerClass = DEFAULT_VALUE_allowDefaultControllerClass;
    private String defaultActionMethod = DEFAULT_VALUE_defaultActionMethod;
    private String allowDefaultActionMethod = DEFAULT_VALUE_allowDefaultActionMethod;
    private String webPageDirectoryName = DEFAULT_VALUE_webPageDirectoryName;
    private String allowForwardToControllerNameViewWhenControllerNotExist = DEFAULT_VALUE_allowForwardToControllerNameViewWhenControllerNotExist;
    private String allowForwardToActionNameViewWhenActionNotExist = DEFAULT_VALUE_allowForwardToActionNameViewWhenActionNotExist;
    private String runningEnvironment = DEFAULT_VALUE_runningEnvironment;
    private String passwordScheme = DEFAULT_VALUE_passwordScheme;
    private String benchmark = DEFAULT_VALUE_benchmark;
    private String benchmarkInHeader = DEFAULT_VALUE_benchmarkInHeader;
    private Locale gloabalLocale = DEFAULT_VALUE_gloabalLocale;
    private String messageResourcesFileBase = DEFAULT_VALUE_messageResourcesFileBase;
    private String actionExtension = DEFAULT_VALUE_actionExtension;
    private String viewExtension = DEFAULT_VALUE_viewExtension;
    private String rootURL = DEFAULT_VALUE_rootURL;
    private String errorPageURI = DEFAULT_VALUE_errorPageURI;
    private String compileErrorPageURI = DEFAULT_VALUE_compileErrorPageURI;
    private String allowDisplayingErrorDetails = DEFAULT_VALUE_allowDisplayingErrorDetails;
    private String allowDataBrowser = DEFAULT_VALUE_allowDataBrowser;
    private String additionalMimeTypes = DEFAULT_VALUE_additionalMimeTypes;
    private String additionalSinglePlural = DEFAULT_VALUE_additionalSinglePlural;
    private String defaultCacheProvider = DEFAULT_VALUE_defaultCacheProvider;
    private String defaultCacheName = DEFAULT_VALUE_defaultCacheName;
    private String uploadFileRepository = DEFAULT_VALUE_upload_file_repository;
    private String useRequestCache = DEFAULT_VALUE_useRequestCache;
    private String useSecondLevelCache = DEFAULT_VALUE_useSecondLevelCache;
    private String flushCacheOnChange = DEFAULT_VALUE_flushCacheOnChange;
    private String localUseCacheExceptions = DEFAULT_VALUE_localUseCacheExceptions;
    private String localFlushCacheExceptions = DEFAULT_VALUE_localFlushCacheExceptions;
    private String allowCacheAssociatedObjects = DEFAULT_VALUE_allowCacheAssociatedObjects;
    private String maximumBytesPerUploadedFileInMemory = DEFAULT_VALUE_maximum_bytes_per_uploaded_file_in_memory;
    private String maximumTotalBytesPerUploadRequest = DEFAULT_VALUE_maximum_total_bytes_per_upload_request;
    private String maximumBytesPerUploadedFile = DEFAULT_VALUE_maximum_bytes_per_uploaded_file;

    private DiskFileItemFactory fileFactory;
    private ServletFileUpload fileUpload;

    private ConcurrentMap<String, Object> cacheProvidersMap = new ConcurrentHashMap<String, Object>();
    private ConcurrentMap<String, String> mimeTypesMap = new ConcurrentHashMap<String, String>();

    static {
        try {
            Properties extMimeMapping = PropertyFileUtil.loadPropertiesFromResource(EXTENSION_MIMETYPES_RESOURCE);
            for (Map.Entry<Object, Object> entry : extMimeMapping.entrySet()) {
                Object name = entry.getKey();
                Object value = entry.getValue();
                if (name != null && value != null)
                    defaultMimeTypesMap.put(name.toString(), value.toString());
            }

            me = new EnvConfig();
        } catch (Exception ex) {
            log.fatal("Error instantiating EnvConfig: " + ex.getMessage());
            ex.printStackTrace();
        }
    }

    private EnvConfig() {
        init();
        PropertyFileChangeMonitor.getInstance().registerObserver(this, DATA_PROPERTIES_FILE);

        EventsManager.getInstance().registerListener(Constants.EVENT_COMPILE, SoundPlayer.getInstance());
    }

    private void clear() {
        mimeTypesMap.clear();
        cacheProvidersMap.clear();
        PluginManager.getInstance().removePlugins();
        fileFactory = null;
        fileUpload = null;
    }

    private void init() {
        clear();

        mimeTypesMap.putAll(defaultMimeTypesMap);

        loadProperties();

        siteAdminUsername = getProperty("site.admin.username", DEFAULT_VALUE_siteAdminUsername);
        siteAdminPassword = getProperty("site.admin.password", DEFAULT_VALUE_siteAdminPassword);
        controllerClassPrefix = getProperty("controller.class.prefix", DEFAULT_VALUE_controllerClassPrefix);
        controllerClassSuffix = getProperty("controller.class.suffix", DEFAULT_VALUE_controllerClassSuffix);
        modelClassPrefix = getProperty("model.class.prefix", DEFAULT_VALUE_modelClassPrefix);
        modelClassSuffix = getProperty("model.class.suffix", DEFAULT_VALUE_modelClassSuffix);
        allowDefaultControllerClass = getProperty("auto.crud", DEFAULT_VALUE_allowDefaultControllerClass);
        actionExtension = getProperty("action.extension", DEFAULT_VALUE_actionExtension);
        if (!actionExtension.startsWith(".") && !"".equals(actionExtension))
            actionExtension = "." + actionExtension;
        viewExtension = getProperty("view.extension", DEFAULT_VALUE_viewExtension);
        if (!viewExtension.startsWith(".") && !"".equals(viewExtension))
            viewExtension = "." + viewExtension;
        defaultActionMethod = getProperty("default.action.method", DEFAULT_VALUE_defaultActionMethod);
        allowDefaultActionMethod = getProperty("allow.default.action.method",
                DEFAULT_VALUE_allowDefaultActionMethod);
        webPageDirectoryName = getProperty("webpage.directory.name", DEFAULT_VALUE_webPageDirectoryName);
        allowForwardToControllerNameViewWhenControllerNotExist = getProperty(
                "allow.forward.to.controller.name.view.when.controller.not.exist",
                DEFAULT_VALUE_allowForwardToControllerNameViewWhenControllerNotExist);
        allowForwardToActionNameViewWhenActionNotExist = getProperty(
                "allow.forward.to.action.name.view.when.action.not.exist",
                DEFAULT_VALUE_allowForwardToActionNameViewWhenActionNotExist);
        rootURL = getProperty("root.url", DEFAULT_VALUE_rootURL);
        if (!rootURL.startsWith("/"))
            rootURL = "/" + rootURL;

        runningEnvironment = getProperty("running.environment", DEFAULT_VALUE_runningEnvironment);
        ApplicationConfig.getInstance().setRunningEnvironment(runningEnvironment);

        passwordScheme = getProperty("password.scheme", DEFAULT_VALUE_passwordScheme);
        benchmark = getProperty("benchmark", DEFAULT_VALUE_benchmark);
        benchmarkInHeader = getProperty("benchmark.in.header", DEFAULT_VALUE_benchmarkInHeader);

        String language = getProperty("locale.language", DEFAULT_VALUE_locale_language);
        String country = getProperty("locale.country", DEFAULT_VALUE_locale_country);
        String variant = getProperty("locale.variant", DEFAULT_VALUE_locale_variant);
        if (language != null && country != null && variant != null) {
            gloabalLocale = new Locale(language, country, variant);
        } else if (language != null && country != null) {
            gloabalLocale = new Locale(language, country);
        } else if (language != null) {
            gloabalLocale = new Locale(language);
        } else {
            gloabalLocale = DEFAULT_VALUE_gloabalLocale;
        }
        ActionContext.setGlobalLocale(gloabalLocale);

        messageResourcesFileBase = getProperty("message.resources.file.base",
                DEFAULT_VALUE_messageResourcesFileBase);
        errorPageURI = getProperty("app.error.page.uri", DEFAULT_VALUE_errorPageURI);
        compileErrorPageURI = getProperty("app.compile.error.page.uri", DEFAULT_VALUE_compileErrorPageURI);
        allowDisplayingErrorDetails = getProperty("allow.displaying.error.details",
                DEFAULT_VALUE_allowDisplayingErrorDetails);
        allowDataBrowser = getProperty("allow.databrowser", DEFAULT_VALUE_allowDataBrowser);
        additionalMimeTypes = getProperty("additional.mimetypes", DEFAULT_VALUE_additionalMimeTypes);
        additionalSinglePlural = getProperty("additional.single.plural", DEFAULT_VALUE_additionalSinglePlural);

        Map<String, String> mimeTypes = Converters.convertStringToMap(additionalMimeTypes, ":", ",");
        mimeTypesMap.putAll(mimeTypes);

        Map<String, String> words = Converters.convertStringToMap(additionalSinglePlural, ":", ",");
        for (Map.Entry<String, String> entry : words.entrySet()) {
            String value = entry.getValue();
            WordUtil.addPlural(entry.getKey(), (value != null) ? value : (String) null);
        }

        Enumeration<Object> en = appProperties.keys();
        while (en.hasMoreElements()) {
            String key = (String) en.nextElement();
            if (key.startsWith("plugin")) {
                String name = key.substring(key.indexOf('.') + 1);
                Properties p = PropertyFileUtil.parseNestedPropertiesFromLine(getProperty(key), "=", ",");
                p.setProperty(Plugin.KEY_PLUGIN_NAME, name);
                PluginManager.getInstance().registerPlugin(name, p);

                if (key.startsWith("plugin.cache.provider.")) {
                    cacheProvidersMap.put(key.substring("plugin.cache.provider.".length()), p);
                }
            }
        }

        defaultCacheProvider = getProperty("default.cache.provider.name", DEFAULT_VALUE_defaultCacheProvider);
        if (defaultCacheProvider != null && !cacheProvidersMap.keySet().contains(defaultCacheProvider)) {
            log.error("There is no definition for default cache provider " + defaultCacheProvider);
        }

        defaultCacheName = getProperty("default.cache.name", DEFAULT_VALUE_defaultCacheName);
        useRequestCache = getProperty("useRequestCache", DEFAULT_VALUE_useRequestCache);
        useSecondLevelCache = getProperty("useSecondLevelCache", DEFAULT_VALUE_useSecondLevelCache);
        flushCacheOnChange = getProperty("flushCacheOnChange", DEFAULT_VALUE_flushCacheOnChange);
        localUseCacheExceptions = getProperty("localUseCacheExceptions", DEFAULT_VALUE_localUseCacheExceptions);
        localFlushCacheExceptions = getProperty("localFlushCacheExceptions",
                DEFAULT_VALUE_localFlushCacheExceptions);
        allowCacheAssociatedObjects = getProperty("allowCacheAssociatedObjects",
                DEFAULT_VALUE_allowCacheAssociatedObjects);

        uploadFileRepository = getProperty("upload.file.repository", DEFAULT_VALUE_upload_file_repository);
        maximumBytesPerUploadedFileInMemory = getProperty("maximum.bytes.per.uploaded.file.in.memory",
                DEFAULT_VALUE_maximum_bytes_per_uploaded_file_in_memory);
        maximumTotalBytesPerUploadRequest = getProperty("maximum.total.bytes.per.upload.request",
                DEFAULT_VALUE_maximum_total_bytes_per_upload_request);
        maximumBytesPerUploadedFile = getProperty("maximum.bytes.per.uploaded.file",
                DEFAULT_VALUE_maximum_bytes_per_uploaded_file);
        configFileUpload();
    }

    private void loadProperties() {
        if (appProperties != null)
            appProperties.clear();

        appProperties = PropertyReader.loadPropertiesFromFile(DATA_PROPERTIES_FILE);

        if (appProperties == null)
            appProperties = new Properties();
    }

    /*
     * Configures file upload setting.
     */
    private void configFileUpload() {
        int maxMemorySize = getMaximumBytesPerUploadedFileInMemory();
        String uploadDirectory = getUploadFileRepository();
        long maxRequestSize = getMaximumTotalBytesPerUploadRequest();
        long fileSizeMax = getMaximumBytesPerUploadedFile();

        fileFactory = (uploadDirectory == null) ? new DiskFileItemFactory(maxMemorySize, null)
                : new DiskFileItemFactory(maxMemorySize, new File(uploadDirectory));
        fileUpload = new ServletFileUpload(fileFactory);
        fileUpload.setSizeMax(maxRequestSize);
        fileUpload.setFileSizeMax(fileSizeMax);
    }

    private void initializePlugins() {
        PluginManager.getInstance().startPlugins();
    }

    public static EnvConfig getInstance() {
        return me;
    }

    public void update(Observable o, Object arg) {
        init();
        initializePlugins();
    }

    /**
     * Returns all properties.
     */
    public Properties getProperties() {
        return appProperties;
    }

    /**
     * Returns a String property corresponding to a key.
     */
    public String getProperty(String key) {
        return appProperties.getProperty(key);
    }

    /**
     * Returns a String property corresponding to a key. The method returns the
     * default value argument if the property is not found.
     */
    public String getProperty(String key, String defaultValue) {
        return appProperties.getProperty(key, defaultValue);
    }

    /**
     * Returns site admin username.
     */
    public String getSiteAdminUsername() {
        return siteAdminUsername;
    }

    /**
     * Returns site admin password.
     */
    public String getSiteAdminPassword() {
        return siteAdminPassword;
    }

    /**
     * Returns action extension
     *
     * @return action extension
     */
    public String getActionExtension() {
        return actionExtension;
    }

    /**
     * Returns view extension
     *
     * @return view extension
     */
    public String getViewExtension() {
        return viewExtension;
    }

    /**
     * Returns root url.
     *
     * @return root url
     */
    public String getRootURL() {
        return rootURL;
    }

    /**
     * Returns controller class name prefix.
     */
    public String getControllerClassPrefix() {
        return controllerClassPrefix;
    }

    /**
     * Returns controller class name suffix.
     */
    public String getControllerClassSuffix() {
        return controllerClassSuffix;
    }

    /**
     * Checks if a built-in default controller class is allowed to use.
     */
    public boolean allowDefaultControllerClass() {
        return ("true".equalsIgnoreCase(allowDefaultControllerClass)) ? true : false;
    }

    /**
     * Alias of <tt>allowDefaultControllerClass</tt> method.
     */
    public boolean allowAutoCRUD() {
        return allowDefaultControllerClass();
    }

    /**
     * Returns default method name of a controller class.
     */
    public String getDefaultActionMethod() {
        return defaultActionMethod;
    }

    /**
     * Checks if a default action name is used when an action method is not
     * obtained from parsing a url.
     */
    public boolean allowDefaultActionMethod() {
        return ("true".equalsIgnoreCase(allowDefaultActionMethod)) ? true : false;
    }

    /**
     * Returns model class name prefix.
     */
    public String getModelClassPrefix() {
        return modelClassPrefix;
    }

    /**
     * Returns model class name suffix.
     */
    public String getModelClassSuffix() {
        return modelClassSuffix;
    }

    /**
     * Returns directory name which contains all web pages.
     */
    public String getWebPageDirectoryName() {
        if (webPageDirectoryName == null)
            return "";
        if (!"".equals(webPageDirectoryName) && !webPageDirectoryName.startsWith("/")) {
            webPageDirectoryName = "/" + webPageDirectoryName;
        }
        return webPageDirectoryName;
    }

    /**
     * Returns action URI for a specific action method.
     *
     * If there is no extension type in the action name, the action is treated
     * as an extension type defined in config properties file. For example,
     * if the action is "show", the action could be treated as "show.do" action if
     * the <tt>actionExtension</tt> property is defined as ".do".
     *
     * <pre>
     * Examples:
     *      getActionUriFor("list") => list.do (extension = .do)
     * </pre>
     * @param action the action method
     * @return action URI string
     */
    public static String getActionUriFor(String action) {
        return getActionUriFor(null, action);
    }

    /**
     * Returns action URI for a controller path with a specific action method.
     *
     * If there is no extension type in the action name, the action is treated
     * as an extension type defined in config properties file. For example,
     * if the action is "show", the action could be treated as "show.do" action if
     * the <tt>actionExtension</tt> property is defined as ".do".
     *
     * <pre>
     * Examples:
     *      getActionUriFor("/posts", "list") => /posts/list.do
     * </pre>
     * @param controllerPath path to the action
     * @param action the action method
     * @return action URI string
     */
    public static String getActionUriFor(String controllerPath, String action) {
        if (action == null || "".equals(action)) {
            if (getInstance().allowDefaultActionMethod()) {
                action = getInstance().getDefaultActionMethod();
            } else {
                throw new IllegalArgumentException(
                        "The value for action " + "input cannot be null or empty unless the default action "
                                + "method is specified in property file.");
            }
        }

        if (!action.endsWith(EnvConfig.getInstance().getActionExtension())) {
            action += EnvConfig.getInstance().getActionExtension();
        }
        return (controllerPath == null || "".equals(controllerPath)) ? action : (controllerPath + "/" + action);
    }

    /**
     * <p>
     * Returns view URI for a controller with a specific action.</p>
     *
     * <p>
     * If there is no extension type in the view name, the view is treated
     * as an extension type defined in config properties file. For example,
     * if the view is "show", the view could be treated as "show.jsp" action if
     * the <tt>view.extension</tt> property is defined as ".jsp". The directory
     * path for view files is set by the <tt>webpage.directory.name</tt>
     * property in properties file.</p>
     *
     * <pre>
     * Examples:
     *      If your JSP web pages are under webapp:
     *      webpage.directory.name=""
     *      getViewURI("posts", "list") => /posts/list.jsp
     *
     *      If your jsp web pages are under WEB-INF/views:
     *      webpage.directory.name="WEB-INF/views" (default)
     *      getViewURI("posts", "list") => /WEB-INF/views/posts/list.jsp
     * </pre>
     * @param controller controller name
     * @param actionOrView the action method name or view name
     * @return view URI.
     */
    public static String getViewURI(String controller, String actionOrView) {
        return getViewURI(controller, actionOrView, null);
    }

    /**
     * Returns a  view URI. This URI is a real view file associated with the
     * controller and the action or view name.
     *
     * @param controller the name of the controller
     * @param actionOrView the action method name or view name
     * @return view URI.
     */
    public static String getViewURI(String controller, String actionOrView, String defaultViewDir) {
        if (controller == null) {
            controller = "";
        } else {
            controller = controller.toLowerCase();
        }

        if (!controller.startsWith("/") && !"".equals(controller))
            controller = "/" + controller;

        if (actionOrView == null || "".equals(actionOrView)) {
            if (getInstance().allowDefaultActionMethod()) {
                actionOrView = getInstance().getDefaultActionMethod();
            } else {
                throw new IllegalArgumentException(
                        "The value for action " + "input cannot be null or empty unless the default action "
                                + "method is specified in property file.");
            }
        }

        if (!actionOrView.endsWith(EnvConfig.getInstance().getViewExtension()) && actionOrView.indexOf('.') == -1) {
            actionOrView += EnvConfig.getInstance().getViewExtension();
        }

        String uri = "";
        if (!actionOrView.startsWith("/")) {
            uri = EnvConfig.getInstance().getWebPageDirectoryName() + controller + "/" + actionOrView;
        } else {
            uri = EnvConfig.getInstance().getWebPageDirectoryName() + controller + actionOrView;
        }

        String realPath = getRealPath();
        String filePath = realPath + uri;

        File f = new File(filePath);
        if (!f.exists()) {
            if (defaultViewDir != null) {
                log.warn("File \"" + filePath + "\" does not exist. Use " + "default view file in \""
                        + defaultViewDir + "\".");
                return getViewURI(defaultViewDir, actionOrView, null);
            } else {
                String errorMsg = "View file \"" + filePath + "\" does not exist.";
                log.error(errorMsg);
                throw new NoViewFileException(errorMsg, filePath);
            }
        }

        return uri;
    }

    /**
     * Returns real path of the application.
     *
     * @return real path of the application.
     */
    public static String getRealPath() {
        return ApplicationConfig.getInstance().getApplicationPath();
    }

    /**
     * Returns directory name which contains default view files.
     *
     * Default view files directory specifies directory name for default view
     * files. These view files are related to the non-restful way of actions.
     * For restful way of requests, the default view files are in
     * {DEFAULT_VALUE_defaultViewFilesDirectory}_restful directory.
     */
    public String getDefaultViewFilesDirectory() {
        return DEFAULT_VALUE_defaultViewFilesDirectory;
    }

    /**
     * Returns directory name which contains default view files for builtin
     * RestfulCRUDController or RestfulRequestProcessor.
     */
    public String getDefaultViewFilesDirectoryForREST() {
        return DEFAULT_VALUE_defaultViewFilesDirectory + "_restful";
    }

    /**
     * Checks if controller name is used as a view when the controller does not exist.
     */
    public boolean allowForwardToControllerNameViewWhenControllerNotExist() {
        return ("true".equalsIgnoreCase(allowForwardToControllerNameViewWhenControllerNotExist)) ? true : false;
    }

    /**
     * Checks if action name is used as a view when the action does not exist.
     */
    public boolean allowForwardToActionNameViewWhenActionNotExist() {
        return ("true".equalsIgnoreCase(allowForwardToActionNameViewWhenActionNotExist)) ? true : false;
    }

    /**
     * Returns server type
     */
    public String getServerType() {
        return ApplicationConfig.getInstance().getConfiguredMode();
    }

    /**
     * Returns password scheme
     */
    public String getPasswordScheme() {
        return passwordScheme;
    }

    public String applyPasswordScheme(String password) {
        if (passwordScheme == null || "".equals(passwordScheme))
            return password;
        return passwordScheme + "(" + password + ")";
    }

    /**
     * Returns full controller class name.
     *
     * @param controllerPath controller path
     * @return full java class name
     */
    public String getControllerClassName(String controllerPath) {
        String fullName = Converters.convertToJavaClassLikeString(controllerPath.toLowerCase());
        String prefix = getControllerClassPrefix();
        String suffix = getControllerClassSuffix();

        if (!fullName.startsWith(prefix) && prefix != null && !"".equals(prefix)) {
            fullName = prefix + "." + fullName;
        }

        if (!fullName.endsWith(getControllerClassSuffix()) && suffix != null && !"".equals(suffix)) {
            fullName = fullName + getControllerClassSuffix();
        }

        return fullName;
    }

    /**
     * Returns controller name in lower case.
     *
     * @param fullClassName full class name of the controller
     * @return short controller name (in lower case)
     */
    public String getControllerName(String fullClassName) {
        if (fullClassName.indexOf('.') == -1)
            return fullClassName;

        String controller = fullClassName.substring(fullClassName.lastIndexOf('.') + 1);

        String suffix = getControllerClassSuffix();
        if (!"".equals(suffix) && controller.indexOf(suffix) != -1) {
            controller = controller.substring(0, controller.indexOf(suffix));
        }
        return controller.toLowerCase();
    }

    /**
     * Returns full model class name.
     *
     * @param model model name
     * @return full java class name
     */
    public String getModelClassName(String model) {
        String fullName = Converters.convertToJavaClassLikeString(model);
        if (!model.startsWith(getModelClassPrefix())) {
            fullName = getModelClassPrefix() + "." + fullName;
        }
        if (!model.endsWith(getModelClassSuffix())) {
            fullName = fullName + getModelClassSuffix();
        }
        return fullName;
    }

    /**
     * Returns model class name based on controller class name.
     */
    public String getModelClassNameFromControllerClassName(String controllerClassName) {
        String controller = getControllerName(controllerClassName);
        String model = (DatabaseConfig.getInstance().usePluralTableName()) ? WordUtil.singularize(controller)
                : controller;
        return getModelClassName(model);
    }

    /**
     * <p>Returns home instance of a model.</p>
     *
     * <p>A home instance of a record is a read-only instance for a model type.
     * Its main function is to provide meta information of the model and some
     * finder methods.</p>
     *
     * @param model name corresponding to the model home instance
     * @return a home instance of a model
     */
    public ActiveRecord getHomeInstance(String model) {
        return ActiveRecordUtil.getHomeInstance(getModelClassName(model));
    }

    /**
     * Checks if bench mark info is allowed to record.
     *
     * @return true if allowed
     */
    public boolean allowRecordBenchmark() {
        return ("true".equalsIgnoreCase(benchmark)) ? true : false;
    }

    /**
     * Checks if bench mark info is allowed to record in header.
     *
     * @return true if allowed
     */
    public boolean allowRecordBenchmarkInHeader() {
        return ("true".equalsIgnoreCase(benchmarkInHeader)) ? true : false;
    }

    /**
     * Returns configured locale.
     */
    public Locale getGlobalLocale() {
        return gloabalLocale;
    }

    /**
     * Returns the language for the configured locale.
     */
    public String getGlobalLanguage() {
        return (gloabalLocale != null) ? gloabalLocale.getLanguage() : null;
    }

    /**
     * Returns the country for the configured locale.
     */
    public String getGlobalCountry() {
        return (gloabalLocale != null) ? gloabalLocale.getCountry() : null;
    }

    /**
     * Returns the variant for the configured locale.
     */
    public String getGlobalVariant() {
        return (gloabalLocale != null) ? gloabalLocale.getVariant() : null;
    }

    /**
     * Returns the base name of message files.
     */
    public String getMessageResourcesFileBase() {
        return messageResourcesFileBase;
    }

    /**
     * Returns error page URI.
     */
    public String getErrorPageURI() {
        return errorPageURI;
    }

    /**
     * Returns compile error page URI.
     */
    public String getCompileErrorPageURI() {
        return compileErrorPageURI;
    }

    /**
     * Checks if displaying error details is allowed.
     *
     * @return true if allowed
     */
    public boolean allowDisplayingErrorDetails() {
        return ("true".equalsIgnoreCase(allowDisplayingErrorDetails)) ? true : false;
    }

    /**
     * Checks if DataBroser is allowed.
     *
     * @return true if allowed
     */
    public boolean allowDataBrowser() {
        return ("true".equalsIgnoreCase(allowDataBrowser)) ? true : false;
    }

    /**
     * Returns cache provider properties
     */
    public Properties getPredefinedCacheProviderProperties(String providerName) {
        Properties p = (Properties) cacheProvidersMap.get(providerName);
        return (p != null) ? p : (new Properties());
    }

    /**
     * Returns cache provider names
     */
    public Iterator<String> getPredefinedCacheProviderNames() {
        return cacheProvidersMap.keySet().iterator();
    }

    /**
     * Returns default cache provider name
     */
    public String getDefaultCacheProviderName() {
        return defaultCacheProvider;
    }

    /**
     * Returns default cache name
     */
    public String getDefaultCacheName() {
        return defaultCacheName;
    }

    /**
     * Returns default cache provider properties
     */
    public Properties getDefaultCacheProviderProperties() {
        return getPredefinedCacheProviderProperties(getDefaultCacheProviderName());
    }

    /**
     * Checks if using thread cache for all model classes.
     *
     * @return true if using thread cache
     */
    public boolean getUseThreadCache() {
        return "true".equals(useRequestCache);
    }

    /**
     * Checks if using second-level cache for all model classes.
     *
     * @return true if using second-level cache
     */
    public boolean getUseSecondLevelCache() {
        return "true".equals(useSecondLevelCache);
    }

    /**
     * Checks if using second-level cache for all model classes.
     *
     * @return true if using second-level cache
     */
    public boolean getFlushCacheOnChange() {
        return "true".equals(flushCacheOnChange);
    }

    /**
     * Returns a collection methods that are exceptional to the setting of 
     * <tt>useRequestCache</tt> and <tt>useSecondLevelCache</tt>.
     *
     * @return true if using thread cache
     */
    public Collection<String> getLocalUseCacheExceptions(String modelClassName) {
        if (localUseCacheExceptions == null)
            return null;
        Set<String> localUseCacheExceptionsSet = new HashSet<String>();
        StringTokenizer st = new StringTokenizer(localUseCacheExceptions, ",");
        while (st.hasMoreTokens()) {
            String token = st.nextToken().trim();
            if (token.startsWith(modelClassName))
                localUseCacheExceptionsSet.add(token);
        }
        return localUseCacheExceptionsSet;
    }

    /**
     * Returns a collection methods that are exceptional to the setting of 
     * <tt>useRequestCache</tt> and <tt>useSecondLevelCache</tt>.
     *
     * @return a collection of exceptional method names
     */
    public Collection<String> getLocalFlushCacheExceptions(String modelClassName) {
        if (localFlushCacheExceptions == null)
            return null;
        Set<String> localFlushCacheExceptionsSet = new HashSet<String>();
        StringTokenizer st = new StringTokenizer(localFlushCacheExceptions, ",");
        while (st.hasMoreTokens()) {
            String token = st.nextToken().trim();
            if (token.startsWith(modelClassName))
                localFlushCacheExceptionsSet.add(token);
        }
        return localFlushCacheExceptionsSet;
    }

    /**
     * Checks if using second-level cache.
     *
     * @return true if using second-level cache
     */
    public boolean allowCacheAssociatedObjects(String modelClassName) {
        return "true".equals(allowCacheAssociatedObjects);
    }

    /**
     * Returns plugin properties
     */
    public Properties getPluginProperties(String pluginName) {
        return PluginManager.getInstance().getPluginProperties(pluginName);
    }

    /**
     * Returns the extension/mimetype mapping.
     */
    public Map<String, String> getMimeTypeMap() {
        return mimeTypesMap;
    }

    /**
     * Returns MIME type corresponding the specific extension.
     * 
     * @param extension The extension string.
     * @return the MIME type string for the extension.
     */
    public String getMimeType(String extension) {
        Object o = mimeTypesMap.get(extension);
        return (o != null) ? o.toString() : (String) null;
    }

    /**
     * Checks if the specific <tt>extension</tt> has a registered MIME type.
     * 
     * @param extension The extension string.
     * @return true if there is a MIME type for the <tt>extension</tt>.
     */
    public boolean hasMimeTypeFor(String extension) {
        return mimeTypesMap.containsKey(extension);
    }

    /**
     * Returns file directory for storing uploaded files.
     */
    public String getUploadFileRepository() {
        if (uploadFileRepository == null || "null".equals(uploadFileRepository) || "".equals(uploadFileRepository))
            return null;
        return uploadFileRepository;
    }

    /**
     * Returns the threshold, in bytes, below which items will be retained in 
     * memory and above which they will be stored as a file. 
     * The default value is 10240 bytes. 
     */
    public int getMaximumBytesPerUploadedFileInMemory() {
        return Integer.valueOf(maximumBytesPerUploadedFileInMemory);
    }

    /**
     * Returns the maximum allowed bytes of an upload request. 
     * The default value of -1 indicates, that there is no limit.
     */
    public long getMaximumTotalBytesPerUploadRequest() {
        return Long.valueOf(maximumTotalBytesPerUploadRequest);
    }

    /**
     * Returns the maximum allowed bytes of an upload file.
     * The default value of -1 indicates, that there is no limit.
     */
    public long getMaximumBytesPerUploadedFile() {
        return Long.valueOf(maximumBytesPerUploadedFile);
    }

    /**
     * Returns DiskFileItemFactory instance for file upload.
     */
    public DiskFileItemFactory getDiskFileItemFactory() {
        if (fileFactory == null) {
            configFileUpload();
        }
        return fileFactory;
    }

    /**
     * Returns ServletFileUpload instance for file upload.
     */
    public ServletFileUpload getServletFileUpload() {
        if (fileUpload == null) {
            configFileUpload();
        }
        return fileUpload;
    }

    /**
     * Checks whether a file is a text file. 
     * 
     * @param file  the file to check
     * @return true if it is a text file.
     */
    public boolean isTextFile(File file) {
        if (file == null)
            return false;
        String fName = file.getName();
        String extension = fName.substring(fName.lastIndexOf('.') + 1);
        return isTextFile(extension);
    }

    /**
     * Checks whether a file extension indicates that it is a text file. 
     * 
     * @param extension  the file extension to check
     * @return true if it is a text file.
     */
    public boolean isTextFile(String extension) {
        if (extension == null)
            return false;
        boolean result = false;
        String mimeType = EnvConfig.getInstance().getMimeType(extension);

        if ((mimeType != null && (mimeType.startsWith("text") || mimeType.endsWith("xml")))
                || "csh".equalsIgnoreCase(extension) || "dtd".equalsIgnoreCase(extension)
                || "js".equalsIgnoreCase(extension)) {
            result = true;
        }
        return result;
    }

    /**
     * Checks if the content of the file can be highlighted. Only a selected
     * types of file extensions are highlightable.
     * 
     * @param fileExtension
     * @return true if highlightable
     */
    public boolean isHighlightable(String fileExtension) {
        return isTextFile(fileExtension);
    }
}