webmcp
view framework/cgi-bin/webmcp.lua @ 0:9fdfb27f8e67
Version 1.0.0
| author | jbe/bsw | 
|---|---|
| date | Sun Oct 25 12:00:00 2009 +0100 (2009-10-25) | 
| parents | |
| children | 985024b16520 | 
 line source
     1 #!/usr/bin/env lua
     3 -- include "../lib/" in search path for libraries
     4 do
     5   package.path = '../lib/?.lua;' .. package.path
     6   -- find out which file name extension shared libraries have
     7   local slib_exts = {}
     8   for ext in string.gmatch(package.cpath, "%?%.([A-Za-z0-9_-]+)") do
     9     slib_exts[ext] = true
    10   end
    11   local paths = {}
    12   for ext in pairs(slib_exts) do
    13     paths[#paths+1] = "../accelerator/?." .. ext
    14   end
    15   for ext in pairs(slib_exts) do
    16     paths[#paths+1] = "../lib/?." .. ext
    17   end
    18   paths[#paths+1] = package.cpath
    19   package.cpath = table.concat(paths, ";")
    20 end
    22 -- load os extensions for lua
    23 -- (should happen as soon as possible due to run time measurement)
    24 require 'extos'
    26 -- load nihil library
    27 require 'nihil'
    29 -- load random generator library
    30 require 'multirand'
    32 -- load rocketcgi library and map it to cgi
    33 do
    34   local option = os.getenv("WEBMCP_INTERACTIVE")
    35   if option and option ~= "" and option ~= "0" then
    36     cgi = nil
    37   else
    38     require 'rocketcgi'
    39     cgi = rocketcgi
    40   end
    41 end
    43 -- load database access library with object relational mapper
    44 require 'mondelefant'
    45 mondelefant.connection_prototype.error_objects = true
    47 -- load type system "atom"
    48 require 'atom'
    50 -- load mondelefant atom connector
    51 require 'mondelefant_atom_connector'
    53 --[[--
    54 cloned_table =  -- newly generated table
    55 table.new(
    56   table_or_nil  -- keys of a given table will be copied to the new table
    57 )
    59 If a table is given, then a cloned table is returned.
    60 If nil is given, then a new empty table is returned.
    62 --]]--
    63 function table.new(tbl)
    64   new_tbl = {}
    65   if tbl then
    66     for key, value in pairs(tbl) do
    67       new_tbl[key] = value
    68     end
    69   end
    70   return new_tbl
    71 end
    72 --//--
    74 --[[--
    75 at_exit(
    76   func  -- function to be called before the process is ending
    77 )
    79 Registers a function to be called before the CGI process is exiting.
    80 --]]--
    81 do
    82   local exit_handlers = {}
    83   function at_exit(func)
    84     table.insert(exit_handlers, func)
    85   end
    86   function exit(code)
    87     for i = #exit_handlers, 1, -1 do
    88       exit_handlers[i]()
    89     end
    90     os.exit(code)
    91   end
    92 end
    93 --//--
    95 --[[--
    96 app  -- table to store an application state
    98 'app' is a global table for storing any application state data
    99 --]]--
   100 app = {}
   101 --//--
   103 --[[--
   104 config  -- table to store application configuration
   106 'config' is a global table, which can be modified by a config file of an application to modify the behaviour of that application.
   107 --]]--
   108 config = {}
   109 --//--
   111 -- autoloader system for WebMCP environment "../env/",
   112 -- application environment extensions "$WEBMCP_APP_BASE/env/"
   113 -- and models "$WEBMCP_APP_BASE/model/"
   114 do
   115   local app_base = os.getenv("WEBMCP_APP_BASEPATH")
   116   if not app_base then
   117     error(
   118       "Failed to initialize autoloader " ..
   119       "due to unset WEBMCP_APP_BASEPATH environment variable."
   120     )
   121   end
   122   local weakkey_mt = { __mode = "k" }
   123   local autoloader_category = setmetatable({}, weakkey_mt)
   124   local autoloader_path     = setmetatable({}, weakkey_mt)
   125   local autoloader_mt       = {}
   126   local function install_autoloader(self, category, path)
   127     autoloader_category[self] = category
   128     autoloader_path[self]     = path
   129     setmetatable(self, autoloader_mt)
   130   end
   131   local function try_exec(filename)
   132     local file = io.open(filename, "r")
   133     if file then
   134       local filedata = file:read("*a")
   135       io.close(file)
   136       local func, errmsg = loadstring(filedata, "=" .. filename)
   137       if func then
   138         func()
   139         return true
   140       else
   141         error(errmsg, 0)
   142       end
   143     else
   144       return false
   145     end
   146   end
   147   local function compose_path_string(base, path, key)
   148     return string.gsub(
   149       base .. table.concat(path, "/") .. "/" .. key, "/+", "/"
   150     )
   151   end
   152   function autoloader_mt.__index(self, key)
   153     local category, base_path, merge_base_path, file_key
   154     local merge = false
   155     if
   156       string.find(key, "^[a-z_][A-Za-z0-9_]*$") and
   157       not string.find(key, "^__")
   158     then
   159       category        = "env"
   160       base_path       = "../env/"
   161       merge           = true
   162       merge_base_path = app_base .. "/env/"
   163       file_key        = key
   164     elseif string.find(key, "^[A-Z][A-Za-z0-9]*$") then
   165       category        = "model"
   166       base_path       = app_base .. "/model/"
   167       local first = true
   168       file_key = string.gsub(key, "[A-Z]",
   169         function(c)
   170           if first then
   171             first = false
   172             return string.lower(c)
   173           else
   174             return "_" .. string.lower(c)
   175           end
   176         end
   177       )
   178     else
   179       return
   180     end
   181     local required_category = autoloader_category[self]
   182     if required_category and required_category ~= category then return end
   183     local path = autoloader_path[self]
   184     local path_string = compose_path_string(base_path, path, file_key)
   185     local merge_path_string
   186     if merge then
   187       merge_path_string = compose_path_string(
   188         merge_base_path, path, file_key
   189       )
   190     end
   191     local function try_dir(dirname)
   192       local dir = io.open(dirname)
   193       if dir then
   194         io.close(dir)
   195         local obj = {}
   196         local sub_path = {}
   197         for i, v in ipairs(path) do sub_path[i] = v end
   198         table.insert(sub_path, file_key)
   199         install_autoloader(obj, category, sub_path)
   200         rawset(self, key, obj)
   201         try_exec(path_string .. "/__init.lua")
   202         if merge then try_exec(merge_path_string .. "/__init.lua") end
   203         return true
   204       else
   205         return false
   206       end
   207     end
   208     if merge and try_exec(merge_path_string .. ".lua") then
   209     elseif merge and try_dir(merge_path_string .. "/") then
   210     elseif try_exec(path_string .. ".lua") then
   211     elseif try_dir(path_string .. "/") then
   212     else end
   213     return rawget(self, key)
   214   end
   215   install_autoloader(_G, nil, {})
   216   try_exec("../env/__init.lua")
   217 end
   219 -- interactive console mode
   220 if not cgi then
   221   local config_name = request.get_config_name()
   222   if config_name then
   223     execute.config(config_name)
   224   end
   225   return
   226 end
   228 local success, error_info = xpcall(
   229   function()
   231     -- execute configuration file
   232     do
   233       local config_name = request.get_config_name()
   234       if config_name then
   235         execute.config(config_name)
   236       end
   237     end
   239     -- restore slots if coming from http redirect
   240     if cgi.params.tempstore then
   241       trace.restore_slots{}
   242       local blob = tempstore.pop(cgi.params.tempstore)
   243       if blob then slot.restore_all(blob) end
   244     end
   246     local function file_exists(filename)
   247       local file = io.open(filename, "r")
   248       if file then
   249         io.close(file)
   250         return true
   251       else
   252         return false
   253       end
   254     end
   256     if cgi.params["_webmcp_404"] then
   257       request.force_absolute_baseurl()
   258       request.set_status("404 Not Found")
   259       if request.get_404_route() then
   260         request.forward(request.get_404_route())
   261       else
   262         error("No 404 page set.")
   263       end
   264     elseif request.get_action() then
   265       trace.request{
   266         module = request.get_module(),
   267         action = request.get_action()
   268       }
   269       if
   270         request.get_404_route() and
   271         not file_exists(
   272           encode.action_file_path{
   273             module = request.get_module(),
   274             action = request.get_action()
   275           }
   276         )
   277       then
   278         request.set_status("404 Not Found")
   279         request.forward(request.get_404_route())
   280       else
   281         if cgi.method ~= "POST" then
   282           request.set_status("405 Method Not Allowed")
   283           cgi.add_header("Allow: POST")
   284           error("Tried to invoke an action with a GET request.")
   285         end
   286         local action_status = execute.filtered_action{
   287           module = request.get_module(),
   288           action = request.get_action(),
   289         }
   290         if not request.is_rerouted() then
   291           local routing_mode, routing_module, routing_view
   292           routing_mode   = cgi.params["_webmcp_routing." .. action_status .. ".mode"]
   293           routing_module = cgi.params["_webmcp_routing." .. action_status .. ".module"]
   294           routing_view   = cgi.params["_webmcp_routing." .. action_status .. ".view"]
   295           if not (routing_mode or routing_module or routing_view) then
   296             action_status = "default"
   297             routing_mode   = cgi.params["_webmcp_routing.default.mode"]
   298             routing_module = cgi.params["_webmcp_routing.default.module"]
   299             routing_view   = cgi.params["_webmcp_routing.default.view"]
   300           end
   301           assert(routing_module, "Routing information has no module.")
   302           assert(routing_view,   "Routing information has no view.")
   303           if routing_mode == "redirect" then
   304             local routing_params = {}
   305             for key, value in pairs(cgi.params) do
   306               local status, stripped_key = string.match(
   307                 key, "^_webmcp_routing%.([^%.]*)%.params%.(.*)$"
   308               )
   309               if status == action_status then
   310                 routing_params[stripped_key] = value
   311               end
   312             end
   313             request.redirect{
   314               module = routing_module,
   315               view   = routing_view,
   316               id     = cgi.params["_webmcp_routing." .. action_status .. ".id"],
   317               params = routing_params
   318             }
   319           elseif routing_mode == "forward" then
   320             request.forward{ module = routing_module, view = routing_view }
   321           else
   322             error("Missing or unknown routing mode in request parameters.")
   323           end
   324         end
   325       end
   326     else
   327       -- no action
   328       trace.request{
   329         module = request.get_module(),
   330         view   = request.get_view()
   331       }
   332       if
   333         request.get_404_route() and
   334         not file_exists(
   335           encode.view_file_path{
   336             module = request.get_module(),
   337             view   = request.get_view()
   338           }
   339         )
   340       then
   341         request.set_status("404 Not Found")
   342         request.forward(request.get_404_route())
   343       end
   344     end
   346     if not request.get_redirect_data() then
   347       request.process_forward()
   348       execute.filtered_view{
   349         module = request.get_module(),
   350         view = request.get_view(),
   351       }
   352     end
   354     -- force error due to missing absolute base URL until its too late to display error message
   355     --if request.get_redirect_data() then
   356     --  request.get_absolute_baseurl()
   357     --end
   359   end,
   361   function(errobj)
   362     return {
   363       errobj = errobj,
   364       stacktrace = string.gsub(
   365         debug.traceback('', 2),
   366         "^\r?\n?stack traceback:\r?\n?", ""
   367       )
   368     }
   369   end
   370 )
   372 if not success then trace.error{} end
   374 -- laufzeitermittlung
   375 trace.exectime{ real = os.monotonic_hires_time(), cpu = os.clock() }
   377 slot.select('trace', trace.render)  -- render trace information
   379 local redirect_data = request.get_redirect_data()
   381 -- log error and switch to error layout, unless success
   382 if not success then
   383   local errobj     = error_info.errobj
   384   local stacktrace = error_info.stacktrace
   385   if not request.get_status() then
   386     request.set_status("500 Internal Server Error")
   387   end
   388   slot.set_layout('system_error')
   389   slot.select('system_error', function()
   390     if getmetatable(errobj) == mondelefant.errorobject_metatable then
   391       slot.put(
   392         "<p>Database error of class <b>",
   393         encode.html(errobj.code),
   394         "</b> occured:<br/><b>",
   395         encode.html(errobj.message),
   396         "</b></p>"
   397       )
   398     else
   399       slot.put("<p><b>", encode.html(tostring(errobj)), "</b></p>")
   400     end
   401     slot.put("<p>Stack trace follows:<br/>")
   402     slot.put(encode.html_newlines(encode.html(stacktrace)))
   403     slot.put("</p>")
   404   end)
   405 elseif redirect_data then
   406   local redirect_params = {}
   407   for key, value in pairs(redirect_data.params) do
   408     redirect_params[key] = value
   409   end
   410   local slot_dump = slot.dump_all()
   411   if slot_dump ~= "" then
   412     redirect_params.tempstore = tempstore.save(slot_dump)
   413   end
   414   cgi.redirect(
   415     encode.url{
   416       base   = request.get_absolute_baseurl(),
   417       module = redirect_data.module,
   418       view   = redirect_data.view,
   419       id     = redirect_data.id,
   420       params = redirect_params
   421     }
   422   )
   423   cgi.send_data()
   424 end
   426 if not success or not redirect_data then
   428   local http_status = request.get_status()
   429   if http_status then
   430     cgi.set_status(http_status)
   431   end
   433   -- ajax
   434   if request.is_ajax() then
   435     cgi.set_content_type('text/html')
   436     for i, slot_ident in ipairs{'main', 'actions', 'title', 'topnav', 'sidenav', 'debug', 'notice', 'warning', 'error'} do
   437       local html = slot.get_content(slot_ident)
   438       if html then
   439         cgi.send_data("document.getElementById('" .. slot_ident .. "').innerHTML=" .. encode.json(html or ' ') .. ";")
   440       end
   441     end
   442   -- oder ganz herkoemmlich
   443   else
   444     cgi.set_content_type(slot.get_content_type())
   445     cgi.send_data(slot.render_layout())
   446   end
   447 end
   449 exit()
