def configuration(config):
    # Check Bugzilla specific configuration parameters.
    check_config.check_string_or_none(config, 'bugzilla_directory')
    check_config.check_host(config, 'dbms_host')
    check_config.check_int(config, 'dbms_port')
    check_config.check_string(config, 'dbms_database')
    check_config.check_string(config, 'dbms_user')
    check_config.check_string(config, 'dbms_password')
    check_config.check_string(config, 'migrated_user_password')
    check_config.check_list_of(config, 'migrated_user_groups',
                               types.StringType, 'strings')
    check_config.check_list_of(config, 'replicated_fields',
                               types.StringType, 'strings')
    check_config.check_list_of(config, 'omitted_fields',
                               types.StringType, 'strings')
    check_config.check_list_of_string_pairs(config, 'field_names')

    check_bugzilla_directory(config)
    check_field_lists(config)

    # Handle logger.  We need a list of logger objects:
    log_params = {
        'priority': config.log_level,
        'max_length': config.log_max_message_length,
        }
    # The log messages should go to (up to) three places:
    # 1. to standard output (if running from a command line);
    loggers = []
    if config.use_stdout_log:
        loggers.append(apply(logger.file_logger, (), log_params))
    # 2. to the file named by the log_file configuration parameter (if
    # not None);
    if config.log_file != None:
        loggers.append(apply(logger.file_logger,
                             (open(config.log_file, "a"),),
                             log_params))
    # 3. to the Windows event log (if use_windows_event_log is true).
    if os.name == 'nt' and config.use_windows_event_log:
        loggers.append(apply(logger.win32_event_logger,
                             (config.rid,),
                             log_params))
    # 4. to the Unix syslog (if use_system_log is true).
    if os.name == 'posix' and config.use_system_log:
        loggers.append(apply(logger.sys_logger,
                             (),
                             log_params))

    # now a single logger object which logs to all of the logger objects
    # in our list:
    config.logger = logger.multi_logger(loggers)

    # Open a connection to the Bugzilla database.  This makes a DB-API
    # v2.0 connection object.  To work with a database other than
    # MySQL, change this to make an appropriate connection object.
    # Note that in that case changes are also needed in bugzilla.py
    # where we deal with MySQL-specific types such as tinyint.
    db = mysqldb_support.connect(config)

    # Make a Bugzilla DB object.  Note that this same object is used
    # subsequently by the replicator itself.
    config.bugzilla = bugzilla.bugzilla(db, config)

    # Get the types of the 'bugs' table from Bugzilla
    bugs_types = config.bugzilla.get_types('bugs')

    # Check field names against Bugzilla database and construct
    # a map, Bugzilla field name to Perforce field name.
    fields_bz_to_p4 = get_fields_bz_to_p4(config, bugs_types)

    user_name_length = get_user_name_length(config)

    # strict user translator doesn't allow unknown users
    strict_user_translator = dt_bugzilla.user_translator(
        config.replicator_address, config.p4_user, allow_unknown = 0)

    # lax user translator does allow unknown users
    lax_user_translator = dt_bugzilla.user_translator(
        config.replicator_address, config.p4_user, allow_unknown = 1)

    # p4_fields maps Bugzilla field name to the jobspec data (number,
    # name, type, length, dispositon, preset, values, help text,
    # translator).  The fields Job and Date are special: they are not
    # replicated from Bugzilla but are required by Perforce, so we
    # have them here.  Note that their help text is given (the other
    # help texts will be obtained from the bz_field_map).

    p4_fields = { \
        '(JOB)':       ( 101, 'Job', 'word', 32, 'required',
                         None, None,
                         "The job name.",
                         None ),
        '(DATE)':      ( 104, 'Date', 'date', 20, 'always',
                         '$now', None,
                         "The date this job was last modified.",
                         None ),
        # P4DTI fields:
        '(FILESPECS)': ( 191, 'P4DTI-filespecs', 'text', 0, 'optional',
                         None, None,
                         "Associated filespecs.",
                         None ),
        '(RID)':       ( 192, 'P4DTI-rid', 'word', 32, 'required',
                         'None', None,
                         "P4DTI replicator identifier. Do not edit!",
                         None ),
        '(ISSUE)':     ( 193, 'P4DTI-issue-id', 'word', 32, 'required',
                         'None', None,
                         "Bugzilla issue database identifier. Do not "
                         "edit!",
                         None ),
        '(USER)':      ( 194, 'P4DTI-user', 'word', 32, 'always',
                         '$user', None,
                         "Last user to edit this job. You can't edit "
                         "this!",
                         None ),
        }

    if fields_bz_to_p4.has_key('bug_status'):
        if bugs_types['bug_status']['type'] != 'enum':
            # "The 'bug_status' column of Bugzilla's 'bugs' table is not an
            # enum type."
            raise error, catalog.msg(308)
        # Make a list of (Bugzilla state, Perforce state) pairs.
        state_pairs = make_state_pairs(bugs_types['bug_status']['values'],
                                       config.closed_state)


        # Work out the legal values of the State field in the jobspec.  Note
        # that "closed" must be a legal state because "p4 fix -c CHANGE
        # JOBNAME" always sets the State to "closed" even if "closed" is not
        # a legal value.  See job000225.
        legal_states = map((lambda x: x[1]), state_pairs)
        if 'closed' not in legal_states:
            legal_states.append('closed')
        state_values = string.join(legal_states, '/')
        p4_fields['bug_status'] = ( 102,
                                    fields_bz_to_p4['bug_status'],
                                    'select',
                                    bugs_types['bug_status']['length'],
                                    'required',
                                    state_pairs[0][1],
                                    state_values,
                                    bz_field_map['bug_status'][1],
                                    dt_bugzilla.status_translator(state_pairs))

    if fields_bz_to_p4.has_key('assigned_to'):
        p4_fields['assigned_to'] = ( 103,
                                     fields_bz_to_p4['assigned_to'],
                                     'word', user_name_length,
                                     'required',
                                     '$user', None,
                                     bz_field_map['assigned_to'][1],
                                     strict_user_translator)

    if fields_bz_to_p4.has_key('short_desc'):
        p4_fields['short_desc'] = ( 105,
                                    fields_bz_to_p4['short_desc'],
                                    'text',
                                    bugs_types['short_desc']['length'],
                                    'required',
                                    '$blank', None,
                                    bz_field_map['short_desc'][1],
                                    dt_bugzilla.text_translator() )

    if fields_bz_to_p4.has_key('resolution'):
        if bugs_types['resolution']['type'] != 'enum':
            # "The 'resolution' column of Bugzilla's 'bugs' table is not an
            # enum type."
            raise error, catalog.msg(309)
        if not 'FIXED' in bugs_types['resolution']['values']:
            # "The 'resolution' column of Bugzilla's 'bugs' table does not
            # have a 'FIXED' value."
            raise error, catalog.msg(310)

        # Make a list of possible resolutions.
        (resolutions,
         default_resolution) = translate_enum('resolution',
                                              bugs_types['resolution'])
        p4_fields['resolution'] = ( 106,
                                    fields_bz_to_p4['resolution'],
                                    'select',
                                    bugs_types['resolution']['length'],
                                    'required',
                                    default_resolution,
                                    resolutions,
                                    bz_field_map['resolution'][1],
                                    enum_translator)

    # Additional replicated fields will be sequential from this field id.
    p4_field_id = 110

    # Go through the replicated_fields list, build structures and add
    # them to p4_fields.
    for bz_field in config.replicated_fields:
        p4_field = fields_bz_to_p4[bz_field]
        p4_fields[bz_field] = ((p4_field_id, ) +
                               make_p4_field_spec(bz_field, p4_field,
                                                  bugs_types[bz_field],
                                                  user_name_length,
                                                  strict_user_translator))
        p4_field_id = p4_field_id + 1
        if p4_field_id >= 191:
            # "Too many fields to replicate: Perforce jobs can contain
            # only 99 fields."
            raise error, catalog.msg(317)

    comment = ("# A Perforce Job Specification automatically "
               "produced by the\n"
               "# Perforce Defect Tracking Integration\n")

    jobspec = (comment, p4_fields.values())

    # Set configuration parameters needed by dt_bugzilla.
    config.append_only_fields = append_only_fields
    config.read_only_fields = read_only_fields
    config.jobname_function = lambda bug: 'bug%d' % bug['bug_id']

    # Set configuration parameters needed by the replicator.
    config.date_translator = dt_bugzilla.date_translator()
    config.job_owner_field = fields_bz_to_p4.get('assigned_to', 'User')
    config.job_status_field = fields_bz_to_p4.get('bug_status', 'Status')
    config.job_date_field = 'Date'
    config.jobspec = jobspec
    config.prepare_issue_advanced = prepare_issue_advanced
    config.text_translator = dt_bugzilla.text_translator()
    config.translate_jobspec_advanced = translate_jobspec_advanced
    config.user_translator = lax_user_translator

    # The field_map parameter is a list of triples (Bugzilla database
    # field name, Perforce field name, translator) required by the
    # replicator.  We use the filter to remove the fields that aren't
    # replicated: these have no translator.
    config.field_map = \
        map(lambda item: (item[0], item[1][1], item[1][8]),
            filter(lambda item: item[1][8] != None, p4_fields.items()))

    return config
