blob: 7bec393cbc303d3aec08d1dbe8c5f6b7c379ebb6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package chat;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.comet.CometEvent;
import org.apache.catalina.comet.CometProcessor;
/**
* Helper class to implement Comet functionality.
*/
public class ChatServlet
extends HttpServlet implements CometProcessor {
private static final long serialVersionUID = 1L;
private static final String CHARSET = "UTF-8";
protected final ArrayList<HttpServletResponse> connections =
new ArrayList<>();
protected transient MessageSender messageSender = null;
@Override
public void init() throws ServletException {
messageSender = new MessageSender();
Thread messageSenderThread =
new Thread(messageSender, "MessageSender[" + getServletContext().getContextPath() + "]");
messageSenderThread.setDaemon(true);
messageSenderThread.start();
}
@Override
public void destroy() {
connections.clear();
messageSender.stop();
messageSender = null;
}
/**
* Process the given Comet event.
*
* @param event The Comet event that will be processed
* @throws IOException
* @throws ServletException
*/
@Override
public void event(CometEvent event)
throws IOException, ServletException {
// Note: There should really be two servlets in this example, to avoid
// mixing Comet stuff with regular connection processing
HttpServletRequest request = event.getHttpServletRequest();
HttpServletResponse response = event.getHttpServletResponse();
if (event.getEventType() == CometEvent.EventType.BEGIN) {
String action = request.getParameter("action");
if (action != null) {
if ("login".equals(action)) {
String nickname = request.getParameter("nickname");
request.getSession(true).setAttribute("nickname", nickname);
response.sendRedirect("index.jsp");
event.close();
return;
}
String nickname = (String) request.getSession(true).getAttribute("nickname");
String message = request.getParameter("message");
messageSender.send(nickname, message);
response.sendRedirect("post.jsp");
event.close();
return;
}
if (request.getSession(true).getAttribute("nickname") == null) {
// Redirect to "login"
log("Redirect to login for session: " + request.getSession(true).getId());
response.sendRedirect("login.jsp");
event.close();
return;
}
begin(event, request, response);
} else if (event.getEventType() == CometEvent.EventType.ERROR) {
error(event, request, response);
} else if (event.getEventType() == CometEvent.EventType.END) {
end(event, request, response);
} else if (event.getEventType() == CometEvent.EventType.READ) {
read(event, request, response);
}
}
protected void begin(@SuppressWarnings("unused") CometEvent event,
HttpServletRequest request, HttpServletResponse response)
throws IOException {
log("Begin for session: " + request.getSession(true).getId());
response.setContentType("text/html; charset=" + CHARSET);
PrintWriter writer = response.getWriter();
writer.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
writer.println("<html><head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">");
writer.println("<div>Welcome to the chat. <a href='chat'>Click here to reload this window</a></div>");
writer.flush();
synchronized(connections) {
connections.add(response);
}
messageSender.send("Tomcat", request.getSession(true).getAttribute("nickname") + " joined the chat.");
}
protected void end(CometEvent event, HttpServletRequest request, HttpServletResponse response)
throws IOException {
log("End for session: " + request.getSession(true).getId());
synchronized(connections) {
connections.remove(response);
}
PrintWriter writer = response.getWriter();
writer.println("</body></html>");
event.close();
}
protected void error(CometEvent event, HttpServletRequest request, HttpServletResponse response)
throws IOException {
log("Error for session: " + request.getSession(true).getId());
synchronized(connections) {
connections.remove(response);
}
event.close();
}
protected void read(CometEvent event, HttpServletRequest request, HttpServletResponse response)
throws IOException {
InputStream is = request.getInputStream();
byte[] buf = new byte[512];
while (is.available() > 0) {
log("Available: " + is.available());
int n = is.read(buf);
if (n > 0) {
log("Read " + n + " bytes: " + new String(buf, 0, n)
+ " for session: " + request.getSession(true).getId());
} else if (n < 0) {
log("End of file: " + n);
end(event, request, response);
return;
}
}
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// Compatibility method: equivalent method using the regular connection model
response.setContentType("text/html; charset=" + CHARSET);
PrintWriter writer = response.getWriter();
writer.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
writer.println("<html><head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">");
writer.println("Chat example only supports Comet processing. ");
writer.println("Configure a connector that supports Comet and try again.");
writer.println("</body></html>");
}
/**
* Poller class.
*/
public class MessageSender implements Runnable {
protected boolean running = true;
protected final ArrayList<String> messages = new ArrayList<>();
public MessageSender() {
// Default contructor
}
public void stop() {
running = false;
synchronized (messages) {
messages.notify();
}
}
public void send(String user, String message) {
synchronized (messages) {
messages.add("[" + user + "]: " + message);
messages.notify();
}
}
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
@Override
public void run() {
// Loop until we receive a shutdown command
while (running) {
String[] pendingMessages;
synchronized (messages) {
try {
if (messages.size() == 0) {
messages.wait();
}
} catch (InterruptedException e) {
// Ignore
}
pendingMessages = messages.toArray(new String[0]);
messages.clear();
}
synchronized (connections) {
for (int i = 0; i < connections.size(); i++) {
try {
PrintWriter writer = connections.get(i).getWriter();
for (int j = 0; j < pendingMessages.length; j++) {
writer.println("<div>"+filter(pendingMessages[j]) + "</div>");
}
writer.flush();
} catch (IOException e) {
log("IOException sending message", e);
}
}
}
}
}
}
/**
* Filter the specified message string for characters that are sensitive
* in HTML.
*
* @param message The message string to be filtered
* @author Copied from org.apache.catalina.util.RequestUtil#filter(String)
*/
protected static String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("&lt;");
break;
case '>':
result.append("&gt;");
break;
case '&':
result.append("&amp;");
break;
case '"':
result.append("&quot;");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
}