sourceafBedSheet::BedSheetModule.fan

using web
using afIoc
using afIocEnv::IocEnvModule
using concurrent::Actor
using afPlastic::PlasticCompiler
using afIocConfig::FactoryDefaults
using afIocConfig::IocConfigSource
using afIocConfig::IocConfigModule

** The [afIoc]`http://repo.status302.com/doc/afIoc/#overview` module class.
** 
** This class is public so it may be referenced explicitly in test code.
@SubModule { modules=[IocConfigModule#, IocEnvModule#] }
const class BedSheetModule {
    // IocConfigModule is referenced explicitly so there is no dicking about with transitive 
    // dependencies on BedSheet startup
    
    static Void bind(ServiceBinder binder) {
        
        // Utils
        // FIXME: not provided in afIoc-1.4, only afIoc-1.5
//      binder.bindImpl(PipelineBuilder#)

        // Routing
        binder.bindImpl(Routes#)
        binder.bindImpl(ValueEncoders#)
        
        // Request handlers
        binder.bindImpl(FileHandler#)
        binder.bindImpl(PodHandler#)

        // Collections (services with contributions)
        binder.bindImpl(ResponseProcessors#)
        binder.bindImpl(ErrProcessors#)
        binder.bindImpl(HttpStatusProcessors#) 
        
        // Other services
        binder.bindImpl(GzipCompressible#)
        binder.bindImpl(ErrPrinterHtml#)
        binder.bindImpl(ErrPrinterStr#)
        binder.bindImpl(HttpSession#)
        binder.bindImpl(HttpFlash#).withScope(ServiceScope.perThread)   // Because HttpFlash is thread scope, it needs a proxy to be injected into AppScope services
        binder.bindImpl(BedSheetPage#)

        // TODO: afIoc-1.5 : use createProxy() instead
        binder.bindImpl(DefaultHttpStatusProcessor#)
        binder.bindImpl(DefaultErrProcessor#)
    }

    @Build { serviceId="BedSheetMetaData" }
    static BedSheetMetaData buildBedSheetMetaData(RegistryOptions options) {
        if (!options.options.containsKey("bedSheetMetaData"))
            throw BedSheetErr(BsErrMsgs.bedSheetMetaDataNotInOptions)
        return options.options["bedSheetMetaData"] 
    }

    @Build { serviceId="HttpPipeline"; disableProxy=true }  // no need for a proxy, you don't advice the pipeline, you contribute to it!
    static HttpPipeline buildHttpPipeline(HttpPipelineFilter[] filters, PipelineBuilder bob, Registry reg) {
        terminator := reg.autobuild(HttpPipelineTerminator#)
        return bob.build(HttpPipeline#, HttpPipelineFilter#, filters, terminator)
    }

    @Build { serviceId="HttpRequest" }
    static HttpRequest buildHttpRequest(DelegateChainBuilder[] builders, Registry reg) {
        makeDelegateChain(builders, reg.autobuild(HttpRequestImpl#))
    }

    @Build { serviceId="HttpResponse" }
    static HttpResponse buildHttpResponse(DelegateChainBuilder[] builders, Registry reg) {
        makeDelegateChain(builders, reg.autobuild(HttpResponseImpl#))
    }

    @Build { serviceId="HttpOutStream"; disableProxy=true; scope=ServiceScope.perThread }
    static OutStream buildHttpOutStream(DelegateChainBuilder[] builders, Registry reg) {
        makeDelegateChain(builders, reg.autobuild(WebResOutProxy#))
    }

    @Contribute { serviceType=HttpPipeline# }
    static Void contributeHttpPipeline(OrderedConfig conf, Routes routes) {
        conf.addOrdered("HttpCleanupFilter",    conf.autobuild(HttpCleanupFilter#),     ["before: BedSheetFilters", "before: HttpErrFilter"])
        conf.addOrdered("HttpErrFilter",        conf.autobuild(HttpErrFilter#),         ["before: BedSheetFilters", "before: HttpRequestLogFilter"])
        conf.addOrdered("HttpRequestLogFilter", conf.autobuild(HttpRequestLogFilter#),  ["before: BedSheetFilters", "before: HttpFlashFilter"])
        conf.addOrdered("HttpFlashFilter",      conf.autobuild(HttpFlashFilter#),       ["before: BedSheetFilters"])
        conf.addPlaceholder("BedSheetFilters")
        conf.addOrdered("HttpRoutesFilter",     conf.autobuild(HttpRoutesBeforeFilter#, [routes]), ["after: BedSheetFilters"])
    }

    @Contribute { serviceId="HttpOutStream" }
    static Void contributeHttpOutStream(OrderedConfig conf) {
        conf.addOrdered("HttpOutStreamBuffBuilder",     conf.autobuild(HttpOutStreamBuffBuilder#), ["before: HttpOutStreamGzipBuilder"])
        conf.addOrdered("HttpOutStreamGzipBuilder",     conf.autobuild(HttpOutStreamGzipBuilder#))
    }

    @Contribute { serviceType=ResponseProcessors# }
    static Void contributeResponseProcessors(MappedConfig conf, HttpStatusProcessors httpStatusProcessor) {
        conf[Text#]             = conf.autobuild(TextResponseProcessor#)
        conf[File#]             = conf.autobuild(FileResponseProcessor#)
        conf[Redirect#]         = conf.autobuild(RedirectResponseProcessor#)
        conf[InStream#]         = conf.autobuild(InStreamResponseProcessor#)
        conf[MethodCall#]       = conf.autobuild(MethodCallResponseProcessor#)
        conf[HttpStatus#]       = httpStatusProcessor
    }

    @Contribute { serviceType=ErrProcessors# }
    static Void contributeErrProcessors(MappedConfig conf) {
        conf[HttpStatusErr#]    = conf.autobuild(HttpStatusErrProcessor#)
    }

    @Contribute { serviceType=FactoryDefaults# }
    static Void contributeFactoryDefaults(MappedConfig conf, DefaultHttpStatusProcessor defaultHttpStatus, DefaultErrProcessor defaultErr) {
        conf[BedSheetConfigIds.proxyPingInterval]               = 1sec
        conf[BedSheetConfigIds.gzipDisabled]                    = false
        conf[BedSheetConfigIds.gzipThreshold]                   = 376
        conf[BedSheetConfigIds.responseBufferThreshold]         = 32 * 1024 // todo: why not kB?
        conf[BedSheetConfigIds.defaultHttpStatusProcessor]      = defaultHttpStatus
        conf[BedSheetConfigIds.defaultErrProcessor]             = defaultErr
        conf[BedSheetConfigIds.noOfStackFrames]                 = 50
        conf[BedSheetConfigIds.srcCodeErrPadding]               = 5
        conf[BedSheetConfigIds.disableWelcomePage]              = false

        conf[BedSheetConfigIds.httpRequestLogDir]               = null
        conf[BedSheetConfigIds.httpRequestLogFilenamePattern]   = "bedSheet-{YYYY-MM}.log"
        conf[BedSheetConfigIds.httpRequestLogFields]            = "date time c-ip cs(X-Real-IP) cs-method cs-uri-stem cs-uri-query sc-status time-taken cs(User-Agent) cs(Referer) cs(Cookie)"
    }

    @Contribute { serviceType=ValueEncoders# }
    static Void contributeValueEncoders(MappedConfig config) {
        // wot no value encoders!? Aha! I see you're using fromStr() instead!
    }

    @Contribute { serviceType=DependencyProviderSource# } 
    static Void contributeDependencyProviderSource(OrderedConfig config) {
        // this is a copy of IocConfig's ConfigProvider so we can use BedSheet's @Config
        config.add(config.autobuild(ConfigProvider2#))
    }
    
    @Contribute { serviceType=GzipCompressible# }
    static Void contributeGzipCompressible(MappedConfig conf) {
        // add some standard compressible mime types
        conf["text/plain"]                  = true
        conf["text/css"]                    = true
        conf["text/tab-separated-values"]   = true
        conf["text/html"]                   = true
        conf["text/javascript"]             = true
        conf["text/xml"]                    = true
        conf["application/rss+xml"]         = true
        conf["application/json"]            = true
    }
    
    @Contribute { serviceType=ErrPrinterHtml# }
    static Void contributeErrPrinterHtml(OrderedConfig config) {
        printer := (ErrPrinterHtmlSections) config.autobuild(ErrPrinterHtmlSections#)

        // these are all the sections you see on the Err500 page
        config.addOrdered("Causes",                 |WebOutStream out, Err? err| { printer.printCauses                  (out, err) })
        config.addOrdered("AvailableValues",        |WebOutStream out, Err? err| { printer.printAvailableValues         (out, err) })
        config.addOrdered("IocOperationTrace",      |WebOutStream out, Err? err| { printer.printIocOperationTrace       (out, err) })
        config.addOrdered("SrcCodeErrs",            |WebOutStream out, Err? err| { printer.printSrcCodeErrs             (out, err) })
        config.addOrdered("StackTrace",             |WebOutStream out, Err? err| { printer.printStackTrace              (out, err) })
        config.addOrdered("RequestDetails",         |WebOutStream out, Err? err| { printer.printRequestDetails          (out, err) })
        config.addOrdered("RequestHeaders",         |WebOutStream out, Err? err| { printer.printRequestHeaders          (out, err) })
        config.addOrdered("FormParameters",         |WebOutStream out, Err? err| { printer.printFormParameters          (out, err) })
        config.addOrdered("Session",                |WebOutStream out, Err? err| { printer.printSession                 (out, err) })
        config.addOrdered("Cookies",                |WebOutStream out, Err? err| { printer.printCookies                 (out, err) })
        config.addOrdered("Locales",                |WebOutStream out, Err? err| { printer.printLocales                 (out, err) })
        config.addOrdered("Locals",                 |WebOutStream out, Err? err| { printer.printLocals                  (out, err) })
        config.addOrdered("FantomEnvironment",      |WebOutStream out, Err? err| { printer.printFantomEnvironment       (out, err) })
        config.addOrdered("FantomIndexedProps",     |WebOutStream out, Err? err| { printer.printFantomIndexedProps      (out, err) })
        config.addOrdered("EnvironmentVariables",   |WebOutStream out, Err? err| { printer.printEnvironmentVariables    (out, err) })
        config.addOrdered("FantomDiagnostics",      |WebOutStream out, Err? err| { printer.printFantomDiagnostics       (out, err) })
    }

    @Contribute { serviceType=ErrPrinterStr# }
    static Void contributeErrPrinterStr(OrderedConfig config) {
        printer := (ErrPrinterStrSections) config.autobuild(ErrPrinterStrSections#)
        
        // these are all the sections you see on the Err log
        config.addOrdered("Causes",                 |StrBuf out, Err? err| { printer.printCauses            (out, err) })
        config.addOrdered("AvailableValues",        |StrBuf out, Err? err| { printer.printAvailableValues   (out, err) })
        config.addOrdered("IocOperationTrace",      |StrBuf out, Err? err| { printer.printIocOperationTrace (out, err) })
        config.addOrdered("SrcCodeErrs",            |StrBuf out, Err? err| { printer.printSrcCodeErrs       (out, err) })       
        config.addOrdered("StackTrace",             |StrBuf out, Err? err| { printer.printStackTrace        (out, err) })
        config.addOrdered("RequestDetails",         |StrBuf out, Err? err| { printer.printRequestDetails    (out, err) })
        config.addOrdered("RequestHeaders",         |StrBuf out, Err? err| { printer.printRequestHeaders    (out, err) })
        config.addOrdered("FormParameters",         |StrBuf out, Err? err| { printer.printFormParameters    (out, err) })
        config.addOrdered("Session",                |StrBuf out, Err? err| { printer.printSession           (out, err) })
        config.addOrdered("Cookies",                |StrBuf out, Err? err| { printer.printCookies           (out, err) })
        config.addOrdered("Locales",                |StrBuf out, Err? err| { printer.printLocales           (out, err) })
        config.addOrdered("Locals",                 |StrBuf out, Err? err| { printer.printLocals            (out, err) })
    }
    
    @Build { serviceId="WebReq"; scope=ServiceScope.perThread } 
    private static WebReq buildWebReq() {
        try return Actor.locals["web.req"]
        catch (NullErr e) 
            throw Err("No web request active in thread")
    }

    @Build { serviceId="WebRes"; scope=ServiceScope.perThread } 
    private static WebRes buildWebRes() {
        try return Actor.locals["web.res"]
        catch (NullErr e)
            throw Err("No web request active in thread")
    }
    
    @Contribute { serviceType=RegistryStartup# }
    static Void contributeRegistryStartup(OrderedConfig conf, PlasticCompiler plasticCompiler, IocConfigSource configSrc) {
        conf.add |->| {
            plasticCompiler.srcCodePadding = configSrc.getCoerced(BedSheetConfigIds.srcCodeErrPadding, Int#)
        }
    }
    
    private static Obj makeDelegateChain(DelegateChainBuilder[] delegateBuilders, Obj service) {
        delegateBuilders.reduce(service) |Obj delegate, DelegateChainBuilder builder -> Obj| {      
            return builder.build(delegate) 
        }
    }
}