/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * https://opensso.dev.java.net/public/CDDLv1.0.html or
 * opensso/legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at opensso/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * $Id: AttributeSchema.java,v 1.13 2009/01/13 06:56:08 mahesh_prasad_r Exp $
 *
 * Portions Copyrighted 2014-2016 ForgeRock AS.
 */

package com.sun.identity.sm;

import java.security.AccessController;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.xml.XMLUtils;
import com.iplanet.sso.SSOException;
import com.sun.identity.security.EncodeAction;

/**
 * The class <code>AttributeSchema</code> provides methods to access the
 * schema of a configuration parameter. Also, it provides methods to set default
 * and choice values.
 *
 * @supported.all.api
 */
public class AttributeSchema {
    // Debug
    private Debug debug = Debug.getInstance("amSMS");

    // Instance variable
    ServiceSchemaManager ssm;

    ServiceSchema ss;

    PluginSchema ps;

    AttributeSchemaImpl as;

    /**
     * Constructor. Makes it private so that it cannot be instantiated.
     */
    private AttributeSchema() {
    }

    /**
     * Constructor used by ServiceSchema to instantiate
     * <code>AttributeSchema</code> objects.
     */
    protected AttributeSchema(AttributeSchemaImpl as, ServiceSchemaManager ssm,
            ServiceSchema ss) {
        this.ssm = ssm;
        this.ss = ss;
        this.as = as;
        if (as == null) {
            debug.error("AttributeSchema:: IMPL is NULL");
        }
    }

    protected AttributeSchema(AttributeSchemaImpl as, PluginSchema ps) {
        this.as = as;
        this.ps = ps;
    }

    /**
     * Returns the name of the attribute.
     * 
     * @return the name of the attribute
     */
    public String getName() {
        return (as.getName());
    }

    /**
     * Returns the type of the attribute.
     * 
     * @return the type of the attribute
     */
    public AttributeSchema.Type getType() {
        return (as.getType());
    }

    /**
     * Returns Service Schema.
     * 
     * @return Service Schema.
     */
    public ServiceSchema getServiceSchema() {
        return ss;
    }

    /**
     * Sets the type.
     * 
     * @param type
     *            to be changed to
     * @throws SMSException
     *             if an error is encountered when trying to set
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setType(String type) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_TYPE, type);
    }

    /**
     * Returns the list order of the attribute.
     *
     * @return The list order of the attribute, or {@code null} if the list order is not defined.
     */
    public AttributeSchema.ListOrder getListOrder() {
        return as.getListOrder();
    }

    /**
     * Returns the UI type of the attribute.
     * 
     * @return the UI type of the attribute; or null if the UI Type is not
     *         defined
     */
    public AttributeSchema.UIType getUIType() {
        return (as.getUIType());
    }

    /**
     * Sets the <code>UIType</code> attribute.
     * 
     * @param uiType
     *            user interface type.
     * @throws SMSException
     *             if an error is encountered when trying to set
     *             <code>UIType</code> to the attribute schema.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setUIType(String uiType) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_UITYPE, uiType);
    }

    /**
     * Returns the syntax of the attribute.
     * 
     * @return the syntax of the attribute
     */
    public AttributeSchema.Syntax getSyntax() {
        return (as.getSyntax());
    }

    /**
     * Sets the Syntax attribute.
     * 
     * @param synt
     *            syntax
     * @throws SMSException
     *             if an error is encountered when trying to set the attribute
     *             syntax
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setSyntax(String synt) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_SYNTAX, synt);
    }

    /**
     * Returns the I18N key to describe the configuration attribute.
     * 
     * @return the I18N key to describe the configuration attribute
     */
    public String getI18NKey() {
        return (as.getI18NKey());
    }

    /**
     * Returns the order of this attribute.
     *
     * @return the order of this attribute, null if not defined
     */
    public Integer getOrder() {
        return as.getOrder();
    }

    /**
     * Sets the I18N key to describe the configuration attribute.
     * 
     * @param i18nKey
     *            the I18N key to describe the attribute
     * @throws SMSException
     *             if an error is encountered when trying to set I18N key to the
     *             attribute schema
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setI18NKey(String i18nKey) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.I18N_KEY, i18nKey);
    }

    /**
     * Sets the order of this attribute.
     *
     * @param order
     *            the integer value representing the order
     * @throws SMSException
     *             if an error is encountered
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setOrder(Integer order) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ORDER, String.valueOf(order));
    }

    /**
     * Returns the value of the <code>cosQualifier</code> for this attribute
     * that is <code>default, override, operational or merge-cos</code>.
     * 
     * @return the value of the <code>cosQualifier</code>.
     */
    public String getCosQualifier() {
        return (as.getCosQualifier());
    }

    /**
     * Sets the <code>cosQualifier</code> attribute
     * 
     * @param cosq
     *            value of <code>cosQualifier</code>.
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setCosQualifier(String cosq) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_COS_QUALIFIER, cosq);
    }

    /**
     * Returns the default values of the attribute. If there are no default
     * values defined for this attribute in the schema then this method returns
     * a Collections.EMPTY_SET
     * 
     * @return set of default values of the attribute
     */
    public Set getDefaultValues() {
        return (as.getDefaultValues());
    }

