def route(unrouted):
    """
    Route an UnroutedNotification to the appropriate repositories

    The function will extract all the metadata and match data from any binary content associated with
    the notification, and in combination from match data taken from the notification metadata itself will
    determine if there is a RepositoryConfig whose criteria it matches.

    If there is a match to one or more of the criteria, MatchProvenance objects will be created for
    each matching repository, and persisted for later inspection.

    If one or more repositories are matched, a RoutedNotification will be created and enhanced with any
    metadata extracted from the associated package (if present), then persisted.

    If no repositories match, a FailedNotification will be created and enhanced with any
    metadata extracted from the associated package (if present), then persisted.

    :param unrouted: an UnroutedNotification object
    :return: True if the notification was routed to a repository, False if there were no matches
    """
    app.logger.debug(u"Routing - Notification:{y}".format(y=unrouted.id))

    # first get the packaging system to load and retrieve all the metadata
    # and match data from the content file (if it exists)
    try:
        metadata, pmd = packages.PackageManager.extract(unrouted.id, unrouted.packaging_format)
    except packages.PackageException as e:
        app.logger.debug(u"Routing - Notification:{y} failed with error '{x}'".format(y=unrouted.id, x=e.message))
        raise RoutingException(e.message)

    # extract the match data from the notification and combine it with the match data from the package
    match_data = unrouted.match_data()
    if pmd is not None:
        match_data.merge(pmd)

    # iterate through all the repository configs, collecting match provenance and
    # id information
    # FIXME: at the moment this puts all the provenance in memory and then writes it all
    # in one go later.  Probably that's OK, but it will depend on the number of fields the
    # repository matches and the number of repositories as to how big this gets.
    match_provenance = []
    match_ids = []
    try:
        for rc in models.RepositoryConfig.scroll(page_size=10, keepalive="1m"):
            prov = models.MatchProvenance()
            prov.repository = rc.repository
            prov.notification = unrouted.id
            app.logger.debug(u"Routing - Notification:{y} matching against Repository:{x}".format(y=unrouted.id, x=rc.repository))
            match(match_data, rc, prov)
            if len(prov.provenance) > 0:
                match_provenance.append(prov)
                match_ids.append(rc.repository)
                app.logger.debug(u"Routing - Notification:{y} successfully matched Repository:{x}".format(y=unrouted.id, x=rc.repository))
            else:
                app.logger.debug(u"Routing - Notification:{y} did not match Repository:{x}".format(y=unrouted.id, x=rc.repository))

    except esprit.tasks.ScrollException as e:
        app.logger.error(u"Routing - Notification:{y} failed with error '{x}'".format(y=unrouted.id, x=e.message))
        raise RoutingException(e.message)

    app.logger.debug(u"Routing - Notification:{y} matched to {x} repositories".format(y=unrouted.id, x=len(match_ids)))

    # write all the match provenance out to the index (could be an empty list)
    for p in match_provenance:
        p.save()
        app.logger.debug(u"Routing - Provenance:{z} written for Notification:{y} for match to Repisitory:{x}".format(x=p.repository, y=unrouted.id, z=p.id))

    # if there are matches then the routing is successful, and we want to finalise the
    # notification for the routed index and its content for download
    if len(match_ids) > 0:
        # repackage the content that came with the unrouted notification (if necessary) into
        # the formats required by the repositories for which there was a match
        pack_links = repackage(unrouted, match_ids)

        # update the record with the information, and then
        # write it to the index
        routed = unrouted.make_routed()
        for pl in pack_links:
            routed.add_link(pl.get("url"), pl.get("type"), pl.get("format"), pl.get("access"), pl.get("packaging"))
        routed.repositories = match_ids
        routed.analysis_date = dates.now()
        if metadata is not None:
            enhance(routed, metadata)
        links(routed)
        routed.save()
        app.logger.debug(u"Routing - Notification:{y} successfully routed".format(y=unrouted.id))
        return True
    else:
        # log the failure
        app.logger.error(u"Routing - Notification:{y} was not routed".format(y=unrouted.id))

        # if config says so, convert the unrouted notification to a failed notification, enhance and save
        # for later diagnosis
        if app.config.get("KEEP_FAILED_NOTIFICATIONS", False):
            failed = unrouted.make_failed()
            failed.analysis_date = dates.now()
            if metadata is not None:
                enhance(failed, metadata)
            failed.save()
            app.logger.debug(u"Routing - Notification:{y} as stored as a Failed Notification".format(y=unrouted.id))

        return False

    # Note that we don't delete the unrouted notification here - that's for the caller to decide