JBossWeb SVN: r2265 - in branches/7.4.x/src/main/java/org/apache: tomcat/websocket and 1 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-27 10:01:51 -0400 (Fri, 27 Sep 2013)
New Revision: 2265
Modified:
branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11AprProcessor.java
branches/7.4.x/src/main/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/FutureToSendHandler.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsSession.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
Log:
- Add flushing at the end of a message write (Tomcat uses unbuffered stream and direct socket writes)
- Try using autoblocking for the blocking get() on the future
Modified: branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11AprProcessor.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11AprProcessor.java 2013-09-24 17:35:22 UTC (rev 2264)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11AprProcessor.java 2013-09-27 14:01:51 UTC (rev 2265)
@@ -96,6 +96,8 @@
initializeFilters();
+ Http11AbstractProcessor.containerThread.set(Boolean.FALSE);
+
// Cause loading of HexUtils
int foo = HexUtils.DEC[0];
@@ -109,12 +111,6 @@
/**
- * Thread local marker.
- */
- public static ThreadLocal<Boolean> containerThread = new ThreadLocal<Boolean>();
-
-
- /**
* Associated adapter.
*/
protected Adapter adapter = null;
@@ -762,7 +758,7 @@
// Set error flag right away
error = true;
}
- containerThread.set(Boolean.TRUE);
+ Http11AbstractProcessor.containerThread.set(Boolean.TRUE);
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
error = !adapter.event(request, response, status);
} catch (InterruptedIOException e) {
Modified: branches/7.4.x/src/main/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/InternalAprOutputBuffer.java 2013-09-24 17:35:22 UTC (rev 2264)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/InternalAprOutputBuffer.java 2013-09-27 14:01:51 UTC (rev 2265)
@@ -538,7 +538,7 @@
// If non blocking (event) and there are leftover bytes,
// and lastWrite was 0 -> error
- if (leftover.getLength() > 0 && !(Http11AprProcessor.containerThread.get() == Boolean.TRUE)) {
+ if (leftover.getLength() > 0 && !(Http11AbstractProcessor.containerThread.get() == Boolean.TRUE)) {
throw new IOException(MESSAGES.invalidBacklog());
}
@@ -789,7 +789,7 @@
// - If the call is asynchronous, throw an exception
// - If the call is synchronous, make regular blocking writes to flush the data
if (leftover.getLength() > 0) {
- if (Http11AprProcessor.containerThread.get() == Boolean.TRUE) {
+ if (Http11AbstractProcessor.containerThread.get() == Boolean.TRUE) {
Socket.timeoutSet(socket, endpoint.getSoTimeout() * 1000);
// Send leftover bytes
res = Socket.send(socket, leftover.getBuffer(), leftover.getOffset(), leftover.getEnd());
@@ -820,7 +820,7 @@
}
}
if (pos < end) {
- if (response.getFlushLeftovers() && (Http11AprProcessor.containerThread.get() == Boolean.TRUE)) {
+ if (response.getFlushLeftovers() && (Http11AbstractProcessor.containerThread.get() == Boolean.TRUE)) {
// Switch to blocking mode and write the data
Socket.timeoutSet(socket, endpoint.getSoTimeout() * 1000);
res = Socket.sendbb(socket, 0, end);
Modified: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/FutureToSendHandler.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/FutureToSendHandler.java 2013-09-24 17:35:22 UTC (rev 2264)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/FutureToSendHandler.java 2013-09-27 14:01:51 UTC (rev 2265)
@@ -16,6 +16,7 @@
*/
package org.apache.tomcat.websocket;
+import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -25,6 +26,8 @@
import javax.websocket.SendHandler;
import javax.websocket.SendResult;
+import org.apache.coyote.http11.Http11AbstractProcessor;
+
/**
* Converts a Future to a SendHandler.
*/
@@ -71,15 +74,26 @@
@Override
public Void get() throws InterruptedException,
ExecutionException {
- try {
- wsSession.registerFuture(this);
- latch.await();
- } finally {
- wsSession.unregisterFuture(this);
+ // If inside a container thread, must use an autoblocking flush as the write
+ // event will never come to the Servlet layer until the container thread returns
+ if (Http11AbstractProcessor.containerThread.get() == Boolean.TRUE) {
+ // FIXME: this uses the IO timeout rather than no timeout as per the API contract
+ try {
+ wsSession.forceFlush();
+ } catch (IOException e) {
+ throw new ExecutionException(e);
+ }
+ } else {
+ try {
+ wsSession.registerFuture(this);
+ latch.await();
+ } finally {
+ wsSession.unregisterFuture(this);
+ }
+ if (result.getException() != null) {
+ throw new ExecutionException(result.getException());
+ }
}
- if (result.getException() != null) {
- throw new ExecutionException(result.getException());
- }
return null;
}
@@ -87,20 +101,31 @@
public Void get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,
TimeoutException {
- boolean retval = false;
- try {
- wsSession.registerFuture(this);
- retval = latch.await(timeout, unit);
- } finally {
- wsSession.unregisterFuture(this);
+ // If inside a container thread, must use an autoblocking flush as the write
+ // event will never come to the Servlet layer until the container thread returns
+ if (Http11AbstractProcessor.containerThread.get() == Boolean.TRUE) {
+ // FIXME: this uses the IO timeout rather than the timeout specified by the user
+ try {
+ wsSession.forceFlush();
+ } catch (IOException e) {
+ throw new ExecutionException(e);
+ }
+ } else {
+ boolean retval = false;
+ try {
+ wsSession.registerFuture(this);
+ retval = latch.await(timeout, unit);
+ } finally {
+ wsSession.unregisterFuture(this);
+ }
+ if (retval == false) {
+ throw new TimeoutException();
+ }
+ if (result.getException() != null) {
+ throw new ExecutionException(result.getException());
+ }
}
- if (retval == false) {
- throw new TimeoutException();
- }
- if (result.getException() != null) {
- throw new ExecutionException(result.getException());
- }
return null;
}
}
Modified: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsSession.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsSession.java 2013-09-24 17:35:22 UTC (rev 2264)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsSession.java 2013-09-27 14:01:51 UTC (rev 2265)
@@ -436,6 +436,12 @@
}
}
+ /**
+ * Force an autoblocking flush.
+ */
+ public void forceFlush() throws IOException {
+ wsRemoteEndpoint.flushBatch();
+ }
private void fireEndpointOnClose(CloseReason closeReason) {
Modified: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java 2013-09-24 17:35:22 UTC (rev 2264)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java 2013-09-27 14:01:51 UTC (rev 2265)
@@ -82,9 +82,8 @@
}
if (complete) {
wsWriteTimeout.unregister(this);
- // ADD: Explicit flush
+ // Explicit flush for compatibility with buffered streams
sos.flush();
- // END ADD
if (close) {
close();
}
11 years, 3 months
JBossWeb SVN: r2264 - in branches/7.4.x/src/main/java/org: jboss/web and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-24 13:35:22 -0400 (Tue, 24 Sep 2013)
New Revision: 2264
Modified:
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
branches/7.4.x/src/main/java/org/jboss/web/WebsocketsMessages.java
Log:
Port patch fixing CCE when wrapping.
Modified: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UpgradeUtil.java 2013-09-24 13:00:09 UTC (rev 2263)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UpgradeUtil.java 2013-09-24 17:35:22 UTC (rev 2264)
@@ -16,6 +16,8 @@
*/
package org.apache.tomcat.websocket.server;
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -181,11 +183,11 @@
}
if (inner instanceof RequestFacade) {
WsHttpUpgradeHandler wsHandler =
- ((RequestFacade) req).upgrade(WsHttpUpgradeHandler.class);
+ ((RequestFacade) inner).upgrade(WsHttpUpgradeHandler.class);
wsHandler.preInit(ep, sec, sc, wsRequest, subProtocol,
pathParams, req.isSecure());
} else {
- throw new ServletException("Upgrade failed");
+ throw new ServletException(MESSAGES.upgradeFailed());
}
}
Modified: branches/7.4.x/src/main/java/org/jboss/web/WebsocketsMessages.java
===================================================================
--- branches/7.4.x/src/main/java/org/jboss/web/WebsocketsMessages.java 2013-09-24 13:00:09 UTC (rev 2263)
+++ branches/7.4.x/src/main/java/org/jboss/web/WebsocketsMessages.java 2013-09-24 17:35:22 UTC (rev 2264)
@@ -293,4 +293,7 @@
@Message(id = 8585, value = "Failed to create configurator of type [%s] for POJO of type [%s]")
String configuratorFailed(String configurator, String className);
+ @Message(id = 8586, value = "Upgrade failed")
+ String upgradeFailed();
+
}
11 years, 3 months
JBossWeb SVN: r2263 - in branches/7.4.x/src/main/java/org/apache: tomcat/websocket/server and 1 other directory.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-24 09:00:09 -0400 (Tue, 24 Sep 2013)
New Revision: 2263
Modified:
branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteAdapter.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/Request.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
Log:
Add an explicit flush when completing a real write.
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteAdapter.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteAdapter.java 2013-09-23 15:21:13 UTC (rev 2262)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteAdapter.java 2013-09-24 13:00:09 UTC (rev 2263)
@@ -341,6 +341,10 @@
(request.getAsyncContext() == null) ? Boolean.TRUE : Boolean.FALSE);
event = true;
}
+ if (request.getUpgradeHandler() != null) {
+ // Call to signal that the upgrade is now done
+ request.getUpgradeHandler().init(request.getEvent());
+ }
} else if (request.getAsyncContext() != null) {
// The AC was closed right away, so call onComplete as no event callback
// will occur in that case
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/Request.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/Request.java 2013-09-23 15:21:13 UTC (rev 2262)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/Request.java 2013-09-24 13:00:09 UTC (rev 2263)
@@ -3323,8 +3323,8 @@
}
response.sendUpgrade();
eventMode = true;
- ugradeHandler.init(getEvent());
this.upgradeHandler = ugradeHandler;
+ asyncContext = new AsyncContextImpl();
return ugradeHandler;
}
Modified: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java 2013-09-23 15:21:13 UTC (rev 2262)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java 2013-09-24 13:00:09 UTC (rev 2263)
@@ -82,6 +82,9 @@
}
if (complete) {
wsWriteTimeout.unregister(this);
+ // ADD: Explicit flush
+ sos.flush();
+ // END ADD
if (close) {
close();
}
11 years, 3 months
JBossWeb SVN: r2262 - in branches/7.4.x/src/main/java: javax and 2 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-23 11:21:13 -0400 (Mon, 23 Sep 2013)
New Revision: 2262
Added:
branches/7.4.x/src/main/java/javax/
branches/7.4.x/src/main/java/javax/websocket/
branches/7.4.x/src/main/java/javax/websocket/ClientEndpoint.java
branches/7.4.x/src/main/java/javax/websocket/ClientEndpointConfig.java
branches/7.4.x/src/main/java/javax/websocket/CloseReason.java
branches/7.4.x/src/main/java/javax/websocket/ContainerProvider.java
branches/7.4.x/src/main/java/javax/websocket/DecodeException.java
branches/7.4.x/src/main/java/javax/websocket/Decoder.java
branches/7.4.x/src/main/java/javax/websocket/DefaultClientEndpointConfig.java
branches/7.4.x/src/main/java/javax/websocket/DeploymentException.java
branches/7.4.x/src/main/java/javax/websocket/EncodeException.java
branches/7.4.x/src/main/java/javax/websocket/Encoder.java
branches/7.4.x/src/main/java/javax/websocket/Endpoint.java
branches/7.4.x/src/main/java/javax/websocket/EndpointConfig.java
branches/7.4.x/src/main/java/javax/websocket/Extension.java
branches/7.4.x/src/main/java/javax/websocket/HandshakeResponse.java
branches/7.4.x/src/main/java/javax/websocket/MessageHandler.java
branches/7.4.x/src/main/java/javax/websocket/OnClose.java
branches/7.4.x/src/main/java/javax/websocket/OnError.java
branches/7.4.x/src/main/java/javax/websocket/OnMessage.java
branches/7.4.x/src/main/java/javax/websocket/OnOpen.java
branches/7.4.x/src/main/java/javax/websocket/PongMessage.java
branches/7.4.x/src/main/java/javax/websocket/RemoteEndpoint.java
branches/7.4.x/src/main/java/javax/websocket/SendHandler.java
branches/7.4.x/src/main/java/javax/websocket/SendResult.java
branches/7.4.x/src/main/java/javax/websocket/Session.java
branches/7.4.x/src/main/java/javax/websocket/SessionException.java
branches/7.4.x/src/main/java/javax/websocket/WebSocketContainer.java
branches/7.4.x/src/main/java/javax/websocket/server/
branches/7.4.x/src/main/java/javax/websocket/server/DefaultServerEndpointConfig.java
branches/7.4.x/src/main/java/javax/websocket/server/HandshakeRequest.java
branches/7.4.x/src/main/java/javax/websocket/server/PathParam.java
branches/7.4.x/src/main/java/javax/websocket/server/ServerApplicationConfig.java
branches/7.4.x/src/main/java/javax/websocket/server/ServerContainer.java
branches/7.4.x/src/main/java/javax/websocket/server/ServerEndpoint.java
branches/7.4.x/src/main/java/javax/websocket/server/ServerEndpointConfig.java
Log:
It turns out the API classes are very specific, so bundle them here.
Added: branches/7.4.x/src/main/java/javax/websocket/ClientEndpoint.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/ClientEndpoint.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/ClientEndpoint.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,34 @@
+/*
+ * 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 javax.websocket;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.websocket.ClientEndpointConfig.Configurator;
+
+(a)Retention(RetentionPolicy.RUNTIME)
+(a)Target(ElementType.TYPE)
+public @interface ClientEndpoint {
+ String[] subprotocols() default {};
+ Class<? extends Decoder>[] decoders() default {};
+ Class<? extends Encoder>[] encoders() default {};
+ public Class<? extends Configurator> configurator()
+ default Configurator.class;
+}
Added: branches/7.4.x/src/main/java/javax/websocket/ClientEndpointConfig.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/ClientEndpointConfig.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/ClientEndpointConfig.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,138 @@
+/*
+ * 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 javax.websocket;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public interface ClientEndpointConfig extends EndpointConfig {
+
+ List<String> getPreferredSubprotocols();
+
+ List<Extension> getExtensions();
+
+ public Configurator getConfigurator();
+
+ public final class Builder {
+
+ private static final Configurator DEFAULT_CONFIGURATOR =
+ new Configurator() {};
+
+
+ public static Builder create() {
+ return new Builder();
+ }
+
+
+ private Builder() {
+ // Hide default constructor
+ }
+
+ private Configurator configurator = DEFAULT_CONFIGURATOR;
+ private List<String> preferredSubprotocols = Collections.emptyList();
+ private List<Extension> extensions = Collections.emptyList();
+ private List<Class<? extends Encoder>> encoders =
+ Collections.emptyList();
+ private List<Class<? extends Decoder>> decoders =
+ Collections.emptyList();
+
+
+ public ClientEndpointConfig build() {
+ return new DefaultClientEndpointConfig(preferredSubprotocols,
+ extensions, encoders, decoders, configurator);
+ }
+
+
+ public Builder configurator(Configurator configurator) {
+ if (configurator == null) {
+ this.configurator = DEFAULT_CONFIGURATOR;
+ } else {
+ this.configurator = configurator;
+ }
+ return this;
+ }
+
+
+ public Builder preferredSubprotocols(
+ List<String> preferredSubprotocols) {
+ if (preferredSubprotocols == null ||
+ preferredSubprotocols.size() == 0) {
+ this.preferredSubprotocols = Collections.emptyList();
+ } else {
+ this.preferredSubprotocols =
+ Collections.unmodifiableList(preferredSubprotocols);
+ }
+ return this;
+ }
+
+
+ public Builder extensions(
+ List<Extension> extensions) {
+ if (extensions == null || extensions.size() == 0) {
+ this.extensions = Collections.emptyList();
+ } else {
+ this.extensions = Collections.unmodifiableList(extensions);
+ }
+ return this;
+ }
+
+
+ public Builder encoders(List<Class<? extends Encoder>> encoders) {
+ if (encoders == null || encoders.size() == 0) {
+ this.encoders = Collections.emptyList();
+ } else {
+ this.encoders = Collections.unmodifiableList(encoders);
+ }
+ return this;
+ }
+
+
+ public Builder decoders(List<Class<? extends Decoder>> decoders) {
+ if (decoders == null || decoders.size() == 0) {
+ this.decoders = Collections.emptyList();
+ } else {
+ this.decoders = Collections.unmodifiableList(decoders);
+ }
+ return this;
+ }
+ }
+
+
+ public class Configurator {
+
+ /**
+ * Provides the client with a mechanism to inspect and/or modify the headers
+ * that are sent to the server to start the WebSocket handshake.
+ *
+ * @param headers The HTTP headers
+ */
+ public void beforeRequest(Map<String, List<String>> headers) {
+ // NO-OP
+ }
+
+ /**
+ * Provides the client with a mechanism to inspect the handshake response
+ * that is returned from the server.
+ *
+ * @param handshakeResponse The response
+ */
+ public void afterResponse(HandshakeResponse handshakeResponse) {
+ // NO-OP
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/CloseReason.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/CloseReason.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/CloseReason.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,122 @@
+/*
+ * 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 javax.websocket;
+
+public class CloseReason {
+
+ private final CloseCode closeCode;
+ private final String reasonPhrase;
+
+ public CloseReason(CloseReason.CloseCode closeCode, String reasonPhrase) {
+ this.closeCode = closeCode;
+ this.reasonPhrase = reasonPhrase;
+ }
+
+ public CloseCode getCloseCode() {
+ return closeCode;
+ }
+
+ public String getReasonPhrase() {
+ return reasonPhrase;
+ }
+
+ @Override
+ public String toString() {
+ return "CloseReason: code [" + closeCode.getCode() +
+ "], reason [" + reasonPhrase + "]";
+ }
+
+ public interface CloseCode {
+ int getCode();
+ }
+
+ public enum CloseCodes implements CloseReason.CloseCode {
+
+ NORMAL_CLOSURE(1000),
+ GOING_AWAY(1001),
+ PROTOCOL_ERROR(1002),
+ CANNOT_ACCEPT(1003),
+ RESERVED(1004),
+ NO_STATUS_CODE(1005),
+ CLOSED_ABNORMALLY(1006),
+ NOT_CONSISTENT(1007),
+ VIOLATED_POLICY(1008),
+ TOO_BIG(1009),
+ NO_EXTENSION(1010),
+ UNEXPECTED_CONDITION(1011),
+ SERVICE_RESTART(1012),
+ TRY_AGAIN_LATER(1013),
+ TLS_HANDSHAKE_FAILURE(1015);
+
+ private int code;
+
+ CloseCodes(int code) {
+ this.code = code;
+ }
+
+ public static CloseCode getCloseCode(final int code) {
+ if (code > 2999 && code < 5000) {
+ return new CloseCode() {
+ @Override
+ public int getCode() {
+ return code;
+ }
+ };
+ }
+ switch (code) {
+ case 1000:
+ return CloseCodes.NORMAL_CLOSURE;
+ case 1001:
+ return CloseCodes.GOING_AWAY;
+ case 1002:
+ return CloseCodes.PROTOCOL_ERROR;
+ case 1003:
+ return CloseCodes.CANNOT_ACCEPT;
+ case 1004:
+ return CloseCodes.RESERVED;
+ case 1005:
+ return CloseCodes.NO_STATUS_CODE;
+ case 1006:
+ return CloseCodes.CLOSED_ABNORMALLY;
+ case 1007:
+ return CloseCodes.NOT_CONSISTENT;
+ case 1008:
+ return CloseCodes.VIOLATED_POLICY;
+ case 1009:
+ return CloseCodes.TOO_BIG;
+ case 1010:
+ return CloseCodes.NO_EXTENSION;
+ case 1011:
+ return CloseCodes.UNEXPECTED_CONDITION;
+ case 1012:
+ return CloseCodes.SERVICE_RESTART;
+ case 1013:
+ return CloseCodes.TRY_AGAIN_LATER;
+ case 1015:
+ return CloseCodes.TLS_HANDSHAKE_FAILURE;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid close code: [" + code + "]");
+ }
+ }
+
+ @Override
+ public int getCode() {
+ return code;
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/ContainerProvider.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/ContainerProvider.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/ContainerProvider.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,64 @@
+/*
+ * 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 javax.websocket;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * Use the {@link ServiceLoader} mechanism to provide instances of the WebSocket
+ * client container.
+ */
+public abstract class ContainerProvider {
+
+ private static final String DEFAULT_PROVIDER_CLASS_NAME =
+ "org.apache.tomcat.websocket.WsWebSocketContainer";
+
+ /**
+ * Create a new container used to create outgoing WebSocket connections.
+ */
+ public static WebSocketContainer getWebSocketContainer() {
+ WebSocketContainer result = null;
+
+ ServiceLoader<ContainerProvider> serviceLoader =
+ ServiceLoader.load(ContainerProvider.class);
+ Iterator<ContainerProvider> iter = serviceLoader.iterator();
+ while (result == null && iter.hasNext()) {
+ result = iter.next().getContainer();
+ }
+
+ // Fall-back. Also used by unit tests
+ if (result == null) {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<WebSocketContainer> clazz =
+ (Class<WebSocketContainer>) Class.forName(
+ DEFAULT_PROVIDER_CLASS_NAME);
+ result = clazz.newInstance();
+ } catch (ClassNotFoundException e) {
+ // No options left. Just return null.
+ } catch (InstantiationException e) {
+ // No options left. Just return null.
+ } catch (IllegalAccessException e) {
+ // No options left. Just return null.
+ }
+ }
+ return result;
+ }
+
+ protected abstract WebSocketContainer getContainer();
+}
Added: branches/7.4.x/src/main/java/javax/websocket/DecodeException.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/DecodeException.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/DecodeException.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,56 @@
+/*
+ * 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 javax.websocket;
+
+import java.nio.ByteBuffer;
+
+public class DecodeException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private ByteBuffer bb;
+ private String encodedString;
+
+ public DecodeException(ByteBuffer bb, String message, Throwable cause) {
+ super(message, cause);
+ this.bb = bb;
+ }
+
+ public DecodeException(String encodedString, String message,
+ Throwable cause) {
+ super(message, cause);
+ this.encodedString = encodedString;
+ }
+
+ public DecodeException(ByteBuffer bb, String message) {
+ super(message);
+ this.bb = bb;
+ }
+
+ public DecodeException(String encodedString, String message) {
+ super(message);
+ this.encodedString = encodedString;
+ }
+
+ public ByteBuffer getBytes() {
+ return bb;
+ }
+
+ public String getText() {
+ return encodedString;
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/Decoder.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/Decoder.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/Decoder.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,53 @@
+/*
+ * 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 javax.websocket;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+
+public interface Decoder {
+
+ abstract void init(EndpointConfig endpointConfig);
+
+ abstract void destroy();
+
+ interface Binary<T> extends Decoder {
+
+ T decode(ByteBuffer bytes) throws DecodeException;
+
+ boolean willDecode(ByteBuffer bytes);
+ }
+
+ interface BinaryStream<T> extends Decoder {
+
+ T decode(InputStream is) throws DecodeException, IOException;
+ }
+
+ interface Text<T> extends Decoder {
+
+ T decode(String s) throws DecodeException;
+
+ boolean willDecode(String s);
+ }
+
+ interface TextStream<T> extends Decoder {
+
+ T decode(Reader reader) throws DecodeException, IOException;
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/DefaultClientEndpointConfig.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/DefaultClientEndpointConfig.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/DefaultClientEndpointConfig.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,80 @@
+/*
+ * 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 javax.websocket;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class DefaultClientEndpointConfig implements ClientEndpointConfig {
+
+ private final List<String> preferredSubprotocols;
+ private final List<Extension> extensions;
+ private final List<Class<? extends Encoder>> encoders;
+ private final List<Class<? extends Decoder>> decoders;
+ private final Map<String,Object> userProperties = new ConcurrentHashMap<String, Object>();
+ private final Configurator configurator;
+
+
+ DefaultClientEndpointConfig(List<String> preferredSubprotocols,
+ List<Extension> extensions,
+ List<Class<? extends Encoder>> encoders,
+ List<Class<? extends Decoder>> decoders,
+ Configurator configurator) {
+ this.preferredSubprotocols = preferredSubprotocols;
+ this.extensions = extensions;
+ this.decoders = decoders;
+ this.encoders = encoders;
+ this.configurator = configurator;
+ }
+
+
+ @Override
+ public List<String> getPreferredSubprotocols() {
+ return preferredSubprotocols;
+ }
+
+
+ @Override
+ public List<Extension> getExtensions() {
+ return extensions;
+ }
+
+
+ @Override
+ public List<Class<? extends Encoder>> getEncoders() {
+ return encoders;
+ }
+
+
+ @Override
+ public List<Class<? extends Decoder>> getDecoders() {
+ return decoders;
+ }
+
+
+ @Override
+ public final Map<String, Object> getUserProperties() {
+ return userProperties;
+ }
+
+
+ @Override
+ public Configurator getConfigurator() {
+ return configurator;
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/DeploymentException.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/DeploymentException.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/DeploymentException.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,30 @@
+/*
+ * 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 javax.websocket;
+
+public class DeploymentException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public DeploymentException(String message) {
+ super(message);
+ }
+
+ public DeploymentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/EncodeException.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/EncodeException.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/EncodeException.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,38 @@
+/*
+ * 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 javax.websocket;
+
+public class EncodeException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private Object object;
+
+ public EncodeException(Object object, String message) {
+ super(message);
+ this.object = object;
+ }
+
+ public EncodeException(Object object, String message, Throwable cause) {
+ super(message, cause);
+ this.object = object;
+ }
+
+ public Object getObject() {
+ return this.object;
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/Encoder.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/Encoder.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/Encoder.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,51 @@
+/*
+ * 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 javax.websocket;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+
+public interface Encoder {
+
+ abstract void init(EndpointConfig endpointConfig);
+
+ abstract void destroy();
+
+ interface Text<T> extends Encoder {
+
+ String encode(T object) throws EncodeException;
+ }
+
+ interface TextStream<T> extends Encoder {
+
+ void encode(T object, Writer writer)
+ throws EncodeException, IOException;
+ }
+
+ interface Binary<T> extends Encoder {
+
+ ByteBuffer encode(T object) throws EncodeException;
+ }
+
+ interface BinaryStream<T> extends Encoder {
+
+ void encode(T object, OutputStream os)
+ throws EncodeException, IOException;
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/Endpoint.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/Endpoint.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/Endpoint.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,47 @@
+/*
+ * 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 javax.websocket;
+
+public abstract class Endpoint {
+
+ /**
+ * Event that is triggered when a new session starts.
+ *
+ * @param session The new session.
+ */
+ public abstract void onOpen(Session session, EndpointConfig config);
+
+ /**
+ * Event that is triggered when a session has closed.
+ *
+ * @param session The session
+ * @param closeReason Why the session was closed
+ */
+ public void onClose(Session session, CloseReason closeReason) {
+ // NO-OP by default
+ }
+
+ /**
+ * Event that is triggered when a protocol error occurs.
+ *
+ * @param session The session
+ * @param throwable The exception
+ */
+ public void onError(Session session, Throwable throwable) {
+ // NO-OP by default
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/EndpointConfig.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/EndpointConfig.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/EndpointConfig.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,29 @@
+/*
+ * 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 javax.websocket;
+
+import java.util.List;
+import java.util.Map;
+
+public interface EndpointConfig {
+
+ List<Class<? extends Encoder>> getEncoders();
+
+ List<Class<? extends Decoder>> getDecoders();
+
+ Map<String,Object> getUserProperties();
+}
Added: branches/7.4.x/src/main/java/javax/websocket/Extension.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/Extension.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/Extension.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,29 @@
+/*
+ * 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 javax.websocket;
+
+import java.util.List;
+
+public interface Extension {
+ String getName();
+ List<Parameter> getParameters();
+
+ interface Parameter {
+ String getName();
+ String getValue();
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/HandshakeResponse.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/HandshakeResponse.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/HandshakeResponse.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,30 @@
+/*
+ * 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 javax.websocket;
+
+import java.util.List;
+import java.util.Map;
+
+public interface HandshakeResponse {
+
+ /**
+ * Name of the WebSocket accept HTTP header.
+ */
+ public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept";
+
+ Map<String,List<String>> getHeaders();
+}
Added: branches/7.4.x/src/main/java/javax/websocket/MessageHandler.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/MessageHandler.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/MessageHandler.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,42 @@
+/*
+ * 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 javax.websocket;
+
+public interface MessageHandler {
+
+ interface Partial<T> extends MessageHandler {
+
+ /**
+ * Called when part of a message is available to be processed.
+ *
+ * @param messagePart The message part
+ * @param last <code>true</code> if this is the last part of
+ * this message, else <code>false</code>
+ */
+ void onMessage(T messagePart, boolean last);
+ }
+
+ interface Whole<T> extends MessageHandler {
+
+ /**
+ * Called when a whole message is available to be processed.
+ *
+ * @param message The message
+ */
+ void onMessage(T message);
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/OnClose.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/OnClose.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/OnClose.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,27 @@
+/*
+ * 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 javax.websocket;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+(a)Retention(RetentionPolicy.RUNTIME)
+(a)Target(ElementType.METHOD)
+public @interface OnClose {
+}
Added: branches/7.4.x/src/main/java/javax/websocket/OnError.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/OnError.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/OnError.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,27 @@
+/*
+ * 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 javax.websocket;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+(a)Retention(RetentionPolicy.RUNTIME)
+(a)Target(ElementType.METHOD)
+public @interface OnError {
+}
Added: branches/7.4.x/src/main/java/javax/websocket/OnMessage.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/OnMessage.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/OnMessage.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,28 @@
+/*
+ * 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 javax.websocket;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+(a)Retention(RetentionPolicy.RUNTIME)
+(a)Target(ElementType.METHOD)
+public @interface OnMessage {
+ long maxMessageSize() default -1;
+}
Added: branches/7.4.x/src/main/java/javax/websocket/OnOpen.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/OnOpen.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/OnOpen.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,27 @@
+/*
+ * 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 javax.websocket;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+(a)Retention(RetentionPolicy.RUNTIME)
+(a)Target(ElementType.METHOD)
+public @interface OnOpen {
+}
Added: branches/7.4.x/src/main/java/javax/websocket/PongMessage.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/PongMessage.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/PongMessage.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,30 @@
+/*
+ * 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 javax.websocket;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Represents a WebSocket Pong message and used by message handlers to enable
+ * applications to process the response to any Pings they send.
+ */
+public interface PongMessage {
+ /**
+ * Obtain the payload of the Pong message as a ByteBuffer.
+ */
+ ByteBuffer getApplicationData();
+}
Added: branches/7.4.x/src/main/java/javax/websocket/RemoteEndpoint.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/RemoteEndpoint.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/RemoteEndpoint.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,173 @@
+/*
+ * 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 javax.websocket;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+
+public interface RemoteEndpoint {
+
+ interface Async extends RemoteEndpoint {
+
+ /**
+ * Obtain the timeout (in milliseconds) for sending a message
+ * asynchronously. A non-positive value means an infinite timeout. The
+ * default value is determined by
+ * {@link WebSocketContainer#getDefaultAsyncSendTimeout()}.
+ */
+ long getSendTimeout();
+
+ /**
+ * Set the timeout (in milliseconds) for sending a message asynchronously. A
+ * non-positive value means an infinite timeout. The default value is
+ * determined by {@link WebSocketContainer#getDefaultAsyncSendTimeout()}.
+ */
+ void setSendTimeout(long timeout);
+
+ /**
+ * Send the message asynchronously, using the SendHandler to signal to the
+ * client when the message has been sent.
+ * @param text The text message to send
+ * @param completion Used to signal to the client when the message has
+ * been sent
+ */
+ void sendText(String text, SendHandler completion);
+
+ /**
+ * Send the message asynchronously, using the Future to signal to the client
+ * when the message has been sent.
+ * @param text The text message to send
+ */
+ Future<Void> sendText(String text);
+
+ /**
+ * Send the message asynchronously, using the Future to signal to the client
+ * when the message has been sent.
+ * @param data The text message to send
+ */
+ Future<Void> sendBinary(ByteBuffer data);
+
+ /**
+ * Send the message asynchronously, using the SendHandler to signal to the
+ * client when the message has been sent.
+ * @param data The text message to send
+ * @param completion Used to signal to the client when the message has
+ * been sent
+ */
+ void sendBinary(ByteBuffer data, SendHandler completion);
+
+ Future<Void> sendObject(Object obj);
+
+ void sendObject(Object obj, SendHandler completion);
+
+ }
+
+ interface Basic extends RemoteEndpoint {
+
+ /**
+ * Send the message, blocking until the message is sent.
+ * @param text The text message to send.
+ * @throws IOException
+ */
+ void sendText(String text) throws IOException;
+
+ /**
+ * Send the message, blocking until the message is sent.
+ * @param data The binary message to send
+ * @throws IOException
+ */
+ void sendBinary(ByteBuffer data) throws IOException;
+
+ /**
+ * Sends part of a text message to the remote endpoint. Once the first part
+ * of a message has been sent, no other text or binary messages may be sent
+ * until all remaining parts of this message have been sent.
+ *
+ * @param fragment The partial message to send
+ * @param isLast <code>true</code> if this is the last part of the
+ * message, otherwise <code>false</code>
+ * @throws IOException
+ */
+ void sendText(String fragment, boolean isLast) throws IOException;
+
+ /**
+ * Sends part of a binary message to the remote endpoint. Once the first
+ * part of a message has been sent, no other text or binary messages may be
+ * sent until all remaining parts of this message have been sent.
+ *
+ * @param partialByte The partial message to send
+ * @param isLast <code>true</code> if this is the last part of the
+ * message, otherwise <code>false</code>
+ * @throws IOException
+ */
+ void sendBinary(ByteBuffer partialByte, boolean isLast) throws IOException;
+
+ OutputStream getSendStream() throws IOException;
+
+ Writer getSendWriter() throws IOException;
+
+ void sendObject(Object o) throws IOException, EncodeException;
+
+ }
+ /**
+ * Enable or disable the batching of outgoing messages for this endpoint. If
+ * batching is disabled when it was previously enabled then this method will
+ * block until any currently batched messages have been written.
+ *
+ * @param batchingAllowed New setting
+ * @throws IOException If changing the value resulted in a call to
+ * {@link #flushBatch()} and that call threw an
+ * {@link IOException}.
+ */
+ void setBatchingAllowed(boolean batchingAllowed) throws IOException;
+
+ /**
+ * Obtains the current batching status of the endpoint.
+ */
+ boolean getBatchingAllowed();
+
+ /**
+ * Flush any currently batched messages to the remote endpoint. This method
+ * will block until the flush completes.
+ */
+ void flushBatch() throws IOException;
+
+ /**
+ * Send a ping message blocking until the message has been sent. Note that
+ * if a message is in the process of being sent asynchronously, this method
+ * will block until that message and this ping has been sent.
+ *
+ * @param applicationData The payload for the ping message
+ */
+ void sendPing(ByteBuffer applicationData)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Send a pong message blocking until the message has been sent. Note that
+ * if a message is in the process of being sent asynchronously, this method
+ * will block until that message and this pong has been sent.
+ *
+ * @param applicationData The payload for the pong message
+ */
+ void sendPong(ByteBuffer applicationData)
+ throws IOException, IllegalArgumentException;
+}
+
Added: branches/7.4.x/src/main/java/javax/websocket/SendHandler.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/SendHandler.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/SendHandler.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,22 @@
+/*
+ * 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 javax.websocket;
+
+public interface SendHandler {
+
+ void onResult(SendResult result);
+}
Added: branches/7.4.x/src/main/java/javax/websocket/SendResult.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/SendResult.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/SendResult.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,39 @@
+/*
+ * 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 javax.websocket;
+
+public final class SendResult {
+ private Throwable exception;
+ private boolean ok = true;
+
+ public SendResult(Throwable exception) {
+ this.exception = exception;
+ this.ok = false;
+ }
+
+ public SendResult() {
+ // NO-OP
+ }
+
+ public Throwable getException() {
+ return exception;
+ }
+
+ public boolean isOK() {
+ return ok;
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/Session.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/Session.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/Session.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,129 @@
+/*
+ * 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 javax.websocket;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URI;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface Session extends Closeable {
+
+ /**
+ * Returns the container that created this session.
+ */
+ WebSocketContainer getContainer();
+
+ void addMessageHandler(MessageHandler listener)
+ throws IllegalStateException;
+
+ Set<MessageHandler> getMessageHandlers();
+
+ void removeMessageHandler(MessageHandler listener);
+
+ String getProtocolVersion();
+
+ String getNegotiatedSubprotocol();
+
+ List<Extension> getNegotiatedExtensions();
+
+ boolean isSecure();
+
+ boolean isOpen();
+
+ /**
+ * Get the idle timeout for this session in milliseconds. Zero or negative
+ * values indicate an infinite timeout.
+ */
+ long getMaxIdleTimeout();
+
+ /**
+ * Set the idle timeout for this session in milliseconds. Zero or negative
+ * values indicate an infinite timeout.
+ */
+ void setMaxIdleTimeout(long seconds);
+
+ /**
+ * Set the current maximum buffer size (in bytes) for binary messages.
+ */
+ void setMaxBinaryMessageBufferSize(int max);
+
+ /**
+ * Get the current maximum buffer size (in bytes) for binary messages.
+ */
+ int getMaxBinaryMessageBufferSize();
+
+ /**
+ * Set the current maximum buffer size (in characters) for text messages.
+ */
+ void setMaxTextMessageBufferSize(int max);
+
+ /**
+ * Get the current maximum buffer size (in characters) for text messages.
+ */
+ int getMaxTextMessageBufferSize();
+
+ RemoteEndpoint.Async getAsyncRemote();
+
+ RemoteEndpoint.Basic getBasicRemote();
+
+ /**
+ * Provides a unique identifier for the session. This identifier should not
+ * be relied upon to be generated from a secure random source.
+ */
+ String getId();
+
+ /**
+ * Close the connection to the remote end point using the code
+ * {@link javax.websocket.CloseReason.CloseCodes#NORMAL_CLOSURE} and an
+ * empty reason phrase.
+ *
+ * @throws IOException
+ */
+ @Override
+ void close() throws IOException;
+
+
+ /**
+ * Close the connection to the remote end point using the specified code
+ * and reason phrase.
+ *
+ * @throws IOException
+ */
+ void close(CloseReason closeStatus) throws IOException;
+
+ URI getRequestURI();
+
+ Map<String, List<String>> getRequestParameterMap();
+
+ String getQueryString();
+
+ Map<String,String> getPathParameters();
+
+ Map<String,Object> getUserProperties();
+
+ Principal getUserPrincipal();
+
+ /**
+ * Obtain the set of currently open sessions for the local endpoint that
+ * this session is associated with.
+ */
+ Set<Session> getOpenSessions();
+}
Added: branches/7.4.x/src/main/java/javax/websocket/SessionException.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/SessionException.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/SessionException.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,35 @@
+/*
+ * 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 javax.websocket;
+
+public class SessionException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Session session;
+
+
+ public SessionException(String message, Throwable cause, Session session) {
+ super(message, cause);
+ this.session = session;
+ }
+
+
+ public Session getSession() {
+ return session;
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/WebSocketContainer.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/WebSocketContainer.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/WebSocketContainer.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,118 @@
+/*
+ * 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 javax.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Set;
+
+public interface WebSocketContainer {
+
+ /**
+ * Obtain the default timeout (in milliseconds) for sending a message
+ * asynchronously. A non-positive value means an infinite timeout.
+ */
+ long getDefaultAsyncSendTimeout();
+
+ /**
+ * Set the default timeout (in milliseconds) for sending a message
+ * asynchronously. A non-positive value means an infinite timeout.
+ */
+ void setAsyncSendTimeout(long timeout);
+
+ Session connectToServer(Object endpoint, URI path)
+ throws DeploymentException, IOException;
+
+ Session connectToServer(Class<?> annotatedEndpointClass, URI path)
+ throws DeploymentException, IOException;
+
+ /**
+ * Creates a new connection to the WebSocket.
+ *
+ * @param endpoint
+ * The endpoint instance that will handle responses from the
+ * server
+ * @param clientEndpointConfiguration
+ * Used to configure the new connection
+ * @param path
+ * The full URL of the WebSocket endpoint to connect to
+ *
+ * @return The WebSocket session for the connection
+ *
+ * @throws DeploymentException If the connection can not be established
+ */
+ Session connectToServer(Endpoint endpoint,
+ ClientEndpointConfig clientEndpointConfiguration, URI path)
+ throws DeploymentException, IOException;
+
+ /**
+ * Creates a new connection to the WebSocket.
+ *
+ * @param endpoint
+ * An instance of this class will be created to handle responses
+ * from the server
+ * @param clientEndpointConfiguration
+ * Used to configure the new connection
+ * @param path
+ * The full URL of the WebSocket endpoint to connect to
+ *
+ * @return The WebSocket session for the connection
+ *
+ * @throws DeploymentException If the connection can not be established
+ */
+ Session connectToServer(Class<? extends Endpoint> endpoint,
+ ClientEndpointConfig clientEndpointConfiguration, URI path)
+ throws DeploymentException, IOException;
+
+ /**
+ * Get the current default session idle timeout in milliseconds. Zero or
+ * negative values indicate an infinite timeout.
+ */
+ long getDefaultMaxSessionIdleTimeout();
+
+ /**
+ * Set the current default session idle timeout in milliseconds. Zero or
+ * negative values indicate an infinite timeout.
+ */
+ void setDefaultMaxSessionIdleTimeout(long timeout);
+
+ /**
+ * Get the default maximum buffer size (in bytes) for binary messages.
+ */
+ int getDefaultMaxBinaryMessageBufferSize();
+
+ /**
+ * Set the default maximum buffer size (in bytes) for binary messages.
+ */
+ void setDefaultMaxBinaryMessageBufferSize(int max);
+
+ /**
+ * Get the default maximum buffer size (in characters) for text messages.
+ */
+ int getDefaultMaxTextMessageBufferSize();
+
+ /**
+ * Set the default maximum buffer size (in characters) for text messages.
+ */
+ void setDefaultMaxTextMessageBufferSize(int max);
+
+ /**
+ * Get the set of extensions that are supported by this WebSocket
+ * implementation.
+ */
+ Set<Extension> getInstalledExtensions();
+}
Added: branches/7.4.x/src/main/java/javax/websocket/server/DefaultServerEndpointConfig.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/server/DefaultServerEndpointConfig.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/server/DefaultServerEndpointConfig.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,95 @@
+/*
+ * 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 javax.websocket.server;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.Extension;
+
+/**
+ * Provides the default configuration for WebSocket server endpoints.
+ */
+final class DefaultServerEndpointConfig implements ServerEndpointConfig {
+
+ private final Class<?> endpointClass;
+ private final String path;
+ private final List<String> subprotocols;
+ private final List<Extension> extensions;
+ private final List<Class<? extends Encoder>> encoders;
+ private final List<Class<? extends Decoder>> decoders;
+ private final Configurator serverEndpointConfigurator;
+ private final Map<String,Object> userProperties = new ConcurrentHashMap<String, Object>();
+
+ DefaultServerEndpointConfig(
+ Class<?> endpointClass, String path,
+ List<String> subprotocols, List<Extension> extensions,
+ List<Class<? extends Encoder>> encoders,
+ List<Class<? extends Decoder>> decoders,
+ Configurator serverEndpointConfigurator) {
+ this.endpointClass = endpointClass;
+ this.path = path;
+ this.subprotocols = subprotocols;
+ this.extensions = extensions;
+ this.encoders = encoders;
+ this.decoders = decoders;
+ this.serverEndpointConfigurator = serverEndpointConfigurator;
+ }
+
+ @Override
+ public Class<?> getEndpointClass() {
+ return endpointClass;
+ }
+
+ @Override
+ public List<Class<? extends Encoder>> getEncoders() {
+ return this.encoders;
+ }
+
+ @Override
+ public List<Class<? extends Decoder>> getDecoders() {
+ return this.decoders;
+ }
+
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public Configurator getConfigurator() {
+ return serverEndpointConfigurator;
+ }
+
+ @Override
+ public final Map<String, Object> getUserProperties() {
+ return userProperties;
+ }
+
+ @Override
+ public final List<String> getSubprotocols() {
+ return subprotocols;
+ }
+
+ @Override
+ public final List<Extension> getExtensions() {
+ return extensions;
+ }
+}
Added: branches/7.4.x/src/main/java/javax/websocket/server/HandshakeRequest.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/server/HandshakeRequest.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/server/HandshakeRequest.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,51 @@
+/*
+ * 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 javax.websocket.server;
+
+import java.net.URI;
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents the HTTP request that asked to be upgraded to WebSocket.
+ */
+public interface HandshakeRequest {
+
+ static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key";
+ static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
+ static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version";
+ static final String SEC_WEBSOCKET_EXTENSIONS= "Sec-WebSocket-Extensions";
+
+ Map<String,List<String>> getHeaders();
+
+ Principal getUserPrincipal();
+
+ URI getRequestURI();
+
+ boolean isUserInRole(String role);
+
+ /**
+ * Get the HTTP Session object associated with this request. Object is used
+ * to avoid a direct dependency on the Servlet API.
+ */
+ Object getHttpSession();
+
+ Map<String, List<String>> getParameterMap();
+
+ String getQueryString();
+}
Added: branches/7.4.x/src/main/java/javax/websocket/server/PathParam.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/server/PathParam.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/server/PathParam.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,33 @@
+/*
+ * 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 javax.websocket.server;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to annotate method parameters on POJO endpoints the the {@link
+ * ServerEndpoint} has been defined with a {@link ServerEndpoint#value()} that
+ * uses a URI template.
+ */
+(a)Retention(RetentionPolicy.RUNTIME)
+(a)Target(ElementType.PARAMETER)
+public @interface PathParam {
+ String value();
+}
Added: branches/7.4.x/src/main/java/javax/websocket/server/ServerApplicationConfig.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/server/ServerApplicationConfig.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/server/ServerApplicationConfig.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,51 @@
+/*
+ * 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 javax.websocket.server;
+
+import java.util.Set;
+
+import javax.websocket.Endpoint;
+
+/**
+ * Applications may provide an implementation of this interface to filter the
+ * discovered WebSocket endpoints that are deployed. Implementations of this
+ * class will be discovered via an ServletContainerInitializer scan.
+ */
+public interface ServerApplicationConfig {
+
+ /**
+ * Enables applications to filter the discovered implementations of
+ * {@link ServerEndpointConfig}.
+ *
+ * @param scanned The {@link Endpoint} implementations found in the
+ * application
+ * @return The set of configurations for the endpoint the application
+ * wishes to deploy
+ */
+ Set<ServerEndpointConfig> getEndpointConfigs(
+ Set<Class<? extends Endpoint>> scanned);
+
+ /**
+ * Enables applications to filter the discovered classes annotated with
+ * {@link ServerEndpoint}.
+ *
+ * @param scanned The POJOs annotated with {@link ServerEndpoint} found in
+ * the application
+ * @return The set of POJOs the application wishes to deploy
+ */
+ Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned);
+}
Added: branches/7.4.x/src/main/java/javax/websocket/server/ServerContainer.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/server/ServerContainer.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/server/ServerContainer.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,30 @@
+/*
+ * 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 javax.websocket.server;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.WebSocketContainer;
+
+/**
+ * Provides the ability to deploy endpoints programmatically.
+ */
+public interface ServerContainer extends WebSocketContainer {
+ public abstract void addEndpoint(Class<?> clazz) throws DeploymentException;
+
+ public abstract void addEndpoint(ServerEndpointConfig sec)
+ throws DeploymentException;
+}
Added: branches/7.4.x/src/main/java/javax/websocket/server/ServerEndpoint.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/server/ServerEndpoint.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/server/ServerEndpoint.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,44 @@
+/*
+ * 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 javax.websocket.server;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+
+(a)Retention(RetentionPolicy.RUNTIME)
+(a)Target(ElementType.TYPE)
+public @interface ServerEndpoint {
+
+ /**
+ * URI or URI-template that the annotated class should be mapped to.
+ */
+ String value();
+
+ String[] subprotocols() default {};
+
+ Class<? extends Decoder>[] decoders() default {};
+
+ Class<? extends Encoder>[] encoders() default {};
+
+ public Class<? extends ServerEndpointConfig.Configurator> configurator()
+ default ServerEndpointConfig.Configurator.class;
+}
Added: branches/7.4.x/src/main/java/javax/websocket/server/ServerEndpointConfig.java
===================================================================
--- branches/7.4.x/src/main/java/javax/websocket/server/ServerEndpointConfig.java (rev 0)
+++ branches/7.4.x/src/main/java/javax/websocket/server/ServerEndpointConfig.java 2013-09-23 15:21:13 UTC (rev 2262)
@@ -0,0 +1,214 @@
+/*
+ * 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 javax.websocket.server;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+
+import javax.websocket.Decoder;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+
+/**
+ * Provides configuration information for WebSocket endpoints published to a
+ * server. Applications may provide their own implementation or use
+ * {@link Builder}.
+ */
+public interface ServerEndpointConfig extends EndpointConfig {
+
+ Class<?> getEndpointClass();
+
+ /**
+ * Returns the path at which this WebSocket server endpoint has been
+ * registered. It may be a path or a level 0 URI template.
+ */
+ String getPath();
+
+ List<String> getSubprotocols();
+
+ List<Extension> getExtensions();
+
+ Configurator getConfigurator();
+
+
+ public final class Builder {
+
+ public static Builder create(
+ Class<?> endpointClass, String path) {
+ return new Builder(endpointClass, path);
+ }
+
+
+ private final Class<?> endpointClass;
+ private final String path;
+ private List<Class<? extends Encoder>> encoders =
+ Collections.emptyList();
+ private List<Class<? extends Decoder>> decoders =
+ Collections.emptyList();
+ private List<String> subprotocols = Collections.emptyList();
+ private List<Extension> extensions = Collections.emptyList();
+ private Configurator configurator =
+ Configurator.fetchContainerDefaultConfigurator();
+
+
+ private Builder(Class<?> endpointClass,
+ String path) {
+ this.endpointClass = endpointClass;
+ this.path = path;
+ }
+
+ public ServerEndpointConfig build() {
+ return new DefaultServerEndpointConfig(endpointClass, path,
+ subprotocols, extensions, encoders, decoders, configurator);
+ }
+
+
+ public Builder encoders(
+ List<Class<? extends Encoder>> encoders) {
+ if (encoders == null || encoders.size() == 0) {
+ this.encoders = Collections.emptyList();
+ } else {
+ this.encoders = Collections.unmodifiableList(encoders);
+ }
+ return this;
+ }
+
+
+ public Builder decoders(
+ List<Class<? extends Decoder>> decoders) {
+ if (decoders == null || decoders.size() == 0) {
+ this.decoders = Collections.emptyList();
+ } else {
+ this.decoders = Collections.unmodifiableList(decoders);
+ }
+ return this;
+ }
+
+
+ public Builder subprotocols(
+ List<String> subprotocols) {
+ if (subprotocols == null || subprotocols.size() == 0) {
+ this.subprotocols = Collections.emptyList();
+ } else {
+ this.subprotocols = Collections.unmodifiableList(subprotocols);
+ }
+ return this;
+ }
+
+
+ public Builder extensions(
+ List<Extension> extensions) {
+ if (extensions == null || extensions.size() == 0) {
+ this.extensions = Collections.emptyList();
+ } else {
+ this.extensions = Collections.unmodifiableList(extensions);
+ }
+ return this;
+ }
+
+
+ public Builder configurator(Configurator serverEndpointConfigurator) {
+ if (serverEndpointConfigurator == null) {
+ this.configurator = Configurator.fetchContainerDefaultConfigurator();
+ } else {
+ this.configurator = serverEndpointConfigurator;
+ }
+ return this;
+ }
+ }
+
+
+ public class Configurator {
+
+ private static volatile Configurator defaultImpl = null;
+ private static final Object defaultImplLock = new Object();
+
+ private static final String DEFAULT_IMPL_CLASSNAME =
+ "org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator";
+
+ static Configurator fetchContainerDefaultConfigurator() {
+ if (defaultImpl == null) {
+ synchronized (defaultImplLock) {
+ if (defaultImpl == null) {
+ defaultImpl = loadDefault();
+ }
+ }
+ }
+ return defaultImpl;
+ }
+
+
+ private static Configurator loadDefault() {
+ Configurator result = null;
+
+ ServiceLoader<Configurator> serviceLoader =
+ ServiceLoader.load(Configurator.class);
+
+ Iterator<Configurator> iter = serviceLoader.iterator();
+ while (result == null && iter.hasNext()) {
+ result = iter.next();
+ }
+
+ // Fall-back. Also used by unit tests
+ if (result == null) {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<Configurator> clazz =
+ (Class<Configurator>) Class.forName(
+ DEFAULT_IMPL_CLASSNAME);
+ result = clazz.newInstance();
+ } catch (ClassNotFoundException e) {
+ // No options left. Just return null.
+ } catch (InstantiationException e) {
+ // No options left. Just return null.
+ } catch (IllegalAccessException e) {
+ // No options left. Just return null.
+ }
+ }
+ return result;
+ }
+
+ public String getNegotiatedSubprotocol(List<String> supported,
+ List<String> requested) {
+ return fetchContainerDefaultConfigurator().getNegotiatedSubprotocol(supported, requested);
+ }
+
+ public List<Extension> getNegotiatedExtensions(List<Extension> installed,
+ List<Extension> requested) {
+ return fetchContainerDefaultConfigurator().getNegotiatedExtensions(installed, requested);
+ }
+
+ public boolean checkOrigin(String originHeaderValue) {
+ return fetchContainerDefaultConfigurator().checkOrigin(originHeaderValue);
+ }
+
+ public void modifyHandshake(ServerEndpointConfig sec,
+ HandshakeRequest request, HandshakeResponse response) {
+ fetchContainerDefaultConfigurator().modifyHandshake(sec, request, response);
+ }
+
+ public <T extends Object> T getEndpointInstance(Class<T> clazz)
+ throws InstantiationException {
+ return fetchContainerDefaultConfigurator().getEndpointInstance(
+ clazz);
+ }
+ }
+}
11 years, 3 months
JBossWeb SVN: r2261 - in branches/7.4.x/src/main/java/org: apache/catalina/core and 14 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-20 12:27:05 -0400 (Fri, 20 Sep 2013)
New Revision: 2261
Added:
branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/
branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java
branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java
branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/
branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/HttpUpgradeHandler.java
branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/ReadListener.java
branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/WebConnection.java
branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/WriteListener.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/ExceptionUtils.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/buf/Utf8Encoder.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/BinaryDecoder.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/BinaryEncoder.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/Decoder.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/DecoderException.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/Encoder.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/EncoderException.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/Base64.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/StringUtils.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/package.html
branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/
branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/Constants.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/TaskQueue.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/TaskThread.java
branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/BackgroundProcess.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/BackgroundProcessManager.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/Constants.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/DecoderEntry.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/FutureToSendHandler.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/MessageHandlerResult.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/MessageHandlerResultType.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/ReadBufferOverflowException.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/SendHandlerToCompletionHandler.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/Util.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WrappedMessageHandler.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsContainerProvider.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsFrameBase.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsFrameClient.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsIOException.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsPongMessage.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointImplClient.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsSession.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/Constants.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointServer.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBase.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBinary.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialText.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBase.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBinary.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholePong.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeText.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoPathParam.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/package-info.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/Constants.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UriTemplate.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsContextListener.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsFilter.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsFrameServer.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsMappingResult.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsSci.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsServerContainer.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsSessionListener.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsWriteTimeout.java
branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/package-info.java
branches/7.4.x/src/main/java/org/jboss/web/WebsocketsLogger.java
branches/7.4.x/src/main/java/org/jboss/web/WebsocketsMessages.java
Removed:
branches/7.4.x/src/main/java/org/apache/tomcat/util/buf/UTF8Decoder.java
Modified:
branches/7.4.x/src/main/java/org/apache/catalina/connector/Connector.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteInputStream.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteOutputStream.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/HttpEventImpl.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/InputBuffer.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/OutputBuffer.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/Request.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/RequestFacade.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/Response.java
branches/7.4.x/src/main/java/org/apache/catalina/connector/ResponseFacade.java
branches/7.4.x/src/main/java/org/apache/catalina/core/StandardWrapperValve.java
branches/7.4.x/src/main/java/org/apache/coyote/ActionCode.java
branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11AprProcessor.java
branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11NioProcessor.java
branches/7.4.x/src/main/java/org/jboss/web/CatalinaMessages.java
branches/7.4.x/src/main/java/org/jboss/web/CoyoteMessages.java
Log:
- Add Servlet 3.1 IO API from Tomcat 7.
- Port Websockets 1.0 from Tomcat 7.
- No Websockets 1.0 testing yet (but no apparent regression for the usual Servlet functionality).
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/Connector.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/Connector.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/Connector.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -588,7 +588,7 @@
}
} else {
if ("HTTP/1.1".equals(protocol) || "http".equals(protocol)) {
- /*try {
+ try {
Class.forName("java.nio.channels.CompletionHandler");
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11NioProtocol");
@@ -597,9 +597,7 @@
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11Protocol");
CatalinaLogger.CONNECTOR_LOGGER.usingJavaIoConnector();
- }*/
- setProtocolHandlerClassName
- ("org.apache.coyote.http11.Http11Protocol");
+ }
} else if ("AJP/1.3".equals(protocol) || "ajp".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.ajp.AjpProtocol");
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteInputStream.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteInputStream.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteInputStream.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -23,9 +23,9 @@
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
-import javax.servlet.ServletInputStream;
-
import org.apache.catalina.security.SecurityUtil;
+import org.apache.coyote.http11.upgrade.AbstractServletInputStream;
+import org.apache.coyote.http11.upgrade.servlet31.ReadListener;
/**
* This class handles reading bytes.
@@ -34,7 +34,7 @@
* @author Jean-Francois Arcand
*/
public class CoyoteInputStream
- extends ServletInputStream {
+ extends AbstractServletInputStream {
// ----------------------------------------------------- Instance Variables
@@ -231,4 +231,18 @@
}
}
+ public boolean isFinished() {
+ return ib.isEof();
+ }
+
+ public boolean isReady() {
+ return (ib.available() > 0);
+ }
+
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+ ib.setReadListener(readListener);
+ }
+
}
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteOutputStream.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteOutputStream.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/CoyoteOutputStream.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -20,7 +20,8 @@
import java.io.IOException;
-import javax.servlet.ServletOutputStream;
+import org.apache.coyote.http11.upgrade.AbstractServletOutputStream;
+import org.apache.coyote.http11.upgrade.servlet31.WriteListener;
/**
* Coyote implementation of the servlet output stream.
@@ -29,7 +30,7 @@
* @author Remy Maucherat
*/
public class CoyoteOutputStream
- extends ServletOutputStream {
+ extends AbstractServletOutputStream {
// ----------------------------------------------------- Instance Variables
@@ -105,5 +106,14 @@
}
+ public boolean isReady() {
+ return (ob.lastWrite() > 0);
+ }
+
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+ ob.setWriteListener(writeListener);
+ }
+
}
-
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/HttpEventImpl.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/HttpEventImpl.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/HttpEventImpl.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -23,9 +23,12 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.apache.coyote.http11.upgrade.AbstractServletInputStream;
+import org.apache.coyote.http11.upgrade.AbstractServletOutputStream;
+import org.apache.coyote.http11.upgrade.servlet31.WebConnection;
import org.jboss.servlet.http.HttpEvent;
-public class HttpEventImpl implements HttpEvent {
+public class HttpEventImpl implements HttpEvent, WebConnection {
public HttpEventImpl(Request request, Response response) {
this.request = request;
@@ -105,6 +108,14 @@
request.suspend();
}
+ public AbstractServletInputStream getInputStream() throws IOException {
+ return (AbstractServletInputStream) request.getInputStream();
+ }
+
+ public AbstractServletOutputStream getOutputStream() throws IOException {
+ return (AbstractServletOutputStream) response.getOutputStream();
+ }
+
public String toString() {
StringBuilder buf = new StringBuilder("HttpEventImpl[");
buf.append(super.toString());
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/InputBuffer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/InputBuffer.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/InputBuffer.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -30,6 +30,7 @@
import org.apache.catalina.security.SecurityUtil;
import org.apache.coyote.ActionCode;
import org.apache.coyote.Request;
+import org.apache.coyote.http11.upgrade.servlet31.ReadListener;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.CharChunk;
@@ -143,6 +144,12 @@
private int size = -1;
+ /**
+ * Read listener.
+ */
+ private ReadListener readListener = null;
+
+
// ----------------------------------------------------------- Constructors
@@ -229,7 +236,8 @@
gotEnc = false;
enc = null;
-
+ readListener = null;
+
}
@@ -575,4 +583,22 @@
}
+ public ReadListener getReadListener() {
+ return readListener;
+ }
+
+ public void setReadListener(ReadListener readListener) {
+ if (this.readListener != null) {
+ throw MESSAGES.readListenerAlreadySet();
+ }
+ if (readListener == null) {
+ throw MESSAGES.nullListener();
+ }
+ if (!request.isEventMode()) {
+ throw MESSAGES.cannotSetListenerWithoutUpgradeOrAsync();
+ }
+ this.readListener = readListener;
+ coyoteRequest.action(ActionCode.ACTION_EVENT_READ_BEGIN, null);
+ }
+
}
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/OutputBuffer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/OutputBuffer.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/OutputBuffer.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -18,6 +18,8 @@
package org.apache.catalina.connector;
+import static org.jboss.web.CatalinaMessages.MESSAGES;
+
import java.io.IOException;
import java.io.Writer;
import java.security.AccessController;
@@ -28,6 +30,7 @@
import org.apache.coyote.ActionCode;
import org.apache.coyote.Response;
+import org.apache.coyote.http11.upgrade.servlet31.WriteListener;
import org.apache.catalina.Globals;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.C2BConverter;
@@ -143,20 +146,32 @@
/**
+ * Associated request.
+ */
+ private org.apache.catalina.connector.Response response;
+
+
+ /**
* Suspended flag. All output bytes will be swallowed if this is true.
*/
private boolean suspended = false;
+ /**
+ * Write listener.
+ */
+ private WriteListener writeListener = null;
+
+
// ----------------------------------------------------------- Constructors
/**
* Default constructor. Allocate the buffer with the default buffer size.
*/
- public OutputBuffer() {
+ public OutputBuffer(org.apache.catalina.connector.Response response) {
- this(DEFAULT_BUFFER_SIZE);
+ this(response, DEFAULT_BUFFER_SIZE);
}
@@ -166,8 +181,9 @@
*
* @param size Buffer size to use
*/
- public OutputBuffer(int size) {
+ public OutputBuffer(org.apache.catalina.connector.Response response, int size) {
+ this.response = response;
bb = new ByteChunk(size);
bb.setLimit(size);
bb.setByteOutputChannel(this);
@@ -257,6 +273,7 @@
gotEnc = false;
enc = null;
+ writeListener = null;
}
@@ -662,5 +679,22 @@
return bb.getLimit();
}
+ public WriteListener getWriteListener() {
+ return writeListener;
+ }
+ public void setWriteListener(WriteListener writeListener) {
+ if (this.writeListener != null) {
+ throw MESSAGES.writeListenerAlreadySet();
+ }
+ if (writeListener == null) {
+ throw MESSAGES.nullListener();
+ }
+ if (!response.getRequest().isEventMode()) {
+ throw MESSAGES.cannotSetListenerWithoutUpgradeOrAsync();
+ }
+ this.writeListener = writeListener;
+ coyoteResponse.action(ActionCode.ACTION_EVENT_WRITE_BEGIN, null);
+ }
+
}
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/Request.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/Request.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/Request.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -83,6 +83,8 @@
import org.apache.catalina.util.ParameterMap;
import org.apache.catalina.util.StringParser;
import org.apache.coyote.ActionCode;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
+import org.apache.coyote.http11.upgrade.servlet31.ReadListener;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.EncodingToCharset;
@@ -487,6 +489,12 @@
protected LinkedList<AsyncListener> asyncListenerInstances = new LinkedList<AsyncListener>();
+ /**
+ * Upgrade handler.
+ */
+ protected HttpUpgradeHandler upgradeHandler = null;
+
+
// --------------------------------------------------------- Public Methods
@@ -518,7 +526,8 @@
event.clear();
event = null;
}
-
+ upgradeHandler = null;
+
sslAttributes = false;
asyncContext = null;
asyncTimeout = -1;
@@ -3295,6 +3304,38 @@
}
+ public long getContentLengthLong() {
+ return (coyoteRequest.getContentLengthLong());
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends HttpUpgradeHandler> T upgrade(Class<T> upgradeHandlerClass)
+ throws IOException {
+ T ugradeHandler = null;
+ Throwable upgradeError = null;
+ try {
+ ugradeHandler = (T) context.getInstanceManager().newInstance(upgradeHandlerClass);
+ } catch (Throwable t) {
+ upgradeError = t;
+ }
+ if (ugradeHandler == null) {
+ throw new IOException(MESSAGES.upgradeError(), upgradeError);
+ }
+ response.sendUpgrade();
+ eventMode = true;
+ ugradeHandler.init(getEvent());
+ this.upgradeHandler = ugradeHandler;
+ return ugradeHandler;
+ }
+
+ public HttpUpgradeHandler getUpgradeHandler() {
+ return upgradeHandler;
+ }
+
+ public ReadListener getReadListener() {
+ return inputBuffer.getReadListener();
+ }
+
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("Current Servlet stack for thread ").append(Thread.currentThread().getName());
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/RequestFacade.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/RequestFacade.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/RequestFacade.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -46,6 +46,7 @@
import org.apache.catalina.Globals;
import org.apache.catalina.core.ApplicationFilterChain;
import org.apache.catalina.security.SecurityUtil;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
/**
* Facade class that wraps a Coyote request object.
@@ -1044,4 +1045,21 @@
return request.hasSendfile();
}
+ public long getContentLengthLong() {
+ if (request == null) {
+ throw MESSAGES.nullRequestFacade();
+ }
+
+ return request.getContentLengthLong();
+ }
+
+ public <T extends HttpUpgradeHandler> T upgrade(Class<T> upgradeHandler)
+ throws IOException {
+ if (request == null) {
+ throw MESSAGES.nullRequestFacade();
+ }
+
+ return request.upgrade(upgradeHandler);
+ }
+
}
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/Response.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/Response.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/Response.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -50,6 +50,7 @@
import org.apache.catalina.util.CharsetMapper;
import org.apache.catalina.util.DateTool;
import org.apache.coyote.ActionCode;
+import org.apache.coyote.http11.upgrade.servlet31.WriteListener;
import org.apache.naming.resources.CacheEntry;
import org.apache.naming.resources.ProxyDirContext;
import org.apache.tomcat.util.buf.CharChunk;
@@ -125,9 +126,9 @@
this.connector = connector;
if("AJP/1.3".equals(connector.getProtocol())) {
// default size to size of one ajp-packet
- outputBuffer = new OutputBuffer(8184);
+ outputBuffer = new OutputBuffer(this, 8184);
} else {
- outputBuffer = new OutputBuffer();
+ outputBuffer = new OutputBuffer(this);
}
outputStream = new CoyoteOutputStream(outputBuffer);
writer = new CoyoteWriter(outputBuffer);
@@ -1339,9 +1340,6 @@
if (!connector.hasIoEvents())
throw MESSAGES.cannotUpgradeWithoutEvents();
- if (!request.isEventMode() || request.getAsyncContext() != null)
- throw MESSAGES.cannotUpgradeWithoutEventServlet();
-
// Ignore any call from an included servlet
if (included)
return;
@@ -1360,9 +1358,6 @@
if (!connector.hasIoEvents())
throw MESSAGES.cannotUpgradeWithoutEvents();
- if (!request.isEventMode() || request.getAsyncContext() != null)
- throw MESSAGES.cannotUpgradeWithoutEventServlet();
-
// Ignore any call from an included servlet
if (included)
return;
@@ -1549,7 +1544,25 @@
}
+ public void setContentLengthLong(long length) {
+ if (isCommitted())
+ return;
+ // Ignore any call from an included servlet
+ if (included)
+ return;
+
+ if (usingWriter)
+ return;
+
+ coyoteResponse.setContentLength(length);
+
+ }
+
+ public WriteListener getWriteListener() {
+ return outputBuffer.getWriteListener();
+ }
+
// ------------------------------------------------------ Protected Methods
Modified: branches/7.4.x/src/main/java/org/apache/catalina/connector/ResponseFacade.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/connector/ResponseFacade.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/connector/ResponseFacade.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -605,4 +605,12 @@
return response.getStatus();
}
+ public void setContentLengthLong(long contentLength) {
+ if (response == null) {
+ throw MESSAGES.nullResponseFacade();
+ }
+
+ response.setContentLengthLong(contentLength);
+ }
+
}
Modified: branches/7.4.x/src/main/java/org/apache/catalina/core/StandardWrapperValve.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/catalina/core/StandardWrapperValve.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/catalina/core/StandardWrapperValve.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -509,8 +509,8 @@
if (event.getType() == EventType.END || event.getType() == EventType.ERROR
|| event.getType() == EventType.TIMEOUT) {
// Invoke the listeners with onComplete or onTimeout
- boolean timeout = (event.getType() == EventType.TIMEOUT) ? true : false;
- boolean error = (event.getType() == EventType.ERROR) ? true : false;
+ boolean timeout = (event.getType() == EventType.TIMEOUT);
+ boolean error = (event.getType() == EventType.ERROR);
Iterator<AsyncListenerRegistration> asyncListenerRegistrations =
asyncContext.getAsyncListeners().values().iterator();
while (asyncListenerRegistrations.hasNext()) {
@@ -535,12 +535,71 @@
exception(request, response, e);
}
}
+ boolean end = (event.getType() == EventType.END) || error;
if (timeout && request.isEventMode() && asyncContext.getPath() == null) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ end = true;
}
+ try {
+ // Call error notifications for IO listeners
+ if (error) {
+ Throwable throwable = asyncContext.getError();
+ if (throwable == null) {
+ throwable = new Exception();
+ }
+ if (request.getReadListener() != null) {
+ request.getReadListener().onError(throwable);
+ }
+ if (response.getWriteListener() != null) {
+ response.getWriteListener().onError(throwable);
+ }
+ }
+ } catch (Throwable e) {
+ container.getLogger().error(MESSAGES.ioListenerError(getContainer().getName()), e);
+ exception(request, response, e);
+ }
+ if (end && (request.getUpgradeHandler() != null)) {
+ try {
+ // FIXME: Examine if need to call elsewhere
+ request.getUpgradeHandler().destroy();
+ } catch (Throwable e) {
+ container.getLogger().error(MESSAGES.upgradeHandlerDestroyError(getContainer().getName()), e);
+ exception(request, response, e);
+ }
+ }
if (error && request.isEventMode() && asyncContext.getPath() == null) {
exception(request, response, asyncContext.getError());
}
+ } else if (event.getType() == EventType.READ) {
+ // Read notification
+ if (request.getReadListener() != null) {
+ try {
+ request.getReadListener().onDataAvailable();
+ } catch (Throwable e) {
+ container.getLogger().error(MESSAGES.ioListenerError(getContainer().getName()), e);
+ exception(request, response, e);
+ }
+ }
+ } else if (event.getType() == EventType.EOF) {
+ // End of stream notification
+ if (request.getReadListener() != null) {
+ try {
+ request.getReadListener().onAllDataRead();
+ } catch (Throwable e) {
+ container.getLogger().error(MESSAGES.ioListenerError(getContainer().getName()), e);
+ exception(request, response, e);
+ }
+ }
+ } else if (event.getType() == EventType.WRITE) {
+ // Write notification
+ if (response.getWriteListener() != null) {
+ try {
+ response.getWriteListener().onWritePossible();
+ } catch (Throwable e) {
+ container.getLogger().error(MESSAGES.ioListenerError(getContainer().getName()), e);
+ exception(request, response, e);
+ }
+ }
} else if (asyncContext.getRunnable() != null) {
// Execute the runnable
try {
Modified: branches/7.4.x/src/main/java/org/apache/coyote/ActionCode.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/ActionCode.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/coyote/ActionCode.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -181,6 +181,16 @@
*/
public static final ActionCode ACTION_EVENT_WAKEUP = new ActionCode(29);
+ /**
+ * Ask for read callbacks
+ */
+ public static final ActionCode ACTION_EVENT_READ_BEGIN = new ActionCode(30);
+
+ /**
+ * Ask for a write callbacks
+ */
+ public static final ActionCode ACTION_EVENT_WRITE_BEGIN = new ActionCode(31);
+
// ----------------------------------------------------------- Constructors
int code;
Modified: branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11AprProcessor.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11AprProcessor.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11AprProcessor.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -1288,6 +1288,17 @@
writeNotification = true;
} else if (actionCode == ActionCode.ACTION_EVENT_TIMEOUT) {
timeout = ((Integer) param).intValue();
+ } else if (actionCode == ActionCode.ACTION_EVENT_READ_BEGIN) {
+ Socket.timeoutSet(socket, 0);
+ inputBuffer.setNonBlocking(true);
+ readNotifications = true;
+ } else if (actionCode == ActionCode.ACTION_EVENT_WRITE_BEGIN) {
+ Socket.timeoutSet(socket, 0);
+ outputBuffer.setNonBlocking(true);
+ if (!eventProcessing && !writeNotification) {
+ endpoint.getEventPoller().add(socket, timeout, false, true, false, true);
+ }
+ writeNotification = true;
} else if (actionCode == ActionCode.UPGRADE) {
// Switch to raw bytes mode
inputBuffer.removeActiveFilters();
Modified: branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11NioProcessor.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11NioProcessor.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/Http11NioProcessor.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -855,6 +855,12 @@
} else if (actionCode == ActionCode.ACTION_EVENT_TIMEOUT) {
// Timeout event
timeoutEvent(param);
+ } else if (actionCode == ActionCode.ACTION_EVENT_READ_BEGIN) {
+ inputBuffer.setNonBlocking(true);
+ readNotifications = true;
+ } else if (actionCode == ActionCode.ACTION_EVENT_WRITE_BEGIN) {
+ outputBuffer.setNonBlocking(true);
+ writeEvent(param);
} else if (actionCode == ActionCode.UPGRADE) {
// Switch to raw bytes mode
inputBuffer.removeActiveFilters();
Added: branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,43 @@
+/*
+ * 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.coyote.http11.upgrade;
+
+import javax.servlet.ServletInputStream;
+
+import org.apache.coyote.http11.upgrade.servlet31.ReadListener;
+
+/**
+ * Implements the new Servlet 3.1 methods for {@link ServletInputStream}.
+ */
+public abstract class AbstractServletInputStream extends ServletInputStream {
+
+ /**
+ * New Servlet 3.1 method.
+ */
+ public abstract boolean isFinished();
+
+ /**
+ * New Servlet 3.1 method.
+ */
+ public abstract boolean isReady();
+
+ /**
+ * New Servlet 3.1 method.
+ */
+ public abstract void setReadListener(ReadListener listener);
+
+}
Added: branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,38 @@
+/*
+ * 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.coyote.http11.upgrade;
+
+import javax.servlet.ServletOutputStream;
+
+import org.apache.coyote.http11.upgrade.servlet31.WriteListener;
+
+/**
+ * Implements the new Servlet 3.1 methods for {@link ServletOutputStream}.
+ */
+public abstract class AbstractServletOutputStream extends ServletOutputStream {
+
+ /**
+ * New Servlet 3.1 method.
+ */
+ public abstract boolean isReady();
+
+ /**
+ * New Servlet 3.1 method.
+ */
+ public abstract void setWriteListener(WriteListener listener);
+
+}
Added: branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/HttpUpgradeHandler.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/HttpUpgradeHandler.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/HttpUpgradeHandler.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,38 @@
+/*
+ * 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.coyote.http11.upgrade.servlet31;
+
+/**
+ * Interface between the HTTP upgrade process and the new protocol.
+ */
+public interface HttpUpgradeHandler {
+
+ /**
+ * This method is called once the request/response pair where the upgrade
+ * is initiated has completed processing and is the point where control of
+ * the connection passes from the container to the
+ * {@link HttpUpgradeHandler}.
+ *
+ * @param connection The connection that has been upgraded
+ */
+ void init(WebConnection connection);
+
+ /**
+ * This method is called after the upgraded connection has been closed.
+ */
+ void destroy();
+}
Added: branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/ReadListener.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/ReadListener.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/ReadListener.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,50 @@
+/*
+ * 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.coyote.http11.upgrade.servlet31;
+
+import java.io.IOException;
+
+/**
+ * Receives notification of read events when using non-blocking IO.
+ */
+public interface ReadListener extends java.util.EventListener{
+
+ /**
+ * Invoked when data is available to read. The container will invoke this
+ * method the first time for a request as soon as there is data to read.
+ * Subsequent invocations will only occur if a call to {@link
+ * org.apache.coyote.http11.upgrade.AbstractServletInputStream#isReady()}
+ * has returned false and data has subsequently become available to read.
+ *
+ * @throws IOException
+ */
+ public abstract void onDataAvailable() throws IOException;
+
+ /**
+ * Invoked when the request body has been fully read.
+ *
+ * @throws IOException
+ */
+ public abstract void onAllDataRead() throws IOException;
+
+ /**
+ * Invoked if an error occurs while reading the request body.
+ *
+ * @param throwable The exception that occurred
+ */
+ public abstract void onError(java.lang.Throwable throwable);
+}
Added: branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/WebConnection.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/WebConnection.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/WebConnection.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,47 @@
+/*
+ * 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.coyote.http11.upgrade.servlet31;
+
+import java.io.IOException;
+
+import org.apache.coyote.http11.upgrade.AbstractServletInputStream;
+import org.apache.coyote.http11.upgrade.AbstractServletOutputStream;
+
+/**
+ * The interface used by a {@link HttpUpgradeHandler} to interact with an upgraded
+ * HTTP connection.
+ */
+public interface WebConnection {
+
+ /**
+ * Provides access to the {@link AbstractServletInputStream} for reading
+ * data from the client.
+ */
+ AbstractServletInputStream getInputStream() throws IOException;
+
+ /**
+ * Provides access to the {@link AbstractServletOutputStream} for writing
+ * data to the client.
+ */
+ AbstractServletOutputStream getOutputStream() throws IOException;
+
+ /**
+ * The Servlet 3.1 interface extends AutoCloseable but that is not available
+ * in Java 6 so this is the single method from that interface.
+ */
+ void close() throws Exception;
+}
\ No newline at end of file
Added: branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/WriteListener.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/WriteListener.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/coyote/http11/upgrade/servlet31/WriteListener.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,43 @@
+/*
+ * 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.coyote.http11.upgrade.servlet31;
+
+import java.io.IOException;
+
+/**
+ * Receives notification of write events when using non-blocking IO.
+ */
+public interface WriteListener extends java.util.EventListener{
+
+ /**
+ * Invoked when it it possible to write data without blocking. The container
+ * will invoke this method the first time for a request as soon as data can
+ * be written. Subsequent invocations will only occur if a call to {@link
+ * org.apache.coyote.http11.upgrade.AbstractServletOutputStream#isReady()}
+ * has returned false and it has since become possible to write data.
+ *
+ * @throws IOException
+ */
+ public void onWritePossible() throws IOException;
+
+ /**
+ * Invoked if an error occurs while writing the response.
+ *
+ * @param throwable
+ */
+ public void onError(java.lang.Throwable throwable);
+}
\ No newline at end of file
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/ExceptionUtils.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/ExceptionUtils.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/ExceptionUtils.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * Utilities for handling Throwables and Exceptions.
+ */
+public class ExceptionUtils {
+
+ /**
+ * Checks whether the supplied Throwable is one that needs to be
+ * rethrown and swallows all others.
+ * @param t the Throwable to check
+ */
+ public static void handleThrowable(Throwable t) {
+ if (t instanceof ThreadDeath) {
+ throw (ThreadDeath) t;
+ }
+ if (t instanceof VirtualMachineError) {
+ throw (VirtualMachineError) t;
+ }
+ // All other instances of Throwable will be silently swallowed
+ }
+
+ /**
+ * Checks whether the supplied Throwable is an instance of
+ * <code>InvocationTargetException</code> and returns the throwable that is
+ * wrapped by it, if there is any.
+ *
+ * @param t the Throwable to check
+ * @return <code>t</code> or <code>t.getCause()</code>
+ */
+ public static Throwable unwrapInvocationTargetException(Throwable t) {
+ if (t instanceof InvocationTargetException && t.getCause() != null) {
+ return t.getCause();
+ }
+ return t;
+ }
+}
Deleted: branches/7.4.x/src/main/java/org/apache/tomcat/util/buf/UTF8Decoder.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/buf/UTF8Decoder.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/buf/UTF8Decoder.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -1,126 +0,0 @@
-/*
- * 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.util.buf;
-
-import static org.jboss.web.CoyoteMessages.MESSAGES;
-
-import java.io.IOException;
-
-/**
- * Moved from ByteChunk - code to convert from UTF8 bytes to chars.
- * Not used in the current tomcat3.3 : the performance gain is not very
- * big if the String is created, only if we avoid that and work only
- * on char[]. Until than, it's better to be safe. ( I tested this code
- * with 2 and 3 bytes chars, and it works fine in xerces )
- *
- * Cut from xerces' UTF8Reader.copyMultiByteCharData()
- *
- * @author Costin Manolache
- * @author ( Xml-Xerces )
- */
-public final class UTF8Decoder extends B2CConverter {
-
-
- // may have state !!
-
- public UTF8Decoder()
- throws IOException {
- super("UTF-8");
- }
-
- public void recycle() {
- }
-
- public void convert(ByteChunk mb, CharChunk cb )
- throws IOException {
- int bytesOff=mb.getOffset();
- int bytesLen=mb.getLength();
- byte bytes[]=mb.getBytes();
-
- int j=bytesOff;
- int end=j+bytesLen;
-
- while( j< end ) {
- int b0=0xff & bytes[j];
-
- if( (b0 & 0x80) == 0 ) {
- cb.append((char)b0);
- j++;
- continue;
- }
-
- // 2 byte ?
- if( j++ >= end ) {
- // ok, just ignore - we could throw exception
- throw new IOException(MESSAGES.utf8DecodingEof());
- }
- int b1=0xff & bytes[j];
-
- // ok, let's the fun begin - we're handling UTF8
- if ((0xe0 & b0) == 0xc0) { // 110yyyyy 10xxxxxx (0x80 to 0x7ff)
- int ch = ((0x1f & b0)<<6) + (0x3f & b1);
-
- cb.append((char)ch);
- j++;
- continue;
- }
-
- if( j++ >= end )
- return ;
- int b2=0xff & bytes[j];
-
- if( (b0 & 0xf0 ) == 0xe0 ) {
- if ((b0 == 0xED && b1 >= 0xA0) ||
- (b0 == 0xEF && b1 == 0xBF && b2 >= 0xBE)) {
- throw new IOException(MESSAGES.utf8DecodingFailure(b0, b1, b2));
- }
-
- int ch = ((0x0f & b0)<<12) + ((0x3f & b1)<<6) + (0x3f & b2);
- cb.append((char)ch);
- j++;
- continue;
- }
-
- if( j++ >= end )
- return ;
- int b3=0xff & bytes[j];
-
- if (( 0xf8 & b0 ) == 0xf0 ) {
- if (b0 > 0xF4 || (b0 == 0xF4 && b1 >= 0x90)) {
- throw new IOException(MESSAGES.utf8DecodingFailure(b0, b1, b2, b3));
- }
- int ch = ((0x0f & b0)<<18) + ((0x3f & b1)<<12) +
- ((0x3f & b2)<<6) + (0x3f & b3);
-
- if (ch < 0x10000) {
- cb.append( (char)ch );
- } else {
- cb.append((char)(((ch-0x00010000)>>10)+
- 0xd800));
- cb.append((char)(((ch-0x00010000)&0x3ff)+
- 0xdc00));
- }
- j++;
- continue;
- } else {
- throw new IOException(MESSAGES.utf8DecodingFailure(b0, b1, b2, b3));
- }
- }
- }
-
-}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/buf/Utf8Encoder.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/buf/Utf8Encoder.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/buf/Utf8Encoder.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,234 @@
+/*
+ * 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.util.buf;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+
+/**
+ * Encodes characters as bytes using UTF-8. Extracted from Apache Harmony with
+ * some minor bug fixes applied.
+ */
+public class Utf8Encoder extends CharsetEncoder {
+
+ public Utf8Encoder() {
+ super(EncodingToCharset.UTF_8, 1.1f, 4.0f);
+ }
+
+ @Override
+ protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
+ if (in.hasArray() && out.hasArray()) {
+ return encodeHasArray(in, out);
+ }
+ return encodeNotHasArray(in, out);
+ }
+
+ private CoderResult encodeHasArray(CharBuffer in, ByteBuffer out) {
+ int outRemaining = out.remaining();
+ int pos = in.position();
+ int limit = in.limit();
+ byte[] bArr;
+ char[] cArr;
+ int x = pos;
+ bArr = out.array();
+ cArr = in.array();
+ int outPos = out.position();
+ int rem = in.remaining();
+ for (x = pos; x < pos + rem; x++) {
+ int jchar = (cArr[x] & 0xFFFF);
+
+ if (jchar <= 0x7F) {
+ if (outRemaining < 1) {
+ in.position(x);
+ out.position(outPos);
+ return CoderResult.OVERFLOW;
+ }
+ bArr[outPos++] = (byte) (jchar & 0xFF);
+ outRemaining--;
+ } else if (jchar <= 0x7FF) {
+
+ if (outRemaining < 2) {
+ in.position(x);
+ out.position(outPos);
+ return CoderResult.OVERFLOW;
+ }
+ bArr[outPos++] = (byte) (0xC0 + ((jchar >> 6) & 0x1F));
+ bArr[outPos++] = (byte) (0x80 + (jchar & 0x3F));
+ outRemaining -= 2;
+
+ } else if (jchar >= 0xD800 && jchar <= 0xDFFF) {
+
+ // in has to have one byte more.
+ if (limit <= x + 1) {
+ in.position(x);
+ out.position(outPos);
+ return CoderResult.UNDERFLOW;
+ }
+
+ if (outRemaining < 4) {
+ in.position(x);
+ out.position(outPos);
+ return CoderResult.OVERFLOW;
+ }
+
+ // The surrogate pair starts with a low-surrogate.
+ if (jchar >= 0xDC00) {
+ in.position(x);
+ out.position(outPos);
+ return CoderResult.malformedForLength(1);
+ }
+
+ int jchar2 = cArr[x + 1] & 0xFFFF;
+
+ // The surrogate pair ends with a high-surrogate.
+ if (jchar2 < 0xDC00) {
+ in.position(x);
+ out.position(outPos);
+ return CoderResult.malformedForLength(1);
+ }
+
+ // Note, the Unicode scalar value n is defined
+ // as follows:
+ // n = (jchar-0xD800)*0x400+(jchar2-0xDC00)+0x10000
+ // Where jchar is a high-surrogate,
+ // jchar2 is a low-surrogate.
+ int n = (jchar << 10) + jchar2 + 0xFCA02400;
+
+ bArr[outPos++] = (byte) (0xF0 + ((n >> 18) & 0x07));
+ bArr[outPos++] = (byte) (0x80 + ((n >> 12) & 0x3F));
+ bArr[outPos++] = (byte) (0x80 + ((n >> 6) & 0x3F));
+ bArr[outPos++] = (byte) (0x80 + (n & 0x3F));
+ outRemaining -= 4;
+ x++;
+
+ } else {
+
+ if (outRemaining < 3) {
+ in.position(x);
+ out.position(outPos);
+ return CoderResult.OVERFLOW;
+ }
+ bArr[outPos++] = (byte) (0xE0 + ((jchar >> 12) & 0x0F));
+ bArr[outPos++] = (byte) (0x80 + ((jchar >> 6) & 0x3F));
+ bArr[outPos++] = (byte) (0x80 + (jchar & 0x3F));
+ outRemaining -= 3;
+ }
+ if (outRemaining == 0) {
+ in.position(x + 1);
+ out.position(outPos);
+ // If both input and output are exhausted, return UNDERFLOW
+ if (x + 1 == limit) {
+ return CoderResult.UNDERFLOW;
+ } else {
+ return CoderResult.OVERFLOW;
+ }
+ }
+
+ }
+ if (rem != 0) {
+ in.position(x);
+ out.position(outPos);
+ }
+ return CoderResult.UNDERFLOW;
+ }
+
+ private CoderResult encodeNotHasArray(CharBuffer in, ByteBuffer out) {
+ int outRemaining = out.remaining();
+ int pos = in.position();
+ int limit = in.limit();
+ try {
+ while (pos < limit) {
+ if (outRemaining == 0) {
+ return CoderResult.OVERFLOW;
+ }
+
+ int jchar = (in.get() & 0xFFFF);
+
+ if (jchar <= 0x7F) {
+
+ if (outRemaining < 1) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put((byte) jchar);
+ outRemaining--;
+
+ } else if (jchar <= 0x7FF) {
+
+ if (outRemaining < 2) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put((byte) (0xC0 + ((jchar >> 6) & 0x1F)));
+ out.put((byte) (0x80 + (jchar & 0x3F)));
+ outRemaining -= 2;
+
+ } else if (jchar >= 0xD800 && jchar <= 0xDFFF) {
+
+ // in has to have one byte more.
+ if (limit <= pos + 1) {
+ return CoderResult.UNDERFLOW;
+ }
+
+ if (outRemaining < 4) {
+ return CoderResult.OVERFLOW;
+ }
+
+ // The surrogate pair starts with a low-surrogate.
+ if (jchar >= 0xDC00) {
+ return CoderResult.malformedForLength(1);
+ }
+
+ int jchar2 = (in.get() & 0xFFFF);
+
+ // The surrogate pair ends with a high-surrogate.
+ if (jchar2 < 0xDC00) {
+ return CoderResult.malformedForLength(1);
+ }
+
+ // Note, the Unicode scalar value n is defined
+ // as follows:
+ // n = (jchar-0xD800)*0x400+(jchar2-0xDC00)+0x10000
+ // Where jchar is a high-surrogate,
+ // jchar2 is a low-surrogate.
+ int n = (jchar << 10) + jchar2 + 0xFCA02400;
+
+ out.put((byte) (0xF0 + ((n >> 18) & 0x07)));
+ out.put((byte) (0x80 + ((n >> 12) & 0x3F)));
+ out.put((byte) (0x80 + ((n >> 6) & 0x3F)));
+ out.put((byte) (0x80 + (n & 0x3F)));
+ outRemaining -= 4;
+ pos++;
+
+ } else {
+
+ if (outRemaining < 3) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put((byte) (0xE0 + ((jchar >> 12) & 0x0F)));
+ out.put((byte) (0x80 + ((jchar >> 6) & 0x3F)));
+ out.put((byte) (0x80 + (jchar & 0x3F)));
+ outRemaining -= 3;
+ }
+ pos++;
+ }
+ } finally {
+ in.position(pos);
+ }
+ return CoderResult.UNDERFLOW;
+ }
+}
\ No newline at end of file
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/BinaryDecoder.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/BinaryDecoder.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/BinaryDecoder.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,37 @@
+/*
+ * 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.util.codec;
+
+/**
+ * Defines common decoding methods for byte array decoders.
+ *
+ * @version $Id: BinaryDecoder.java 1459218 2013-03-21 10:31:50Z markt $
+ */
+public interface BinaryDecoder extends Decoder {
+
+ /**
+ * Decodes a byte array and returns the results as a byte array.
+ *
+ * @param source
+ * A byte array which has been encoded with the appropriate encoder
+ * @return a byte array that contains decoded content
+ * @throws DecoderException
+ * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process.
+ */
+ byte[] decode(byte[] source) throws DecoderException;
+}
+
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/BinaryEncoder.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/BinaryEncoder.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/BinaryEncoder.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,37 @@
+/*
+ * 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.util.codec;
+
+/**
+ * Defines common encoding methods for byte array encoders.
+ *
+ * @version $Id: BinaryEncoder.java 1459218 2013-03-21 10:31:50Z markt $
+ */
+public interface BinaryEncoder extends Encoder {
+
+ /**
+ * Encodes a byte array and return the encoded data as a byte array.
+ *
+ * @param source
+ * Data to be encoded
+ * @return A byte array containing the encoded data
+ * @throws EncoderException
+ * thrown if the Encoder encounters a failure condition during the encoding process.
+ */
+ byte[] encode(byte[] source) throws EncoderException;
+}
+
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/Decoder.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/Decoder.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/Decoder.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,46 @@
+/*
+ * 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.util.codec;
+
+/**
+ * Provides the highest level of abstraction for Decoders.
+ * <p>
+ * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface.
+ * Allows a user to pass a generic Object to any Decoder implementation in the codec package.
+ * <p>
+ * One of the two interfaces at the center of the codec package.
+ *
+ * @version $Id: Decoder.java 1459218 2013-03-21 10:31:50Z markt $
+ */
+public interface Decoder {
+
+ /**
+ * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will
+ * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a
+ * {@link ClassCastException} occurs this decode method will throw a DecoderException.
+ *
+ * @param source
+ * the object to decode
+ * @return a 'decoded" object
+ * @throws DecoderException
+ * a decoder exception can be thrown for any number of reasons. Some good candidates are that the
+ * parameter passed to this method is null, a param cannot be cast to the appropriate type for a
+ * specific encoder.
+ */
+ Object decode(Object source) throws DecoderException;
+}
+
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/DecoderException.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/DecoderException.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/DecoderException.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,85 @@
+/*
+ * 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.util.codec;
+
+/**
+ * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder}
+ * encounters a decoding specific exception such as invalid data, or characters outside of the expected range.
+ *
+ * @version $Id: DecoderException.java 1459218 2013-03-21 10:31:50Z markt $
+ */
+public class DecoderException extends Exception {
+
+ /**
+ * Declares the Serial Version Uid.
+ *
+ * @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a>
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new exception with {@code null} as its detail message. The cause is not initialized, and may
+ * subsequently be initialized by a call to {@link #initCause}.
+ *
+ * @since 1.4
+ */
+ public DecoderException() {
+ super();
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
+ * be initialized by a call to {@link #initCause}.
+ *
+ * @param message
+ * The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+ */
+ public DecoderException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and cause.
+ * <p>
+ * Note that the detail message associated with <code>cause</code> is not automatically incorporated into this
+ * exception's detail message.
+ *
+ * @param message
+ * The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+ * @param cause
+ * The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null}
+ * value is permitted, and indicates that the cause is nonexistent or unknown.
+ * @since 1.4
+ */
+ public DecoderException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new exception with the specified cause and a detail message of <code>(cause==null ?
+ * null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>).
+ * This constructor is useful for exceptions that are little more than wrappers for other throwables.
+ *
+ * @param cause
+ * The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null}
+ * value is permitted, and indicates that the cause is nonexistent or unknown.
+ * @since 1.4
+ */
+ public DecoderException(final Throwable cause) {
+ super(cause);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/Encoder.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/Encoder.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/Encoder.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,43 @@
+/*
+ * 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.util.codec;
+
+/**
+ * Provides the highest level of abstraction for Encoders.
+ * <p>
+ * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this
+ * common generic interface which allows a user to pass a generic Object to any Encoder implementation
+ * in the codec package.
+ *
+ * @version $Id: Encoder.java 1459218 2013-03-21 10:31:50Z markt $
+ */
+public interface Encoder {
+
+ /**
+ * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be
+ * <code>byte[]</code> or <code>String</code>s depending on the implementation used.
+ *
+ * @param source
+ * An object to encode
+ * @return An "encoded" Object
+ * @throws EncoderException
+ * An encoder exception is thrown if the encoder experiences a failure condition during the encoding
+ * process.
+ */
+ Object encode(Object source) throws EncoderException;
+}
+
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/EncoderException.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/EncoderException.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/EncoderException.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,88 @@
+/*
+ * 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.util.codec;
+
+/**
+ * Thrown when there is a failure condition during the encoding process. This exception is thrown when an
+ * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum,
+ * characters outside of the expected range.
+ *
+ * @version $Id: EncoderException.java 1459218 2013-03-21 10:31:50Z markt $
+ */
+public class EncoderException extends Exception {
+
+ /**
+ * Declares the Serial Version Uid.
+ *
+ * @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a>
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new exception with {@code null} as its detail message. The cause is not initialized, and may
+ * subsequently be initialized by a call to {@link #initCause}.
+ *
+ * @since 1.4
+ */
+ public EncoderException() {
+ super();
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
+ * be initialized by a call to {@link #initCause}.
+ *
+ * @param message
+ * a useful message relating to the encoder specific error.
+ */
+ public EncoderException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and cause.
+ *
+ * <p>
+ * Note that the detail message associated with <code>cause</code> is not automatically incorporated into this
+ * exception's detail message.
+ * </p>
+ *
+ * @param message
+ * The detail message which is saved for later retrieval by the {@link #getMessage()} method.
+ * @param cause
+ * The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null}
+ * value is permitted, and indicates that the cause is nonexistent or unknown.
+ * @since 1.4
+ */
+ public EncoderException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new exception with the specified cause and a detail message of <code>(cause==null ?
+ * null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>).
+ * This constructor is useful for exceptions that are little more than wrappers for other throwables.
+ *
+ * @param cause
+ * The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null}
+ * value is permitted, and indicates that the cause is nonexistent or unknown.
+ * @since 1.4
+ */
+ public EncoderException(final Throwable cause) {
+ super(cause);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/Base64.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/Base64.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/Base64.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,779 @@
+/*
+ * 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.util.codec.binary;
+
+import java.math.BigInteger;
+
+/**
+ * Provides Base64 encoding and decoding as defined by <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
+ *
+ * <p>
+ * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
+ * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
+ * </p>
+ * <p>
+ * The class can be parameterized in the following manner with various constructors:
+ * <ul>
+ * <li>URL-safe mode: Default off.</li>
+ * <li>Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of
+ * 4 in the encoded data.
+ * <li>Line separator: Default is CRLF ("\r\n")</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only
+ * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252,
+ * UTF-8, etc).
+ * </p>
+ * <p>
+ * This class is thread-safe.
+ * </p>
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
+ * @since 1.0
+ * @version $Id: Base64.java 1459346 2013-03-21 15:05:54Z markt $
+ */
+public class Base64 extends BaseNCodec {
+
+ /**
+ * BASE32 characters are 6 bits in length.
+ * They are formed by taking a block of 3 octets to form a 24-bit string,
+ * which is converted into 4 BASE64 characters.
+ */
+ private static final int BITS_PER_ENCODED_BYTE = 6;
+ private static final int BYTES_PER_UNENCODED_BLOCK = 3;
+ private static final int BYTES_PER_ENCODED_BLOCK = 4;
+
+ /**
+ * Chunk separator per RFC 2045 section 2.1.
+ *
+ * <p>
+ * N.B. The next major release may break compatibility and make this field private.
+ * </p>
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
+ */
+ static final byte[] CHUNK_SEPARATOR = {'\r', '\n'};
+
+ /**
+ * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet"
+ * equivalents as specified in Table 1 of RFC 2045.
+ *
+ * Thanks to "commons" project in ws.apache.org for this code.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ */
+ private static final byte[] STANDARD_ENCODE_TABLE = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+ };
+
+ /**
+ * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and /
+ * changed to - and _ to make the encoded Base64 results more URL-SAFE.
+ * This table is only used when the Base64's mode is set to URL-SAFE.
+ */
+ private static final byte[] URL_SAFE_ENCODE_TABLE = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
+ };
+
+ /**
+ * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified
+ * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64
+ * alphabet but fall within the bounds of the array are translated to -1.
+ *
+ * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both
+ * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit).
+ *
+ * Thanks to "commons" project in ws.apache.org for this code.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ */
+ private static final byte[] DECODE_TABLE = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+ };
+
+ /**
+ * Base64 uses 6-bit fields.
+ */
+ /** Mask used to extract 6 bits, used when encoding */
+ private static final int MASK_6BITS = 0x3f;
+
+ // The static final fields above are used for the original static byte[] methods on Base64.
+ // The private member fields below are used with the new streaming approach, which requires
+ // some state be preserved between calls of encode() and decode().
+
+ /**
+ * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able
+ * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch
+ * between the two modes.
+ */
+ private final byte[] encodeTable;
+
+ // Only one decode table currently; keep for consistency with Base32 code
+ private final byte[] decodeTable = DECODE_TABLE;
+
+ /**
+ * Line separator for encoding. Not used when decoding. Only used if lineLength > 0.
+ */
+ private final byte[] lineSeparator;
+
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * <code>decodeSize = 3 + lineSeparator.length;</code>
+ */
+ private final int decodeSize;
+
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * <code>encodeSize = 4 + lineSeparator.length;</code>
+ */
+ private final int encodeSize;
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ * <p>
+ * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE.
+ * </p>
+ *
+ * <p>
+ * When decoding all variants are supported.
+ * </p>
+ */
+ public Base64() {
+ this(0);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode.
+ * <p>
+ * When encoding the line length is 76, the line separator is CRLF, and the encoding table is
+ * STANDARD_ENCODE_TABLE.
+ * </p>
+ *
+ * <p>
+ * When decoding all variants are supported.
+ * </p>
+ *
+ * @param urlSafe
+ * if {@code true}, URL-safe encoding is used. In most cases this should be set to {@code false}.
+ * @since 1.4
+ */
+ public Base64(final boolean urlSafe) {
+ this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ * <p>
+ * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is
+ * STANDARD_ENCODE_TABLE.
+ * </p>
+ * <p>
+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+ * </p>
+ * <p>
+ * When decoding all variants are supported.
+ * </p>
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @since 1.4
+ */
+ public Base64(final int lineLength) {
+ this(lineLength, CHUNK_SEPARATOR);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ * <p>
+ * When encoding the line length and line separator are given in the constructor, and the encoding table is
+ * STANDARD_ENCODE_TABLE.
+ * </p>
+ * <p>
+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+ * </p>
+ * <p>
+ * When decoding all variants are supported.
+ * </p>
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @throws IllegalArgumentException
+ * Thrown when the provided lineSeparator included some base64 characters.
+ * @since 1.4
+ */
+ public Base64(final int lineLength, final byte[] lineSeparator) {
+ this(lineLength, lineSeparator, false);
+ }
+
+ /**
+ * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode.
+ * <p>
+ * When encoding the line length and line separator are given in the constructor, and the encoding table is
+ * STANDARD_ENCODE_TABLE.
+ * </p>
+ * <p>
+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data.
+ * </p>
+ * <p>
+ * When decoding all variants are supported.
+ * </p>
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of
+ * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when
+ * decoding.
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @param urlSafe
+ * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode
+ * operations. Decoding seamlessly handles both modes.
+ * <b>Note: no padding is added when using the URL-safe alphabet.</b>
+ * @throws IllegalArgumentException
+ * The provided lineSeparator included some base64 characters. That's not going to work!
+ * @since 1.4
+ */
+ public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) {
+ super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK,
+ lineLength,
+ lineSeparator == null ? 0 : lineSeparator.length);
+ // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0
+ // @see test case Base64Test.testConstructors()
+ if (lineSeparator != null) {
+ if (containsAlphabetOrPad(lineSeparator)) {
+ final String sep = StringUtils.newStringUtf8(lineSeparator);
+ throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]");
+ }
+ if (lineLength > 0){ // null line-sep forces no chunking rather than throwing IAE
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length;
+ this.lineSeparator = new byte[lineSeparator.length];
+ System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
+ } else {
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.lineSeparator = null;
+ }
+ } else {
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.lineSeparator = null;
+ }
+ this.decodeSize = this.encodeSize - 1;
+ this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
+ }
+
+ /**
+ * Returns our current encode mode. True if we're URL-SAFE, false otherwise.
+ *
+ * @return true if we're in URL-SAFE mode, false otherwise.
+ * @since 1.4
+ */
+ public boolean isUrlSafe() {
+ return this.encodeTable == URL_SAFE_ENCODE_TABLE;
+ }
+
+ /**
+ * <p>
+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
+ * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last
+ * remaining bytes (if not multiple of 3).
+ * </p>
+ * <p><b>Note: no padding is added when encoding using the URL-safe alphabet.</b></p>
+ * <p>
+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ * </p>
+ *
+ * @param in
+ * byte[] array of binary data to base64 encode.
+ * @param inPos
+ * Position to start reading data from.
+ * @param inAvail
+ * Amount of bytes available from input for encoding.
+ * @param context
+ * the context to be used
+ */
+ @Override
+ void encode(final byte[] in, int inPos, final int inAvail, final Context context) {
+ if (context.eof) {
+ return;
+ }
+ // inAvail < 0 is how we're informed of EOF in the underlying data we're
+ // encoding.
+ if (inAvail < 0) {
+ context.eof = true;
+ if (0 == context.modulus && lineLength == 0) {
+ return; // no leftovers to process and not using chunking
+ }
+ final byte[] buffer = ensureBufferSize(encodeSize, context);
+ final int savedPos = context.pos;
+ switch (context.modulus) { // 0-2
+ case 0 : // nothing to do here
+ break;
+ case 1 : // 8 bits = 6 + 2
+ // top 6 bits:
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS];
+ // remaining 2:
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS];
+ // URL-SAFE skips the padding to further reduce size.
+ if (encodeTable == STANDARD_ENCODE_TABLE) {
+ buffer[context.pos++] = PAD;
+ buffer[context.pos++] = PAD;
+ }
+ break;
+
+ case 2 : // 16 bits = 6 + 6 + 4
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS];
+ // URL-SAFE skips the padding to further reduce size.
+ if (encodeTable == STANDARD_ENCODE_TABLE) {
+ buffer[context.pos++] = PAD;
+ }
+ break;
+ default:
+ throw new IllegalStateException("Impossible modulus "+context.modulus);
+ }
+ context.currentLinePos += context.pos - savedPos; // keep track of current line position
+ // if currentPos == 0 we are at the start of a line, so don't add CRLF
+ if (lineLength > 0 && context.currentLinePos > 0) {
+ System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
+ context.pos += lineSeparator.length;
+ }
+ } else {
+ for (int i = 0; i < inAvail; i++) {
+ final byte[] buffer = ensureBufferSize(encodeSize, context);
+ context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK;
+ int b = in[inPos++];
+ if (b < 0) {
+ b += 256;
+ }
+ context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE
+ if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS];
+ buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS];
+ context.currentLinePos += BYTES_PER_ENCODED_BLOCK;
+ if (lineLength > 0 && lineLength <= context.currentLinePos) {
+ System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length);
+ context.pos += lineSeparator.length;
+ context.currentLinePos = 0;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
+ * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
+ * call is not necessary when decoding, but it doesn't hurt, either.
+ * </p>
+ * <p>
+ * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
+ * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
+ * garbage-out philosophy: it will not check the provided data for validity.
+ * </p>
+ * <p>
+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
+ * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
+ * </p>
+ *
+ * @param in
+ * byte[] array of ascii data to base64 decode.
+ * @param inPos
+ * Position to start reading data from.
+ * @param inAvail
+ * Amount of bytes available from input for encoding.
+ * @param context
+ * the context to be used
+ */
+ @Override
+ void decode(final byte[] in, int inPos, final int inAvail, final Context context) {
+ if (context.eof) {
+ return;
+ }
+ if (inAvail < 0) {
+ context.eof = true;
+ }
+ for (int i = 0; i < inAvail; i++) {
+ final byte[] buffer = ensureBufferSize(decodeSize, context);
+ final byte b = in[inPos++];
+ if (b == PAD) {
+ // We're done.
+ context.eof = true;
+ break;
+ } else {
+ if (b >= 0 && b < DECODE_TABLE.length) {
+ final int result = DECODE_TABLE[b];
+ if (result >= 0) {
+ context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK;
+ context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result;
+ if (context.modulus == 0) {
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
+ }
+ }
+ }
+ }
+ }
+
+ // Two forms of EOF as far as base64 decoder is concerned: actual
+ // EOF (-1) and first time '=' character is encountered in stream.
+ // This approach makes the '=' padding characters completely optional.
+ if (context.eof && context.modulus != 0) {
+ final byte[] buffer = ensureBufferSize(decodeSize, context);
+
+ // We have some spare bits remaining
+ // Output all whole multiples of 8 bits and ignore the rest
+ switch (context.modulus) {
+// case 0 : // impossible, as excluded above
+ case 1 : // 6 bits - ignore entirely
+ // TODO not currently tested; perhaps it is impossible?
+ break;
+ case 2 : // 12 bits = 8 + 4
+ context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
+ break;
+ case 3 : // 18 bits = 8 + 8 + 2
+ context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
+ buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
+ break;
+ default:
+ throw new IllegalStateException("Impossible modulus "+context.modulus);
+ }
+ }
+ }
+
+ /**
+ * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the
+ * method treats whitespace as valid.
+ *
+ * @param arrayOctet
+ * byte array to test
+ * @return {@code true} if all bytes are valid characters in the Base64 alphabet or if the byte array is empty;
+ * {@code false}, otherwise
+ * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0.
+ */
+ @Deprecated
+ public static boolean isArrayByteBase64(final byte[] arrayOctet) {
+ return isBase64(arrayOctet);
+ }
+
+ /**
+ * Returns whether or not the <code>octet</code> is in the base 64 alphabet.
+ *
+ * @param octet
+ * The value to test
+ * @return {@code true} if the value is defined in the the base 64 alphabet, {@code false} otherwise.
+ * @since 1.4
+ */
+ public static boolean isBase64(final byte octet) {
+ return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1);
+ }
+
+ /**
+ * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the
+ * method treats whitespace as valid.
+ *
+ * @param base64
+ * String to test
+ * @return {@code true} if all characters in the String are valid characters in the Base64 alphabet or if
+ * the String is empty; {@code false}, otherwise
+ * @since 1.5
+ */
+ public static boolean isBase64(final String base64) {
+ return isBase64(StringUtils.getBytesUtf8(base64));
+ }
+
+ /**
+ * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the
+ * method treats whitespace as valid.
+ *
+ * @param arrayOctet
+ * byte array to test
+ * @return {@code true} if all bytes are valid characters in the Base64 alphabet or if the byte array is empty;
+ * {@code false}, otherwise
+ * @since 1.5
+ */
+ public static boolean isBase64(final byte[] arrayOctet) {
+ for (int i = 0; i < arrayOctet.length; i++) {
+ if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm but does not chunk the output.
+ *
+ * @param binaryData
+ * binary data to encode
+ * @return byte[] containing Base64 characters in their UTF-8 representation.
+ */
+ public static byte[] encodeBase64(final byte[] binaryData) {
+ return encodeBase64(binaryData, false);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm but does not chunk the output.
+ *
+ * NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to
+ * single-line non-chunking (commons-codec-1.5).
+ *
+ * @param binaryData
+ * binary data to encode
+ * @return String containing Base64 characters.
+ * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not).
+ */
+ public static String encodeBase64String(final byte[] binaryData) {
+ return StringUtils.newStringUtf8(encodeBase64(binaryData, false));
+ }
+
+ /**
+ * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The
+ * url-safe variation emits - and _ instead of + and / characters.
+ * <b>Note: no padding is added.</b>
+ * @param binaryData
+ * binary data to encode
+ * @return byte[] containing Base64 characters in their UTF-8 representation.
+ * @since 1.4
+ */
+ public static byte[] encodeBase64URLSafe(final byte[] binaryData) {
+ return encodeBase64(binaryData, false, true);
+ }
+
+ /**
+ * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The
+ * url-safe variation emits - and _ instead of + and / characters.
+ * <b>Note: no padding is added.</b>
+ * @param binaryData
+ * binary data to encode
+ * @return String containing Base64 characters
+ * @since 1.4
+ */
+ public static String encodeBase64URLSafeString(final byte[] binaryData) {
+ return StringUtils.newStringUtf8(encodeBase64(binaryData, false, true));
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
+ *
+ * @param binaryData
+ * binary data to encode
+ * @return Base64 characters chunked in 76 character blocks
+ */
+ public static byte[] encodeBase64Chunked(final byte[] binaryData) {
+ return encodeBase64(binaryData, true);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+ *
+ * @param binaryData
+ * Array containing binary data to encode.
+ * @param isChunked
+ * if {@code true} this encoder will chunk the base64 output into 76 character blocks
+ * @return Base64-encoded data.
+ * @throws IllegalArgumentException
+ * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
+ */
+ public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) {
+ return encodeBase64(binaryData, isChunked, false);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+ *
+ * @param binaryData
+ * Array containing binary data to encode.
+ * @param isChunked
+ * if {@code true} this encoder will chunk the base64 output into 76 character blocks
+ * @param urlSafe
+ * if {@code true} this encoder will emit - and _ instead of the usual + and / characters.
+ * <b>Note: no padding is added when encoding using the URL-safe alphabet.</b>
+ * @return Base64-encoded data.
+ * @throws IllegalArgumentException
+ * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
+ * @since 1.4
+ */
+ public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) {
+ return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+ *
+ * @param binaryData
+ * Array containing binary data to encode.
+ * @param isChunked
+ * if {@code true} this encoder will chunk the base64 output into 76 character blocks
+ * @param urlSafe
+ * if {@code true} this encoder will emit - and _ instead of the usual + and / characters.
+ * <b>Note: no padding is added when encoding using the URL-safe alphabet.</b>
+ * @param maxResultSize
+ * The maximum result size to accept.
+ * @return Base64-encoded data.
+ * @throws IllegalArgumentException
+ * Thrown when the input array needs an output array bigger than maxResultSize
+ * @since 1.4
+ */
+ public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked,
+ final boolean urlSafe, final int maxResultSize) {
+ if (binaryData == null || binaryData.length == 0) {
+ return binaryData;
+ }
+
+ // Create this so can use the super-class method
+ // Also ensures that the same roundings are performed by the ctor and the code
+ final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe);
+ final long len = b64.getEncodedLength(binaryData);
+ if (len > maxResultSize) {
+ throw new IllegalArgumentException("Input array too big, the output array would be bigger (" +
+ len +
+ ") than the specified maximum size of " +
+ maxResultSize);
+ }
+
+ return b64.encode(binaryData);
+ }
+
+ /**
+ * Decodes a Base64 String into octets
+ *
+ * @param base64String
+ * String containing Base64 data
+ * @return Array containing decoded data.
+ * @since 1.4
+ */
+ public static byte[] decodeBase64(final String base64String) {
+ return new Base64().decode(base64String);
+ }
+
+ /**
+ * Decodes Base64 data into octets
+ *
+ * @param base64Data
+ * Byte array containing Base64 data
+ * @return Array containing decoded data.
+ */
+ public static byte[] decodeBase64(final byte[] base64Data) {
+ return decodeBase64(base64Data, 0, base64Data.length);
+ }
+
+ public static byte[] decodeBase64(
+ final byte[] base64Data, final int off, final int len) {
+ return new Base64().decode(base64Data, off, len);
+ }
+
+ // Implementation of the Encoder Interface
+
+ // Implementation of integer encoding used for crypto
+ /**
+ * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature
+ *
+ * @param pArray
+ * a byte array containing base64 character data
+ * @return A BigInteger
+ * @since 1.4
+ */
+ public static BigInteger decodeInteger(final byte[] pArray) {
+ return new BigInteger(1, decodeBase64(pArray));
+ }
+
+ /**
+ * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature
+ *
+ * @param bigInt
+ * a BigInteger
+ * @return A byte array containing base64 character data
+ * @throws NullPointerException
+ * if null is passed in
+ * @since 1.4
+ */
+ public static byte[] encodeInteger(final BigInteger bigInt) {
+ if (bigInt == null) {
+ throw new NullPointerException("encodeInteger called with null parameter");
+ }
+ return encodeBase64(toIntegerBytes(bigInt), false);
+ }
+
+ /**
+ * Returns a byte-array representation of a <code>BigInteger</code> without sign bit.
+ *
+ * @param bigInt
+ * <code>BigInteger</code> to be converted
+ * @return a byte array representation of the BigInteger parameter
+ */
+ static byte[] toIntegerBytes(final BigInteger bigInt) {
+ int bitlen = bigInt.bitLength();
+ // round bitlen
+ bitlen = ((bitlen + 7) >> 3) << 3;
+ final byte[] bigBytes = bigInt.toByteArray();
+
+ if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
+ return bigBytes;
+ }
+ // set up params for copying everything but sign bit
+ int startSrc = 0;
+ int len = bigBytes.length;
+
+ // if bigInt is exactly byte-aligned, just skip signbit in copy
+ if ((bigInt.bitLength() % 8) == 0) {
+ startSrc = 1;
+ len--;
+ }
+ final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
+ final byte[] resizedBytes = new byte[bitlen / 8];
+ System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
+ return resizedBytes;
+ }
+
+ /**
+ * Returns whether or not the <code>octet</code> is in the Base64 alphabet.
+ *
+ * @param octet
+ * The value to test
+ * @return {@code true} if the value is defined in the the Base64 alphabet {@code false} otherwise.
+ */
+ @Override
+ protected boolean isInAlphabet(final byte octet) {
+ return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1;
+ }
+
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,503 @@
+/*
+ * 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.util.codec.binary;
+
+import org.apache.tomcat.util.codec.BinaryDecoder;
+import org.apache.tomcat.util.codec.BinaryEncoder;
+import org.apache.tomcat.util.codec.DecoderException;
+import org.apache.tomcat.util.codec.EncoderException;
+
+/**
+ * Abstract superclass for Base-N encoders and decoders.
+ *
+ * <p>
+ * This class is thread-safe.
+ * </p>
+ *
+ * @version $Id: BaseNCodec.java 1459390 2013-03-21 16:38:39Z markt $
+ */
+public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder {
+
+ /**
+ * Holds thread context so classes can be thread-safe.
+ *
+ * This class is not itself thread-safe; each thread must allocate its own copy.
+ *
+ * @since 1.7
+ */
+ static class Context {
+
+ /**
+ * Place holder for the bytes we're dealing with for our based logic.
+ * Bitwise operations store and extract the encoding or decoding from this variable.
+ */
+ int ibitWorkArea;
+
+ /**
+ * Place holder for the bytes we're dealing with for our based logic.
+ * Bitwise operations store and extract the encoding or decoding from this variable.
+ */
+ long lbitWorkArea;
+
+ /**
+ * Buffer for streaming.
+ */
+ byte[] buffer;
+
+ /**
+ * Position where next character should be written in the buffer.
+ */
+ int pos;
+
+ /**
+ * Position where next character should be read from the buffer.
+ */
+ int readPos;
+
+ /**
+ * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
+ * and must be thrown away.
+ */
+ boolean eof;
+
+ /**
+ * Variable tracks how many characters have been written to the current line. Only used when encoding. We use
+ * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0).
+ */
+ int currentLinePos;
+
+ /**
+ * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This
+ * variable helps track that.
+ */
+ int modulus;
+
+ Context() {
+ }
+
+ /**
+ * Returns a String useful for debugging (especially within a debugger.)
+ *
+ * @return a String useful for debugging.
+ */
+ @SuppressWarnings("boxing") // OK to ignore boxing here
+ @Override
+ public String toString() {
+ return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " +
+ "modulus=%s, pos=%s, readPos=%s]", this.getClass().getSimpleName(), buffer, currentLinePos, eof,
+ ibitWorkArea, lbitWorkArea, modulus, pos, readPos);
+ }
+ }
+
+ /**
+ * EOF
+ *
+ * @since 1.7
+ */
+ static final int EOF = -1;
+
+ /**
+ * MIME chunk size per RFC 2045 section 6.8.
+ *
+ * <p>
+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
+ * equal signs.
+ * </p>
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
+ */
+ public static final int MIME_CHUNK_SIZE = 76;
+
+ /**
+ * PEM chunk size per RFC 1421 section 4.3.2.4.
+ *
+ * <p>
+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any
+ * equal signs.
+ * </p>
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421 section 4.3.2.4</a>
+ */
+ public static final int PEM_CHUNK_SIZE = 64;
+
+ private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
+
+ /**
+ * Defines the default buffer size - currently {@value}
+ * - must be large enough for at least one encoded block+separator
+ */
+ private static final int DEFAULT_BUFFER_SIZE = 128;
+
+ /** Mask used to extract 8 bits, used in decoding bytes */
+ protected static final int MASK_8BITS = 0xff;
+
+ /**
+ * Byte used to pad output.
+ */
+ protected static final byte PAD_DEFAULT = '='; // Allow static access to default
+
+ protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later
+
+ /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */
+ private final int unencodedBlockSize;
+
+ /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */
+ private final int encodedBlockSize;
+
+ /**
+ * Chunksize for encoding. Not used when decoding.
+ * A value of zero or less implies no chunking of the encoded data.
+ * Rounded down to nearest multiple of encodedBlockSize.
+ */
+ protected final int lineLength;
+
+ /**
+ * Size of chunk separator. Not used unless {@link #lineLength} > 0.
+ */
+ private final int chunkSeparatorLength;
+
+ /**
+ * Note <code>lineLength</code> is rounded down to the nearest multiple of {@link #encodedBlockSize}
+ * If <code>chunkSeparatorLength</code> is zero, then chunking is disabled.
+ * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3)
+ * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4)
+ * @param lineLength if > 0, use chunking with a length <code>lineLength</code>
+ * @param chunkSeparatorLength the chunk separator length, if relevant
+ */
+ protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize,
+ final int lineLength, final int chunkSeparatorLength) {
+ this.unencodedBlockSize = unencodedBlockSize;
+ this.encodedBlockSize = encodedBlockSize;
+ final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0;
+ this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0;
+ this.chunkSeparatorLength = chunkSeparatorLength;
+ }
+
+ /**
+ * Returns true if this object has buffered data for reading.
+ *
+ * @param context the context to be used
+ * @return true if there is data still available for reading.
+ */
+ boolean hasData(final Context context) { // package protected for access from I/O streams
+ return context.buffer != null;
+ }
+
+ /**
+ * Returns the amount of buffered data available for reading.
+ *
+ * @param context the context to be used
+ * @return The amount of buffered data available for reading.
+ */
+ int available(final Context context) { // package protected for access from I/O streams
+ return context.buffer != null ? context.pos - context.readPos : 0;
+ }
+
+ /**
+ * Get the default buffer size. Can be overridden.
+ *
+ * @return {@link #DEFAULT_BUFFER_SIZE}
+ */
+ protected int getDefaultBufferSize() {
+ return DEFAULT_BUFFER_SIZE;
+ }
+
+ /**
+ * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
+ * @param context the context to be used
+ */
+ private byte[] resizeBuffer(final Context context) {
+ if (context.buffer == null) {
+ context.buffer = new byte[getDefaultBufferSize()];
+ context.pos = 0;
+ context.readPos = 0;
+ } else {
+ final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR];
+ System.arraycopy(context.buffer, 0, b, 0, context.buffer.length);
+ context.buffer = b;
+ }
+ return context.buffer;
+ }
+
+ /**
+ * Ensure that the buffer has room for <code>size</code> bytes
+ *
+ * @param size minimum spare space required
+ * @param context the context to be used
+ */
+ protected byte[] ensureBufferSize(final int size, final Context context){
+ if ((context.buffer == null) || (context.buffer.length < context.pos + size)){
+ return resizeBuffer(context);
+ }
+ return context.buffer;
+ }
+
+ /**
+ * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail
+ * bytes. Returns how many bytes were actually extracted.
+ * <p>
+ * Package protected for access from I/O streams.
+ *
+ * @param b
+ * byte[] array to extract the buffered data into.
+ * @param bPos
+ * position in byte[] array to start extraction at.
+ * @param bAvail
+ * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available).
+ * @param context
+ * the context to be used
+ * @return The number of bytes successfully extracted into the provided byte[] array.
+ */
+ int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) {
+ if (context.buffer != null) {
+ final int len = Math.min(available(context), bAvail);
+ System.arraycopy(context.buffer, context.readPos, b, bPos, len);
+ context.readPos += len;
+ if (context.readPos >= context.pos) {
+ context.buffer = null; // so hasData() will return false, and this method can return -1
+ }
+ return len;
+ }
+ return context.eof ? EOF : 0;
+ }
+
+ /**
+ * Checks if a byte value is whitespace or not.
+ * Whitespace is taken to mean: space, tab, CR, LF
+ * @param byteToCheck
+ * the byte to check
+ * @return true if byte is whitespace, false otherwise
+ */
+ protected static boolean isWhiteSpace(final byte byteToCheck) {
+ switch (byteToCheck) {
+ case ' ' :
+ case '\n' :
+ case '\r' :
+ case '\t' :
+ return true;
+ default :
+ return false;
+ }
+ }
+
+ /**
+ * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
+ * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[].
+ *
+ * @param obj
+ * Object to encode
+ * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied.
+ * @throws EncoderException
+ * if the parameter supplied is not of type byte[]
+ */
+ @Override
+ public Object encode(final Object obj) throws EncoderException {
+ if (!(obj instanceof byte[])) {
+ throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]");
+ }
+ return encode((byte[]) obj);
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
+ * Uses UTF8 encoding.
+ *
+ * @param pArray
+ * a byte array containing binary data
+ * @return A String containing only Base-N character data
+ */
+ public String encodeToString(final byte[] pArray) {
+ return StringUtils.newStringUtf8(encode(pArray));
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet.
+ * Uses UTF8 encoding.
+ *
+ * @param pArray a byte array containing binary data
+ * @return String containing only character data in the appropriate alphabet.
+ */
+ public String encodeAsString(final byte[] pArray){
+ return StringUtils.newStringUtf8(encode(pArray));
+ }
+
+ /**
+ * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of
+ * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String.
+ *
+ * @param obj
+ * Object to decode
+ * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String
+ * supplied.
+ * @throws DecoderException
+ * if the parameter supplied is not of type byte[]
+ */
+ @Override
+ public Object decode(final Object obj) throws DecoderException {
+ if (obj instanceof byte[]) {
+ return decode((byte[]) obj);
+ } else if (obj instanceof String) {
+ return decode((String) obj);
+ } else {
+ throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String");
+ }
+ }
+
+ /**
+ * Decodes a String containing characters in the Base-N alphabet.
+ *
+ * @param pArray
+ * A String containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ public byte[] decode(final String pArray) {
+ return decode(StringUtils.getBytesUtf8(pArray));
+ }
+
+ /**
+ * Decodes a byte[] containing characters in the Base-N alphabet.
+ *
+ * @param pArray
+ * A byte array containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ @Override
+ public byte[] decode(final byte[] pArray) {
+ return decode(pArray, 0, pArray.length);
+ }
+
+ public byte[] decode(final byte[] pArray, final int off, final int len) {
+ if (pArray == null || len == 0) {
+ return new byte[0];
+ }
+ final Context context = new Context();
+ decode(pArray, off, len, context);
+ decode(pArray, off, EOF, context); // Notify decoder of EOF.
+ final byte[] result = new byte[context.pos];
+ readResults(result, 0, result.length, context);
+ return result;
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
+ *
+ * @param pArray
+ * a byte array containing binary data
+ * @return A byte array containing only the basen alphabetic character data
+ */
+ @Override
+ public byte[] encode(final byte[] pArray) {
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ final Context context = new Context();
+ encode(pArray, 0, pArray.length, context);
+ encode(pArray, 0, EOF, context); // Notify encoder of EOF.
+ final byte[] buf = new byte[context.pos - context.readPos];
+ readResults(buf, 0, buf.length, context);
+ return buf;
+ }
+
+ // package protected for access from I/O streams
+ abstract void encode(byte[] pArray, int i, int length, Context context);
+
+ // package protected for access from I/O streams
+ abstract void decode(byte[] pArray, int i, int length, Context context);
+
+ /**
+ * Returns whether or not the <code>octet</code> is in the current alphabet.
+ * Does not allow whitespace or pad.
+ *
+ * @param value The value to test
+ *
+ * @return {@code true} if the value is defined in the current alphabet, {@code false} otherwise.
+ */
+ protected abstract boolean isInAlphabet(byte value);
+
+ /**
+ * Tests a given byte array to see if it contains only valid characters within the alphabet.
+ * The method optionally treats whitespace and pad as valid.
+ *
+ * @param arrayOctet byte array to test
+ * @param allowWSPad if {@code true}, then whitespace and PAD are also allowed
+ *
+ * @return {@code true} if all bytes are valid characters in the alphabet or if the byte array is empty;
+ * {@code false}, otherwise
+ */
+ public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) {
+ for (int i = 0; i < arrayOctet.length; i++) {
+ if (!isInAlphabet(arrayOctet[i]) &&
+ (!allowWSPad || (arrayOctet[i] != PAD) && !isWhiteSpace(arrayOctet[i]))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Tests a given String to see if it contains only valid characters within the alphabet.
+ * The method treats whitespace and PAD as valid.
+ *
+ * @param basen String to test
+ * @return {@code true} if all characters in the String are valid characters in the alphabet or if
+ * the String is empty; {@code false}, otherwise
+ * @see #isInAlphabet(byte[], boolean)
+ */
+ public boolean isInAlphabet(final String basen) {
+ return isInAlphabet(StringUtils.getBytesUtf8(basen), true);
+ }
+
+ /**
+ * Tests a given byte array to see if it contains any characters within the alphabet or PAD.
+ *
+ * Intended for use in checking line-ending arrays
+ *
+ * @param arrayOctet
+ * byte array to test
+ * @return {@code true} if any byte is a valid character in the alphabet or PAD; {@code false} otherwise
+ */
+ protected boolean containsAlphabetOrPad(final byte[] arrayOctet) {
+ if (arrayOctet == null) {
+ return false;
+ }
+ for (final byte element : arrayOctet) {
+ if (PAD == element || isInAlphabet(element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Calculates the amount of space needed to encode the supplied array.
+ *
+ * @param pArray byte[] array which will later be encoded
+ *
+ * @return amount of space needed to encoded the supplied array.
+ * Returns a long since a max-len array will require > Integer.MAX_VALUE
+ */
+ public long getEncodedLength(final byte[] pArray) {
+ // Calculate non-chunked size - rounded up to allow for padding
+ // cast to long is needed to avoid possibility of overflow
+ long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize;
+ if (lineLength > 0) { // We're using chunking
+ // Round up to nearest multiple
+ len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength;
+ }
+ return len;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/StringUtils.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/StringUtils.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/StringUtils.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,91 @@
+/*
+ * 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.util.codec.binary;
+
+import java.nio.charset.Charset;
+
+import org.apache.tomcat.util.buf.EncodingToCharset;
+
+/**
+ * Converts String to and from bytes using the encodings required by the Java specification. These encodings are
+ * specified in <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">
+ * Standard charsets</a>.
+ *
+ * <p>This class is immutable and thread-safe.</p>
+ *
+ * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
+ * @version $Id: StringUtils.java 1459218 2013-03-21 10:31:50Z markt $
+ * @since 1.4
+ */
+public class StringUtils {
+
+ /**
+ * Calls {@link String#getBytes(Charset)}
+ *
+ * @param string
+ * The string to encode (if null, return null).
+ * @param charset
+ * The {@link Charset} to encode the {@code String}
+ * @return the encoded bytes
+ */
+ private static byte[] getBytes(final String string, final Charset charset) {
+ if (string == null) {
+ return null;
+ }
+ return string.getBytes(charset);
+ }
+
+ /**
+ * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte
+ * array.
+ *
+ * @param string
+ * the String to encode, may be {@code null}
+ * @return encoded bytes, or {@code null} if the input string was {@code null}
+ * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
+ */
+ public static byte[] getBytesUtf8(final String string) {
+ return getBytes(string, EncodingToCharset.UTF_8);
+ }
+
+ /**
+ * Constructs a new <code>String</code> by decoding the specified array of bytes using the given charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters
+ * @param charset
+ * The {@link Charset} to encode the {@code String}
+ * @return A new <code>String</code> decoded from the specified array of bytes using the given charset,
+ * or {@code null} if the input byte array was {@code null}.
+ */
+ private static String newString(final byte[] bytes, final Charset charset) {
+ return bytes == null ? null : new String(bytes, charset);
+ }
+
+ /**
+ * Constructs a new <code>String</code> by decoding the specified array of bytes using the UTF-8 charset.
+ *
+ * @param bytes
+ * The bytes to be decoded into characters
+ * @return A new <code>String</code> decoded from the specified array of bytes using the UTF-8 charset,
+ * or {@code null} if the input byte array was {@code null}.
+ */
+ public static String newStringUtf8(final byte[] bytes) {
+ return newString(bytes, EncodingToCharset.UTF_8);
+ }
+
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/package.html
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/package.html (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/codec/binary/package.html 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,21 @@
+<!--
+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.
+-->
+<html>
+ <body>
+ Base64, Base32, Binary, and Hexadecimal String encoding and decoding.
+ </body>
+</html>
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/Constants.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/Constants.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/Constants.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,27 @@
+/*
+ * 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.util.threads;
+
+/**
+ * Static constants for this package.
+ */
+public final class Constants {
+
+ public static final String Package = "org.apache.tomcat.util.threads";
+
+ public static final long DEFAULT_THREAD_RENEWAL_DELAY = 1000L;
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/TaskQueue.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/TaskQueue.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/TaskQueue.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,123 @@
+/*
+ * 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.util.threads;
+
+import java.util.Collection;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * As task queue specifically designed to run with a thread pool executor.
+ * The task queue is optimised to properly utilize threads within
+ * a thread pool executor. If you use a normal queue, the executor will spawn threads
+ * when there are idle threads and you wont be able to force items unto the queue itself
+ * @author fhanik
+ *
+ */
+public class TaskQueue extends LinkedBlockingQueue<Runnable> {
+
+ private static final long serialVersionUID = 1L;
+
+ private ThreadPoolExecutor parent = null;
+
+ // no need to be volatile, the one times when we change and read it occur in
+ // a single thread (the one that did stop a context and fired listeners)
+ private Integer forcedRemainingCapacity = null;
+
+ public TaskQueue() {
+ super();
+ }
+
+ public TaskQueue(int capacity) {
+ super(capacity);
+ }
+
+ public TaskQueue(Collection<? extends Runnable> c) {
+ super(c);
+ }
+
+ public void setParent(ThreadPoolExecutor tp) {
+ parent = tp;
+ }
+
+ public boolean force(Runnable o) {
+ if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
+ return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
+ }
+
+ public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
+ if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
+ return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected
+ }
+
+ @Override
+ public boolean offer(Runnable o) {
+ //we can't do any checks
+ if (parent==null) return super.offer(o);
+ //we are maxed out on threads, simply queue the object
+ if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
+ //we have idle threads, just add it to the queue
+ if (parent.getSubmittedCount()<(parent.getPoolSize())) return super.offer(o);
+ //if we have less threads than maximum force creation of a new thread
+ if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
+ //if we reached here, we need to add it to the queue
+ return super.offer(o);
+ }
+
+
+ @Override
+ public Runnable poll(long timeout, TimeUnit unit)
+ throws InterruptedException {
+ Runnable runnable = super.poll(timeout, unit);
+ if (runnable == null && parent != null) {
+ // the poll timed out, it gives an opportunity to stop the current
+ // thread if needed to avoid memory leaks.
+ parent.stopCurrentThreadIfNeeded();
+ }
+ return runnable;
+ }
+
+ @Override
+ public Runnable take() throws InterruptedException {
+ if (parent != null && parent.currentThreadShouldBeStopped()) {
+ return poll(parent.getKeepAliveTime(TimeUnit.MILLISECONDS),
+ TimeUnit.MILLISECONDS);
+ // yes, this may return null (in case of timeout) which normally
+ // does not occur with take()
+ // but the ThreadPoolExecutor implementation allows this
+ }
+ return super.take();
+ }
+
+ @Override
+ public int remainingCapacity() {
+ if (forcedRemainingCapacity != null) {
+ // ThreadPoolExecutor.setCorePoolSize checks that
+ // remainingCapacity==0 to allow to interrupt idle threads
+ // I don't see why, but this hack allows to conform to this
+ // "requirement"
+ return forcedRemainingCapacity.intValue();
+ }
+ return super.remainingCapacity();
+ }
+
+ public void setForcedRemainingCapacity(Integer forcedRemainingCapacity) {
+ this.forcedRemainingCapacity = forcedRemainingCapacity;
+ }
+
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/TaskThread.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/TaskThread.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/TaskThread.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,45 @@
+/*
+ * 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.util.threads;
+
+/**
+ * A Thread implementation that records the time at which it was created.
+ *
+ */
+public class TaskThread extends Thread {
+
+ private final long creationTime;
+
+ public TaskThread(ThreadGroup group, Runnable target, String name) {
+ super(group, target, name);
+ this.creationTime = System.currentTimeMillis();
+ }
+
+ public TaskThread(ThreadGroup group, Runnable target, String name,
+ long stackSize) {
+ super(group, target, name, stackSize);
+ this.creationTime = System.currentTimeMillis();
+ }
+
+ /**
+ * @return the time (in ms) at which this thread was created
+ */
+ public final long getCreationTime() {
+ return creationTime;
+ }
+
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,237 @@
+/*
+ * 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.util.threads;
+
+import static org.jboss.web.CoyoteMessages.MESSAGES;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.jboss.web.CoyoteLogger;
+
+/**
+ * Same as a java.util.concurrent.ThreadPoolExecutor but implements a much more efficient
+ * {@link #getSubmittedCount()} method, to be used to properly handle the work queue.
+ * If a RejectedExecutionHandler is not specified a default one will be configured
+ * and that one will always throw a RejectedExecutionException
+ * @author fhanik
+ *
+ */
+public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
+
+ /**
+ * The number of tasks submitted but not yet finished. This includes tasks
+ * in the queue and tasks that have been handed to a worker thread but the
+ * latter did not start executing the task yet.
+ * This number is always greater or equal to {@link #getActiveCount()}.
+ */
+ private final AtomicInteger submittedCount = new AtomicInteger(0);
+ private final AtomicLong lastContextStoppedTime = new AtomicLong(0L);
+
+ /**
+ * Most recent time in ms when a thread decided to kill itself to avoid
+ * potential memory leaks. Useful to throttle the rate of renewals of
+ * threads.
+ */
+ private final AtomicLong lastTimeThreadKilledItself = new AtomicLong(0L);
+
+ /**
+ * Delay in ms between 2 threads being renewed. If negative, do not renew threads.
+ */
+ private long threadRenewalDelay = Constants.DEFAULT_THREAD_RENEWAL_DELAY;
+
+ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
+ }
+
+ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
+ RejectedExecutionHandler handler) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
+ }
+
+ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new RejectHandler());
+ }
+
+ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new RejectHandler());
+ }
+
+ public long getThreadRenewalDelay() {
+ return threadRenewalDelay;
+ }
+
+ public void setThreadRenewalDelay(long threadRenewalDelay) {
+ this.threadRenewalDelay = threadRenewalDelay;
+ }
+
+ @Override
+ protected void afterExecute(Runnable r, Throwable t) {
+ submittedCount.decrementAndGet();
+
+ if (t == null) {
+ stopCurrentThreadIfNeeded();
+ }
+ }
+
+ /**
+ * If the current thread was started before the last time when a context was
+ * stopped, an exception is thrown so that the current thread is stopped.
+ */
+ protected void stopCurrentThreadIfNeeded() {
+ if (currentThreadShouldBeStopped()) {
+ long lastTime = lastTimeThreadKilledItself.longValue();
+ if (lastTime + threadRenewalDelay < System.currentTimeMillis()) {
+ if (lastTimeThreadKilledItself.compareAndSet(lastTime,
+ System.currentTimeMillis() + 1)) {
+ // OK, it's really time to dispose of this thread
+
+ final String msg = MESSAGES.threadStopped(Thread.currentThread().getName());
+
+ Thread.currentThread().setUncaughtExceptionHandler(
+ new UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread t,
+ Throwable e) {
+ // yes, swallow the exception
+ CoyoteLogger.UTIL_LOGGER.debug(msg);
+ }
+ });
+ throw new RuntimeException(msg);
+ }
+ }
+ }
+ }
+
+ protected boolean currentThreadShouldBeStopped() {
+ if (threadRenewalDelay >= 0
+ && Thread.currentThread() instanceof TaskThread) {
+ TaskThread currentTaskThread = (TaskThread) Thread.currentThread();
+ if (currentTaskThread.getCreationTime() <
+ this.lastContextStoppedTime.longValue()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int getSubmittedCount() {
+ return submittedCount.get();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void execute(Runnable command) {
+ execute(command,0,TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Executes the given command at some time in the future. The command
+ * may execute in a new thread, in a pooled thread, or in the calling
+ * thread, at the discretion of the <tt>Executor</tt> implementation.
+ * If no threads are available, it will be added to the work queue.
+ * If the work queue is full, the system will wait for the specified
+ * time and it throw a RejectedExecutionException if the queue is still
+ * full after that.
+ *
+ * @param command the runnable task
+ * @throws RejectedExecutionException if this task cannot be
+ * accepted for execution - the queue is full
+ * @throws NullPointerException if command or unit is null
+ */
+ public void execute(Runnable command, long timeout, TimeUnit unit) {
+ submittedCount.incrementAndGet();
+ try {
+ super.execute(command);
+ } catch (RejectedExecutionException rx) {
+ if (super.getQueue() instanceof TaskQueue) {
+ final TaskQueue queue = (TaskQueue)super.getQueue();
+ try {
+ if (!queue.force(command, timeout, unit)) {
+ submittedCount.decrementAndGet();
+ throw new RejectedExecutionException("Queue capacity is full.");
+ }
+ } catch (InterruptedException x) {
+ submittedCount.decrementAndGet();
+ Thread.interrupted();
+ throw new RejectedExecutionException(x);
+ }
+ } else {
+ submittedCount.decrementAndGet();
+ throw rx;
+ }
+
+ }
+ }
+
+ public void contextStopping() {
+ this.lastContextStoppedTime.set(System.currentTimeMillis());
+
+ // save the current pool parameters to restore them later
+ int savedCorePoolSize = this.getCorePoolSize();
+ TaskQueue taskQueue =
+ getQueue() instanceof TaskQueue ? (TaskQueue) getQueue() : null;
+ if (taskQueue != null) {
+ // note by slaurent : quite oddly threadPoolExecutor.setCorePoolSize
+ // checks that queue.remainingCapacity()==0. I did not understand
+ // why, but to get the intended effect of waking up idle threads, I
+ // temporarily fake this condition.
+ taskQueue.setForcedRemainingCapacity(Integer.valueOf(0));
+ }
+
+ // setCorePoolSize(0) wakes idle threads
+ this.setCorePoolSize(0);
+
+ // wait a little so that idle threads wake and poll the queue again,
+ // this time always with a timeout (queue.poll() instead of
+ // queue.take())
+ // even if we did not wait enough, TaskQueue.take() takes care of timing
+ // out, so that we are sure that all threads of the pool are renewed in
+ // a limited time, something like
+ // (threadKeepAlive + longest request time)
+ try {
+ Thread.sleep(200L);
+ } catch (InterruptedException e) {
+ // yes, ignore
+ }
+
+ if (taskQueue != null) {
+ // ok, restore the state of the queue and pool
+ taskQueue.setForcedRemainingCapacity(null);
+ }
+ this.setCorePoolSize(savedCorePoolSize);
+ }
+
+ private static class RejectHandler implements RejectedExecutionHandler {
+ @Override
+ public void rejectedExecution(Runnable r,
+ java.util.concurrent.ThreadPoolExecutor executor) {
+ throw new RejectedExecutionException();
+ }
+
+ }
+
+
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapper.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,47 @@
+/*
+ * 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.websocket;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLException;
+
+/**
+ * This is a wrapper for a {@link java.nio.channels.AsynchronousSocketChannel}
+ * that limits the methods available thereby simplifying the process of
+ * implementing SSL/TLS support since there are fewer methods to intercept.
+ */
+public interface AsyncChannelWrapper {
+
+ Future<Integer> read(ByteBuffer dst);
+
+ <B,A extends B> void read(ByteBuffer dst, A attachment,
+ CompletionHandler<Integer,B> handler);
+
+ Future<Integer> write(ByteBuffer src);
+
+ <B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
+ long timeout, TimeUnit unit, A attachment,
+ CompletionHandler<Long,B> handler);
+
+ void close();
+
+ Future<Void> handshake() throws SSLException;
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapperNonSecure.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,112 @@
+/*
+ * 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.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Generally, just passes calls straight to the wrapped
+ * {@link AsynchronousSocketChannel}. In some cases exceptions may be swallowed
+ * to save them being swallowed by the calling code.
+ */
+public class AsyncChannelWrapperNonSecure implements AsyncChannelWrapper {
+
+ private static final Future<Void> NOOP_FUTURE = new NoOpFuture();
+
+ private final AsynchronousSocketChannel socketChannel;
+
+ public AsyncChannelWrapperNonSecure(
+ AsynchronousSocketChannel socketChannel) {
+ this.socketChannel = socketChannel;
+ }
+
+ @Override
+ public Future<Integer> read(ByteBuffer dst) {
+ return socketChannel.read(dst);
+ }
+
+ @Override
+ public <B,A extends B> void read(ByteBuffer dst, A attachment,
+ CompletionHandler<Integer,B> handler) {
+ socketChannel.read(dst, attachment, handler);
+ }
+
+ @Override
+ public Future<Integer> write(ByteBuffer src) {
+ return socketChannel.write(src);
+ }
+
+ @Override
+ public <B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
+ long timeout, TimeUnit unit, A attachment,
+ CompletionHandler<Long,B> handler) {
+ socketChannel.write(
+ srcs, offset, length, timeout, unit, attachment, handler);
+ }
+
+ @Override
+ public void close() {
+ try {
+ socketChannel.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ @Override
+ public Future<Void> handshake() {
+ return NOOP_FUTURE;
+ }
+
+
+ private static final class NoOpFuture implements Future<Void> {
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public Void get() throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public Void get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException,
+ TimeoutException {
+ return null;
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/AsyncChannelWrapperSecure.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,541 @@
+/*
+ * 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.websocket;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+
+import org.jboss.web.WebsocketsLogger;
+
+/**
+ * Wraps the {@link AsynchronousSocketChannel} with SSL/TLS. This needs a lot
+ * more testing before it can be considered robust.
+ */
+public class AsyncChannelWrapperSecure implements AsyncChannelWrapper {
+
+ private static final ByteBuffer DUMMY = ByteBuffer.allocate(8192);
+ private final AsynchronousSocketChannel socketChannel;
+ private final SSLEngine sslEngine;
+ private final ByteBuffer socketReadBuffer;
+ private final ByteBuffer socketWriteBuffer;
+ // One thread for read, one for write
+ private final ExecutorService executor = Executors.newFixedThreadPool(2);
+ private AtomicBoolean writing = new AtomicBoolean(false);
+ private AtomicBoolean reading = new AtomicBoolean(false);
+
+ public AsyncChannelWrapperSecure(AsynchronousSocketChannel socketChannel,
+ SSLEngine sslEngine) {
+ this.socketChannel = socketChannel;
+ this.sslEngine = sslEngine;
+
+ int socketBufferSize = sslEngine.getSession().getPacketBufferSize();
+ socketReadBuffer = ByteBuffer.allocateDirect(socketBufferSize);
+ socketWriteBuffer = ByteBuffer.allocateDirect(socketBufferSize);
+ }
+
+ @Override
+ public Future<Integer> read(ByteBuffer dst) {
+ WrapperFuture<Integer,Void> future = new WrapperFuture<Integer, Void>();
+
+ if (!reading.compareAndSet(false, true)) {
+ throw MESSAGES.invalidConcurrentRead();
+ }
+
+ ReadTask readTask = new ReadTask(dst, future);
+
+ executor.execute(readTask);
+
+ return future;
+ }
+
+ @Override
+ public <B,A extends B> void read(ByteBuffer dst, A attachment,
+ CompletionHandler<Integer,B> handler) {
+
+ WrapperFuture<Integer,B> future =
+ new WrapperFuture<Integer, B>(handler, attachment);
+
+ if (!reading.compareAndSet(false, true)) {
+ throw MESSAGES.invalidConcurrentRead();
+ }
+
+ ReadTask readTask = new ReadTask(dst, future);
+
+ executor.execute(readTask);
+ }
+
+ @Override
+ public Future<Integer> write(ByteBuffer src) {
+
+ WrapperFuture<Long,Void> inner = new WrapperFuture<Long, Void>();
+
+ if (!writing.compareAndSet(false, true)) {
+ throw MESSAGES.invalidConcurrentWrite();
+ }
+
+ WriteTask writeTask =
+ new WriteTask(new ByteBuffer[] {src}, 0, 1, inner);
+
+ executor.execute(writeTask);
+
+ Future<Integer> future = new LongToIntegerFuture(inner);
+ return future;
+ }
+
+ @Override
+ public <B,A extends B> void write(ByteBuffer[] srcs, int offset, int length,
+ long timeout, TimeUnit unit, A attachment,
+ CompletionHandler<Long,B> handler) {
+
+ WrapperFuture<Long,B> future =
+ new WrapperFuture<Long, B>(handler, attachment);
+
+ if (!writing.compareAndSet(false, true)) {
+ throw MESSAGES.invalidConcurrentWrite();
+ }
+
+ WriteTask writeTask = new WriteTask(srcs, offset, length, future);
+
+ executor.execute(writeTask);
+ }
+
+ @Override
+ public void close() {
+ try {
+ socketChannel.close();
+ } catch (IOException e) {
+ WebsocketsLogger.ROOT_LOGGER.errorClose();
+ }
+ }
+
+ @Override
+ public Future<Void> handshake() throws SSLException {
+
+ WrapperFuture<Void,Void> wFuture = new WrapperFuture<Void, Void>();
+
+ Thread t = new WebSocketSslHandshakeThread(wFuture);
+ t.start();
+
+ return wFuture;
+ }
+
+
+ private class WriteTask implements Runnable {
+
+ private final ByteBuffer[] srcs;
+ private final int offset;
+ private final int length;
+ private final WrapperFuture<Long,?> future;
+
+ public WriteTask(ByteBuffer[] srcs, int offset, int length,
+ WrapperFuture<Long,?> future) {
+ this.srcs = srcs;
+ this.future = future;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ @Override
+ public void run() {
+ long written = 0;
+
+ try {
+ for (int i = offset; i < offset + length; i++) {
+ ByteBuffer src = srcs[i];
+ while (src.hasRemaining()) {
+ socketWriteBuffer.clear();
+
+ // Encrypt the data
+ SSLEngineResult r = sslEngine.wrap(src, socketWriteBuffer);
+ written += r.bytesConsumed();
+ Status s = r.getStatus();
+
+ if (s == Status.OK || s == Status.BUFFER_OVERFLOW) {
+ // Need to write out the bytes and may need to read from
+ // the source again to empty it
+ } else {
+ // Status.BUFFER_UNDERFLOW - only happens on unwrap
+ // Status.CLOSED - unexpected
+ throw MESSAGES.unexpectedStatusAfterWrap();
+ }
+
+ // Check for tasks
+ if (r.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+ Runnable runnable = sslEngine.getDelegatedTask();
+ while (runnable != null) {
+ runnable.run();
+ runnable = sslEngine.getDelegatedTask();
+ }
+ }
+
+ socketWriteBuffer.flip();
+
+ // Do the write
+ int toWrite = r.bytesProduced();
+ while (toWrite > 0) {
+ Future<Integer> f =
+ socketChannel.write(socketWriteBuffer);
+ Integer socketWrite = f.get();
+ toWrite -= socketWrite.intValue();
+ }
+ }
+ }
+
+
+ if (writing.compareAndSet(true, false)) {
+ future.complete(Long.valueOf(written));
+ } else {
+ future.fail(MESSAGES.invalidWriteState());
+ }
+ } catch (Exception e) {
+ future.fail(e);
+ }
+ }
+ }
+
+
+ private class ReadTask implements Runnable {
+
+ private final ByteBuffer dest;
+ private final WrapperFuture<Integer,?> future;
+
+ public ReadTask(ByteBuffer dest, WrapperFuture<Integer,?> future) {
+ this.dest = dest;
+ this.future = future;
+ }
+
+ @Override
+ public void run() {
+ int read = 0;
+
+ boolean forceRead = false;
+
+ try {
+ while (read == 0) {
+ socketReadBuffer.compact();
+
+ if (forceRead) {
+ Future<Integer> f =
+ socketChannel.read(socketReadBuffer);
+ Integer socketRead = f.get();
+ if (socketRead.intValue() == -1) {
+ throw new EOFException(MESSAGES.unexpectedEndOfStream());
+ }
+ }
+
+ socketReadBuffer.flip();
+
+ if (socketReadBuffer.hasRemaining()) {
+ // Decrypt the data in the buffer
+ SSLEngineResult r =
+ sslEngine.unwrap(socketReadBuffer, dest);
+ read += r.bytesProduced();
+ Status s = r.getStatus();
+
+ if (s == Status.OK) {
+ // Bytes available for reading and there may be
+ // sufficient data in the socketReadBuffer to
+ // support further reads without reading from the
+ // socket
+ } else if (s == Status.BUFFER_UNDERFLOW) {
+ // There is partial data in the socketReadBuffer
+ if (read == 0) {
+ // Need more data before the partial data can be
+ // processed and some output generated
+ forceRead = true;
+ }
+ // else return the data we have and deal with the
+ // partial data on the next read
+ } else if (s == Status.BUFFER_OVERFLOW) {
+ // Not enough space in the destination buffer to
+ // store all of the data. We could use a bytes read
+ // value of -bufferSizeRequired to signal the new
+ // buffer size required but an explicit exception is
+ // clearer.
+ if (reading.compareAndSet(true, false)) {
+ throw new ReadBufferOverflowException(sslEngine.
+ getSession().getApplicationBufferSize());
+ } else {
+ future.fail(MESSAGES.invalidReadState());
+ }
+ } else {
+ // Status.CLOSED - unexpected
+ throw MESSAGES.unexpectedStatusAfterUnwrap();
+ }
+
+ // Check for tasks
+ if (r.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+ Runnable runnable = sslEngine.getDelegatedTask();
+ while (runnable != null) {
+ runnable.run();
+ runnable = sslEngine.getDelegatedTask();
+ }
+ }
+ } else {
+ forceRead = true;
+ }
+ }
+
+
+ if (reading.compareAndSet(true, false)) {
+ future.complete(Integer.valueOf(read));
+ } else {
+ future.fail(MESSAGES.invalidReadState());
+ }
+ } catch (Exception e) {
+ future.fail(e);
+ }
+ }
+ }
+
+
+ private class WebSocketSslHandshakeThread extends Thread {
+
+ private final WrapperFuture<Void,Void> hFuture;
+
+ private HandshakeStatus handshakeStatus;
+ private Status resultStatus;
+
+ public WebSocketSslHandshakeThread(WrapperFuture<Void,Void> hFuture) {
+ this.hFuture = hFuture;
+ }
+
+ @Override
+ public void run() {
+ try {
+ sslEngine.beginHandshake();
+ // So the first compact does the right thing
+ socketReadBuffer.position(socketReadBuffer.limit());
+
+ handshakeStatus = sslEngine.getHandshakeStatus();
+ resultStatus = Status.OK;
+
+ boolean handshaking = true;
+
+ while(handshaking) {
+ switch (handshakeStatus) {
+ case NEED_WRAP: {
+ socketWriteBuffer.clear();
+ SSLEngineResult r =
+ sslEngine.wrap(DUMMY, socketWriteBuffer);
+ checkResult(r, true);
+ socketWriteBuffer.flip();
+ Future<Integer> fWrite =
+ socketChannel.write(socketWriteBuffer);
+ fWrite.get();
+ break;
+ }
+ case NEED_UNWRAP: {
+ socketReadBuffer.compact();
+ if (socketReadBuffer.position() == 0 ||
+ resultStatus == Status.BUFFER_UNDERFLOW) {
+ Future<Integer> fRead =
+ socketChannel.read(socketReadBuffer);
+ fRead.get();
+ }
+ socketReadBuffer.flip();
+ SSLEngineResult r =
+ sslEngine.unwrap(socketReadBuffer, DUMMY);
+ checkResult(r, false);
+ break;
+ }
+ case NEED_TASK: {
+ Runnable r = null;
+ while ((r = sslEngine.getDelegatedTask()) != null) {
+ r.run();
+ }
+ handshakeStatus = sslEngine.getHandshakeStatus();
+ break;
+ }
+ case FINISHED: {
+ handshaking = false;
+ break;
+ }
+ default: {
+ throw new SSLException("TODO");
+ }
+ }
+ }
+ } catch (SSLException e) {
+ hFuture.fail(e);
+ } catch (InterruptedException e) {
+ hFuture.fail(e);
+ } catch (ExecutionException e) {
+ hFuture.fail(e);
+ }
+
+ hFuture.complete(null);
+ }
+
+ private void checkResult(SSLEngineResult result, boolean wrap)
+ throws SSLException {
+
+ handshakeStatus = result.getHandshakeStatus();
+ resultStatus = result.getStatus();
+
+ if (resultStatus != Status.OK &&
+ (wrap || resultStatus != Status.BUFFER_UNDERFLOW)) {
+ throw new SSLException("TODO");
+ }
+ if (wrap && result.bytesConsumed() != 0) {
+ throw new SSLException("TODO");
+ }
+ if (!wrap && result.bytesProduced() != 0) {
+ throw new SSLException("TODO");
+ }
+ }
+ }
+
+
+ private static class WrapperFuture<T,A> implements Future<T> {
+
+ private final CompletionHandler<T,A> handler;
+ private final A attachment;
+
+ private volatile T result = null;
+ private volatile Throwable throwable = null;
+ private CountDownLatch completionLatch = new CountDownLatch(1);
+
+ public WrapperFuture() {
+ this(null, null);
+ }
+
+ public WrapperFuture(CompletionHandler<T,A> handler, A attachment) {
+ this.handler = handler;
+ this.attachment = attachment;
+ }
+
+ public void complete(T result) {
+ this.result = result;
+ completionLatch.countDown();
+ if (handler != null) {
+ handler.completed(result, attachment);
+ }
+ }
+
+ public void fail(Throwable t) {
+ throwable = t;
+ completionLatch.countDown();
+ if (handler != null) {
+ handler.failed(throwable, attachment);
+ }
+ }
+
+ @Override
+ public final boolean cancel(boolean mayInterruptIfRunning) {
+ // Could support cancellation by closing the connection
+ return false;
+ }
+
+ @Override
+ public final boolean isCancelled() {
+ // Could support cancellation by closing the connection
+ return false;
+ }
+
+ @Override
+ public final boolean isDone() {
+ return completionLatch.getCount() > 0;
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ completionLatch.await();
+ if (throwable != null) {
+ throw new ExecutionException(throwable);
+ }
+ return result;
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException,
+ TimeoutException {
+ boolean latchResult = completionLatch.await(timeout, unit);
+ if (latchResult == false) {
+ throw new TimeoutException();
+ }
+ if (throwable != null) {
+ throw new ExecutionException(throwable);
+ }
+ return result;
+ }
+ }
+
+ private static final class LongToIntegerFuture implements Future<Integer> {
+
+ private final Future<Long> wrapped;
+
+ public LongToIntegerFuture(Future<Long> wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return wrapped.cancel(mayInterruptIfRunning);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return wrapped.isCancelled();
+ }
+
+ @Override
+ public boolean isDone() {
+ return wrapped.isDone();
+ }
+
+ @Override
+ public Integer get() throws InterruptedException, ExecutionException {
+ Long result = wrapped.get();
+ if (result.longValue() > Integer.MAX_VALUE) {
+ throw new ExecutionException(MESSAGES.notAnInteger(result), null);
+ }
+ return new Integer(result.intValue());
+ }
+
+ @Override
+ public Integer get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException,
+ TimeoutException {
+ Long result = wrapped.get(timeout, unit);
+ if (result.longValue() > Integer.MAX_VALUE) {
+ throw new ExecutionException(MESSAGES.notAnInteger(result), null);
+ }
+ return new Integer(result.intValue());
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/BackgroundProcess.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/BackgroundProcess.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/BackgroundProcess.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,26 @@
+/*
+ * 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.websocket;
+
+public interface BackgroundProcess {
+
+ void backgroundProcess();
+
+ void setProcessPeriod(int period);
+
+ int getProcessPeriod();
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/BackgroundProcessManager.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/BackgroundProcessManager.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/BackgroundProcessManager.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,121 @@
+/*
+ * 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.websocket;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tomcat.util.ExceptionUtils;
+import org.jboss.web.WebsocketsLogger;
+
+/**
+ * Provides a background processing mechanism that triggers roughly once a
+ * second. The class maintains a thread that only runs when there is at least
+ * one instance of {@link BackgroundProcess} registered.
+ */
+public class BackgroundProcessManager {
+
+ private static final BackgroundProcessManager instance;
+
+
+ static {
+ instance = new BackgroundProcessManager();
+ }
+
+
+ public static BackgroundProcessManager getInstance() {
+ return instance;
+ }
+
+ private final Set<BackgroundProcess> processes = new HashSet<BackgroundProcess>();
+ private final Object processesLock = new Object();
+ private WsBackgroundThread wsBackgroundThread = null;
+
+ private BackgroundProcessManager() {
+ // Hide default constructor
+ }
+
+
+ public void register(BackgroundProcess process) {
+ synchronized (processesLock) {
+ if (processes.size() == 0) {
+ wsBackgroundThread = new WsBackgroundThread(this);
+ wsBackgroundThread.setContextClassLoader(
+ this.getClass().getClassLoader());
+ wsBackgroundThread.setDaemon(true);
+ wsBackgroundThread.start();
+ }
+ processes.add(process);
+ }
+ }
+
+
+ public void unregister(BackgroundProcess process) {
+ synchronized (processesLock) {
+ processes.remove(process);
+ if (wsBackgroundThread != null && processes.size() == 0) {
+ wsBackgroundThread.halt();
+ wsBackgroundThread = null;
+ }
+ }
+ }
+
+
+ private void process() {
+ Set<BackgroundProcess> currentProcesses = new HashSet<BackgroundProcess>();
+ synchronized (processesLock) {
+ currentProcesses.addAll(processes);
+ }
+ for (BackgroundProcess process : currentProcesses) {
+ try {
+ process.backgroundProcess();
+ } catch (Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ WebsocketsLogger.ROOT_LOGGER.backgroundProcessFailed(t);
+ }
+ }
+ }
+
+
+ private static class WsBackgroundThread extends Thread {
+
+ private final BackgroundProcessManager manager;
+ private volatile boolean running = true;
+
+ public WsBackgroundThread(BackgroundProcessManager manager) {
+ setName("WebSocket background processing");
+ this.manager = manager;
+ }
+
+ @Override
+ public void run() {
+ while (running) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ manager.process();
+ }
+ }
+
+ public void halt() {
+ setName("WebSocket background processing - stopping");
+ running = false;
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/Constants.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/Constants.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/Constants.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,67 @@
+/*
+ * 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.websocket;
+
+import java.util.Locale;
+
+/**
+ * Internal implementation constants.
+ */
+public class Constants {
+
+ protected static final String PACKAGE_NAME =
+ Constants.class.getPackage().getName();
+ // OP Codes
+ public static final byte OPCODE_CONTINUATION = 0x00;
+ public static final byte OPCODE_TEXT = 0x01;
+ public static final byte OPCODE_BINARY = 0x02;
+ public static final byte OPCODE_CLOSE = 0x08;
+ public static final byte OPCODE_PING = 0x09;
+ public static final byte OPCODE_PONG = 0x0A;
+
+ // Internal OP Codes
+ // RFC 6455 limits OP Codes to 4 bits so these should never clash
+ // Always set bit 4 so these will be treated as control codes
+ static final byte INTERNAL_OPCODE_FLUSH = 0x18;
+
+ // Buffers
+ static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
+
+ // Client connection
+ public static final String HOST_HEADER_NAME = "Host";
+ public static final String UPGRADE_HEADER_NAME = "Upgrade";
+ public static final String UPGRADE_HEADER_VALUE = "websocket";
+ public static final String CONNECTION_HEADER_NAME = "Connection";
+ public static final String CONNECTION_HEADER_VALUE = "upgrade";
+ public static final String WS_VERSION_HEADER_NAME = "Sec-WebSocket-Version";
+ public static final String WS_VERSION_HEADER_VALUE = "13";
+ public static final String WS_KEY_HEADER_NAME = "Sec-WebSocket-Key";
+ public static final String WS_PROTOCOL_HEADER_NAME =
+ "Sec-WebSocket-Protocol";
+ public static final String WS_PROTOCOL_HEADER_NAME_LOWER =
+ WS_PROTOCOL_HEADER_NAME.toLowerCase(Locale.ENGLISH);
+ public static final String WS_EXTENSIONS_HEADER_NAME =
+ "Sec-WebSocket-Extensions";
+
+ public static final boolean STRICT_SPEC_COMPLIANCE =
+ Boolean.getBoolean(
+ "org.apache.tomcat.websocket.STRICT_SPEC_COMPLIANCE");
+
+ private Constants() {
+ // Hide default constructor
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/DecoderEntry.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/DecoderEntry.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/DecoderEntry.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,39 @@
+/*
+ * 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.websocket;
+
+import javax.websocket.Decoder;
+
+public class DecoderEntry {
+
+ private final Class<?> clazz;
+ private final Class<? extends Decoder> decoderClazz;
+
+ public DecoderEntry(Class<?> clazz,
+ Class<? extends Decoder> decoderClazz) {
+ this.clazz = clazz;
+ this.decoderClazz = decoderClazz;
+ }
+
+ public Class<?> getClazz() {
+ return clazz;
+ }
+
+ public Class<? extends Decoder> getDecoderClazz() {
+ return decoderClazz;
+ }
+}
\ No newline at end of file
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/FutureToSendHandler.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/FutureToSendHandler.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/FutureToSendHandler.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,109 @@
+/*
+ * 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.websocket;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+/**
+ * Converts a Future to a SendHandler.
+ */
+class FutureToSendHandler implements Future<Void>, SendHandler {
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private final WsSession wsSession;
+ private volatile SendResult result = null;
+
+ public FutureToSendHandler(WsSession wsSession) {
+ this.wsSession = wsSession;
+ }
+
+
+ // --------------------------------------------------------- SendHandler
+
+ @Override
+ public void onResult(SendResult result) {
+
+ this.result = result;
+ latch.countDown();
+ }
+
+
+ // -------------------------------------------------------------- Future
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ // Cancelling the task is not supported
+ return false;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ // Cancelling the task is not supported
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return latch.getCount() == 0;
+ }
+
+ @Override
+ public Void get() throws InterruptedException,
+ ExecutionException {
+ try {
+ wsSession.registerFuture(this);
+ latch.await();
+ } finally {
+ wsSession.unregisterFuture(this);
+ }
+ if (result.getException() != null) {
+ throw new ExecutionException(result.getException());
+ }
+ return null;
+ }
+
+ @Override
+ public Void get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException,
+ TimeoutException {
+ boolean retval = false;
+ try {
+ wsSession.registerFuture(this);
+ retval = latch.await(timeout, unit);
+ } finally {
+ wsSession.unregisterFuture(this);
+
+ }
+ if (retval == false) {
+ throw new TimeoutException();
+ }
+ if (result.getException() != null) {
+ throw new ExecutionException(result.getException());
+ }
+ return null;
+ }
+}
+
+
+
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/MessageHandlerResult.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/MessageHandlerResult.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/MessageHandlerResult.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,42 @@
+/*
+ * 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.websocket;
+
+import javax.websocket.MessageHandler;
+
+public class MessageHandlerResult {
+
+ private final MessageHandler handler;
+ private final MessageHandlerResultType type;
+
+
+ public MessageHandlerResult(MessageHandler handler,
+ MessageHandlerResultType type) {
+ this.handler = handler;
+ this.type = type;
+ }
+
+
+ public MessageHandler getHandler() {
+ return handler;
+ }
+
+
+ public MessageHandlerResultType getType() {
+ return type;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/MessageHandlerResultType.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/MessageHandlerResultType.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/MessageHandlerResultType.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,23 @@
+/*
+ * 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.websocket;
+
+public enum MessageHandlerResultType {
+ BINARY,
+ TEXT,
+ PONG
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/ReadBufferOverflowException.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/ReadBufferOverflowException.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/ReadBufferOverflowException.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,34 @@
+/*
+ * 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.websocket;
+
+import java.io.IOException;
+
+public class ReadBufferOverflowException extends IOException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final int minBufferSize;
+
+ public ReadBufferOverflowException(int minBufferSize) {
+ this.minBufferSize = minBufferSize;
+ }
+
+ public int getMinBufferSize() {
+ return minBufferSize;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/SendHandlerToCompletionHandler.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/SendHandlerToCompletionHandler.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/SendHandlerToCompletionHandler.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,42 @@
+/*
+ * 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.websocket;
+
+import java.nio.channels.CompletionHandler;
+
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+public class SendHandlerToCompletionHandler
+ implements CompletionHandler<Long,Void> {
+
+ private SendHandler handler;
+
+ public SendHandlerToCompletionHandler(SendHandler handler) {
+ this.handler = handler;
+ }
+
+ @Override
+ public void completed(Long result, Void attachment) {
+ handler.onResult(new SendResult());
+ }
+
+ @Override
+ public void failed(Throwable exc, Void attachment) {
+ handler.onResult(new SendResult(exc));
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/Util.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/Util.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/Util.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,473 @@
+/*
+ * 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.websocket;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.nio.ByteBuffer;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.websocket.CloseReason.CloseCode;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.Decoder;
+import javax.websocket.Decoder.Binary;
+import javax.websocket.Decoder.BinaryStream;
+import javax.websocket.Decoder.Text;
+import javax.websocket.Decoder.TextStream;
+import javax.websocket.DeploymentException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.PongMessage;
+
+import org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeBinary;
+import org.apache.tomcat.websocket.pojo.PojoMessageHandlerWholeText;
+
+/**
+ * Utility class for internal use only within the
+ * {@link org.apache.tomcat.websocket} package.
+ */
+public class Util {
+
+ private static final Queue<SecureRandom> randoms =
+ new ConcurrentLinkedQueue<SecureRandom>();
+
+ private Util() {
+ // Hide default constructor
+ }
+
+
+ static boolean isControl(byte opCode) {
+ return (opCode & 0x08) > 0;
+ }
+
+
+ static boolean isText(byte opCode) {
+ return opCode == Constants.OPCODE_TEXT;
+ }
+
+
+ static CloseCode getCloseCode(int code) {
+ if (code > 2999 && code < 5000) {
+ return CloseCodes.NORMAL_CLOSURE;
+ }
+ switch (code) {
+ case 1000:
+ return CloseCodes.NORMAL_CLOSURE;
+ case 1001:
+ return CloseCodes.GOING_AWAY;
+ case 1002:
+ return CloseCodes.PROTOCOL_ERROR;
+ case 1003:
+ return CloseCodes.CANNOT_ACCEPT;
+ case 1004:
+ // Should not be used in a close frame
+ // return CloseCodes.RESERVED;
+ return CloseCodes.PROTOCOL_ERROR;
+ case 1005:
+ // Should not be used in a close frame
+ // return CloseCodes.NO_STATUS_CODE;
+ return CloseCodes.PROTOCOL_ERROR;
+ case 1006:
+ // Should not be used in a close frame
+ // return CloseCodes.CLOSED_ABNORMALLY;
+ return CloseCodes.PROTOCOL_ERROR;
+ case 1007:
+ return CloseCodes.NOT_CONSISTENT;
+ case 1008:
+ return CloseCodes.VIOLATED_POLICY;
+ case 1009:
+ return CloseCodes.TOO_BIG;
+ case 1010:
+ return CloseCodes.NO_EXTENSION;
+ case 1011:
+ return CloseCodes.UNEXPECTED_CONDITION;
+ case 1012:
+ // Not in RFC6455
+ // return CloseCodes.SERVICE_RESTART;
+ return CloseCodes.PROTOCOL_ERROR;
+ case 1013:
+ // Not in RFC6455
+ // return CloseCodes.TRY_AGAIN_LATER;
+ return CloseCodes.PROTOCOL_ERROR;
+ case 1015:
+ // Should not be used in a close frame
+ // return CloseCodes.TLS_HANDSHAKE_FAILURE;
+ return CloseCodes.PROTOCOL_ERROR;
+ default:
+ return CloseCodes.PROTOCOL_ERROR;
+ }
+ }
+
+
+ static byte[] generateMask() {
+ // SecureRandom is not thread-safe so need to make sure only one thread
+ // uses it at a time. In theory, the pool could grow to the same size
+ // as the number of request processing threads. In reality it will be
+ // a lot smaller.
+
+ // Get a SecureRandom from the pool
+ SecureRandom sr = randoms.poll();
+
+ // If one isn't available, generate a new one
+ if (sr == null) {
+ try {
+ sr = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ // Fall back to platform default
+ sr = new SecureRandom();
+ }
+ }
+
+ // Generate the mask
+ byte[] result = new byte[4];
+ sr.nextBytes(result);
+
+ // Put the SecureRandom back in the poll
+ randoms.add(sr);
+
+ return result;
+ }
+
+
+ static Class<?> getMessageType(MessageHandler listener) {
+ return (Class<?>) Util.getGenericType(MessageHandler.class,
+ listener.getClass());
+ }
+
+
+ public static Class<?> getDecoderType(Class<? extends Decoder> Decoder) {
+ return (Class<?>) Util.getGenericType(Decoder.class, Decoder);
+ }
+
+
+ static Class<?> getEncoderType(Class<? extends Encoder> encoder) {
+ return (Class<?>) Util.getGenericType(Encoder.class, encoder);
+ }
+
+
+ private static <T> Object getGenericType(Class<T> type,
+ Class<? extends T> clazz) {
+
+ // Look to see if this class implements the generic MessageHandler<>
+ // interface
+
+ // Get all the interfaces
+ Type[] interfaces = clazz.getGenericInterfaces();
+ for (Type iface : interfaces) {
+ // Only need to check interfaces that use generics
+ if (iface instanceof ParameterizedType) {
+ ParameterizedType pi = (ParameterizedType) iface;
+ // Look for the MessageHandler<> interface
+ if (pi.getRawType() instanceof Class) {
+ if (type.isAssignableFrom((Class<?>) pi.getRawType())) {
+ return getTypeParameter(
+ clazz, pi.getActualTypeArguments()[0]);
+ }
+ }
+ }
+ }
+
+ // Interface not found on this class. Look at the superclass.
+ @SuppressWarnings("unchecked")
+ Class<? extends T> superClazz =
+ (Class<? extends T>) clazz.getSuperclass();
+
+ Object result = getGenericType(type, superClazz);
+ if (result instanceof Class<?>) {
+ // Superclass implements interface and defines explicit type for
+ // MessageHandler<>
+ return result;
+ } else if (result instanceof Integer) {
+ // Superclass implements interface and defines unknown type for
+ // MessageHandler<>
+ // Map that unknown type to the generic types defined in this class
+ ParameterizedType superClassType =
+ (ParameterizedType) clazz.getGenericSuperclass();
+ return getTypeParameter(clazz,
+ superClassType.getActualTypeArguments()[
+ ((Integer) result).intValue()]);
+ } else {
+ // Error will be logged further up the call stack
+ return null;
+ }
+ }
+
+
+ /*
+ * For a generic parameter, return either the Class used or if the type
+ * is unknown, the index for the type in definition of the class
+ */
+ private static Object getTypeParameter(Class<?> clazz, Type argType) {
+ if (argType instanceof Class<?>) {
+ return argType;
+ } else {
+ TypeVariable<?>[] tvs = clazz.getTypeParameters();
+ for (int i = 0; i < tvs.length; i++) {
+ if (tvs[i].equals(argType)) {
+ return Integer.valueOf(i);
+ }
+ }
+ return null;
+ }
+ }
+
+
+ public static boolean isPrimitive(Class<?> clazz) {
+ if (clazz.isPrimitive()) {
+ return true;
+ } else if(clazz.equals(Boolean.class) ||
+ clazz.equals(Byte.class) ||
+ clazz.equals(Character.class) ||
+ clazz.equals(Double.class) ||
+ clazz.equals(Float.class) ||
+ clazz.equals(Integer.class) ||
+ clazz.equals(Long.class) ||
+ clazz.equals(Short.class)) {
+ return true;
+ }
+ return false;
+ }
+
+
+ public static Object coerceToType(Class<?> type, String value) {
+ if (type.equals(String.class)) {
+ return value;
+ } else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
+ return Boolean.valueOf(value);
+ } else if (type.equals(byte.class) || type.equals(Byte.class)) {
+ return Byte.valueOf(value);
+ } else if (value.length() == 1 &&
+ (type.equals(char.class) || type.equals(Character.class))) {
+ return Character.valueOf(value.charAt(0));
+ } else if (type.equals(double.class) || type.equals(Double.class)) {
+ return Double.valueOf(value);
+ } else if (type.equals(float.class) || type.equals(Float.class)) {
+ return Float.valueOf(value);
+ } else if (type.equals(int.class) || type.equals(Integer.class)) {
+ return Integer.valueOf(value);
+ } else if (type.equals(long.class) || type.equals(Long.class)) {
+ return Long.valueOf(value);
+ } else if (type.equals(short.class) || type.equals(Short.class)) {
+ return Short.valueOf(value);
+ } else {
+ throw MESSAGES.invalidType(value, type.getName());
+ }
+ }
+
+
+ public static List<DecoderEntry> getDecoders(
+ Class<? extends Decoder>[] decoderClazzes)
+ throws DeploymentException{
+
+ List<DecoderEntry> result = new ArrayList<DecoderEntry>();
+ for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
+ // Need to instantiate decoder to ensure it is valid and that
+ // deployment can be failed if it is not
+ @SuppressWarnings("unused")
+ Decoder instance;
+ try {
+ instance = decoderClazz.newInstance();
+ } catch (InstantiationException e) {
+ throw new DeploymentException(MESSAGES.cannotInstatiateDecoder(decoderClazz.getName()), e);
+ } catch (IllegalAccessException e) {
+ throw new DeploymentException(MESSAGES.cannotInstatiateDecoder(decoderClazz.getName()), e);
+ }
+ DecoderEntry entry = new DecoderEntry(
+ Util.getDecoderType(decoderClazz), decoderClazz);
+ result.add(entry);
+ }
+
+ return result;
+ }
+
+
+
+ public static Set<MessageHandlerResult> getMessageHandlers(
+ MessageHandler listener, EndpointConfig endpointConfig) {
+
+ Class<?> target = Util.getMessageType(listener);
+
+ // Will never be more than 2 types
+ Set<MessageHandlerResult> results = new HashSet<MessageHandlerResult>(2);
+
+ // Simple cases - handlers already accepts one of the types expected by
+ // the frame handling code
+ if (String.class.isAssignableFrom(target)) {
+ MessageHandlerResult result =
+ new MessageHandlerResult(listener,
+ MessageHandlerResultType.TEXT);
+ results.add(result);
+ } else if (ByteBuffer.class.isAssignableFrom(target)) {
+ MessageHandlerResult result =
+ new MessageHandlerResult(listener,
+ MessageHandlerResultType.BINARY);
+ results.add(result);
+ } else if (PongMessage.class.isAssignableFrom(target)) {
+ MessageHandlerResult result =
+ new MessageHandlerResult(listener,
+ MessageHandlerResultType.PONG);
+ results.add(result);
+ // Relatively simple cases - handler needs wrapping but no decoder to
+ // convert it to one of the types expected by the frame handling code
+ } else if (byte[].class.isAssignableFrom(target)) {
+ MessageHandlerResult result = new MessageHandlerResult(
+ new PojoMessageHandlerWholeBinary(listener,
+ getOnMessageMethod(listener), null,
+ endpointConfig, null, new Object[1], 0, true, -1,
+ false, -1),
+ MessageHandlerResultType.BINARY);
+ results.add(result);
+ } else if (InputStream.class.isAssignableFrom(target)) {
+ MessageHandlerResult result = new MessageHandlerResult(
+ new PojoMessageHandlerWholeBinary(listener,
+ getOnMessageMethod(listener), null,
+ endpointConfig, null, new Object[1], 0, true, -1,
+ true, -1),
+ MessageHandlerResultType.BINARY);
+ results.add(result);
+ } else if (Reader.class.isAssignableFrom(target)) {
+ MessageHandlerResult result = new MessageHandlerResult(
+ new PojoMessageHandlerWholeText(listener,
+ getOnMessageMethod(listener), null,
+ endpointConfig, null, new Object[1], 0, true, -1,
+ -1),
+ MessageHandlerResultType.TEXT);
+ results.add(result);
+ } else {
+ // More complex case - listener that requires a decoder
+ DecoderMatch decoderMatch;
+ try {
+ List<Class<? extends Decoder>> decoders =
+ endpointConfig.getDecoders();
+ @SuppressWarnings("unchecked")
+ List<DecoderEntry> decoderEntries = getDecoders(
+ decoders.toArray(new Class[decoders.size()]));
+ decoderMatch = new DecoderMatch(target, decoderEntries);
+ } catch (DeploymentException e) {
+ throw new IllegalArgumentException(e);
+ }
+ Method m = getOnMessageMethod(listener);
+ if (decoderMatch.getBinaryDecoders().size() > 0) {
+ MessageHandlerResult result = new MessageHandlerResult(
+ new PojoMessageHandlerWholeBinary(listener, m, null,
+ endpointConfig,
+ decoderMatch.getBinaryDecoders(), new Object[1],
+ 0, false, -1, false, -1),
+ MessageHandlerResultType.BINARY);
+ results.add(result);
+ }
+ if (decoderMatch.getTextDecoders().size() > 0) {
+ MessageHandlerResult result = new MessageHandlerResult(
+ new PojoMessageHandlerWholeText(listener, m, null,
+ endpointConfig,
+ decoderMatch.getTextDecoders(), new Object[1],
+ 0, false, -1, -1),
+ MessageHandlerResultType.TEXT);
+ results.add(result);
+ }
+ }
+
+ if (results.size() == 0) {
+ throw MESSAGES.unknownHandler(listener, target);
+ }
+
+ return results;
+ }
+
+
+ private static Method getOnMessageMethod(MessageHandler listener) {
+ try {
+ return listener.getClass().getMethod("onMessage", Object.class);
+ } catch (NoSuchMethodException e) {
+ throw MESSAGES.invalidMessageHandler(e);
+ } catch ( SecurityException e) {
+ throw MESSAGES.invalidMessageHandler(e);
+ }
+ }
+
+ public static class DecoderMatch {
+
+ private final List<Class<? extends Decoder>> textDecoders =
+ new ArrayList<Class<? extends Decoder>>();
+ private final List<Class<? extends Decoder>> binaryDecoders =
+ new ArrayList<Class<? extends Decoder>>();
+
+
+ public DecoderMatch(Class<?> target, List<DecoderEntry> decoderEntries) {
+ for (DecoderEntry decoderEntry : decoderEntries) {
+ if (decoderEntry.getClazz().isAssignableFrom(target)) {
+ if (Binary.class.isAssignableFrom(
+ decoderEntry.getDecoderClazz())) {
+ binaryDecoders.add(decoderEntry.getDecoderClazz());
+ // willDecode() method means this decoder may or may not
+ // decode a message so need to carry on checking for
+ // other matches
+ } else if (BinaryStream.class.isAssignableFrom(
+ decoderEntry.getDecoderClazz())) {
+ binaryDecoders.add(decoderEntry.getDecoderClazz());
+ // Stream decoders have to process the message so no
+ // more decoders can be matched
+ break;
+ } else if (Text.class.isAssignableFrom(
+ decoderEntry.getDecoderClazz())) {
+ textDecoders.add(decoderEntry.getDecoderClazz());
+ // willDecode() method means this decoder may or may not
+ // decode a message so need to carry on checking for
+ // other matches
+ } else if (TextStream.class.isAssignableFrom(
+ decoderEntry.getDecoderClazz())) {
+ textDecoders.add(decoderEntry.getDecoderClazz());
+ // Stream decoders have to process the message so no
+ // more decoders can be matched
+ break;
+ } else {
+ throw MESSAGES.unknownDecoderType(decoderEntry.getDecoderClazz().getName());
+ }
+ }
+ }
+ }
+
+
+ public List<Class<? extends Decoder>> getTextDecoders() {
+ return textDecoders;
+ }
+
+
+ public List<Class<? extends Decoder>> getBinaryDecoders() {
+ return binaryDecoders;
+ }
+
+
+ public boolean hasMatches() {
+ return (textDecoders.size() > 0) || (binaryDecoders.size() > 0);
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WrappedMessageHandler.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WrappedMessageHandler.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WrappedMessageHandler.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,25 @@
+/*
+ * 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.websocket;
+
+import javax.websocket.MessageHandler;
+
+public interface WrappedMessageHandler {
+ long getMaxMessageSize();
+
+ MessageHandler getWrappedHandler();
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsContainerProvider.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsContainerProvider.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsContainerProvider.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,28 @@
+/*
+ * 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.websocket;
+
+import javax.websocket.ContainerProvider;
+import javax.websocket.WebSocketContainer;
+
+public class WsContainerProvider extends ContainerProvider {
+
+ @Override
+ protected WebSocketContainer getContainer() {
+ return new WsWebSocketContainer();
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsFrameBase.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsFrameBase.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsFrameBase.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,669 @@
+/*
+ * 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.websocket;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.MessageHandler;
+import javax.websocket.PongMessage;
+
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.Utf8Decoder;
+
+/**
+ * Takes the ServletInputStream, processes the WebSocket frames it contains and
+ * extracts the messages. WebSocket Pings received will be responded to
+ * automatically without any action required by the application.
+ */
+public abstract class WsFrameBase {
+
+ // Connection level attributes
+ protected final WsSession wsSession;
+ protected final byte[] inputBuffer;
+
+ // Attributes for control messages
+ // Control messages can appear in the middle of other messages so need
+ // separate attributes
+ private final ByteBuffer controlBufferBinary = ByteBuffer.allocate(125);
+ private final CharBuffer controlBufferText = CharBuffer.allocate(125);
+
+ // Attributes of the current message
+ private final CharsetDecoder utf8DecoderControl = new Utf8Decoder().
+ onMalformedInput(CodingErrorAction.REPORT).
+ onUnmappableCharacter(CodingErrorAction.REPORT);
+ private final CharsetDecoder utf8DecoderMessage = new Utf8Decoder().
+ onMalformedInput(CodingErrorAction.REPORT).
+ onUnmappableCharacter(CodingErrorAction.REPORT);
+ private boolean continuationExpected = false;
+ private boolean textMessage = false;
+ private ByteBuffer messageBufferBinary;
+ private CharBuffer messageBufferText;
+ // Cache the message handler in force when the message starts so it is used
+ // consistently for the entire message
+ private MessageHandler binaryMsgHandler = null;
+ private MessageHandler textMsgHandler = null;
+
+ // Attributes of the current frame
+ private boolean fin = false;
+ private int rsv = 0;
+ private byte opCode = 0;
+ private final byte[] mask = new byte[4];
+ private int maskIndex = 0;
+ private long payloadLength = 0;
+ private long payloadWritten = 0;
+
+ // Attributes tracking state
+ private State state = State.NEW_FRAME;
+ private volatile boolean open = true;
+ private int readPos = 0;
+ protected int writePos = 0;
+
+ public WsFrameBase(WsSession wsSession) {
+
+ inputBuffer = new byte[Constants.DEFAULT_BUFFER_SIZE];
+ messageBufferBinary =
+ ByteBuffer.allocate(wsSession.getMaxBinaryMessageBufferSize());
+ messageBufferText =
+ CharBuffer.allocate(wsSession.getMaxTextMessageBufferSize());
+ this.wsSession = wsSession;
+ }
+
+
+ protected void processInputBuffer() throws IOException {
+ while (true) {
+ wsSession.updateLastActive();
+
+ if (state == State.NEW_FRAME) {
+ if (!processInitialHeader()) {
+ break;
+ }
+ // If a close frame has been received, no further data should
+ // have seen
+ if (!open) {
+ throw new IOException(MESSAGES.receivedFrameAfterClose());
+ }
+ }
+ if (state == State.PARTIAL_HEADER) {
+ if (!processRemainingHeader()) {
+ break;
+ }
+ }
+ if (state == State.DATA) {
+ if (!processData()) {
+ break;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * @return <code>true</code> if sufficient data was present to process all
+ * of the initial header
+ */
+ private boolean processInitialHeader() throws IOException {
+ // Need at least two bytes of data to do this
+ if (writePos - readPos < 2) {
+ return false;
+ }
+ int b = inputBuffer[readPos++];
+ fin = (b & 0x80) > 0;
+ rsv = (b & 0x70) >>> 4;
+ if (rsv != 0) {
+ // Note extensions may use rsv bits but currently no extensions are
+ // supported
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.unsupportedReservedBitsSet(Integer.valueOf(rsv))));
+ }
+ opCode = (byte) (b & 0x0F);
+ if (Util.isControl(opCode)) {
+ if (!fin) {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.invalidFragmentedControlFrame()));
+ }
+ if (opCode != Constants.OPCODE_PING &&
+ opCode != Constants.OPCODE_PONG &&
+ opCode != Constants.OPCODE_CLOSE) {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.invalidFrameOpcode(opCode)));
+ }
+ } else {
+ if (continuationExpected) {
+ if (opCode != Constants.OPCODE_CONTINUATION) {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.noContinuationFrame()));
+ }
+ } else {
+ try {
+ if (opCode == Constants.OPCODE_BINARY) {
+ // New binary message
+ textMessage = false;
+ int size = wsSession.getMaxBinaryMessageBufferSize();
+ if (size != messageBufferBinary.capacity()) {
+ messageBufferBinary = ByteBuffer.allocate(size);
+ }
+ binaryMsgHandler = wsSession.getBinaryMessageHandler();
+ textMsgHandler = null;
+ } else if (opCode == Constants.OPCODE_TEXT) {
+ // New text message
+ textMessage = true;
+ int size = wsSession.getMaxTextMessageBufferSize();
+ if (size != messageBufferText.capacity()) {
+ messageBufferText = CharBuffer.allocate(size);
+ }
+ binaryMsgHandler = null;
+ textMsgHandler = wsSession.getTextMessageHandler();
+ } else {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.invalidFrameOpcode(opCode)));
+ }
+ } catch (IllegalStateException ise) {
+ // Thrown if the session is already closed
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.sessionClosed()));
+ }
+ }
+ continuationExpected = !fin;
+ }
+ b = inputBuffer[readPos++];
+ // Client data must be masked
+ if ((b & 0x80) == 0 && isMasked()) {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.frameWithoutMask()));
+ }
+ payloadLength = b & 0x7F;
+ state = State.PARTIAL_HEADER;
+ return true;
+ }
+
+
+ protected abstract boolean isMasked();
+
+
+ /**
+ * @return <code>true</code> if sufficient data was present to complete the
+ * processing of the header
+ */
+ private boolean processRemainingHeader() throws IOException {
+ // Ignore the 2 bytes already read. 4 for the mask
+ int headerLength;
+ if (isMasked()) {
+ headerLength = 4;
+ } else {
+ headerLength = 0;
+ }
+ // Add additional bytes depending on length
+ if (payloadLength == 126) {
+ headerLength += 2;
+ } else if (payloadLength == 127) {
+ headerLength += 8;
+ }
+ if (writePos - readPos < headerLength) {
+ return false;
+ }
+ // Calculate new payload length if necessary
+ if (payloadLength == 126) {
+ payloadLength = byteArrayToLong(inputBuffer, readPos, 2);
+ readPos += 2;
+ } else if (payloadLength == 127) {
+ payloadLength = byteArrayToLong(inputBuffer, readPos, 8);
+ readPos += 8;
+ }
+ if (Util.isControl(opCode)) {
+ if (payloadLength > 125) {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.controlFramePayloadTooLarge(Long.valueOf(payloadLength))));
+ }
+ if (!fin) {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.controlFrameWithoutFin()));
+ }
+ }
+ if (isMasked()) {
+ System.arraycopy(inputBuffer, readPos, mask, 0, 4);
+ readPos += 4;
+ }
+ state = State.DATA;
+ return true;
+ }
+
+
+ private boolean processData() throws IOException {
+ checkRoomPayload();
+ if (Util.isControl(opCode)) {
+ return processDataControl();
+ } else if (textMessage) {
+ return processDataText();
+ } else {
+ return processDataBinary();
+ }
+ }
+
+
+ private boolean processDataControl() throws IOException {
+ if (!appendPayloadToMessage(controlBufferBinary)) {
+ return false;
+ }
+ controlBufferBinary.flip();
+ if (opCode == Constants.OPCODE_CLOSE) {
+ open = false;
+ String reason = null;
+ int code = CloseCodes.NORMAL_CLOSURE.getCode();
+ if (controlBufferBinary.remaining() == 1) {
+ controlBufferBinary.clear();
+ // Payload must be zero or greater than 2
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.invalidOneByteClose()));
+ }
+ if (controlBufferBinary.remaining() > 1) {
+ code = controlBufferBinary.getShort();
+ if (controlBufferBinary.remaining() > 0) {
+ CoderResult cr = utf8DecoderControl.decode(
+ controlBufferBinary, controlBufferText, true);
+ if (cr.isError()) {
+ controlBufferBinary.clear();
+ controlBufferText.clear();
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.invalidUtf8Close()));
+ }
+ // There will be no overflow as the output buffer is big
+ // enough. There will be no underflow as all the data is
+ // passed to the decoder in a single call.
+ controlBufferText.flip();
+ reason = controlBufferText.toString();
+ }
+ }
+ wsSession.onClose(new CloseReason(Util.getCloseCode(code), reason));
+ } else if (opCode == Constants.OPCODE_PING) {
+ if (wsSession.isOpen()) {
+ wsSession.getBasicRemote().sendPong(controlBufferBinary);
+ }
+ } else if (opCode == Constants.OPCODE_PONG) {
+ MessageHandler.Whole<PongMessage> mhPong =
+ wsSession.getPongMessageHandler();
+ if (mhPong != null) {
+ try {
+ mhPong.onMessage(new WsPongMessage(controlBufferBinary));
+ } catch (Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ wsSession.getLocal().onError(wsSession, t);
+ } finally {
+ controlBufferBinary.clear();
+ }
+ }
+ } else {
+ // Should have caught this earlier but just in case...
+ controlBufferBinary.clear();
+ throw new WsIOException(new CloseReason(
+ CloseCodes.PROTOCOL_ERROR,
+ MESSAGES.invalidFrameOpcode(Integer.valueOf(opCode))));
+ }
+ controlBufferBinary.clear();
+ newFrame();
+ return true;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private void sendMessageText(boolean last) throws WsIOException {
+ if (textMsgHandler != null) {
+ if (textMsgHandler instanceof WrappedMessageHandler) {
+ long maxMessageSize =
+ ((WrappedMessageHandler) textMsgHandler).getMaxMessageSize();
+ if (maxMessageSize > -1 &&
+ messageBufferText.remaining() > maxMessageSize) {
+ throw new WsIOException(new CloseReason(CloseCodes.TOO_BIG,
+ MESSAGES.messageTooLarge(Long.valueOf(messageBufferText.remaining()),
+ Long.valueOf(maxMessageSize))));
+ }
+ }
+
+ try {
+ if (textMsgHandler instanceof MessageHandler.Partial<?>) {
+ ((MessageHandler.Partial<String>) textMsgHandler).onMessage(
+ messageBufferText.toString(), last);
+ } else {
+ // Caller ensures last == true if this branch is used
+ ((MessageHandler.Whole<String>) textMsgHandler).onMessage(
+ messageBufferText.toString());
+ }
+ } catch (Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ wsSession.getLocal().onError(wsSession, t);
+ } finally {
+ messageBufferText.clear();
+ }
+ }
+ }
+
+
+ private boolean processDataText() throws IOException {
+ // Copy the available data to the buffer
+ while (!appendPayloadToMessage(messageBufferBinary)) {
+ // Frame not complete - we ran out of something
+ // Convert bytes to UTF-8
+ messageBufferBinary.flip();
+ while (true) {
+ CoderResult cr = utf8DecoderMessage.decode(
+ messageBufferBinary, messageBufferText, false);
+ if (cr.isError()) {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.NOT_CONSISTENT,
+ MESSAGES.invalidUtf8()));
+ } else if (cr.isOverflow()) {
+ // Ran out of space in text buffer - flush it
+ if (usePartial()) {
+ messageBufferText.flip();
+ sendMessageText(false);
+ messageBufferText.clear();
+ } else {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.TOO_BIG,
+ MESSAGES.textMessageTooLarge()));
+ }
+ } else if (cr.isUnderflow()) {
+ // Need more input
+ // Compact what we have to create as much space as possible
+ messageBufferBinary.compact();
+
+ // What did we run out of?
+ if (readPos == writePos) {
+ // Ran out of input data - get some more
+ return false;
+ } else {
+ // Ran out of message buffer - exit inner loop and
+ // refill
+ break;
+ }
+ }
+ }
+ }
+
+ messageBufferBinary.flip();
+ boolean last = false;
+ // Frame is fully received
+ // Convert bytes to UTF-8
+ while (true) {
+ CoderResult cr = utf8DecoderMessage.decode(messageBufferBinary,
+ messageBufferText, last);
+ if (cr.isError()) {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.NOT_CONSISTENT,
+ MESSAGES.invalidUtf8()));
+ } else if (cr.isOverflow()) {
+ // Ran out of space in text buffer - flush it
+ if (usePartial()) {
+ messageBufferText.flip();
+ sendMessageText(false);
+ messageBufferText.clear();
+ } else {
+ throw new WsIOException(new CloseReason(
+ CloseCodes.TOO_BIG,
+ MESSAGES.textMessageTooLarge()));
+ }
+ } else if (cr.isUnderflow() & !last) {
+ // End of frame and possible message as well.
+
+ if (continuationExpected) {
+ // If partial messages are supported, send what we have
+ // managed to decode
+ if (usePartial()) {
+ messageBufferText.flip();
+ sendMessageText(false);
+ messageBufferText.clear();
+ }
+ messageBufferBinary.compact();
+ newFrame();
+ // Process next frame
+ return true;
+ } else {
+ // Make sure coder has flushed all output
+ last = true;
+ }
+ } else {
+ // End of message
+ messageBufferText.flip();
+ sendMessageText(true);
+
+ newMessage();
+ return true;
+ }
+ }
+ }
+
+
+ private boolean processDataBinary() throws IOException {
+ // Copy the available data to the buffer
+ while (!appendPayloadToMessage(messageBufferBinary)) {
+ // Frame not complete - what did we run out of?
+ if (readPos == writePos) {
+ // Ran out of input data - get some more
+ return false;
+ } else {
+ // Ran out of message buffer - flush it
+ if (!usePartial()) {
+ CloseReason cr = new CloseReason(CloseCodes.TOO_BIG,
+ MESSAGES.bufferTooSmall(Integer.valueOf(messageBufferBinary.capacity()),
+ Long.valueOf(payloadLength)));
+ throw new WsIOException(cr);
+ }
+ messageBufferBinary.flip();
+ ByteBuffer copy =
+ ByteBuffer.allocate(messageBufferBinary.limit());
+ copy.put(messageBufferBinary);
+ copy.flip();
+ sendMessageBinary(copy, false);
+ messageBufferBinary.clear();
+ }
+ }
+
+ // Frame is fully received
+ // Send the message if either:
+ // - partial messages are supported
+ // - the message is complete
+ if (usePartial() || !continuationExpected) {
+ messageBufferBinary.flip();
+ ByteBuffer copy =
+ ByteBuffer.allocate(messageBufferBinary.limit());
+ copy.put(messageBufferBinary);
+ copy.flip();
+ sendMessageBinary(copy, !continuationExpected);
+ messageBufferBinary.clear();
+ }
+
+ if (continuationExpected) {
+ // More data for this message expected, start a new frame
+ newFrame();
+ } else {
+ // Message is complete, start a new message
+ newMessage();
+ }
+
+ return true;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private void sendMessageBinary(ByteBuffer msg, boolean last)
+ throws WsIOException {
+ if (binaryMsgHandler != null) {
+ if (binaryMsgHandler instanceof WrappedMessageHandler) {
+ long maxMessageSize =
+ ((WrappedMessageHandler) binaryMsgHandler).getMaxMessageSize();
+ if (maxMessageSize > -1 && msg.remaining() > maxMessageSize) {
+ throw new WsIOException(new CloseReason(CloseCodes.TOO_BIG,
+ MESSAGES.messageTooLarge(
+ Long.valueOf(msg.remaining()),
+ Long.valueOf(maxMessageSize))));
+ }
+ }
+ try {
+ if (binaryMsgHandler instanceof MessageHandler.Partial<?>) {
+ ((MessageHandler.Partial<ByteBuffer>) binaryMsgHandler).onMessage(msg, last);
+ } else {
+ // Caller ensures last == true if this branch is used
+ ((MessageHandler.Whole<ByteBuffer>) binaryMsgHandler).onMessage(msg);
+ }
+ } catch(Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ wsSession.getLocal().onError(wsSession, t);
+ }
+ }
+ }
+
+
+ private void newMessage() {
+ messageBufferBinary.clear();
+ messageBufferText.clear();
+ utf8DecoderMessage.reset();
+ continuationExpected = false;
+ newFrame();
+ }
+
+
+ private void newFrame() {
+ if (readPos == writePos) {
+ readPos = 0;
+ writePos = 0;
+ }
+
+ maskIndex = 0;
+ payloadWritten = 0;
+ state = State.NEW_FRAME;
+
+ // These get reset in processInitialHeader()
+ // fin, rsv, opCode, payloadLength, mask
+
+ checkRoomHeaders();
+ }
+
+
+ private void checkRoomHeaders() {
+ // Is the start of the current frame too near the end of the input
+ // buffer?
+ if (inputBuffer.length - readPos < 131) {
+ // Limit based on a control frame with a full payload
+ makeRoom();
+ }
+ }
+
+
+ private void checkRoomPayload() {
+ if (inputBuffer.length - readPos - payloadLength + payloadWritten < 0) {
+ makeRoom();
+ }
+ }
+
+
+ private void makeRoom() {
+ System.arraycopy(inputBuffer, readPos, inputBuffer, 0,
+ writePos - readPos);
+ writePos = writePos - readPos;
+ readPos = 0;
+ }
+
+
+ private boolean usePartial() {
+ if (Util.isControl(opCode)) {
+ return false;
+ } else if (textMessage) {
+ if (textMsgHandler != null) {
+ return textMsgHandler instanceof MessageHandler.Partial<?>;
+ }
+ return false;
+ } else {
+ // Must be binary
+ if (binaryMsgHandler != null) {
+ return binaryMsgHandler instanceof MessageHandler.Partial<?>;
+ }
+ return false;
+ }
+ }
+
+
+ private boolean appendPayloadToMessage(ByteBuffer dest) {
+ if (isMasked()) {
+ while (payloadWritten < payloadLength && readPos < writePos &&
+ dest.hasRemaining()) {
+ byte b = (byte) ((inputBuffer[readPos] ^ mask[maskIndex]) & 0xFF);
+ maskIndex++;
+ if (maskIndex == 4) {
+ maskIndex = 0;
+ }
+ readPos++;
+ payloadWritten++;
+ dest.put(b);
+ }
+ return (payloadWritten == payloadLength);
+ } else {
+ long toWrite = Math.min(
+ payloadLength - payloadWritten, writePos - readPos);
+ toWrite = Math.min(toWrite, dest.remaining());
+
+ dest.put(inputBuffer, readPos, (int) toWrite);
+ readPos += toWrite;
+ payloadWritten += toWrite;
+ return (payloadWritten == payloadLength);
+
+ }
+ }
+
+
+ protected static long byteArrayToLong(byte[] b, int start, int len)
+ throws IOException {
+ if (len > 8) {
+ throw new IOException(MESSAGES.invalidLong(Long.valueOf(len)));
+ }
+ int shift = 0;
+ long result = 0;
+ for (int i = start + len - 1; i >= start; i--) {
+ result = result + ((b[i] & 0xFF) << shift);
+ shift += 8;
+ }
+ return result;
+ }
+
+
+ protected boolean isOpen() {
+ return open;
+ }
+
+
+ private static enum State {
+ NEW_FRAME, PARTIAL_HEADER, DATA
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsFrameClient.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsFrameClient.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsFrameClient.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,126 @@
+/*
+ * 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.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.CompletionHandler;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+
+public class WsFrameClient extends WsFrameBase {
+
+ private final AsyncChannelWrapper channel;
+ private final CompletionHandler<Integer,Void> handler;
+ // Not final as it may need to be re-sized
+ private ByteBuffer response;
+
+ public WsFrameClient(ByteBuffer response, AsyncChannelWrapper channel,
+ WsSession wsSession) {
+ super(wsSession);
+ this.response = response;
+ this.channel = channel;
+ this.handler = new WsFrameClientCompletionHandler();
+
+ try {
+ processSocketRead();
+ } catch (IOException e) {
+ close(e);
+ }
+ }
+
+
+ private void processSocketRead() throws IOException {
+
+ while (response.hasRemaining()) {
+ int remaining = response.remaining();
+
+ int toCopy = Math.min(remaining, inputBuffer.length - writePos);
+
+ // Copy remaining bytes read in HTTP phase to input buffer used by
+ // frame processing
+ response.get(inputBuffer, writePos, toCopy);
+ writePos += toCopy;
+
+ // Process the data we have
+ processInputBuffer();
+ }
+ response.clear();
+
+ // Get some more data
+ if (isOpen()) {
+ channel.read(response, null, handler);
+ }
+ }
+
+
+ private final void close(Throwable t) {
+ CloseReason cr;
+ if (t instanceof WsIOException) {
+ cr = ((WsIOException) t).getCloseReason();
+ } else {
+ cr = new CloseReason(
+ CloseCodes.CLOSED_ABNORMALLY, t.getMessage());
+ }
+
+ try {
+ wsSession.close(cr);
+ } catch (IOException ignore) {
+ // Ignore
+ }
+ }
+
+
+ @Override
+ protected boolean isMasked() {
+ // Data is from the server so it is not masked
+ return false;
+ }
+
+
+ private class WsFrameClientCompletionHandler
+ implements CompletionHandler<Integer,Void> {
+
+ @Override
+ public void completed(Integer result, Void attachment) {
+ response.flip();
+ try {
+ processSocketRead();
+ } catch (IOException e) {
+ close(e);
+ }
+ }
+
+ @Override
+ public void failed(Throwable exc, Void attachment) {
+ if (exc instanceof ReadBufferOverflowException) {
+ // response will be empty if this exception is thrown
+ response = ByteBuffer.allocate(
+ ((ReadBufferOverflowException) exc).getMinBufferSize());
+ response.flip();
+ try {
+ processSocketRead();
+ } catch (IOException e) {
+ close(e);
+ }
+ } else {
+ close(exc);
+ }
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsHandshakeResponse.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsHandshakeResponse.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsHandshakeResponse.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,47 @@
+/*
+ * 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.websocket;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.websocket.HandshakeResponse;
+
+/**
+ * Represents the response to a WebSocket handshake.
+ */
+public class WsHandshakeResponse implements HandshakeResponse {
+
+ private final Map<String,List<String>> headers;
+
+
+ public WsHandshakeResponse() {
+ this(new HashMap<String,List<String>>());
+ }
+
+
+ public WsHandshakeResponse(Map<String,List<String>> headers) {
+ this.headers = headers;
+ }
+
+
+ @Override
+ public Map<String,List<String>> getHeaders() {
+ return headers;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsIOException.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsIOException.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsIOException.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,41 @@
+/*
+ * 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.websocket;
+
+import java.io.IOException;
+
+import javax.websocket.CloseReason;
+
+/**
+ * Allows the WebSocket implementation to throw an {@link IOException} that
+ * includes a {@link CloseReason} specific to the error that can be passed back
+ * to the client.
+ */
+public class WsIOException extends IOException {
+
+ private static final long serialVersionUID = 1L;
+
+ private final CloseReason closeReason;
+
+ public WsIOException(CloseReason closeReason) {
+ this.closeReason = closeReason;
+ }
+
+ public CloseReason getCloseReason() {
+ return closeReason;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsPongMessage.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsPongMessage.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsPongMessage.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,39 @@
+/*
+ * 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.websocket;
+
+import java.nio.ByteBuffer;
+
+import javax.websocket.PongMessage;
+
+public class WsPongMessage implements PongMessage {
+
+ private final ByteBuffer applicationData;
+
+
+ public WsPongMessage(ByteBuffer applicationData) {
+ byte[] dst = new byte[applicationData.limit()];
+ applicationData.get(dst);
+ this.applicationData = ByteBuffer.wrap(dst);
+ }
+
+
+ @Override
+ public ByteBuffer getApplicationData() {
+ return applicationData;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointAsync.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,79 @@
+/*
+ * 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.websocket;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendHandler;
+
+public class WsRemoteEndpointAsync extends WsRemoteEndpointBase
+ implements RemoteEndpoint.Async {
+
+ WsRemoteEndpointAsync(WsRemoteEndpointImplBase base) {
+ super(base);
+ }
+
+
+ @Override
+ public long getSendTimeout() {
+ return base.getSendTimeout();
+ }
+
+
+ @Override
+ public void setSendTimeout(long timeout) {
+ base.setSendTimeout(timeout);
+ }
+
+
+ @Override
+ public void sendText(String text, SendHandler completion) {
+ base.sendStringByCompletion(text, completion);
+ }
+
+
+ @Override
+ public Future<Void> sendText(String text) {
+ return base.sendStringByFuture(text);
+ }
+
+
+ @Override
+ public Future<Void> sendBinary(ByteBuffer data) {
+ return base.sendBytesByFuture(data);
+ }
+
+
+ @Override
+ public void sendBinary(ByteBuffer data, SendHandler completion) {
+ base.sendBytesByCompletion(data, completion);
+ }
+
+
+ @Override
+ public Future<Void> sendObject(Object obj) {
+ return base.sendObjectByFuture(obj);
+ }
+
+
+ @Override
+ public void sendObject(Object obj, SendHandler completion) {
+ base.sendObjectByCompletion(obj, completion);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointBase.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,64 @@
+/*
+ * 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.websocket;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.RemoteEndpoint;
+
+public abstract class WsRemoteEndpointBase implements RemoteEndpoint {
+
+ protected final WsRemoteEndpointImplBase base;
+
+
+ WsRemoteEndpointBase(WsRemoteEndpointImplBase base) {
+ this.base = base;
+ }
+
+
+ @Override
+ public final void setBatchingAllowed(boolean batchingAllowed) throws IOException {
+ base.setBatchingAllowed(batchingAllowed);
+ }
+
+
+ @Override
+ public final boolean getBatchingAllowed() {
+ return base.getBatchingAllowed();
+ }
+
+
+ @Override
+ public final void flushBatch() throws IOException {
+ base.flushBatch();
+ }
+
+
+ @Override
+ public final void sendPing(ByteBuffer applicationData) throws IOException,
+ IllegalArgumentException {
+ base.sendPing(applicationData);
+ }
+
+
+ @Override
+ public final void sendPong(ByteBuffer applicationData) throws IOException,
+ IllegalArgumentException {
+ base.sendPong(applicationData);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointBasic.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,76 @@
+/*
+ * 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.websocket;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.RemoteEndpoint;
+
+public class WsRemoteEndpointBasic extends WsRemoteEndpointBase
+ implements RemoteEndpoint.Basic {
+
+ WsRemoteEndpointBasic(WsRemoteEndpointImplBase base) {
+ super(base);
+ }
+
+
+ @Override
+ public void sendText(String text) throws IOException {
+ base.sendString(text);
+ }
+
+
+ @Override
+ public void sendBinary(ByteBuffer data) throws IOException {
+ base.sendBytes(data);
+ }
+
+
+ @Override
+ public void sendText(String fragment, boolean isLast) throws IOException {
+ base.sendPartialString(fragment, isLast);
+ }
+
+
+ @Override
+ public void sendBinary(ByteBuffer partialByte, boolean isLast)
+ throws IOException {
+ base.sendPartialBytes(partialByte, isLast);
+ }
+
+
+ @Override
+ public OutputStream getSendStream() throws IOException {
+ return base.getSendStream();
+ }
+
+
+ @Override
+ public Writer getSendWriter() throws IOException {
+ return base.getSendWriter();
+ }
+
+
+ @Override
+ public void sendObject(Object o) throws IOException, EncodeException {
+ base.sendObject(o);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,932 @@
+/*
+ * 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.websocket;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.EncodeException;
+import javax.websocket.Encoder;
+import javax.websocket.EndpointConfig;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+import org.apache.tomcat.util.buf.Utf8Encoder;
+import org.jboss.web.WebsocketsLogger;
+
+public abstract class WsRemoteEndpointImplBase implements RemoteEndpoint {
+
+ // Milliseconds so this is 20 seconds
+ private static final long DEFAULT_BLOCKING_SEND_TIMEOUT = 20 * 1000;
+
+ public static final String BLOCKING_SEND_TIMEOUT_PROPERTY =
+ "org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT";
+
+ private boolean messagePartInProgress = false;
+ private final Queue<MessagePart> messagePartQueue = new ArrayDeque<MessagePart>();
+ private final Object messagePartLock = new Object();
+ private boolean dataMessageInProgress = false;
+
+ // State
+ private boolean closed = false;
+ private boolean fragmented = false;
+ private boolean nextFragmented = false;
+ private boolean text = false;
+ private boolean nextText = false;
+
+ // Max size of WebSocket header is 14 bytes
+ private final ByteBuffer headerBuffer = ByteBuffer.allocate(14);
+ private final ByteBuffer outputBuffer = ByteBuffer.allocate(8192);
+ private final CharsetEncoder encoder = new Utf8Encoder();
+ private final ByteBuffer encoderBuffer = ByteBuffer.allocate(8192);
+ private final AtomicBoolean batchingAllowed = new AtomicBoolean(false);
+ private volatile long sendTimeout = -1;
+ private WsSession wsSession;
+ private List<EncoderEntry> encoderEntries = new ArrayList<EncoderEntry>();
+
+ public long getSendTimeout() {
+ return sendTimeout;
+ }
+
+
+ public void setSendTimeout(long timeout) {
+ this.sendTimeout = timeout;
+ }
+
+
+ @Override
+ public void setBatchingAllowed(boolean batchingAllowed) throws IOException {
+ boolean oldValue = this.batchingAllowed.getAndSet(batchingAllowed);
+
+ if (oldValue && !batchingAllowed) {
+ flushBatch();
+ }
+ }
+
+
+ @Override
+ public boolean getBatchingAllowed() {
+ return batchingAllowed.get();
+ }
+
+
+ @Override
+ public void flushBatch() throws IOException {
+ startMessageBlock(Constants.INTERNAL_OPCODE_FLUSH, null, true);
+ }
+
+
+ public void sendBytes(ByteBuffer data) throws IOException {
+ startMessageBlock(Constants.OPCODE_BINARY, data, true);
+ }
+
+
+ public Future<Void> sendBytesByFuture(ByteBuffer data) {
+ FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+ sendBytesByCompletion(data, f2sh);
+ return f2sh;
+ }
+
+
+ public void sendBytesByCompletion(ByteBuffer data, SendHandler handler) {
+ startMessage(Constants.OPCODE_BINARY, data, true, handler);
+ }
+
+
+ public void sendPartialBytes(ByteBuffer partialByte, boolean last)
+ throws IOException {
+ startMessageBlock(Constants.OPCODE_BINARY, partialByte, last);
+ }
+
+
+ @Override
+ public void sendPing(ByteBuffer applicationData) throws IOException,
+ IllegalArgumentException {
+ startMessageBlock(Constants.OPCODE_PING, applicationData, true);
+ }
+
+
+ @Override
+ public void sendPong(ByteBuffer applicationData) throws IOException,
+ IllegalArgumentException {
+ startMessageBlock(Constants.OPCODE_PONG, applicationData, true);
+ }
+
+
+ public void sendString(String text) throws IOException {
+ sendPartialString(CharBuffer.wrap(text), true);
+ }
+
+
+ public Future<Void> sendStringByFuture(String text) {
+ FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+ sendStringByCompletion(text, f2sh);
+ return f2sh;
+ }
+
+
+ public void sendStringByCompletion(String text, SendHandler handler) {
+ TextMessageSendHandler tmsh = new TextMessageSendHandler(handler,
+ CharBuffer.wrap(text), true, encoder, encoderBuffer, this);
+ tmsh.write();
+ }
+
+
+ public void sendPartialString(String fragment, boolean isLast)
+ throws IOException {
+ sendPartialString(CharBuffer.wrap(fragment), isLast);
+ }
+
+
+ public OutputStream getSendStream() {
+ return new WsOutputStream(this);
+ }
+
+
+ public Writer getSendWriter() {
+ return new WsWriter(this);
+ }
+
+
+ void sendPartialString(CharBuffer part, boolean last) throws IOException {
+ try {
+ // Get the timeout before we send the message. The message may
+ // trigger a session close and depending on timing the client
+ // session may close before we can read the timeout.
+ long timeout = getBlockingSendTimeout();
+ FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+ TextMessageSendHandler tmsh = new TextMessageSendHandler(f2sh, part,
+ last, encoder, encoderBuffer, this);
+ tmsh.write();
+ if (timeout == -1) {
+ f2sh.get();
+ } else {
+ f2sh.get(timeout, TimeUnit.MILLISECONDS);
+ }
+ } catch (InterruptedException e) {
+ throw new IOException(e);
+ } catch (ExecutionException e) {
+ throw new IOException(e);
+ } catch (TimeoutException e) {
+ throw new IOException(e);
+ }
+ }
+
+
+ void startMessageBlock(byte opCode, ByteBuffer payload, boolean last)
+ throws IOException {
+ // Get the timeout before we send the message. The message may
+ // trigger a session close and depending on timing the client
+ // session may close before we can read the timeout.
+ long timeout = getBlockingSendTimeout();
+ FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+ startMessage(opCode, payload, last, f2sh);
+ try {
+ if (timeout == -1) {
+ f2sh.get();
+ } else {
+ f2sh.get(timeout, TimeUnit.MILLISECONDS);
+ }
+ } catch (InterruptedException e) {
+ throw new IOException(e);
+ } catch (ExecutionException e) {
+ throw new IOException(e);
+ } catch (TimeoutException e) {
+ throw new IOException(e);
+ }
+ }
+
+
+ void startMessage(byte opCode, ByteBuffer payload, boolean last,
+ SendHandler handler) {
+
+ wsSession.updateLastActive();
+
+ MessagePart mp = new MessagePart(opCode, payload, last, handler, this);
+
+ synchronized (messagePartLock) {
+ if (Constants.OPCODE_CLOSE == mp.getOpCode()) {
+ try {
+ setBatchingAllowed(false);
+ } catch (IOException e) {
+ WebsocketsLogger.ROOT_LOGGER.flushOnCloseFailed(e);
+ }
+ }
+ if (messagePartInProgress) {
+ if (!Util.isControl(opCode)) {
+ if (dataMessageInProgress) {
+ throw MESSAGES.messageInProgress();
+ } else {
+ dataMessageInProgress = true;
+ }
+ }
+ messagePartQueue.add(mp);
+ } else {
+ messagePartInProgress = true;
+ writeMessagePart(mp);
+ }
+ }
+ }
+
+
+ void endMessage(SendHandler handler, SendResult result,
+ boolean dataMessage) {
+ synchronized (messagePartLock) {
+
+ fragmented = nextFragmented;
+ text = nextText;
+
+ if (dataMessage) {
+ dataMessageInProgress = false;
+ }
+ MessagePart mpNext = messagePartQueue.poll();
+ if (mpNext == null) {
+ messagePartInProgress = false;
+ } else {
+ writeMessagePart(mpNext);
+ }
+ }
+
+ wsSession.updateLastActive();
+
+ handler.onResult(result);
+ }
+
+
+ void writeMessagePart(MessagePart mp) {
+
+ if (closed) {
+ throw MESSAGES.messageSessionClosed();
+ }
+
+ if (Constants.INTERNAL_OPCODE_FLUSH == mp.getOpCode()) {
+ nextFragmented = fragmented;
+ nextText = text;
+ doWrite(mp.getHandler(), outputBuffer);
+ return;
+ }
+
+ // Control messages may be sent in the middle of fragmented message
+ // so they have no effect on the fragmented or text flags
+ boolean first;
+ if (Util.isControl(mp.getOpCode())) {
+ nextFragmented = fragmented;
+ nextText = text;
+ if (mp.getOpCode() == Constants.OPCODE_CLOSE) {
+ closed = true;
+ }
+ first = true;
+ } else {
+ boolean isText = Util.isText(mp.getOpCode());
+
+ if (fragmented) {
+ // Currently fragmented
+ if (text != isText) {
+ throw MESSAGES.messageFragmentTypeChange();
+ }
+ nextText = text;
+ nextFragmented = !mp.isLast();
+ first = false;
+ } else {
+ // Wasn't fragmented. Might be now
+ if (mp.isLast()) {
+ nextFragmented = false;
+ } else {
+ nextFragmented = true;
+ nextText = isText;
+ }
+ first = true;
+ }
+ }
+
+ byte[] mask;
+
+ if (isMasked()) {
+ mask = Util.generateMask();
+ } else {
+ mask = null;
+ }
+
+ headerBuffer.clear();
+ writeHeader(headerBuffer, mp.getOpCode(), mp.getPayload(), first,
+ mp.isLast(), isMasked(), mask);
+ headerBuffer.flip();
+
+ if (getBatchingAllowed() || isMasked()) {
+ // Need to write via output buffer
+ OutputBufferSendHandler obsh = new OutputBufferSendHandler(
+ mp.getHandler(), headerBuffer, mp.getPayload(), mask,
+ outputBuffer, !getBatchingAllowed(), this);
+ obsh.write();
+ } else {
+ // Can write directly
+ doWrite(mp.getHandler(), headerBuffer, mp.getPayload());
+ }
+
+ }
+
+
+ private long getBlockingSendTimeout() {
+ Object obj = wsSession.getUserProperties().get(
+ BLOCKING_SEND_TIMEOUT_PROPERTY);
+ Long userTimeout = null;
+ if (obj instanceof Long) {
+ userTimeout = (Long) obj;
+ }
+ if (userTimeout == null) {
+ return DEFAULT_BLOCKING_SEND_TIMEOUT;
+ } else {
+ return userTimeout.longValue();
+ }
+ }
+
+
+ private static class MessagePart {
+ private final byte opCode;
+ private final ByteBuffer payload;
+ private final boolean last;
+ private final SendHandler handler;
+
+ public MessagePart(byte opCode, ByteBuffer payload, boolean last,
+ SendHandler handler, WsRemoteEndpointImplBase endpoint) {
+ this.opCode = opCode;
+ this.payload = payload;
+ this.last = last;
+ this.handler = new EndMessageHandler(
+ endpoint, handler, !Util.isControl(opCode));
+ }
+
+
+ public byte getOpCode() {
+ return opCode;
+ }
+
+
+ public ByteBuffer getPayload() {
+ return payload;
+ }
+
+
+ public boolean isLast() {
+ return last;
+ }
+
+
+ public SendHandler getHandler() {
+ return handler;
+ }
+ }
+
+
+ /**
+ * Wraps the user provided handler so that the end point is notified when
+ * the message is complete.
+ */
+ private static class EndMessageHandler implements SendHandler {
+
+ private final WsRemoteEndpointImplBase endpoint;
+ private final SendHandler handler;
+ private final boolean dataMessage;
+
+ public EndMessageHandler(WsRemoteEndpointImplBase endpoint,
+ SendHandler handler, boolean dataMessage) {
+ this.endpoint = endpoint;
+ this.handler = handler;
+ this.dataMessage = dataMessage;
+ }
+
+
+ @Override
+ public void onResult(SendResult result) {
+ endpoint.endMessage(handler, result, dataMessage);
+ }
+ }
+
+
+ public void sendObject(Object obj) throws IOException {
+ Future<Void> f = sendObjectByFuture(obj);
+ try {
+ f.get();
+ } catch (InterruptedException e) {
+ throw new IOException(e);
+ } catch (ExecutionException e) {
+ throw new IOException(e);
+ }
+ }
+
+ public Future<Void> sendObjectByFuture(Object obj) {
+ FutureToSendHandler f2sh = new FutureToSendHandler(wsSession);
+ sendObjectByCompletion(obj, f2sh);
+ return f2sh;
+ }
+
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public void sendObjectByCompletion(Object obj, SendHandler completion) {
+
+ if (Util.isPrimitive(obj.getClass())) {
+ String msg = obj.toString();
+ sendStringByCompletion(msg, completion);
+ return;
+ }
+
+ Encoder encoder = findEncoder(obj);
+
+ try {
+ if (encoder instanceof Encoder.Text) {
+ String msg = ((Encoder.Text) encoder).encode(obj);
+ sendStringByCompletion(msg, completion);
+ } else if (encoder instanceof Encoder.TextStream) {
+ Writer w = null;
+ try {
+ w = getSendWriter();
+ ((Encoder.TextStream) encoder).encode(obj, w);
+ } finally {
+ if (w != null) {
+ try {
+ w.close();
+ } catch (IOException ioe) {
+ // Ignore
+ }
+ }
+ }
+ completion.onResult(new SendResult());
+ } else if (encoder instanceof Encoder.Binary) {
+ ByteBuffer msg = ((Encoder.Binary) encoder).encode(obj);
+ sendBytesByCompletion(msg, completion);
+ } else if (encoder instanceof Encoder.BinaryStream) {
+ OutputStream os = null;
+ try {
+ os = getSendStream();
+ ((Encoder.BinaryStream) encoder).encode(obj, os);
+ } finally {
+ if (os != null) {
+ try {
+ os.close();
+ } catch (IOException ioe) {
+ // Ignore
+ }
+ }
+ }
+ completion.onResult(new SendResult());
+ } else {
+ throw new EncodeException(obj, MESSAGES.noEncoderForClass(obj.getClass().getName()));
+ }
+ } catch (EncodeException e) {
+ SendResult sr = new SendResult(e);
+ completion.onResult(sr);
+ } catch (IOException e) {
+ SendResult sr = new SendResult(e);
+ completion.onResult(sr);
+ }
+ }
+
+
+ protected void setSession(WsSession wsSession) {
+ this.wsSession = wsSession;
+ }
+
+
+ protected void setEncoders(EndpointConfig endpointConfig)
+ throws DeploymentException {
+ encoderEntries.clear();
+ for (Class<? extends Encoder> encoderClazz :
+ endpointConfig.getEncoders()) {
+ Encoder instance;
+ try {
+ instance = encoderClazz.newInstance();
+ instance.init(endpointConfig);
+ } catch (InstantiationException e) {
+ throw new DeploymentException(
+ MESSAGES.cannotInstatiateEncoder(encoderClazz.getName()), e);
+ } catch (IllegalAccessException e) {
+ throw new DeploymentException(
+ MESSAGES.cannotInstatiateEncoder(encoderClazz.getName()), e);
+ }
+ EncoderEntry entry = new EncoderEntry(
+ Util.getEncoderType(encoderClazz), instance);
+ encoderEntries.add(entry);
+ }
+ }
+
+
+ private Encoder findEncoder(Object obj) {
+ for (EncoderEntry entry : encoderEntries) {
+ if (entry.getClazz().isAssignableFrom(obj.getClass())) {
+ return entry.getEncoder();
+ }
+ }
+ return null;
+ }
+
+
+ public final void close() {
+ for (EncoderEntry entry : encoderEntries) {
+ entry.getEncoder().destroy();
+ }
+ doClose();
+ }
+
+
+ protected abstract void doWrite(SendHandler handler, ByteBuffer... data);
+ protected abstract boolean isMasked();
+ protected abstract void doClose();
+
+ private static void writeHeader(ByteBuffer headerBuffer, byte opCode,
+ ByteBuffer payload, boolean first, boolean last, boolean masked,
+ byte[] mask) {
+
+ byte b = 0;
+
+ if (last) {
+ // Set the fin bit
+ b = -128;
+ }
+
+ if (first) {
+ // This is the first fragment of this message
+ b = (byte) (b + opCode);
+ }
+ // If not the first fragment, it is a continuation with opCode of zero
+
+ headerBuffer.put(b);
+
+ if (masked) {
+ b = (byte) 0x80;
+ } else {
+ b = 0;
+ }
+
+ // Next write the mask && length length
+ if (payload.limit() < 126) {
+ headerBuffer.put((byte) (payload.limit() | b));
+ } else if (payload.limit() < 65536) {
+ headerBuffer.put((byte) (126 | b));
+ headerBuffer.put((byte) (payload.limit() >>> 8));
+ headerBuffer.put((byte) (payload.limit() & 0xFF));
+ } else {
+ // Will never be more than 2^31-1
+ headerBuffer.put((byte) (127 | b));
+ headerBuffer.put((byte) 0);
+ headerBuffer.put((byte) 0);
+ headerBuffer.put((byte) 0);
+ headerBuffer.put((byte) 0);
+ headerBuffer.put((byte) (payload.limit() >>> 24));
+ headerBuffer.put((byte) (payload.limit() >>> 16));
+ headerBuffer.put((byte) (payload.limit() >>> 8));
+ headerBuffer.put((byte) (payload.limit() & 0xFF));
+ }
+ if (masked) {
+ headerBuffer.put(mask[0]);
+ headerBuffer.put(mask[1]);
+ headerBuffer.put(mask[2]);
+ headerBuffer.put(mask[3]);
+ }
+ }
+
+
+ private static class TextMessageSendHandler implements SendHandler {
+
+ private final SendHandler handler;
+ private final CharBuffer message;
+ private final boolean isLast;
+ private final CharsetEncoder encoder;
+ private final ByteBuffer buffer;
+ private final WsRemoteEndpointImplBase endpoint;
+ private volatile boolean isDone = false;
+
+ public TextMessageSendHandler(SendHandler handler, CharBuffer message,
+ boolean isLast, CharsetEncoder encoder,
+ ByteBuffer encoderBuffer, WsRemoteEndpointImplBase endpoint) {
+ this.handler = handler;
+ this.message = message;
+ this.isLast = isLast;
+ this.encoder = encoder.reset();
+ this.buffer = encoderBuffer;
+ this.endpoint = endpoint;
+ }
+
+ public void write() {
+ buffer.clear();
+ CoderResult cr = encoder.encode(message, buffer, true);
+ if (cr.isError()) {
+ throw new IllegalArgumentException(cr.toString());
+ }
+ isDone = !cr.isOverflow();
+ buffer.flip();
+ endpoint.startMessage(Constants.OPCODE_TEXT, buffer,
+ isDone && isLast, this);
+ }
+
+ @Override
+ public void onResult(SendResult result) {
+ if (isDone || !result.isOK()) {
+ handler.onResult(result);
+ } else {
+ write();
+ }
+ }
+ }
+
+
+ /**
+ * Used to write data to the output buffer, flushing the buffer if it fills
+ * up.
+ */
+ private static class OutputBufferSendHandler implements SendHandler {
+
+ private final SendHandler handler;
+ private final ByteBuffer headerBuffer;
+ private final ByteBuffer payload;
+ private final byte[] mask;
+ private final ByteBuffer outputBuffer;
+ private final boolean flushRequired;
+ private final WsRemoteEndpointImplBase endpoint;
+ private int maskIndex = 0;
+
+ public OutputBufferSendHandler(SendHandler completion,
+ ByteBuffer headerBuffer, ByteBuffer payload, byte[] mask,
+ ByteBuffer outputBuffer, boolean flushRequired,
+ WsRemoteEndpointImplBase endpoint) {
+ this.handler = completion;
+ this.headerBuffer = headerBuffer;
+ this.payload = payload;
+ this.mask = mask;
+ this.outputBuffer = outputBuffer;
+ this.flushRequired = flushRequired;
+ this.endpoint = endpoint;
+ }
+
+ public void write() {
+ // Write the header
+ while (headerBuffer.hasRemaining() && outputBuffer.hasRemaining()) {
+ outputBuffer.put(headerBuffer.get());
+ }
+ if (headerBuffer.hasRemaining()) {
+ // Still more headers to write, need to flush
+ outputBuffer.flip();
+ endpoint.doWrite(this, outputBuffer);
+ return;
+ }
+
+ // Write the payload
+ int payloadLeft = payload.remaining();
+ int payloadLimit = payload.limit();
+ int outputSpace = outputBuffer.remaining();
+ int toWrite = payloadLeft;
+
+ if (payloadLeft > outputSpace) {
+ toWrite = outputSpace;
+ // Temporarily reduce the limit
+ payload.limit(payload.position() + toWrite);
+ }
+
+ if (mask == null) {
+ // Use a bulk copy
+ outputBuffer.put(payload);
+ } else {
+ for (int i = 0; i < toWrite; i++) {
+ outputBuffer.put(
+ (byte) (payload.get() ^ (mask[maskIndex++] & 0xFF)));
+ if (maskIndex > 3) {
+ maskIndex = 0;
+ }
+ }
+ }
+
+ if (payloadLeft > outputSpace) {
+ // Restore the original limit
+ payload.limit(payloadLimit);
+ // Still more headers to write, need to flush
+ outputBuffer.flip();
+ endpoint.doWrite(this, outputBuffer);
+ return;
+ }
+
+ if (flushRequired) {
+ outputBuffer.flip();
+ if (outputBuffer.remaining() == 0) {
+ handler.onResult(new SendResult());
+ } else {
+ endpoint.doWrite(this, outputBuffer);
+ }
+ } else {
+ handler.onResult(new SendResult());
+ }
+ }
+
+ // ------------------------------------------------- SendHandler methods
+ @Override
+ public void onResult(SendResult result) {
+ if (result.isOK()) {
+ if (outputBuffer.hasRemaining()) {
+ endpoint.doWrite(this, outputBuffer);
+ } else {
+ outputBuffer.clear();
+ write();
+ }
+ } else {
+ handler.onResult(result);
+ }
+ }
+ }
+
+ private static class WsOutputStream extends OutputStream {
+
+ private final WsRemoteEndpointImplBase endpoint;
+ private final ByteBuffer buffer = ByteBuffer.allocate(8192);
+ private final Object closeLock = new Object();
+ private volatile boolean closed = false;
+
+ public WsOutputStream(WsRemoteEndpointImplBase endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (closed) {
+ throw MESSAGES.closedOutputStream();
+ }
+
+ if (buffer.remaining() == 0) {
+ flush();
+ }
+ buffer.put((byte) b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (closed) {
+ throw MESSAGES.closedOutputStream();
+ }
+ if (len == 0) {
+ return;
+ }
+ if ((off < 0) || (off > b.length) || (len < 0) ||
+ ((off + len) > b.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (buffer.remaining() == 0) {
+ flush();
+ }
+ int remaining = buffer.remaining();
+ int written = 0;
+
+ while (remaining < len - written) {
+ buffer.put(b, off + written, remaining);
+ written += remaining;
+ flush();
+ remaining = buffer.remaining();
+ }
+ buffer.put(b, off + written, len - written);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (closed) {
+ throw MESSAGES.closedOutputStream();
+ }
+
+ doWrite(false);
+ }
+
+ @Override
+ public void close() throws IOException {
+ synchronized (closeLock) {
+ if (closed) {
+ return;
+ }
+ closed = true;
+ }
+
+ doWrite(true);
+ }
+
+ private void doWrite(boolean last) throws IOException {
+ buffer.flip();
+ endpoint.sendPartialBytes(buffer, last);
+ buffer.clear();
+ }
+ }
+
+
+ private static class WsWriter extends Writer {
+
+ private final WsRemoteEndpointImplBase endpoint;
+ private final CharBuffer buffer = CharBuffer.allocate(8192);
+ private final Object closeLock = new Object();
+ private volatile boolean closed = false;
+
+ public WsWriter(WsRemoteEndpointImplBase endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ if (closed) {
+ throw MESSAGES.closedWriter();
+ }
+ if (len == 0) {
+ return;
+ }
+ if ((off < 0) || (off > cbuf.length) || (len < 0) ||
+ ((off + len) > cbuf.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ if (buffer.remaining() == 0) {
+ flush();
+ }
+ int remaining = buffer.remaining();
+ int written = 0;
+
+ while (remaining < len - written) {
+ buffer.put(cbuf, off + written, remaining);
+ written += remaining;
+ flush();
+ remaining = buffer.remaining();
+ }
+ buffer.put(cbuf, off + written, len - written);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (closed) {
+ throw MESSAGES.closedWriter();
+ }
+
+ doWrite(false);
+ }
+
+ @Override
+ public void close() throws IOException {
+ synchronized (closeLock) {
+ if (closed) {
+ return;
+ }
+ closed = true;
+ }
+
+ doWrite(true);
+ }
+
+ private void doWrite(boolean last) throws IOException {
+ buffer.flip();
+ endpoint.sendPartialString(buffer, last);
+ buffer.clear();
+ }
+ }
+
+
+ private static class EncoderEntry {
+
+ private final Class<?> clazz;
+ private final Encoder encoder;
+
+ public EncoderEntry(Class<?> clazz, Encoder encoder) {
+ this.clazz = clazz;
+ this.encoder = encoder;
+ }
+
+ public Class<?> getClazz() {
+ return clazz;
+ }
+
+ public Encoder getEncoder() {
+ return encoder;
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointImplClient.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointImplClient.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsRemoteEndpointImplClient.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,56 @@
+/*
+ * 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.websocket;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import javax.websocket.SendHandler;
+
+public class WsRemoteEndpointImplClient extends WsRemoteEndpointImplBase {
+
+ private final AsyncChannelWrapper channel;
+
+ public WsRemoteEndpointImplClient(AsyncChannelWrapper channel) {
+ this.channel = channel;
+ }
+
+
+ @Override
+ protected boolean isMasked() {
+ return true;
+ }
+
+
+ @Override
+ protected void doWrite(SendHandler handler, ByteBuffer... data) {
+ long timeout = getSendTimeout();
+ if (timeout < 1) {
+ timeout = Long.MAX_VALUE;
+
+ }
+ SendHandlerToCompletionHandler sh2ch =
+ new SendHandlerToCompletionHandler(handler);
+ channel.write(data, 0, data.length, timeout, TimeUnit.MILLISECONDS,
+ null, sh2ch);
+ }
+
+ @Override
+ protected void doClose() {
+ channel.close();
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsSession.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsSession.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsSession.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,632 @@
+/*
+ * 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.websocket;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Extension;
+import javax.websocket.MessageHandler;
+import javax.websocket.PongMessage;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.SendResult;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.jboss.web.WebsocketsLogger;
+
+public class WsSession implements Session {
+
+ // An ellipsis is a single character that looks like three periods in a row
+ // and is used to indicate a continuation.
+ private static final byte[] ELLIPSIS_BYTES =
+ "\u2026".getBytes(StandardCharsets.UTF_8);
+ // An ellipsis is three bytes in UTF-8
+ private static final int ELLIPSIS_BYTES_LEN = ELLIPSIS_BYTES.length;
+
+ private static AtomicLong ids = new AtomicLong(0);
+
+ private final Endpoint localEndpoint;
+ private final WsRemoteEndpointImplBase wsRemoteEndpoint;
+ private final RemoteEndpoint.Async remoteEndpointAsync;
+ private final RemoteEndpoint.Basic remoteEndpointBasic;
+ private final ClassLoader applicationClassLoader;
+ private final WsWebSocketContainer webSocketContainer;
+ private final URI requestUri;
+ private final Map<String,List<String>> requestParameterMap;
+ private final String queryString;
+ private final Principal userPrincipal;
+ private final EndpointConfig endpointConfig;
+
+ private final String subProtocol;
+ private final Map<String,String> pathParameters;
+ private final boolean secure;
+ private final String httpSessionId;
+ private final String id;
+
+ // Expected to handle message types of <String> only
+ private MessageHandler textMessageHandler = null;
+ // Expected to handle message types of <ByteBuffer> only
+ private MessageHandler binaryMessageHandler = null;
+ private MessageHandler.Whole<PongMessage> pongMessageHandler = null;
+ private volatile State state = State.OPEN;
+ private final Object stateLock = new Object();
+ private final Map<String,Object> userProperties = new ConcurrentHashMap<String, Object>();
+ private volatile int maxBinaryMessageBufferSize =
+ Constants.DEFAULT_BUFFER_SIZE;
+ private volatile int maxTextMessageBufferSize =
+ Constants.DEFAULT_BUFFER_SIZE;
+ private volatile long maxIdleTimeout = 0;
+ private volatile long lastActive = System.currentTimeMillis();
+ private Map<FutureToSendHandler,FutureToSendHandler> futures =
+ new ConcurrentHashMap<FutureToSendHandler,FutureToSendHandler>();
+
+ /**
+ * Creates a new WebSocket session for communication between the two
+ * provided end points. The result of {@link Thread#getContextClassLoader()}
+ * at the time this constructor is called will be used when calling
+ * {@link Endpoint#onClose(Session, CloseReason)}.
+ *
+ * @param localEndpoint
+ * @param wsRemoteEndpoint
+ * @throws DeploymentException
+ */
+ public WsSession(Endpoint localEndpoint,
+ WsRemoteEndpointImplBase wsRemoteEndpoint,
+ WsWebSocketContainer wsWebSocketContainer,
+ URI requestUri, Map<String,List<String>> requestParameterMap,
+ String queryString, Principal userPrincipal, String httpSessionId,
+ String subProtocol, Map<String,String> pathParameters,
+ boolean secure, EndpointConfig endpointConfig)
+ throws DeploymentException {
+ this.localEndpoint = localEndpoint;
+ this.wsRemoteEndpoint = wsRemoteEndpoint;
+ this.wsRemoteEndpoint.setSession(this);
+ this.remoteEndpointAsync = new WsRemoteEndpointAsync(wsRemoteEndpoint);
+ this.remoteEndpointBasic = new WsRemoteEndpointBasic(wsRemoteEndpoint);
+ this.webSocketContainer = wsWebSocketContainer;
+ applicationClassLoader = Thread.currentThread().getContextClassLoader();
+ wsRemoteEndpoint.setSendTimeout(
+ wsWebSocketContainer.getDefaultAsyncSendTimeout());
+ this.maxBinaryMessageBufferSize =
+ webSocketContainer.getDefaultMaxBinaryMessageBufferSize();
+ this.maxTextMessageBufferSize =
+ webSocketContainer.getDefaultMaxTextMessageBufferSize();
+ this.maxIdleTimeout =
+ webSocketContainer.getDefaultMaxSessionIdleTimeout();
+ this.requestUri = requestUri;
+ if (requestParameterMap == null) {
+ this.requestParameterMap = Collections.emptyMap();
+ } else {
+ this.requestParameterMap = requestParameterMap;
+ }
+ this.queryString = queryString;
+ this.userPrincipal = userPrincipal;
+ this.httpSessionId = httpSessionId;
+ if (subProtocol == null) {
+ this.subProtocol = "";
+ } else {
+ this.subProtocol = subProtocol;
+ }
+ this.pathParameters = pathParameters;
+ this.secure = secure;
+ this.wsRemoteEndpoint.setEncoders(endpointConfig);
+ this.endpointConfig = endpointConfig;
+
+ this.userProperties.putAll(endpointConfig.getUserProperties());
+ this.id = Long.toHexString(ids.getAndIncrement());
+ }
+
+
+ @Override
+ public WebSocketContainer getContainer() {
+ checkState();
+ return webSocketContainer;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void addMessageHandler(MessageHandler listener) {
+
+ checkState();
+
+ // Message handlers that require decoders may map to text messages,
+ // binary messages, both or neither.
+
+ // The frame processing code expects binary message handlers to
+ // accept ByteBuffer
+
+ // Use the POJO message handler wrappers as they are designed to wrap
+ // arbitrary objects with MessageHandlers and can wrap MessageHandlers
+ // just as easily.
+
+ Set<MessageHandlerResult> mhResults =
+ Util.getMessageHandlers(listener, endpointConfig);
+
+ for (MessageHandlerResult mhResult : mhResults) {
+ switch (mhResult.getType()) {
+ case TEXT: {
+ if (textMessageHandler != null) {
+ throw MESSAGES.duplicateHandlerText();
+ }
+ textMessageHandler = mhResult.getHandler();
+ break;
+ }
+ case BINARY: {
+ if (binaryMessageHandler != null) {
+ throw MESSAGES.duplicateHandlerBinary();
+ }
+ binaryMessageHandler = mhResult.getHandler();
+ break;
+ }
+ case PONG: {
+ if (pongMessageHandler != null) {
+ throw MESSAGES.duplicateHandlerPong();
+ }
+ MessageHandler handler = mhResult.getHandler();
+ if (handler instanceof MessageHandler.Whole<?>) {
+ pongMessageHandler =
+ (MessageHandler.Whole<PongMessage>) handler;
+ } else {
+ throw MESSAGES.invalidHandlerPong();
+ }
+
+ break;
+ }
+ default: {
+ throw MESSAGES.invalidMessageHandler(listener,
+ mhResult.getType());
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public Set<MessageHandler> getMessageHandlers() {
+ checkState();
+ Set<MessageHandler> result = new HashSet<MessageHandler>();
+ if (binaryMessageHandler != null) {
+ result.add(binaryMessageHandler);
+ }
+ if (textMessageHandler != null) {
+ result.add(textMessageHandler);
+ }
+ if (pongMessageHandler != null) {
+ result.add(pongMessageHandler);
+ }
+ return result;
+ }
+
+
+ @Override
+ public void removeMessageHandler(MessageHandler listener) {
+ checkState();
+ if (listener == null) {
+ return;
+ }
+
+ MessageHandler wrapped = null;
+
+ if (listener instanceof WrappedMessageHandler) {
+ wrapped = ((WrappedMessageHandler) listener).getWrappedHandler();
+ }
+
+ if (wrapped == null) {
+ wrapped = listener;
+ }
+
+ boolean removed = false;
+ if (wrapped.equals(textMessageHandler) ||
+ listener.equals(textMessageHandler)) {
+ textMessageHandler = null;
+ removed = true;
+ }
+
+ if (listener.equals(binaryMessageHandler) ||
+ listener.equals(binaryMessageHandler)) {
+ binaryMessageHandler = null;
+ removed = true;
+ }
+
+ if (listener.equals(pongMessageHandler) ||
+ listener.equals(pongMessageHandler)) {
+ pongMessageHandler = null;
+ removed = true;
+ }
+
+ if (!removed) {
+ // ISE for now. Could swallow this silently / log this if the ISE
+ // becomes a problem
+ throw MESSAGES.cannotRemoveHandler(listener);
+ }
+ }
+
+
+ @Override
+ public String getProtocolVersion() {
+ checkState();
+ return Constants.WS_VERSION_HEADER_VALUE;
+ }
+
+
+ @Override
+ public String getNegotiatedSubprotocol() {
+ checkState();
+ return subProtocol;
+ }
+
+
+ @Override
+ public List<Extension> getNegotiatedExtensions() {
+ checkState();
+ return Collections.emptyList();
+ }
+
+
+ @Override
+ public boolean isSecure() {
+ checkState();
+ return secure;
+ }
+
+
+ @Override
+ public boolean isOpen() {
+ return state == State.OPEN;
+ }
+
+
+ @Override
+ public long getMaxIdleTimeout() {
+ checkState();
+ return maxIdleTimeout;
+ }
+
+
+ @Override
+ public void setMaxIdleTimeout(long timeout) {
+ checkState();
+ this.maxIdleTimeout = timeout;
+ }
+
+
+ @Override
+ public void setMaxBinaryMessageBufferSize(int max) {
+ checkState();
+ this.maxBinaryMessageBufferSize = max;
+ }
+
+
+ @Override
+ public int getMaxBinaryMessageBufferSize() {
+ checkState();
+ return maxBinaryMessageBufferSize;
+ }
+
+
+ @Override
+ public void setMaxTextMessageBufferSize(int max) {
+ checkState();
+ this.maxTextMessageBufferSize = max;
+ }
+
+
+ @Override
+ public int getMaxTextMessageBufferSize() {
+ checkState();
+ return maxTextMessageBufferSize;
+ }
+
+
+ @Override
+ public Set<Session> getOpenSessions() {
+ checkState();
+ return webSocketContainer.getOpenSessions(localEndpoint.getClass());
+ }
+
+
+ @Override
+ public RemoteEndpoint.Async getAsyncRemote() {
+ checkState();
+ return remoteEndpointAsync;
+ }
+
+
+ @Override
+ public RemoteEndpoint.Basic getBasicRemote() {
+ checkState();
+ return remoteEndpointBasic;
+ }
+
+
+ @Override
+ public void close() throws IOException {
+ close(new CloseReason(CloseCodes.NORMAL_CLOSURE, ""));
+ }
+
+
+ @Override
+ public void close(CloseReason closeReason) throws IOException {
+ doClose(closeReason, closeReason);
+ }
+
+
+ /**
+ * WebSocket 1.0. Section 2.1.5.
+ * Need internal close method as spec requires that the local endpoint
+ * receives a 1006 on timeout.
+ */
+ private void doClose(CloseReason closeReasonMessage,
+ CloseReason closeReasonLocal) {
+ // Double-checked locking. OK because state is volatile
+ if (state != State.OPEN) {
+ return;
+ }
+
+ synchronized (stateLock) {
+ if (state != State.OPEN) {
+ return;
+ }
+
+ state = State.CLOSING;
+
+ sendCloseMessage(closeReasonMessage);
+ fireEndpointOnClose(closeReasonLocal);
+
+ state = State.CLOSED;
+ }
+
+ IOException ioe = new IOException(MESSAGES.messageFailed());
+ SendResult sr = new SendResult(ioe);
+ for (FutureToSendHandler f2sh : futures.keySet()) {
+ f2sh.onResult(sr);
+ }
+ }
+
+
+ /**
+ * Called when a close message is received. Should only ever happen once.
+ * Also called after a protocol error when the ProtocolHandler needs to
+ * force the closing of the connection.
+ */
+ public void onClose(CloseReason closeReason) {
+
+ synchronized (stateLock) {
+ if (state == State.OPEN) {
+ sendCloseMessage(closeReason);
+ fireEndpointOnClose(closeReason);
+ state = State.CLOSED;
+ }
+
+ // Close the socket
+ wsRemoteEndpoint.close();
+ }
+ }
+
+
+ private void fireEndpointOnClose(CloseReason closeReason) {
+
+ // Fire the onClose event
+ Thread t = Thread.currentThread();
+ ClassLoader cl = t.getContextClassLoader();
+ t.setContextClassLoader(applicationClassLoader);
+ try {
+ localEndpoint.onClose(this, closeReason);
+ } finally {
+ t.setContextClassLoader(cl);
+ }
+ }
+
+
+ private void sendCloseMessage(CloseReason closeReason) {
+ // 125 is maximum size for the payload of a control message
+ ByteBuffer msg = ByteBuffer.allocate(125);
+ msg.putShort((short) closeReason.getCloseCode().getCode());
+
+ String reason = closeReason.getReasonPhrase();
+ if (reason != null && reason.length() > 0) {
+ appendCloseReasonWithTruncation(msg, reason);
+ }
+ msg.flip();
+ try {
+ wsRemoteEndpoint.startMessageBlock(
+ Constants.OPCODE_CLOSE, msg, true);
+ } catch (IOException ioe) {
+ // Failed to send close message. Close the socket and let the caller
+ // deal with the Exception
+ WebsocketsLogger.ROOT_LOGGER.closeMessageFail(ioe);
+ wsRemoteEndpoint.close();
+ localEndpoint.onError(this, ioe);
+ } finally {
+ webSocketContainer.unregisterSession(localEndpoint, this);
+ }
+ }
+
+
+ /**
+ * Use protected so unit tests can access this method directly.
+ */
+ protected static void appendCloseReasonWithTruncation(ByteBuffer msg,
+ String reason) {
+ // Once the close code has been added there are a maximum of 123 bytes
+ // left for the reason phrase. If it is truncated then care needs to be
+ // taken to ensure the bytes are not truncated in the middle of a
+ // multi-byte UTF-8 character.
+ byte[] reasonBytes = reason.getBytes(StandardCharsets.UTF_8);
+
+ if (reasonBytes.length <= 123) {
+ // No need to truncate
+ msg.put(reasonBytes);
+ } else {
+ // Need to truncate
+ int remaining = 123 - ELLIPSIS_BYTES_LEN;
+ int pos = 0;
+ byte[] bytesNext = reason.substring(pos, pos + 1).getBytes(
+ StandardCharsets.UTF_8);
+ while (remaining >= bytesNext.length) {
+ msg.put(bytesNext);
+ remaining -= bytesNext.length;
+ pos++;
+ bytesNext = reason.substring(pos, pos + 1).getBytes(
+ StandardCharsets.UTF_8);
+ }
+ msg.put(ELLIPSIS_BYTES);
+ }
+ }
+
+
+ /**
+ * Make the session aware of a {@link FutureToSendHandler} that will need to
+ * be forcibly closed if the session closes before the
+ * {@link FutureToSendHandler} completes.
+ */
+ protected void registerFuture(FutureToSendHandler f2sh) {
+ futures.put(f2sh, f2sh);
+ }
+
+
+ /**
+ * Remove a {@link FutureToSendHandler} from the set of tracked instances.
+ */
+ protected void unregisterFuture(FutureToSendHandler f2sh) {
+ futures.remove(f2sh);
+ }
+
+
+ @Override
+ public URI getRequestURI() {
+ checkState();
+ return requestUri;
+ }
+
+
+ @Override
+ public Map<String,List<String>> getRequestParameterMap() {
+ checkState();
+ return requestParameterMap;
+ }
+
+
+ @Override
+ public String getQueryString() {
+ checkState();
+ return queryString;
+ }
+
+
+ @Override
+ public Principal getUserPrincipal() {
+ checkState();
+ return userPrincipal;
+ }
+
+
+ @Override
+ public Map<String,String> getPathParameters() {
+ checkState();
+ return pathParameters;
+ }
+
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+
+ @Override
+ public Map<String,Object> getUserProperties() {
+ checkState();
+ return userProperties;
+ }
+
+
+ public Endpoint getLocal() {
+ return localEndpoint;
+ }
+
+
+ public String getHttpSessionId() {
+ return httpSessionId;
+ }
+
+
+ protected MessageHandler getTextMessageHandler() {
+ return textMessageHandler;
+ }
+
+
+ protected MessageHandler getBinaryMessageHandler() {
+ return binaryMessageHandler;
+ }
+
+
+ protected MessageHandler.Whole<PongMessage> getPongMessageHandler() {
+ return pongMessageHandler;
+ }
+
+
+ protected void updateLastActive() {
+ lastActive = System.currentTimeMillis();
+ }
+
+
+ protected void checkExpiration() {
+ long timeout = maxIdleTimeout;
+ if (timeout < 1) {
+ return;
+ }
+
+ if (System.currentTimeMillis() - lastActive > timeout) {
+ String msg = MESSAGES.sessionTimeout();
+ doClose(new CloseReason(CloseCodes.GOING_AWAY, msg),
+ new CloseReason(CloseCodes.CLOSED_ABNORMALLY, msg));
+ }
+ }
+
+
+ private void checkState() {
+ if (state == State.CLOSED) {
+ throw MESSAGES.sessionAlreadyClosed();
+ }
+ }
+
+ private static enum State {
+ OPEN,
+ CLOSING,
+ CLOSED
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsWebSocketContainer.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/WsWebSocketContainer.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,821 @@
+/*
+ * 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.websocket;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousChannelGroup;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+import javax.websocket.ClientEndpoint;
+import javax.websocket.ClientEndpointConfig;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.apache.tomcat.util.threads.ThreadPoolExecutor;
+import org.apache.tomcat.websocket.pojo.PojoEndpointClient;
+import org.jboss.web.WebsocketsLogger;
+
+public class WsWebSocketContainer
+ implements WebSocketContainer, BackgroundProcess {
+
+ /**
+ * Property name to set to configure the value that is passed to
+ * {@link SSLEngine#setEnabledProtocols(String[])}. The value should be a
+ * comma separated string.
+ */
+ public static final String SSL_PROTOCOLS_PROPERTY =
+ "org.apache.tomcat.websocket.SSL_PROTOCOLS";
+ public static final String SSL_TRUSTSTORE_PROPERTY =
+ "org.apache.tomcat.websocket.SSL_TRUSTSTORE";
+ public static final String SSL_TRUSTSTORE_PWD_PROPERTY =
+ "org.apache.tomcat.websocket.SSL_TRUSTSTORE_PWD";
+ public static final String SSL_TRUSTSTORE_PWD_DEFAULT = "changeit";
+
+ private static final Random random = new Random();
+ private static final byte[] crlf = new byte[] {13, 10};
+ private static final AsynchronousChannelGroup asynchronousChannelGroup;
+
+ static {
+ AsynchronousChannelGroup result = null;
+
+ // Need to do this with the right thread context class loader else the
+ // first web app to call this will trigger a leak
+ ClassLoader original = Thread.currentThread().getContextClassLoader();
+
+ try {
+ Thread.currentThread().setContextClassLoader(
+ AsyncIOThreadFactory.class.getClassLoader());
+
+ // These are the same settings as the default
+ // AsynchronousChannelGroup
+ int initialSize = Runtime.getRuntime().availableProcessors();
+ ExecutorService executorService = new ThreadPoolExecutor(
+ 0,
+ Integer.MAX_VALUE,
+ Long.MAX_VALUE, TimeUnit.MILLISECONDS,
+ new SynchronousQueue<Runnable>(),
+ new AsyncIOThreadFactory());
+
+ try {
+ result = AsynchronousChannelGroup.withCachedThreadPool(
+ executorService, initialSize);
+ } catch (IOException e) {
+ // No good reason for this to happen.
+ throw MESSAGES.asyncGroupFail();
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(original);
+ }
+
+ asynchronousChannelGroup = result;
+ }
+
+ private final Map<Class<?>, Set<WsSession>> endpointSessionMap =
+ new HashMap<Class<?>, Set<WsSession>>();
+ private final Map<WsSession,WsSession> sessions = new ConcurrentHashMap<WsSession, WsSession>();
+ private final Object endPointSessionMapLock = new Object();
+
+ private long defaultAsyncTimeout = -1;
+ private int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
+ private int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
+ private volatile long defaultMaxSessionIdleTimeout = 0;
+ private int backgroundProcessCount = 0;
+ private int processPeriod = 10;
+
+
+ @Override
+ public Session connectToServer(Object pojo, URI path)
+ throws DeploymentException {
+
+ ClientEndpoint annotation =
+ pojo.getClass().getAnnotation(ClientEndpoint.class);
+ if (annotation == null) {
+ throw new DeploymentException(
+ MESSAGES.missingClientEndpointAnnotation(pojo.getClass().getName()));
+ }
+
+ Endpoint ep = new PojoEndpointClient(pojo, annotation.decoders());
+
+ Class<? extends ClientEndpointConfig.Configurator> configuratorClazz =
+ pojo.getClass().getAnnotation(
+ ClientEndpoint.class).configurator();
+
+ ClientEndpointConfig.Configurator configurator = null;
+ if (!ClientEndpointConfig.Configurator.class.equals(
+ configuratorClazz)) {
+ try {
+ configurator = configuratorClazz.newInstance();
+ } catch (InstantiationException e) {
+ throw new DeploymentException(MESSAGES.defaultConfiguratorFailed(), e);
+ } catch (IllegalAccessException e) {
+ throw new DeploymentException(MESSAGES.defaultConfiguratorFailed(), e);
+ }
+ }
+
+ ClientEndpointConfig config = ClientEndpointConfig.Builder.create().
+ configurator(configurator).
+ decoders(Arrays.asList(annotation.decoders())).
+ encoders(Arrays.asList(annotation.encoders())).
+ build();
+ return connectToServer(ep, config, path);
+ }
+
+
+ @Override
+ public Session connectToServer(Class<?> annotatedEndpointClass, URI path)
+ throws DeploymentException {
+
+ Object pojo;
+ try {
+ pojo = annotatedEndpointClass.newInstance();
+ } catch (InstantiationException e) {
+ throw new DeploymentException(MESSAGES.endpointCreateFailed(annotatedEndpointClass.getName()), e);
+ } catch (IllegalAccessException e) {
+ throw new DeploymentException(MESSAGES.endpointCreateFailed(annotatedEndpointClass.getName()), e);
+ }
+
+ return connectToServer(pojo, path);
+ }
+
+
+ @Override
+ public Session connectToServer(Class<? extends Endpoint> clazz,
+ ClientEndpointConfig clientEndpointConfiguration, URI path)
+ throws DeploymentException {
+
+ Endpoint endpoint;
+ try {
+ endpoint = clazz.newInstance();
+ } catch (InstantiationException e) {
+ throw new DeploymentException(MESSAGES.endpointCreateFailed(clazz.getName()), e);
+ } catch (IllegalAccessException e) {
+ throw new DeploymentException(MESSAGES.endpointCreateFailed(clazz.getName()), e);
+ }
+
+ return connectToServer(endpoint, clientEndpointConfiguration, path);
+ }
+
+
+ @Override
+ public Session connectToServer(Endpoint endpoint,
+ ClientEndpointConfig clientEndpointConfiguration, URI path)
+ throws DeploymentException {
+
+ boolean secure = false;
+
+ String scheme = path.getScheme();
+ if (!("ws".equalsIgnoreCase(scheme) ||
+ "wss".equalsIgnoreCase(scheme))) {
+ throw new DeploymentException(MESSAGES.pathWrongScheme(scheme));
+ }
+ String host = path.getHost();
+ if (host == null) {
+ throw new DeploymentException(MESSAGES.pathNoHost());
+ }
+ int port = path.getPort();
+ Map<String,List<String>> reqHeaders = createRequestHeaders(host, port,
+ clientEndpointConfiguration.getPreferredSubprotocols(),
+ clientEndpointConfiguration.getExtensions());
+ clientEndpointConfiguration.getConfigurator().
+ beforeRequest(reqHeaders);
+
+ ByteBuffer request = createRequest(path, reqHeaders);
+
+ SocketAddress sa;
+ if (port == -1) {
+ if ("ws".equalsIgnoreCase(scheme)) {
+ sa = new InetSocketAddress(host, 80);
+ } else if ("wss".equalsIgnoreCase(scheme)) {
+ sa = new InetSocketAddress(host, 443);
+ secure = true;
+ } else {
+ throw new DeploymentException(MESSAGES.invalidScheme(scheme));
+ }
+ } else {
+ if ("wss".equalsIgnoreCase(scheme)) {
+ secure = true;
+ }
+ sa = new InetSocketAddress(host, port);
+ }
+
+ AsynchronousSocketChannel socketChannel;
+ try {
+ socketChannel =
+ AsynchronousSocketChannel.open(asynchronousChannelGroup);
+ } catch (IOException ioe) {
+ throw new DeploymentException(MESSAGES.connectionFailed(), ioe);
+ }
+
+ Future<Void> fConnect = socketChannel.connect(sa);
+
+ AsyncChannelWrapper channel;
+ if (secure) {
+ SSLEngine sslEngine = createSSLEngine(
+ clientEndpointConfiguration.getUserProperties());
+ channel = new AsyncChannelWrapperSecure(socketChannel, sslEngine);
+ } else {
+ channel = new AsyncChannelWrapperNonSecure(socketChannel);
+ }
+
+ ByteBuffer response;
+ String subProtocol;
+ try {
+ fConnect.get();
+
+ Future<Void> fHandshake = channel.handshake();
+ fHandshake.get();
+
+ int toWrite = request.limit();
+
+ Future<Integer> fWrite = channel.write(request);
+ Integer thisWrite = fWrite.get();
+ toWrite -= thisWrite.intValue();
+
+ while (toWrite > 0) {
+ fWrite = channel.write(request);
+ thisWrite = fWrite.get();
+ toWrite -= thisWrite.intValue();
+ }
+ // Same size as the WsFrame input buffer
+ response = ByteBuffer.allocate(maxBinaryMessageBufferSize);
+
+ HandshakeResponse handshakeResponse =
+ processResponse(response, channel);
+ clientEndpointConfiguration.getConfigurator().
+ afterResponse(handshakeResponse);
+
+ // Sub-protocol
+ // Header names are always stored in lower case
+ List<String> values = handshakeResponse.getHeaders().get(
+ Constants.WS_PROTOCOL_HEADER_NAME_LOWER);
+ if (values == null || values.size() == 0) {
+ subProtocol = null;
+ } else if (values.size() == 1) {
+ subProtocol = values.get(0);
+ } else {
+ throw new DeploymentException(MESSAGES.invalidProtocolHeader());
+ }
+ } catch (ExecutionException e) {
+ throw new DeploymentException(MESSAGES.httpRequestFailed(), e);
+ } catch (InterruptedException e) {
+ throw new DeploymentException(MESSAGES.httpRequestFailed(), e);
+ } catch (SSLException e) {
+ throw new DeploymentException(MESSAGES.httpRequestFailed(), e);
+ } catch (EOFException e) {
+ throw new DeploymentException(MESSAGES.httpRequestFailed(), e);
+ }
+
+ // Switch to WebSocket
+ WsRemoteEndpointImplClient wsRemoteEndpointClient =
+ new WsRemoteEndpointImplClient(channel);
+
+
+ WsSession wsSession = new WsSession(endpoint, wsRemoteEndpointClient,
+ this, null, null, null, null, null, subProtocol,
+ Collections.<String, String> emptyMap(), false,
+ clientEndpointConfiguration);
+ endpoint.onOpen(wsSession, clientEndpointConfiguration);
+ registerSession(endpoint, wsSession);
+
+ // Object creation will trigger input processing
+ @SuppressWarnings("unused")
+ WsFrameClient wsFrameClient = new WsFrameClient(response, channel,
+ wsSession);
+
+ return wsSession;
+ }
+
+
+ protected void registerSession(Endpoint endpoint, WsSession wsSession) {
+
+ Class<?> endpointClazz = endpoint.getClass();
+
+ if (!wsSession.isOpen()) {
+ // The session was closed during onOpen. No need to register it.
+ return;
+ }
+ synchronized (endPointSessionMapLock) {
+ if (endpointSessionMap.size() == 0) {
+ BackgroundProcessManager.getInstance().register(this);
+ }
+ Set<WsSession> wsSessions = endpointSessionMap.get(endpointClazz);
+ if (wsSessions == null) {
+ wsSessions = new HashSet<WsSession>();
+ endpointSessionMap.put(endpointClazz, wsSessions);
+ }
+ wsSessions.add(wsSession);
+ }
+ sessions.put(wsSession, wsSession);
+ }
+
+
+ protected void unregisterSession(Endpoint endpoint, WsSession wsSession) {
+
+ Class<?> endpointClazz = endpoint.getClass();
+
+ synchronized (endPointSessionMapLock) {
+ Set<WsSession> wsSessions = endpointSessionMap.get(endpointClazz);
+ if (wsSessions != null) {
+ wsSessions.remove(wsSession);
+ if (wsSessions.size() == 0) {
+ endpointSessionMap.remove(endpointClazz);
+ }
+ }
+ if (endpointSessionMap.size() == 0) {
+ BackgroundProcessManager.getInstance().unregister(this);
+ }
+ }
+ sessions.remove(wsSession);
+ }
+
+
+ Set<Session> getOpenSessions(Class<?> endpoint) {
+ HashSet<Session> result = new HashSet<Session>();
+ synchronized (endPointSessionMapLock) {
+ Set<WsSession> sessions = endpointSessionMap.get(endpoint);
+ if (sessions != null) {
+ result.addAll(sessions);
+ }
+ }
+ return result;
+ }
+
+ private Map<String,List<String>> createRequestHeaders(String host,
+ int port, List<String> subProtocols, List<Extension> extensions) {
+
+ Map<String,List<String>> headers = new HashMap<String, List<String>>();
+
+ // Host header
+ List<String> hostValues = new ArrayList<String>(1);
+ if (port == -1) {
+ hostValues.add(host);
+ } else {
+ hostValues.add(host + ':' + port);
+ }
+
+ headers.put(Constants.HOST_HEADER_NAME, hostValues);
+
+ // Upgrade header
+ List<String> upgradeValues = new ArrayList<String>(1);
+ upgradeValues.add(Constants.UPGRADE_HEADER_VALUE);
+ headers.put(Constants.UPGRADE_HEADER_NAME, upgradeValues);
+
+ // Connection header
+ List<String> connectionValues = new ArrayList<String>(1);
+ connectionValues.add(Constants.CONNECTION_HEADER_VALUE);
+ headers.put(Constants.CONNECTION_HEADER_NAME, connectionValues);
+
+ // WebSocket version header
+ List<String> wsVersionValues = new ArrayList<String>(1);
+ wsVersionValues.add(Constants.WS_VERSION_HEADER_VALUE);
+ headers.put(Constants.WS_VERSION_HEADER_NAME, wsVersionValues);
+
+ // WebSocket key
+ List<String> wsKeyValues = new ArrayList<String>(1);
+ wsKeyValues.add(generateWsKeyValue());
+ headers.put(Constants.WS_KEY_HEADER_NAME, wsKeyValues);
+
+ // WebSocket sub-protocols
+ if (subProtocols != null && subProtocols.size() > 0) {
+ headers.put(Constants.WS_PROTOCOL_HEADER_NAME, subProtocols);
+ }
+
+ // WebSocket extensions
+ if (extensions != null && extensions.size() > 0) {
+ headers.put(Constants.WS_EXTENSIONS_HEADER_NAME,
+ generateExtensionHeaders(extensions));
+ }
+
+ return headers;
+ }
+
+
+ private List<String> generateExtensionHeaders(List<Extension> extensions) {
+ List<String> result = new ArrayList<String>(extensions.size());
+ for (Extension extension : extensions) {
+ StringBuilder header = new StringBuilder();
+ header.append(extension.getName());
+ for (Extension.Parameter param : extension.getParameters()) {
+ header.append(';');
+ header.append(param.getName());
+ String value = param.getValue();
+ if (value != null && value.length() > 0) {
+ header.append('=');
+ header.append(value);
+ }
+ }
+ }
+ return result;
+ }
+
+
+ private String generateWsKeyValue() {
+ byte[] keyBytes = new byte[16];
+ random.nextBytes(keyBytes);
+ return Base64.encodeBase64String(keyBytes);
+ }
+
+
+ private ByteBuffer createRequest(URI uri,
+ Map<String,List<String>> reqHeaders) {
+ ByteBuffer result = ByteBuffer.allocate(4 * 1024);
+
+ // Request line
+ result.put("GET ".getBytes(StandardCharsets.ISO_8859_1));
+ result.put(uri.getRawPath().getBytes(StandardCharsets.ISO_8859_1));
+ String query = uri.getRawQuery();
+ if (query != null) {
+ result.put((byte) '?');
+ result.put(query.getBytes(StandardCharsets.ISO_8859_1));
+ }
+ result.put(" HTTP/1.1\r\n".getBytes(StandardCharsets.ISO_8859_1));
+
+ // Headers
+ Iterator<Entry<String,List<String>>> iter =
+ reqHeaders.entrySet().iterator();
+ while (iter.hasNext()) {
+ Entry<String,List<String>> entry = iter.next();
+ addHeader(result, entry.getKey(), entry.getValue());
+ }
+
+ // Terminating CRLF
+ result.put(crlf);
+
+ result.flip();
+
+ return result;
+ }
+
+
+ private void addHeader(ByteBuffer result, String key, List<String> values) {
+ StringBuilder sb = new StringBuilder();
+
+ Iterator<String> iter = values.iterator();
+ if (!iter.hasNext()) {
+ return;
+ }
+ sb.append(iter.next());
+ while (iter.hasNext()) {
+ sb.append(',');
+ sb.append(iter.next());
+ }
+
+ result.put(key.getBytes(StandardCharsets.ISO_8859_1));
+ result.put(": ".getBytes(StandardCharsets.ISO_8859_1));
+ result.put(sb.toString().getBytes(StandardCharsets.ISO_8859_1));
+ result.put(crlf);
+ }
+
+
+ /**
+ * Process response, blocking until HTTP response has been fully received.
+ * @throws ExecutionException
+ * @throws InterruptedException
+ * @throws DeploymentException
+ */
+ private HandshakeResponse processResponse(ByteBuffer response,
+ AsyncChannelWrapper channel) throws InterruptedException,
+ ExecutionException, DeploymentException, EOFException {
+
+ Map<String,List<String>> headers = new HashMap<String, List<String>>();
+
+ boolean readStatus = false;
+ boolean readHeaders = false;
+ String line = null;
+ while (!readHeaders) {
+ // Blocking read
+ Future<Integer> read = channel.read(response);
+ Integer bytesRead = read.get();
+ if (bytesRead.intValue() == -1) {
+ throw new EOFException();
+ }
+ response.flip();
+ while (response.hasRemaining() && !readHeaders) {
+ if (line == null) {
+ line = readLine(response);
+ } else {
+ line += readLine(response);
+ }
+ if ("\r\n".equals(line)) {
+ readHeaders = true;
+ } else if (line.endsWith("\r\n")) {
+ if (readStatus) {
+ parseHeaders(line, headers);
+ } else {
+ parseStatus(line);
+ readStatus = true;
+ }
+ line = null;
+ }
+ }
+ }
+
+ return new WsHandshakeResponse(headers);
+ }
+
+
+ private void parseStatus(String line) throws DeploymentException {
+ // This client only understands HTTP 1.1
+ // RFC2616 is case specific
+ if (!line.startsWith("HTTP/1.1 101")) {
+ throw new DeploymentException(MESSAGES.invalidHttpStatus(line));
+ }
+ }
+
+
+ private void parseHeaders(String line, Map<String,List<String>> headers) {
+ // Treat headers as single values by default.
+
+ int index = line.indexOf(':');
+ if (index == -1) {
+ WebsocketsLogger.ROOT_LOGGER.invalidHttpHeader(line);
+ return;
+ }
+ // Header names are case insensitive so always use lower case
+ String headerName = line.substring(0, index).trim().toLowerCase();
+ // TODO handle known multi-value headers
+ String headerValue = line.substring(index + 1).trim();
+
+ List<String> values = headers.get(headerName);
+ if (values == null) {
+ values = new ArrayList<String>(1);
+ headers.put(headerName, values);
+ }
+ values.add(headerValue);
+ }
+
+
+ private String readLine(ByteBuffer response) {
+ // All ISO-8859-1
+ StringBuilder sb = new StringBuilder();
+
+ char c = 0;
+ while (response.hasRemaining()) {
+ c = (char) response.get();
+ sb.append(c);
+ if (c == 10) {
+ break;
+ }
+ }
+
+ return sb.toString();
+ }
+
+
+ private SSLEngine createSSLEngine(Map<String,Object> userProperties)
+ throws DeploymentException {
+
+ try {
+ // Create the SSL Context
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+
+ // Trust store
+ String sslTrustStoreValue =
+ (String) userProperties.get(SSL_TRUSTSTORE_PROPERTY);
+ if (sslTrustStoreValue != null) {
+ String sslTrustStorePwdValue = (String) userProperties.get(
+ SSL_TRUSTSTORE_PWD_PROPERTY);
+ if (sslTrustStorePwdValue == null) {
+ sslTrustStorePwdValue = SSL_TRUSTSTORE_PWD_DEFAULT;
+ }
+
+ File keyStoreFile = new File(sslTrustStoreValue);
+ KeyStore ks = KeyStore.getInstance("JKS");
+ InputStream is = null;
+ try {
+ is = new FileInputStream(keyStoreFile);
+ ks.load(is, sslTrustStorePwdValue.toCharArray());
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ // Ignore
+ }
+ }
+ }
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ks);
+
+ sslContext.init(null, tmf.getTrustManagers(), null);
+ } else {
+ sslContext.init(null, null, null);
+ }
+
+ SSLEngine engine = sslContext.createSSLEngine();
+
+ String sslProtocolsValue =
+ (String) userProperties.get(SSL_PROTOCOLS_PROPERTY);
+ if (sslProtocolsValue != null) {
+ engine.setEnabledProtocols(sslProtocolsValue.split(","));
+ }
+
+ engine.setUseClientMode(true);
+
+ return engine;
+ } catch (Exception e) {
+ throw new DeploymentException(MESSAGES.sslEngineFail(), e);
+ }
+ }
+
+
+ @Override
+ public long getDefaultMaxSessionIdleTimeout() {
+ return defaultMaxSessionIdleTimeout;
+ }
+
+
+ @Override
+ public void setDefaultMaxSessionIdleTimeout(long timeout) {
+ this.defaultMaxSessionIdleTimeout = timeout;
+ }
+
+
+ @Override
+ public int getDefaultMaxBinaryMessageBufferSize() {
+ return maxBinaryMessageBufferSize;
+ }
+
+
+ @Override
+ public void setDefaultMaxBinaryMessageBufferSize(int max) {
+ maxBinaryMessageBufferSize = max;
+ }
+
+
+ @Override
+ public int getDefaultMaxTextMessageBufferSize() {
+ return maxTextMessageBufferSize;
+ }
+
+
+ @Override
+ public void setDefaultMaxTextMessageBufferSize(int max) {
+ maxTextMessageBufferSize = max;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * Currently, this implementation does not support any extensions.
+ */
+ @Override
+ public Set<Extension> getInstalledExtensions() {
+ return Collections.emptySet();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * The default value for this implementation is -1.
+ */
+ @Override
+ public long getDefaultAsyncSendTimeout() {
+ return defaultAsyncTimeout;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * The default value for this implementation is -1.
+ */
+ @Override
+ public void setAsyncSendTimeout(long timeout) {
+ this.defaultAsyncTimeout = timeout;
+ }
+
+
+ /**
+ * Cleans up the resources still in use by WebSocket sessions created from
+ * this container. This includes closing sessions and cancelling
+ * {@link Future}s associated with blocking read/writes.
+ */
+ public void destroy() {
+ CloseReason cr = new CloseReason(
+ CloseCodes.GOING_AWAY, MESSAGES.webappStopping());
+
+ for (WsSession session : sessions.keySet()) {
+ try {
+ session.close(cr);
+ } catch (IOException ioe) {
+ WebsocketsLogger.ROOT_LOGGER.sessionCloseFailed(session.getId(), ioe);
+ }
+ }
+ }
+
+
+ // ----------------------------------------------- BackgroundProcess methods
+
+ @Override
+ public void backgroundProcess() {
+ // This method gets called once a second.
+ backgroundProcessCount ++;
+
+ if (backgroundProcessCount >= processPeriod) {
+ backgroundProcessCount = 0;
+
+ for (WsSession wsSession : sessions.keySet()) {
+ wsSession.checkExpiration();
+ }
+ }
+
+ }
+
+
+ @Override
+ public void setProcessPeriod(int period) {
+ this.processPeriod = period;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * The default value is 10 which means session expirations are processed
+ * every 10 seconds.
+ */
+ @Override
+ public int getProcessPeriod() {
+ return processPeriod;
+ }
+
+
+ /**
+ * Create threads for AsyncIO that have the right context class loader to
+ * prevent memory leaks.
+ */
+ private static class AsyncIOThreadFactory implements ThreadFactory {
+
+ private AtomicInteger count = new AtomicInteger(0);
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setName("WebSocketClient-AsyncIO-" + count.incrementAndGet());
+ t.setContextClassLoader(this.getClass().getClassLoader());
+ t.setDaemon(true);
+ return t;
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/Constants.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/Constants.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/Constants.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,30 @@
+/*
+ * 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.websocket.pojo;
+
+/**
+ * Internal implementation constants.
+ */
+public class Constants {
+
+ protected static final String PACKAGE_NAME =
+ Constants.class.getPackage().getName();
+
+ private Constants() {
+ // Hide default constructor
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointBase.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,146 @@
+/*
+ * 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.websocket.pojo;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+import java.util.Set;
+
+import javax.websocket.CloseReason;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+import org.apache.tomcat.util.ExceptionUtils;
+import org.jboss.web.WebsocketsLogger;
+
+/**
+ * Base implementation (client and server have different concrete
+ * implementations) of the wrapper that converts a POJO instance into a
+ * WebSocket endpoint instance.
+ */
+public abstract class PojoEndpointBase extends Endpoint {
+
+ private Object pojo;
+ private Map<String,String> pathParameters;
+ private PojoMethodMapping methodMapping;
+
+
+ protected final void doOnOpen(Session session, EndpointConfig config) {
+ PojoMethodMapping methodMapping = getMethodMapping();
+ Object pojo = getPojo();
+ Map<String,String> pathParameters = getPathParameters();
+
+ if (methodMapping.getOnOpen() != null) {
+ try {
+ methodMapping.getOnOpen().invoke(pojo,
+ methodMapping.getOnOpenArgs(
+ pathParameters, session, config));
+
+ } catch (IllegalAccessException e) {
+ // Reflection related problems
+ WebsocketsLogger.ROOT_LOGGER.onOpenFailed(pojo.getClass().getName(), e);
+ handleOnOpenError(session, e);
+ return;
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ handleOnOpenError(session, cause);
+ return;
+ } catch (Throwable t) {
+ handleOnOpenError(session, t);
+ return;
+ }
+ }
+
+ for (MessageHandler mh : methodMapping.getMessageHandlers(pojo,
+ pathParameters, session, config)) {
+ session.addMessageHandler(mh);
+ }
+ }
+
+
+ private void handleOnOpenError(Session session, Throwable t) {
+ // If really fatal - re-throw
+ ExceptionUtils.handleThrowable(t);
+
+ // Trigger the error handler and close the session
+ onError(session, t);
+ try {
+ session.close();
+ } catch (IOException ioe) {
+ WebsocketsLogger.ROOT_LOGGER.closeSessionFailed(ioe);
+ }
+ }
+
+ @Override
+ public final void onClose(Session session, CloseReason closeReason) {
+
+ if (methodMapping.getOnClose() != null) {
+ try {
+ methodMapping.getOnClose().invoke(pojo,
+ methodMapping.getOnCloseArgs(pathParameters, session, closeReason));
+ } catch (Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ WebsocketsLogger.ROOT_LOGGER.onCloseFailed(pojo.getClass().getName(), t);
+ }
+ }
+
+ // Trigger the destroy method for any associated decoders
+ Set<MessageHandler> messageHandlers = session.getMessageHandlers();
+ for (MessageHandler messageHandler : messageHandlers) {
+ if (messageHandler instanceof PojoMessageHandlerWholeBase<?>) {
+ ((PojoMessageHandlerWholeBase<?>) messageHandler).onClose();
+ }
+ }
+ }
+
+
+ @Override
+ public final void onError(Session session, Throwable throwable) {
+
+ if (methodMapping.getOnError() == null) {
+ WebsocketsLogger.ROOT_LOGGER.noOnError(pojo.getClass().getName(), throwable);
+ } else {
+ try {
+ methodMapping.getOnError().invoke(
+ pojo,
+ methodMapping.getOnErrorArgs(pathParameters, session,
+ throwable));
+ } catch (Throwable t) {
+ ExceptionUtils.handleThrowable(t);
+ WebsocketsLogger.ROOT_LOGGER.onErrorFailed(pojo.getClass().getName(), t);
+ }
+ }
+ }
+
+ protected Object getPojo() { return pojo; }
+ protected void setPojo(Object pojo) { this.pojo = pojo; }
+
+
+ protected Map<String,String> getPathParameters() { return pathParameters; }
+ protected void setPathParameters(Map<String,String> pathParameters) {
+ this.pathParameters = pathParameters;
+ }
+
+
+ protected PojoMethodMapping getMethodMapping() { return methodMapping; }
+ protected void setMethodMapping(PojoMethodMapping methodMapping) {
+ this.methodMapping = methodMapping;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,46 @@
+/*
+ * 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.websocket.pojo;
+
+import java.util.Collections;
+
+import javax.websocket.Decoder;
+import javax.websocket.DeploymentException;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+
+/**
+ * Wrapper class for instances of POJOs annotated with
+ * {@link javax.websocket.ClientEndpoint} so they appear as standard
+ * {@link javax.websocket.Endpoint} instances.
+ */
+public class PojoEndpointClient extends PojoEndpointBase {
+
+ public PojoEndpointClient(Object pojo,
+ Class<? extends Decoder>[] decoders) throws DeploymentException {
+ setPojo(pojo);
+ setMethodMapping(
+ new PojoMethodMapping(pojo.getClass(), decoders, null));
+ setPathParameters(Collections.<String, String> emptyMap());
+ }
+
+ @Override
+ public void onOpen(Session session, EndpointConfig config) {
+ doOnOpen(session, config);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointServer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointServer.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoEndpointServer.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,67 @@
+/*
+ * 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.websocket.pojo;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.util.Map;
+
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpointConfig;
+
+/**
+ * Wrapper class for instances of POJOs annotated with
+ * {@link javax.websocket.server.ServerEndpoint} so they appear as standard
+ * {@link javax.websocket.Endpoint} instances.
+ */
+public class PojoEndpointServer extends PojoEndpointBase {
+
+ public static final String POJO_PATH_PARAM_KEY =
+ "org.apache.tomcat.websocket.pojo.PojoEndpoint.pathParams";
+ public static final String POJO_METHOD_MAPPING_KEY =
+ "org.apache.tomcat.websocket.pojo.PojoEndpoint.methodMapping";
+
+
+ @Override
+ public void onOpen(Session session, EndpointConfig endpointConfig) {
+
+ ServerEndpointConfig sec = (ServerEndpointConfig) endpointConfig;
+
+ Object pojo;
+ try {
+ pojo = sec.getConfigurator().getEndpointInstance(
+ sec.getEndpointClass());
+ } catch (InstantiationException e) {
+ throw MESSAGES.pojoInstanceFailed(sec.getEndpointClass().getName(), e);
+ }
+ setPojo(pojo);
+
+ @SuppressWarnings("unchecked")
+ Map<String,String> pathParameters =
+ (Map<String, String>) sec.getUserProperties().get(
+ POJO_PATH_PARAM_KEY);
+ setPathParameters(pathParameters);
+
+ PojoMethodMapping methodMapping =
+ (PojoMethodMapping) sec.getUserProperties().get(
+ POJO_METHOD_MAPPING_KEY);
+ setMethodMapping(methodMapping);
+
+ doOnOpen(session, endpointConfig);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerBase.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,104 @@
+/*
+ * 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.websocket.pojo;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import javax.websocket.EncodeException;
+import javax.websocket.MessageHandler;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+
+import org.apache.tomcat.websocket.WrappedMessageHandler;
+
+/**
+ * Common implementation code for the POJO message handlers.
+ *
+ * @param <T> The type of message to handle
+ */
+public abstract class PojoMessageHandlerBase<T>
+ implements WrappedMessageHandler {
+
+ protected final Object pojo;
+ protected final Method method;
+ protected final Session session;
+ protected final Object[] params;
+ protected final int indexPayload;
+ protected final boolean convert;
+ protected final int indexSession;
+ protected final long maxMessageSize;
+
+ public PojoMessageHandlerBase(Object pojo, Method method,
+ Session session, Object[] params, int indexPayload, boolean convert,
+ int indexSession, long maxMessageSize) {
+ this.pojo = pojo;
+ this.method = method;
+ this.session = session;
+ this.params = params;
+ this.indexPayload = indexPayload;
+ this.convert = convert;
+ this.indexSession = indexSession;
+ this.maxMessageSize = maxMessageSize;
+ }
+
+
+ protected final void processResult(Object result) {
+ if (result == null) {
+ return;
+ }
+
+ RemoteEndpoint.Basic remoteEndpoint = session.getBasicRemote();
+ try {
+ if (result instanceof String) {
+ remoteEndpoint.sendText((String) result);
+ } else if (result instanceof ByteBuffer) {
+ remoteEndpoint.sendBinary((ByteBuffer) result);
+ } else if (result instanceof byte[]) {
+ remoteEndpoint.sendBinary(ByteBuffer.wrap((byte[]) result));
+ } else {
+ remoteEndpoint.sendObject(result);
+ }
+ } catch (IOException ioe) {
+ throw new IllegalStateException(ioe);
+ } catch (EncodeException ee) {
+ throw new IllegalStateException(ee);
+ }
+ }
+
+
+ /**
+ * Expose the POJO if it is a message handler so the Session is able to
+ * match requests to remove handlers if the original handler has been
+ * wrapped.
+ */
+ @Override
+ public final MessageHandler getWrappedHandler() {
+ if (pojo instanceof MessageHandler) {
+ return (MessageHandler) pojo;
+ } else {
+ return null;
+ }
+ }
+
+
+ @Override
+ public final long getMaxMessageSize() {
+ return maxMessageSize;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBase.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBase.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBase.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,79 @@
+/*
+ * 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.websocket.pojo;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import javax.websocket.DecodeException;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+import org.apache.tomcat.websocket.WsSession;
+
+/**
+ * Common implementation code for the POJO partial message handlers. All
+ * the real work is done in this class and in the superclass.
+ *
+ * @param <T> The type of message to handle
+ */
+public abstract class PojoMessageHandlerPartialBase<T>
+ extends PojoMessageHandlerBase<T> implements MessageHandler.Partial<T> {
+
+ private final int indexBoolean;
+
+ public PojoMessageHandlerPartialBase(Object pojo, Method method,
+ Session session, Object[] params, int indexPayload,
+ boolean convert, int indexBoolean, int indexSession,
+ long maxMessageSize) {
+ super(pojo, method, session, params, indexPayload, convert,
+ indexSession, maxMessageSize);
+ this.indexBoolean = indexBoolean;
+ }
+
+
+ @Override
+ public final void onMessage(T message, boolean last) {
+ if (params.length == 1 && params[0] instanceof DecodeException) {
+ ((WsSession) session).getLocal().onError(session,
+ (DecodeException) params[0]);
+ return;
+ }
+ Object[] parameters = params.clone();
+ if (indexBoolean != -1) {
+ parameters[indexBoolean] = Boolean.valueOf(last);
+ }
+ if (indexSession != -1) {
+ parameters[indexSession] = session;
+ }
+ if (convert) {
+ parameters[indexPayload] = ((ByteBuffer) message).array();
+ } else {
+ parameters[indexPayload] = message;
+ }
+ Object result;
+ try {
+ result = method.invoke(pojo, parameters);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException(e);
+ }
+ processResult(result);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBinary.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBinary.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialBinary.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,36 @@
+/*
+ * 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.websocket.pojo;
+
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+
+import javax.websocket.Session;
+
+/**
+ * ByteBuffer specific concrete implementation for handling partial messages.
+ */
+public class PojoMessageHandlerPartialBinary
+ extends PojoMessageHandlerPartialBase<ByteBuffer>{
+
+ public PojoMessageHandlerPartialBinary(Object pojo, Method method,
+ Session session, Object[] params, int indexPayload, boolean convert,
+ int indexBoolean, int indexSession, long maxMessageSize) {
+ super(pojo, method, session, params, indexPayload, convert, indexBoolean,
+ indexSession, maxMessageSize);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialText.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialText.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerPartialText.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,35 @@
+/*
+ * 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.websocket.pojo;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.Session;
+
+/**
+ * Text specific concrete implementation for handling partial messages.
+ */
+public class PojoMessageHandlerPartialText
+ extends PojoMessageHandlerPartialBase<String>{
+
+ public PojoMessageHandlerPartialText(Object pojo, Method method,
+ Session session, Object[] params, int indexPayload, boolean convert,
+ int indexBoolean, int indexSession, long maxMessageSize) {
+ super(pojo, method, session, params, indexPayload, convert, indexBoolean,
+ indexSession, maxMessageSize);
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBase.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBase.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBase.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,96 @@
+/*
+ * 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.websocket.pojo;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.websocket.DecodeException;
+import javax.websocket.MessageHandler;
+import javax.websocket.Session;
+
+import org.apache.tomcat.websocket.WsSession;
+
+/**
+ * Common implementation code for the POJO whole message handlers. All the real
+ * work is done in this class and in the superclass.
+ *
+ * @param <T> The type of message to handle
+ */
+public abstract class PojoMessageHandlerWholeBase<T>
+ extends PojoMessageHandlerBase<T> implements MessageHandler.Whole<T> {
+
+ public PojoMessageHandlerWholeBase(Object pojo, Method method,
+ Session session, Object[] params, int indexPayload,
+ boolean convert, int indexSession, long maxMessageSize) {
+ super(pojo, method, session, params, indexPayload, convert,
+ indexSession, maxMessageSize);
+ }
+
+
+ @Override
+ public final void onMessage(T message) {
+
+ if (params.length == 1 && params[0] instanceof DecodeException) {
+ ((WsSession) session).getLocal().onError(session,
+ (DecodeException) params[0]);
+ return;
+ }
+
+ // Can this message be decoded?
+ Object payload;
+ try {
+ payload = decode(message);
+ } catch (DecodeException de) {
+ ((WsSession) session).getLocal().onError(session, de);
+ return;
+ }
+
+ if (payload == null) {
+ // Not decoded. Convert if required.
+ if (convert) {
+ payload = convert(message);
+ } else {
+ payload = message;
+ }
+ }
+
+ Object[] parameters = params.clone();
+ if (indexSession != -1) {
+ parameters[indexSession] = session;
+ }
+ parameters[indexPayload] = payload;
+
+ Object result;
+ try {
+ result = method.invoke(pojo, parameters);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalArgumentException(e);
+ }
+ processResult(result);
+ }
+
+ protected Object convert(T message) {
+ return message;
+ }
+
+
+ protected abstract Object decode(T message) throws DecodeException;
+ protected abstract void onClose();
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBinary.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBinary.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeBinary.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,120 @@
+/*
+ * 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.websocket.pojo;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.Decoder.Binary;
+import javax.websocket.Decoder.BinaryStream;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+/**
+ * ByteBuffer specific concrete implementation for handling whole messages.
+ */
+public class PojoMessageHandlerWholeBinary
+ extends PojoMessageHandlerWholeBase<ByteBuffer> {
+
+ private final List<Decoder> decoders = new ArrayList<Decoder>();
+
+ private final boolean isForInputStream;
+
+ public PojoMessageHandlerWholeBinary(Object pojo, Method method,
+ Session session, EndpointConfig config,
+ List<Class<? extends Decoder>> decoderClazzes, Object[] params,
+ int indexPayload, boolean convert, int indexSession,
+ boolean isForInputStream, long maxMessageSize) {
+ super(pojo, method, session, params, indexPayload, convert,
+ indexSession, maxMessageSize);
+ try {
+ if (decoderClazzes != null) {
+ for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
+ if (Binary.class.isAssignableFrom(decoderClazz)) {
+ Binary<?> decoder =
+ (Binary<?>) decoderClazz.newInstance();
+ decoder.init(config);
+ decoders.add(decoder);
+ } else if (BinaryStream.class.isAssignableFrom(
+ decoderClazz)) {
+ BinaryStream<?> decoder =
+ (BinaryStream<?>) decoderClazz.newInstance();
+ decoder.init(config);
+ decoders.add(decoder);
+ } else {
+ // Text decoder - ignore it
+ }
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException(e);
+ }
+ this.isForInputStream = isForInputStream;
+ }
+
+
+ @Override
+ protected Object decode(ByteBuffer message) throws DecodeException {
+ for (Decoder decoder : decoders) {
+ if (decoder instanceof Binary) {
+ if (((Binary<?>) decoder).willDecode(message)) {
+ return ((Binary<?>) decoder).decode(message);
+ }
+ } else {
+ byte[] array = new byte[message.limit() - message.position()];
+ message.get(array);
+ ByteArrayInputStream bais = new ByteArrayInputStream(array);
+ try {
+ return ((BinaryStream<?>) decoder).decode(bais);
+ } catch (IOException ioe) {
+ throw new DecodeException(message, MESSAGES.errorDecodingMessage(), ioe);
+ }
+ }
+ }
+ return null;
+ }
+
+
+ @Override
+ protected Object convert(ByteBuffer message) {
+ byte[] array = new byte[message.remaining()];
+ message.get(array);
+ if (isForInputStream) {
+ return new ByteArrayInputStream(array);
+ } else {
+ return array;
+ }
+ }
+
+
+ @Override
+ protected void onClose() {
+ for (Decoder decoder : decoders) {
+ decoder.destroy();
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholePong.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholePong.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholePong.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -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.websocket.pojo;
+
+import java.lang.reflect.Method;
+
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+
+/**
+ * PongMessage specific concrete implementation for handling whole messages.
+ */
+public class PojoMessageHandlerWholePong
+ extends PojoMessageHandlerWholeBase<PongMessage> {
+
+ public PojoMessageHandlerWholePong(Object pojo, Method method,
+ Session session, Object[] params, int indexPayload, boolean convert,
+ int indexSession) {
+ super(pojo, method, session, params, indexPayload, convert,
+ indexSession, -1);
+ }
+
+ @Override
+ protected Object decode(PongMessage message) {
+ // Never decoded
+ return null;
+ }
+
+
+ @Override
+ protected void onClose() {
+ // NO-OP
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeText.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeText.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMessageHandlerWholeText.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,126 @@
+/*
+ * 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.websocket.pojo;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.Decoder.Text;
+import javax.websocket.Decoder.TextStream;
+import javax.websocket.EndpointConfig;
+import javax.websocket.Session;
+
+import org.apache.tomcat.websocket.Util;
+
+
+/**
+ * Text specific concrete implementation for handling whole messages.
+ */
+public class PojoMessageHandlerWholeText
+ extends PojoMessageHandlerWholeBase<String> {
+
+ private final List<Decoder> decoders = new ArrayList<Decoder>();
+ private final Class<?> primitiveType;
+
+ public PojoMessageHandlerWholeText(Object pojo, Method method,
+ Session session, EndpointConfig config,
+ List<Class<? extends Decoder>> decoderClazzes, Object[] params,
+ int indexPayload, boolean convert, int indexSession,
+ long maxMessageSize) {
+ super(pojo, method, session, params, indexPayload, convert,
+ indexSession, maxMessageSize);
+
+ // Check for primitives
+ Class<?> type = method.getParameterTypes()[indexPayload];
+ if (Util.isPrimitive(type)) {
+ primitiveType = type;
+ return;
+ } else {
+ primitiveType = null;
+ }
+
+ try {
+ if (decoderClazzes != null) {
+ for (Class<? extends Decoder> decoderClazz : decoderClazzes) {
+ if (Text.class.isAssignableFrom(decoderClazz)) {
+ Text<?> decoder = (Text<?>) decoderClazz.newInstance();
+ decoder.init(config);
+ decoders.add(decoder);
+ } else if (TextStream.class.isAssignableFrom(
+ decoderClazz)) {
+ TextStream<?> decoder =
+ (TextStream<?>) decoderClazz.newInstance();
+ decoder.init(config);
+ decoders.add(decoder);
+ } else {
+ // Binary decoder - ignore it
+ }
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+
+ @Override
+ protected Object decode(String message) throws DecodeException {
+ // Handle primitives
+ if (primitiveType != null) {
+ return Util.coerceToType(primitiveType, message);
+ }
+ // Handle full decoders
+ for (Decoder decoder : decoders) {
+ if (decoder instanceof Text) {
+ if (((Text<?>) decoder).willDecode(message)) {
+ return ((Text<?>) decoder).decode(message);
+ }
+ } else {
+ StringReader r = new StringReader(message);
+ try {
+ return ((TextStream<?>) decoder).decode(r);
+ } catch (IOException ioe) {
+ throw new DecodeException(message, MESSAGES.errorDecodingMessage(), ioe);
+ }
+ }
+ }
+ return null;
+ }
+
+
+ @Override
+ protected Object convert(String message) {
+ return new StringReader(message);
+ }
+
+
+ @Override
+ protected void onClose() {
+ for (Decoder decoder : decoders) {
+ decoder.destroy();
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,544 @@
+/*
+ * 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.websocket.pojo;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.websocket.CloseReason;
+import javax.websocket.DecodeException;
+import javax.websocket.Decoder;
+import javax.websocket.DeploymentException;
+import javax.websocket.EndpointConfig;
+import javax.websocket.MessageHandler;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.PongMessage;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+
+import org.apache.tomcat.websocket.DecoderEntry;
+import org.apache.tomcat.websocket.Util;
+import org.apache.tomcat.websocket.Util.DecoderMatch;
+
+/**
+ * For a POJO class annotated with
+ * {@link javax.websocket.server.ServerEndpoint}, an instance of this class
+ * creates and caches the method handler, method information and parameter
+ * information for the onXXX calls.
+ */
+public class PojoMethodMapping {
+
+ private final Method onOpen;
+ private final Method onClose;
+ private final Method onError;
+ private final PojoPathParam[] onOpenParams;
+ private final PojoPathParam[] onCloseParams;
+ private final PojoPathParam[] onErrorParams;
+ private final Set<MessageHandlerInfo> onMessage = new HashSet<MessageHandlerInfo>();
+ private final String wsPath;
+
+
+ public PojoMethodMapping(Class<?> clazzPojo,
+ Class<? extends Decoder>[] decoderClazzes, String wsPath)
+ throws DeploymentException {
+
+ this.wsPath = wsPath;
+
+ List<DecoderEntry> decoders = Util.getDecoders(decoderClazzes);
+ Method open = null;
+ Method close = null;
+ Method error = null;
+ for (Method method : clazzPojo.getDeclaredMethods()) {
+ if (method.getAnnotation(OnOpen.class) != null) {
+ checkPublic(method);
+ if (open == null) {
+ open = method;
+ } else {
+ // Duplicate annotation
+ throw new DeploymentException(MESSAGES.duplicateAnnotations(OnOpen.class, clazzPojo));
+ }
+ } else if (method.getAnnotation(OnClose.class) != null) {
+ checkPublic(method);
+ if (close == null) {
+ close = method;
+ } else {
+ // Duplicate annotation
+ throw new DeploymentException(MESSAGES.duplicateAnnotations(OnClose.class, clazzPojo));
+ }
+ } else if (method.getAnnotation(OnError.class) != null) {
+ checkPublic(method);
+ if (error == null) {
+ error = method;
+ } else {
+ // Duplicate annotation
+ throw new DeploymentException(MESSAGES.duplicateAnnotations(OnError.class, clazzPojo));
+ }
+ } else if (method.getAnnotation(OnMessage.class) != null) {
+ checkPublic(method);
+ onMessage.add(new MessageHandlerInfo(method, decoders));
+ } else {
+ // Method not annotated
+ }
+ }
+ this.onOpen = open;
+ this.onClose = close;
+ this.onError = error;
+ onOpenParams = getPathParams(onOpen, MethodType.ON_OPEN);
+ onCloseParams = getPathParams(onClose, MethodType.ON_CLOSE);
+ onErrorParams = getPathParams(onError, MethodType.ON_ERROR);
+ }
+
+
+ private void checkPublic(Method m) throws DeploymentException {
+ if (!Modifier.isPublic(m.getModifiers())) {
+ throw new DeploymentException(MESSAGES.methodNotPublic(m.getName()));
+ }
+ }
+
+
+ public String getWsPath() {
+ return wsPath;
+ }
+
+
+ public Method getOnOpen() {
+ return onOpen;
+ }
+
+
+ public Object[] getOnOpenArgs(Map<String,String> pathParameters,
+ Session session, EndpointConfig config) throws DecodeException {
+ return buildArgs(onOpenParams, pathParameters, session, config, null,
+ null);
+ }
+
+
+ public Method getOnClose() {
+ return onClose;
+ }
+
+
+ public Object[] getOnCloseArgs(Map<String,String> pathParameters,
+ Session session, CloseReason closeReason) throws DecodeException {
+ return buildArgs(onCloseParams, pathParameters, session, null, null,
+ closeReason);
+ }
+
+
+ public Method getOnError() {
+ return onError;
+ }
+
+
+ public Object[] getOnErrorArgs(Map<String,String> pathParameters,
+ Session session, Throwable throwable) throws DecodeException {
+ return buildArgs(onErrorParams, pathParameters, session, null,
+ throwable, null);
+ }
+
+
+ public Set<MessageHandler> getMessageHandlers(Object pojo,
+ Map<String,String> pathParameters, Session session,
+ EndpointConfig config) {
+ Set<MessageHandler> result = new HashSet<MessageHandler>();
+ for (MessageHandlerInfo messageMethod : onMessage) {
+ result.addAll(messageMethod.getMessageHandlers(pojo, pathParameters,
+ session, config));
+ }
+ return result;
+ }
+
+
+ private static PojoPathParam[] getPathParams(Method m,
+ MethodType methodType) throws DeploymentException {
+ if (m == null) {
+ return new PojoPathParam[0];
+ }
+ boolean foundThrowable = false;
+ Class<?>[] types = m.getParameterTypes();
+ Annotation[][] paramsAnnotations = m.getParameterAnnotations();
+ PojoPathParam[] result = new PojoPathParam[types.length];
+ for (int i = 0; i < types.length; i++) {
+ Class<?> type = types[i];
+ if (type.equals(Session.class)) {
+ result[i] = new PojoPathParam(type, null);
+ } else if (methodType == MethodType.ON_OPEN &&
+ type.equals(EndpointConfig.class)) {
+ result[i] = new PojoPathParam(type, null);
+ } else if (methodType == MethodType.ON_ERROR
+ && type.equals(Throwable.class)) {
+ foundThrowable = true;
+ result[i] = new PojoPathParam(type, null);
+ } else if (methodType == MethodType.ON_CLOSE &&
+ type.equals(CloseReason.class)) {
+ result[i] = new PojoPathParam(type, null);
+ } else {
+ Annotation[] paramAnnotations = paramsAnnotations[i];
+ for (Annotation paramAnnotation : paramAnnotations) {
+ if (paramAnnotation.annotationType().equals(
+ PathParam.class)) {
+ // Check that the type is valid. "0" coerces to every
+ // valid type
+ try {
+ Util.coerceToType(type, "0");
+ } catch (IllegalArgumentException iae) {
+ throw new DeploymentException(MESSAGES.invalidPathParamType(),
+ iae);
+ }
+ result[i] = new PojoPathParam(type,
+ ((PathParam) paramAnnotation).value());
+ break;
+ }
+ }
+ // Parameters without annotations are not permitted
+ if (result[i] == null) {
+ throw new DeploymentException(MESSAGES.pathParamWithoutAnnotation(type, m.getName(), m.getClass().getName()));
+ }
+ }
+ }
+ if (methodType == MethodType.ON_ERROR && !foundThrowable) {
+ throw new DeploymentException(MESSAGES.onErrorWithoutThrowable(m.getName(), m.getDeclaringClass().getName()));
+ }
+ return result;
+ }
+
+
+ private static Object[] buildArgs(PojoPathParam[] pathParams,
+ Map<String,String> pathParameters, Session session,
+ EndpointConfig config, Throwable throwable, CloseReason closeReason)
+ throws DecodeException {
+ Object[] result = new Object[pathParams.length];
+ for (int i = 0; i < pathParams.length; i++) {
+ Class<?> type = pathParams[i].getType();
+ if (type.equals(Session.class)) {
+ result[i] = session;
+ } else if (type.equals(EndpointConfig.class)) {
+ result[i] = config;
+ } else if (type.equals(Throwable.class)) {
+ result[i] = throwable;
+ } else if (type.equals(CloseReason.class)) {
+ result[i] = closeReason;
+ } else {
+ String name = pathParams[i].getName();
+ String value = pathParameters.get(name);
+ try {
+ result[i] = Util.coerceToType(type, value);
+ } catch (Exception e) {
+ throw new DecodeException(value, MESSAGES.errorDecodingPathParam(value, type), e);
+ }
+ }
+ }
+ return result;
+ }
+
+
+ private static class MessageHandlerInfo {
+
+ private final Method m;
+ private int indexString = -1;
+ private int indexByteArray = -1;
+ private int indexByteBuffer = -1;
+ private int indexPong = -1;
+ private int indexBoolean = -1;
+ private int indexSession = -1;
+ private int indexInputStream = -1;
+ private int indexReader = -1;
+ private int indexPrimitive = -1;
+ private Map<Integer,PojoPathParam> indexPathParams = new HashMap<Integer, PojoPathParam>();
+ private int indexPayload = -1;
+ private DecoderMatch decoderMatch = null;
+ private long maxMessageSize = -1;
+
+ public MessageHandlerInfo(Method m, List<DecoderEntry> decoderEntries) {
+ this.m = m;
+
+ Class<?>[] types = m.getParameterTypes();
+ Annotation[][] paramsAnnotations = m.getParameterAnnotations();
+
+ for (int i = 0; i < types.length; i++) {
+ boolean paramFound = false;
+ Annotation[] paramAnnotations = paramsAnnotations[i];
+ for (Annotation paramAnnotation : paramAnnotations) {
+ if (paramAnnotation.annotationType().equals(
+ PathParam.class)) {
+ indexPathParams.put(
+ Integer.valueOf(i), new PojoPathParam(types[i],
+ ((PathParam) paramAnnotation).value()));
+ paramFound = true;
+ break;
+ }
+ }
+ if (paramFound) {
+ continue;
+ }
+ if (String.class.isAssignableFrom(types[i])) {
+ if (indexString == -1) {
+ indexString = i;
+ } else {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else if (Reader.class.isAssignableFrom(types[i])) {
+ if (indexReader == -1) {
+ indexReader = i;
+ } else {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else if (boolean.class == types[i]) {
+ if (indexBoolean == -1) {
+ indexBoolean = i;
+ } else {
+ throw MESSAGES.duplicateLastMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else if (ByteBuffer.class.isAssignableFrom(types[i])) {
+ if (indexByteBuffer == -1) {
+ indexByteBuffer = i;
+ } else {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else if (byte[].class == types[i]) {
+ if (indexByteArray == -1) {
+ indexByteArray = i;
+ } else {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else if (InputStream.class.isAssignableFrom(types[i])) {
+ if (indexInputStream == -1) {
+ indexInputStream = i;
+ } else {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else if (Util.isPrimitive(types[i])) {
+ if (indexPrimitive == -1) {
+ indexPrimitive = i;
+ } else {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else if (Session.class.isAssignableFrom(types[i])) {
+ if (indexSession == -1) {
+ indexSession = i;
+ } else {
+ throw MESSAGES.duplicateSessionParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else if (PongMessage.class.isAssignableFrom(types[i])) {
+ if (indexPong == -1) {
+ indexPong = i;
+ } else {
+ throw MESSAGES.duplicatePongMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ } else {
+ if (decoderMatch != null && decoderMatch.hasMatches()) {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ }
+ decoderMatch = new DecoderMatch(types[i], decoderEntries);
+
+ if (decoderMatch.hasMatches()) {
+ indexPayload = i;
+ }
+ }
+ }
+
+ // Additional checks required
+ if (indexString != -1) {
+ if (indexPayload != -1) {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ } else {
+ indexPayload = indexString;
+ }
+ }
+ if (indexReader != -1) {
+ if (indexPayload != -1) {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ } else {
+ indexPayload = indexReader;
+ }
+ }
+ if (indexByteArray != -1) {
+ if (indexPayload != -1) {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ } else {
+ indexPayload = indexByteArray;
+ }
+ }
+ if (indexByteBuffer != -1) {
+ if (indexPayload != -1) {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ } else {
+ indexPayload = indexByteBuffer;
+ }
+ }
+ if (indexInputStream != -1) {
+ if (indexPayload != -1) {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ } else {
+ indexPayload = indexInputStream;
+ }
+ }
+ if (indexPrimitive != -1) {
+ if (indexPayload != -1) {
+ throw MESSAGES.duplicateMessageParameter(m.getName(), m.getDeclaringClass().getName());
+ } else {
+ indexPayload = indexPrimitive;
+ }
+ }
+ if (indexPong != -1) {
+ if (indexPayload != -1) {
+ throw MESSAGES.invalidPongWithPayload(m.getName(), m.getDeclaringClass().getName());
+ } else {
+ indexPayload = indexPong;
+ }
+ }
+ if (indexPayload == -1 && indexPrimitive == -1 &&
+ indexBoolean != -1) {
+ // The boolean we found is a payload, not a last flag
+ indexPayload = indexBoolean;
+ indexPrimitive = indexBoolean;
+ indexBoolean = -1;
+ }
+ if (indexPayload == -1) {
+ throw MESSAGES.missingPayload(m.getName(), m.getDeclaringClass().getName());
+ }
+ if (indexPong != -1 && indexBoolean != -1) {
+ throw MESSAGES.partialPong(m.getName(), m.getDeclaringClass().getName());
+ }
+ if(indexReader != -1 && indexBoolean != -1) {
+ throw MESSAGES.partialReader(m.getName(), m.getDeclaringClass().getName());
+ }
+ if(indexInputStream != -1 && indexBoolean != -1) {
+ throw MESSAGES.partialInputStream(m.getName(), m.getDeclaringClass().getName());
+ }
+ if (decoderMatch != null && decoderMatch.hasMatches() &&
+ indexBoolean != -1) {
+ throw MESSAGES.partialObject(m.getName(), m.getDeclaringClass().getName());
+ }
+
+ maxMessageSize = m.getAnnotation(OnMessage.class).maxMessageSize();
+ }
+
+
+ public Set<MessageHandler> getMessageHandlers(Object pojo,
+ Map<String,String> pathParameters, Session session,
+ EndpointConfig config) {
+ Object[] params = new Object[m.getParameterTypes().length];
+
+ for (Map.Entry<Integer,PojoPathParam> entry :
+ indexPathParams.entrySet()) {
+ PojoPathParam pathParam = entry.getValue();
+ String valueString = pathParameters.get(pathParam.getName());
+ Object value = null;
+ try {
+ value = Util.coerceToType(pathParam.getType(), valueString);
+ } catch (Exception e) {
+ DecodeException de = new DecodeException(valueString,
+ MESSAGES.errorDecodingPathParam(valueString, pathParam.getType()), e);
+ params = new Object[] { de };
+ }
+ params[entry.getKey().intValue()] = value;
+ }
+
+ Set<MessageHandler> results = new HashSet<MessageHandler>(2);
+ if (indexBoolean == -1) {
+ // Basic
+ if (indexString != -1 || indexPrimitive != -1) {
+ MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,
+ session, config, null, params, indexPayload, false,
+ indexSession, maxMessageSize);
+ results.add(mh);
+ } else if (indexReader != -1) {
+ MessageHandler mh = new PojoMessageHandlerWholeText(pojo, m,
+ session, config, null, params, indexReader, true,
+ indexSession, maxMessageSize);
+ results.add(mh);
+ } else if (indexByteArray != -1) {
+ MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
+ m, session, config, null, params, indexByteArray,
+ true, indexSession, false, maxMessageSize);
+ results.add(mh);
+ } else if (indexByteBuffer != -1) {
+ MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
+ m, session, config, null, params, indexByteBuffer,
+ false, indexSession, false, maxMessageSize);
+ results.add(mh);
+ } else if (indexInputStream != -1) {
+ MessageHandler mh = new PojoMessageHandlerWholeBinary(pojo,
+ m, session, config, null, params, indexInputStream,
+ true, indexSession, true, maxMessageSize);
+ results.add(mh);
+ } else if (decoderMatch != null && decoderMatch.hasMatches()) {
+ if (decoderMatch.getBinaryDecoders().size() > 0) {
+ MessageHandler mh = new PojoMessageHandlerWholeBinary(
+ pojo, m, session, config,
+ decoderMatch.getBinaryDecoders(), params,
+ indexPayload, true, indexSession, true,
+ maxMessageSize);
+ results.add(mh);
+ }
+ if (decoderMatch.getTextDecoders().size() > 0) {
+ MessageHandler mh = new PojoMessageHandlerWholeText(
+ pojo, m, session, config,
+ decoderMatch.getTextDecoders(), params,
+ indexPayload, true, indexSession, maxMessageSize);
+ results.add(mh);
+ }
+ } else {
+ MessageHandler mh = new PojoMessageHandlerWholePong(pojo, m,
+ session, params, indexPong, false, indexSession);
+ results.add(mh);
+ }
+ } else {
+ // ASync
+ if (indexString != -1) {
+ MessageHandler mh = new PojoMessageHandlerPartialText(pojo,
+ m, session, params, indexString, false,
+ indexBoolean, indexSession, maxMessageSize);
+ results.add(mh);
+ } else if (indexByteArray != -1) {
+ MessageHandler mh = new PojoMessageHandlerPartialBinary(
+ pojo, m, session, params, indexByteArray, true,
+ indexBoolean, indexSession, maxMessageSize);
+ results.add(mh);
+ } else {
+ MessageHandler mh = new PojoMessageHandlerPartialBinary(
+ pojo, m, session, params, indexByteBuffer, false,
+ indexBoolean, indexSession, maxMessageSize);
+ results.add(mh);
+ }
+ }
+ return results;
+ }
+ }
+
+
+ private static enum MethodType {
+ ON_OPEN,
+ ON_CLOSE,
+ ON_ERROR
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoPathParam.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoPathParam.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/PojoPathParam.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,47 @@
+/*
+ * 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.websocket.pojo;
+
+/**
+ * Stores the parameter type and name for a parameter that needs to be passed to
+ * an onXxx method of {@link javax.websocket.Endpoint}. The name is only present
+ * for parameters annotated with
+ * {@link javax.websocket.server.PathParam}. For the
+ * {@link javax.websocket.Session} and {@link java.lang.Throwable} parameters,
+ * {@link #getName()} will always return <code>null</code>.
+ */
+public class PojoPathParam {
+
+ private final Class<?> type;
+ private final String name;
+
+
+ public PojoPathParam(Class<?> type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+
+ public Class<?> getType() {
+ return type;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/package-info.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/package-info.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/pojo/package-info.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+/**
+ * This package provides the necessary plumbing to convert an annotated POJO
+ * into a WebSocket {@link javax.websocket.Endpoint}.
+ */
+package org.apache.tomcat.websocket.pojo;
\ No newline at end of file
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/Constants.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/Constants.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/Constants.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,41 @@
+/*
+ * 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.websocket.server;
+
+/**
+ * Internal implementation constants.
+ */
+public class Constants {
+
+ protected static final String PACKAGE_NAME =
+ Constants.class.getPackage().getName();
+
+ public static final String BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM =
+ "org.apache.tomcat.websocket.binaryBufferSize";
+ public static final String TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM =
+ "org.apache.tomcat.websocket.textBufferSize";
+ public static final String ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM =
+ "org.apache.tomcat.websocket.noAddAfterHandshake";
+
+ public static final String SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE =
+ "javax.websocket.server.ServerContainer";
+
+
+ private Constants() {
+ // Hide default constructor
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/DefaultServerEndpointConfigurator.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,81 @@
+/*
+ * 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.websocket.server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+
+public class DefaultServerEndpointConfigurator
+ extends ServerEndpointConfig.Configurator {
+
+ @Override
+ public <T> T getEndpointInstance(Class<T> clazz)
+ throws InstantiationException {
+ try {
+ return clazz.newInstance();
+ } catch (IllegalAccessException e) {
+ InstantiationException ie = new InstantiationException();
+ ie.initCause(e);
+ throw ie;
+ }
+ }
+
+
+ @Override
+ public String getNegotiatedSubprotocol(List<String> supported,
+ List<String> requested) {
+
+ for (String request : requested) {
+ if (supported.contains(request)) {
+ return request;
+ }
+ }
+ return "";
+ }
+
+
+ @Override
+ public List<Extension> getNegotiatedExtensions(List<Extension> installed,
+ List<Extension> requested) {
+
+ List<Extension> result = new ArrayList<Extension>();
+ for (Extension request : requested) {
+ if (installed.contains(request)) {
+ result.add(request);
+ }
+ }
+ return result;
+ }
+
+
+ @Override
+ public boolean checkOrigin(String originHeaderValue) {
+ return true;
+ }
+
+ @Override
+ public void modifyHandshake(ServerEndpointConfig sec,
+ HandshakeRequest request, HandshakeResponse response) {
+ // NO-OP
+ }
+
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UpgradeUtil.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UpgradeUtil.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,247 @@
+/*
+ * 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.websocket.server;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.websocket.Endpoint;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.apache.catalina.connector.RequestFacade;
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.apache.tomcat.websocket.Constants;
+import org.apache.tomcat.websocket.WsHandshakeResponse;
+import org.apache.tomcat.websocket.pojo.PojoEndpointServer;
+
+public class UpgradeUtil {
+
+ private static final byte[] WS_ACCEPT =
+ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(
+ StandardCharsets.ISO_8859_1);
+ private static final Queue<MessageDigest> sha1Helpers =
+ new ConcurrentLinkedQueue<MessageDigest>();
+
+ private UpgradeUtil() {
+ // Utility class. Hide default constructor.
+ }
+
+ /**
+ * Checks to see if this is an HTTP request that includes a valid upgrade
+ * request to web socket.
+ * <p>
+ * Note: RFC 2616 does not limit HTTP upgrade to GET requests but the Java
+ * WebSocket spec 1.0, section 8.2 implies such a limitation and RFC
+ * 6455 section 4.1 requires that a WebSocket Upgrade uses GET.
+ */
+ public static boolean isWebSocketUpgrageRequest(ServletRequest request,
+ ServletResponse response) {
+
+ return ((request instanceof HttpServletRequest) &&
+ (response instanceof HttpServletResponse) &&
+ headerContainsToken((HttpServletRequest) request,
+ Constants.UPGRADE_HEADER_NAME,
+ Constants.UPGRADE_HEADER_VALUE) &&
+ "GET".equals(((HttpServletRequest) request).getMethod()));
+ }
+
+
+ public static void doUpgrade(WsServerContainer sc, HttpServletRequest req,
+ HttpServletResponse resp, ServerEndpointConfig sec,
+ Map<String,String> pathParams)
+ throws ServletException, IOException {
+
+ // Validate the rest of the headers and reject the request if that
+ // validation fails
+ String key;
+ String subProtocol = null;
+ List<Extension> extensions = Collections.emptyList();
+ if (!headerContainsToken(req, Constants.CONNECTION_HEADER_NAME,
+ Constants.CONNECTION_HEADER_VALUE)) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+ if (!headerContainsToken(req, Constants.WS_VERSION_HEADER_NAME,
+ Constants.WS_VERSION_HEADER_VALUE)) {
+ resp.setStatus(426);
+ resp.setHeader(Constants.WS_VERSION_HEADER_NAME,
+ Constants.WS_VERSION_HEADER_VALUE);
+ return;
+ }
+ key = req.getHeader(Constants.WS_KEY_HEADER_NAME);
+ if (key == null) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+
+ // Origin check
+ String origin = req.getHeader("Origin");
+ if (!sec.getConfigurator().checkOrigin(origin)) {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+ // Sub-protocols
+ List<String> subProtocols = getTokensFromHeader(req,
+ "Sec-WebSocket-Protocol");
+ if (!subProtocols.isEmpty()) {
+ subProtocol = sec.getConfigurator().
+ getNegotiatedSubprotocol(
+ sec.getSubprotocols(), subProtocols);
+ }
+
+ // Extensions
+ // Currently no extensions are supported by this implementation
+
+ // If we got this far, all is good. Accept the connection.
+ resp.setHeader(Constants.UPGRADE_HEADER_NAME,
+ Constants.UPGRADE_HEADER_VALUE);
+ resp.setHeader(Constants.CONNECTION_HEADER_NAME,
+ Constants.CONNECTION_HEADER_VALUE);
+ resp.setHeader(HandshakeResponse.SEC_WEBSOCKET_ACCEPT,
+ getWebSocketAccept(key));
+ if (subProtocol != null) {
+ resp.setHeader("Sec-WebSocket-Protocol", subProtocol);
+ }
+ if (!extensions.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ Iterator<Extension> iter = extensions.iterator();
+ // There must be at least one
+ sb.append(iter.next());
+ while (iter.hasNext()) {
+ sb.append(',');
+ sb.append(iter.next().getName());
+ }
+ resp.setHeader("Sec-WebSocket-Extensions", sb.toString());
+ }
+ Endpoint ep;
+ try {
+ Class<?> clazz = sec.getEndpointClass();
+ if (Endpoint.class.isAssignableFrom(clazz)) {
+ ep = (Endpoint) sec.getConfigurator().getEndpointInstance(
+ clazz);
+ } else {
+ ep = new PojoEndpointServer();
+ }
+ } catch (InstantiationException e) {
+ throw new ServletException(e);
+ }
+
+ WsHandshakeRequest wsRequest = new WsHandshakeRequest(req);
+ WsHandshakeResponse wsResponse = new WsHandshakeResponse();
+ sec.getConfigurator().modifyHandshake(sec, wsRequest, wsResponse);
+ wsRequest.finished();
+
+ // Add any additional headers
+ for (Entry<String,List<String>> entry :
+ wsResponse.getHeaders().entrySet()) {
+ for (String headerValue: entry.getValue()) {
+ resp.addHeader(entry.getKey(), headerValue);
+ }
+ }
+
+ // Small hack until the Servlet API provides a way to do this.
+ ServletRequest inner = req;
+ // Unwrap the request
+ while (inner instanceof ServletRequestWrapper) {
+ inner = ((ServletRequestWrapper) inner).getRequest();
+ }
+ if (inner instanceof RequestFacade) {
+ WsHttpUpgradeHandler wsHandler =
+ ((RequestFacade) req).upgrade(WsHttpUpgradeHandler.class);
+ wsHandler.preInit(ep, sec, sc, wsRequest, subProtocol,
+ pathParams, req.isSecure());
+ } else {
+ throw new ServletException("Upgrade failed");
+ }
+ }
+
+
+ /*
+ * This only works for tokens. Quoted strings need more sophisticated
+ * parsing.
+ */
+ private static boolean headerContainsToken(HttpServletRequest req,
+ String headerName, String target) {
+ Enumeration<String> headers = req.getHeaders(headerName);
+ while (headers.hasMoreElements()) {
+ String header = headers.nextElement();
+ String[] tokens = header.split(",");
+ for (String token : tokens) {
+ if (target.equalsIgnoreCase(token.trim())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ /*
+ * This only works for tokens. Quoted strings need more sophisticated
+ * parsing.
+ */
+ private static List<String> getTokensFromHeader(HttpServletRequest req,
+ String headerName) {
+ List<String> result = new ArrayList<String>();
+ Enumeration<String> headers = req.getHeaders(headerName);
+ while (headers.hasMoreElements()) {
+ String header = headers.nextElement();
+ String[] tokens = header.split(",");
+ for (String token : tokens) {
+ result.add(token.trim());
+ }
+ }
+ return result;
+ }
+
+
+ private static String getWebSocketAccept(String key) throws ServletException {
+ MessageDigest sha1Helper = sha1Helpers.poll();
+ if (sha1Helper == null) {
+ try {
+ sha1Helper = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new ServletException(e);
+ }
+ }
+ sha1Helper.reset();
+ sha1Helper.update(key.getBytes(StandardCharsets.ISO_8859_1));
+ String result = Base64.encodeBase64String(sha1Helper.digest(WS_ACCEPT));
+ sha1Helpers.add(sha1Helper);
+ return result;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UriTemplate.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UriTemplate.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/UriTemplate.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,171 @@
+/*
+ * 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.websocket.server;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.websocket.DeploymentException;
+
+/**
+ * Extracts path parameters from URIs used to create web socket connections
+ * using the URI template defined for the associated Endpoint.
+ */
+public class UriTemplate {
+
+ private final String normalized;
+ private final List<Segment> segments = new ArrayList<Segment>();
+ private final boolean hasParameters;
+
+
+ public UriTemplate(String path) throws DeploymentException {
+
+ if (path == null || path.length() ==0 || !path.startsWith("/")) {
+ throw new DeploymentException(MESSAGES.invalidPath(path));
+ }
+
+ StringBuilder normalized = new StringBuilder(path.length());
+ Set<String> paramNames = new HashSet<String>();
+
+ // Include empty segments.
+ String[] segments = path.split("/", -1);
+ int paramCount = 0;
+ int segmentCount = 0;
+
+ for (int i = 0; i < segments.length; i++) {
+ String segment = segments[i];
+ if (segment.length() == 0) {
+ if (i == 0 || (i == segments.length - 1 && paramCount == 0)) {
+ // Ignore the first empty segment as the path must always
+ // start with '/'
+ // Ending with a '/' is also OK for instances used for
+ // matches but not for parameterised templates.
+ continue;
+ } else {
+ // As per EG discussion, all other empty segments are
+ // invalid
+ throw MESSAGES.invalidEmptySegment(path);
+ }
+ }
+ normalized.append('/');
+ int index = -1;
+ if (segment.startsWith("{") && segment.endsWith("}")) {
+ index = segmentCount;
+ segment = segment.substring(1, segment.length() - 1);
+ normalized.append('{');
+ normalized.append(paramCount++);
+ normalized.append('}');
+ if (!paramNames.add(segment)) {
+ throw MESSAGES.duplicateParameter(segment);
+ }
+ } else {
+ if (segment.contains("{") || segment.contains("}")) {
+ throw MESSAGES.invalidPathSegment(segment, path);
+ }
+ normalized.append(segment);
+ }
+ this.segments.add(new Segment(index, segment));
+ segmentCount++;
+ }
+
+ this.normalized = normalized.toString();
+ this.hasParameters = paramCount > 0;
+ }
+
+
+ public Map<String,String> match(UriTemplate candidate) {
+
+ Map<String,String> result = new HashMap<String, String>();
+
+ // Should not happen but for safety
+ if (candidate.getSegmentCount() != getSegmentCount()) {
+ return null;
+ }
+
+ Iterator<Segment> candidateSegments =
+ candidate.getSegments().iterator();
+ Iterator<Segment> targetSegments = segments.iterator();
+
+ while (candidateSegments.hasNext()) {
+ Segment candidateSegment = candidateSegments.next();
+ Segment targetSegment = targetSegments.next();
+
+ if (targetSegment.getParameterIndex() == -1) {
+ // Not a parameter - values must match
+ if (!targetSegment.getValue().equals(
+ candidateSegment.getValue())) {
+ // Not a match. Stop here
+ return null;
+ }
+ } else {
+ // Parameter
+ result.put(targetSegment.getValue(),
+ candidateSegment.getValue());
+ }
+ }
+
+ return result;
+ }
+
+
+ public boolean hasParameters() {
+ return hasParameters;
+ }
+
+
+ public int getSegmentCount() {
+ return segments.size();
+ }
+
+
+ public String getNormalizedPath() {
+ return normalized;
+ }
+
+
+ private List<Segment> getSegments() {
+ return segments;
+ }
+
+
+ private static class Segment {
+ private final int parameterIndex;
+ private final String value;
+
+ public Segment(int parameterIndex, String value) {
+ this.parameterIndex = parameterIndex;
+ this.value = value;
+ }
+
+
+ public int getParameterIndex() {
+ return parameterIndex;
+ }
+
+
+ public String getValue() {
+ return value;
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsContextListener.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsContextListener.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsContextListener.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,51 @@
+/*
+ * 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.websocket.server;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * In normal usage, this {@link ServletContextListener} does not need to be
+ * explicitly configured as the {@link WsSci} performs all the necessary
+ * bootstrap and installs this listener in the {@link ServletContext}. If the
+ * {@link WsSci} is disabled, this listener must be added manually to every
+ * {@link ServletContext} that uses WebSocket to bootstrap the
+ * {@link WsServerContainer} correctly.
+ */
+public class WsContextListener implements ServletContextListener {
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ ServletContext sc = sce.getServletContext();
+ // Don't trigger WebSocket initialization if a WebSocket Server
+ // Container is already present
+ if (sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE) == null) {
+ WsSci.init(sce.getServletContext(), false);
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ ServletContext sc = sce.getServletContext();
+ Object obj = sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+ if (obj instanceof WsServerContainer) {
+ ((WsServerContainer) obj).destroy();
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsFilter.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsFilter.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsFilter.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,87 @@
+/*
+ * 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.websocket.server;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Handles the initial HTTP connection for WebSocket connections.
+ */
+public class WsFilter implements Filter {
+
+ private WsServerContainer sc;
+
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ sc = (WsServerContainer) filterConfig.getServletContext().getAttribute(
+ Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE);
+ }
+
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+
+ // This filter only needs to handle WebSocket upgrade requests
+ if (!UpgradeUtil.isWebSocketUpgrageRequest(request, response)) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ // HTTP request with an upgrade header for WebSocket present
+ HttpServletRequest req = (HttpServletRequest) request;
+ HttpServletResponse resp = (HttpServletResponse) response;
+
+ // Check to see if this WebSocket implementation has a matching mapping
+ String path;
+ String pathInfo = req.getPathInfo();
+ if (pathInfo == null) {
+ path = req.getServletPath();
+ } else {
+ path = req.getServletPath() + pathInfo;
+ }
+ WsMappingResult mappingResult = sc.findMapping(path);
+
+ if (mappingResult == null) {
+ // No endpoint registered for the requested path. Let the
+ // application handle it (it might redirect or forward for example)
+ chain.doFilter(request, response);
+ return;
+ }
+
+ UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(),
+ mappingResult.getPathParams());
+ }
+
+
+ @Override
+ public void destroy() {
+ // NO-OP
+ }
+
+
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsFrameServer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsFrameServer.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsFrameServer.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,65 @@
+/*
+ * 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.websocket.server;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+import org.apache.coyote.http11.upgrade.AbstractServletInputStream;
+import org.apache.tomcat.websocket.WsFrameBase;
+import org.apache.tomcat.websocket.WsSession;
+
+public class WsFrameServer extends WsFrameBase {
+
+ private final AbstractServletInputStream sis;
+ private final Object connectionReadLock = new Object();
+
+
+ public WsFrameServer(AbstractServletInputStream sis, WsSession wsSession) {
+ super(wsSession);
+ this.sis = sis;
+ }
+
+
+ /**
+ * Called when there is data in the ServletInputStream to process.
+ */
+ public void onDataAvailable() throws IOException {
+ synchronized (connectionReadLock) {
+ while (isOpen() && sis.isReady()) {
+ // Fill up the input buffer with as much data as we can
+ int read = sis.read(
+ inputBuffer, writePos, inputBuffer.length - writePos);
+ if (read == 0) {
+ return;
+ }
+ if (read == -1) {
+ throw new EOFException();
+ }
+ writePos += read;
+ processInputBuffer();
+ }
+ }
+ }
+
+
+ @Override
+ protected boolean isMasked() {
+ // Data is from the client so it should be masked
+ return true;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsHandshakeRequest.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,143 @@
+/*
+ * 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.websocket.server;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.websocket.server.HandshakeRequest;
+
+/**
+ * Represents the request that this session was opened under.
+ */
+public class WsHandshakeRequest implements HandshakeRequest {
+
+ private final URI requestUri;
+ private final Map<String,List<String>> parameterMap;
+ private final String queryString;
+ private final Principal userPrincipal;
+ private final Map<String,List<String>> headers;
+ private final Object httpSession;
+
+ private volatile HttpServletRequest request;
+
+
+ public WsHandshakeRequest(HttpServletRequest request) {
+
+ this.request = request;
+
+ queryString = request.getQueryString();
+ userPrincipal = request.getUserPrincipal();
+ httpSession = request.getSession(false);
+
+ // URI
+ StringBuilder sb = new StringBuilder(request.getRequestURI());
+ if (queryString != null) {
+ sb.append("?");
+ sb.append(queryString);
+ }
+ try {
+ requestUri = new URI(sb.toString());
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ // ParameterMap
+ Map<String,String[]> originalParameters = request.getParameterMap();
+ Map<String,List<String>> newParameters =
+ new HashMap<String, List<String>>(originalParameters.size());
+ for (Entry<String,String[]> entry : originalParameters.entrySet()) {
+ newParameters.put(entry.getKey(),
+ Collections.unmodifiableList(
+ Arrays.asList(entry.getValue())));
+ }
+ parameterMap = Collections.unmodifiableMap(newParameters);
+
+ // Headers
+ Map<String,List<String>> newHeaders = new HashMap<String, List<String>>();
+
+ Enumeration<String> headerNames = request.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String headerName = headerNames.nextElement();
+
+ newHeaders.put(headerName, Collections.unmodifiableList(
+ Collections.list(request.getHeaders(headerName))));
+ }
+
+ headers = Collections.unmodifiableMap(newHeaders);
+ }
+
+ @Override
+ public URI getRequestURI() {
+ return requestUri;
+ }
+
+ @Override
+ public Map<String,List<String>> getParameterMap() {
+ return parameterMap;
+ }
+
+ @Override
+ public String getQueryString() {
+ return queryString;
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return userPrincipal;
+ }
+
+ @Override
+ public Map<String,List<String>> getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ if (request == null) {
+ throw new IllegalStateException();
+ }
+
+ return request.isUserInRole(role);
+ }
+
+ @Override
+ public Object getHttpSession() {
+ return httpSession;
+ }
+
+ /**
+ * Called when the HandshakeRequest is no longer required. Since an instance
+ * of this class retains a reference to the current HttpServletRequest that
+ * reference needs to be cleared as the HttpServletRequest may be reused.
+ *
+ * There is no reason for instances of this class to be accessed once the
+ * handshake has been completed.
+ */
+ void finished() {
+ request = null;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,237 @@
+/*
+ * 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.websocket.server;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfig;
+
+import org.apache.coyote.http11.upgrade.AbstractServletInputStream;
+import org.apache.coyote.http11.upgrade.AbstractServletOutputStream;
+import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler;
+import org.apache.coyote.http11.upgrade.servlet31.ReadListener;
+import org.apache.coyote.http11.upgrade.servlet31.WebConnection;
+import org.apache.coyote.http11.upgrade.servlet31.WriteListener;
+import org.apache.tomcat.websocket.WsIOException;
+import org.apache.tomcat.websocket.WsSession;
+import org.jboss.web.WebsocketsLogger;
+
+/**
+ * Servlet 3.1 HTTP upgrade handler for WebSocket connections.
+ */
+public class WsHttpUpgradeHandler implements HttpUpgradeHandler {
+
+ private final ClassLoader applicationClassLoader;
+
+ private Endpoint ep;
+ private EndpointConfig endpointConfig;
+ private WsServerContainer webSocketContainer;
+ private WsHandshakeRequest handshakeRequest;
+ private String subProtocol;
+ private Map<String,String> pathParameters;
+ private boolean secure;
+ private WebConnection connection;
+
+ private WsSession wsSession;
+
+
+ public WsHttpUpgradeHandler() {
+ applicationClassLoader = Thread.currentThread().getContextClassLoader();
+ }
+
+
+ public void preInit(Endpoint ep, EndpointConfig endpointConfig,
+ WsServerContainer wsc, WsHandshakeRequest handshakeRequest,
+ String subProtocol, Map<String,String> pathParameters,
+ boolean secure) {
+ this.ep = ep;
+ this.endpointConfig = endpointConfig;
+ this.webSocketContainer = wsc;
+ this.handshakeRequest = handshakeRequest;
+ this.subProtocol = subProtocol;
+ this.pathParameters = pathParameters;
+ this.secure = secure;
+ }
+
+
+ @Override
+ public void init(WebConnection connection) {
+ if (ep == null) {
+ throw MESSAGES.noPreInit();
+ }
+
+ this.connection = connection;
+
+ AbstractServletInputStream sis;
+ AbstractServletOutputStream sos;
+ try {
+ sis = connection.getInputStream();
+ sos = connection.getOutputStream();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ String httpSessionId = null;
+ Object session = handshakeRequest.getHttpSession();
+ if (session != null ) {
+ httpSessionId = ((HttpSession) session).getId();
+ }
+
+ // Need to call onOpen using the web application's class loader
+ // Create the frame using the application's class loader so it can pick
+ // up application specific config from the ServerContainerImpl
+ Thread t = Thread.currentThread();
+ ClassLoader cl = t.getContextClassLoader();
+ t.setContextClassLoader(applicationClassLoader);
+ try {
+ WsRemoteEndpointImplServer wsRemoteEndpointServer =
+ new WsRemoteEndpointImplServer(sos, webSocketContainer);
+ wsSession = new WsSession(ep, wsRemoteEndpointServer,
+ webSocketContainer, handshakeRequest.getRequestURI(),
+ handshakeRequest.getParameterMap(),
+ handshakeRequest.getQueryString(),
+ handshakeRequest.getUserPrincipal(), httpSessionId,
+ subProtocol, pathParameters, secure, endpointConfig);
+ WsFrameServer wsFrame = new WsFrameServer(
+ sis,
+ wsSession);
+ sos.setWriteListener(
+ new WsWriteListener(this, wsRemoteEndpointServer));
+ ep.onOpen(wsSession, endpointConfig);
+ webSocketContainer.registerSession(ep, wsSession);
+ sis.setReadListener(new WsReadListener(this, wsFrame));
+ } catch (DeploymentException e) {
+ throw new IllegalArgumentException(e);
+ } finally {
+ t.setContextClassLoader(cl);
+ }
+ }
+
+
+ @Override
+ public void destroy() {
+ try {
+ connection.close();
+ } catch (Exception e) {
+ WebsocketsLogger.ROOT_LOGGER.destroyFailed(e);
+ }
+ }
+
+
+ private void onError(Throwable throwable) {
+ // Need to call onError using the web application's class loader
+ Thread t = Thread.currentThread();
+ ClassLoader cl = t.getContextClassLoader();
+ t.setContextClassLoader(applicationClassLoader);
+ try {
+ ep.onError(wsSession, throwable);
+ } finally {
+ t.setContextClassLoader(cl);
+ }
+ }
+
+
+ private void close(CloseReason cr) {
+ /*
+ * Any call to this method is a result of a problem reading from the
+ * client. At this point that state of the connection is unknown.
+ * Attempt to send a close frame to the client and then close the socket
+ * immediately. There is no point in waiting for a close frame from the
+ * client because there is no guarantee that we can recover from
+ * whatever messed up state the client put the connection into.
+ */
+ wsSession.onClose(cr);
+ }
+
+
+ private static class WsReadListener implements ReadListener {
+
+ private final WsHttpUpgradeHandler wsProtocolHandler;
+ private final WsFrameServer wsFrame;
+
+
+ private WsReadListener(WsHttpUpgradeHandler wsProtocolHandler,
+ WsFrameServer wsFrame) {
+ this.wsProtocolHandler = wsProtocolHandler;
+ this.wsFrame = wsFrame;
+ }
+
+
+ @Override
+ public void onDataAvailable() {
+ try {
+ wsFrame.onDataAvailable();
+ } catch (WsIOException ws) {
+ wsProtocolHandler.close(ws.getCloseReason());
+ } catch (EOFException eof) {
+ CloseReason cr = new CloseReason(
+ CloseCodes.CLOSED_ABNORMALLY, eof.getMessage());
+ wsProtocolHandler.close(cr);
+ } catch (IOException ioe) {
+ onError(ioe);
+ }
+ }
+
+
+ @Override
+ public void onAllDataRead() {
+ // Will never happen with WebSocket
+ throw new IllegalStateException();
+ }
+
+
+ @Override
+ public void onError(Throwable throwable) {
+ wsProtocolHandler.onError(throwable);
+ }
+ }
+
+
+ private static class WsWriteListener implements WriteListener {
+
+ private final WsHttpUpgradeHandler wsProtocolHandler;
+ private final WsRemoteEndpointImplServer wsRemoteEndpointServer;
+
+ private WsWriteListener(WsHttpUpgradeHandler wsProtocolHandler,
+ WsRemoteEndpointImplServer wsRemoteEndpointServer) {
+ this.wsProtocolHandler = wsProtocolHandler;
+ this.wsRemoteEndpointServer = wsRemoteEndpointServer;
+ }
+
+
+ @Override
+ public void onWritePossible() {
+ wsRemoteEndpointServer.onWritePossible();
+ }
+
+
+ @Override
+ public void onError(Throwable throwable) {
+ wsProtocolHandler.onError(throwable);
+ wsRemoteEndpointServer.close();
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsMappingResult.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsMappingResult.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsMappingResult.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,44 @@
+/*
+ * 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.websocket.server;
+
+import java.util.Map;
+
+import javax.websocket.server.ServerEndpointConfig;
+
+class WsMappingResult {
+
+ private final ServerEndpointConfig config;
+ private final Map<String,String> pathParams;
+
+
+ WsMappingResult(ServerEndpointConfig config,
+ Map<String,String> pathParams) {
+ this.config = config;
+ this.pathParams = pathParams;
+ }
+
+
+ ServerEndpointConfig getConfig() {
+ return config;
+ }
+
+
+ Map<String,String> getPathParams() {
+ return pathParams;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,154 @@
+/*
+ * 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.websocket.server;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+
+import javax.websocket.SendHandler;
+import javax.websocket.SendResult;
+
+import org.apache.coyote.http11.upgrade.AbstractServletOutputStream;
+import org.apache.tomcat.websocket.WsRemoteEndpointImplBase;
+import org.jboss.web.WebsocketsLogger;
+
+/**
+ * This is the server side {@link javax.websocket.RemoteEndpoint} implementation
+ * - i.e. what the server uses to send data to the client. Communication is over
+ * a {@link javax.servlet.ServletOutputStream}.
+ */
+public class WsRemoteEndpointImplServer extends WsRemoteEndpointImplBase {
+
+ private final AbstractServletOutputStream sos;
+ private final WsWriteTimeout wsWriteTimeout;
+ private volatile SendHandler handler = null;
+ private volatile ByteBuffer[] buffers = null;
+
+ private volatile long timeoutExpiry = -1;
+ private volatile boolean close;
+
+
+ public WsRemoteEndpointImplServer(AbstractServletOutputStream sos,
+ WsServerContainer serverContainer) {
+ this.sos = sos;
+ this.wsWriteTimeout = serverContainer.getTimeout();
+ }
+
+
+ @Override
+ protected final boolean isMasked() {
+ return false;
+ }
+
+
+ @Override
+ protected void doWrite(SendHandler handler, ByteBuffer... buffers) {
+ this.handler = handler;
+ this.buffers = buffers;
+ onWritePossible();
+ }
+
+
+ public void onWritePossible() {
+ boolean complete = true;
+ try {
+ // If this is false there will be a call back when it is true
+ while (sos.isReady()) {
+ complete = true;
+ for (ByteBuffer buffer : buffers) {
+ if (buffer.hasRemaining()) {
+ complete = false;
+ sos.write(buffer.array(), buffer.arrayOffset(),
+ buffer.limit());
+ buffer.position(buffer.limit());
+ break;
+ }
+ }
+ if (complete) {
+ wsWriteTimeout.unregister(this);
+ if (close) {
+ close();
+ }
+ // Setting the result marks this (partial) message as
+ // complete which means the next one may be sent which
+ // could update the value of the handler. Therefore, keep a
+ // local copy before signalling the end of the (partial)
+ // message.
+ clearHandler(null);
+ break;
+ }
+ }
+
+ } catch (IOException ioe) {
+ wsWriteTimeout.unregister(this);
+ close();
+ clearHandler(ioe);
+ }
+ if (!complete) {
+ // Async write is in progress
+
+ long timeout = getSendTimeout();
+ if (timeout > 0) {
+ // Register with timeout thread
+ timeoutExpiry = timeout + System.currentTimeMillis();
+ wsWriteTimeout.register(this);
+ }
+ }
+ }
+
+
+ @Override
+ protected void doClose() {
+ if (handler != null) {
+ clearHandler(new EOFException());
+ }
+ try {
+ sos.close();
+ } catch (IOException e) {
+ WebsocketsLogger.ROOT_LOGGER.closeFailed(e);
+ }
+ wsWriteTimeout.unregister(this);
+ }
+
+
+ protected long getTimeoutExpiry() {
+ return timeoutExpiry;
+ }
+
+
+ protected void onTimeout() {
+ if (handler != null) {
+ clearHandler(new SocketTimeoutException());
+ }
+ close();
+ }
+
+
+ private void clearHandler(Throwable t) {
+ SendHandler sh = handler;
+ handler = null;
+ if (sh != null) {
+ if (t == null) {
+ sh.onResult(new SendResult());
+ } else {
+ sh.onResult(new SendResult(t));
+ }
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsSci.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsSci.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsSci.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,171 @@
+/*
+ * 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.websocket.server;
+
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HandlesTypes;
+import javax.websocket.ContainerProvider;
+import javax.websocket.DeploymentException;
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerApplicationConfig;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.jboss.web.WebsocketsLogger;
+
+/**
+ * Registers an interest in any class that is annotated with
+ * {@link ServerEndpoint} so that Endpoint can be published via the WebSocket
+ * server.
+ */
+(a)HandlesTypes({ServerEndpoint.class, ServerApplicationConfig.class,
+ Endpoint.class})
+public class WsSci implements ServletContainerInitializer {
+
+ private static boolean logMessageWritten = false;
+
+ @Override
+ public void onStartup(Set<Class<?>> clazzes, ServletContext ctx)
+ throws ServletException {
+
+ if (!isJava7OrLater()) {
+ // The WebSocket implementation requires Java 7 so don't initialise
+ // it if Java 7 is not available.
+ if (!logMessageWritten) {
+ logMessageWritten = true;
+ WebsocketsLogger.ROOT_LOGGER.noWebsocketsSupport();
+ }
+ return;
+ }
+
+ WsServerContainer sc = init(ctx, true);
+
+ if (clazzes == null || clazzes.size() == 0) {
+ return;
+ }
+
+ // Group the discovered classes by type
+ Set<ServerApplicationConfig> serverApplicationConfigs = new HashSet<ServerApplicationConfig>();
+ Set<Class<? extends Endpoint>> scannedEndpointClazzes = new HashSet<Class<? extends Endpoint>>();
+ Set<Class<?>> scannedPojoEndpoints = new HashSet<Class<?>>();
+
+ try {
+ // wsPackage is "javax.websocket."
+ String wsPackage = ContainerProvider.class.getName();
+ wsPackage = wsPackage.substring(0, wsPackage.lastIndexOf('.') + 1);
+ for (Class<?> clazz : clazzes) {
+ int modifiers = clazz.getModifiers();
+ if (!Modifier.isPublic(modifiers) ||
+ Modifier.isAbstract(modifiers)) {
+ // Non-public or abstract - skip it.
+ continue;
+ }
+ // Protect against scanning the WebSocket API JARs
+ if (clazz.getName().startsWith(wsPackage)) {
+ continue;
+ }
+ if (ServerApplicationConfig.class.isAssignableFrom(clazz)) {
+ serverApplicationConfigs.add(
+ (ServerApplicationConfig) clazz.newInstance());
+ }
+ if (Endpoint.class.isAssignableFrom(clazz)) {
+ @SuppressWarnings("unchecked")
+ Class<? extends Endpoint> endpoint =
+ (Class<? extends Endpoint>) clazz;
+ scannedEndpointClazzes.add(endpoint);
+ }
+ if (clazz.isAnnotationPresent(ServerEndpoint.class)) {
+ scannedPojoEndpoints.add(clazz);
+ }
+ }
+ } catch (InstantiationException e) {
+ throw new ServletException(e);
+ } catch (IllegalAccessException e) {
+ throw new ServletException(e);
+ }
+
+ // Filter the results
+ Set<ServerEndpointConfig> filteredEndpointConfigs = new HashSet<ServerEndpointConfig>();
+ Set<Class<?>> filteredPojoEndpoints = new HashSet<Class<?>>();
+
+ if (serverApplicationConfigs.isEmpty()) {
+ filteredPojoEndpoints.addAll(scannedPojoEndpoints);
+ } else {
+ for (ServerApplicationConfig config : serverApplicationConfigs) {
+ Set<ServerEndpointConfig> configFilteredEndpoints =
+ config.getEndpointConfigs(scannedEndpointClazzes);
+ if (configFilteredEndpoints != null) {
+ filteredEndpointConfigs.addAll(configFilteredEndpoints);
+ }
+ Set<Class<?>> configFilteredPojos =
+ config.getAnnotatedEndpointClasses(
+ scannedPojoEndpoints);
+ if (configFilteredPojos != null) {
+ filteredPojoEndpoints.addAll(configFilteredPojos);
+ }
+ }
+ }
+
+ try {
+ // Deploy endpoints
+ for (ServerEndpointConfig config : filteredEndpointConfigs) {
+ sc.addEndpoint(config);
+ }
+ // Deploy POJOs
+ for (Class<?> clazz : filteredPojoEndpoints) {
+ sc.addEndpoint(clazz);
+ }
+ } catch (DeploymentException e) {
+ throw new ServletException(e);
+ }
+ }
+
+
+ static WsServerContainer init(ServletContext servletContext,
+ boolean initBySciMechanism) {
+
+ WsServerContainer sc = new WsServerContainer(servletContext);
+
+ servletContext.setAttribute(
+ Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE, sc);
+
+ servletContext.addListener(new WsSessionListener(sc));
+ // Can't register the ContextListener again if the ContextListener is
+ // calling this method
+ if (initBySciMechanism) {
+ servletContext.addListener(new WsContextListener());
+ }
+
+ return sc;
+ }
+
+
+ private static boolean isJava7OrLater() {
+ try {
+ Class.forName("java.nio.channels.AsynchronousSocketChannel");
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsServerContainer.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsServerContainer.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsServerContainer.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,439 @@
+/*
+ * 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.websocket.server;
+
+import static org.jboss.web.WebsocketsMessages.MESSAGES;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.DeploymentException;
+import javax.websocket.Encoder;
+import javax.websocket.Endpoint;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpoint;
+import javax.websocket.server.ServerEndpointConfig;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+import org.apache.tomcat.websocket.WsSession;
+import org.apache.tomcat.websocket.WsWebSocketContainer;
+import org.apache.tomcat.websocket.pojo.PojoEndpointServer;
+import org.apache.tomcat.websocket.pojo.PojoMethodMapping;
+
+/**
+ * Provides a per class loader (i.e. per web application) instance of a
+ * ServerContainer. Web application wide defaults may be configured by setting
+ * the following servlet context initialisation parameters to the desired
+ * values.
+ * <ul>
+ * <li>{@link Constants#BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM}</li>
+ * <li>{@link Constants#TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM}</li>
+ * </ul>
+ */
+public class WsServerContainer extends WsWebSocketContainer
+ implements ServerContainer {
+
+ private static final CloseReason AUTHENTICATED_HTTP_SESSION_CLOSED =
+ new CloseReason(CloseCodes.VIOLATED_POLICY,
+ "This connection was established under an authenticated " +
+ "HTTP session that has ended.");
+
+ private final WsWriteTimeout wsWriteTimeout = new WsWriteTimeout();
+
+ private final ServletContext servletContext;
+ private final Map<String,ServerEndpointConfig> configExactMatchMap =
+ new ConcurrentHashMap<String, ServerEndpointConfig>();
+ private final ConcurrentHashMap<Integer,SortedSet<TemplatePathMatch>>
+ configTemplateMatchMap = new ConcurrentHashMap<Integer, SortedSet<TemplatePathMatch>>();
+ private volatile boolean enforceNoAddAfterHandshake =
+ org.apache.tomcat.websocket.Constants.STRICT_SPEC_COMPLIANCE;
+ private volatile boolean addAllowed = true;
+ private final ConcurrentHashMap<String,Set<WsSession>> authenticatedSessions =
+ new ConcurrentHashMap<String, Set<WsSession>>();
+
+ WsServerContainer(ServletContext servletContext) {
+
+ this.servletContext = servletContext;
+
+ // Configure servlet context wide defaults
+ String value = servletContext.getInitParameter(
+ Constants.BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM);
+ if (value != null) {
+ setDefaultMaxBinaryMessageBufferSize(Integer.parseInt(value));
+ }
+
+ value = servletContext.getInitParameter(
+ Constants.TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM);
+ if (value != null) {
+ setDefaultMaxTextMessageBufferSize(Integer.parseInt(value));
+ }
+
+ value = servletContext.getInitParameter(
+ Constants.ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM);
+ if (value != null) {
+ setEnforceNoAddAfterHandshake(Boolean.parseBoolean(value));
+ }
+
+ FilterRegistration.Dynamic fr = servletContext.addFilter(
+ WsFilter.class.getName(), new WsFilter());
+ fr.setAsyncSupported(true);
+
+ EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST,
+ DispatcherType.FORWARD);
+
+ fr.addMappingForUrlPatterns(types, true, "/*");
+ }
+
+
+ /**
+ * Published the provided endpoint implementation at the specified path with
+ * the specified configuration. {@link #WsServerContainer(ServletContext)}
+ * must be called before calling this method.
+ *
+ * @param sec The configuration to use when creating endpoint instances
+ * @throws DeploymentException
+ */
+ @Override
+ public void addEndpoint(ServerEndpointConfig sec)
+ throws DeploymentException {
+
+ if (enforceNoAddAfterHandshake && !addAllowed) {
+ throw new DeploymentException(MESSAGES.addNotAllowed());
+ }
+
+ if (servletContext == null) {
+ throw new DeploymentException(MESSAGES.missingServletContext());
+ }
+ String path = sec.getPath();
+
+ UriTemplate uriTemplate = new UriTemplate(path);
+ if (uriTemplate.hasParameters()) {
+ Integer key = Integer.valueOf(uriTemplate.getSegmentCount());
+ SortedSet<TemplatePathMatch> templateMatches =
+ configTemplateMatchMap.get(key);
+ if (templateMatches == null) {
+ // Ensure that if concurrent threads execute this block they
+ // both end up using the same TreeSet instance
+ templateMatches = new TreeSet<TemplatePathMatch>(
+ TemplatePathMatchComparator.getInstance());
+ configTemplateMatchMap.putIfAbsent(key, templateMatches);
+ templateMatches = configTemplateMatchMap.get(key);
+ }
+ if (!templateMatches.add(new TemplatePathMatch(sec, uriTemplate))) {
+ // Duplicate uriTemplate;
+ throw new DeploymentException(MESSAGES.duplicatePaths(path));
+ }
+ } else {
+ // Exact match
+ ServerEndpointConfig old = configExactMatchMap.put(path, sec);
+ if (old != null) {
+ // Duplicate path mappings
+ throw new DeploymentException(MESSAGES.duplicatePaths(path));
+ }
+ }
+ }
+
+
+ /**
+ * Provides the equivalent of {@link #addEndpoint(ServerEndpointConfig)}
+ * for publishing plain old java objects (POJOs) that have been annotated as
+ * WebSocket endpoints.
+ *
+ * @param pojo The annotated POJO
+ */
+ @Override
+ public void addEndpoint(Class<?> pojo) throws DeploymentException {
+
+ ServerEndpoint annotation = pojo.getAnnotation(ServerEndpoint.class);
+ if (annotation == null) {
+ throw new DeploymentException(MESSAGES.cannotDeployPojo(pojo.getName()));
+ }
+ String path = annotation.value();
+
+ // Validate encoders
+ validateEncoders(annotation.encoders());
+
+ // Method mapping
+ PojoMethodMapping methodMapping = new PojoMethodMapping(pojo,
+ annotation.decoders(), path);
+
+ // ServerEndpointConfig
+ ServerEndpointConfig sec;
+ Class<? extends Configurator> configuratorClazz =
+ annotation.configurator();
+ Configurator configurator = null;
+ if (!configuratorClazz.equals(Configurator.class)) {
+ try {
+ configurator = annotation.configurator().newInstance();
+ } catch (InstantiationException e) {
+ throw new DeploymentException(MESSAGES.configuratorFailed(annotation.configurator().getName(),
+ pojo.getClass().getName()), e);
+ } catch (IllegalAccessException e) {
+ throw new DeploymentException(MESSAGES.configuratorFailed(annotation.configurator().getName(),
+ pojo.getClass().getName()), e);
+ }
+ }
+ sec = ServerEndpointConfig.Builder.create(pojo, path).
+ decoders(Arrays.asList(annotation.decoders())).
+ encoders(Arrays.asList(annotation.encoders())).
+ subprotocols(Arrays.asList(annotation.subprotocols())).
+ configurator(configurator).
+ build();
+ sec.getUserProperties().put(
+ PojoEndpointServer.POJO_METHOD_MAPPING_KEY,
+ methodMapping);
+
+ addEndpoint(sec);
+ }
+
+
+ public void doUpgrade(HttpServletRequest request,
+ HttpServletResponse response, ServerEndpointConfig sec,
+ Map<String,String> pathParams)
+ throws ServletException, IOException {
+ UpgradeUtil.doUpgrade(this, request, response, sec, pathParams);
+ }
+
+
+ public WsMappingResult findMapping(String path) {
+
+ // Prevent registering additional endpoints once the first attempt has
+ // been made to use one
+ if (addAllowed) {
+ addAllowed = false;
+ }
+
+ // Check an exact match. Simple case as there are no templates.
+ ServerEndpointConfig sec = configExactMatchMap.get(path);
+ if (sec != null) {
+ return new WsMappingResult(sec,
+ Collections.<String, String> emptyMap());
+ }
+
+ // No exact match. Need to look for template matches.
+ UriTemplate pathUriTemplate = null;
+ try {
+ pathUriTemplate = new UriTemplate(path);
+ } catch (DeploymentException e) {
+ // Path is not valid so can't be matched to a WebSocketEndpoint
+ return null;
+ }
+
+ // Number of segments has to match
+ Integer key = Integer.valueOf(pathUriTemplate.getSegmentCount());
+ SortedSet<TemplatePathMatch> templateMatches =
+ configTemplateMatchMap.get(key);
+
+ if (templateMatches == null) {
+ // No templates with an equal number of segments so there will be
+ // no matches
+ return null;
+ }
+
+ // List is in alphabetical order of normalised templates.
+ // Correct match is the first one that matches.
+ Map<String,String> pathParams = null;
+ for (TemplatePathMatch templateMatch : templateMatches) {
+ pathParams = templateMatch.getUriTemplate().match(pathUriTemplate);
+ if (pathParams != null) {
+ sec = templateMatch.getConfig();
+ break;
+ }
+ }
+
+ if (sec == null) {
+ // No match
+ return null;
+ }
+
+ if (!PojoEndpointServer.class.isAssignableFrom(sec.getEndpointClass())) {
+ // Need to make path params available to POJO
+ sec.getUserProperties().put(
+ PojoEndpointServer.POJO_PATH_PARAM_KEY,
+ pathParams);
+ }
+
+ return new WsMappingResult(sec, pathParams);
+ }
+
+
+
+ public boolean isEnforceNoAddAfterHandshake() {
+ return enforceNoAddAfterHandshake;
+ }
+
+
+ public void setEnforceNoAddAfterHandshake(
+ boolean enforceNoAddAfterHandshake) {
+ this.enforceNoAddAfterHandshake = enforceNoAddAfterHandshake;
+ }
+
+
+ protected WsWriteTimeout getTimeout() {
+ return wsWriteTimeout;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * Overridden to make it visible to other classes in this package.
+ */
+ @Override
+ protected void registerSession(Endpoint endpoint, WsSession wsSession) {
+ super.registerSession(endpoint, wsSession);
+ if (wsSession.isOpen() &&
+ wsSession.getUserPrincipal() != null &&
+ wsSession.getHttpSessionId() != null) {
+ registerAuthenticatedSession(wsSession,
+ wsSession.getHttpSessionId());
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * Overridden to make it visible to other classes in this package.
+ */
+ @Override
+ protected void unregisterSession(Endpoint endpoint, WsSession wsSession) {
+ if (wsSession.getUserPrincipal() != null &&
+ wsSession.getHttpSessionId() != null) {
+ unregisterAuthenticatedSession(wsSession,
+ wsSession.getHttpSessionId());
+ }
+ super.unregisterSession(endpoint, wsSession);
+ }
+
+
+ private void registerAuthenticatedSession(WsSession wsSession,
+ String httpSessionId) {
+ Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId);
+ if (wsSessions == null) {
+ wsSessions = Collections.newSetFromMap(
+ new ConcurrentHashMap<WsSession,Boolean>());
+ authenticatedSessions.putIfAbsent(httpSessionId, wsSessions);
+ wsSessions = authenticatedSessions.get(httpSessionId);
+ }
+ wsSessions.add(wsSession);
+ }
+
+
+ private void unregisterAuthenticatedSession(WsSession wsSession,
+ String httpSessionId) {
+ Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId);
+ wsSessions.remove(wsSession);
+ }
+
+
+ public void closeAuthenticatedSession(String httpSessionId) {
+ Set<WsSession> wsSessions = authenticatedSessions.remove(httpSessionId);
+
+ if (wsSessions != null && !wsSessions.isEmpty()) {
+ for (WsSession wsSession : wsSessions) {
+ try {
+ wsSession.close(AUTHENTICATED_HTTP_SESSION_CLOSED);
+ } catch (IOException e) {
+ // Any IOExceptions during close will have been caught and the
+ // onError method called.
+ }
+ }
+ }
+ }
+
+ private static void validateEncoders(Class<? extends Encoder>[] encoders)
+ throws DeploymentException {
+
+ for (Class<? extends Encoder> encoder : encoders) {
+ // Need to instantiate decoder to ensure it is valid and that
+ // deployment can be failed if it is not
+ @SuppressWarnings("unused")
+ Encoder instance;
+ try {
+ encoder.newInstance();
+ } catch(InstantiationException e) {
+ throw new DeploymentException(MESSAGES.cannotInstatiateEncoder(encoder.getName()), e);
+ } catch (IllegalAccessException e) {
+ throw new DeploymentException(MESSAGES.cannotInstatiateEncoder(encoder.getName()), e);
+ }
+ }
+ }
+
+ private static class TemplatePathMatch {
+ private final ServerEndpointConfig config;
+ private final UriTemplate uriTemplate;
+
+ public TemplatePathMatch(ServerEndpointConfig config,
+ UriTemplate uriTemplate) {
+ this.config = config;
+ this.uriTemplate = uriTemplate;
+ }
+
+
+ public ServerEndpointConfig getConfig() {
+ return config;
+ }
+
+
+ public UriTemplate getUriTemplate() {
+ return uriTemplate;
+ }
+ }
+
+
+ /**
+ * This Comparator implementation is thread-safe so only create a single
+ * instance.
+ */
+ private static class TemplatePathMatchComparator
+ implements Comparator<TemplatePathMatch> {
+
+ private static final TemplatePathMatchComparator INSTANCE =
+ new TemplatePathMatchComparator();
+
+ public static TemplatePathMatchComparator getInstance() {
+ return INSTANCE;
+ }
+
+ private TemplatePathMatchComparator() {
+ // Hide default constructor
+ }
+
+ @Override
+ public int compare(TemplatePathMatch tpm1, TemplatePathMatch tpm2) {
+ return tpm1.getUriTemplate().getNormalizedPath().compareTo(
+ tpm2.getUriTemplate().getNormalizedPath());
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsSessionListener.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsSessionListener.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsSessionListener.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,42 @@
+/*
+ * 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.websocket.server;
+
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+public class WsSessionListener implements HttpSessionListener{
+
+ private final WsServerContainer wsServerContainer;
+
+
+ public WsSessionListener(WsServerContainer wsServerContainer) {
+ this.wsServerContainer = wsServerContainer;
+ }
+
+
+ @Override
+ public void sessionCreated(HttpSessionEvent se) {
+ // NO-OP
+ }
+
+
+ @Override
+ public void sessionDestroyed(HttpSessionEvent se) {
+ wsServerContainer.closeAuthenticatedSession(se.getSession().getId());
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsWriteTimeout.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsWriteTimeout.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/WsWriteTimeout.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,129 @@
+/*
+ * 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.websocket.server;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.tomcat.websocket.BackgroundProcess;
+import org.apache.tomcat.websocket.BackgroundProcessManager;
+
+/**
+ * Provides timeouts for asynchronous web socket writes. On the server side we
+ * only have access to {@link javax.servlet.ServletOutputStream} and
+ * {@link javax.servlet.ServletInputStream} so there is no way to set a timeout
+ * for writes to the client.
+ */
+public class WsWriteTimeout implements BackgroundProcess {
+
+ private final Set<WsRemoteEndpointImplServer> endpoints =
+ new ConcurrentSkipListSet<WsRemoteEndpointImplServer>(new EndpointComparator());
+ private final AtomicInteger count = new AtomicInteger(0);
+ private int backgroundProcessCount = 0;
+ private volatile int processPeriod = 1;
+
+ @Override
+ public void backgroundProcess() {
+ // This method gets called once a second.
+ backgroundProcessCount ++;
+
+ if (backgroundProcessCount >= processPeriod) {
+ backgroundProcessCount = 0;
+
+ long now = System.currentTimeMillis();
+ Iterator<WsRemoteEndpointImplServer> iter = endpoints.iterator();
+ while (iter.hasNext()) {
+ WsRemoteEndpointImplServer endpoint = iter.next();
+ if (endpoint.getTimeoutExpiry() < now) {
+ endpoint.onTimeout();
+ } else {
+ // Endpoints are ordered by timeout expiry so if this point
+ // is reached there is no need to check the remaining
+ // endpoints
+ break;
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public void setProcessPeriod(int period) {
+ this.processPeriod = period;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * The default value is 1 which means asynchronous write timeouts are
+ * processed every 1 second.
+ */
+ @Override
+ public int getProcessPeriod() {
+ return processPeriod;
+ }
+
+
+ public void register(WsRemoteEndpointImplServer endpoint) {
+ boolean result = endpoints.add(endpoint);
+ if (result) {
+ int newCount = count.incrementAndGet();
+ if (newCount == 1) {
+ BackgroundProcessManager.getInstance().register(this);
+ }
+ }
+ }
+
+
+ public void unregister(WsRemoteEndpointImplServer endpoint) {
+ boolean result = endpoints.remove(endpoint);
+ if (result) {
+ int newCount = count.decrementAndGet();
+ if (newCount == 0) {
+ BackgroundProcessManager.getInstance().unregister(this);
+ }
+ }
+ }
+
+
+ /**
+ * Note: this comparator imposes orderings that are inconsistent with equals
+ */
+ private static class EndpointComparator implements
+ Comparator<WsRemoteEndpointImplServer> {
+
+ @Override
+ public int compare(WsRemoteEndpointImplServer o1,
+ WsRemoteEndpointImplServer o2) {
+
+ long t1 = o1.getTimeoutExpiry();
+ long t2 = o2.getTimeoutExpiry();
+
+ if (t1 < t2) {
+ return -1;
+ } else if (t1 == t2) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+ }
+}
Added: branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/package-info.java
===================================================================
--- branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/package-info.java (rev 0)
+++ branches/7.4.x/src/main/java/org/apache/tomcat/websocket/server/package-info.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+/**
+ * Server-side specific implementation classes. These are in a separate package
+ * to make packaging a pure client JAR simpler.
+ */
+package org.apache.tomcat.websocket.server;
\ No newline at end of file
Modified: branches/7.4.x/src/main/java/org/jboss/web/CatalinaMessages.java
===================================================================
--- branches/7.4.x/src/main/java/org/jboss/web/CatalinaMessages.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/jboss/web/CatalinaMessages.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -141,9 +141,6 @@
@Message(id = 34, value = "Cannot upgrade from HTTP/1.1 without IO events")
IllegalStateException cannotUpgradeWithoutEvents();
- @Message(id = 35, value = "Cannot upgrade from HTTP/1.1 is not using an HttpEventServlet")
- IllegalStateException cannotUpgradeWithoutEventServlet();
-
@Message(id = 36, value = "Cannot call sendFile() after the response has been committed")
IllegalStateException cannotSendFile();
@@ -1002,4 +999,25 @@
@Message(id = 369, value = "The WebSocket connection has been closed")
String outboundClosed();
+ @Message(id = 370, value = "Read listener already set")
+ IllegalStateException readListenerAlreadySet();
+
+ @Message(id = 371, value = "Write listener already set")
+ IllegalStateException writeListenerAlreadySet();
+
+ @Message(id = 372, value = "Upgrade error")
+ String upgradeError();
+
+ @Message(id = 373, value = "Upgrade destroy processing for servlet %s threw exception")
+ String upgradeHandlerDestroyError(String servletName);
+
+ @Message(id = 374, value = "IO listener processing for servlet %s threw exception")
+ String ioListenerError(String servletName);
+
+ @Message(id = 375, value = "Null read or write listener")
+ NullPointerException nullListener();
+
+ @Message(id = 376, value = "Cannot use a read or write listener without upgrading or starting async")
+ IllegalStateException cannotSetListenerWithoutUpgradeOrAsync();
+
}
Modified: branches/7.4.x/src/main/java/org/jboss/web/CoyoteMessages.java
===================================================================
--- branches/7.4.x/src/main/java/org/jboss/web/CoyoteMessages.java 2013-09-18 14:42:28 UTC (rev 2260)
+++ branches/7.4.x/src/main/java/org/jboss/web/CoyoteMessages.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -277,4 +277,7 @@
@Message(id = 2079, value = "Unexpected data read during handshake")
String sslHandshakeData();
+ @Message(id = 2080, value = "Thread [%s] stoppe to avoid potential leak")
+ String threadStopped(String threadName);
+
}
Added: branches/7.4.x/src/main/java/org/jboss/web/WebsocketsLogger.java
===================================================================
--- branches/7.4.x/src/main/java/org/jboss/web/WebsocketsLogger.java (rev 0)
+++ branches/7.4.x/src/main/java/org/jboss/web/WebsocketsLogger.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,101 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.jboss.web;
+
+import static org.jboss.logging.Logger.Level.DEBUG;
+import static org.jboss.logging.Logger.Level.ERROR;
+import static org.jboss.logging.Logger.Level.INFO;
+import static org.jboss.logging.Logger.Level.WARN;
+
+import org.jboss.logging.BasicLogger;
+import org.jboss.logging.Cause;
+import org.jboss.logging.LogMessage;
+import org.jboss.logging.Logger;
+import org.jboss.logging.Message;
+import org.jboss.logging.MessageLogger;
+
+/**
+ * Logging IDs 8800-9000
+ * @author Remy Maucherat
+ */
+@MessageLogger(projectCode = "JBWEB")
+public interface WebsocketsLogger extends BasicLogger {
+
+ /**
+ * A logger with the category of the package name.
+ */
+ WebsocketsLogger ROOT_LOGGER = Logger.getMessageLogger(WebsocketsLogger.class, "org.apache.tomcat.websocket");
+
+ @LogMessage(level = INFO)
+ @Message(id = 8800, value = "Failed to close channel cleanly")
+ void errorClose();
+
+ @LogMessage(level = ERROR)
+ @Message(id = 8801, value = "A background process failed")
+ void backgroundProcessFailed(@Cause Throwable t);
+
+ @LogMessage(level = WARN)
+ @Message(id = 8802, value = "Flushing batched messages before closing the session failed")
+ void flushOnCloseFailed(@Cause Throwable t);
+
+ @LogMessage(level = DEBUG)
+ @Message(id = 8803, value = "Failed to send close message to remote endpoint")
+ void closeMessageFail(@Cause Throwable t);
+
+ @LogMessage(level = WARN)
+ @Message(id = 8804, value = "Unable to parse HTTP header as no colon is present to delimit header name and header value in [%s]. The header has been skipped.")
+ void invalidHttpHeader(String header);
+
+ @LogMessage(level = DEBUG)
+ @Message(id = 8805, value = "Session with ID [%s] did not close cleanly")
+ void sessionCloseFailed(String id, @Cause Throwable t);
+
+ @LogMessage(level = ERROR)
+ @Message(id = 8806, value = "Failed to call onOpen method of POJO end point for POJO of type [%s]")
+ void onOpenFailed(String className, @Cause Throwable t);
+
+ @LogMessage(level = WARN)
+ @Message(id = 8807, value = "Failed to close WebSocket session during error handling")
+ void closeSessionFailed(@Cause Throwable t);
+
+ @LogMessage(level = ERROR)
+ @Message(id = 8808, value = "Failed to call onClose method of POJO end point for POJO of type [%s]")
+ void onCloseFailed(String className, @Cause Throwable t);
+
+ @LogMessage(level = ERROR)
+ @Message(id = 8809, value = "No error handling configured for [%s] and the following error occurred")
+ void noOnError(String className, @Cause Throwable t);
+
+ @LogMessage(level = ERROR)
+ @Message(id = 8810, value = "Failed to call onError method of POJO end point for POJO of type [%s]")
+ void onErrorFailed(String className, @Cause Throwable t);
+
+ @LogMessage(level = ERROR)
+ @Message(id = 8811, value = "Failed to close WebConnection while destroying the WebSocket HttpUpgradeHandler")
+ void destroyFailed(@Cause Throwable t);
+
+ @LogMessage(level = INFO)
+ @Message(id = 8812, value = "Failed to close the ServletOutputStream connection cleanly")
+ void closeFailed(@Cause Throwable t);
+
+ @LogMessage(level = INFO)
+ @Message(id = 8813, value = "WebSocket support is not available when running on Java 6")
+ void noWebsocketsSupport();
+
+}
Added: branches/7.4.x/src/main/java/org/jboss/web/WebsocketsMessages.java
===================================================================
--- branches/7.4.x/src/main/java/org/jboss/web/WebsocketsMessages.java (rev 0)
+++ branches/7.4.x/src/main/java/org/jboss/web/WebsocketsMessages.java 2013-09-20 16:27:05 UTC (rev 2261)
@@ -0,0 +1,296 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed 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.jboss.web;
+
+import org.jboss.logging.Cause;
+import org.jboss.logging.Message;
+import org.jboss.logging.MessageBundle;
+import org.jboss.logging.Messages;
+
+/**
+ * Logging IDs 8500-8850
+ * @author Remy Maucherat
+ */
+@MessageBundle(projectCode = "JBWEB")
+public interface WebsocketsMessages {
+
+ /**
+ * The messages
+ */
+ WebsocketsMessages MESSAGES = Messages.getBundle(WebsocketsMessages.class);
+
+ @Message(id = 8500, value = "Concurrent read operations are not permitted")
+ IllegalStateException invalidConcurrentRead();
+
+ @Message(id = 8501, value = "Concurrent write operations are not permitted")
+ IllegalStateException invalidConcurrentWrite();
+
+ @Message(id = 8502, value = "Unexpected Status of SSLEngineResult after a wrap() operation")
+ IllegalStateException unexpectedStatusAfterWrap();
+
+ @Message(id = 8503, value = "Flag that indicates a write is in progress was found to be false (it should have been true) when trying to complete a write operation")
+ IllegalStateException invalidWriteState();
+
+ @Message(id = 8504, value = "Unexpected end of stream")
+ String unexpectedEndOfStream();
+
+ @Message(id = 8505, value = "Flag that indicates a read is in progress was found to be false (it should have been true) when trying to complete a read operation")
+ IllegalStateException invalidReadState();
+
+ @Message(id = 8506, value = "Unexpected Status of SSLEngineResult after an unwrap() operation")
+ IllegalStateException unexpectedStatusAfterUnwrap();
+
+ @Message(id = 8507, value = "The result [%s] is too big to be expressed as an Integer")
+ String notAnInteger(long value);
+
+ @Message(id = 8508, value = "Unable to coerce value [%s] to type [%s]. That type is not supported.")
+ IllegalArgumentException invalidType(String value, String type);
+
+ @Message(id = 8509, value = "The specified decoder of type [%s] could not be instantiated")
+ String cannotInstatiateDecoder(String className);
+
+ @Message(id = 8510, value = "Unable to add the message handler [%s] as it was for the unrecognised type [%s]")
+ IllegalArgumentException unknownHandler(Object listener, Object target);
+
+ @Message(id = 8511, value = "The message handler provided does not have an onMessage(Object) method")
+ IllegalArgumentException invalidMessageHandler(@Cause Throwable t);
+
+ @Message(id = 8512, value = "The Decoder type [%s] is not recognized")
+ IllegalArgumentException unknownDecoderType(String className);
+
+ @Message(id = 8513, value = "New frame received after a close control frame")
+ String receivedFrameAfterClose();
+
+ @Message(id = 8514, value = "The client frame set the reserved bits to [%s] which was not supported by this endpoint")
+ String unsupportedReservedBitsSet(int bit);
+
+ @Message(id = 8515, value = "A fragmented control frame was received but control frames may not be fragmented")
+ String invalidFragmentedControlFrame();
+
+ @Message(id = 8516, value = "A WebSocket frame was sent with an unrecognised opCode of [%s]")
+ String invalidFrameOpcode(int code);
+
+ @Message(id = 8517, value = "A new message was started when a continuation frame was expected")
+ String noContinuationFrame();
+
+ @Message(id = 8518, value = "The client data can not be processed because the session has already been closed")
+ String sessionClosed();
+
+ @Message(id = 8519, value = "The client frame was not masked but all client frames must be masked")
+ String frameWithoutMask();
+
+ @Message(id = 8520, value = "A control frame was sent with a payload of size [%s] which is larger than the maximum permitted of 125 bytes")
+ String controlFramePayloadTooLarge(long size);
+
+ @Message(id = 8521, value = "A control frame was sent that did not have the fin bit set. Control frames are not permitted to use continuation frames.")
+ String controlFrameWithoutFin();
+
+ @Message(id = 8522, value = "The client sent a close frame with a single byte payload which is not valid")
+ String invalidOneByteClose();
+
+ @Message(id = 8523, value = "A WebSocket close frame was received with a close reason that contained invalid UTF-8 byte sequences")
+ String invalidUtf8Close();
+
+ @Message(id = 8524, value = "The message was [%s] bytes long but the MessageHandler has a limit of [%s] bytes")
+ String messageTooLarge(long size, long limit);
+
+ @Message(id = 8525, value = "A WebSocket text frame was received that could not be decoded to UTF-8 because it contained invalid byte sequences")
+ String invalidUtf8();
+
+ @Message(id = 8526, value = "The decoded text message was too big for the output buffer and the endpoint does not support partial messages")
+ String textMessageTooLarge();
+
+ @Message(id = 8527, value = "No async message support and buffer too small. Buffer size: [%s], Message size: [%s]")
+ String bufferTooSmall(int capacity, long payload);
+
+ @Message(id = 8528, value = "Too many bytes ([%s]) were provided to be converted into a long")
+ String invalidLong(long length);
+
+ @Message(id = 8529, value = "Message will not be sent because the WebSocket session is currently sending another message")
+ IllegalStateException messageInProgress();
+
+ @Message(id = 8530, value = "Message will not be sent because the WebSocket session has been closed")
+ IllegalStateException messageSessionClosed();
+
+ @Message(id = 8531, value = "When sending a fragmented message, all fragments bust be of the same type")
+ IllegalStateException messageFragmentTypeChange();
+
+ @Message(id = 8532, value = "No encoder specified for object of class [%s]")
+ String noEncoderForClass(String className);
+
+ @Message(id = 8533, value = "The specified encoder of type [%s] could not be instantiated")
+ String cannotInstatiateEncoder(String className);
+
+ @Message(id = 8534, value = "This method may not be called as the OutputStream has been closed")
+ IllegalStateException closedOutputStream();
+
+ @Message(id = 8535, value = "This method may not be called as the Writer has been closed")
+ IllegalStateException closedWriter();
+
+ @Message(id = 8536, value = "A text message handler has already been configured")
+ IllegalStateException duplicateHandlerText();
+
+ @Message(id = 8537, value = "A binary message handler has already been configured")
+ IllegalStateException duplicateHandlerBinary();
+
+ @Message(id = 8538, value = "A pong message handler has already been configured")
+ IllegalStateException duplicateHandlerPong();
+
+ @Message(id = 8539, value = "A pong message handler must implement MessageHandler.Basic")
+ IllegalStateException invalidHandlerPong();
+
+ @Message(id = 8540, value = "Unable to add the message handler [%s] as it was wrapped as the unrecognised type [%s]")
+ IllegalArgumentException invalidMessageHandler(Object listener, Object type);
+
+ @Message(id = 8541, value = "Unable to remove the handler [%s] as it was not registered with this session")
+ IllegalStateException cannotRemoveHandler(Object listener);
+
+ @Message(id = 8542, value = "Unable to write the complete message as the WebSocket connection has been closed")
+ String messageFailed();
+
+ @Message(id = 8543, value = "The WebSocket session timeout expired")
+ String sessionTimeout();
+
+ @Message(id = 8544, value = "The WebSocket session has been closed and no method (apart from close()) may be called on a closed session")
+ IllegalStateException sessionAlreadyClosed();
+
+ @Message(id = 8545, value = "Unable to create dedicated AsynchronousChannelGroup for WebSocket clients which is required to prevent memory leaks in complex class loader environments like J2EE containers")
+ IllegalStateException asyncGroupFail();
+
+ @Message(id = 8546, value = "Cannot use POJO class [%s] as it is not annotated with @ClientEndpoint")
+ String missingClientEndpointAnnotation(String className);
+
+ @Message(id = 8547, value = "Failed to create the default configurator")
+ String defaultConfiguratorFailed();
+
+ @Message(id = 8548, value = "Failed to create a local endpoint of type [%s]")
+ String endpointCreateFailed(String className);
+
+ @Message(id = 8549, value = "The scheme [%s] is not supported")
+ String pathWrongScheme(String scheme);
+
+ @Message(id = 8550, value = "No host was specified in URI")
+ String pathNoHost();
+
+ @Message(id = 8551, value = "The requested scheme, [%s], is not supported. The supported schemes are ws and wss")
+ String invalidScheme(String scheme);
+
+ @Message(id = 8552, value = "Unable to open a connection to the server")
+ String connectionFailed();
+
+ @Message(id = 8553, value = "The HTTP request to initiate the WebSocket connection failed")
+ String httpRequestFailed();
+
+ @Message(id = 8554, value = "Invalid websockets protocol header")
+ String invalidProtocolHeader();
+
+ @Message(id = 8555, value = "The HTTP response from the server [%s] did not permit the HTTP upgrade to WebSocket")
+ String invalidHttpStatus(String line);
+
+ @Message(id = 8556, value = "Unable to create SSLEngine to support SSL/TLS connections")
+ String sslEngineFail();
+
+ @Message(id = 8557, value = "The web application is stopping")
+ String webappStopping();
+
+ @Message(id = 8558, value = "Failed to create instance of POJO of type [%s]")
+ IllegalArgumentException pojoInstanceFailed(String className, @Cause Throwable t);
+
+ @Message(id = 8559, value = "IO error while decoding message")
+ String errorDecodingMessage();
+
+ @Message(id = 8560, value = "Duplicate annotations [%s] present on class [%s]")
+ String duplicateAnnotations(Class<?> annotation, Class<?> clazz);
+
+ @Message(id = 8561, value = "The annotated method [%s] is not public")
+ String methodNotPublic(String method);
+
+ @Message(id = 8562, value = "Parameters annotated with @PathParam may only be Strings, Java primitives or a boxed version thereof")
+ String invalidPathParamType();
+
+ @Message(id = 8563, value = "A parameter of type [%s] was found on method[%s] of class [%s] that did not have a @PathParam annotation")
+ String pathParamWithoutAnnotation(Class<?> clazz, String method, String className);
+
+ @Message(id = 8564, value = "No Throwable parameter was present on the method [%s] of class [%s] that was annotated with OnError")
+ String onErrorWithoutThrowable(String method, String className);
+
+ @Message(id = 8565, value = "Failed to decode path parameter value [%s] to expected type [%s]")
+ String errorDecodingPathParam(String value, Class<?> clazz);
+
+ @Message(id = 8566, value = "Multiple message parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException duplicateMessageParameter(String method, String className);
+
+ @Message(id = 8567, value = "Multiple boolean (last) parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException duplicateLastMessageParameter(String method, String className);
+
+ @Message(id = 8568, value = "Multiple session parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException duplicateSessionParameter(String method, String className);
+
+ @Message(id = 8569, value = "Multiple PongMessage parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException duplicatePongMessageParameter(String method, String className);
+
+ @Message(id = 8570, value = "Invalid PongMessgae and Message parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException invalidPongWithPayload(String method, String className);
+
+ @Message(id = 8571, value = "No payload parameter present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException missingPayload(String method, String className);
+
+ @Message(id = 8572, value = "Invalid PongMesssge and boolean parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException partialPong(String method, String className);
+
+ @Message(id = 8573, value = "Invalid Reader and boolean parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException partialReader(String method, String className);
+
+ @Message(id = 8574, value = "Invalid InputStream and boolean parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException partialInputStream(String method, String className);
+
+ @Message(id = 8575, value = "Invalid Object and boolean parameters present on the method [%s] of class [%s] that was annotated with OnMessage")
+ IllegalArgumentException partialObject(String method, String className);
+
+ @Message(id = 8576, value = "The path [%s] is not valid.")
+ String invalidPath(String path);
+
+ @Message(id = 8577, value = "The path [%s] contains one or more empty segments which are is not permitted")
+ IllegalArgumentException invalidEmptySegment(String path);
+
+ @Message(id = 8578, value = "The parameter [%s] appears more than once in the path which is not permitted")
+ IllegalArgumentException duplicateParameter(String path);
+
+ @Message(id = 8579, value = "The segment [%s] is not valid in the provided path [%s]")
+ IllegalArgumentException invalidPathSegment(String segment, String path);
+
+ @Message(id = 8580, value = "The preInit() method must be called to configure the WebSocket HttpUpgradeHandler before the container calls init(). Usually, this means the Servlet that created the WsHttpUpgradeHandler instance should also call preInit()")
+ IllegalStateException noPreInit();
+
+ @Message(id = 8581, value = "No further Endpoints may be registered once an attempt has been made to use one of the previously registered endpoints")
+ String addNotAllowed();
+
+ @Message(id = 8582, value = "No ServletContext was specified")
+ String missingServletContext();
+
+ @Message(id = 8583, value = "Multiple Endpoints may not be deployed to using the same path [%s]")
+ String duplicatePaths(String path);
+
+ @Message(id = 8584, value = "Cannot deploy POJO class [%s] as it is not annotated with @ServerEndpoint")
+ String cannotDeployPojo(String className);
+
+ @Message(id = 8585, value = "Failed to create configurator of type [%s] for POJO of type [%s]")
+ String configuratorFailed(String configurator, String className);
+
+}
11 years, 3 months
JBossWeb SVN: r2260 - in branches: 7.2.x/src/main/java/org/apache/catalina/websocket and 2 other directories.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-18 10:42:28 -0400 (Wed, 18 Sep 2013)
New Revision: 2260
Modified:
branches/7.2.x/pom.xml
branches/7.2.x/src/main/java/org/apache/catalina/websocket/WebSocketServlet.java
branches/7.3.x/pom.xml
branches/7.4.x/.classpath
branches/7.4.x/pom.xml
Log:
- Branch cleanup.
- Disable Tomcat websockets API in 7.2.
Modified: branches/7.2.x/pom.xml
===================================================================
--- branches/7.2.x/pom.xml 2013-09-17 09:44:30 UTC (rev 2259)
+++ branches/7.2.x/pom.xml 2013-09-18 14:42:28 UTC (rev 2260)
@@ -33,7 +33,7 @@
<groupId>org.jboss.web</groupId>
<artifactId>jbossweb</artifactId>
- <version>7.2.3.Final</version>
+ <version>7.2.4.Final</version>
<name>JBoss Web</name>
<description>Servlet 3.0 container</description>
Modified: branches/7.2.x/src/main/java/org/apache/catalina/websocket/WebSocketServlet.java
===================================================================
--- branches/7.2.x/src/main/java/org/apache/catalina/websocket/WebSocketServlet.java 2013-09-17 09:44:30 UTC (rev 2259)
+++ branches/7.2.x/src/main/java/org/apache/catalina/websocket/WebSocketServlet.java 2013-09-18 14:42:28 UTC (rev 2260)
@@ -45,8 +45,10 @@
* Provides the base implementation of a Servlet for processing WebSocket
* connections as per RFC6455. It is expected that applications will extend this
* implementation and provide application specific functionality.
+ *
+ * Note: In this branch, the API is disabled.
*/
-public abstract class WebSocketServlet extends HttpServlet implements HttpEventServlet {
+public final class WebSocketServlet extends HttpServlet implements HttpEventServlet {
private static final long serialVersionUID = 1L;
private static final byte[] WS_ACCEPT =
@@ -276,6 +278,8 @@
* method. If Tomcat detects such access, it will throw
* an IllegalStateException
*/
- protected abstract StreamInbound createWebSocketInbound(String subProtocol,
- HttpServletRequest request);
+ protected /*abstract*/ StreamInbound createWebSocketInbound(String subProtocol,
+ HttpServletRequest request) {
+ return null;
+ }
}
Modified: branches/7.3.x/pom.xml
===================================================================
--- branches/7.3.x/pom.xml 2013-09-17 09:44:30 UTC (rev 2259)
+++ branches/7.3.x/pom.xml 2013-09-18 14:42:28 UTC (rev 2260)
@@ -33,7 +33,7 @@
<groupId>org.jboss.web</groupId>
<artifactId>jbossweb</artifactId>
- <version>7.2.3.Final</version>
+ <version>7.3.0.Final</version>
<name>JBoss Web</name>
<description>Servlet 3.0 container</description>
Modified: branches/7.4.x/.classpath
===================================================================
--- branches/7.4.x/.classpath 2013-09-17 09:44:30 UTC (rev 2259)
+++ branches/7.4.x/.classpath 2013-09-18 14:42:28 UTC (rev 2260)
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="lib" path="/home/remm/.m2/repository/org/jboss/logging/jboss-logging/3.1.1.GA/jboss-logging-3.1.1.GA.jar"/>
<classpathentry kind="lib" path="/home/remm/.m2/repository/org/jboss/spec/javax/servlet/jboss-servlet-api_3.0_spec/1.0.1.Final/jboss-servlet-api_3.0_spec-1.0.1.Final.jar"/>
<classpathentry kind="lib" path="/home/remm/.m2/repository/org/jboss/spec/javax/servlet/jsp/jboss-jsp-api_2.2_spec/1.0.1.Final/jboss-jsp-api_2.2_spec-1.0.1.Final.jar"/>
<classpathentry kind="lib" path="/home/remm/.m2/repository/org/jboss/spec/javax/el/jboss-el-api_2.2_spec/1.0.1.Final/jboss-el-api_2.2_spec-1.0.1.Final.jar"/>
<classpathentry kind="lib" path="/home/remm/.m2/repository/org/jboss/web/jasper-jdt/7.0.3.Final/jasper-jdt-7.0.3.Final.jar"/>
+ <classpathentry kind="lib" path="/home/remm/.m2/repository/org/jboss/spec/javax/websocket/jboss-websocket-api_1.0_spec/1.0.0.Beta1/jboss-websocket-api_1.0_spec-1.0.0.Beta1.jar"/>
<classpathentry kind="output" path=".settings/output"/>
</classpath>
Modified: branches/7.4.x/pom.xml
===================================================================
--- branches/7.4.x/pom.xml 2013-09-17 09:44:30 UTC (rev 2259)
+++ branches/7.4.x/pom.xml 2013-09-18 14:42:28 UTC (rev 2260)
@@ -45,6 +45,7 @@
<version.org.jboss.spec.javax.el>1.0.1.Final</version.org.jboss.spec.javax.el>
<version.org.jboss.spec.javax.servlet>1.0.1.Final</version.org.jboss.spec.javax.servlet>
<version.org.jboss.spec.javax.servlet.jsp>1.0.1.Final</version.org.jboss.spec.javax.servlet.jsp>
+ <version.org.jboss.spec.javax.websocket>1.0.0.Beta1</version.org.jboss.spec.javax.websocket>
<version.org.eclipse.jdt.core.compiler.ecj>3.7.2</version.org.eclipse.jdt.core.compiler.ecj>
<version.org.jboss.xnio>3.0.4.GA</version.org.jboss.xnio>
<!-- Build configuration -->
@@ -156,6 +157,13 @@
</dependency>
<dependency>
+ <groupId>org.jboss.spec.javax.websocket</groupId>
+ <artifactId>jboss-websocket-api_1.0_spec</artifactId>
+ <version>${version.org.jboss.spec.javax.websocket}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>${version.org.eclipse.jdt.core.compiler.ecj}</version>
11 years, 3 months
JBossWeb SVN: r2259 - branches/7.4.x.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-17 05:44:30 -0400 (Tue, 17 Sep 2013)
New Revision: 2259
Modified:
branches/7.4.x/.project
branches/7.4.x/pom.xml
Log:
Update version numbers.
Modified: branches/7.4.x/.project
===================================================================
--- branches/7.4.x/.project 2013-09-17 09:41:04 UTC (rev 2258)
+++ branches/7.4.x/.project 2013-09-17 09:44:30 UTC (rev 2259)
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
- <name>jbossweb-3.0.x</name>
+ <name>jbossweb-7.4.x</name>
<comment></comment>
<projects>
</projects>
Modified: branches/7.4.x/pom.xml
===================================================================
--- branches/7.4.x/pom.xml 2013-09-17 09:41:04 UTC (rev 2258)
+++ branches/7.4.x/pom.xml 2013-09-17 09:44:30 UTC (rev 2259)
@@ -33,7 +33,7 @@
<groupId>org.jboss.web</groupId>
<artifactId>jbossweb</artifactId>
- <version>7.2.3.Final</version>
+ <version>7.4.0.Final</version>
<name>JBoss Web</name>
<description>Servlet 3.0 container</description>
11 years, 3 months
JBossWeb SVN: r2258 - branches.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-17 05:41:04 -0400 (Tue, 17 Sep 2013)
New Revision: 2258
Added:
branches/7.4.x/
Log:
For EAP 6.3.
11 years, 3 months
JBossWeb SVN: r2257 - branches.
by jbossweb-commits@lists.jboss.org
Author: remy.maucherat(a)jboss.com
Date: 2013-09-17 05:24:08 -0400 (Tue, 17 Sep 2013)
New Revision: 2257
Added:
branches/7.3.x/
Log:
For EAP 6.2.
11 years, 3 months
JBossWeb SVN: r2256 - branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp.
by jbossweb-commits@lists.jboss.org
Author: tfonteyn
Date: 2013-09-06 06:40:39 -0400 (Fri, 06 Sep 2013)
New Revision: 2256
Modified:
branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/AjpAprProtocol.java
branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/AjpProtocol.java
branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/Constants.java
Log:
[bz-1005130] Allow configuration of the requiredSecret attribute for the AJP connector
Modified: branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/AjpAprProtocol.java
===================================================================
--- branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/AjpAprProtocol.java 2013-09-06 09:47:21 UTC (rev 2255)
+++ branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/AjpAprProtocol.java 2013-09-06 10:40:39 UTC (rev 2256)
@@ -324,7 +324,7 @@
/**
* Required secret.
*/
- protected String requiredSecret = null;
+ protected String requiredSecret = Constants.DEFAULT_REQUIRED_SECRET;
public void setRequiredSecret(String requiredSecret) { this.requiredSecret = requiredSecret; }
/**
Modified: branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/AjpProtocol.java
===================================================================
--- branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/AjpProtocol.java 2013-09-06 09:47:21 UTC (rev 2255)
+++ branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/AjpProtocol.java 2013-09-06 10:40:39 UTC (rev 2256)
@@ -299,7 +299,7 @@
/**
* Required secret.
*/
- protected String requiredSecret = null;
+ protected String requiredSecret = Constants.DEFAULT_REQUIRED_SECRET;
public void setRequiredSecret(String requiredSecret) { this.requiredSecret = requiredSecret; }
/**
Modified: branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/Constants.java
===================================================================
--- branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/Constants.java 2013-09-06 09:47:21 UTC (rev 2255)
+++ branches/JBOSSWEB_7_2_2_FINAL_BZ-1005130/src/main/java/org/apache/coyote/ajp/Constants.java 2013-09-06 10:40:39 UTC (rev 2256)
@@ -46,6 +46,8 @@
public static final boolean DEFAULT_TCP_NO_DELAY = true;
public static final boolean DEFAULT_TOMCAT_AUTHENTICATION =
Boolean.valueOf(System.getProperty("org.apache.coyote.ajp.DEFAULT_TOMCAT_AUTHENTICATION", "true")).booleanValue();
+ public static final String DEFAULT_REQUIRED_SECRET =
+ System.getProperty("org.apache.coyote.ajp.DEFAULT_REQUIRED_SECRET");
// Prefix codes for message types from server to container
public static final byte JK_AJP13_FORWARD_REQUEST = 2;
11 years, 4 months