/*
 * RHQ Management Platform
 * Copyright (C) 2011 Red Hat, Inc.
 * All rights reserved.
 *
 * This program 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 2 of the License.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.core.domain.drift;

import java.io.Serializable;
import java.util.ArrayList;

import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.ConfigurationFormat;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionEnumeration;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionList;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionMap;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.core.domain.configuration.definition.PropertySimpleType;
import org.rhq.core.domain.configuration.definition.constraint.RegexConstraint;

/**
 * The drift subsystem has a fixed configuration definition. That is, its property definitions
 * are the same always. There is no metadata that needs to be read in from a descriptor - this definition
 * is fixed and the code requires all the property definitions to follow what is encoded in this POJO.
 * 
 * Note that this class must mimic the definition data as found in the database. The installer
 * will prepopulate the configuration definition tables that match the definitions encoded in this POJO.
 *  
 * @author Jay Shaughnessy
 * @author John Mazzitelli
 */
public class DriftConfigurationDefinition implements Serializable {
    private static final long serialVersionUID = 1L;

    public static final String PROP_NAME = "name";
    public static final String PROP_DESCRIPTION = "description";
    public static final String PROP_ENABLED = "enabled";
    public static final String PROP_BASEDIR = "basedir";
    public static final String PROP_BASEDIR_VALUECONTEXT = "valueContext";
    public static final String PROP_BASEDIR_VALUENAME = "valueName";
    public static final String PROP_INTERVAL = "interval";
    public static final String PROP_DRIFT_HANDLING_MODE = "driftHandlingMode";
    public static final String PROP_PINNED = "pinned";
    public static final String PROP_ATTACHED = "attached";
    public static final String PROP_INCLUDES = "includes";
    public static final String PROP_INCLUDES_INCLUDE = "include";
    public static final String PROP_EXCLUDES = "excludes";
    public static final String PROP_EXCLUDES_EXCLUDE = "exclude";
    public static final String PROP_PATH = "path"; // for both include and exclude
    public static final String PROP_PATTERN = "pattern"; // for both include and exclude

    // because we know drift definition names will actually be used by the agent's plugin container as directories names,
    // we must make sure they are restricted to only be characters valid for file system pathnames.
    // Thus, we only allow config names to only include spaces or "." or "-" or alphanumeric or "_" characters.
    public static final String PROP_NAME_REGEX_PATTERN = "[ \\.\\-\\w]+";

    // because we know drift definition base directory paths and filter paths will actually be used by the agent's
    // plugin container as part of regex expressions, we must make sure they are restricted to only be characters
    // valid for file system paths and regex.  And, additionally for filter patterns, the (ant-style) filter
    // wildcards. Thus, we only allow the base paths to include the separator characters "\", "/", the windows
    // drive character ":" and the filename characters (see PROP_NAME_REGEX_PATTERN).  Filter paths can
    // contain the same as the base path, less the drive character.  And filter patterns are the same as
    // filter paths, plus the wildcard characters. "*", "?".
    public static final String PROP_BASEDIR_PATH_REGEX_PATTERN = "[ \\.\\-\\w/\\:\\\\]+";
    public static final String PROP_FILTER_PATH_REGEX_PATTERN = "[ \\.\\-\\w/\\\\]+";
    public static final String PROP_FILTER_PATTERN_REGEX_PATTERN = "[ \\.\\-\\w/\\\\\\?\\*]+";

    public static final boolean DEFAULT_ENABLED = true;
    public static final boolean DEFAULT_ATTACHED = true;
    public static final long DEFAULT_INTERVAL = 1800L;
    public static final DriftHandlingMode DEFAULT_DRIFT_HANDLING_MODE = DriftHandlingMode.normal;

    /**
     * The basedir property is specified in two parts - a "context" and a "name". Taken together
     * the value of the basedir can be determined. The value name is just a simple name that
     * is used to look up the basedir value within the appropriate context. A context can be
     * one of four places - either the value is a named property in a resource's plugin configuration,
     * a named property in a resource's resource configuration, a named trait that is emitted by a
     * resource or an absolute path found on a file system.
     */
    public enum BaseDirValueContext {
        pluginConfiguration, resourceConfiguration, measurementTrait, fileSystem
    }

    /**
     * The enumerated values for drift handling mode property
     */
    public enum DriftHandlingMode {
        normal, plannedChanges
    }

