"Webgate" is much more complex than "formit". The C/C++ "webgate" contains about 9,000 lines of code while the Java one contains about 20,000 lines. During the reengineering, we greatly enhanced the flexibility and scalability of the system by making the Java "webgate" evolve to a new layer (user information layer) and maintaining weak coupling in the design. By allowing the Java "webgate" communicate with multiple MARIAN servers in parallel and thus distribute searching to multiple machines, the scalability of the system is also increased.
The C/C++ "webgate" creates and manages a number of sessions based on the user input it gets from "formit". It is the "webgate" which maintains the state information on top of HTTP (since HTTP is stateless). The state information is embedded inside the HTML responses that "webgate" sends back to "formit".
[Figure 5.1] describes the top-level architecture of the C/C++ "webgate".
Figure 5.1. C/C++ "webgate" top-level architecture
(We use ellipses to represent objects and rectangles to represent threads in all the design diagrams in this thesis.)
It is composed of five major parts the "listen_forever" thread, "new_connection" thread(s), a "uip" object, a "session_pool" object, and a "delete_old_session" thread.
The "listen_forever" thread is responsible for accepting "formit" connections. It keeps listening on the well-known port of the "webgate". Whenever a "formit" connection is detected, this thread will generate a "new_connection" thread to handle this connection, and then will listen on the well-known port again.
Each "new_connection" thread is responsible for getting user input from "formit", sending requests to the MARIAN server (through "uip"), getting result documents from "session_pool", and returning an HTML response to "formit". A "new_connection" thread is created for each "formit" connection detected. The thread will die after it sends back the HTML response.
The "uip" (user interaction protocol) object is responsible for communication between "webgate" and the MARIAN server. It can pass complicated data structures from one part to the other and vice versa. The complicated data structure here is represented as a function in the MARIAN system. Each function has a name and a number of parameters. There are several types of parameters. Some types, like integer and string, are very simple. Others are complicated, like array of array of integers. Readers can just regard functions passed through "uip" as complicated data structures. Functions passed by "uip" may contain query data, documents, and document circulation information.
The "session_pool" object manages a number of "session" objects. Each "session" object represents a user's activity over a certain period of time. There is a timer inside each "session" object. Each time the corresponding user performs an action involving the system this timer will be updated. So the "session" can tell how long the corresponding user has been inactive based on the current time and the last time the timer was updated.
The "delete_old_session" thread is used to delete old "sessions" when they are inactive for a long period of time. This thread will check the "session_pool" from time to time. If it finds an "old" "session" it will remove it from the "session_pool" and release resources occupied by it.
Each "session" object contains a number of queries performed by the corresponding user within this session. Each "query" object contains query data and query results. Query data is the information submitted by the end user, and query results are the documents returned by the MARIAN server for this query. Each session object also has a state variable. This variable is used for the last query performed. The session behaves as a state machine. Its operation will be explained next.
Upon starting, "webgate" creates an empty "session_pool" object. Then the thread "delete_old_session" is created for house cleaning. After that a "uip" object is started for communication with the MARIAN server. At last the thread "listen_forever" waits for connection(s) from "formit" by listening on a well-known TCP port (using the "webgate" port number).
When an end user of the system performs an action in the browser, "formit" will be run by the Web server. "Formit" gets the user input from the Web server and opens a socket connection to the well-known port of the "webgate". The "listen_forever" thread accepts the connection and creates a "new_connection" thread to handle this "formit". The "new_connection" thread reads the user input from "formit". If the user wants to submit a query, the thread tries to find a "session" from the "session_pool" corresponding to this user. If there is no such "session" (either this is the first time the user has accessed the system or the "session" has been removed by the thread "delete_old_session"), it will inform the "session_pool" to create one. Then it changes the state of the "session" to "query_sent_out", indicating this "session" just submitted a query and is waiting for result documents. After that, it sends the query (user input) to the "uip" object. The query is passed to the MARIAN server by the "uip" object in two functions, "search_collection" and "biblio_query_text". The parameters of the first function indicate on which collection the query should be performed (since one MARIAN server may support multiple collections). The parameters of the second function contain the query data, like the user's input in the three text fields and the options for the three fields.
After all these things are done, the thread keeps on checking the state of the "session" to see if documents have come back for the query. Some time after the query is sent to the MARIAN server, the server may return a number of documents regarding this query to the "webgate". The documents are passed to the "uip" object in the function "show_retrieval_coll". The "uip" object stores the documents in the corresponding "session" object and sets the state of the "session" to "query_done", indicating that the query results are ready. When the thread "new_connection" finds that the state of the session has changed, it takes out the documents, creates an HTML page with them, and passes the HTML page to the "formit" it is connecting with.
As explained in chapter 3, the "formit" then passes the HTML page to the Web server which passes it to the browser, and the browser shows the HTML page to the end user.
The "new_connection" thread will die after it serves the "formit" request. Another "new_connection" thread will be created if a new "formit" connection is detected by the "listen_forever" thread. There may be multiple "new_connection" threads running at the same time since there may be multiple "formit" invocations active at the same time.
The Java "webgate" creates and manages a number of "user" objects. A "user" object contains more information than a session object in the C/C++ "webgate" and is persistent. The Java "webgate" also can communicate with multiple MARIAN servers at the same time and merge documents coming back from them.
[Figure 5.2] illustrates the top-level architecture of the Java "webgate".
Figure 5.2. Java "webgate" top-level architecture
Viewed from the top-level, it has seven major parts: "main" thread, "request_response" thread(s), "log_manager" object, "configuration" object, "user_manager" object, "call_back_processor" object, and "uip_manager" object.
The "configuration" object contains configuration information for "webgate". This currently includes the "webgate" well-known port number and the directories from which to create the "user_manager" object, "uip_manager" object, and "log_manager" object.
As its name suggests, the "log_manager" object manages the logs of the system. It is responsible not only for writing system logs to a file but also for retrieving logs from system log files.
The "uip_manager" object manages the communication between "webgate" and multiple MARIAN servers. [Figure 5.3] illustrates its architecture.
Figure 5.3. "Uip_manager" object architecture
This object creates and manages a number of "client_uip" objects, each of which is responsible for communication with one MARIAN server. All the data (queries or result documents) transmitted between the "webgate" and a MARIAN server go through the "client_uip" object connected to that MARIAN server. The "uip_manager" also creates a thread "dynamic_uip_manage". From time to time, this thread searches the available MARIAN servers and then informs the "uip_manager" to create new "client_uip" objects to connect to new servers or delete some "client_uip" objects connected to old servers.
The "user_manager" object manages a number of "user" objects. Each "user" object corresponds to an end user of the system. [Figure 5.4] illustrates the architecture of the "user_manager" object.
Figure 5.4. "User_manager" object architecture
Upon creation, the "user_manager" object creates a thread "user_manage". From time to time, this thread informs the "user_manager" object to unload user information from memory to disk for those users who have been inactive for a long period of time. Each "user" object contains a "preferences" object and a "query_manager" object. The "preferences" object contains the preferences of this user such as the default time-out value for performing a query, the batch size, and so on. Time-out value specifies how long the system will block the user when waiting for the results of a query to come back. Batch size specifies how many documents the system should return each time the user submits a query or clicks the "Get More" button. The "query_manager" object contains all the queries performed on the system by this user to date. Each "query" object contains a "query_data" object which is filled by the user when submitting the query and a "results" object which contains all the documents returned by the MARIAN server regarding this query.
The "call_back_processor" object in [Figure 5.2] is used to process functions received from MARIAN servers. For example, if a function comes from a MARIAN server which contains documents corresponding to a certain query performed by a certain user, the "call_back_processor" will take the documents out of the function, find the corresponding "user" object, find the "query" object from the "user" object, and store the documents into the "results" object of the "query".
The "main" (see [Figure 5.2]) thread's responsibility is similar to the "listen_forever" thread in the C/C++ "webgate". It listens on the well-known port of the "webgate" and generates a "request_response" thread when it detects a "formit" connection.
The "request_response" (see [Figure 5.2]) thread is similar to the "new_connection" thread in the C/C++ "webgate" except for being more object-oriented. It is created when a "formit" connection is detected. [Figure 5.5] illustrates its architecture and workflow.
Figure 5.5. "Request_response" thread architecture and workflow
When the "main" thread creates a "request_response" thread, it passes to the "request_response" thread a socket which connects to a "formit". The "request_response" thread first reads out a "request" object from the socket, then creates a "response" object using the "request" object and possibly the "uip_manager" and "user_manager" objects. After that it writes the "response" object to the socket. Finally it logs the "request"-"response" pair to the "log_manager" object and then dies.
At system startup, the "configuration" object is created. This object reads from a configuration file all the configuration information of the "webgate". Then the object "user_manager" is created from the directory specified by the "configuration" object. The "user_manager" will be empty if this is the first time the "webgate" is run. If the directory already contains user information the "user_manager" object will read it out. Then the "call_back_processor" object and "uip_manager" objects are created for communication with MARIAN servers. A "log_manager" object also is created to manage the logs of the system. At last the "main" thread begins to listen on the well-known port for "formit" connections.
When it detects a "formit" connection, the "main" thread will create and start a "request_response" thread. The thread first reads out a "request" from the socket input stream, then creates a "response" for this "request" possibly using "uip_manager" and "user_manager" objects.
Each request has a type. When the end user clicks different buttons on different pages of the system, requests of different types will be created. The C/C++ "webgate" recognizes about 10 request types while the Java "webgate" extended the number to more than 100. All types of requests are handled by the constructor of the "response" object separately. Table 5.1 illustrates some of the request types along with their descriptions in the Java "webgate".
Table 5.1. Java "webgate" request types and descriptions
| Request description | Request type |
|---|---|
| User clicks button "CANCEL" on change password page. | change_password_cancel |
| User clicks button "NEW" on main menu page | main_menu_new_query |
| Super user clicks on button "OK" on log file contents page | log_file_contents_ok |
| Super user clicks on button "ONLINE HELP" on log file contents page | log_file_contents_help |
| ... | ... |
Suppose the "request" specifies that the user wants to submit a query. The constructor of the "response" object will first use the user name in the "request" (almost all the requests contain user name since this is a user oriented system) to find the corresponding "user" object from the "user_manager" object. Then it creates a query from the information contained in the "request" and adds the query into the "query_manager" object of the "user" object. After that it sends a "search_collection" function to "uip_manager" to pass to MARIAN server(s). The function will be passed to one or multiple MARIAN servers based on how many servers have been selected by the user in the "request". After that the constructor of the "response" object keeps on checking the "results" object of the "query" object.
When the MARIAN server(s) receive the "search_collection" function(s), they will respond by asking for queries appropriate to their particular collection(s). A library catalog server, for instance, will respond with a "solicit_biblio_query" function asking the user to fill the form to do the search. The function(s) are received by "uip_manager" and passed to "call_back_processor". All the functions passing through "uip_manager" contain user name and query number specifying which "query" object of which "user" object the function corresponds to. The "call_back_processor" uses the user name and query number to find the corresponding "query" object, gets the user input from its "query_data" object, and sends back a "biblio_query_text" function with the user input in it to MARIAN server(s).
Sometime later, the MARIAN server(s) return(s) documents in the form of the function "show_retrieval_coll". The function gets passed to "call_back_processor" by the "uip_manager" as before. This time the "call_back_processor" takes out the documents from the function, finds the corresponding "query" object from the user name and query number contained in the function, and stores the documents to the "results" object in the "query" object.
Later on, the constructor of the "response" object detects that there are new documents in the "results" object it is checking, extracts the documents, and forms an HTML page from them. Here the constructor of the "response" finishes its task and a "response" object corresponding to this "request" object is created.
The "request_response" thread writes the "response" object to "formit" through the socket output stream and then logs the "request"-"response" pair to the "log_manager" and dies.
Not all the "requests" require the constructor of the "response" object to communicate with "uip_manager". Some of them (like change user password and preference) only require the communication with the "user_manager" to get and/or change the corresponding user information, and thus are much faster to process.
A super user can shut down the system from any browser. The system first informs the "log_manager" to exit. The "log_manager" will close any log file(s) it opened. Then the system asks the "uip_manager" to exit. The "uip_manager" will disconnect all the connections to MARIAN server(s) and close all the sockets it created. It also will kill the "dynamic_uip_manage" thread it created. Finally the system asks the object "user_manager" to exit. The "user_manager" object first stops the thread "user_manage" it created and then writes all user information to disk.
Since the system writes back all the user information to disk before its shutdown, the user information is kept between different runs of the system. This means that even if the system is restarted, all the queries performed by a user along with his/her preferences are not lost. Even if the system crashes during execution or the shutdown does not occur properly, since the "user_manage" thread informs the "user_manager" object to unload user information to disk from time to time, the majority of user information is not lost.
By maintaining user information like queries performed and preferences, the Java "webgate" has evolved to a new (user information) layer in our system design. As a result, the new system can provide more flexible services to its users.
Due to this design, the system allows its users to view queries performed by them earlier. A user also is allowed to submit a query to the system based on one of his previous queries. Queries performed by different users may have different values in terms of the time-out value and batch size since each person may have different preferences.
Also due to this design, the super user of the Java "webgate" is allowed to manage individual users of the system. For example, a super user can check when a certain user first logged into the system. A super user also can check how many queries have been performed by a certain user to date. Of course, a super user can delete users along with their queries and preferences.
This design also makes the logs recorded by the "log_manager" object more valuable to system designers. With such a design, the "log_manager" is able to split logs by users, thus enabling user characteristics analysis not possible otherwise.
Since the system maintains persistent user information, it can provide some valuable functions not possible in traditional session based systems. For example, in the Java "webgate", after a user submits a query to the system and before documents come back, he/she can close the browser or even the computer. In this case, the "webgate" will take care of the result documents when they come back. So when the user comes back to the system later (for example, the next day), the system can still present the query along with the result documents to him/her.
Many other flexible services can be added easily to the system due to the user information layer. The next chapter gives a detailed description of the benefits associated with this layer.
Weak coupling was maintained throughout the design of the Java "webgate". Viewed from the top level, weak coupling was maintained between major objects of the Java "webgate". Weak coupling also was maintained inside major classes as much as possible. All this makes the "webgate" very flexible.
Weak coupling was maintained between the object "user_manager" and the rest of the system (see [Figure 5.2]). Other objects of the system only know that the "user_manager" object creates and manages a number of "user" objects. They can inform the "user_manager" to create a new "user" from a user name and password or they can get a "user" object from the "user_manager" by passing it a user name or ID. They don't know that inside the "user_manager" object, there is the thread "user_manage" which takes care of the cache management. Thus if the mechanism inside the "user_manager" was changed (for example, databases were used), there is no need to change other classes.
Similar weak coupling was maintained between the "uip_manager" object and other classes of the system. Other classes don't know that there is a thread "dynamic_uip_manage" which is responsible for searching current MARIAN servers from time to time. They even don't know that the "uip_manager" is using multiple "client_uip" objects to communicate with multiple MARIAN servers. They only know that the "uip_manager" can communicate with multiple MARIAN servers, each server is identified by a hostname and port number, the server list may change from time to time, and the "uip_manager" can tell the current server list. These design decisions were made to achieve weak coupling between "uip_manager" and other classes. It allows the flexibility to change the mechanism of the "uip_manager" (for example, not use the service of "client_uip") without affecting other classes in the system.
The "log_manager" object also has very weak coupling with other classes in the system. The "request_response" thread only knows to inform the "log_manager" to log a "request"-"response" pair. The "response" object only knows that it can get log content from the "log_manager" by passing it a query. How a "request" "response" pair is written to logs and how the "log_manager" processes a log query are hidden from other classes. Those design decisions make the coupling between "log_manager" and other classes weak. Currently, the "log_manager" only creates one log file in its directory. If we change this mechanism to let the "log_manager" create multiple log files, or use databases to store logs, or even write the logs to another computer through a socket, no changes will be needed for other classes in the system.
Weak coupling was maintained between the "user_manager" object and the "user_manage" thread (see [Figure 5.4]). From time to time, the "user_manage" thread asks the "user_manager" object to unload user information for those users who are inactive longer than a certain period of time. The thread does so by calling the method "unload(long time)" of the "user_manager" object. Thus the thread is responsible for applying a caching algorithm to calculate values like the interval used to inform the "user_manager" object to unload users and how long a user must be inactive before considered inactive. It does not know how the user information is written back to disk. On the other hand, the "user_manager" object knows how to unload user information, but does not know what caching algorithm is being used. These design decisions make the coupling between the two weak. As a result, if we want to change the caching algorithm (for example, from fixed values to dynamically adjustable values), we only need to change the "user_manage" thread. Also if we want to change the way to unload user information to disk (for example, change number of directories and files written), we only need to change the "user_manager" class or classes in it.
Weak coupling was also maintained among objects "user_manager", "user", "query_manager", and "query". The "user_manager" object knows that a "user" object has a user name, password, and user ID. It knows to unload the information inside a "user" object to disk by calling the latter's method "unload()". But it doesn't know what information there is inside a "user" object and how the information is written back to disk. The same thing applies to the "user" and "query_manager" objects. A "user" object calls the method "unload(..)" of its "query_manager" object to unload query information to disk. It doesn't know how the queries are stored in memory or how they are written back to disk. Again, the "query_manager" object only knows to call the method "unload(..)" of a query object to do so. As a result of this weak coupling, if we want to change the way query information is stored in memory and/or it is written back to disk, we only need to change the class "query" and classes inside it. No other classes need to be changed.
Weak coupling was maintained between the thread "dynamic_uip_manage" and the object "uip_manager" (see [Figure 5.3]). The thread "dynamic_uip_manage" is responsible for searching available MARIAN servers and for asking the "uip_manager" object to add new server(s) or delete old server(s). The thread doesn't know how new server(s) are added or old server(s) are deleted in the "uip_manager" object. On the other hand, the object "uip_manager" doesn't know what protocol is used to search available MARIAN servers. Thus if we want to change the protocol used to search MARIAN servers, we only need to change the thread "dynamic_uip_manage" (in fact, only the method "search_server(String protocol)" of it). If we want to change the way to add or delete a server, we only need to change the object "uip_manager".
The coupling between the objects "uip_manager" and "client_uip" is weak too. "Uip_manager" knows that each "client_uip" object connects to a MARIAN server identified by hostname and port number. It doesn't know that the communication between a "client_uip" and a MARIAN server is currently using two sockets. It also does not know that the "client_uip" object is using the XDR protocol [21] to pass data through sockets to maintain platform independence. As a result of this weak coupling, if we want to change the mechanism to pass functions (for example, using one socket or not using XDR), we only need to change the "client_uip" class.
Please refer to [Figure 5.5]. The thread only knows a "request" object can create itself from a stream; a "response" object can be created by passing it a "request" object and objects "uip_manager" and "user_manager"; and a "response" object can write itself to a stream. The thread doesn't know what is really read out when creating a "request" object from a stream. It also doesn't know what is written to a stream when a "response" object writes itself to the stream. Those design decisions were made to keep the coupling among them weak. As a result, if we want to change the data read out from a stream when a "request" object is created from it, or the data written to a stream when a "response" object writes itself to the stream, the "request_response" thread need not to be changed.
When a "request" object is created from a stream, it only cares about the content read out from the stream. It doesn't care what is used to implement the stream. The same thing happens to the "response" object when it is written to a stream. Thus if we change the implementation of the streams, we don't need to change the two classes.
[Figure 5.6] illustrates the relationship among a "results" object of a "query" object, a "request_response" thread, and the "call_back_processor" object.
Figure 5.6. Relation among "results", "request_response", and "call_back_processor"
The "call_back_processor" object only knows to store documents returned by the MARIAN server(s) to a "results" object. The "request_response" thread knows to take out documents from a "results" object of a certain "query" object of a certain "user" object. How the "results" object stores documents possibly returned by multiple MARIAN server(s) and how it orders documents are the "results" object's own responsibility. They are hidden to other classes. These design decisions were chosen to maintain weak coupling between them. As a result of this weak coupling, we can apply various sorting and merging rules inside the "results" object without affecting any other classes in the future. This brings flexibility to the system.
The Java "webgate" evolved to a new (user information layer) layer in the system. It contains much more information than the C/C++ "webgate". This includes all the "user" objects and all the queries performed by each user. Thus how to store and manage so much information becomes a problem in terms of system scalability. For example, suppose there are 1,000 users, each user performs 50 queries on the average, and each query results in 20KB documents. Then the information for all the queries will take 1GB. Obviously, storing all this in memory is not a good solution. Thus, how to store and manage so much information becomes a problem in terms of system scalability. The cache management of the "user_manager" was designed to solve this problem. From time to time, the thread "user_manage" informs the "user_manager" to unload user information. As a result, only those users who are currently active have their information in the memory of the system. Even for those users, only the queries they performed recently are in memory; queries they haven't accessed for a long time are written back to disk. To increase the speed of the system, a change flag is maintained for each user. When the information of a user needs to be unloaded to disk, the system first checks the flag to see whether the information of this user has been changed since it was last read out from the disk. No writing will happen unless there are changes. This mechanism greatly reduces the memory occupied by the Java "webgate" and thus increases its scalability.
The design of the "uip_manager" improves the scalability of the system when the MARIAN server is the bottleneck. In this case, we can partition our document collection across several MARIAN servers and let the Java "webgate" send queries to them at the same time through the "uip_manager". Though the query rate to each of them is not changed, they will respond faster because each contains databases of much smaller size.
The design of the "uip_manager" also facilitates the improvement of system scalability in terms of the number of MARIAN servers supported. If the MARIAN system becomes very popular in the future, there may be hundreds or even thousands of MARIAN servers all over the world. There may be even more "webgates". Obviously, letting each "webgate" maintain persistent TCP socket connections to each MARIAN server will waste too many system resources. Ideally, each "webgate" should maintain semi-persistent socket connections to each MARIAN server: when there are data sent from one to the other, the connections exist. The connections will be removed when there are no data transmissions between the two over long periods of time. The design of the "uip_manager" makes this implementation easy. Only the "client_uip" class needs to be changed due to the weak coupling between it and the rest of the system (see section 5.3.2.3). A timer will be needed in the class; the timer will be updated each time a function is passed or received. If there is no transmission over a certain period of time (for example, 5 minutes), the "client_uip" will remove the socket connection with the MARIAN server. When the next function comes, it will reconnect to the server and repeat the above operation. All these changes are inside this class and transparent to the rest of the system. But these changes will greatly increase the scalability of the system by using the network connections more efficiently.
[Figure 5.7] illustrates the beginning page of the new system.
Figure 5.7. New system beginning page
From this page, a new user (a user who first uses this system) can register by clicking the button "REGISTER" and then entering his/her user name and password. An old user (a user who has already opened an account in the system) can enter the system by typing in his/her user name and password directly. In either case the system will present the following page to the user, as in [Figure 5.8].
Figure 5.8. New system main menu page
Four major functions are provided from this page. The "PASSWORD" button allows the user to change his/her password. The "PREFERENCES" button allows the user to change his/her preferences, like query time-out value or batch size. If the user clicks on the button "NEW", the system will bring the user to a page where he/she can enter a new query, as shown in [Figure 5.9].
Figure 5.9. New system query page
Since this page is large, we used two screen drawings to show it. The top part of the page is similar to the query page in the old system, providing three fields to enter the query data. But in the new interface another field -- Query Comments -- is added. This field allows entering of a reminder comment about this query, for the user to view it from the query history page. Also there is a section "Servers" which lists all the MARIAN servers currently available. The user can select any or all of the servers to perform the query by marking the checkbox(s) corresponding to the server(s). Finally there is a section "Preferences" where the user can customize values for this query. The default values are this user's most recent selections of preferences.
If the user clicks the "HISTORY" button from the main menu page, the system will bring the user to the query history page illustrated in [Figure 5.10].
Figure 5.10. New system query history page
On this page, the system lists all the queries performed by this user to date. For each of them the system lists the time the query was performed, the comments the user entered when performing the query and the number of documents returned for the query. The user can mark a radio box near a query and then click the button "EDIT" to see and perhaps modify the query content he/she entered at that time, or he/she can click the button "RESULTS" to see the documents returned for this query previously. When viewing the query content, the user may choose to make some modifications and then invoke the query against the current collection. In this case, the modified query becomes a new query item in the query history. The user also is allowed to perform a query from his/her query history without any modifications; the same query may result in different result documents since the collection may have changed.
[Figure 5.11] illustrates the page the system shows to the super user when he/she logs into the system.
Figure 5.11. New system super user main menu page
From this page, the super user is allowed to change his/her password, shut down the "webgate", do some system log analysis, as well as manage the users in the system. The following page, shown in [Figure 5.12], will appear if the super user clicks on the button "LOGS" in the above page.
Figure 5.12. New system log query page
This page allows the super user to specify how many log elements he/she wants the system to show. The following page, illustrated in [Figure 5.13], will be shown after the number of log elements is entered and the button "SUBMIT" is clicked by the super user.
Figure 5.13. New system log contents page
This page shows the latest 20 log elements of the system. For each log element, the "client" field specifies the file name of the "formit" from which this request came. The "user" field specifies the login name of the user who sent this request. The "request type" field specifies which button from which page was clicked which resulted in this request being sent out. The "response type" field specifies whether or not this request has been served correctly. The time the request is received and the time the response is created also are listed.
If the super user clicks the button "USERS" on the super user main menu page, the following page, as illustrated in [Figure 5.14], will appear.
Figure 5.14. New system user management page
This page lists all the users currently registered in the system. Information about the creation time and number of queries performed so far is included for each user. The super user can delete user(s) by marking the corresponding checkboxes and then clicking the "DELETE" button.
| ETD-ML Version 0.9.7a (beta) | http://etd.vt.edu/etd-ml/ | Mon Jul 19 11:13:10 1999 |