public static void validEmailAddress(String addr, boolean personal) throws ServiceException {
    if (addr.indexOf('@') == -1)
      throw AccountServiceException.INVALID_ATTR_VALUE(
          "address '" + addr + "' does not include domain", null);

    try {
      InternetAddress ia = new JavaMailInternetAddress(addr, true);
      // is this even needed?
      ia.validate();
      if (!personal && ia.getPersonal() != null && !ia.getPersonal().equals(""))
        throw AccountServiceException.INVALID_ATTR_VALUE("invalid email address: " + addr, null);
    } catch (AddressException e) {
      throw AccountServiceException.INVALID_ATTR_VALUE("invalid email address: " + addr, e);
    }
  }
  protected void checkValue(String value, Map attrsToModify) throws ServiceException {

    // means to delete/unset the attribute
    if (value == null || value.equals("")) return;

    switch (mType) {
      case TYPE_BOOLEAN:
        if ("TRUE".equals(value) || "FALSE".equals(value)) return;
        else
          throw AccountServiceException.INVALID_ATTR_VALUE(mName + " must be TRUE or FALSE", null);
      case TYPE_BINARY:
      case TYPE_CERTIFICATE:
        byte[] binary = ByteUtil.decodeLDAPBase64(value);
        if (binary.length > mMax)
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " value length(" + binary.length + ") larger than max allowed: " + mMax,
              null);
        return;
      case TYPE_DURATION:
        if (!DURATION_PATTERN.matcher(value).matches())
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " " + DURATION_PATTERN_DOC, null);
        long l = DateUtil.getTimeInterval(value, 0);
        if (l < mMin)
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " is shorter than minimum allowed: " + mMinDuration, null);
        if (l > mMax)
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " is longer than max allowed: " + mMaxDuration, null);
        return;
      case TYPE_EMAIL:
        if (value.length() > mMax)
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " value length(" + value.length() + ") larger than max allowed: " + mMax,
              null);
        validEmailAddress(value, false);
        return;
      case TYPE_EMAILP:
        if (value.length() > mMax)
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " value length(" + value.length() + ") larger than max allowed: " + mMax,
              null);
        validEmailAddress(value, true);
        return;
      case TYPE_CS_EMAILP:
        if (value.length() > mMax)
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " value length(" + value.length() + ") larger than max allowed: " + mMax,
              null);
        String[] emails = value.split(",");
        for (String email : emails) validEmailAddress(email, true);
        return;
      case TYPE_ENUM:
        if (mEnumSet.contains(value)) return;
        else
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " must be one of: " + mValue, null);
      case TYPE_GENTIME:
        if (GENTIME_PATTERN.matcher(value).matches()) return;
        else
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " must be a valid generalized time: yyyyMMddHHmmssZ", null);
      case TYPE_ID:
        // For bug 21776 we check format for id only if the Provisioning class mandates
        // that all attributes of type id must be an UUID.
        //
        if (!Provisioning.getInstance().idIsUUID()) return;

        if (ID_PATTERN.matcher(value).matches()) return;
        else throw AccountServiceException.INVALID_ATTR_VALUE(mName + " must be a valid id", null);
      case TYPE_INTEGER:
        try {
          int v = Integer.parseInt(value);
          if (v < mMin)
            throw AccountServiceException.INVALID_ATTR_VALUE(
                mName + " value(" + v + ") smaller than minimum allowed: " + mMin, null);
          if (v > mMax)
            throw AccountServiceException.INVALID_ATTR_VALUE(
                mName + " value(" + v + ") larger than max allowed: " + mMax, null);
          return;
        } catch (NumberFormatException e) {
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " must be a valid integer: " + value, e);
        }
      case TYPE_LONG:
        try {
          long v = Long.parseLong(value);
          if (v < mMin)
            throw AccountServiceException.INVALID_ATTR_VALUE(
                mName + " value(" + v + ") smaller than minimum allowed: " + mMin, null);
          if (v > mMax)
            throw AccountServiceException.INVALID_ATTR_VALUE(
                mName + " value(" + v + ") larger than max allowed: " + mMax, null);
          return;
        } catch (NumberFormatException e) {
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " must be a valid long: " + value, e);
        }
      case TYPE_PORT:
        try {
          int v = Integer.parseInt(value);
          if (v >= 0 && v <= 65535) return;
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " must be a valid port: " + value, null);
        } catch (NumberFormatException e) {
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " must be a valid port: " + value, null);
        }
      case TYPE_STRING:
      case TYPE_ASTRING:
      case TYPE_OSTRING:
      case TYPE_CSTRING:
      case TYPE_PHONE:
        if (value.length() > mMax)
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " value length(" + value.length() + ") larger than max allowed: " + mMax,
              null);
        // TODO
        return;
      case TYPE_REGEX:
        if (mRegex.matcher(value).matches()) return;
        else
          throw AccountServiceException.INVALID_ATTR_VALUE(
              mName + " must match the regex: " + mValue, null);
      default:
        ZimbraLog.misc.warn("unknown type(" + mType + ") for attribute: " + value);
        return;
    }
  }