    /**
     * Returns the default values of the attribute for the given environment
     * parameters. If there are no default values defined for this attribute in
     * the schema then this method returns a Collections.EMPTY_SET
     * 
     * @param envParams
     *            Map of environment parameter to a set of values
     * @return set of default values of the attribute
     */
    public Set getDefaultValues(Map envParams) {
        return (as.getDefaultValues(envParams));
    }

    /**
     * Returns the example values of the attribute. If there are no example
     * values defined for this attribute in the schema then this method returns
     * a Collections.EMPTY_SET
     *
     * @return set of example values of the attribute
     */
    public Set<String> getExampleValues() {
        return (as.getExampleValues());
    }

    /**
     * Sets the default values of the attribute.
     * 
     * @param values
     *            the set of default values
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setDefaultValues(Set values) throws SMSException, SSOException {
        updateDefaultValues(values);
    }

    /**
     * Protected method to set the default values in the given XML document.
     * 
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    void setDefaultValues(Set values, Document document) throws SMSException,
            SSOException {
        updateDefaultValues(values, document);
    }

    /**
     * Adds a default value to the existing set of default values.
     * 
     * @param value
     *            the default value to add
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void addDefaultValue(String value) throws SMSException, SSOException
    {
        Set defaultValues = getDefaultValues();
        if (defaultValues != Collections.EMPTY_SET) {
            defaultValues.add(value);
        } else {
            defaultValues = new HashSet();
            defaultValues.add(value);
        }
        updateDefaultValues(defaultValues);
    }

    /**
     * Removes the all the default values for the attribute.
     * 
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     * 
     */
    public void removeDefaultValues() throws SMSException, SSOException {
        updateDefaultValues(new HashSet());
    }

    /**
     * Removes the given value from the set of default values.
     * 
     * @param value
     *            the default value to remove
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void removeDefaultValue(String value) throws SMSException,
            SSOException {
        Set defaultValues = getDefaultValues();
        if (defaultValues != Collections.EMPTY_SET) {
            defaultValues.remove(value);
            updateDefaultValues(defaultValues);
        }
    }

    /**
     * Returns the possible choice values for the attribute if the attribute
     * type is either <code>SINGLE_CHOICE</code> or
     * <code>MULTIPLE_CHOICE</code>.
     * 
     * @return set of possible choice values
     */
    public String[] getChoiceValues() {
        return (as.getChoiceValues());
    }

    /**
     * Returns the possible choice values for the attribute if the attribute
     * type is either <code>SINGLE_CHOICE</code> or
     * <code>MULTIPLE_CHOICE</code>, for the given environment parameters.
     *
     * @param envParams
     *            Map of environment parameter to a set of values
     * @return set of possible choice values
     */
    public String[] getChoiceValues(Map envParams) {
        return (as.getChoiceValues(envParams));
    }

    /**
     * Returns the possible choice values for the attribute if the attribute
     * type is either <code>SINGLE_CHOICE</code> or
     * <code>MULTIPLE_CHOICE</code>, for the given environment parameters,
     * along with the values' i18n keys.
     *
     * @param envParams
     *            Map of environment parameter to a set of values
     * @return Map of value to i18n key.
     */
    public Map getChoiceValuesMap(Map envParams) {
        return as.getChoiceValuesMap(envParams);
    }

    /**
     * Indicates whether this AttributeSchema has choice values defined.
     */
    public boolean hasChoiceValues() {
        return as.hasChoiceValues();
    }

    /**
     * Returns the I18N key for the given choice value.
     * 
     * @param cValue
     *            choice value.
     * @return the I18N key for the given choice value
     */
    public String getChoiceValueI18NKey(String cValue) {
        return (as.getChoiceValueI18NKey(cValue));
    }

    /**
     * Adds a choice value and its i18n key to the existing set of choice
     * values.
     * 
     * @param value
     *            the choice value to add
     * @param i18nKey
     *            the I18N key for the choice value
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void addChoiceValue(String value, String i18nKey)
            throws SMSException, SSOException {
        Map choiceValues = as.getChoiceValuesMap();
        choiceValues.put(value, i18nKey);
        updateChoiceValues(choiceValues);
    }

    /**
     * Removes the given value from the set of choice values.
     * 
     * @param value
     *            the choice value to remove
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void removeChoiceValue(String value) throws SMSException,
            SSOException {
        Map choiceValues = as.getChoiceValuesMap();
        if (choiceValues.remove(value) != null) {
            updateChoiceValues(choiceValues);
        }
    }

    /**
     * Returns the start range if the attribute syntax is either
     * <code>NUMBER_RANGE</code> or <code>DECIMAL_RANGE</code>.
     * 
     * @return the start range for the attribute value
     */
    public String getStartRange() {
        return (as.getStartRange());
    }

