MODULE DynamicWebpagePlugin; (** AUTHOR "Luc Blaeser"; PURPOSE "HTTP Webserver Plugin for Dynamic Webpages"; *)
IMPORT
DynamicWebpage, HTTPSupport, HTTPSession, WebHTTP, WebHTTPServer, Files, Dates, Strings, Streams, Commands,
KernelLog, XML, XMLScanner, XMLParser, XMLObjects, DynamicStrings, TFClasses, Configuration, Modules;
CONST
DEBUG = FALSE; (* disply debug information *)
ShowRegisteredElements = FALSE; (* show all registered active elements *)
PluginName = "Dynamic Webpage Plugin";
PreTransformation = TRUE;
PostTransformation = FALSE;
MaxTransformationDepth = 40; (* maxmimum number of recursive steps to transform a transformation result *)
DocType = '';
TYPE
DynamicWebpagePlugin = OBJECT (WebHTTPServer.HTTPPlugin)
PROCEDURE &Init*(CONST name: WebHTTPServer.Name);
BEGIN
Init^(PluginName)
END Init;
PROCEDURE CanHandle*(host: WebHTTPServer.Host; VAR request: WebHTTP.RequestHeader; secure : BOOLEAN) : BOOLEAN;
VAR name, ext: Files.FileName; f: Files.File; newuri : ARRAY 4096 OF CHAR;
BEGIN
HTTPSupport.RemoveVariablesFromURI(request.uri, newuri);
IF (request.method IN {WebHTTP.GetM, WebHTTP.PostM, WebHTTP.HeadM}) THEN
IF (newuri[Strings.Length(newuri)-1] = "/") THEN (* check for default webpage "index.dxp" *)
COPY(host.prefix, name); Strings.Append(name, newuri);
Strings.Append(name, DynamicWebpage.DefaultWebpage);
f := Files.Old(name);
RETURN (f # NIL)
ELSE
Files.SplitExtension(newuri, name, ext);
Strings.UpperCase(ext);
RETURN (ext = DynamicWebpage.DynamicWebpageExtension)
END
ELSE
RETURN FALSE
END
END CanHandle;
PROCEDURE Handle*(host: WebHTTPServer.Host; VAR requestHeader: WebHTTP.RequestHeader; VAR reply: WebHTTP.ResponseHeader;
VAR in: Streams.Reader; VAR out: Streams.Writer);
VAR chunker: WebHTTP.ChunkedOutStream; w: Streams.Writer; f: Files.File; backupUri: ARRAY 4096 OF CHAR;
request: HTTPSupport.HTTPRequest; session: HTTPSession.Session;
BEGIN
(* requestHeader.method IN {WebHTTP.GetM, WebHTTP.PostM, WebHTTP.HeadM} *)
NEW(request, requestHeader, in);
WebHTTP.SetAdditionalFieldValue(requestHeader.additionalFields, "If-Modified-Since", " "); (* prohibit conditional get for dynamic webpages *)
WebHTTPServer.GetDefaultResponseHeader(requestHeader, reply);
IF (request.shortUri[Strings.Length(request.shortUri)-1] = "/") THEN (* get default webpage "index.dxp" in a directory *)
Strings.Append(request.shortUri, DynamicWebpage.DefaultWebpage);
Strings.Concat("http://", requestHeader.host, reply.contentlocation);
Strings.Append(reply.contentlocation, request.shortUri);
END;
(* LocateResource works only with variable-free URI in requestHeader *)
COPY(requestHeader.uri, backupUri); COPY(request.shortUri, requestHeader.uri);
LocateResource(host, requestHeader, reply, f); (* sets also reply.statuscode *)
COPY(backupUri, requestHeader.uri);
IF ((f # NIL) & ((reply.statuscode = WebHTTP.OK) OR (reply.statuscode = WebHTTP.NotModified))) THEN
reply.statuscode := WebHTTP.OK;
Strings.FormatDateTime(WebHTTP.DateTimeFormat, Dates.Now(), reply.lastmodified); (* dynamic webpages have no last modified *)
WebHTTP.SetAdditionalFieldValue(reply.additionalFields, "Expires", reply.lastmodified);
(* deny web caching; Expires=0 is illegal but often used to deny web caching *)
WebHTTP.SetAdditionalFieldValue(reply.additionalFields, "Pragma", "no-cache"); (* deny web caching *)
COPY("text/html", reply.contenttype); (* result is XHTML *)
NEW(chunker, w, out, requestHeader, reply);
WebHTTP.SendResponseHeader(reply, out);
session := HTTPSession.GetSession(request);
session.IncreaseLifeTime;
(* detect whether the user has used the back or refresh button of the navigation bar *)
IF (BackRefreshButtonWasPressed(request, session)) THEN
HandleBackRefreshButtonError(request, w)
ELSE
HandleClientAction(request); (* first handle a client event *)
IF ((requestHeader.method = WebHTTP.GetM) OR (requestHeader.method = WebHTTP.PostM)) THEN
GenerateDynamicWebpage(f, request, w)
END
END;
chunker.Close
ELSIF (reply.statuscode = WebHTTP.ObjectMoved) THEN
NEW(chunker, w, out, requestHeader, reply);
WebHTTP.SendResponseHeader(reply, out);
w.String(DocType); w.Ln;
w.String("
Document Moved"); w.Ln;
w.String('Document Moved
This document may be found here.
");
w.String(WebHTTPServer.ServerVersion); w.String(""); w.Ln;
w.Update;
chunker.Close
ELSIF ((reply.statuscode = WebHTTP.NotFound) OR (f = NIL)) THEN
reply.statuscode := WebHTTP.NotFound;
NEW(chunker, w, out, requestHeader, reply);
WebHTTP.SendResponseHeader(reply, out);
w.String(DocType); w.Ln;
w.String("404 - Not Found");
w.String("HTTP 404 - File Not Found
");
w.String(WebHTTPServer.ServerVersion); w.String("");
w.Ln;
w.Update;
chunker.Close
ELSE
reply.statuscode := WebHTTP.NotImplemented;
WebHTTP.WriteStatus(reply, out)
END
END Handle;
END DynamicWebpagePlugin;
ParserError = POINTER TO RECORD
pos, line, row: LONGINT;
msg: ARRAY 1024 OF CHAR
END;
SessionStateFullElement = OBJECT
VAR
(* each statefull object instance is identified by an object id (constructed out of file and explicit id in xml representation) *)
objectId: Strings.String;
session: HTTPSession.Session;
activeElem: DynamicWebpage.StateFullActiveElement;
eventHandlers: DynamicWebpage.EventHandlerList;
PROCEDURE &Init*(id: Strings.String; sess: HTTPSession.Session; elem: DynamicWebpage.StateFullActiveElement;
handlerList : DynamicWebpage.EventHandlerList);
BEGIN (* id # NIL & sess # NIL & elem # NIL *)
NEW(objectId, LEN(id)); COPY(id^, objectId^);
session := sess; activeElem := elem; eventHandlers:= handlerList
END Init;
END SessionStateFullElement;
(* Abstracts the creation and delegation to a statefull or stateless active element instances.
The type of the active element (stateless or statefull) is defined by the creation on the first access
Stateless active elements are singleton and used by all sessions. Statefull active elements belong
to exactly one session and have multiple instances identified explicitly by the webpage file and
an "id" attribute in the XML representation. *)
ActiveElementFactory = OBJECT
VAR
moduleName: ARRAY 128 OF CHAR;
activeElemDesc: DynamicWebpage.ActiveElementDescriptor;
(* used if it is a stateless active element, otherwise NIL *)
stateLessActiveElem: DynamicWebpage.StateLessActiveElement; (* singleton *)
stateLessEventHandlers: DynamicWebpage.EventHandlerList;
(* used if it is a statefull active element, otherwise NIL *)
stateFullActiveElems: TFClasses.List; (* List of SessionStateFullElement *)
PROCEDURE &Init*(module: Strings.String; desc: DynamicWebpage.ActiveElementDescriptor);
BEGIN
ASSERT(module # NIL); ASSERT(desc # NIL); ASSERT(desc.factory # NIL);
COPY(module^, moduleName); activeElemDesc := desc
END Init;
PROCEDURE SessionExpired(session: HTTPSession.Session);
VAR sessionElem: SessionStateFullElement; expElemList: TFClasses.List; (* List of SessionStateFullElement *)
i : LONGINT; p: ANY;
BEGIN {EXCLUSIVE}
(* there could be multiple instances of a statefull active element belonging to session *)
NEW(expElemList);
stateFullActiveElems.Lock;
FOR i := 0 TO stateFullActiveElems.GetCount()-1 DO
p := stateFullActiveElems.GetItem(i); sessionElem := p(SessionStateFullElement); (* sessionElem # NIL *)
IF (sessionElem.session = session) THEN
expElemList.Add(sessionElem)
END
END;
stateFullActiveElems.Unlock;
FOR i:= 0 TO expElemList.GetCount()-1 DO
p := expElemList.GetItem(i);
stateFullActiveElems.Remove(p)
END;
IF (DEBUG) THEN
KernelLog.String("Statefull active element instances '"); KernelLog.String(activeElemDesc.elementName);
KernelLog.String("' in module '"); KernelLog.String(moduleName); KernelLog.String("' have been freed for session '");
KernelLog.String(session.sessionId); KernelLog.String("'."); KernelLog.Ln
END
END SessionExpired;
(* must be called before disposing the object *)
PROCEDURE PrepareDisposal;
BEGIN
IF (stateFullActiveElems # NIL) THEN (* contains statefull active element *)
HTTPSession.RemoveExpirationHandler(SessionExpired)
END
END PrepareDisposal;
(* objectId is only used if a statefull active element is requested, otherwise objectId can be NIL *)
PROCEDURE GetElementInstance(session : HTTPSession.Session; objectId: Strings.String) : DynamicWebpage.ActiveElement;
VAR i: LONGINT; p: ANY; sessionElem: SessionStateFullElement; elem: DynamicWebpage.ActiveElement;
stateFullElem: DynamicWebpage.StateFullActiveElement; eventHandlerList: DynamicWebpage.EventHandlerList;
BEGIN {EXCLUSIVE}
IF (stateLessActiveElem # NIL) THEN (* it is a stateless active element *)
RETURN stateLessActiveElem
ELSIF (stateFullActiveElems # NIL) THEN (* it is a statefull active element *)
IF (objectId # NIL) THEN
stateFullActiveElems.Lock;
FOR i := 0 TO stateFullActiveElems.GetCount()-1 DO
p := stateFullActiveElems.GetItem(i); sessionElem := p(SessionStateFullElement);
(* sessionElem # NIL & sessionElem.objectId # NIL *)
IF ((sessionElem.session = session) & (sessionElem.objectId^ = objectId^)) THEN
stateFullActiveElems.Unlock;
RETURN sessionElem.activeElem;
END
END;
stateFullActiveElems.Unlock;
(* create a new statefull element *)
elem := activeElemDesc.factory();
(* elem # NIL since there was already a statefull element instance created by this factory method *)
stateFullElem := elem(DynamicWebpage.StateFullActiveElement);
eventHandlerList := elem.GetEventHandlers();
NEW(sessionElem, objectId, session, stateFullElem, eventHandlerList);
stateFullActiveElems.Add(sessionElem);
RETURN elem
ELSE
KernelLog.String("Dynamic Webpage Plugin: The statefull active element '");
KernelLog.String(activeElemDesc.elementName); KernelLog.String("' in module '");
KernelLog.String(moduleName); KernelLog.String("' must be used together with an id in a webpage file.");
KernelLog.Ln;
RETURN NIL
END
ELSE (* it is not yet determined if it is a statefull or stateless active element *)
elem := activeElemDesc.factory();
IF (elem # NIL) THEN
IF (elem IS DynamicWebpage.StateFullActiveElement) THEN
IF (objectId # NIL) THEN
(* initialize as statefull active element factory *)
NEW(stateFullActiveElems);
HTTPSession.AddExpirationHandler(SessionExpired);
stateFullElem := elem(DynamicWebpage.StateFullActiveElement);
eventHandlerList := elem.GetEventHandlers();
NEW(sessionElem, objectId, session, stateFullElem, eventHandlerList);
stateFullActiveElems.Add(sessionElem);
RETURN stateFullElem
ELSE
KernelLog.String("Dynamic Webpage Plugin: The statefull active element '");
KernelLog.String(activeElemDesc.elementName); KernelLog.String("' in module '");
KernelLog.String(moduleName); KernelLog.String("' must be used together with an attribute '");
KernelLog.String(DynamicWebpage.XMLAttributeObjectIdName); KernelLog.String("'.");
KernelLog.Ln;
RETURN NIL
END
ELSIF (elem IS DynamicWebpage.StateLessActiveElement) THEN
(* initialize as stateless active element factory *)
stateLessActiveElem := elem(DynamicWebpage.StateLessActiveElement);
stateLessEventHandlers := elem.GetEventHandlers();
RETURN elem
ELSE (* elem IS DynamicWebpage.ActiveElement *)
KernelLog.String("Dynamic Webpage Plugin: The active element '");
KernelLog.String(activeElemDesc.elementName); KernelLog.String("' in module '");
KernelLog.String(moduleName); KernelLog.String("' must be either a stateless or statefull active element.");
KernelLog.Ln;
RETURN NIL
END
ELSE
KernelLog.String("Dynamic Webpage Plugin: Invalid result from the factory for the active element '");
KernelLog.String(activeElemDesc.elementName); KernelLog.String("' in module '");
KernelLog.String(moduleName); KernelLog.String("'"); KernelLog.Ln;
RETURN NIL
END
END
END GetElementInstance;
(* objectId is only used for statefull activ elements and is NIL in case of stateless active elements *)
PROCEDURE FindEventHandler(session: HTTPSession.Session; objectId: Strings.String; CONST handlerName: ARRAY OF CHAR) : DynamicWebpage.EventHandler;
VAR elem: DynamicWebpage.ActiveElement; sessionElem: SessionStateFullElement; p: ANY; i : LONGINT;
PROCEDURE GetEventHandlerFromList(eventList: DynamicWebpage.EventHandlerList) : DynamicWebpage.EventHandler;
VAR j: LONGINT;
BEGIN
IF (eventList # NIL) THEN
FOR j := 0 TO LEN(eventList^)-1 DO
IF (eventList[j] # NIL) THEN
IF (eventList[j].methodName = handlerName) THEN
RETURN eventList[j].handler
END
ELSE
KernelLog.String("Dynamic Webpage Plugin: The "); KernelLog.Int(j, 0);
KernelLog.String(".th event handler is not defined in the event handler list in the active element '");
KernelLog.String(activeElemDesc.elementName); KernelLog.String("' in module '");
KernelLog.String(moduleName); KernelLog.String("'"); KernelLog.Ln
END
END
END;
RETURN NIL
END GetEventHandlerFromList;
BEGIN
elem := GetElementInstance(session, objectId); (* this guarantees that the needed active element instance is now present *)
IF ((elem # NIL) & (elem IS DynamicWebpage.StateLessActiveElement)) THEN (* it is a stateless active element *)
RETURN GetEventHandlerFromList(stateLessEventHandlers)
ELSIF ((objectId # NIL) & (elem # NIL) & (elem IS DynamicWebpage.StateFullActiveElement)) THEN (* it is a statefull active element *)
(* stateFullActiveElems # NIL by GetElementInstance *)
stateFullActiveElems.Lock;
FOR i := 0 TO stateFullActiveElems.GetCount()-1 DO
p := stateFullActiveElems.GetItem(i); sessionElem := p(SessionStateFullElement); (* sessionElem # NIL *)
IF ((sessionElem.session = session) & (sessionElem.objectId^ = objectId^)) THEN
stateFullActiveElems.Unlock;
RETURN GetEventHandlerFromList(sessionElem.eventHandlers);
END
END;
stateFullActiveElems.Unlock;
RETURN NIL
ELSE (* error message already displayed by GetElementInstance *)
RETURN NIL
END
END FindEventHandler;
END ActiveElementFactory;
VAR
dynamicPagePlugin: DynamicWebpagePlugin; (* singleton instance to be able to uninstall *)
lockServingHosts: BOOLEAN; (* lock hold when operating on servingHosts *)
servingHosts: TFClasses.List; (* List of WebHTTPServer.Host *)
registeredActiveElemFact: TFClasses.List; (* List of ActiveElementFactory *)
parserError: ParserError; (* since there is no DELEGATE for the reportErrorHandler XML-Module possible *)
(* Returns true iff back or refresh button was pressed and increases the state counter if back button was not pressed *)
PROCEDURE BackRefreshButtonWasPressed(request: HTTPSupport.HTTPRequest; session: HTTPSession.Session) : BOOLEAN;
VAR httpVar: HTTPSupport.HTTPVariable; httpCounter, sessionCounter: LONGINT; p: ANY;
dynStr: DynamicStrings.DynamicString; str: Strings.String; numberStr: ARRAY 14 OF CHAR;
BEGIN (* request # NIL & session # NIL *)
httpVar := request.GetVariableByName(DynamicWebpage.StateCounterVariable);
httpCounter := 0;
IF (httpVar # NIL) THEN
Strings.StrToInt(httpVar.value, httpCounter);
p := session.GetVariableValue(DynamicWebpage.StateCounterVariable);
IF ((p # NIL) & (p IS DynamicStrings.DynamicString)) THEN
dynStr := p(DynamicStrings.DynamicString); str := dynStr.ToArrOfChar(); (* str # NIL *)
Strings.StrToInt(str^, sessionCounter);
IF (httpCounter < sessionCounter) THEN
RETURN TRUE
END
END
END;
INC(sessionCounter);
Strings.IntToStr(sessionCounter, numberStr); NEW(dynStr); dynStr.Append(numberStr);
session.AddVariableValue(DynamicWebpage.StateCounterVariable, dynStr);
RETURN FALSE
END BackRefreshButtonWasPressed;
(* handle the case that the user has used the back button of the browser's navigation bar *)
PROCEDURE HandleBackRefreshButtonError(request: HTTPSupport.HTTPRequest; w: Streams.Writer);
VAR sessionId: HTTPSession.SessionId;
BEGIN
HTTPSession.GetSessionId(request, sessionId);
w.String(DocType); w.Ln;
w.String("Do not use the back or refresh button"); w.Ln;
w.String("Do not use the back or refresh button in the navigation bar
");
w.String("Using the back or refresh button in the navigation bar of this browser is not allowed when using dynamic ");
w.String("webpages.
To continue click ");w.String('here.
'); w.String(WebHTTPServer.ServerVersion);
w.String(""); w.Ln;
w.Update;
END HandleBackRefreshButtonError;
PROCEDURE GenerateDynamicWebpage(f: Files.File; request: HTTPSupport.HTTPRequest; w: Streams.Writer);
VAR scanner: XMLScanner.Scanner; parser: XMLParser.Parser; doc: XML.Document;
root: XML.Element; rootContent: XML.Content; errormsg: ARRAY 1024 OF CHAR;
reader : Files.Reader;
BEGIN (* f # NIL *)
NEW(reader, f, 0);
NEW(scanner, reader);
NEW(parser, scanner);
scanner.reportError := ReportXMLParserScannerError;
parser.reportError := ReportXMLParserScannerError;
BEGIN {EXCLUSIVE}
(* the error handler needs additional information about the file and writer but is not a delegate *)
parserError := NIL;
doc := parser.Parse();
IF (parserError # NIL) THEN
Strings.Concat("Error while parsing: ", parserError.msg, errormsg);
ReportGeneratorError(f, w, parserError.pos, parserError.line, parserError.row, errormsg);
doc := NIL
END
END;
IF doc # NIL THEN
rootContent := doc;
IF (TransformXMLTree(f, rootContent, request, 0, w)) THEN (* transformation worked successfully *)
(* SetDocument should be provided by the XML module *)
IF (rootContent IS XML.Element) THEN
root := rootContent(XML.Element);
ELSE
(* Error "transformation result has not a root element"*)
END;
w.String(DocType); w.Ln;
doc.Write(w, NIL, 0); (* externalize transformation result *)
END
END;
w.Update
END GenerateDynamicWebpage;
(* returns true iff the transformation worked without an error *)
PROCEDURE TransformXMLTree(file: Files.File; VAR n: XML.Content; VAR request: HTTPSupport.HTTPRequest;
transformationDepth: INTEGER; w: Streams.Writer) : BOOLEAN;
VAR enum, resultEnum: XMLObjects.Enumerator; pChild, pResultChild: ANY; elem: XML.Element;
child, newChild, resultChild: XML.Content; errormsg: ARRAY 256 OF CHAR; wasTransformed: BOOLEAN;
elemName : Strings.String; container, snapshot, resultContainer: XML.Container;
BEGIN
IF ((n # NIL) & (n IS XML.Element) & (transformationDepth > MaxTransformationDepth)) THEN
elem := n(XML.Element); elemName := elem.GetName();
Strings.Concat("In element '", elemName^, errormsg);
Strings.Append(errormsg, "': Maximum recursive transformation steps reached. There could be an endless loop in a transformation procedure.");
ReportGeneratorError(file, w, elem.GetPos(), 0, 0, errormsg);
KernelLog.String("Error in Stream: "); KernelLog.String(errormsg); KernelLog.Ln;
RETURN FALSE (* stop the further traversal *)
ELSIF ((n # NIL) & (n IS XML.Container)) THEN
(* pre-postorder traversal with recursive traversal of the post transformation result
increase transformation depth only if it is a transformation of the transformation result *)
wasTransformed := FALSE;
(* pre transformation *)
IF (n IS XML.Element) THEN
elem := n(XML.Element);
IF (IsActive(elem))THEN
IF (~TransformActiveElement(file, n, PreTransformation, request, w)) THEN RETURN FALSE END;
wasTransformed := TRUE
END
END;
IF ((n # NIL) & (n IS XML.Container)) THEN (* transformation result of PreTransform could be not a container *)
container := n(XML.Container);
(* no modification while iteration allowed, extract first the contents into a snapshot *)
ExtractContentsOfContainer(container, snapshot);
enum := snapshot.GetContents();
WHILE (enum.HasMoreElements()) DO
pChild := enum.GetNext(); child := pChild(XML.Content); newChild := child;
IF (~TransformXMLTree(file, newChild, request, transformationDepth, w)) THEN RETURN FALSE END;
IF (newChild # NIL) THEN
IF ((newChild IS XML.Container) & (~(newChild IS XML.Element))) THEN (* avoid nested containers *)
resultContainer := newChild(XML.Container);
resultEnum := resultContainer.GetContents();
WHILE(resultEnum.HasMoreElements()) DO
pResultChild := resultEnum.GetNext(); resultChild := pResultChild(XML.Content);
container.AddContent(resultChild)
END
ELSE
container.AddContent(newChild)
END
END
END;
(* post transformation *)
IF (n IS XML.Element) THEN
elem := n(XML.Element);
IF (IsActive(elem)) THEN
IF (~TransformActiveElement(file, n, PostTransformation, request, w)) THEN RETURN FALSE END
END
END
END;
IF (wasTransformed) THEN
(* transformation of the transformation result is needed *)
IF (~TransformXMLTree(file, n, request, transformationDepth+1, w)) THEN RETURN FALSE END;
IF (DEBUG) THEN Log(elem) END
END
END; (* transformation result could be not a container *)
RETURN TRUE
END TransformXMLTree;
PROCEDURE Log(elem: XML.Element);
VAR sw: Streams.StringWriter; w: Streams.Writer; msg: ARRAY 1024 OF CHAR;
BEGIN
NEW(sw, LEN(msg)); w := sw; elem.Write(w, NIL, 0);
sw.Get(msg); KernelLog.String(msg); KernelLog.Ln
END Log;
PROCEDURE ExtractContentsOfContainer(input: XML.Container; VAR output: XML.Container);
VAR e: XMLObjects.Enumerator; p : ANY; child: XML.Content;
BEGIN
NEW(output);
(* first copy contents to output *)
e := input.GetContents();
WHILE (e.HasMoreElements()) DO
p := e.GetNext(); child := p(XML.Content);
output.AddContent(child);
END;
(* then remove contents from input *)
e := output.GetContents();
WHILE (e.HasMoreElements()) DO
p := e.GetNext(); child := p(XML.Content);
input.RemoveContent(child);
END
END ExtractContentsOfContainer;
PROCEDURE IsActive(n : XML.Element) : BOOLEAN;
VAR module, obj: Strings.String;
BEGIN (* n # NIL *)
ExtractModuleObjectName(n, module, obj);
(* check whether the module is declared to represent an active namespace *)
IF ((module # NIL) & (obj # NIL)) THEN
RETURN IsModuleRegistered(module^)
ELSE
RETURN FALSE
END
END IsActive;
(* get the objectId if specfified used for statefull active elements *)
PROCEDURE GetObjectId(CONST id: ARRAY OF CHAR; request: HTTPSupport.HTTPRequest) : Strings.String;
VAR objectId: Strings.String;
BEGIN
(* the object id is composed by the actual uri and id attribute for the active element *)
(* '&' does occur neither in a xml attribute nor in shortUri *)
NEW(objectId, LEN(id)+Strings.Length(request.shortUri)+1);
Strings.Concat(request.shortUri, "&", objectId^);
Strings.Append(objectId^, id);
RETURN objectId
END GetObjectId;
(** if isPreTransformation is TRUE then PreTansform() is called otherwise PostTransform(),
* returns true iff the transformation worked without an error *)
PROCEDURE TransformActiveElement(file: Files.File; VAR n: XML.Content; isPreTransformation: BOOLEAN;
request: HTTPSupport.HTTPRequest; w: Streams.Writer) : BOOLEAN;
VAR moduleName, objName, elemName, objectId, oidAttrVal: Strings.String; elem: XML.Element;
activeElemFact: ActiveElementFactory; errormsg: ARRAY 256 OF CHAR; activeElem: DynamicWebpage.ActiveElement;
session: HTTPSession.Session;
BEGIN (* n IS XML.ELement & IsActive(n) is TRUE *)
elem := n(XML.Element); elemName := elem.GetName();
IF (DEBUG) THEN KernelLog.String(elemName^); KernelLog.String(" is active"); KernelLog.Ln END;
ExtractModuleObjectName(elem, moduleName, objName);
(* moduleName # NIL & objName # NIL since IsActive(n) = TRUE *)
activeElemFact := FindActiveElemFactory(moduleName^, objName^);
IF (activeElemFact # NIL) THEN
session := HTTPSession.GetSession(request);
oidAttrVal := elem.GetAttributeValue(DynamicWebpage.XMLAttributeObjectIdName);
IF (oidAttrVal # NIL) THEN (* seems to be a statefull active element *)
objectId := GetObjectId(oidAttrVal^, request);
ELSE (* seems to be a stateless active element *)
objectId := NIL
END;
activeElem := activeElemFact.GetElementInstance(session, objectId);
IF (activeElem # NIL) THEN
(* here would be an exception handler fine *)
IF (isPreTransformation) THEN
n := activeElem.PreTransform(elem, request)
ELSE
n := activeElem.Transform(elem, request)
END
ELSE
Strings.Concat("In element '", elemName^, errormsg);
Strings.Append(errormsg, "': Could not create an instance for the active element '");
Strings.Append(errormsg, moduleName^); Strings.Append(errormsg, ".");
Strings.Append(errormsg, objName^);
Strings.Append(errormsg, "'. If you use a statefull active element then you must identify the instance with the xml attribute '");
Strings.Append(errormsg, DynamicWebpage.XMLAttributeObjectIdName); Strings.Append(errormsg, "'.");
ReportGeneratorError(file, w, elem.GetPos(), 0, 0, errormsg);
KernelLog.String("Error in Stream: "); KernelLog.String(errormsg); KernelLog.Ln;
RETURN FALSE (* stop transformation process *)
END
ELSE
Strings.Concat("In element '", elemName^, errormsg);
Strings.Append(errormsg, "': The active element '");
Strings.Append(errormsg, moduleName^); Strings.Append(errormsg, ".");
Strings.Append(errormsg, objName^); Strings.Append(errormsg, "' is not defined.");
ReportGeneratorError(file, w, elem.GetPos(), 0, 0, errormsg);
KernelLog.String("Error in Stream: "); KernelLog.String(errormsg); KernelLog.Ln;
RETURN FALSE (* stop transformation process *)
END;
RETURN TRUE
END TransformActiveElement;
PROCEDURE IsModuleRegistered(CONST moduleName: ARRAY OF CHAR) : BOOLEAN;
VAR i : LONGINT; p : ANY; obj: ActiveElementFactory;
BEGIN
registeredActiveElemFact.Lock;
FOR i := 0 TO registeredActiveElemFact.GetCount()-1 DO
p := registeredActiveElemFact.GetItem(i);
obj := p(ActiveElementFactory);
IF (obj.moduleName = moduleName) THEN
registeredActiveElemFact.Unlock;
RETURN TRUE
END
END;
registeredActiveElemFact.Unlock;
RETURN FALSE
END IsModuleRegistered;
PROCEDURE FindActiveElemFactory(CONST moduleName, objName: ARRAY OF CHAR) : ActiveElementFactory;
VAR i : LONGINT; p : ANY; obj: ActiveElementFactory;
BEGIN
registeredActiveElemFact.Lock;
FOR i := 0 TO registeredActiveElemFact.GetCount()-1 DO
p := registeredActiveElemFact.GetItem(i);
obj := p(ActiveElementFactory);
IF ((obj.moduleName = moduleName) & (obj.activeElemDesc.elementName = objName)) THEN
registeredActiveElemFact.Unlock;
RETURN obj
END
END;
registeredActiveElemFact.Unlock;
RETURN NIL
END FindActiveElemFactory;
PROCEDURE ExtractModuleObjectName(n: XML.Element; VAR moduleName: Strings.String; VAR objName: Strings.String);
VAR elemNameDyn : DynamicStrings.DynamicString; elemName, namespaceId, attrVal: Strings.String;
pos: LONGINT; attrName: ARRAY 128 OF CHAR; attr: XML.Attribute; tempElem : XML.Element;
BEGIN (* n # NIL *)
moduleName := NIL; objName := NIL;
elemName := n.GetName();
DynamicStrings.Search(":", elemName^, pos);
IF ((pos > 0) & (Strings.Length(elemName^) > pos+1)) THEN (* elemName^ = "a:b" and len(a) > 0 and len(b) > 0 *)
NEW(elemNameDyn); elemNameDyn.FromArrOfChar(elemName);
namespaceId := elemNameDyn.Extract(0, pos);
Strings.Concat("xmlns:", namespaceId^, attrName);
(* look for the namespace declaration recursively in parent elements *)
tempElem := n; attr := NIL;
WHILE ((tempElem # NIL) & (attr = NIL)) DO
attr := tempElem.GetAttribute(attrName);
tempElem := tempElem.GetParent();
END;
IF (attr # NIL) THEN
attrVal := attr.GetValue();
moduleName := attrVal; objName := elemNameDyn.Extract(pos+1, Strings.Length(elemName^)-pos)
END
END
END ExtractModuleObjectName;
PROCEDURE ReportGeneratorError(f: Files.File; w: Streams.Writer; pos, line, row: LONGINT; CONST msg: ARRAY OF CHAR);
VAR fname: Files.FileName;
BEGIN
IF (f # NIL) THEN
f.GetName(fname);
ELSE
COPY("?", fname);
END;
KernelLog.String("DynamicWebpagePlugin while processing file '"); KernelLog.String(fname); KernelLog.String("':");
KernelLog.Ln; KernelLog.String("pos "); KernelLog.Int(pos, 6); KernelLog.String(", line "); KernelLog.Int(line, 0);
KernelLog.String(", row "); KernelLog.Int(row, 0); KernelLog.String(" "); KernelLog.String(msg); KernelLog.Ln;
w.String(DocType); w.Ln;
w.String("Error while processing dynamic webpage");
w.Ln; w.String("Error while processing dynamic webpage
file '");
w.String(fname); w.String("' pos "); w.Int(pos, 6);
w.String(", line "); w.Int(line, 0); w.String(", row ");
w.Int(row, 0); w.String(" "); w.String(msg); w.Ln;
w.String("
"); w.String(WebHTTPServer.ServerVersion);
w.String("")
END ReportGeneratorError;
PROCEDURE ReportXMLParserScannerError(pos, line, row: LONGINT; CONST msg: ARRAY OF CHAR); (* Error handler for the XML parser *)
BEGIN
NEW(parserError); parserError.pos := pos; parserError.line := line; COPY(msg, parserError.msg)
END ReportXMLParserScannerError;
PROCEDURE HandleClientAction(request: HTTPSupport.HTTPRequest);
VAR moduleVar, objectVar, methodVar, objectIdVar, var: HTTPSupport.HTTPVariable; par: DynamicWebpage.Parameter;
params : DynamicWebpage.ParameterList; paramTempList : TFClasses.List; activeFact: ActiveElementFactory;
handler: DynamicWebpage.EventHandler; p : ANY; varPrefix : ARRAY 40 OF CHAR;
i, prefixLength, restLength: LONGINT; session: HTTPSession.Session; objectId: Strings.String;
BEGIN
prefixLength := Strings.Length(DynamicWebpage.HTTPVarCommandParamPrefix);
moduleVar := request.GetVariableByName(DynamicWebpage.HTTPVarCommandModule);
objectVar := request.GetVariableByName(DynamicWebpage.HTTPVarCommandObject);
objectIdVar := request.GetVariableByName(DynamicWebpage.HTTPVarCommandObjectId);
methodVar := request.GetVariableByName(DynamicWebpage.HTTPVarCommandMethod);
IF (DEBUG) THEN
IF (moduleVar # NIL) THEN KernelLog.String(moduleVar.value) END;
KernelLog.String(".");
IF (objectVar # NIL) THEN KernelLog.String(objectVar.value) END;
KernelLog.String(".");
IF (methodVar # NIL) THEN KernelLog.String(methodVar.value) END;
IF (objectIdVar # NIL) THEN KernelLog.String(" id="); KernelLog.String(objectIdVar.value) END;
KernelLog.Ln
END;
IF ((moduleVar # NIL) & (objectVar # NIL) & (methodVar # NIL)) THEN
(* search all parameters *)
NEW(paramTempList);
request.variables.Lock;
FOR i := 0 TO request.variables.GetCount()-1 DO
p := request.variables.GetItem(i); var := p(HTTPSupport.HTTPVariable);
Strings.Copy(var.name, 0, prefixLength, varPrefix);
restLength := Strings.Length(var.name)-prefixLength;
IF ((varPrefix = DynamicWebpage.HTTPVarCommandParamPrefix) & (restLength > 0)) THEN
NEW(par); NEW(par.name, restLength+1);
Strings.Copy(var.name, prefixLength, restLength, par.name^);
NEW(par.value, Strings.Length(var.value)+1); COPY(var.value, par.value^);
paramTempList.Add(par)
END
END;
request.variables.Unlock;
NEW(params);
IF paramTempList.GetCount() > 0 THEN
NEW(params.parameters, paramTempList.GetCount());
FOR i := 0 TO paramTempList.GetCount()-1 DO
p := paramTempList.GetItem(i); params.parameters[i] := p(DynamicWebpage.Parameter)
END
ELSE
params.parameters := NIL
END;
(* invoke the event delegate *)
activeFact := FindActiveElemFactory(moduleVar.value, objectVar.value);
IF (activeFact # NIL) THEN
session := HTTPSession.GetSession(request);
IF (objectIdVar # NIL) THEN
objectId:= GetObjectId(objectIdVar.value, request)
ELSE
objectId := NIL
END;
handler := activeFact.FindEventHandler(session, objectId, methodVar.value);
IF (handler # NIL) THEN
(* here would be an exception handler fine *)
handler(request, params)
ELSE
KernelLog.String("Dynamic Webpage Plugin: Event handler '"); KernelLog.String(methodVar.value);
KernelLog.String("' in "); KernelLog.String(moduleVar.value); KernelLog.String("."); KernelLog.String(objectVar.value);
KernelLog.String(" is not registered to handle webclient events. If you use a statefull active element then you");
KernelLog.String(" have to specify the instance id."); KernelLog.Ln
END
ELSE
KernelLog.String("Dynamic Webpage Plugin: Active element ");
KernelLog.String(moduleVar.value); KernelLog.String("."); KernelLog.String(objectVar.value);
KernelLog.String(" is not registered."); KernelLog.Ln
END
END
END HandleClientAction;
PROCEDURE ClearFactoryList;
VAR p: ANY; fact: ActiveElementFactory; i: LONGINT;
BEGIN
IF (registeredActiveElemFact # NIL) THEN
registeredActiveElemFact.Lock;
FOR i := 0 TO registeredActiveElemFact.GetCount()-1 DO
p := registeredActiveElemFact.GetItem(i); fact := p(ActiveElementFactory); (* fact # NIL *)
fact.PrepareDisposal
END;
registeredActiveElemFact.Unlock;
registeredActiveElemFact := NIL
END
END ClearFactoryList;
PROCEDURE ReadRegisteredModules;
VAR elem, child: XML.Element; enum: XMLObjects.Enumerator; p: ANY; childName, moduleName: Strings.String;
attr: XML.Attribute;
BEGIN
ClearFactoryList;
NEW(registeredActiveElemFact);
IF (Configuration.config # NIL) THEN
elem := Configuration.config.GetRoot();
elem := Configuration.GetNamedElement(elem, "Section", DynamicWebpage.ConfigurationSupperSectionName);
IF (elem # NIL) THEN
elem := Configuration.GetNamedElement(elem, "Section", DynamicWebpage.ConfigurationSubSectionName);
IF (elem # NIL) THEN
enum := elem.GetContents();
WHILE (enum.HasMoreElements()) DO
p := enum.GetNext();
IF (p IS XML.Element) THEN
child := p(XML.Element); childName := child.GetName();
IF (childName^ = "Setting") THEN
attr := child.GetAttribute("value");
IF (attr # NIL) THEN
moduleName := attr.GetValue();
RegisterModuleByName(moduleName)
END
END
END
END
ELSE
KernelLog.String("Dynamic Webpage plugin: In Configuration.XML under '");
KernelLog.String(DynamicWebpage.ConfigurationSupperSectionName); KernelLog.String("' is no section '");
KernelLog.String(DynamicWebpage.ConfigurationSubSectionName); KernelLog.String(" defined."); KernelLog.Ln
END
ELSE
KernelLog.String("Dynamic Webpage plugin: In Configuration.XML is no section '");
KernelLog.String(DynamicWebpage.ConfigurationSupperSectionName); KernelLog.String("' defined."); KernelLog.Ln
END
ELSE
KernelLog.String("Dynamic Webpage plugin: Cannot open Configuration.XML"); KernelLog.Ln
END
END ReadRegisteredModules;
PROCEDURE RegisterModuleByName(moduleName: Strings.String);
VAR module: Modules.Module; factory : DynamicWebpage.ActiveElementDescSetFactory; i: LONGINT; res: WORD;
msg: ARRAY 1024 OF CHAR; desc: DynamicWebpage.ActiveElementDescriptor;
descList: DynamicWebpage.ActiveElementDescSet;
BEGIN
(* load the module if not already loaded *)
module := Modules.ThisModule(moduleName^, res, msg);
IF ((res = 0) & (module # NIL)) THEN
GETPROCEDURE(moduleName^, DynamicWebpage.ProcNameGetDescriptors, factory);
IF (factory # NIL) THEN
descList := factory();
IF (descList # NIL) THEN (* register all present descriptors *)
FOR i := 0 TO descList.GetCount()-1 DO
desc := descList.GetItem(i);
RegisterActiveElement(moduleName, desc)
END
ELSE
KernelLog.String("Dynamic Webpage Plugin: Wrong result type from procedure '");
KernelLog.String(DynamicWebpage.ProcNameGetDescriptors); KernelLog.String("' in module '");
KernelLog.String(moduleName^); KernelLog.String("'"); KernelLog.Ln
END
ELSE
KernelLog.String("Dynamic Webpage Plugin: Procedure '"); KernelLog.String(DynamicWebpage.ProcNameGetDescriptors);
KernelLog.String("' in module '"); KernelLog.String(moduleName^); KernelLog.String("' is not present."); KernelLog.Ln
END
ELSE
KernelLog.String("Dynamic Webpage Plugin: Module '"); KernelLog.String(moduleName^);
KernelLog.String("' is not present."); KernelLog.Ln
END
END RegisterModuleByName;
PROCEDURE RegisterActiveElement(moduleName: Strings.String; desc: DynamicWebpage.ActiveElementDescriptor);
VAR activeElemFact : ActiveElementFactory;
BEGIN
IF (desc.factory # NIL) THEN
NEW(activeElemFact, moduleName, desc);
(* the new active element instance is created by the first usage and it is then determined by the dynamic type of
factory method result whether it is a statefull or stateless active element *)
registeredActiveElemFact.Add(activeElemFact);
IF ((DEBUG) OR (ShowRegisteredElements)) THEN
KernelLog.String("Active element '"); KernelLog.String(moduleName^); KernelLog.String(".");
KernelLog.String(desc.elementName); KernelLog.String("' has been registered."); KernelLog.Ln
END
ELSE
KernelLog.String("Dynamic Webpage Plugin: No factory method defined for active element '");
KernelLog.String(desc.elementName); KernelLog.String("' in module '");
KernelLog.String(moduleName^); KernelLog.String("'"); KernelLog.Ln
END
END RegisterActiveElement;
PROCEDURE LockServingHosts;
BEGIN {EXCLUSIVE}
AWAIT(~lockServingHosts); lockServingHosts := TRUE
END LockServingHosts;
PROCEDURE UnlockServingHosts;
BEGIN {EXCLUSIVE}
lockServingHosts := FALSE
END UnlockServingHosts;
PROCEDURE Install*(context : Commands.Context); (** [{host}]. Host may include wildcards. *)
VAR host: ARRAY 1024 OF CHAR; hl: WebHTTPServer.HostList;
BEGIN
LockServingHosts;
IF dynamicPagePlugin = NIL THEN (* Singleton *)
NEW(dynamicPagePlugin, PluginName)
END;
IF (servingHosts.GetCount() = 0) THEN
ReadRegisteredModules
END;
REPEAT
context.arg.String(host); Strings.Trim(host, " ");
hl := WebHTTPServer.FindHosts(host);
IF (hl # NIL) THEN
WHILE (hl # NIL) DO
context.out.String(PluginName);
IF (servingHosts.IndexOf(hl.host) >= 0) THEN
context.out.String(" already installed at ")
ELSE
hl.host.AddPlugin(dynamicPagePlugin);
servingHosts.Add(hl.host);
context.out.String(" added to ")
END;
IF (hl.host.name = "") THEN context.out.String("default host ")
ELSE context.out.String(hl.host.name)
END;
context.out.Ln;
hl := hl.next
END
ELSE
context.error.String("Host '"); context.error.String(host); context.error.String("' not present."); context.error.Ln
END
UNTIL ((context.arg.res # Streams.Ok) OR (Strings.Length(host) = 0));
UnlockServingHosts;
END Install;
PROCEDURE ModuleTerminator;
VAR p: ANY; h: WebHTTPServer.Host; i : LONGINT;
BEGIN
LockServingHosts;
FOR i := 0 TO servingHosts.GetCount()-1 DO
p := servingHosts.GetItem(i); h := p(WebHTTPServer.Host);
UnInstallHost(h)
END;
UnlockServingHosts;
ClearFactoryList
END ModuleTerminator;
PROCEDURE UnInstallHost(host: WebHTTPServer.Host);
BEGIN
host.RemovePlugin(dynamicPagePlugin);
KernelLog.String(PluginName); KernelLog.String(" removed from ");
IF (host.name = "") THEN KernelLog.String("default host ")
ELSE KernelLog.String(host.name)
END;
KernelLog.Ln
END UnInstallHost;
PROCEDURE Uninstall*(context : Commands.Context); (** [{host}]. Host may include wildcards *)
VAR host: ARRAY 1024 OF CHAR; hl: WebHTTPServer.HostList;
BEGIN
IF dynamicPagePlugin # NIL THEN
LockServingHosts;
REPEAT
context.arg.String(host); Strings.Trim(host, " ");
hl := WebHTTPServer.FindHosts(host);
IF (hl # NIL) THEN
WHILE (hl # NIL) DO
UnInstallHost(hl.host);
servingHosts.Remove(hl.host);
hl := hl.next
END
ELSE
context.error.String("Host '"); context.error.String(host); context.error.String("' not present."); context.error.Ln
END
UNTIL ((context.arg.res # Streams.Ok) OR (Strings.Length(host) = 0));
UnlockServingHosts
ELSE
context.error.String(PluginName); context.error.String(" is not installed"); context.error.Ln
END;
IF (servingHosts.GetCount() = 0) THEN
ClearFactoryList
END;
END Uninstall;
BEGIN
NEW(servingHosts); lockServingHosts := FALSE;
Modules.InstallTermHandler(ModuleTerminator)
END DynamicWebpagePlugin.
System.Free DynamicWebpagePlugin~
System.Free WebHTTPServerTools WebHTTPServer WebHTTP~
DynamicWebpagePlugin.Install ~
DynamicWebpagePlugin.Uninstall ~