class SecurityAuditManagerContainer {
        private static SecurityAuditManager instance = new SecurityAuditManager();
    }

    /**
     * Returns the currently-installed SecurityAuditProvider. <b>Warning:</b> in virtually all
     * cases the security audit provider should not be used directly. Instead, the appropriate
     * methods in SecurityAuditManager should be called. Direct access to the security audit
     * provider is only provided for special-case logic.
     *
     * @return the current SecurityAuditProvider.
     */
    public static SecurityAuditProvider getSecurityAuditProvider() {
        return SecurityAuditManagerContainer.instance.provider;
    }

    /**
     * Returns a singleton instance of SecurityAuditManager.
     *
     * @return a SecurityAuditManager instance.
     */
    public static SecurityAuditManager getInstance() {
        return SecurityAuditManagerContainer.instance;
    }

    private static SecurityAuditProvider provider;

    /**
     * Constructs a SecurityAuditManager, setting up the provider, and a listener.
     */
    private SecurityAuditManager() {
        // Load an security audit provider.
        initProvider(AUDIT_PROVIDER.getValue());
    }

    /**
     * Initializes the server's security audit provider, based on configuration and defaults to
     * DefaultSecurityAuditProvider if the specified provider is not valid or not specified.
     */
    private static void initProvider(final Class clazz) {
        if (provider == null || !clazz.equals(provider.getClass())) {
            try {
                provider = (SecurityAuditProvider) clazz.newInstance();
            }
            catch (Exception e) {
                Log.error("Error loading security audit provider: " + clazz.getName(), e);
                provider = new DefaultSecurityAuditProvider();
            }
        }
    }

    /**
     * Records a security event in the audit logs.
     *
     * @param username Username of user who performed the security event.
     * @param summary Short description of the event, similar to a subject.
     * @param details Detailed description of the event, can be null if not desired.
     */
    public void logEvent(String username, String summary, String details) {
        provider.logEvent(username, summary, details);
    }

    /**
     * Retrieves security events that have occurred, filtered by the parameters passed.
     * The events will be in order of most recent to least recent.
     *
     * Any parameters that are left null are to be ignored.  In other words, if username is null,
     * then no specific username is being searched for.
     *
     * @param username Username of user to look up.  Can be null for no username filtering.
     * @param skipEvents Number of events to skip past (typically for paging).  Can be null for first page.
     * @param numEvents Number of events to retrieve.  Can be null for "all" events.
     * @param startTime Oldest date of range of events to retrieve.  Can be null for forever.
     * @param endTime Most recent date of range of events to retrieve.  Can be null for "now".
     * @return Array of security events.
     * @throws AuditWriteOnlyException if provider can not be read from.
     */
    public List<SecurityAuditEvent> getEvents(String username, Integer skipEvents, Integer numEvents, Date startTime, Date endTime) throws AuditWriteOnlyException {<FILL_FUNCTION_BODY>

        if (provider.isWriteOnly()) {
            throw new AuditWriteOnlyException();
        }
        return provider.getEvents(username, skipEvents, numEvents, startTime, endTime);
    
<no_super_class>