    private static final ConfigurationDefinition INSTANCE = new ConfigurationDefinition("GLOBAL_DRIFT_CONFIG_DEF",
        "The drift detection definition");

    /**
     * For drift definitions that have already been created, this definition can be used for editing those existing configuration.
     * Existing drift definitions cannot have their name changed nor can their base directory or includes/excludes be altered.
     */
    private static final ConfigurationDefinition INSTANCE_FOR_EXISTING_CONFIGS = new ConfigurationDefinition(
        "GLOBAL_DRIFT_CONFIG_DEF", "The drift detection definition");

    private static final ConfigurationDefinition NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE = new ConfigurationDefinition(
        "NEW_RESOURCE_DRIFT_DEF_BY_PINNED_TEMPLATE", "A new resource drift definition created from a pinned template");

    private static final ConfigurationDefinition EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE = new ConfigurationDefinition(
        "EXISTING_RESOURCE_DRIFT_DEF_BY_PINNED_TEMPLATE",
        "An existing resource drift definition created from a pinned template");

    private static final ConfigurationDefinition NEW_TEMPLATE_INSTANCE = new ConfigurationDefinition(
        "NEW_TEMPLATE_DRIFT_CONFIG_DEF", "A new template drift definition");

    private static final ConfigurationDefinition EXISTING_TEMPLATE_INSTANCE = new ConfigurationDefinition(
        "EXISTING_TEMPLATE_DRIFT_CONFIG_DEF", "An existing template drift definition");

    private static final ConfigurationDefinition NEW_PINNED_TEMPLATE_INSTANCE = new ConfigurationDefinition(
        "NEW_PINNED_TEMPLATE_DRIFT_CONFIG_DEF", "A new pinned template drift definition");

    /**
     * Returns a configuration definition suitable for showing a new configuration form - that is,
     * a configuration that has not yet been created.
     * This will allow all fields to be editable.
     * If you need a configuration definition to show an existing configuration, use the definition
     * returned by {@link #getInstanceForExistingConfiguration()}.
     * 
     * @return configuration definition
     */
    public static ConfigurationDefinition getInstance() {
        return INSTANCE;
    }

    /**
     * Returns a configuration definition suitable for showing an existing drift definition.
     * This will set certain fields as read-only - those fields which the user is not allowed to
     * edit on exiting drift definition (which includes name, basedir and includes/excludes filters).
     * 
     * @return configuration definition
     */
    public static ConfigurationDefinition getInstanceForExistingConfiguration() {
        return INSTANCE_FOR_EXISTING_CONFIGS;
    }

    public static ConfigurationDefinition getNewResourceInstanceByPinnedTemplate() {
        return NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE;
    }

    public static ConfigurationDefinition getExistingResourceInstanceByPinnedTemplate() {
        return EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE;
    }

    public static ConfigurationDefinition getNewTemplateInstance() {
        return NEW_TEMPLATE_INSTANCE;
    }

    public static ConfigurationDefinition getExistingTemplateInstance() {
        return EXISTING_TEMPLATE_INSTANCE;
    }

    public static ConfigurationDefinition getNewPinnedTemplateInstance() {
        return NEW_PINNED_TEMPLATE_INSTANCE;
    }