    /**
     * Sets the start range attribute.
     * 
     * @param stRange
     *            start range.
     * @throws SMSException
     *             if an error is encountered when trying to set
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setStartRange(String stRange) throws SMSException, SSOException
    {
        updateXMLDocument(SMSUtils.ATTRIBUTE_RANGE_START, stRange);
    }

    /**
     * Returns the end range if the attribute syntax is either
     * <code>NUMBER_RANGE</code> or <code>DECIMAL_RANGE</code>.
     * 
     * @return the end range for the attribute value
     */
    public String getEndRange() {
        return (as.getEndRange());
    }

    /**
     * Sets the end range Attribute.
     * 
     * @param edRange
     *            end range.
     * @throws SMSException
     *             if an error is encountered when trying to set
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setEndRange(String edRange) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_RANGE_END, edRange);
    }

    /**
     * Method to get the validator name for using to validate this service
     * attribute
     * 
     * @return the validator name
     */
    public String getValidator() {
        return (as.getValidator());
    }

    /**
     * Sets the Validator attribute
     * 
     * @param valid
     *            validator
     * @throws SMSException
     *             if an error is encountered when trying to set
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setValidator(String valid) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_VALIDATOR, valid);
    }

    /**
     * Returns the minimum number of values for the attribute if the attribute
     * is of type <code>MULTIPLE_CHOICE</code>.
     * 
     * @return the minimum number of values
     */
    public int getMinValue() {
        return (as.getMinValue());
    }

    /**
     * Sets the minimum value attribute.
     * 
     * @param minV
     *            minimum value.
     * @throws SMSException
     *             if an error is encountered when trying to set
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setMinValue(String minV) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_MIN_VALUE, minV);
    }

    /**
     * Returns the maximum number of values for the attribute if the attribute
     * is of type <code>MULTIPLE_CHOICE</code>.
     * 
     * @return the maximum number of values
     */
    public int getMaxValue() {
        return (as.getMaxValue());
    }

    /**
     * Sets the maximum value attribute.
     * 
     * @param maxV
     *            maximum value.
     * @throws SMSException
     *             if an error is encountered when trying to set
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setMaxValue(String maxV) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_MAX_VALUE, maxV);
    }

    /**
     * Sets the boolean values of the attribute.
     *
     * @param trueValue string value for <code>BooleanTrueValue</code>.
     * @param trueValueI18nKey <code>I18N</code> key for
     *        <code>BooleanTrueValue</code>.
     * @param falseValue string value for <code>BooleanFalseValue</code>.
     * @param falseValueI18nKey <code>I18N</code> Key for
     *        <code>BooleanFalseValue</code>.
     * @throws SMSException if an error is encountered when trying to  set.
     * @throws SSOException if the single sign on token is invalid or expired
     */
    public void setBooleanValues(
        String trueValue,
        String trueValueI18nKey,
        String falseValue,
        String falseValueI18nKey
    ) throws SSOException, SMSException {
      updateBooleanValues(trueValue, trueValueI18nKey,
            falseValue, falseValueI18nKey, null);
    }


    /**
     * Returns the string value for <code>BooleanTrueValue</code>.
     * 
     * @return the string value for <code>BooleanTrueValue</code>.
     */
    public String getTrueValue() {
        return (as.getTrueValue());
    }

    /**
     * Returns the <code>I18N</code> key for <code>BooleanTrueValue</code>.
     * 
     * @return the <code>I18N</code> key for <code>BooleanTrueValue</code>.
     */
    public String getTrueValueI18NKey() {
        return (as.getTrueValueI18NKey());
    }

    /**
     * Returns the string value for <code>BooleanFalseValue</code>.
     * 
     * @return the string value for <code>BooleanFalseValue</code>.
     */
    public String getFalseValue() {
        return (as.getFalseValue());
    }

    /**
     * Returns the <code>I18N</code> Key for <code>BooleanFalseValue</code>.
     * 
     * @return the <code>I18N</code> Key for <code>BooleanFalseValue</code>.
     */
    public String getFalseValueI18NKey() {
        return (as.getFalseValueI18NKey());
    }

    /**
     * Returns true if the attribute is an optional attribute.
     * 
     * @return true if the attribute is an optional attribute.
     */
    public boolean isOptional() {
        return (as.isOptional());
    }

    /**
     * Returns true if the attribute is a service identifier (i.e., in the case
     * of LDAP it would be the COS Specifier attribute).
     * 
     * @return true if the attribute is service identifier attribute.
     */
    public boolean isServiceIdentifier() {
        return (as.isServiceIdentifier());
    }

    /**
     * Checks if the attribute allows to have resource name.
     * 
     * @return true if the attribute allows to have resource name; false
     *         otherwise
     */
    public boolean isResourceNameAllowed() {
        return (as.isResourceNameAllowed());
    }

    /**
     * Returns true if the attribute is a service's status attribute.
     * 
     * @return true if the attribute is a status attribute.
     */
    public boolean isStatusAttribute() {
        return (as.isStatusAttribute());
    }

