001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package echo2example.chatclient;
031:
032: import java.io.IOException;
033: import java.util.ArrayList;
034: import java.util.Date;
035: import java.util.List;
036:
037: import javax.xml.parsers.DocumentBuilder;
038: import javax.xml.parsers.DocumentBuilderFactory;
039: import javax.xml.parsers.ParserConfigurationException;
040:
041: import nextapp.echo2.webcontainer.ContainerContext;
042: import nextapp.echo2.webrender.ClientProperties;
043: import nextapp.echo2.webrender.Connection;
044: import nextapp.echo2.webrender.WebRenderServlet;
045:
046: import org.w3c.dom.Document;
047: import org.w3c.dom.Element;
048: import org.w3c.dom.NodeList;
049: import org.w3c.dom.Text;
050:
051: /**
052: * Representation of a chat-room session for a single user.
053: * This object handles life-cycle of a user in the chat room and provides
054: * capability to post messages and retrieve messages from the chat room.
055: */
056: public class ChatSession {
057:
058: /**
059: * A representation of a single message posted in the chat session.
060: */
061: public static class Message {
062:
063: private String content;
064: private Date date;
065: private String userName;
066:
067: /**
068: * Creates a new <code>Message</code>.
069: *
070: * @param userName the name of the user posting the message
071: * (null for system announcements)
072: * @param date the time the message was posted
073: * @param content the content of the message
074: */
075: private Message(String userName, Date date, String content) {
076: super ();
077: this .userName = userName;
078: this .date = date;
079: this .content = content;
080: }
081:
082: /**
083: * Returns the content of the message.
084: *
085: * @return the content
086: */
087: public String getContent() {
088: return content;
089: }
090:
091: /**
092: * Returns the time the message was posted
093: *
094: * @return the post time
095: */
096: public Date getDate() {
097: return date;
098: }
099:
100: /**
101: * Returns the name of the user who posted the message
102: *
103: * @return the name of the user, or null in the case of a system
104: * announcement
105: */
106: public String getUserName() {
107: return userName;
108: }
109: }
110:
111: /**
112: * Factory method to create a new <code>ChatSession</code> for a user.
113: *
114: * @param userName the desired user name
115: * @return the <code>ChatSession</code> if one could be created or null
116: * otherwise (e.g., in the case the user name was taken)
117: */
118: public static ChatSession forUserName(String userName)
119: throws IOException {
120: ChatSession chatSession = new ChatSession(userName);
121: return chatSession.authToken == null ? null : chatSession;
122: }
123:
124: /**
125: * The id of the last retrieved chat message.
126: */
127: private String lastRetrievedId;
128:
129: /**
130: * The authentication token associated with the user name. This token is
131: * used to authenticate in order to post messages with the user name and
132: * release the user name.
133: */
134: private String authToken;
135:
136: /**
137: * The name of the user.
138: */
139: private String userName;
140:
141: /**
142: * List of new messages recently retrieved from the chat server which have
143: * not yet been retrieved by the application.
144: */
145: private List newMessages = new ArrayList();
146:
147: /**
148: * The URI of the chat server's web service.
149: */
150: private String chatServerUri;
151:
152: /**
153: * Creates a new <code>ChatSession</code>.
154: * Attempts to connect to chat server and obtain lock / authentication token
155: * for user name.
156: *
157: * @param userName the desired user name for the session
158: */
159: private ChatSession(String userName) throws IOException {
160: super ();
161: this .userName = userName;
162: loadServerUri();
163:
164: Document requestDocument = createRequestDocument();
165:
166: Element userAddElement = requestDocument
167: .createElement("user-add");
168: userAddElement.setAttribute("name", userName);
169: requestDocument.getDocumentElement()
170: .appendChild(userAddElement);
171:
172: Document responseDocument = XmlHttpConnection.send(
173: this .chatServerUri, requestDocument);
174: NodeList userAuthNodes = responseDocument
175: .getElementsByTagName("user-auth");
176: if (userAuthNodes.getLength() != 1) {
177: throw new IOException("Unexpected response.");
178: }
179: Element userAuthElement = (Element) userAuthNodes.item(0);
180: if ("true".equals(userAuthElement.getAttribute("failed"))) {
181: } else {
182: authToken = userAuthElement.getAttribute("auth-token");
183: }
184: }
185:
186: /**
187: * Determines the URI of the chat server based on either
188: */
189: private void loadServerUri() {
190: Connection conn = WebRenderServlet.getActiveConnection();
191: String chatServerUri = conn.getServlet().getInitParameter(
192: "ChatServerURI");
193: if (chatServerUri != null && chatServerUri.trim().length() > 0) {
194: this .chatServerUri = chatServerUri;
195: } else {
196: this .chatServerUri = (conn.getRequest().isSecure() ? "https"
197: : "http")
198: + "://"
199: + conn.getRequest().getServerName()
200: + ":"
201: + conn.getRequest().getServerPort()
202: + "/ChatServer/app";
203: }
204: }
205:
206: /**
207: * Creates an XML DOM for a request to the Chat Server's Web Service.
208: * The returned DOM will contain a 'chat-server-request' document element.
209: *
210: * @return the created DOM
211: */
212: private Document createRequestDocument() throws IOException {
213: try {
214: DocumentBuilderFactory factory = DocumentBuilderFactory
215: .newInstance();
216: factory.setNamespaceAware(true);
217: DocumentBuilder builder = factory.newDocumentBuilder();
218: Document document = builder.newDocument();
219: Element rootElement = document
220: .createElement("chat-server-request");
221:
222: ChatApp chatApp = ChatApp.getApp();
223: if (chatApp != null) {
224: ContainerContext containerContext = (ContainerContext) chatApp
225: .getContextProperty(ContainerContext.CONTEXT_PROPERTY_NAME);
226: String remoteHost = containerContext
227: .getClientProperties().getString(
228: ClientProperties.REMOTE_HOST);
229: rootElement.setAttribute("remote-host", remoteHost);
230: }
231:
232: if (lastRetrievedId != null) {
233: rootElement.setAttribute("last-retrieved-id",
234: lastRetrievedId);
235: }
236: document.appendChild(rootElement);
237: return document;
238: } catch (ParserConfigurationException ex) {
239: throw new IOException("Cannot create document: " + ex);
240: }
241: }
242:
243: /**
244: * Disposes of the <code>ChatSession</code>.
245: * This operation will make a request to the chat server to release the
246: * user name being used by the session.
247: */
248: public void dispose() throws IOException {
249: Document requestDocument = createRequestDocument();
250: Element userRemoveElement = requestDocument
251: .createElement("user-remove");
252: userRemoveElement.setAttribute("name", userName);
253: userRemoveElement.setAttribute("auth-token", authToken);
254: requestDocument.getDocumentElement().appendChild(
255: userRemoveElement);
256: XmlHttpConnection.send(chatServerUri, requestDocument);
257: }
258:
259: /**
260: * Retrieves new messages that have been posted to the server but which
261: * were not previously retrieved.
262: *
263: * @return the new <code>Message</code>s
264: */
265: public Message[] getNewMessages() {
266: Message[] messages = (Message[]) newMessages
267: .toArray(new Message[newMessages.size()]);
268: newMessages.clear();
269: return messages;
270: }
271:
272: /**
273: * Determines if any new messages have been posted to the chat server.
274: *
275: * @return true if any messages have been posted.
276: */
277: public boolean hasNewMessages() {
278: return newMessages.size() != 0;
279: }
280:
281: /**
282: * Returns the name of the user.
283: *
284: * @return the name of the user
285: */
286: public String getUserName() {
287: return userName;
288: }
289:
290: /**
291: * Contacts the chat server's web service and loads new messages.
292: *
293: * @throws IOException
294: */
295: public void pollServer() throws IOException {
296: Document requestDocument = createRequestDocument();
297: Document responseDocument = XmlHttpConnection.send(
298: chatServerUri, requestDocument);
299: updateLocalMessages(responseDocument);
300: }
301:
302: /**
303: * Posts a message to the chat server.
304: * Local messages will also be updated.
305: *
306: * @param content the content of the message
307: */
308: public void postMessage(String content) throws IOException {
309: Document requestDocument = createRequestDocument();
310:
311: Element postMessageElement = requestDocument
312: .createElement("post-message");
313: postMessageElement.setAttribute("user-name", userName);
314: postMessageElement.setAttribute("auth-token", authToken);
315: postMessageElement.appendChild(requestDocument
316: .createTextNode(content));
317: requestDocument.getDocumentElement().appendChild(
318: postMessageElement);
319:
320: Document responseDocument = XmlHttpConnection.send(
321: chatServerUri, requestDocument);
322: updateLocalMessages(responseDocument);
323: }
324:
325: /**
326: * Retrieves messages from the chat's server's web service's response
327: * message and stores them in the chat session.
328: *
329: * @param responseDocument the response DOM from the web service
330: */
331: private void updateLocalMessages(Document responseDocument) {
332: NodeList newMessageElements = responseDocument
333: .getDocumentElement().getElementsByTagName("message");
334: int length = newMessageElements.getLength();
335: for (int i = 0; i < length; ++i) {
336: Element messageElement = (Element) newMessageElements
337: .item(i);
338: lastRetrievedId = messageElement.getAttribute("id");
339: String userName = messageElement.hasAttribute("user-name") ? messageElement
340: .getAttribute("user-name")
341: : null;
342: NodeList childNodes = messageElement.getChildNodes();
343: String content = null;
344: for (int j = 0; j < childNodes.getLength(); ++j) {
345: if (childNodes.item(j) instanceof Text) {
346: content = childNodes.item(j).getNodeValue();
347: break;
348: }
349: }
350: long timeMs = Long.parseLong(messageElement
351: .getAttribute("time"));
352: Message message = new Message(userName, new Date(timeMs),
353: content);
354: newMessages.add(message);
355: }
356: }
357: }
|