    static {
        INSTANCE.setConfigurationFormat(ConfigurationFormat.STRUCTURED);
        INSTANCE.put(createName(INSTANCE, false));
        INSTANCE.put(createDescription(INSTANCE));
        INSTANCE.put(createEnabled(INSTANCE));
        INSTANCE.put(createDriftHandlingMode(INSTANCE));
        INSTANCE.put(createInterval(INSTANCE));
        INSTANCE.put(createBasedir(INSTANCE, false));
        INSTANCE.put(createIncludes(INSTANCE, false));
        INSTANCE.put(createExcludes(INSTANCE, false));
        INSTANCE.put(createPinned(INSTANCE, false));
        INSTANCE.put(createAttached(INSTANCE, false));

        INSTANCE_FOR_EXISTING_CONFIGS.setConfigurationFormat(ConfigurationFormat.STRUCTURED);
        INSTANCE_FOR_EXISTING_CONFIGS.put(createName(INSTANCE_FOR_EXISTING_CONFIGS, true));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createDescription(INSTANCE_FOR_EXISTING_CONFIGS));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createEnabled(INSTANCE_FOR_EXISTING_CONFIGS));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createDriftHandlingMode(INSTANCE_FOR_EXISTING_CONFIGS));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createInterval(INSTANCE_FOR_EXISTING_CONFIGS));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createBasedir(INSTANCE_FOR_EXISTING_CONFIGS, true));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createIncludes(INSTANCE_FOR_EXISTING_CONFIGS, true));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createExcludes(INSTANCE_FOR_EXISTING_CONFIGS, true));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createExcludes(INSTANCE_FOR_EXISTING_CONFIGS, true));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createAttached(INSTANCE_FOR_EXISTING_CONFIGS, false));
        INSTANCE_FOR_EXISTING_CONFIGS.put(createPinned(INSTANCE_FOR_EXISTING_CONFIGS, false));

        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.setConfigurationFormat(ConfigurationFormat.STRUCTURED);
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createName(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE, false));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createDescription(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createInterval(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createEnabled(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createDriftHandlingMode(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createBasedir(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE, true));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createIncludes(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE, true));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createExcludes(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE, true));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createPinned(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE, true));
        NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createAttached(NEW_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE, true));

        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.setConfigurationFormat(ConfigurationFormat.STRUCTURED);
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createName(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE,
            true));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE
            .put(createDescription(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE
            .put(createInterval(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createEnabled(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE
            .put(createDriftHandlingMode(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createBasedir(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE,
            true));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createIncludes(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE,
            true));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createExcludes(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE,
            true));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createPinned(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE,
            true));
        EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE.put(createAttached(EXISTING_RESOURCE_INSTANCE_BY_PINNED_TEMPLATE,
            true));

        NEW_TEMPLATE_INSTANCE.setConfigurationFormat(ConfigurationFormat.STRUCTURED);
        NEW_TEMPLATE_INSTANCE.put(createName(NEW_TEMPLATE_INSTANCE, false));
        NEW_TEMPLATE_INSTANCE.put(createDescription(NEW_TEMPLATE_INSTANCE));
        NEW_TEMPLATE_INSTANCE.put(createInterval(NEW_TEMPLATE_INSTANCE));
        NEW_TEMPLATE_INSTANCE.put(createEnabled(NEW_TEMPLATE_INSTANCE));
        NEW_TEMPLATE_INSTANCE.put(createDriftHandlingMode(NEW_TEMPLATE_INSTANCE));
        NEW_TEMPLATE_INSTANCE.put(createBasedir(NEW_TEMPLATE_INSTANCE, false));
        NEW_TEMPLATE_INSTANCE.put(createIncludes(NEW_TEMPLATE_INSTANCE, false));
        NEW_TEMPLATE_INSTANCE.put(createExcludes(NEW_TEMPLATE_INSTANCE, false));

        EXISTING_TEMPLATE_INSTANCE.setConfigurationFormat(ConfigurationFormat.STRUCTURED);
        EXISTING_TEMPLATE_INSTANCE.put(createName(EXISTING_TEMPLATE_INSTANCE, true));
        EXISTING_TEMPLATE_INSTANCE.put(createDescription(EXISTING_TEMPLATE_INSTANCE));
        EXISTING_TEMPLATE_INSTANCE.put(createInterval(EXISTING_TEMPLATE_INSTANCE));
        EXISTING_TEMPLATE_INSTANCE.put(createEnabled(EXISTING_TEMPLATE_INSTANCE));
        EXISTING_TEMPLATE_INSTANCE.put(createDriftHandlingMode(EXISTING_TEMPLATE_INSTANCE));
        EXISTING_TEMPLATE_INSTANCE.put(createBasedir(EXISTING_TEMPLATE_INSTANCE, true));
        EXISTING_TEMPLATE_INSTANCE.put(createIncludes(EXISTING_TEMPLATE_INSTANCE, true));
        EXISTING_TEMPLATE_INSTANCE.put(createExcludes(EXISTING_TEMPLATE_INSTANCE, true));

        NEW_PINNED_TEMPLATE_INSTANCE.setConfigurationFormat(ConfigurationFormat.STRUCTURED);
        NEW_PINNED_TEMPLATE_INSTANCE.put(createName(NEW_PINNED_TEMPLATE_INSTANCE, false));
        NEW_PINNED_TEMPLATE_INSTANCE.put(createDescription(NEW_PINNED_TEMPLATE_INSTANCE));
        NEW_PINNED_TEMPLATE_INSTANCE.put(createInterval(NEW_PINNED_TEMPLATE_INSTANCE));
        NEW_PINNED_TEMPLATE_INSTANCE.put(createEnabled(NEW_PINNED_TEMPLATE_INSTANCE));
        NEW_PINNED_TEMPLATE_INSTANCE.put(createDriftHandlingMode(NEW_PINNED_TEMPLATE_INSTANCE));
        NEW_PINNED_TEMPLATE_INSTANCE.put(createBasedir(NEW_PINNED_TEMPLATE_INSTANCE, true));
        NEW_PINNED_TEMPLATE_INSTANCE.put(createIncludes(NEW_PINNED_TEMPLATE_INSTANCE, true));
        NEW_PINNED_TEMPLATE_INSTANCE.put(createExcludes(NEW_PINNED_TEMPLATE_INSTANCE, true));
    }

    private static PropertyDefinitionSimple createName(ConfigurationDefinition configDef, boolean readOnly) {
        String name = PROP_NAME;
        String description = "The drift detection definition name";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Drift Definition Name");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(0);
        pd.setAllowCustomEnumeratedValue(false);
        pd.setConfigurationDefinition(configDef);

        RegexConstraint constraint = new RegexConstraint();
        constraint.setDetails(PROP_NAME_REGEX_PATTERN);
        pd.addConstraints(constraint);

        return pd;
    }

    private static PropertyDefinitionSimple createDescription(ConfigurationDefinition configDef) {
        String name = PROP_DESCRIPTION;
        String description = "A description of the drift detection definition or template";
        boolean required = false;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Drift Definition Description");
        pd.setSummary(true);
        pd.setOrder(1);
        pd.setAllowCustomEnumeratedValue(false);
        pd.setConfigurationDefinition(configDef);

        return pd;
    }

    private static PropertyDefinitionSimple createEnabled(ConfigurationDefinition configDef) {
        String name = PROP_ENABLED;
        String description = "Enables or disables the drift definition";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.BOOLEAN;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Enabled");
        pd.setReadOnly(false);
        pd.setSummary(true);
        pd.setOrder(2);
        pd.setAllowCustomEnumeratedValue(false);
        pd.setConfigurationDefinition(configDef);
        pd.setDefaultValue(String.valueOf(DEFAULT_ENABLED));
        return pd;
    }

    private static PropertyDefinitionSimple createAttached(ConfigurationDefinition configDef, boolean readOnly) {
        String name = PROP_ATTACHED;
        String description = "A flag that indicates whether or not the definition is attached to the template from "
            + "which it is created. When a template is updated, the changes will be propagated to any attached "
            + "definitions. Furthermore, if you pin an existing template to a snapshot, then attached definitions will "
            + "become pinned as well. Finally, if you delete a template, attached definitions will also be deleted.";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.BOOLEAN;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Attached to Template");
        pd.setDefaultValue("true");
        pd.setOrder(3);
        pd.setReadOnly(readOnly);
        pd.setConfigurationDefinition(configDef);

        return pd;
    }

    private static PropertyDefinitionSimple createDriftHandlingMode(ConfigurationDefinition configDef) {
        String name = PROP_DRIFT_HANDLING_MODE;
        String description = "" //
            + "Specifies the way in which drift instances will be handled when reported. Normal " //
            + "handling implies the reported drift is unexpected and as such can trigger alerts, " //
            + "will be present in recent drift reports, etc.  Setting to 'Planned Changes' implies " //
            + "that the reported drift is happening at a time when drift is expected due to " //
            + "planned changes in the monitored environment, such as an application deployment, a " //
            + "configuration change, or something similar.  With this setting drift is only reported " //
            + " for inspection, in drift snapshot views.";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Drift Handling Mode");
        pd.setReadOnly(false);
        pd.setSummary(true);
        pd.setOrder(4);
        pd.setConfigurationDefinition(configDef);

        PropertyDefinitionEnumeration normalEnum = new PropertyDefinitionEnumeration(DriftHandlingMode.normal.name(),
            DriftHandlingMode.normal.name());
        normalEnum.setOrderIndex(0);

        PropertyDefinitionEnumeration plannedEnum = new PropertyDefinitionEnumeration(DriftHandlingMode.plannedChanges
            .name(), DriftHandlingMode.plannedChanges.name());
        plannedEnum.setOrderIndex(1);

        ArrayList<PropertyDefinitionEnumeration> pdEnums = new ArrayList<PropertyDefinitionEnumeration>(2);
        pdEnums.add(normalEnum);
        pdEnums.add(plannedEnum);
        pd.setEnumeratedValues(pdEnums, false);
        pd.setDefaultValue(DEFAULT_DRIFT_HANDLING_MODE.name());

        return pd;
    }

    private static PropertyDefinitionSimple createPinned(ConfigurationDefinition configDef, boolean readOnly) {
        String name = PROP_PINNED;
        String description = "If set, pins the snapshot that the agent uses for comparing files during drift "
            + "detection. Normally, the agent compares those files being monitored for drift against the latest "
            + "snapshot. If you pin a snapshot, the agent will use that pinned version to compare against files "
            + "being monitored for drift";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.BOOLEAN;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Pinned");
        pd.setDefaultValue("false");
        pd.setOrder(5);
        pd.setReadOnly(readOnly);
        pd.setConfigurationDefinition(configDef);

        return pd;
    }

    private static PropertyDefinitionSimple createInterval(ConfigurationDefinition configDef) {
        String name = PROP_INTERVAL;
        String description = "The frequency in seconds in which drift detection should run. Defaults to 1800 seconds (i.e. 30 minutes)";
        boolean required = false;
        PropertySimpleType type = PropertySimpleType.LONG;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Interval");
        pd.setReadOnly(false);
        pd.setSummary(true);
        pd.setOrder(6);
        pd.setAllowCustomEnumeratedValue(false);
        pd.setConfigurationDefinition(configDef);
        pd.setDefaultValue(String.valueOf(DEFAULT_INTERVAL));
        return pd;
    }

    private static PropertyDefinitionMap createBasedir(ConfigurationDefinition configDef, boolean readOnly) {
        String name = PROP_BASEDIR;
        String description = "The root directory from which snapshots will be generated during drift monitoring.";
        boolean required = true;

        PropertyDefinitionSimple valueContext = createBasedirValueContext(readOnly);
        PropertyDefinitionSimple valueName = createBasedirValueName(readOnly);

        PropertyDefinitionMap pd = new PropertyDefinitionMap(name, description, required, valueContext, valueName);
        pd.setDisplayName("Base Directory");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(7);
        pd.setConfigurationDefinition(configDef);

        return pd;
    }

    private static PropertyDefinitionSimple createBasedirValueContext(boolean readOnly) {
        String name = PROP_BASEDIR_VALUECONTEXT;
        String description = "Identifies where the named value can be found.";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Value Context");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(0);

        PropertyDefinitionEnumeration pcEnum = new PropertyDefinitionEnumeration(
            BaseDirValueContext.pluginConfiguration.name(), BaseDirValueContext.pluginConfiguration.name());
        pcEnum.setOrderIndex(0);

        PropertyDefinitionEnumeration rcEnum = new PropertyDefinitionEnumeration(
            BaseDirValueContext.resourceConfiguration.name(), BaseDirValueContext.resourceConfiguration.name());
        rcEnum.setOrderIndex(1);

        PropertyDefinitionEnumeration mtEnum = new PropertyDefinitionEnumeration(BaseDirValueContext.measurementTrait
            .name(), BaseDirValueContext.measurementTrait.name());
        mtEnum.setOrderIndex(2);

        PropertyDefinitionEnumeration fsEnum = new PropertyDefinitionEnumeration(BaseDirValueContext.fileSystem.name(),
            BaseDirValueContext.fileSystem.name());
        fsEnum.setOrderIndex(3);

        ArrayList<PropertyDefinitionEnumeration> pdEnums = new ArrayList<PropertyDefinitionEnumeration>(4);
        pdEnums.add(pcEnum);
        pdEnums.add(rcEnum);
        pdEnums.add(mtEnum);
        pdEnums.add(fsEnum);
        pd.setEnumeratedValues(pdEnums, false);

        return pd;
    }

    private static PropertyDefinitionSimple createBasedirValueName(boolean readOnly) {
        String name = PROP_BASEDIR_VALUENAME;
        String description = "The name of the value as found in the context.";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Value Name");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(1);
        pd.setAllowCustomEnumeratedValue(false);

        RegexConstraint constraint = new RegexConstraint();
        constraint.setDetails(PROP_BASEDIR_PATH_REGEX_PATTERN);
        pd.addConstraints(constraint);

        return pd;
    }

    private static PropertyDefinitionList createIncludes(ConfigurationDefinition configDef, boolean readOnly) {
        String name = PROP_INCLUDES;
        String description = "A set of patterns that specify files and/or directories to include.";
        boolean required = false;

        PropertyDefinitionMap map = createInclude(readOnly);

        PropertyDefinitionList pd = new PropertyDefinitionList(name, description, required, map);
        pd.setDisplayName("Includes");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(8);
        pd.setConfigurationDefinition(configDef);

        return pd;
    }

    private static PropertyDefinitionMap createInclude(boolean readOnly) {
        String name = PROP_INCLUDES_INCLUDE;
        String description = "A pattern that specifies a file or directory to include.";
        boolean required = true;

        PropertyDefinitionSimple path = createIncludePath(readOnly);
        PropertyDefinitionSimple pattern = createIncludePattern(readOnly);

        PropertyDefinitionMap pd = new PropertyDefinitionMap(name, description, required, path, pattern);
        pd.setDisplayName("Include");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(0);

        return pd;
    }

    private static PropertyDefinitionSimple createIncludePath(boolean readOnly) {
        String name = PROP_PATH;
        String description = "A file system path that can be a directory or a file. The path is assumed to be relative to the base directory of the drift definition.";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Path");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(0);
        pd.setAllowCustomEnumeratedValue(false);

        RegexConstraint constraint = new RegexConstraint();
        constraint.setDetails(PROP_FILTER_PATH_REGEX_PATTERN);
        pd.addConstraints(constraint);

        return pd;
    }

    private static PropertyDefinitionSimple createIncludePattern(boolean readOnly) {
        String name = PROP_PATTERN;
        String description = "Pathname pattern that must match for the items in the directory path to be included. '*' matches zero or more characters, '?' matches one character. For example, '*.txt' (no quotes) will match text files in the filter's path directory.  '**/*.txt' will match text files in any subdirectory below the filter's path directory";
        boolean required = false;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Pattern");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(1);
        pd.setAllowCustomEnumeratedValue(false);

        RegexConstraint constraint = new RegexConstraint();
        constraint.setDetails(PROP_FILTER_PATTERN_REGEX_PATTERN);
        pd.addConstraints(constraint);

        return pd;
    }

    private static PropertyDefinitionList createExcludes(ConfigurationDefinition configDef, boolean readOnly) {
        String name = PROP_EXCLUDES;
        String description = "A set of patterns that specify files and/or directories to exclude.";
        boolean required = false;

        PropertyDefinitionMap map = createExclude(readOnly);

        PropertyDefinitionList pd = new PropertyDefinitionList(name, description, required, map);
        pd.setDisplayName("Excludes");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(9);
        pd.setConfigurationDefinition(configDef);
        return pd;
    }

    private static PropertyDefinitionMap createExclude(boolean readOnly) {
        String name = PROP_EXCLUDES_EXCLUDE;
        String description = "A pattern that specifies a file or directory to exclude.";
        boolean required = true;

        PropertyDefinitionSimple path = createExcludePath(readOnly);
        PropertyDefinitionSimple pattern = createExcludePattern(readOnly);

        PropertyDefinitionMap pd = new PropertyDefinitionMap(name, description, required, path, pattern);
        pd.setDisplayName("Exclude");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(0);
        return pd;
    }

    private static PropertyDefinitionSimple createExcludePath(boolean readOnly) {
        String name = PROP_PATH;
        String description = "A file system path that can be a directory or a file. The path is assumed to be relative to the base directory of the drift definition.";
        boolean required = true;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Path");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(0);
        pd.setAllowCustomEnumeratedValue(false);
        return pd;
    }

    private static PropertyDefinitionSimple createExcludePattern(boolean readOnly) {
        String name = PROP_PATTERN;
        String description = "Pathname pattern that must match for the items in the directory path to be excluded.";
        boolean required = false;
        PropertySimpleType type = PropertySimpleType.STRING;

        PropertyDefinitionSimple pd = new PropertyDefinitionSimple(name, description, required, type);
        pd.setDisplayName("Pattern");
        pd.setReadOnly(readOnly);
        pd.setSummary(true);
        pd.setOrder(1);
        pd.setAllowCustomEnumeratedValue(false);
        return pd;
    }

}
