XML, XSLT, Java, and JSP: A Case Study in Developing a Web Application- P6

Chia sẻ: Thanh Cong | Ngày: | Loại File: PDF | Số trang:50

lượt xem

XML, XSLT, Java, and JSP: A Case Study in Developing a Web Application- P6

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

XML, XSLT, Java, and JSP: A Case Study in Developing a Web Application- P6: Là một nhà phát triển Web, bạn biết những thách thức trong việc xây dựng các ứng dụng mạnh mẽ trên nhiều nền tảng. Tạo các ứng dụng di động trở nên thật sự có thể bằng cách sử dụng Java cho code và XML để tổ chức và quản lý dữ liệu. "XML, XSLT, Java, và JSP: Một trường hợp học" sẽ giúp bạn tối đa hóa khả năng của XML, XSLT, Java, và JSP trong các ứng dụng web của bạn....

Chủ đề:

Nội dung Text: XML, XSLT, Java, and JSP: A Case Study in Developing a Web Application- P6

  1. 232 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore Table 8.7 JSP Transitions vs. Actor-Chat Relationships Chat Exists Already Chat DoesNot Exist Actor Is Not Actor Is Actor Is in Chat Host Guest visitor guest host guest host executes starts chat executes chat(1) executes chat(1) executes chat(1)(2) chat visitor guest executes host guest forum joins chat chat executes chat(3) executes chat error Here are some notes for this table: n Numbered table items are optional, to be set by user preferences in a command, with alternatives as follows: 1. visitor starts chat 2. host executes chat, if multihosted chat allowed 3. guest executes chat n If the actor is a host or a guest, the actor is rejoining the chat. Rejoining Existing Chats As you can see, if a chat with the requested subject and topic combination does not exist, the visitor will become the host of a new chat for that combination. If the requested chat exists already, then what happens depends upon an option setting. One option is that the user will be taken back to “visitor starts chat” to try again with a dif- ferent subject, topic, or both. (Actually, in the release of bonForum for this book, this and other options listed in the notes are not yet available!) As seen in the table cells for the “visitor starts chat” row, the outcome when a requested chat already exists can be made to depend upon whether the visitor is already a host or a guest in that chat. If not, the visitor becomes a new guest in the chat. If the visitor already a member of the chat, the visitor rejoins as a host or a guest, whichever was the case before. (Again, in the book release of bonForum, the options within the table cells are the only options!) In a later release of bonForum, we will implement user preference settings using session attributes. Choices can be offered for the behavior of “visitor starts chat” when the requested chat already exists, as follows: 1. Always warn the user and request a new subject or new topic. 2. If the actor was in the chat, always join it with the previous status; otherwise, warn and ask again. 3. If the actor was in the chat, always join as a guest; otherwise, warn and ask again.
  2. 8.1 The BonForumEngine Servlet 233 All these choices can be modified further according to whether the actor is restarting the current chat. Until these preference settings are added, bonForum implements only the second choice. Looking at the table again, it is very easy to cause the various outcomes of this desired logic to happen.You simply need to set the bonForumCommand value to the cor- responding value in the table cell (or optional value, when that is implemented). Implementing the logic can also be quite simple.We leave the “visitor joins chat” part aside until Section 8.1.21, “The processRequest() Method: Handling ‘Guest Executes Chat.’” Also leaving aside the numbered options (in the table notes), we could suggest the following pseudocode: set bonForumCommand to host_executes_chat if chat exists if actor is not host in chat set bonForumCommand to guest_executes_chat endif endif However, the code that actually exists is not that simple. Are the subject and the topic okay? If not, the user is sent back to reinput them. If the subject and the topic are okay, the code determines whether they have already been taken by an existing chat. If they are available, then a new chat will be started now. If they are taken, the code finds out even more. Is the visitor trying to restart the current chat for the session? (In the future, that information can be used for user messages or to control user preferences.) Is the actor already in the chat as a host or as a guest? If so, will the actor be joining or rejoining an existing chat? If so, the code must set some session attributes with the right values so that they reflect the chat. Some of the methods and variables used by this code might not become clear until later in the section. Here is the code, excerpted from the processRequest() method, with one part of it substituted by comments that show the pseudocode for the omit- ted source: if(haveSubject && haveTopic) { String fakeChatItem = chatSubject + “_[“; fakeChatItem = fakeChatItem + chatTopic + “]”; // ‘_’ is separator in a chatItem // ‘.’ is separator in pathNameHashtable keys fakeChatItem = fakeChatItem.replace(‘.’, ‘_’); // example fakeChatItem: // Animals_Bird_Hawk_[Medieval falconry] String foundChatNodeKeyKey = getBonForumStore().getBonForumChatNodeKeyKey( ➥fakeChatItem ); if((foundChatNodeKeyKey != null) && (foundChatNodeKeyKey.length() > 0)) { ➥chatExistsForSubjectAndTopic = true; //
  3. 234 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore // There is more code here, not shown! // It does the following: // // if subject and topic are not new // (requested chat is the current chat) { // if chatNodeKeyKey exists // (current chat exists) { // if foundChatNodeKeyKey is // chatNodeKeyKey { // set actorRestartingCurrentChat // true; // } else { // set // chatExistsForSubjectAndTopic // false; // set actorRestartingCurrentChat // false; // endif // endif // endif // String actorKeyValue = normalize((String)session.getAttribute( ➥“hostKey” )); if(actorKeyValue.trim().length() > 0) { actorIsHostInChat = getBonForumStore().isHostInChat( ➥actorKeyValue, foundChatNodeKeyKey ); } if(!actorIsHostInChat) { actorKeyValue = normalize((String)session.getAttribute( ➥“guestKey” )); if(actorKeyValue.trim().length() > 0) { actorIsGuestInChat = getBonForumStore().isGuestInChat( ➥actorKeyValue, foundChatNodeKeyKey ); } } } boolean actorWillRejoinChat = false; if(chatExistsForSubjectAndTopic) { // cannot start an existing chat haveTopic = false; if(actorIsHostInChat) { bonForumCommand = “host_executes_chat”; actorWillRejoinChat = true; } else if(actorIsGuestInChat) { bonForumCommand = “guest_executes_chat”; actorWillRejoinChat = true; else { // set attribute to trigger
  4. 8.1 The BonForumEngine Servlet 235 // user message that chat exists: session.setAttribute( “chatSubjectAndTopicTaken”, fakeChatItem ➥); chatTopic = “”; session.setAttribute( “chatTopic”, “” ); session.setAttribute( “newChatTopic”, “no” ); bonForumCommand = “visitor_starts_chat”; } } if(actorWillRejoinChat) { // set session attributes // usually set when actor starts new chat. // // nodeNameHashtable key // for the chat node key, needed for: // 1. adding messages to chat later. // 2. seeing if a chat is the current chat session.setAttribute( “chatNodeKeyKey”, foundChatNodeKeyKey ); // host session doesn’t need this, // but if rejoining chat as guest, might? session.setAttribute(“chatItem”, fakeChatItem); // itemKey for this chat // is added as message attributes later, // is needed for finding messages // (temporarily), // and for guest sessions to find chat. String foundChatItemKey = ➥getBonForumStore().getBonForumChatItemNodeKey( fakeChatItem ).toString(); session.setAttribute( “itemKey”, foundChatItemKey ); } } Setting haveTopic (or haveSubject) to false sends the user back to the “visitor starts chat” bonForum state. Starting a Chat In our discussion of the processRequest() method, we have come to a very important block of code, the one that transforms a bonForum visitor into a chat host. It adds quite a few elements to the XML data: a host element (if the visitor has none yet), a chat element, and a chatItem element (that relates the chat to its subject and contains its topic).The method also adds the key to the new chatItem as an XML attribute in the new chat element, which will relate the chat to its chatItem and later to its mes- sage elements. In addition, some important session attributes are set: the key to the host element, the key to the chat nodeKey in the nodeNameHashtable, and the itemKey.
  5. 236 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore All this sounds more complex than it is.The hardest part is showing how simple it is in this book. You can follow it with the source code to BonForumEngine, also in Appendix C. Finally, we suggest using an XML viewer, such as Microsoft’s free XMLpad, to follow the discussion using one of the XSLT output files that contains the complete bonForumXML contents. First, use bonForum a while from a couple of browser instances. Start a chat in one, join it in another, and send some messages from both browsers.Then use the Output bonForum XML Data option on the System Commands page (reachable from the start of bonForum).You should then be able to view the file TOMCAT_HOME\webapps\ bonForum\mldocs\bonForumIdentityTransform.xml. The following listing shows the entire block of code that starts a chat in processRequest(). After the listing, you will find a discussion of the code. if(haveSubject && haveTopic) { // actor starts chat // Each actorNickname is unique in bonForum, // Only one host node is allowed per actorNickname actorNickname = normalize((String)session.getAttribute(“actorNickname”)); // Try getting key to a host node // for current actor’s nickname NodeKey hostNicknameNodeKey = getBonForumStore().getActorNicknameNodeKey( ➥actorNickname, “host” ); NodeKey hostNodeKey = null; if(hostNicknameNodeKey != null) { BonNode hostNicknameNode = ➥getBonForumStore().getBonForumXML().getBonNode( hostNicknameNodeKey); hostNodeKey = hostNicknameNode.parentNodeKey; } if(hostNodeKey == null) { // If a host node key does not exist, // then current actor is not yet a host, // so add a new host node, // with actorNickname, // actorAge and // actorRating children, // to the “actors” root-child node // of bonForumXML nameAndAttributes = “host”;
  6. 8.1 The BonForumEngine Servlet 237 content = “”; forestHashtableName = “bonForumXML”; obj = bonForumStore.add( “bonAddElement”, “actors”, nameAndAttributes, ➥content, forestHashtableName, “nodeNameHashtable”, sessionId ); hostNodeKey = (NodeKey)obj; String creationTimeMillis = hostNodeKey.aKey; String hostNodeKeyKey = sessionId + “_” + creationTimeMillis + ➥“:host”; // Make nodeNameHashtable key // for the hostNodeKeyKey // available to session. // It gives quick access // to last host nodeKey for session session.setAttribute( “hostNodeKeyKey”, hostNodeKeyKey ); nameAndAttributes = “actorNickname”; content = actorNickname; forestHashtableName = “bonForumXML”; obj = bonForumStore.add( “bonAddElement”, hostNodeKeyKey, ➥nameAndAttributes, content, forestHashtableName, “nodeNameHashtable”, sessionId ➥); //NOTICE: the commented-out line below here // is more efficient than the above line. // It does not require the reconstructed // hostNodeKeyKey. However, we may want that // in a session attribute for later. // Also, if we use this next statement, then // we are using two ways to add data to the // XML, and it may be better to only use the // wrapper method. Still trying to decide. // There are other similar lines below! // They are in “host” handling, but not in // “message” or “guest” handling. // bonForumStore.getBonForumXML( // ).addChildNodeToNonRootNode( // “actorNickname”, “”, content, hostNodeKey, // “nodeNameHashtable”, sessionId); nameAndAttributes = “actorAge”; content = normalize((String)session.getAttribute( “actorAge” )); ➥forestHashtableName = “bonForumXML”; obj = bonForumStore.add( “bonAddElement”, hostNodeKeyKey, ➥nameAndAttributes, content, forestHashtableName, “nodeNameHashtable”, sessionId ➥); nameAndAttributes = “actorRating”;
  7. 238 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore content = normalize((String)session.getAttribute( “actorRating” )); ➥if(content.length() < 1) { content = “5”; } forestHashtableName = “bonForumXML”; obj = bonForumStore.add( “bonAddElement”, hostNodeKeyKey, ➥nameAndAttributes, content, forestHashtableName, “nodeNameHashtable”, sessionId ); } // Add a chat node to the “things” // root-child node of bonForumXML, // with a chatModerated attribute, // and no text content. chatModerated = normalize((String)session.getAttribute( “chatModerated” )); ➥if (chatModerated.equalsIgnoreCase(“yes”)) { nameAndAttributes = “chat moderated=\”yes\””; } else { nameAndAttributes = “chat moderated=\”no\””; } content = “”; forestHashtableName = “bonForumXML”; obj = bonForumStore.add( “bonAddElement”, “things”, nameAndAttributes, ➥content, forestHashtableName, “nodeNameHashtable”, sessionId ); NodeKey chatNodeKey = (NodeKey)obj; // Add a hostKey to the new chat node, // its text content is the key to the host node // example: 987195762454.987195735516.987195735486 String creationTimeMillis = chatNodeKey.aKey; chatNodeKeyKey = sessionId + “_” + creationTimeMillis + “:chat”; nameAndAttributes = “hostKey”; content = hostNodeKey.toString(); forestHashtableName = “bonForumXML”; obj = bonForumStore.add( “bonAddElement”, chatNodeKeyKey, nameAndAttributes, ➥content, forestHashtableName, “nodeNameHashtable”, sessionId ); // Make the hostKey available to this session. // It is later used for these things: // 1. finding out if an actor is a host in a chat // 2. branding messages with a host as sender session.setAttribute(“hostKey”, content); // Make nodeNameHashtable key // for the chat node key // available to session. // Example key: ofl37sijm1_987195762494:chat
  8. 8.1 The BonForumEngine Servlet 239 // It is useful later for these things: // 1. adding messages to chat // 2. finding the chat node // (to add nodes or attributes) // 3. determining if a chat is the current chat session.setAttribute( “chatNodeKeyKey”, chatNodeKeyKey ); // Add a “chatItem” child // to the selected chat subject element. // That selected element is // the chat subject category // in bonForumXML. // The name of the new child is “sessionID_” + // the sessionId of // the visitor starting the chat + // the time the chat node was created in millis. // The time suffix allows more than one chat // to exist per session. // Also add an attribute called chatTopic, // with the (escaped) chatTopic // input by the visitor. // The sessionId (recoverable from // the name of the new child) can // be used later to quickly find the chat nodeKey. // That is useful for example // when a visitor joins a chat // Note: when adding the sessionId // element, its parent is found // using the pathNameHashtable. // The parent nodeKey is there // with a key which is its pathName // (and equal to chatSubject) nameAndAttributes = “sessionID_”; nameAndAttributes += sessionId; nameAndAttributes += “_”; nameAndAttributes += creationTimeMillis; nameAndAttributes += “ chatTopic=\””; nameAndAttributes += chatTopic; nameAndAttributes += “\””; content = “”; forestHashtableName = “bonForumXML”; obj = bonForumStore.add( “bonAddElement”, chatSubject, nameAndAttributes, ➥content, forestHashtableName, “pathNameHashtable”, sessionId ); NodeKey itemNodeKey = (NodeKey)obj; // set itemKey to itemNodeKey as a string
  9. 240 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore String itemKey = itemNodeKey.toString(); // Add the key to the chatItem element (itemKey) // to the chat element as an attribute // The itemKey connects a chat // to its subject, topic and messages! String attributeName = “itemKey”; String attributeValue = itemKey; NodeKey nk = bonForumStore.addChatNodeAttribute( chatNodeKeyKey, ➥attributeName, attributeValue ); // Make the itemKey available to the session session.setAttribute(“itemKey”, itemKey); } if(!(haveSubject && haveTopic)) { // missing information, must return to get it // LATER: set attribute to trigger message to user bonForumCommand = “visitor_starts_chat”; } Adding a Host Actor Recall that in the bonForum XML data, a child of the root node is called actors. For a chat, two important children of actors are host and guest.When processRequest() handles the host_executes_chat bonForumCommand, which originates in the “visitor starts chat” state, it must decide whether to add the visitor to the XML data as a host element, a child of actors. It finds out by using the visitor’s nickname, actorNickname. Nicknames used by bonForum users (actors) must be unique.That is enforced by storing them as keys in a hashtable, the nicknameRegistry. Here, we make sure that each nickname has no more than one host element related to it. A user can host more than one chat, but all the chats share one host node. The code gets the nickname for the current request from a session attribute, where it was stored after input, by processRequest().To find out whether a host node exists for the current nickname, the code first invokes a method of BonForumStore: getActorNicknameNodeKey() with the nickname and host as arguments.The returned nickname nodeKey, if any, is used to get the nickname node itself, using the getBonNode() method of ForestHashtable.The parentNodeKey member of the nick- name node is the host nodeKey. Actually, if the getActorNicknameNodeKey() method fails to return a nodeKey, we know already that there is no host node for the nickname.Then why continue on to get the node itself and its parentNodeKey? Because we will need this host nodeKey as a string (hostKey) later, to add to the new chat and to a session attribute as well (see the section “Adding a Chat Element”).
  10. 8.1 The BonForumEngine Servlet 241 If no host nodeKey is found for the nickname, then a new host node is added to the actors child of the XML root node, using the add() method of BonForumStore, which wraps the ForestHashtable addChildNodeToNonRootNode() method.The add() method returns the nodeKey of the newly added host node, which is useful for the next three steps: adding the actorNickname, actorAge, and actorRating children of the host node.The values for these three are found in session attributes, where they were earlier set by the processRequest method (see Section 8.1.14, “The processRequest() Method: Overall View”). Note the following statements from the “add a host” part of the previous long source code listing: hostNodeKey = (NodeKey)obj; String creationTimeMillis = hostNodeKey.aKey; String hostNodeKeyKey = sessionId + “_” + creationTimeMillis + “:host”; The hostNodeKey is obtained by casting the returned object from the add() method of BonForumStore.The next two lines re-create the nodeNameHashtable key for the host nodeKey stored there.That is needed by the next add() method invocation to directly add child nodes to the host node, without searching for it in the XML data. The aKey is available from the return value after casting (as it is from any NodeKey). The aKey was given a value using the system clock time in milliseconds.That hap- pened when the NodeKey was used to store the host node.The NodeKey for the host node (as a string) was then stored in the nodeNameHashtable with a key that looked something like this: ofl37sijm1_987195762454:host The first part, before the underscore character, is the session ID for the thread that added the host.The part between the underscore and the colon character is the same system clock value that was used for the aKey in the host nodeKey when the host node was stored.That happened deep in the bowels of the ForestHashtable class, in a state- ment like this: nodeKeyKey = sessionId + “_” + nodeKey.aKey +”:” + nodeName; The nodeKey.aKey acts as a timestamp allowing multiple keys per session in nodeNameHashtable. It is used whenever we need to be able to find multiple nodes with the same name for one session. It allows bonForum to have multiple chats, hosts, and guests associated with each session object. Before this timestamp part of the nodeKeyKey was implemented (for this edition of the book), bonForum users could be a host or a guest in only one chat per browser instance. Now, a host and a guest can enter and leave chats at will. Before, if a user started two or more different chats in the same session, a visitor could join only the latest one, although all would appear to be available.This small change made a big difference in the usability of bonForum. The same session ID and timestamp mechanism just described applies also to chatNodeKeyKey and guestNodeKeyKey, as you will see in the following sections. Of course, these longer timestamped keys take up memory space and entries in the nodeNameHashtable, so we have included an option to suppress them when they are
  11. 242 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore not needed. Message nodes, for example, use a shorter nodeNameHashtable key such as ofl37sijm1:messageKey so that only the last message nodeKey (messageKey) for each session is kept in the nodeNameHashtable. There is one wrinkle here that is not obvious and that you will see in several other locations in the code. (There are some comments regarding this in the source code.) It involves the use of the BonForumStore.add() method to add children to the host node. In this case, we could have used the method that is wrapped by that add() method instead.We will show and discuss the difference now. Here is the way that the actorNickname is actually added (the first three variables are string objects): nameAndAttributes = “actorNickname”; content = actorNickname; forestHashtableName = “bonForumXML”; obj = bonForumStore.add( “bonAddElement”, hostNodeKeyKey, nameAndAttributes, ➥content, forestHashtableName, “nodeNameHashtable”, sessionId ); As you can see, that required us to reconstruct the hostNodeKeyKey. Here is the way that the actorNickname could be added more efficiently.That substitution can be made here and in several other locations in the code, where we already have the nodeKey of the future parent node handy (here, the hostNodeKey).Therefore, there is no real need for the nodeKey key and the lookup in the nodeNameHashtable. String name = “actorNickname”; String attributes = “”; content = actorNickname; forestHashtableName = “bonForumXML”; bonForumStore.getBonForumXML().addChildNodeToNonRootNode(name, attributes, ➥content, hostNodeKey, “nodeNameHashtable”, sessionId); The main reason that we use only the add() method is so that we will have just one method adding elements to the XML data in the host threads, the guest threads, and the chat message threads.When we later discuss “visitor joins chat” handling and chat message handling, you will see why we sometimes really do need the add() method, with its second argument (hostNodeKeyKey, chatNodeKeyKey, and so on). Adding a Chat Element In the next part of the long source code listing in the previous section “Starting a Chat,” a new chat element is added to the XML data.The element has an attribute to keep the user’s answer to the “Will you moderate this chat?” question on the browser page.That answer is retrieved from a session attribute, where it was earlier set from a request parameter in the visitor_joins_chat_frame handler (see Section 8.1.18, “The processRequest() Method: Handling Specific Chat JSPs,” and Section 8.1.19, “The processRequest() Method: Handling Chat Variables”). After nameAndAttributes has been prepared by concatenating the chat with the
  12. 8.1 The BonForumEngine Servlet 243 “moderated” attribute name and value, the new element is added with the BonForumStore add() method. As we just discussed, we can cast the return value from add() and use it to keep adding children to the new chat element. Here are the state- ments that re-create the very important chatNodeKeyKey: NodeKey chatNodeKey = (NodeKey)obj; String creationTimeMillis = chatNodeKey.aKey; chatNodeKeyKey = sessionId + “_” + creationTimeMillis + “:chat”; Here is an example of a chatNodeKeyKey: ofl37sijm1_987195762494:chat The chatNodeKeyKey is immediately useful for adding the hostKey to the chat ele- ment. It is added as a string value in the text node of the chat element.That string value is also set in a session attribute named hostKey. Of course, it looks something like this: 987195762454.987195735516.987195735486 We make the hostKey available to the session so that it can later be used for two things: n To find out if the session actor is the host in a chat n To brand messages with the host that sent them The first use was discussed previously in the section “Rejoining Existing Chats.”The second use of hostKey is discussed later, in Section 8.1.22, “The processRequest() Method: Handling Chat Messages.” The last step in adding a chat to bonForum is to make the newly created chatNodeKeyKey available to the session as its attribute.That will come in handy for the following uses: n To determine whether a chat is the current chat n To find the chat node to add nodes or attributes n To add messages to a chat An example of the first use is described in the source code excerpt under the heading “Rejoining Existing Chats.”We just saw the second use in action as we added the hostKey to the chat.The third use is discussed in Section 8.1.22, “The processRequest() Method: Handling Chat Messages.” Adding a Chat Item Marker As you should recall, bonForum loads an XML file called subjects.xml during it startup initialization.That file contains the hierarchical list of possible chat subjects. Its XML tree is added to the bonForum XML data as the subjects subtree of the things root-child node. Now, in the processRequest() method, it is time to add another
  13. 244 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore element in that subtree, one that connects a chat element to its chatSubject node and stores its chatTopic value as an attribute. This new node is called the chatItem. (Note that the term chatItem is also used for the concatenation of the chatSubject and chatTopic that a visitor selects to join a chat, and that is sent in the request as a request parameter.That can be confusing, but they are related: One refers to them in the tree, and the other refers to them in words.) The nodeKey of the new chatItem element, called the itemKey, is saved in the new chat node. A very important part of the new chatItem element is its name.The name given to the new child is always something like the following example: sessionID_ofl37sijm1_987195762494 After being concatenated with a (escaped) chatTopic in a nameAndAttributes string for the add() method, it comes out something like this: sessionID_ofl37sijm1_987195762494 chatTopic=”love” The name part should look familiar, from the very similar hostNodeKeyKey that we discussed previously. Actually, we added the sessionId prefix only to fix a bug—it happened whenever the beginning of the name (the sessionId) started with a digit rather than a letter. It was a cute bug, and it illustrates that Murphy never sleeps. Before we added the prefix, the chatItem “subject marker” elements were to be like these two, in the XML data: ➥ ➥ However, the first one worked and the second crashed the XML database because its name is not well-formed XML—it starts with a digit. After the fixes, the chat marker elements are like this example (they always start with a letter): You probably noticed something here:This bug, and these examples, happened before we added the creation time in milliseconds to the name of the nodeKeyKey values and the chatItem element name.This next example is more current:
  14. 8.1 The BonForumEngine Servlet 245 Again, adding the timestamp to the name of the chatItem element allowed us to pro- vide direct access to multiple chats per session by creating unlimited unique key values for the nodeNameHashtable. You will see in the next section why we have these strange names for the chatItem elements.The chatNodeKeyKey can be reverse-engineered from the element name to find a chat from a different session, as must be done when a visitor later joins a chat. Meanwhile, let’s continue with the discussion of “host executes chat” handling. After the nameAndAttributes string and other arguments are prepared, the add() method is called, as follows: obj = bonForumStore.add( “bonAddElement”, chatSubject, nameAndAttributes, content, ➥forestHashtableName, “pathNameHashtable”, sessionId ); NodeKey itemNodeKey = (NodeKey)obj; What is interesting here is that we are not using the nodeNameHashtable to add the chatItem marker element. As you see from the next-to-last argument, we use pathNameHashtable.The second argument, in these cases, is not the key to the parent node, but a path in the XML data (the chatSubject, in this case), which could be this: Animals.Fish.FlyingFish This brings our discussion to the last act involved in adding a chat. Adding an itemKey to a Chat The return value from the add() method is again cast to a NodeKey, as itemNodeKey. As a string, it becomes itemKey.This time we want to add it to the chat node, not as a child element, but instead as an attribute of the chat element.That is done using the addChatNodeAttribute() method of BonForumStore. It requires the very handy chatNodeKeyKey. No need, this time, to get it from a session attribute because we still have it from adding the chat node. We are going to need the itemKey value elsewhere in this session. It connects a chat to its subject and topic. It also connects a chat to its messages and connects a message to its subject. Here then, we are finally arriving at the end of the code that handles the host_executes_chat bonForumCommand. Before leaving the “host executes chat” handler, each thread checks to see if either haveSubject or haveTopic has been set to false; in this case, bonForumCommand will be set to visitor_starts_chat.That value will take the user back to where the missing information can be supplied. At this point, we have also completed the two steps that need synchronization, so the next statement closes the thread-safe block of code: } // end of synchronized block! After this, each thread will soon be returning its bonForumCommand value and serviceStatus value back to the service() method, to be forwarded—hopefully not to forum_error.jsp!
  15. 246 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore 8.1.21 The processRequest( ) Method: “Handling Guest Executes Chat” In this section, we continue with our discussion of the processRequest() method in BonForumEngine, now with the code that handles the guest_executes_chat bonForumCommand for requests that originate in the “visitor joins chat” bonForum state. We begin here with the bonForumCommand processor in the processRequest() method, beginning with the “else if ” clause that begins as follows: else if (bonForumCommand.equals(“guest_executes_chat”)) { We can call this the ”guest executes chat” handler. Its job is to process a visiting actor’s request to join a chat, transforming the actor from a visitor to a guest. First, let’s set the scene by recapitulating events up to this point. (If you think that you already know what these events must have been, you can safely skip ahead to the section “Getting the chatItem.”) When the thread reaches the “guest executes chat” handler, the visitor has already chosen a chatItem, which describes one available chat. As you can see in Table 8.5, “bonForum Variables: Priority, Name, Origin,Type,” that chatItem variable value orig- inated in an HTML select element displayed (using the XSLT processor) by the JSP visitor_joins_chat_frame.jsp. That chatItem value included in itself both the subject and topic of the chosen chat, and arrived at the BonForumEngine servlet as a request parameter looking some- thing like this example: Animals_Bird_Hawk_[prehistoric falconry] Furthermore, the processRequest() method has already processed the chatItem in the “visitor joins chat” frame handler within its bonForumCommand processor.You can look that up in Table 8.6, “bonForum Variables: Priority, Name, Destination.” Notice that, in this case, the origin JSP and the destination JSP are the same! The request comes to the BonForumEngine servlet from the HTML produced by the _frame JSP, and the servlet forwards it back to the same _frame JSP after processing the chatItem request parameter (putting its value in a chatItem session attribute).The user can sit there all day long selecting one chat after another, without going anywhere. Then the user clicked a button labeled Join, in the controls frame on the browser, which submitted the HTML form produced by the JSP visitor_joins_chat_controls.jsp. That brought a new (and different) request to the BonForumEngine and to its processRequest() method. Its boncommand—and, thus its bonForumCommand—value was visitor_joins_chat_ready, which does not yet have (or need) a handler in the bonForumCommand processor.The service method then forwarded the request to the JSP visitor_joins_chat_ready.jsp. That JSP set up some applet parameter values in its request parameters, including one for a target of _top, and another for a document with the full URI for the JSP guest_executes_chat.jsp.
  16. 8.1 The BonForumEngine Servlet 247 Next, the _ready JSP executed this action, obviously as its last act: That request was servlet-mapped, so it arrived at the BonForumEngine, where it was handled by the servlet-mapped request processor block discussed earlier.With the highest priority, and without being serviced by processRequest(), the request was for- warded to the JSP actor_leaves_frameset_robot.jsp. The BonForumRobot applet in the Java plug-in on that JSP got its applet parameters from the request parameters. It dressed up the document name before asking its applet context object to have it shown in the _top frame. Now the request URI looked something like the following: http://chatterbox:8080/bonForum/jsp/forum/guest_executes_chat.jsp987195879833.tfe That was no static HTML document name but was yet another .tfe URI, again servlet-mapped to the BonForumEngine. Arriving there, its requestURI now looked like this: /bonForum/jsp/forum/guest_executes_chat.jsp987195879833.tfe The servlet-mapped request processor in the service() method matched the guest_executes_chat substring in the requestURI and said, “Aha! This request needs a serviceStatus of ProcessRequest and a bonForumCommand of guest_executes_chat. And that, patient reader, is how the request we are interested in arrived at the subject of this section, its bonForumCommand handler in the processRequest() method. Getting the chatItem For those of you who skipped the last section, welcome back! The first thing done in the “guest executes chat” handler is to assume that every- thing is okay and that the guest will get to join a chat. A flag is set as follows: boolean haveChatItem = true; Next the chatItem is retrieved from the session attribute, where it was safely held through a chain of events involving several different request objects. Here is the statement: chatItem = normalize((String)session.getAttribute(“chatItem”)).trim(); If the chatItem is empty or is set to the special value NONE, then the haveChatItem flag is set to false, which causes the request to be forwarded back to the “visitor joins chat” state for new input. Synchronizing the XML Database for the Chat Just as in the “host executes chat” handler discussed previously, we need for synchro- nization to enable thread-safe execution of code that shares a common resource: the XML data. For general information about synchronization, refer to the previous
  17. 248 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore section, “The Need for Thread Synchronization“. Here, we will get specific about the current need. When a visitor joins a chat, we need to synchronize these two steps: 1. Checking to see whether a chat for chatItem (subject+topic) exists 2. The visitor joining that chat At first glance, it seems that this synchronization is not necessary. Can’t we just assume that the chat for chatItem exists because it was picked from a list generated from existing chats? The problem is that we will soon implement processes that delete exist- ing chats, and they will do so from a different thread than any “visitor joins chat”–generated thread.The chat that is found to be okay in step 1 might have been deleted by the time that thread is ready to do step 2.Worse yet, because step 2 involves several additions to the XML data, the chat could be deleted after some of these, but not all, are completed. If synchronization turns out to affect performance too much, we might have other possible solutions here, such as using a method that checks the integrity of a chat after it is added, together with a way to clean up “bad chat” debris caused by partially com- pleted chat additions.The addition of a chat could loop until it is successful or exceeded a number of retries. For now, synchronization looks pretty good! As an aside, we thought that we had a second reason to synchronize these two steps. Until we implement a user manager and data persistence, some pieces of the bonForum puzzle do not survive a session timeout.While we were adding the syn- chronization, we mistakenly thought that chats expire along with the session that starts them and that it could happen between the two steps listed. In fact, when the session that starts a chat times out, the chat does stay in the XML data, and it remains func- tional. However, it loses its host because the host’s session has expired. Furthermore, a new visitor cannot get the host nickname back again, nor can any actor “reown” the host actor element, although it is still “in” the chat.Visitors can still join as guests and send messages back and forth. (Guest ratings do not survive a guest session timeout, but that is a different problem.) In any case, these problems will all go away when users can reclaim their data from session to session; that is what a user manager and data persistence will do for bonForum in a later release. So, how do we synchronize the two steps that we listed? We would like to use the following way: synchronized(bonForumStore.bonForumXML) { // thread-safe critical section // 1. step one // 2. step two } That way, a thread arriving at the synchronized block would get a lock on the static ForestHashtable member of the static BonForumStore member of the BonForumEngine instance.That would still allow multiple threads to access other nonsynchronized
  18. 8.1 The BonForumEngine Servlet 249 methods of bonForumStore. However, this preferred synchronization needs more test- ing and might require adding other synchronized methods or blocks.We are instead using the following, more severe, lock: synchronized(bonForumStore) { It does takes a while to rule out problems related to undersynchronization.Thread clashes are a bit like motor vehicle accidents:They’re not easy to stage. Finding a Chat and the Actor Status in It The thread is now ready to fulfill the “visitor joins chat” action leading to the “guest executes chat” state. First, it searched for the chat with the chatItem that was requested by the visitor. (Does this sound familiar? This is similar to what was done in the “host executes chat” handler discussed previously, with chatSubject and chatTopic instead of chatItem.) There are parallels involved in joining and starting chats.We will try not to be too repetitive with things covered already. For convenience, though, we will repeat a previous table in Table 8.8 and show the possible outcomes of a chat search. This time, the last row is relevant. Table 8.8 JSP Transitions Versus Actor-Chat Relationships Chat Exists Already Chat Does Not Exist Actor Is Actor Is Actor Is Not in Chat Host Guest visitor guest executes host executes guest executes host executes starts chat chat(1) chat(1) chat(1)(2) chat visitor guest executes host executes guest executes forum joins chat chat chat(3) chat error Again, are the notes for this table: n Numbered table items are optional, to be set by user preferences in a command, with alternatives as follows: 1. Visitor starts chat 2. Host executes chat, if multihosted chat allowed 3. Guest executes chat n If the actor is a host or a guest, the actor is rejoining a chat. Rejoining a Chat In the case of “visitor joins chat,” if a chat with the requested subject and topic combi- nation does not exist, the visitor will certainly not be able to join it! If the requested chat does exist, then what happens will depend upon an option setting. One option is
  19. 250 Chapter 8 Java Servlet and Java Bean: BonForumEngine and BonForumStore that a visitor will join the chat as a guest, unless the visitor is already a host in the chat; in this case, that host role will resume.The other option (not yet implemented) is that the visitor will always enter the chat as a guest.This last option will allow a host in a chat to re-enter the chat as a guest. A user preference setting can later offer a choice of options for the behavior of “visitor joins chat” when the visitor is found already in the chat as a host or guest: 1. Always join it with the previous status. 2. Always join as a guest. 3. Always offer a choice if the visitor already is a host. At this time, the transitions given in Table 8.8 itself are implemented, and the options given in the notes are not yet implemented.We decided to tackle the harder ones first. In the previous section “Finding a Chat and the Actor Status in It,” where this table first appeared, we showed a simple way to implement the transitions for the “visitor starts chat” row. Here, we do the same for the “visitor joins chat” row: if chat exists if actor is not host in chat set bonForumCommand to guest_executes_chat else set bonForumCommand to host_executes_chat endif else set haveChatItem false //error endif Setting the haveChatItem flag to false causes the request to be forwarded back to the “visitor joins chat” state—that is, back where it came from. Eventually, when we are finished with the prototyping, we might decide that the visitor should always have access to both the “visitor joins chat” and “visitor starts chat” functionality on the same page. It will then be quite easy to implement all the logic in the table, as in this pseudocode: if visitor starts chat or visitor joins chat if chat exists if actor is not host in chat set bonForumCommand to guest_executes_chat else set bonForumCommand to host_executes_chat endif else if visitor starts chat set bonForumCommand to host_executes_chat else if visitor joins chat set haveChatItem false //error endif endif
  20. 8.1 The BonForumEngine Servlet 251 These pseudocode listings make it all look so easy. However, as is often the case, the devil is in the details. Let’s start examining the actual code. Passing Information Between Sessions In the previous section “Adding a Host Actor,” we discussed the structure of keys in the nodeNameHashtable and showed how they could be reverse-engineered in the fol- lowing general manner: nodeKeyKey = sessionId + “_” + nodeKey.aKey +”:” + nodeName; We also showed that we did not really have to do that while adding elements to a new host element because whenever we have a nodeKey to get the aKey from, we already have the key to find the node.We wanted to use only the add() method of BonForumStore, however, which requires a nodeKeyKey argument, and we also wanted the nodeKeyKey anyway, to put in a session attribute for other purposes. However, getting back to the code for joining a chat (we can call it the guest thread), we really could use a nodeKeyKey for the chat, for two reasons: to see if the chat exists and to add elements to it.This time, we do not have the chatNodeKey, and having a chatNodeKeyKey would allow us to look up the chatNodeKey in the nodeNameHashtable.Yet, in this present situation, we cannot reconstruct the chatNodeKeyKey. One problem is this:The session ID that the chatNodeKeyKey includes as its prefix is for the session of the chat host, the actor that started the chat.That host session ID is not available to this guest thread. A second problem is this:The guest thread cannot get the aKey from the chatnodeKey to use in the reconstruction of the chatNodeKeyKey. If it had that nodeKey, it could simply use that to find the chat. This situation is very different than the one in the section “Adding a Host Actor.” There, the host node had just been added and its nodeKey had been returned by the add() method.That returned object could be cast to a NodeKey and used along with the available host session ID to reconstruct the hostNodeKeyKey. As you no doubt know by now, this problem of passing information between sessions is the reason that we gave the chat subject marker elements in the XML (chatItem nodes) a name that is made from the chatNodeKeyKey.When a visitor chooses a chat to join (a chatItem variable), we can get from that choice the node that marks the subject and the topic (the chatItem node), that node’s name, and thus the chatNodeKeyKey.Then, instead of searching through all the XML for the chat node, we can find it using its nodeKey from the nodeKeyHashtable, which we get with the chatNodeKeyKey. Some of you who are waiting patiently for more standard ways to use XML in a Web application (a book in itself!) are perhaps saying, “Wait! Isn’t that cheating?”Well, indeed it is, and for a good reason. Ensuring direct access to an object that you are going to require again in a different context looks good compared to some of the alternatives.


Đồng bộ tài khoản