Added: tomcat/trunk/java/org/apache/tomcat/bayeux/RequestFactory.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/bayeux/RequestFactory.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/bayeux/RequestFactory.java (added) +++ tomcat/trunk/java/org/apache/tomcat/bayeux/RequestFactory.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,48 @@ +/* + * 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 org.apache.tomcat.bayeux; + +import org.json.JSONObject; +import org.apache.tomcat.bayeux.request.MetaHandshakeRequest; +import org.apache.catalina.CometEvent; +import org.json.JSONException; +import org.apache.tomcat.bayeux.request.MetaConnectRequest; +import org.apache.tomcat.bayeux.request.MetaDisconnectRequest; +import org.apache.tomcat.bayeux.request.MetaSubscribeRequest; +import org.apache.tomcat.bayeux.request.MetaUnsubscribeRequest; +import org.apache.tomcat.bayeux.request.PublishRequest; +import org.apache.cometd.bayeux.Bayeux; + +public class RequestFactory { + + public static BayeuxRequest getRequest(TomcatBayeux tomcatBayeux, CometEvent event, JSONObject msg) throws JSONException { + String channel = msg.optString(Bayeux.CHANNEL_FIELD); + if (Bayeux.META_HANDSHAKE.equals(channel)) { + return new MetaHandshakeRequest(tomcatBayeux,event,msg); + }else if (Bayeux.META_CONNECT.equals(channel)) { + return new MetaConnectRequest(tomcatBayeux,event,msg); + }else if (Bayeux.META_DISCONNECT.equals(channel)) { + return new MetaDisconnectRequest(tomcatBayeux,event,msg); + }else if (Bayeux.META_SUBSCRIBE.equals(channel)) { + return new MetaSubscribeRequest(tomcatBayeux,event,msg); + }else if (Bayeux.META_UNSUBSCRIBE.equals(channel)) { + return new MetaUnsubscribeRequest(tomcatBayeux,event,msg); + } else { + return new PublishRequest(tomcatBayeux,event,msg); + } + } +} \ No newline at end of file
Added: tomcat/trunk/java/org/apache/tomcat/bayeux/TomcatBayeux.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/bayeux/TomcatBayeux.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/bayeux/TomcatBayeux.java (added) +++ tomcat/trunk/java/org/apache/tomcat/bayeux/TomcatBayeux.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,176 @@ +/* + * 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 org.apache.tomcat.bayeux; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; + +import org.apache.catalina.CometEvent; +import org.apache.catalina.tribes.util.Arrays; +import org.apache.catalina.tribes.util.UUIDGenerator; +import org.apache.cometd.bayeux.Bayeux; +import org.apache.cometd.bayeux.Channel; +import org.apache.cometd.bayeux.Client; +import org.apache.cometd.bayeux.Listener; +import org.apache.cometd.bayeux.Message; +import org.apache.cometd.bayeux.SecurityPolicy; +/** + * + * @author Filip Hanik + * @version 1.0 + */ +public class TomcatBayeux implements Bayeux { + + + protected int reconnectInterval = 5000; + /** + * a list of all active clients + */ + protected HashMap<String,Client> clients = new HashMap<String,Client>(); + + /** + * a list of all active channels + */ + protected LinkedHashMap<String, Channel> channels = new LinkedHashMap<String,Channel>(); + + /** + * security policy to be used. + */ + protected SecurityPolicy securityPolicy = null; + /** + * default client to use when we need to send an error message but don't have a client valid reference + */ + protected static ClientImpl errorClient = new ClientImpl("error-no-client",false); + + /** + * returns the default error client + * @return ClientImpl + */ + public static ClientImpl getErrorClient() { + return errorClient; + } + + protected TomcatBayeux() { + } + + /** + * should be invoked when the servlet is destroyed or when the context shuts down + */ + public void destroy() { + throw new UnsupportedOperationException("TomcatBayeux.destroy() not yet implemented"); + } + + public Channel getChannel(String channelId, boolean create) { + Channel result = channels.get(channelId); + if (result==null && create) { + result = new ChannelImpl(channelId); + channels.put(channelId,result); + } + return result; + } + + public Channel remove(Channel channel) { + return channels.remove(channel.getId()); + } + + public Client remove(Client client) { + if (client==null) return null; + for (Channel ch : getChannels()) { + ch.unsubscribe(client); + } + return clients.remove(client.getId()); + } + + public Client getClient(String clientId) { + return clients.get(clientId); + } + + public boolean hasClient(String clientId) { + return clients.containsKey(clientId); + } + + public List<Client> getClients() { + return java.util.Arrays.asList(clients.entrySet().toArray(new Client[0])); + } + + public SecurityPolicy getSecurityPolicy() { + return securityPolicy; + } + + public int getReconnectInterval() { + return reconnectInterval; + } + + public boolean hasChannel(String channel) { + return channels.containsKey(channel); + } + + public Client newClient(String idprefix, Listener listener, boolean local, CometEvent event) { + String id = createUUID(idprefix); + ClientImpl client = new ClientImpl(id, local); + client.setListener(listener); + clients.put(id, client); + return client; + } + + public Client newClient(String idprefix, Listener listener) { + assert listener!=null; + //if this method gets called, someone is using the API inside + //the JVM, this is a local client + return newClient(idprefix,listener,true, null); + } + + protected ClientImpl getClientImpl(CometEvent event) { + return (ClientImpl)event.getHttpServletRequest().getAttribute(ClientImpl.COMET_EVENT_ATTR); + } + + protected void remove(CometEvent event) { + ClientImpl client = getClientImpl(event); + if (client!=null) { + client.removeCometEvent(event); + } + } + + public String createUUID(String idprefix) { + if (idprefix==null) idprefix=""; + return idprefix + Arrays.toString(UUIDGenerator.randomUUID(false)); + } + + public List<Channel> getChannels() { + return java.util.Arrays.asList(channels.entrySet().toArray(new Channel[0])); + } + + protected Message newMessage() { + String id = createUUID("msg-"); + return new MessageImpl(id); + } + + public Message newMessage(Client from) { + MessageImpl msg = (MessageImpl)newMessage(); + msg.setClient(from); + return msg; + } + public void setSecurityPolicy(SecurityPolicy securityPolicy) { + this.securityPolicy = securityPolicy; + } + + public void setReconnectInterval(int reconnectTimeout) { + this.reconnectInterval = reconnectTimeout; + } + +} Added: tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaConnectRequest.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaConnectRequest.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaConnectRequest.java (added) +++ tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaConnectRequest.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,125 @@ +/* + * 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 org.apache.tomcat.bayeux.request; + +import java.io.IOException; +import java.util.HashMap; +import javax.servlet.ServletException; + +import org.apache.catalina.CometEvent; +import org.apache.tomcat.bayeux.HttpError; +import org.apache.tomcat.bayeux.BayeuxException; +import org.apache.tomcat.bayeux.BayeuxRequest; +import org.apache.tomcat.bayeux.ClientImpl; +import org.apache.tomcat.bayeux.TomcatBayeux; +import org.json.JSONException; +import org.json.JSONObject; +import org.apache.cometd.bayeux.Bayeux; +import org.apache.tomcat.bayeux.*; + +/****************************************************************************** + * Handshake request Bayeux message. + * + * @author Guy A. Molinari + * @author Filip Hanik + * @version 1.0 + * + */ +public class MetaConnectRequest extends RequestBase implements BayeuxRequest { + protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>(); + + static { + responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_CONNECT); + responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE); + responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>()); + } + + public MetaConnectRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException { + super(tb, event, jsReq); + if (clientId!=null && getTomcatBayeux().hasClient(clientId)) { + event.getHttpServletRequest().setAttribute("client",getTomcatBayeux().getClient(clientId)); + } + } + + + /** + * Check client request for validity. + * + * Per section 4.2.1 of the Bayuex spec a connect request must contain: + * 1) The "/meta/connect" channel identifier. + * 2) The clientId returned by the server after handshake. + * 3) The desired connectionType (must be one of the server's supported + * types returned by handshake response. + * + * @return HttpError This method returns null if no errors were found + */ + public HttpError validate() { + if(clientId==null|| (!getTomcatBayeux().hasClient(clientId))) + return new HttpError(400,"Client Id not valid.", null); + if (! (Bayeux.TRANSPORT_LONG_POLL.equals(conType) || Bayeux.TRANSPORT_CALLBACK_POLL.equals(conType))) + return new HttpError(400,"Unsupported connection type.",null); + return null;//no error + } + + /** + * Transition to connected state, flushing pending messages if + * available. If there are pending subscriptions and no messages to + * flush then the connection is held until there is a pending publish + * event to be delivered to this client (Section 4.2.2 of spec). + */ + public int process(int prevops) throws BayeuxException { + prevops = super.process(prevops); + response = (HashMap<String, Object>)responseTemplate.clone(); + ClientImpl client = (ClientImpl)getTomcatBayeux().getClient(clientId); + boolean success = false; + HttpError error = validate(); + if (error == null) { + client.setDesirectConnType(desiredConnTypeFlag); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.RETRY_RESPONSE); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.INTERVAL_FIELD, getReconnectInterval()); + success = true; + }else { + response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE); + response.put(Bayeux.ERROR_FIELD, error.toString()); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.HANDSHAKE_RESPONSE); + if (client==null) client = TomcatBayeux.getErrorClient(); + } + response.put(Bayeux.CLIENT_FIELD, client.getId()); + response.put(Bayeux.TIMESTAMP_FIELD,getTimeStamp()); + try { + JSONObject obj = new JSONObject(response); + addToDeliveryQueue(client, obj); + } catch (ServletException x) { + throw new BayeuxException(x); + } catch (IOException x) { + throw new BayeuxException(x); + } + + //return immediately if there is no subscriptions + //so that we can process the next message + int result = client.isSubscribed()?1:0; + + if (success && client!=null && client.hasMessages()) { + //send out messages + flushMessages(client); + result = 0; //flush out the messages + } + + return result; + } +} + Added: tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaDisconnectRequest.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaDisconnectRequest.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaDisconnectRequest.java (added) +++ tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaDisconnectRequest.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,105 @@ +/* + * 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 org.apache.tomcat.bayeux.request; + +import java.io.IOException; +import java.util.HashMap; +import javax.servlet.ServletException; + +import org.apache.catalina.CometEvent; +import org.apache.tomcat.bayeux.HttpError; +import org.apache.tomcat.bayeux.BayeuxException; +import org.apache.tomcat.bayeux.BayeuxRequest; +import org.apache.tomcat.bayeux.ClientImpl; +import org.apache.tomcat.bayeux.TomcatBayeux; +import org.json.JSONException; +import org.json.JSONObject; +import org.apache.cometd.bayeux.Bayeux; +import org.apache.tomcat.bayeux.*; +import org.apache.cometd.bayeux.Channel; + +/****************************************************************************** + * Handshake request Bayeux message. + * + * @author Guy A. Molinari + * @author Filip Hanik + * @version 1.0 + * + */ +public class MetaDisconnectRequest extends RequestBase implements BayeuxRequest { + + protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>(); + + static { + responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_DISCONNECT); + responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE); + responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>()); + } + + public MetaDisconnectRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException { + super(tb, event, jsReq); + } + + + /** + * Check client request for validity. + * + * Per section 4.4.1 of the Bayuex spec a connect request must contain: + * 1) The "/meta/disconnect" channel identifier. + * 2) The clientId. + * + * @return HttpError This method returns null if no errors were found + */ + public HttpError validate() { + if(clientId==null|| (!this.getTomcatBayeux().hasClient(clientId))) + return new HttpError(400,"Client Id not valid.", null); +// if (! (Bayeux.TRANSPORT_LONG_POLL.equals(conType) || Bayeux.TRANSPORT_CALLBACK_POLL.equals(conType))) +// return new HttpError(400,"Unsupported connection type.",null); + return null;//no error + } + + /** + * Disconnect a client session. + */ + public int process(int prevops) throws BayeuxException { + prevops = super.process(prevops); + response = (HashMap<String, Object>)responseTemplate.clone(); + ClientImpl client = (ClientImpl)getTomcatBayeux().getClient(clientId); + HttpError error = validate(); + if (error == null) { + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "retry"); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("interval", getReconnectInterval()); + }else { + getTomcatBayeux().remove(client); + response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE); + response.put(Bayeux.ERROR_FIELD, error.toString()); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "none"); + if (client==null) client = TomcatBayeux.getErrorClient(); + } + response.put(Bayeux.CLIENT_FIELD, client.getId()); + try { + JSONObject obj = new JSONObject(response); + addToDeliveryQueue(client, obj); + } catch (ServletException x) { + throw new BayeuxException(x); + } catch (IOException x) { + throw new BayeuxException(x); + } + return 0; + } +} + Added: tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaHandshakeRequest.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaHandshakeRequest.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaHandshakeRequest.java (added) +++ tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaHandshakeRequest.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,116 @@ +/* + * 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 org.apache.tomcat.bayeux.request; + +import java.io.IOException; +import java.util.HashMap; +import javax.servlet.ServletException; + +import org.apache.catalina.CometEvent; +import org.apache.tomcat.bayeux.HttpError; +import org.apache.tomcat.bayeux.BayeuxException; +import org.apache.tomcat.bayeux.BayeuxRequest; +import org.apache.tomcat.bayeux.ClientImpl; +import org.apache.tomcat.bayeux.TomcatBayeux; +import org.json.JSONException; +import org.json.JSONObject; +import org.apache.cometd.bayeux.Bayeux; +import org.apache.tomcat.bayeux.*; + +/****************************************************************************** + * Handshake request Bayeux message. + * + * @author Guy A. Molinari + * @author Filip Hanik + * @version 1.0 + * + */ +public class MetaHandshakeRequest extends RequestBase implements BayeuxRequest { + + protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>(); + + static { + responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_HANDSHAKE); + responseTemplate.put(Bayeux.VERSION_FIELD,"1.0"); + responseTemplate.put(Bayeux.SUPP_CONNECTION_TYPE_FIELD,new String[] { Bayeux.TRANSPORT_LONG_POLL, Bayeux.TRANSPORT_CALLBACK_POLL }); + responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE); + responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>()); + } + + public MetaHandshakeRequest(TomcatBayeux tomcatBayeux, CometEvent event, JSONObject jsReq) throws JSONException { + super(tomcatBayeux, event, jsReq); + } + + + public String getVersion() { return version; } + public String getMinimumVersion() { return minVersion; } + + + /** + * Check client request for validity. + * + * Per section 4.1.1 of the Bayuex spec a handshake request must contain: + * 1) The "/meta/handshake" channel identifier. + * 2) The version of the protocol supported by the client + * 3) The client's supported connection types. + * + * @return HttpError This method returns null if no errors were found + */ + public HttpError validate() { + boolean error = (version==null || version.length()==0); + if (!error) error = suppConnTypesFlag==0; + if (error) return new HttpError(400,"Invalid handshake request, supportedConnectionType field missing.",null); + else return null; + } + + /** + * Generate and return a client identifier. Return a list of + * supported connection types. Must be a subset of or identical to + * the list of types supported by the client. See section 4.1.2 of + * the Bayuex specification. + */ + public int process(int prevops) throws BayeuxException { + prevops = super.process(prevops); + response = (HashMap<String, Object>)responseTemplate.clone(); + ClientImpl client = null; + HttpError error = validate(); + if (error == null) { + client = (ClientImpl) getTomcatBayeux().newClient("http-", null, false,getEvent()); + clientId = client.getId(); + client.setSupportedConnTypes(suppConnTypesFlag); + client.setUseJsonFiltered(getExt().get(Bayeux.JSON_COMMENT_FILTERED_FIELD) != null); + response.put(Bayeux.CLIENT_FIELD, client.getId()); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.RETRY_RESPONSE); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.INTERVAL_FIELD, getReconnectInterval()); + }else { + response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE); + response.put(Bayeux.ERROR_FIELD, error.toString()); + client = TomcatBayeux.getErrorClient(); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.NONE_RESPONSE); + } + try { + JSONObject obj = new JSONObject(response); + addToDeliveryQueue(client, obj); + } catch (ServletException x) { + throw new BayeuxException(x); + } catch (IOException x) { + throw new BayeuxException(x); + } + return 0; + } +} + Added: tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaSubscribeRequest.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaSubscribeRequest.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaSubscribeRequest.java (added) +++ tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaSubscribeRequest.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,130 @@ +/* + * 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 org.apache.tomcat.bayeux.request; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import javax.servlet.ServletException; + +import org.apache.catalina.CometEvent; +import org.apache.tomcat.bayeux.HttpError; +import org.apache.tomcat.bayeux.BayeuxException; +import org.apache.tomcat.bayeux.BayeuxRequest; +import org.apache.tomcat.bayeux.ChannelImpl; +import org.apache.tomcat.bayeux.ClientImpl; +import org.apache.tomcat.bayeux.TomcatBayeux; +import org.json.JSONException; +import org.json.JSONObject; +import org.apache.cometd.bayeux.Channel; +import org.apache.cometd.bayeux.Bayeux; +import org.apache.tomcat.bayeux.*; + +/****************************************************************************** + * Handshake request Bayeux message. + * + * @author Guy A. Molinari + * @author Filip Hanik + * @version 1.0 + */ +public class MetaSubscribeRequest extends RequestBase implements BayeuxRequest { + + protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>(); + + static { + responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_SUBSCRIBE); + responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE); + responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>()); + } + + public MetaSubscribeRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException { + super(tb, event, jsReq); + } + + + /** + * Check client request for validity. + * + * Per section 4.5.1 of the Bayuex spec a connect request must contain: + * 1) The "/meta/subscribe" channel identifier. + * 2) The clientId. + * 3) The subscription. This is the name of the channel of interest, + * or a pattern. + * + * @return HttpError This method returns null if no errors were found + */ + public HttpError validate() { + if(clientId==null|| (!this.getTomcatBayeux().hasClient(clientId))) + return new HttpError(400,"Client Id not valid.", null); + if (subscription==null||subscription.length()==0) + return new HttpError(400,"Subscription missing.",null); + return null;//no error + } + + /** + * Register interest for one or more channels. Per section 2.2.1 of the + * Bayeux spec, a pattern may be specified. Assign client to matching + * channels and inverse client to channel reference. + */ + public int process(int prevops) throws BayeuxException { + prevops = super.process(prevops); + response = (HashMap<String, Object>)this.responseTemplate.clone(); + ClientImpl client = (ClientImpl)getTomcatBayeux().getClient(clientId); + HttpError error = validate(); + if (error == null) { + boolean wildcard = subscription.indexOf('*')!=-1; + boolean subscribed = false; + if (wildcard) { + List<Channel> channels = getTomcatBayeux().getChannels(); + Iterator<Channel> it = channels.iterator(); + while (it.hasNext()) { + ChannelImpl ch = (ChannelImpl)it.next(); + if (ch.matches(subscription)) { + ch.subscribe(client); + subscribed = true; + } + } + }else { + ChannelImpl ch = (ChannelImpl)getTomcatBayeux().getChannel(subscription,true); + ch.subscribe(client); + subscribed = true; + } + response.put(Bayeux.SUCCESSFUL_FIELD, Boolean.valueOf(subscribed)); + response.put(Bayeux.SUBSCRIPTION_FIELD,subscription); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "retry"); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("interval", getReconnectInterval()); + }else { + response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE); + response.put(Bayeux.ERROR_FIELD, error.toString()); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "handshake"); + if (client==null) client = TomcatBayeux.getErrorClient(); + } + response.put(Bayeux.CLIENT_FIELD, client.getId()); + response.put(Bayeux.TIMESTAMP_FIELD,getTimeStamp()); + try { + JSONObject obj = new JSONObject(response); + addToDeliveryQueue(client, obj); + } catch (ServletException x) { + throw new BayeuxException(x); + } catch (IOException x) { + throw new BayeuxException(x); + } + return 0; + } +} + Added: tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaUnsubscribeRequest.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaUnsubscribeRequest.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaUnsubscribeRequest.java (added) +++ tomcat/trunk/java/org/apache/tomcat/bayeux/request/MetaUnsubscribeRequest.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,130 @@ +/* + * 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 org.apache.tomcat.bayeux.request; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import javax.servlet.ServletException; + +import org.apache.catalina.CometEvent; +import org.apache.tomcat.bayeux.HttpError; +import org.apache.tomcat.bayeux.BayeuxException; +import org.apache.tomcat.bayeux.BayeuxRequest; +import org.apache.tomcat.bayeux.ChannelImpl; +import org.apache.tomcat.bayeux.ClientImpl; +import org.apache.tomcat.bayeux.TomcatBayeux; +import org.json.JSONException; +import org.json.JSONObject; +import org.apache.cometd.bayeux.Channel; +import org.apache.cometd.bayeux.Bayeux; +import org.apache.tomcat.bayeux.*; + +/****************************************************************************** + * Handshake request Bayeux message. + * + * @author Guy A. Molinari + * @author Filip Hanik + * @version 1.0 + * + */ +public class MetaUnsubscribeRequest extends RequestBase implements BayeuxRequest { + + protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>(); + + static { + responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_UNSUBSCRIBE); + responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE); + responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>()); + } + + public MetaUnsubscribeRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException { + super(tb, event, jsReq); + } + + + /** + * Check client request for validity. + * + * Per section 4.6.1 of the Bayuex spec a connect request must contain: + * 1) The "/meta/unsubscribe" channel identifier. + * 2) The clientId. + * 3) The subscription. This is the name of the channel of interest, + * or a pattern. + * + * @return HttpError This method returns null if no errors were found + */ + public HttpError validate() { + if(clientId==null|| (!this.getTomcatBayeux().hasClient(clientId))) + return new HttpError(400,"Client Id not valid.", null); + if (subscription==null||subscription.length()==0) + return new HttpError(400,"Subscription missing.",null); + return null;//no error + } + + /** + * De-register interest for one or more channels. Per section 2.2.1 of the + * Bayeux spec, a pattern may be specified. Sever relationships. + */ + public int process(int prevops) throws BayeuxException { + prevops = super.process(prevops); + response = (HashMap<String, Object>)responseTemplate.clone(); + ClientImpl client = (ClientImpl)getTomcatBayeux().getClient(clientId); + HttpError error = validate(); + if (error == null) { + boolean wildcard = subscription.indexOf('*')!=-1; + boolean unsubscribed = false; + if (wildcard) { + List<Channel> channels = getTomcatBayeux().getChannels(); + Iterator<Channel> it = channels.iterator(); + while (it.hasNext()) { + ChannelImpl ch = (ChannelImpl)it.next(); + if (ch.matches(subscription)) { + ch.unsubscribe(client); + unsubscribed = true; + } + } + }else { + ChannelImpl ch = (ChannelImpl)getTomcatBayeux().getChannel(subscription,true); + ch.unsubscribe(client); + unsubscribed = true; + } + response.put(Bayeux.SUCCESSFUL_FIELD, Boolean.valueOf(unsubscribed)); + response.put(Bayeux.SUBSCRIPTION_FIELD,subscription); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "retry"); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("interval", getReconnectInterval()); + }else { + response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE); + response.put(Bayeux.ERROR_FIELD, error.toString()); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "handshake"); + if (client==null) client = TomcatBayeux.getErrorClient(); + } + response.put(Bayeux.CLIENT_FIELD, client.getId()); + response.put(Bayeux.TIMESTAMP_FIELD,getTimeStamp()); + try { + JSONObject obj = new JSONObject(response); + addToDeliveryQueue(client, obj); + } catch (ServletException x) { + throw new BayeuxException(x); + } catch (IOException x) { + throw new BayeuxException(x); + } + return 0; + } +} + Added: tomcat/trunk/java/org/apache/tomcat/bayeux/request/PublishRequest.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/bayeux/request/PublishRequest.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/bayeux/request/PublishRequest.java (added) +++ tomcat/trunk/java/org/apache/tomcat/bayeux/request/PublishRequest.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,140 @@ +/* + * 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 org.apache.tomcat.bayeux.request; + +import java.io.IOException; +import java.util.HashMap; +import javax.servlet.ServletException; + +import org.apache.catalina.CometEvent; +import org.apache.tomcat.bayeux.HttpError; +import org.apache.tomcat.bayeux.BayeuxException; +import org.apache.tomcat.bayeux.BayeuxRequest; +import org.apache.tomcat.bayeux.ChannelImpl; +import org.apache.tomcat.bayeux.ClientImpl; +import org.apache.tomcat.bayeux.MessageImpl; +import org.apache.tomcat.bayeux.TomcatBayeux; +import org.json.JSONException; +import org.json.JSONObject; +import org.apache.cometd.bayeux.Bayeux; +import java.util.List; +import org.apache.cometd.bayeux.Message; +import java.util.Iterator; +import org.apache.tomcat.bayeux.*; + +/****************************************************************************** + * Handshake request Bayeux message. + * + * @author Guy A. Molinari + * @author Filip Hanik + * @version 1.0 + * + */ +public class PublishRequest extends RequestBase implements BayeuxRequest { + + JSONObject msgData = null; + + protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>(); + + static { + responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE); + responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>()); + } + + public PublishRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException { + super(tb, event, jsReq); + } + + + /** + * Check client request for validity. + * + * Per section 5.1.1 of the Bayuex spec a connect request must contain: + * 1) The channel identifier of the channel for publication. + * 2) The data to send. + * + * @return HttpError This method returns null if no errors were found + */ + public HttpError validate() { + if(channel==null|| (!this.getTomcatBayeux().hasChannel(channel))) + return new HttpError(400,"Channel Id not valid.", null); + if(data==null || data.length()==0) + return new HttpError(400,"Message data missing.", null); + try { + this.msgData = new JSONObject(data); + }catch (JSONException x) { + return new HttpError(400,"Invalid JSON object in data attribute.",x); + } + if(clientId==null|| (!this.getTomcatBayeux().hasClient(clientId))) + return new HttpError(400,"Client Id not valid.", null); + return null;//no error + } + + /** + * Send the event message to all registered subscribers. + */ + public int process(int prevops) throws BayeuxException { + prevops = super.process(prevops); + response = (HashMap<String, Object>)responseTemplate.clone(); + ClientImpl client = clientId!=null?(ClientImpl)getTomcatBayeux().getClient(clientId): + (ClientImpl)event.getHttpServletRequest().getAttribute("client"); + boolean success = false; + HttpError error = validate(); + if (error == null) { + ChannelImpl chimpl = (ChannelImpl)getTomcatBayeux().getChannel(channel,false); + MessageImpl mimpl = (MessageImpl)getTomcatBayeux().newMessage(client); + + try { + String[] keys = JSONObject.getNames(msgData); + for (int i = 0; i < keys.length; i++) { + mimpl.put(keys[i], msgData.get(keys[i])); + } + success = true; + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.RETRY_RESPONSE); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.INTERVAL_FIELD, getReconnectInterval()); + }catch (JSONException x) { + if (log.isErrorEnabled()) log.error("Unable to parse:"+msgData,x); + throw new BayeuxException(x); + } + chimpl.publish(mimpl); + } + if(!success) { + response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE); + response.put(Bayeux.ERROR_FIELD, error.toString()); + ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.HANDSHAKE_RESPONSE); + if (client==null) client = TomcatBayeux.getErrorClient(); + } + response.put(Bayeux.CHANNEL_FIELD,channel); + response.put(Bayeux.CLIENT_FIELD, client.getId()); + try { + JSONObject obj = new JSONObject(response); + addToDeliveryQueue(client, obj); + } catch (ServletException x) { + throw new BayeuxException(x); + } catch (IOException x) { + throw new BayeuxException(x); + } + + if (success && client!=null && client.hasMessages()) { + //send out messages + flushMessages(client); + } + + return 0; + } +} + Added: tomcat/trunk/test/org/apache/cometd/bayeux/samples/EchoChatClient.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/cometd/bayeux/samples/EchoChatClient.java?rev=691359&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/cometd/bayeux/samples/EchoChatClient.java (added) +++ tomcat/trunk/test/org/apache/cometd/bayeux/samples/EchoChatClient.java Tue Sep 2 13:00:36 2008 @@ -0,0 +1,111 @@ +package org.apache.cometd.bayeux.samples; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextAttributeEvent; +import org.apache.cometd.bayeux.Bayeux; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.cometd.bayeux.Client; +import org.apache.cometd.bayeux.Listener; +import org.apache.cometd.bayeux.Message; +import org.apache.cometd.bayeux.Channel; + +public class EchoChatClient implements ServletContextListener, ServletContextAttributeListener, Listener { + + static AtomicInteger counter = new AtomicInteger(0); + protected int id; + protected Bayeux b; + protected Client c; + protected boolean alive = true; + protected TimestampThread tt = new TimestampThread(); + public EchoChatClient() { + id = counter.incrementAndGet(); + System.out.println("new listener created with id:"+id); + } + + /** + * contextDestroyed + * + * @param servletContextEvent ServletContextEvent + * @todo Implement this javax.servlet.ServletContextListener method + */ + public void contextDestroyed(ServletContextEvent servletContextEvent) { + alive = false; + tt.interrupt(); + } + + /** + * contextInitialized + * + * @param servletContextEvent ServletContextEvent + * @todo Implement this javax.servlet.ServletContextListener method + */ + public void contextInitialized(ServletContextEvent servletContextEvent) { + } + + public void attributeAdded(ServletContextAttributeEvent scae) { + if (scae.getName().equals(Bayeux.DOJOX_COMETD_BAYEUX)) { + b = (Bayeux)scae.getValue(); + c = b.newClient("echochat-",this); + Channel ch = b.getChannel("/chat/demo",true); + ch.subscribe(c); + tt.start(); + } + } + + public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) { + } + + public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) { + } + + public void removed(boolean timeout) { + System.out.println("Client removed."); + } + + public void deliver(Message[] msgs) { + for (int i=0; msgs!=null && i<msgs.length; i++) { + Message msg = msgs[i]; + System.out.println("[echochatclient ]received message:" + msg); + Message m = b.newMessage(c); + m.putAll(msg); + //echo the same message + m.put("user", "echochatserver"); + if (m.containsKey("msg")) { + //simple chat demo + String chat = (String) m.get("msg"); + m.put("msg", "echochatserver|I received your message-" + chat.substring(chat.indexOf("|") + 1)); + } + System.out.println("[echochatclient ]sending message:" + m); + msg.getChannel().publish(m); + } + } + + public class TimestampThread extends Thread { + public TimestampThread() { + setDaemon(true); + } + + public void run() { + while (alive) { + try { + sleep(5000); + Channel ch = b.getChannel("/chat/demo",false); + if (ch.getSubscribers().size()<=1) { + continue; + } + Message m = b.newMessage(c); + m.put("user","echochatserver"); + m.put("chat","Time is:"+new java.sql.Date(System.currentTimeMillis()).toLocaleString()); + m.put("join",false); + ch.publish(m); + }catch (InterruptedException ignore) { + + }catch (Exception x) { + x.printStackTrace(); + } + } + } + } +} Added: tomcat/trunk/webapps/cometd/WEB-INF/filters.json URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/cometd/WEB-INF/filters.json?rev=691359&view=auto ============================================================================== --- tomcat/trunk/webapps/cometd/WEB-INF/filters.json (added) +++ tomcat/trunk/webapps/cometd/WEB-INF/filters.json Tue Sep 2 13:00:36 2008 @@ -0,0 +1,27 @@ +[ + { + "channels": "/**", + "filter" : "org.mortbay.cometd.filter.NoMarkupFilter", + "init" : {} + }, + + { + "channels": "/chat/*", + "filter" : "org.mortbay.cometd.filter.RegexFilter", + "init" : [ + [ "[fF].ck","dang" ], + [ "teh ","the "] + ] + }, + + { + "channels": "/chat/**", + "filter" : "org.mortbay.cometd.filter.RegexFilter", + "init" : [ + [ "[Mm]icrosoft", "Micro\\$oft" ], + [ ".*tomcat.*", null ] + ] + } + + +] \ No newline at end of file Added: tomcat/trunk/webapps/cometd/WEB-INF/web.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/cometd/WEB-INF/web.xml?rev=691359&view=auto ============================================================================== --- tomcat/trunk/webapps/cometd/WEB-INF/web.xml (added) +++ tomcat/trunk/webapps/cometd/WEB-INF/web.xml Tue Sep 2 13:00:36 2008 @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<web-app + xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" + version="2.5"> + <display-name>Cometd Test WebApp</display-name> + + <servlet> + <servlet-name>cometd</servlet-name> + <servlet-class>org.apache.tomcat.bayeux.BayeuxServlet</servlet-class> + <init-param> + <param-name>timeout</param-name> + <param-value>120000000</param-value> + </init-param> + <load-on-startup>1</load-on-startup> + </servlet> + + <servlet-mapping> + <servlet-name>cometd</servlet-name> + <url-pattern>/cometd/*</url-pattern> + </servlet-mapping> + +</web-app> + + Added: tomcat/trunk/webapps/cometd/examples/chat/chat.css URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/cometd/examples/chat/chat.css?rev=691359&view=auto ============================================================================== --- tomcat/trunk/webapps/cometd/examples/chat/chat.css (added) +++ tomcat/trunk/webapps/cometd/examples/chat/chat.css Tue Sep 2 13:00:36 2008 @@ -0,0 +1,57 @@ + +div +{ + border: 0px solid black; +} + +div#chatroom +{ + background-color: #e0e0e0; + border: 1px solid black; + width: 45em; +} + +div#chat +{ + height: 20ex; + overflow: auto; + background-color: #f0f0f0; + padding: 4px; + border: 0px solid black; +} + +div#input +{ + clear: both; + padding: 4px; + border: 0px solid black; + border-top: 1px solid black; +} + +input#phrase +{ + width:28em; + background-color: #e0f0f0; +} + +input#username +{ + width:14em; + background-color: #e0f0f0; +} + +div.hidden +{ + display: none; +} + +span.from +{ + font-weight: bold; +} + +span.alert +{ + font-style: italic; +} + Added: tomcat/trunk/webapps/cometd/examples/chat/chat.js URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/cometd/examples/chat/chat.js?rev=691359&view=auto ============================================================================== --- tomcat/trunk/webapps/cometd/examples/chat/chat.js (added) +++ tomcat/trunk/webapps/cometd/examples/chat/chat.js Tue Sep 2 13:00:36 2008 @@ -0,0 +1,139 @@ +dojo.require("dojox.cometd"); +dojo.require("dojox.cometd.timestamp"); + +var room = { + _last: "", + _username: null, + _connected: true, + + join: function(name){ + + if(name == null || name.length==0 ){ + alert('Please enter a username!'); + }else{ + + dojox.cometd.init(new String(document.location).replace(/http:\/\/[^\/]*/,'').replace(/\/examples\/.*$/,'')+"/cometd"); + // dojox.cometd.init("http://127.0.0.2:8080/cometd"); + this._connected=true; + + this._username=name; + dojo.byId('join').className='hidden'; + dojo.byId('joined').className=''; + dojo.byId('phrase').focus(); + + // subscribe and join + dojox.cometd.startBatch(); + dojox.cometd.subscribe("/chat/demo", room, "_chat"); + dojox.cometd.publish("/chat/demo", { user: room._username, join: true, chat : room._username+" has joined"}); + dojox.cometd.endBatch(); + + // handle cometd failures while in the room + room._meta=dojo.subscribe("/cometd/meta",dojo.hitch(this,function(event){ + console.debug(event); + if (event.action=="handshake"){ + room._chat({data:{join:true,user:"SERVER",chat:"reinitialized"}}); + dojox.cometd.subscribe("/chat/demo", room, "_chat"); + } else if (event.action=="connect") { + if (event.successful && !this._connected) + room._chat({data:{leave:true,user:"SERVER",chat:"reconnected!"}}); + if (!event.successful && this._connected) + room._chat({data:{leave:true,user:"SERVER",chat:"disconnected!"}}); + this._connected=event.successful; + } + })); + } + }, + + leave: function(){ + if (room._username==null) + return; + + if (room._meta) + dojo.unsubscribe(room._meta); + room._meta=null; + + dojox.cometd.startBatch(); + dojox.cometd.unsubscribe("/chat/demo", room, "_chat"); + dojox.cometd.publish("/chat/demo", { user: room._username, leave: true, chat : room._username+" has left"}); + dojox.cometd.endBatch(); + + // switch the input form + dojo.byId('join').className=''; + dojo.byId('joined').className='hidden'; + dojo.byId('username').focus(); + room._username=null; + dojox.cometd.disconnect(); + }, + + chat: function(text){ + if(!text || !text.length){ return false; } + dojox.cometd.publish("/chat/demo", { user: room._username, chat: text}); + }, + + _chat: function(message){ + var chat=dojo.byId('chat'); + if(!message.data){ + alert("bad message format "+message); + return; + } + var from=message.data.user; + var special=message.data.join || message.data.leave; + var text=message.data.chat; + if(!text){ return; } + + if( !special && from == room._last ){ + from="..."; + }else{ + room._last=from; + from+=":"; + } + + if(special){ + chat.innerHTML += "<span class=\"alert\"><span class=\"from\">"+from+" </span><span class=\"text\">"+text+"</span></span><br/>"; + room._last=""; + }else{ + chat.innerHTML += "<span class=\"from\">"+from+" </span><span class=\"text\">"+text+"</span><br/>"; + } + chat.scrollTop = chat.scrollHeight - chat.clientHeight; + }, + + _init: function(){ + dojo.byId('join').className=''; + dojo.byId('joined').className='hidden'; + dojo.byId('username').focus(); + + var element=dojo.byId('username'); + element.setAttribute("autocomplete","OFF"); + dojo.connect(element, "onkeyup", function(e){ + if(e.keyCode == dojo.keys.ENTER){ + room.join(dojo.byId('username').value); + return false; + } + return true; + }); + + dojo.connect(dojo.byId('joinB'), "onclick", function(e){ + room.join(dojo.byId('username').value); + e.preventDefault(); + }); + + element=dojo.byId('phrase'); + element.setAttribute("autocomplete","OFF"); + dojo.connect(element, "onkeyup", function(e){ + if(e.keyCode == dojo.keys.ENTER){ + room.chat(dojo.byId('phrase').value); + dojo.byId('phrase').value=''; + e.preventDefault(); + } + }); + + dojo.connect(dojo.byId('sendB'), "onkeyup", function(e){ + room.chat(dojo.byId('phrase').value); + dojo.byId('phrase').value=''; + }); + dojo.connect(dojo.byId('leaveB'), "onclick", room, "leave"); + } +}; + +dojo.addOnLoad(room, "_init"); +dojo.addOnUnload(room,"leave"); Added: tomcat/trunk/webapps/cometd/examples/chat/index.html URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/cometd/examples/chat/index.html?rev=691359&view=auto ============================================================================== --- tomcat/trunk/webapps/cometd/examples/chat/index.html (added) +++ tomcat/trunk/webapps/cometd/examples/chat/index.html Tue Sep 2 13:00:36 2008 @@ -0,0 +1,26 @@ +<html> +<head> + <title>Cometd chat</title> + <script type="text/javascript" src="../dojo/dojo/dojo.js"></script> + <script type="text/javascript" src="../dojo/dojox/cometd.js.uncompressed.js"></script> + <script type="text/javascript" src="chat.js"></script> + <link rel="stylesheet" type="text/css" href="chat.css"> +</head> +<body> +<h1>Cometd Chat</h1> + +<div id="chatroom"> + <div id="chat"></div> + <div id="input"> + <div id="join" > + Username: <input id="username" type="text"/><input id="joinB" class="button" type="submit" name="join" value="Join"/> + </div> + <div id="joined" class="hidden"> + Chat: <input id="phrase" type="text"/> + <input id="sendB" class="button" type="submit" name="join" value="Send"/> + <input id="leaveB" class="button" type="submit" name="join" value="Leave"/> + </div> + </div> + </div> + +</body> --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]