using afIoc::Inject
using afIoc::Registry
using afIocConfig::Config
using afEfanXtra::EfanXtra
using afEfanXtra::ComponentMeta
using afEfanXtra::InitRender
using afBedSheet::ValueEncoders
using afBedSheet::HttpRequest
using concurrent::Actor
** (Service) - Returns details about the Pillow page currently being rendered.
**
** 'PageMeta' objects may also be created by the `Pages` service.
const mixin PageMeta {
** Returns the page that this Meta object wraps.
abstract Type pageType()
** Returns the context used to initialise this page.
abstract Obj?[] pageContext()
** Returns a URI that can be used to render the given page.
** The URI takes into account:
** - Any welcome URI -> home page conversions
** - The context used to render this page
** - Any parent 'WebMods'
abstract Uri pageUri()
** Returns the 'Content-Type' produced by this page.
**
** Returns `PillowConfigIds#defaultContextType` if it can not be determined.
abstract MimeType contentType()
** Returns 'true' if the page is a welcome page.
abstract Bool isWelcomePage()
** Returns a URI for a given event - use to create client side URIs to call the event.
abstract Uri eventUri(Str eventName, Obj?[]? eventContext)
** Returns a new 'PageMeta' with the given page context.
abstract PageMeta withContext(Obj?[]? pageContext)
abstract Str httpMethod()
@NoDoc
abstract Uri serverGlob()
@NoDoc
abstract Uri eventGlob(Method eventMethod)
@NoDoc
abstract Type[] contextTypes()
@NoDoc
abstract Uri ctxToUri(Obj?[] context)
** Returns 'pageUri'.
override Str toStr() {
pageUri.toStr
}
internal static Obj? push(PageMeta pageMeta, |->Obj?| f) {
ThreadStack.pushAndRun("afPillow.renderingPageMeta", pageMeta, f)
}
internal static PageMeta? peek(Bool checked) {
ThreadStack.peek("afPillow.renderingPageMeta", false) ?: (checked ? throw PillowErr(ErrMsgs.renderingPageMetaNotRendering) : null)
}
}
internal const class PageMetaImpl : PageMeta {
@Config { id="afPillow.welcomePage" }
@Inject private const Str welcomePage
@Inject private const ContentTypeResolver contentTypeResolver
@Inject private const ClientUriResolver clientUriResolver
@Inject private const HttpRequest httpRequest
@Inject private const ComponentMeta componentMeta
@Inject private const ValueEncoders valueEncoders
@Inject private const EfanXtra efanXtra
@Inject private const Registry registry
override const Type pageType
override const Obj?[] pageContext
internal new make(Type pageType, Obj?[]? pageContext, |This|in) {
this.pageType = pageType
this.pageContext = pageContext ?: Obj?#.emptyList
in(this)
}
override Uri pageUri() {
clientUri := clientUriResolver.clientUri(pageType)
// add extra WebMod paths - but only if we're part of a web request!
if (Actor.locals["web.req"] != null && httpRequest.modBase != `/`)
clientUri = httpRequest.modBase + clientUri.toStr[1..-1].toUri
// convert welcome pages
if (isWelcomeUri(clientUri))
clientUri = clientUri.parent
// append page context
// 'checked' because some server operations don't care about the URI, they just want the glob
contextTypes := contextTypes
if (contextTypes.size != pageContext.size)
throw Err(ErrMsgs.invalidNumberOfInitArgs(pageType, contextTypes, pageContext))
clientUri = clientUri.plusSlash + ctxToUri(pageContext)
return clientUri
}
override MimeType contentType() {
contentTypeResolver.contentType(pageType)
}
override Bool isWelcomePage() {
clientUri := clientUriResolver.clientUri(pageType)
return isWelcomeUri(clientUri)
}
override Uri eventUri(Str eventName, Obj?[]? eventContext) {
eventMethod(eventName)
eventUri := pageUri.plusSlash + `${eventName}`
if (eventContext != null)
eventUri = eventUri.plusSlash + ctxToUri(eventContext)
return eventUri
}
override PageMeta withContext(Obj?[]? pageContext) {
registry.autobuild(PageMeta#, [pageType, pageContext, true])
}
override Str httpMethod() {
page := (Page) Type#.method("facet").callOn(pageType, [Page#]) // Stoopid F4
return page.httpMethod
}
// ---- Internal Methods -------------------------------------------------------------------------------------------
override Uri serverGlob() {
clientStr := clientUriResolver.clientUri(pageType).toStr
noOfParams := contextTypes.size
noOfParams.times { clientStr += "/*" }
clientUri := clientStr.toUri
if (isWelcomeUri(clientUri))
clientUri = clientUri.parent
return clientUri
}
override Uri eventGlob(Method eventMethod) {
eventStr := eventMethod.name
noOfParams := eventMethod.params.size
noOfParams.times { eventStr += "/*" }
return eventStr.toUri
}
override Type[] contextTypes() {
fields := pageType.fields.findAll { it.hasFacet(PageContext#) || it.name == PageContext#.name.decapitalize }
initMeth := componentMeta.findMethod(pageType, InitRender#)
if (!fields.isEmpty && initMeth != null)
throw PillowErr(ErrMsgs.pageCanNotHaveInitRenderAndPageContext(pageType))
if (!fields.isEmpty)
return fields.map { it.type }
if (initMeth != null)
return initMeth.params.map { it.type }
return Type#.emptyList
}
override Uri ctxToUri(Obj?[] context) {
context.map { valueEncoders.toClient(it.typeof, it) ?: "" }.join("/").toUri
}
// ---- Private Methods --------------------------------------------------------------------------------------------
private Bool isWelcomeUri(Uri clientUri) {
return clientUri.name.equalsIgnoreCase(welcomePage)
}
private Method eventMethod(Str eventName) {
pageType.methods.find { it.hasFacet(PageEvent#) && it.name.equalsIgnoreCase(eventName) } ?: throw PillowErr(ErrMsgs.eventNotFound(pageType, eventName))
}
}
internal const class PageMetaProxy : PageMeta {
override Type pageType() {
PageMeta.peek(true).pageType
}
override Uri pageUri() {
PageMeta.peek(true).pageUri
}
override Obj?[] pageContext() {
PageMeta.peek(true).pageContext
}
override MimeType contentType() {
PageMeta.peek(true).contentType
}
override Bool isWelcomePage() {
PageMeta.peek(true).isWelcomePage
}
override Uri eventUri(Str eventName, Obj?[]? eventContext) {
PageMeta.peek(true).eventUri(eventName, eventContext)
}
override PageMeta withContext(Obj?[]? pageContext) {
PageMeta.peek(true).withContext(pageContext)
}
override Str httpMethod() {
PageMeta.peek(true).httpMethod
}
override Uri serverGlob() {
PageMeta.peek(true).serverGlob
}
override Uri eventGlob(Method eventMethod) {
PageMeta.peek(true).eventGlob(eventMethod)
}
override Type[] contextTypes() {
PageMeta.peek(true).contextTypes
}
override Uri ctxToUri(Obj?[] context) {
PageMeta.peek(true).ctxToUri(context)
}
}