    /**
     * Method to get service specific attributes. It return the value of the
     * "any" attribute, if set in the XML schema for the service
     * 
     * @return value of "any" attribute
     */
    public String getAny() {
        return (as.getAny());
    }

    /**
     * Sets the any attribute.
     * 
     * @param a
     *            value for any attribute.
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired.
     */
    public void setAny(String a) throws SMSException, SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_ANY, a);
    }

    /**
     * Returns URL of the view bean for the attribute.
     * 
     * @return URL for view bean
     */
    public String getPropertiesViewBeanURL() {
        return (as.getPropertiesViewBeanURL());
    }

    /**
     * Sets the URL of the view bean for the attribute.
     * 
     * @param prop
     *            properties view bean URL.
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired.
     */
    public void setPropertiesViewBeanUR(String prop) throws SMSException,
            SSOException {
        updateXMLDocument(SMSUtils.ATTRIBUTE_VIEW_BEAN_URL, prop);
    }

    /**
     * Returns <code>true</code> if the attribute is searchable;
     * <code>false</code> otherwise
     * 
     * @return <code>true</code> if the attribute is an optional attribute;
     *         <code>false</code> otherwise
     */
    public boolean isSearchable() {
        return (as.isSearchable());
    }

    /**
     * Sets the attribute isSearchable, if value is set to <code>true
     * </code>,
     * or <code>false</code>.
     * 
     * @param value
     *            if set to <code>true</code> the attribute will be
     *            searchable; else searches cannot be performed on this
     *            attribute.
     * @throws SMSException
     *             if an error is encountered when trying to set
     * @throws SSOException
     *             if the single sign on token is invalid or expired
     */
    public void setSearchable(String value) throws SMSException, SSOException {
        if ((!(value.toLowerCase()).equals("yes"))
                && (!(value.toLowerCase()).equals("no"))) {
            String[] arg = { value };
            debug.error("AttributeSchema: Invalid isSearchable value");
            throw new SMSException(SMSEntry.bundle
                    .getString("sms-invalid-searchable-value")
                    + ":" + arg, "sms-invalid-searchable-value");
        }
        updateXMLDocument(SMSUtils.ISSEARCHABLE, value);
    }

    /**
     * Returns the name of this attribute when used in a CREST representation.
     */
    public String getResourceName() {
        String resourceName = as.getResourceName();
        return resourceName == null ? getName() : resourceName;
    }

    /**
     * Sets the CREST representation name for the attribute.
     *
     * @param name
     *            the name of the CREST property.
     * @throws SMSException
     *             if an error is encountered when trying to set.
     * @throws SSOException
     *             if the single sign on token is invalid or expired.
     */
    public void setResourceName(String name) throws SSOException, SMSException {
        updateXMLDocument(SMSUtils.RESOURCE_NAME, name);
    }

    /**
     * Returns a string representation of this <code> AttributeSchema </code>
     * object.
     * 
     * @return String representation of this object
     */
    public String toString() {
        return (as.toString());
    }

    /**
     * Method for modifying default values
     */
    protected void updateDefaultValues(Set defaultValues) throws SMSException,
            SSOException {
        updateDefaultValues(defaultValues, null);
    }

    /**
     * Method for modifying default values given the XML document
     */
    protected void updateDefaultValues(Set defaultValues, Document doc)
            throws SMSException, SSOException {
        // Check if the values are valid
        if (ss != null) {
            Map tempattrs = new HashMap(1);
            tempattrs.put(getName(), defaultValues);
            ss.validateAttributes(tempattrs);
        }

        // Check if the attributes have to be encoded
        boolean encode = false;
        if (getSyntax().equals(Syntax.PASSWORD)
                || getSyntax().equals(Syntax.ENCRYPTED_PASSWORD)) {
            encode = true;
        }

        // Construct DefaultValues node
        StringBuffer sb = new StringBuffer(100);
        sb.append(XML_PREFIX).append(DEFAULT_VALUES_BEGIN);
        Iterator items = defaultValues.iterator();
        while (items.hasNext()) {
            sb.append(VALUE_BEGIN);
            if (encode) {
                String encString = (String) items.next();
                try {
                    encString = (String) AccessController
                            .doPrivileged(new EncodeAction(encString));
                } catch (Throwable e) {
                    debug.error("AttributeSchema: Unable to encode", e);
                }
                sb.append(encString);
            } else {
                sb.append(SMSSchema.escapeSpecialCharacters((String) items
                        .next()));
            }
            sb.append(VALUE_END);
        }
        sb.append(DEFAULT_VALUES_END);
        updateXMLDocument(sb, SMSUtils.ATTRIBUTE_DEFAULT_ELEMENT, doc);
    }

    protected void updateChoiceValues(Map choiceValues) throws SMSException,
            SSOException {
        updateChoiceValues(choiceValues, null);
    }

     protected void updateChoiceValues(Map choiceValues, Document doc)
            throws SMSException, SSOException {
        // Construct ChoiceValues
        StringBuffer sb = new StringBuffer(100);
        sb.append(XML_PREFIX).append(CHOICE_VALUES_BEGIN);
        Iterator items = choiceValues.keySet().iterator();
        while (items.hasNext()) {
            String[] vals = new String[2];
            String value = SMSSchema.escapeSpecialCharacters((String) items
                    .next());
            String i18nKey = (String) choiceValues.get(value);
            if (i18nKey == null) {
                vals[0] = value;
                sb.append(MessageFormat.format(CHOICE_VALUE, (Object[])vals));
            } else {
                vals[0] = i18nKey;
                vals[1] = value;
                sb.append(MessageFormat.format(
                    CHOICE_VALUE_KEY, (Object[])vals));
            }
        }
        sb.append(CHOICE_VALUES_END);
        updateXMLDocument(sb, SMSUtils.ATTRIBUTE_CHOICE_VALUES_ELEMENT, doc);
    }

    protected void updateBooleanValues(
        String trueValue,
        String trueValueI18nKey,
        String falseValue,
        String falseValueI18nKey,
        Document doc
    ) throws SMSException, SSOException {
      // Construct BooleanValues
      StringBuffer sb = new StringBuffer(100);
      sb.append(XML_PREFIX).append(BOOLEAN_VALUES_BEGIN);

      String[] trueVals = new String[2];
        if ((trueValueI18nKey != null) && (trueValue != null)) {
            trueVals[0] = trueValueI18nKey;
            trueVals[1] = SMSSchema.escapeSpecialCharacters(trueValue);
        } else {
            trueVals[0] = getTrueValueI18NKey();
            trueVals[1] = getTrueValue();
        }
        sb.append(MessageFormat.format(TRUE_BOOLEAN_KEY, (Object[])trueVals));

      String[] falseVals = new String[2];
      if ((falseValueI18nKey != null) && (falseValue != null)) {
          falseVals[0] = falseValueI18nKey;
          falseVals[1] = SMSSchema.escapeSpecialCharacters(falseValue);
      } else {
            falseVals[0] = getFalseValueI18NKey();
            falseVals[1] = getFalseValue();
      }
      sb.append(MessageFormat.format(FALSE_BOOLEAN_KEY, (Object[])falseVals));

      sb.append(BOOLEAN_VALUES_END);
      updateXMLDocument(sb, SMSUtils.ATTRIBUTE_BOOLEAN_VALUES_ELEMENT, doc);
    }

    protected void updateXMLDocument(StringBuffer sb, String elementName,
            Document updateDoc) throws SMSException, SSOException {
        // Update the default element in XML
        try {
            // Construct the XML document
            Document doc = SMSSchema.getXMLDocument(sb.toString(), false);
            Node node = XMLUtils.getRootNode(doc, elementName);

            // Convert to Schema's document
            Document schemaDoc = null;
            if (updateDoc != null) {
                schemaDoc = updateDoc;
            } else if (ssm != null) {
                schemaDoc = ssm.getDocumentCopy();
            } else {
                schemaDoc = ps.getDocumentCopy();
            }
            Node nNode = schemaDoc.importNode(node, true);

            // Traverse the document to get this attribute element
            Node schemaNode = null;
            if (ss != null) {
                schemaNode = ss.getSchemaNode(schemaDoc);
            } else {
                schemaNode = ps.getPluginSchemaNode(schemaDoc);
            }
            Node attrSchemaNode = XMLUtils.getNamedChildNode(schemaNode,
                    SMSUtils.SCHEMA_ATTRIBUTE, SMSUtils.NAME, getName());

            // Try getting OrganizationAttributeSchema if AttributeSchema
            // node is not there within Organization node.
            // This will be a special case for idrepo service.
            if (attrSchemaNode == null) {
                schemaNode = ss.getOrgAttrSchemaNode(schemaDoc);
                attrSchemaNode = XMLUtils.getNamedChildNode(schemaNode,
                    SMSUtils.SCHEMA_ATTRIBUTE, SMSUtils.NAME, getName());
            }
            Node oNode = XMLUtils.getChildNode(attrSchemaNode, elementName);
            if (oNode != null) {
                attrSchemaNode.replaceChild(nNode, oNode);
            } else {
                attrSchemaNode.appendChild(nNode);
            }
            // Update the schema in the directory
            if (updateDoc != null) {
                // do nothing
            } else if (ssm != null) {
                ssm.replaceSchema(schemaDoc);
            } else {
                ps.replacePluginSchema(schemaDoc);
            }
        } catch (Exception e) {
            throw (new SMSException(e.getMessage(), e,
                    "sms-cannot-update-xml-document"));
        }
    }

    /**
     * update attribute value in attribute schema element
     */
    protected void updateXMLDocument(String attrName, String attrValue)
            throws SMSException, SSOException {
        // Update the default element in XML
        try {
            // Construct the XML document
            Document schemaDoc = null;
            if (ssm != null) {
                schemaDoc = ssm.getDocumentCopy();
            } else {
                schemaDoc = ps.getDocumentCopy();
            }

            // Traverse the document to get this attribute element
            Node schemaNode = null;
            if (ss != null) {
                schemaNode = ss.getSchemaNode(schemaDoc);
            } else {
                schemaNode = ps.getPluginSchemaNode(schemaDoc);
            }

            Node attrSchemaNode = XMLUtils.getNamedChildNode(schemaNode,
                    SMSUtils.SCHEMA_ATTRIBUTE, SMSUtils.NAME, getName());
            ((Element) attrSchemaNode).setAttribute(attrName, attrValue);

            // Update the schema in the directory
            if (ssm != null) {
                ssm.replaceSchema(schemaDoc);
            } else {
                ps.replacePluginSchema(schemaDoc);
            }
        } catch (Exception e) {
            throw (new SMSException(e.getMessage(), e,
                    "sms-cannot-update-xml-document"));
        }
    }

    /**
     * The class <code>Type</code> defines the types of schema attributes and
     * provides static constants for these types. This could also be viewed as a
     * higher level structured data types like Set, List, etc. The primitive
     * data types are defined by <code>Syntax</code>. Currently defined
     * schema attribute types are <code>SINGLE</code>, <code>LIST</code>,
     * <code>SINGLE_CHOICE</code>, <code>MULTIPLE_CHOICE</code>,
     * <code>SIGNATURE</code> and <code>VALIDATOR</code>.
     */
    public static class Type extends Object {

        /**
         * The <code>SINGLE</code> attribute type specifies that the attribute
         * can have only a single value.
         */
        public static final Type SINGLE = new Type("single");

        /**
         * The <code>LIST</code> attribute type specifies that the attribute
         * can have multiple values, i.e., multi-valued attribute.
         */
        public static final Type LIST = new Type("list");

        /**
         * The <code>SINGLE_CHOICE</code> attribute type specifies that the
         * attribute can have value defined by the <code>getChoiceValues</code>
         * method of <code>AttributeSchema</code>.
         */
        public static final Type SINGLE_CHOICE = new Type("single_choice");

        /**
         * The <code>MULTIPLE_CHOICE</code> attribute type specifies that the
         * attribute can have multiple values defined by the
         * <code>getChoiceValues</code> method of <code>AttributeSchema</code>.
         */
        public static final Type MULTIPLE_CHOICE = new Type("multiple_choice");

        /**
         * The <code>SIGNATURE</code> attribute type specifies that the
         * attribute is a signing attribute.
         */
        public static final Type SIGNATURE = new Type("signature");

        /**
         * The <code>VALIDATOR</code> attribute type specifies that the
         * attribute defines a attribute validator plugin.
         */
        public static final Type VALIDATOR = new Type("validator");

        private String attrType;

        private Type() {
        }

        private Type(String type) {
            attrType = type;
        }

        /**
         * The method returns the string representation of the schema attribute
         * type.
         * 
         * @return String string representation of schema attribute type
         */
        public String toString() {
            return attrType;
        }

        /**
         * Method to check if two schema attribute types are equal.
         * 
         * @param schemaAttrType
         *            the reference object with which to compare
         * 
         * @return <code>true</code> if the objects are same; <code>
         * false</code>
         *         otherwise
         */
        public boolean equals(Object schemaAttrType) {
            if (schemaAttrType instanceof Type) {
                Type s = (Type) schemaAttrType;
                return (s.attrType.equals(attrType));
            }
            return (false);
        }

        /**
         * Returns a hash code value for the object.
         * 
         * @return a hash code value for the object
         */
        public int hashCode() {
            return attrType.hashCode();
        }
    }

    /**
     * This enum {@code ListOrder} defines the list orders of schema attributes and provides constants for these list
     * orders. These types will mainly be used by the GUI to determine how to display the schema attributes.
     */
    public enum ListOrder {
        /** Orders lists naturally. */
        NATURAL,
        /** Orders lists in the order they are entered in LDAP. */
        INSERTION
    }

    /**
     * The class <code>UIType</code> defines the UI types of schema attributes
     * and provides static constants for these types. These types mainly will be
     * used by the GUI to determine how to display the schema attributes.
     * Currently defined schema attribute UI types are <code>RADIO</code>,
     * <code>LINK</code>, <code>BUTTON</code> and
     * <code>NAME_VALUE_LIST</code>
     */
    public static class UIType extends Object {

        /**
         * The <code>RADIO</code> attribute type specifies that the attribute
         * should be display as radio button.
         */
        public static final UIType RADIO = new UIType("radio");

        /**
         * The <code>LINK</code> attribute type specifies that the attribute
         * should be display as a link.
         */
        public static final UIType LINK = new UIType("link");

        /**
         * The <code>BUTTON</code> attribute type specifies that the attribute
         * should be display as a button.
         */
        public static final UIType BUTTON = new UIType("button");

        /**
         * The <code>NAME_VALUE_LIST</code> attribute type specifies that the
         * attribute should be display as a name value list widget.
         */
        public static final UIType NAME_VALUE_LIST = new UIType(
            "name_value_list");

        /**
         * The <code>UNORDERED_LIST</code> attribute type specifies that the
         * attribute should be display as an unordered list widget.
         */
        public static final UIType UNORDEREDLIST = new UIType("unorderedlist");

        /**
         * The <code>ORDERED_LIST</code> attribute type specifies that the
         * attribute should be display as an ordered list widget.
         */
        public static final UIType ORDEREDLIST = new UIType("orderedlist");

        /**
         * The <code>MAP_LIST</code> attribute type specifies that the
         * attribute should be display as an map list widget.
         */
        public static final UIType MAPLIST = new UIType("maplist");

        /**
         * The <code>GLOBALMAP_LIST</code> attribute type specifies that the
         * attribute should be display as a global map list widget.
         */
        public static final UIType GLOBALMAPLIST = new UIType("globalmaplist");
        
        /**
         * The <code>ADDREMOVELIST</code> attribute type specifies that the
         * multiple choice attribute should be display as add remove list
         * widget.
         */
        public static final UIType ADDREMOVELIST = new UIType("addremovelist");

        /**
         * The <code>SCRIPTSELECT</code> attribute type specifies that the
         * attribute should be display as drop down widget.
         */
        public static final UIType SCRIPTSELECT = new UIType("scriptSelect");

        /**
         * The <code>GLOBALSCRIPTSELECT</code> attribute type specifies that the
         * attribute should be display as drop down widget with only global scripts.
         */
        public static final UIType GLOBALSCRIPTSELECT = new UIType("globalScriptSelect");

        private String attrType;

        private UIType() {
        }

        private UIType(String type) {
            attrType = type;
        }

        /**
         * The method returns the string representation of the schema attribute
         * UI type.
         * 
         * @return String string representation of schema attribute UI type
         */
        public String toString() {
            return attrType;
        }

        /**
         * Method to check if two schema attribute UI types are equal.
         * 
         * @param schemaAttrType
         *            the reference object with which to compare
         * 
         * @return <code>true</code> if the objects are same; <code>
         * false</code>
         *         otherwise
         */
        public boolean equals(Object schemaAttrType) {
            if (schemaAttrType instanceof UIType) {
                UIType s = (UIType) schemaAttrType;
                return (s.attrType.equals(attrType));
            }
            return (false);
        }

        /**
         * Returns a hash code value for the object.
         * 
         * @return a hash code value for the object
         */
        public int hashCode() {
            return attrType.hashCode();
        }
    }

    /**
     * The class <code>Syntax</code> defines the syntax of the schema
     * attributes and provides static constants for these types. In other words,
     * this class defines the primitive data types for the schema attributes.
     */
    public static class Syntax {

        /**
         * The <code>BOOLEAN</code> attribute syntax specifies that the
         * attribute is of boolean type, i.e., can have a value of either
         * <code>true</code> or <code>
         * false</code>
         */
        public static final Syntax BOOLEAN = new Syntax("boolean");

        /**
         * The <code>EMAIL</code> attribute syntax specifies that the
         * attribute is a email address.
         */
        public static final Syntax EMAIL = new Syntax("email");

        /**
         * The <code>URL</code> attribute syntax specifies that the attribute
         * is a URL.
         */
        public static final Syntax URL = new Syntax("url");

        /**
         * The <code>STRING</code> attribute syntax specifies that the
         * attribute is of text type, i.e., can have any unicode characters.
         */
        public static final Syntax STRING = new Syntax("string");

        /**
         * The <code>PARAGRAPH</code> attribute syntax specifies that the
         * attribute is of multi-lined text type.
         */
        public static final Syntax PARAGRAPH = new Syntax("paragraph");

        /**
         * The <code>XML</code> attribute syntax specifies that the attribute
         * is of XML type, i.e., can have any unicode characters.
         */
        public static final Syntax XML = new Syntax("xml");

        /**
         * The <code>SCRIPT</code> attribute syntax specifies that the
         * attribute is of multi-lined text type and more specifically a script.
         */
        public static final Syntax SCRIPT = new Syntax("script");

        /**
         * The <code>PASSWORD</code> attribute syntax specifies that the
         * attribute is of password type, will be used by UI to mask the
         * password typed.
         */
        public static final Syntax PASSWORD = new Syntax("password");

        /**
         * The <code>ENCRYPTED PASSWORD</code> attribute syntax specifies that
         * the attribute is of password type, will be used by UI to mask the
         * password typed.
         */
        public static final Syntax ENCRYPTED_PASSWORD = new Syntax(
                "encrypted_password");

        /**
         * The <code>DATE</code> attribute syntax specifies that the attribute
         * is of date type.
         */
        public static final Syntax DATE = new Syntax("date");

        /**
         * The <code>NUMERIC</code> attribute syntax specifies that the
         * attribute is numeric, i.e., can have numbers only.
         */
        public static final Syntax NUMERIC = new Syntax("numeric");

        /**
         * The <code>NUMBER</code> attribute syntax specifies that the
         * attribute is a number.
         */
        public static final Syntax NUMBER = new Syntax("number");

        /**
         * The <code>DECIMAL</code> attribute syntax specifies that the
         * attribute is a decimal value.
         */
        public static final Syntax DECIMAL = new Syntax("decimal");

        /**
         * The <code>PERCENT</code> attribute syntax specifies that the
         * attribute is a percentage.
         */
        public static final Syntax PERCENT = new Syntax("percent");

        /**
         * The <code>NUMBER_RANGE</code> attribute syntax specifies that the
         * attribute is a number within a range.
         */
        public static final Syntax NUMBER_RANGE = new Syntax("number_range");

        /**
         * The <code>DECIMAL_RANGE</code> attribute syntax specifies that the
         * attribute is a decimal number within a range.
         */
        public static final Syntax DECIMAL_RANGE = new Syntax("decimal_range");

        /**
         * The <code>DECIMAL_NUMBER</code> attribute syntax specifies that the
         * attribute is a floating point number, e.g., 1.5, 3.56, etc.
         */
        public static final Syntax DECIMAL_NUMBER = 
            new Syntax("decimal_number");

        /**
         * The <code>DN</code> attribute syntax specifies that the attribute
         * should be an LDAP distinguished name (DN).
         */
        public static final Syntax DN = new Syntax("dn");

        private String attrSyntax;

        private Syntax() {
        }

        private Syntax(String syntax) {
            attrSyntax = syntax;
        }

        /**
         * The method returns the string representation of the schema attribute
         * syntax.
         * 
         * @return String string representation of schema attribute syntax
         */
        public String toString() {
            return (attrSyntax);
        }

        /**
         * Method to check if two schema attribute syntax are equal.
         * 
         * @param schemaAttrSyntax
         *            the reference object with which to compare
         * 
         * @return <code>true</code> if the objects are same; <code>
         * false</code>
         *         otherwise
         */
        public boolean equals(Object schemaAttrSyntax) {
            if (schemaAttrSyntax instanceof Syntax) {
                Syntax s = (Syntax) schemaAttrSyntax;
                return (s.attrSyntax.equals(attrSyntax));
            }
            return (false);
        }

        /**
         * Returns a hash code value for the object.
         * 
         * @return a hash code value for the object
         */
        public int hashCode() {
            return attrSyntax.hashCode();
        }
    }

    private static final String XML_PREFIX = 
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

    private static final String DEFAULT_VALUES_BEGIN = "<"
            + SMSUtils.ATTRIBUTE_DEFAULT_ELEMENT + ">";

    private static final String DEFAULT_VALUES_END = "</"
            + SMSUtils.ATTRIBUTE_DEFAULT_ELEMENT + ">";

    static final String VALUE_BEGIN = "<" + SMSUtils.ATTRIBUTE_VALUE + ">";

    static final String VALUE_END = "</" + SMSUtils.ATTRIBUTE_VALUE + ">";

    private static final String CHOICE_VALUES_BEGIN = "<"
            + SMSUtils.ATTRIBUTE_CHOICE_VALUES_ELEMENT + ">";

    private static final String CHOICE_VALUES_END = "</"
            + SMSUtils.ATTRIBUTE_CHOICE_VALUES_ELEMENT + ">";

    private static final String CHOICE_VALUE_KEY = "<"
            + SMSUtils.ATTRIBUTE_CHOICE_VALUE_ELEMENT + " " + SMSUtils.I18N_KEY
            + "=\"{0}\">{1}</" + SMSUtils.ATTRIBUTE_CHOICE_VALUE_ELEMENT + ">";

    private static final String CHOICE_VALUE = "<"
            + SMSUtils.ATTRIBUTE_CHOICE_VALUE_ELEMENT + ">{0}</"
            + SMSUtils.ATTRIBUTE_CHOICE_VALUE_ELEMENT + ">";

    private static final String BOOLEAN_VALUES_BEGIN =
      "<" + SMSUtils.ATTRIBUTE_BOOLEAN_VALUES_ELEMENT + ">";

    private static final String BOOLEAN_VALUES_END =
      "</" + SMSUtils.ATTRIBUTE_BOOLEAN_VALUES_ELEMENT + ">";

    private static final String TRUE_BOOLEAN_KEY =
      "<" + SMSUtils.ATTRIBUTE_TRUE_BOOLEAN_ELEMENT +
      " " + SMSUtils.I18N_KEY + "=\"{0}\">{1}</" +
      SMSUtils.ATTRIBUTE_TRUE_BOOLEAN_ELEMENT + ">";

    private static final String FALSE_BOOLEAN_KEY =
      "<" + SMSUtils.ATTRIBUTE_FALSE_BOOLEAN_ELEMENT +
      " " + SMSUtils.I18N_KEY + "=\"{0}\">{1}</" +
      SMSUtils.ATTRIBUTE_FALSE_BOOLEAN_ELEMENT + ">";
}