def make_p4_field_spec(bz_field,
                       p4_field,
                       bz_type,
                       user_name_length,
                       strict_user_translator):
    if bz_field_map.has_key(bz_field):
        p4_comment = bz_field_map[bz_field][1]
    else:
        p4_comment = None
        
    if p4_comment is None:
        p4_comment = ("Bugzilla's '%s' field" % bz_field)
        
    bz_type_class = bz_type['type']
    p4_values = None
    p4_length = None

    # if there is a default, use it.
    if bz_type.get('default'):
        p4_class = 'default'
        p4_default = bz_type['default']
    else:
        p4_class = 'optional'
        p4_default = None

    # Figure out the Perforce types, lengths, values, and default, and
    # the translator.
    # Maybe this should be table-driven.
    if bz_type_class == 'float':
        # "Field '%s' specified in 'replicated_fields' list has
        # floating-point type: this is not yet supported by P4DTI."
        raise error, catalog.msg(315, bz_field)
    elif bz_type_class == 'user':
        p4_type = 'word'
        p4_length = user_name_length
        trans = strict_user_translator
    elif bz_type_class == 'enum':
        p4_type = 'select'
        p4_values, p4_default = translate_enum(bz_field, bz_type)
        trans = enum_translator
    elif bz_type_class == 'int':
        p4_type = 'word'
        trans = dt_bugzilla.int_translator()
    elif bz_type_class == 'date':
        p4_type = 'date'
        p4_length = 20
        trans = dt_bugzilla.date_translator()
    elif bz_type_class == 'timestamp':
        p4_type = 'date'
        p4_length = 20
        trans = dt_bugzilla.timestamp_translator()
    elif bz_type_class == 'text':
        p4_type = 'text'
        trans = dt_bugzilla.text_translator()
    else:
        # "Field '%s' specified in 'replicated_fields' list has type
        # '%s': this is not yet supported by P4DTI."
        raise error, catalog.msg(314, (bz_field,
                                       bz_type['sql_type']))

    # "p4 -G" uses the field "code" to indicate whether the Perforce
    # command succeeded or failed.  See job000003.
    if p4_field == 'code':
        # "You can't have a field called 'code' in the Perforce
        # jobspec."
        raise error, catalog.msg(316)

    # Fixed-length fields get the length from Bugzilla.
    if p4_length == None:
        p4_length = bz_type['length']

    if bz_field in read_only_fields:
        p4_comment = (p4_comment + "  DO NOT MODIFY.")

    if bz_field in append_only_fields:
        p4_comment = (p4_comment + "  ONLY MODIFY BY APPENDING.")

    return ( p4_field,
             p4_type,
             p4_length,
             p4_class,
             p4_default,
             p4_values,
             p4_comment,
             trans,
             )